云原生技术公开课--基础
内容大纲
云原生
相关阅读:https://www.cnblogs.com/zwtblog/tag/云原生/
云原生技术发展简史
- 2004 年— 2007 年,Google 已在内部大规模地使用容器技术;
- 2008 年,Google 将 Cgroups 合并进入了 Linux 内核主干;
- 2013 年,Docker 项目正式发布。
- 2014 年,Kubernetes 项目也正式发布。
- 2015 年, CNCF 云原生基金会成立。
- …………
云原生的定义
究其本质,凡是能够提高云上资源利用率和应用交付效率的行为或方式都是云原生的。
技术范畴
- 第一部分是云应用定义与开发流程。
- 这包括应用定义与镜像制作、配置 CI/CD、消息和 Streaming 以及数据库等。
- 第二部分是云应用的编排与管理流程。
- 包括了应用编排与调度、服务发现治理、远程调用、API 网关以及 Service Mesh。
- 第三部分是监控与可观测性。
- 这部分所强调的是云上应用如何进行监控、日志收集、Tracing 以及在云上如何实现破坏性测试。
- 第四部分就是云原生的底层技术。
- 比如容器运行时、云原生存储技术、云原生网络技术等。
- 第五部分是云原生工具集。
- 比如流程自动化与配置管理、容器镜像仓库、云原生安全技术以及云端密码管理等。
- 最后则是 Serverless。
- 指构建和运行不需要服务器管理的应用程序的概念。
关键点
- 如何构建自包含、可定制的应用镜像。
- 能不能实现应用快速部署与隔离能力。
- 应用基础设施创建和销毁的自动化管理。
- 可复制的管控系统和支撑组件。
容器
说容器前 回顾 操作系统 是如何管理进程的。
通过 ps 等操作看到各式各样的进程:
- 第一,这些进程可以相互看到、相互通信。
- 第二,它们使用的是同一个文件系统,可以对同一个文件进行读写操作。
- 第三,这些进程会使用相同的系统资源。
带来的问题:
- 高级权限的进程可以攻击其他进程。
- 因为它们使用的是同一个文件系统,具有高级权限的进程可能会将其他进程的数据删除掉,破坏掉其他进程的正常运行。
- 存在资源抢占的问题。
解决( 为进程提供一个独立的运行环境 ):
-
Linux 和 Unix 操作系统可以通过 chroot 系统调用将子目录变成根目录,达到视图级别的隔离。
-
因为进程之间相互可见并且可以相互通信,使用 Namespace 技术来实现进程在资源的视图上进行隔离。
- 在 chroot 和 Namespace 的帮助下,进程就能够运行在一个独立的环境下了;
-
通过 Cgroup 来限制其资源使用率,设置其能够使用的 CPU 以及内存量。
容器就是一个视图隔离、资源可限制、独立文件系统的进程集合。
容器具有一个独立的文件系统。
因为使用的是系统的资源,所以在独立的文件系统内不需要具备内核相关的代码或者工具。
只需要提供容器所需的二进制文件、配置文件以及依赖即可。
只要容器运行时所需的文件集合都能够具备,那么这个容器就能够运行起来。
容器的生命周期
容器是一组具有隔离特性的进程集合, initial 进程启动的时候,容器也会随之启动。
因此,可以认为容器的生命周期和 initial 进程的生命周期是一致的。
initial 进程本身也可以产生其他的子进程或者通过 docker exec 产生出来的运维操作,也属于 initial 进程管理的范围内。
当 initial 进程退出的时候,所有的子进程也会随之退出,这样也是为了防止资源的泄漏。
问题:
应用里面的程序往往是有状态的,可能会产生一些重要的数据,当一个容器退出被删除之后,数据也就会丢失了。
需要将容器所产生出来的重要数据持久化下来。
容器能够直接将数据持久化到指定的目录上,这个目录就称之为数据卷。
数据卷有一些特点:
其中非常明显的就是数据卷的生命周期是独立于容器的生命周期的。
它是一个特殊的目录,是用于帮助容器进行持久化的。
数据卷管理主要有两种方式:
- 第一种是通过 bind 的方式,直接将宿主机的目录直接挂载到容器内。
- 第二种是将目录管理交给运行引擎。
容器项目架构
moby 是目前最流行的容器管理引擎。
moby daemon 所依赖的最重要的组件就是 containerd,containerd 是一个容器运行时管理引擎,
其独立于 moby daemon ,可以对上提供容器、镜像的相关管理。
containerd 底层有 containerd shim 模块,其类似于一个守护进程,这样设计的原因有几点:
-
首先,containerd 需要管理容器生命周期。
- 而容器可能是由不同的容器运行时所创建出来的,因此需要提供一个灵活的插件化管理。而 shim 就是针对于不同的容器运行时所开发的,这样就能够从 containerd 中脱离出来,通过插件的形式进行管理。
-
其次,因为 shim 插件化的实现,使其能够被 containerd 动态接管。
-
最后,因为随时可能会对 moby 或者 containerd 进行升级,如果不提供 shim 机制,那么就无法做到原地升级,也无法做到不影响业务的升级,因此 containerd shim 非常重要,它实现了动态接管的能力。
容器 VS VM
VM 利用 Hypervisor 虚拟化技术来模拟 CPU、内存等硬件资源,这样就可以在宿主机上建立一个 Guest OS。
每一个 Guest OS 都有一个独立的内核,每个应用都是相互独立的,VM 可以提供一个更好的隔离效果。
但这样的隔离效果需要付出一定的代价,因为需要把一部分的计算资源交给虚拟化,这样就很难充分利用现有的计算资源,并且每个 Guest OS 都需要占用大量的磁盘空间。
容器是针对于进程而言的,只需要一个独立的文件系统提供其所需要文件集合即可。
所有的文件隔离都是进程级别的,因此启动时间快于 VM,并且所需的磁盘空间也小于 VM。
进程级别的隔离并没有想象中的那么好,隔离效果相比 VM 要差很多。
镜像
将这些容器运行时所需要的所有的文件集合称之为容器镜像。
通常情况下,采用 Dockerfile 来构建镜像。
每个构建步骤都会对已有的文件系统进行操作,这样就会带来文件系统内容的变化,这些变化称之为changeset。
把构建步骤所产生的变化依次作用到一个空文件夹上,就能够得到一个完整的镜像。
changeset 的好处:
-
第一,能够提高分发效率。
- 对于大的镜像将其拆分成各个小块就能够提高镜像的分发效率,这是因为镜像拆分之后就可以并行下载这些数据。
-
第二,因为这些数据是相互共享的,也就意味着当本地存储上包含了一些数据的时候,只需要下载本地没有的数据即可。
-
第三,因为镜像数据是共享的,因此可以节约大量的磁盘空间。
Kubernetes
Kubernetes 是一个自动化的容器编排平台,它负责应用的部署、应用的弹性以及应用的管理。
核心功能:
-
服务的发现与负载的均衡。
-
容器的自动装箱,我们也会把它叫做 scheduling,就是“调度”。
- 把一个容器放到一个集群的某一个机器上,Kubernetes 会帮助我们去做存储的编排,让存储的声明周期与容器的生命周期能有一个连接。
-
Kubernetes 会帮助我们去做自动化的容器的恢复。
-
Kubernetes 会帮助我们去做应用的自动发布与应用的回滚,以及与应用相关的配置密文的管理。
-
对于 job 类型任务,Kubernetes 可以去做批量的执行。
-
为了让这个集群、这个应用更富有弹性,Kubernetes 也支持水平的伸缩。
Kubernetes 的架构
典型的二层架构和 server-client 架构。Master 作为中央的管控节点,会去与 Node 进行一个连接。
Kubernetes 的 Master 包含四个主要的组件:API Server、Controller、Scheduler 以及 etcd。
-
API Server:顾名思义是用来处理 API 操作的,Kubernetes 中所有的组件都会和 API Server 进行连接。
-
Controller:是控制器,它用来完成对集群状态的一些管理。
-
Scheduler:是调度器,完成调度的操作。
-
etcd:是一个分布式的一个存储系统,API Server 中所需要的这些原信息都被放置在 etcd 中。
- etcd 本身是一个高可用系统,通过 etcd 保证整个 Kubernetes 的 Master 组件的高可用性。
etcd 相关阅读: 可靠的分布式KV存储产品-ETCD-初见
Node
Kubernetes 的 Node 是真正运行业务负载的,每个业务负载会以 Pod 的形式运行。
一个 Pod 中运行的一个或者多个容器,真正去运行这些 Pod 的组件的是叫做 kubelet,它通过 API Server 接收到所需要 Pod 运行的状态,然后提交到 Container Runtime 组件中。
Kubernetes 的核心概念与它的 API
Pod
Pod 是 Kubernetes 的一个最小调度以及资源单元。
用户可以通过 Kubernetes 的 Pod API 生产一个 Pod,让 Kubernetes 对这个 Pod 进行调度。
一个 Pod 简单来说是对一组容器的抽象,它里面会包含一个或多个容器。
Volume
Volume 就是卷的概念,它是用来管理 Kubernetes 存储的,是用来声明在 Pod 中的容器可以访问文件目录的,一个卷可以被挂载在 Pod 中一个或者多个容器的指定路径下面。
Deployment
Deployment 是在 Pod 这个抽象上更为上层的一个抽象。
它可以定义一组 Pod 的副本数目、以及这个 Pod 的版本。一般大家用 Deployment 这个抽象来做应用的真正的管理,而 Pod 是组成 Deployment 最小的单元。
Service
Service 提供了一个或者多个 Pod 实例的稳定访问地址。
Namespace
Namespace 是用来做一个集群内部的逻辑隔离的,它包括鉴权、资源管理等。
Kubernetes 的每个资源,比如刚才讲的 Pod、Deployment、Service 都属于一个 Namespace,同一个 Namespace 中的资源需要命名的唯一性,不同的 Namespace 中的资源可以重名。
API
Kubernetes API 是由 HTTP+JSON 组成的:用户访问的方式是 HTTP,
访问的 API 中 content 的内容是 JSON 格式的。
Kubernetes 的 kubectl 也就是 command tool,Kubernetes UI,或者有时候用 curl,直接与 Kubernetes 进行沟通,都是使用 HTTP + JSON 这种形式。
例子:
对于这个 Pod 类型的资源,它的 HTTP 访问的路径,就是 API。
然后是 apiVesion: V1,之后是相应的 Namespaces,以及 Pods 资源,最终是 Podname,也就是 Pod 的名字。
Pod和容器设计模式
Pod 实际上正是 kubernetes 项目为你抽象出来的一个可以类比为进程组的概念。
Pod 也是 Kubernetes 的原子调度单位。
例子:
假如一个 Helloworld 程序需要4个进程, Kubernetes 里它会把四个独立的进程分别用四个独立的容器启动起来,然后把它们定义在一个 Pod 里面。
所以当 Kubernetes 把 Helloworld 给拉起来的时候,实际上会看到四个容器,它们共享了某些资源。
这些资源都属于 Pod,所以Pod 在 Kubernetes 里面只有一个逻辑单位,真正起来在物理上存在的东西,就是四个容器。
为什么 Pod 必须是原子调度单位?
能不能通过调度把 Pod 这个事情给解决掉呢?为什么 Pod 必须是 Kubernetes 里面的原子调度单位?
通过一个例子来解释:
假如现在有两个容器,它们是紧密协作的,所以它们应该被部署在一个 Pod 里面。
- 第一个容器叫做 App,就是业务容器,它会写日志文件。
- App 容器需要 1G 内存。
- 第二个容器叫做 LogCollector,它会把刚刚 App 容器写的日志文件转发到后端的 ElasticSearch 中。
- LogCollector 需要 0.5G 内存。
前集群环境的可用内存是这样一个情况:Node_A:1.25G 内存,Node_B:2G 内存。
如果没有 Pod 概念,就只有两个容器,这两个容器要紧密协作、运行在一台机器上。
如果调度器先把 App 调度到了 Node_A 上面,这时会发现:LogCollector 实际上是没办法调度到 Node_A 上的,因为资源不够。
此时整个应用本身就已经出问题了,调度已经失败了,必须去重新调度。
以上就是一个非常典型的成组调度失败的例子。
英文叫做:Task co-scheduling 问题,这个问题不是说不能解,在很多项目里面,这样的问题都有解法。
在 Kubernetes 里,就直接通过 Pod 这样一个概念去解决了。
因为在 Kubernetes 里,这样的一个 App 容器和 LogCollector 容器一定是属于一个 Pod 的,它们在调度时必然是以一个 Pod 为单位进行调度。
Pod 的实现机制
Pod 是一个逻辑概念。那在机器上,它究竟是怎么实现的呢?
Pod 要解决的问题: 如何让一个 Pod 里的多个容器之间最高效的共享某些资源和数据?
容器之间原本是被 Linux Namespace 和 cgroups 隔开的,所以现在实际要解决的是怎么去打破这个隔离,然后共享某些事情和某些信息。这就是 Pod 的设计要解决的核心问题所在。
解法分为两个部分:网络和存储。
1.共享网络
例子:
现在有一个 Pod,其中包含了一个容器 A 和一个容器 B,它们两个就要共享 Network Namespace。
在 Kubernetes 里的解法是这样的:它会在每个 Pod 里,额外起一个 Infra container 小容器来共享整个 Pod 的 Network Namespace。
Infra container 是一个非常小的镜像,大概 100~200KB 左右,是一个汇编语言写的、永远处于“暂停”状态的容器。
由于有了这样一个 Infra container 之后,其他所有容器都会通过 Join Namespace 的方式加入到 Infra container 的 Network Namespace 中。
一个 Pod 里面的所有容器,它们看到的网络视图是完全一样的。
即:它们看到的网络设备、IP地址、Mac地址等等,跟网络相关的信息,其实全是一份。
这一份都来自于 Pod 第一次创建的这个 Infra container。
这就是 Pod 解决网络共享的一个解法。
有一个相当于说中间的容器存在,所以整个 Pod 里面,必然是 Infra container 第一个启动。
并且整个 Pod 的生命周期是等同于 Infra container 的生命周期的,与容器 A 和 B 是无关的。
这也是为什么在 Kubernetes 里面,它是允许去单独更新 Pod 里的某一个镜像的,即:做这个操作,整个 Pod 不会重建,也不会重启。
2.共享存储
把 volume 变成了 Pod level。然后所有容器,就是所有同属于一个 Pod 的容器,共享所有的 volume。
详解容器设计模式
问题:现在要发布一个应用,这个应用是 JAVA 写的,有一个 WAR 包需要把它放到 Tomcat 的 web APP 目录下面。可是像这样一个 WAR 包或 Tomcat 这样一个容器的话,怎么去做,怎么去发布?
在 Kubernetes 里解决方案是 Init Container。
通过组合不同角色的容器,并且按照类似 Init Container 这样的编排方式,统一的去打包这样一个应用,把它用 Pod 来去做。
像这样的一个概念,在 Kubernetes 里面就是一个非常经典的容器设计模式,叫做:“Sidecar”。
这个 Init Container,它就是一个 Sidecar。
它只负责把镜像里的 WAR 包拷贝到共享目录里面,以便被 Tomcat 能够用起来。
这个 Pod 就是一个自包含的,可以把这一个 Pod 在全世界任何一个 Kubernetes 上面都顺利启用起来。
不用担心没有分布式存储、Volume 不是持久化的,它一定是可以公布的。
容器设计模式:Sidecar
Sidecar模式是一种将应用功能从应用本身剥离出来作为单独进程的方式。
该模式允许我们向应用无侵入添加多种功能,避免了为满足第三方组件需求而向应用添加额外的配置代码。
场景:
- 应用与日志收集
前面提到的应用日志收集,业务容器将日志写在一个 Volume 里面,而由于 Volume 在 Pod 里面是被共享的。
所以日志容器 —— 即 Sidecar 容器一定可以通过共享该 Volume,直接把日志文件读出来,然后存到远程存储里面。
- 代理容器
假如现在有个 Pod 需要访问一个外部系统,或者一些外部服务,但是这些外部系统是一个集群。
那么这个时候如何通过一个统一的、简单的方式,用一个 IP 地址,就把这些集群都访问到?
1、修改代码。因为代码里记录了这些集群的地址。
2、解耦的方法,即通过 Sidecar 代理容器。
单独写一个 Proxy,用来处理对接外部的服务集群,它对外暴露出来只有一个 IP 地址。
接下来,业务容器主要访问 Proxy,然后由 Proxy 去连接这些服务集群。
这里的关键在于 Pod 里面多个容器是通过 localhost 直接通信的,因为它们同属于一个 network Namespace,网络视图都一样,所以它们俩通信localhost,并没有性能损耗。
所以说代理容器除了做了解耦之外,并不会降低性能,并且提升了重用性。
- 适配器容器
例子:
业务容器暴露出来的监控接口是 /metrics,访问这个这个容器的 metrics 的这个 URL 就可以拿到了。
可是现在,这个监控系统升级了,它访问的 URL 是 /health,只认得暴露出 health 健康检查的 URL,才能去做监控,metrics 不认识。
可以不去改代码,而是额外写一个 Adapter,用来把所有对 health 的这个请求转发给 metrics 就可以了,所以这个 Adapter 对外暴露的是 health 这样一个监控的 URL。
这样的关键还在于 Pod 之中的容器是通过 localhost 直接通信的,所以没有性能损耗,并且这样一个 Adapter 容器可以被全公司重用起来。
核心原理
Kubernetes 的资源对象组成:
- Spec 部分用来描述期望的状态。
- Status 部分用来描述观测到的状态。
元数据部分
- labels
资源标签是一种具有标识型的 Key:Value 元数据,这里展示了几个常见的标签。
前三个标签都打在了 Pod 对象上,分别标识了对应的应用环境、发布的成熟度和应用的版本。
从应用标签的例子可以看到,标签的名字包括了一个域名的前缀,用来描述打标签的系统和工具。
最后一个标签打在 Node 对象上,还在域名前增加了版本的标识 beta 字符串。
签主要用来筛选资源和组合资源,可以使用类似于 SQL 查询 select,来根据 Label 查询相关的资源。
- Selector
例子:
假设系统中有四个 Pod,每个 Pod 都有标识系统层级和环境的标签。
- Annotations
一般是系统或者工具用来存储资源的非标示性信息,可以用来扩展资源的 spec/status 的描述。
例子:
第一个,存储了阿里云负载器的证书 ID,可以看到 annotations 一样可以拥有域名的前缀,标注中也可以包含版本信息。
第二个 annotation存储了 nginx 接入层的配置信息,可以看到 annotations 中包括“,”这样无法出现在 label 中的特殊字符。
第三个 annotations 一般可以在 kubectl apply 命令行操作后的资源中看到, annotation 值是一个结构化的数据,实际上是一个 json 串,标记了上一次 kubectl 操作的资源的 json 的描述。
- Ownereference
所有者,一般就是指集合类的资源。
集合类资源的控制器会创建对应的归属资源。
Ownereference 使得用户可以方便地查找一个创建资源的对象,另外,还可以用来实现级联删除的效果。
控制型模式
控制型模式最核心的就是控制循环的概念。
在控制循环中包括了控制器,被控制的系统,以及能够观测系统的传感器,三个逻辑组件。
待补充…………
两种 API 设计方法
待补充…………
应用编排与管理(Deployment)
Deployment 管理部署发布的控制器。
作用:
- 定义一组Pod的期望数量, controller会维持Pod数量与期望数量一致。
- 配置Pod发布方式,controller会按照给定策略更新Pod,保证更新过程中不可用的pod数量在限定范围内。
- 如果发布有问题,支持“一键“ 回滚
Job & CronJobs & DaemonSet
Job
为什么需要job?**
- 如何保证 Pod 内进程正确的结束?
- 如何保证进程运行失败后重试?
- 如何管理多个任务,且任务之间有依赖关系?
- 如何并行地运行任务,并管理任务的队列大小?
Job:管理任务的控制器:
- 可以创建一个或多个 Pod 来指定 Pod 的数量,并可以监控它是否成功地运行或终止。
- 可以根据 Pod 的状态来给 Job 设置重置的方式及重试的次数。
- 可以根据依赖关系,保证上一个任务运行完成之后再运行下一个任务。
- 同时还可以控制任务的并行度,根据并行度来确保 Pod 运行过程中的并行次数和总体完成大小。
Job 语法:
CronJob,其实也可以叫定时运行 Job 。
一的不同点就是它可以设计一个时间。
特别适合晚上做一些清理任务,还有可以几分钟执行一次,几小时执行一次等等,这就叫定时任务。
架构设计
Job 管理模式
Job Controller 其实还是主要去创建相对应的 pod。
然后 Job Controller 会去跟踪 Job 的状态,及时地根据提交的一些配置重试或者继续创建。
Job 控制器
所有的 job 都是一个 controller,它会 watch 这个 API Server,每次提交一个 Job 的 yaml 都会经过 api-server 传到 ETCD 里面去。
然后 Job Controller 会注册几个 Handler,每当有添加、更新、删除等操作的时候,它会通过一个内存级的消息队列,发到 controller 里面。
通过 Job Controller 检查当前是否有运行的 pod:
如果没有的话,通过 Scale up 把这个 pod 创建出来。
如果有的话,或者如果大于这个数,对它进行 Scale down。
如果这时 pod 发生了变化,需要及时 Update 它的状态。
同时要去检查它是否是并行的 job,或者是串行的 job,根据设置的配置并行度、串行度,及时地把 pod 的数量给创建出来。
最后,它会把 job 的整个的状态更新到 API Server 里面去。
DaemonSet
为什么要有DaemonSet?
- 如果希望每个节点都运行同样一个 pod 怎么办?
- 如果新节点加入集群的时候,想要立刻感知到它,然后去部署一个 pod,初始化一些东西,这个需求如何做?
- 如果有节点退出的时候,希望对应的 pod 会被删除掉,应该怎么操作?
- 如果 pod 状态异常的时候,需要及时地监控这个节点异常,然后做一些监控或者汇报的一些动作,那么这些东西运用什么控制器来做?
DaemonSet 也是 Kubernetes 提供的一个 default controller,它实际是做一个守护进程的控制器,它能帮我们做到以下几件事情:
- 首先能保证集群内的每一个节点都运行一组相同的 pod。
- 同时还能根据节点的状态保证新加入的节点自动创建对应的 pod。
- 在移除节点的时候,能删除对应的 pod。
- 而且它会跟踪每个 pod 的状态,当这个 pod 出现异常、Crash 掉了,会及时地去 recovery 这个状态。
应用配置管理
问题:
用一个容器镜像来启动一个 container。要启动这个容器,其实有很多需要配套的问题待解决:
- 第一,比如说一些可变的配置。不能把一些可变的配置写到镜像里面,当这个配置需要变化的时候,可能需要重新编译一次镜像,这个肯定是不能接受的。
- 第二就是一些敏感信息的存储和使用。比如说应用需要使用一些密码,或者用一些 token。
- 第三就是容器要访问集群自身。比如要访问 kube-apiserver,那么本身就有一个身份认证的问题。
- 第四就是容器在节点上运行之后,它的资源需求。
- 第五个就是容器在节点上,它们是共享内核的,那么它的一个安全管控怎么办?
- 最后容器启动之前的一个前置条件检验。比如说,一个容器启动之前,可能要确认一下 DNS 服务是不是好用?又或者确认一下网络是不是联通的?
解决:
- 可变配置就用 ConfigMap;
- 敏感信息是用 Secret;
- 身份认证是用 ServiceAccount 这几个独立的资源来实现的;
- 资源配置是用 Resources;
- 安全管控是用 SecurityContext;
- 前置校验是用 InitContainers 这几个在 spec 里面加的字段,来实现的这些配置管理。
ConfigMap
主要管理容器运行所需的配置文件,环境变量,命令行参数等可变配置。
用于解耦容器镜像和可变配置,从而保障工作负载(Pod)的可移植性。
这是 ConfigMap 本身的一个定义,它包括两个部分:
- 一个是 ConfigMap 元信息, name 和 namespace 这两个信息。
- 接下来 data 里面,可以看到它管理了两个配置文件。
- 结构:Map 就是 key:value,key 是一个文件名,value 是这个文件的内容。
Secret
Secret 是一个主要用来存储密码 token 等一些敏感信息的资源对象。
其中,敏感信息是采用 base-64 编码保存起来的,
类型:
第一种是 Opaque,它是普通的 Secret 文件;
第二种是 service-account-token,是用于 service-account 身份认证用的 Secret;
第三种是 dockerconfigjson,这是拉取私有仓库镜像的用的一种 Secret;
第四种是 bootstrap.token,是用于节点接入集群校验用的 Secret。
创建:
-
系统创建:比如 K8s 为每一个 namespace 的默认用户(default ServiceAccount)创建 Secret;
-
用户手动创建:手动创建命令,推荐 kubectl 这个命令行工具,它相对 ConfigMap 会多一个 type 参数。
使用:
ServiceAccount
ServiceAccount 首先是用于解决 pod 在集群里面的身份认证问题,身份认证信息是存在于 Secret 里面。
…………
Resource
目前内部支持类型有三种:CPU、内存,以及临时存储。
自定义配置时,指定的数量必须为整数。
目前资源配置主要分成 request 和 limit 两种类型:
- 一个是需要的数量。
- 一个是资源的界限。
- CPU、内存以及临时存储都是在 container 下的 Resource 字段里进行一个声明。
Pod 服务质量 (QoS) 配置
根据 CPU 对容器内存资源的需求,对 pod 的服务质量进行一个分类,分别是 Guaranteed、Burstable 和 BestEffort。
- Guaranteed :pod 里面每个容器都必须有内存和 CPU 的 request 以及 limit 的一个声明,且 request 和 limit 必须是一样的,这就是 Guaranteed;
- Burstable:Burstable 至少有一个容器存在内存和 CPU 的一个 request;
- BestEffort:只要不是 Guaranteed 和 Burstable,那就是 BestEffort。
那么这个服务质量是什么样的呢?
资源配置好后,当这个节点上 pod 容器运行,比如说节点上 memory 配额资源不足,kubelet会把一些低优先级的,或者说服务质量要求不高的(如:BestEffort、Burstable)pod 驱逐掉。
它们是按照先去除 BestEffort,再去除 Burstable 的一个顺序来驱逐 pod 的。
SecurityContext
主要是用于限制容器的一个行为,它能保证系统和其他容器的安全。
Kubernetes 和 runtime 通过用户的配置,最后下传到内核里,再通过内核的机制让 SecurityContext 来生效。
SecurityContext 主要分为三个级别:
- 第一个是容器级别,仅对容器生效;
- 第二个是 pod 级别,对 pod 里所有容器生效;
- 第三个是集群级别,就是 PSP,对集群内所有 pod 生效。
权限和访问控制设置项,现在一共列有七项(这个数量后续可能会变化):
- 第一个就是通过用户 ID 和组 ID 来控制文件访问权限;
- 第二个是 SELinux,它是通过策略配置来控制用户或者进程对文件的访问控制;
- 第三个是特权容器;
- 第四个是 Capabilities,它也是给特定进程来配置一个 privileged 能力;
- 第五个是 AppArmor,它也是通过一些配置文件来控制可执行文件的一个访问控制权限,比如说一些端口的读写;
- 第六个是一个对系统调用的控制;
- 第七个是对子进程能否获取比父亲更多的权限的一个限制。
InitContainer
主要为普通 container 服务 。
和普通 container 的区别,有以下三点内容:
- InitContainer 首先会比普通 container 先启动,并且直到所有的 InitContainer 执行成功后,普通 container 才会被启动;
- InitContainer 之间是按定义的次序去启动执行的,执行成功一个之后再执行第二个,而普通的 container 是并发启动的;
- InitContainer 执行成功后就结束退出,而普通容器可能会一直在执行。
应用存储与持久化数据卷
Volumes
Pod Volumes
因此 K8s 中又引入了 Persistent Volumes 概念。
它可以将存储和计算分离,通过不同的组件来管理存储资源和计算资源,然后解耦 pod 和 Volume 之间生命周期的关联。
这样,当把 pod 删除之后,它使用的PV仍然存在,还可以被新建的 pod 复用。
Persistent Volumes Claim
通过 PVC 和 PV 的概念,将用户需求和实现细节解耦开。
PV 对象有俩种 产生的方式:
- 第一种产生方式:静态产生方式 - 静态 Provisioning。
- 第二种访问方式:动态 Dynamic Provisioning。
- 集群管理员不预分配 PV,他写了一个模板文件。
架构设计
PV 和 PVC 的处理流程
csi 的全称是 container storage interface,它是K8s社区后面对存储插件实现(out of tree)的官方推荐方式。csi 的实现大体可以分为两部分:
- 第一部分是由k8s社区驱动实现的通用的部分,如图中的 csi-provisioner和 csi-attacher controller;
- 另外一种是由云存储厂商实践的,对接云存储厂商的 OpenApi,主要是实现真正的create/delete/mount/unmount 存储的相关操作,对应到上图中的csi-controller-server和csi-node-server。
PV、PVC 以及通过 csi 使用存储流程
存储快照和拓扑调度
在使用存储时,为了提高数据操作的容错性,我们通常有需要对线上数据进行snapshot,以及能快速restore的能力。
Snapshot
存储快照的设计其实是仿照 pvc & pv 体系的设计思想。
当用户需要存储快照的功能时,可以通过 VolumeSnapshot 对象来声明,并指定相应的 VolumeSnapshotClass 对象。
之后由集群中的相关组件动态生成存储快照以及存储快照对应的对象 VolumeSnapshotContent。
Topolopy
PV 在给 PVC 绑定或者动态生成 PV 的时候,并不知道后面将使用它的 pod 将调度在哪些 node 上。
但 PV 本身的使用,是对 pod 所在的 node 有拓扑位置的限制的。
在 K8s 中将 PV 和 PVC 的 binding 操作和动态创建 PV 的操作做了 delay,delay 到 pod 调度结果出来之后,再去做这两个操作。这样的话有什么好处?
- 首先,如果要是所要使用的 PV 是预分配的,如 Local PV,其实使用这块 PV 的 pod 它对应的 PVC 其实还没有做绑定,就可以通过调度器在调度的过程中,结合 pod 的计算资源需求(如 cpu/mem) 以及 pod 的 PVC 需求,选择的 node 既要满足计算资源的需求又要 pod 使用的 pvc 要能 binding 的 pv 的 nodeaffinity 限制;
- 其次对动态生成 PV 的场景其实就相当于是如果知道 pod 运行的 node 之后,就可以根据 node 上记录的拓扑信息来动态的创建这个 PV,也就是保证新创建出来的 PV 的拓扑位置与运行的 node 所在的拓扑位置是一致的。
为了实现上面所说的延迟绑定和延迟创建 PV,需要在 K8s 中的改动涉及到的相关组件有三个:
- PV Controller 也就是 persistent volume controller,它需要支持延迟 Binding 这个操作。
- 另一个是动态生成 PV 的组件,如果 pod 调度结果出来之后,它要根据 pod 的拓扑信息来去动态的创建 PV。
- 第三组件,也是最重要的一个改动点就是 kube-scheduler。
观测
kubernetes网络概念以及策略控制
问题:
- 外部世界和 service 之间是怎么通信的?
- service 如何与它后端的 pod 通讯?
- pod 和 pod 之间调用是怎么做到通信的?
- 最后就是 pod 内部容器与容器之间的通信?
Pod 与 Netns 的关系
每个 pod 都有着独立的网络空间,pod net container 会共享这个网络空间。
一般 K8s 会推荐选用 Loopback 接口,在 pod net container 之间进行通信,而所有的 container 通过 pod 的 IP 对外提供服务。
另外对于宿主机上的 Root Netns,可以把它看做一个特殊的网络空间,只不过它的 Pid 是1。
主流网络方案简介
Kubernetes Service
为什么需要服务发现?
在 K8s 集群里面应用是通过 pod 去部署的, 而 pod 生命周期是短暂的。
在 pod 的生命周期过程中,比如它创建或销毁,它的 IP 地址都会发生变化,这样就不能使用传统的部署方式,不能指定 IP 去访问指定的应用。
K8s 服务发现以及 K8s Service 是这样的整体的一个架构。
K8s 分为 master 节点和 worker 节点:
- master 里面主要是 K8s 管控的内容;
- worker 节点里面是实际跑用户应用的一个地方。
在 K8s master 节点里面有 APIServer,就是统一管理 K8s 所有对象的地方,所有的组件都会注册到 APIServer 上面去监听这个对象的变化。
这里面最关键的有三个组件:
- 一个是 Cloud Controller Manager。
- 负责去配置 LoadBalancer 的一个负载均衡器给外部去访问;
- 另外一个就是 Coredns,就是通过 Coredns 去观测 APIServer 里面的 service 后端 pod 的一个变化,去配置 service 的 DNS 解析。
- 实现可以通过 service 的名字直接访问到 service 的虚拟 IP,或者是 Headless 类型的 Service 中的 IP 列表的解析;
- 在每个 node 里面会有 kube-proxy 这个组件,它通过监听 service 以及 pod 变化,然后实际去配置集群里面的 node pod 或者是虚拟 IP 地址的一个访问。