文章目录

  • 一. 什么是pod
  • 如何解决超亲密关系
  • 详解容器设计模式
  • Init Container
  • 容器设计模式:Sidecar
  • Sidecar:代理容器
  • Sidecar:适配器容器
  • 二. 应用编排与管理:核心原理
  • 1 . 资源元信息 ,spec,lables,selector,Annotations,Ownereference
  • 2. 控制器模式
  • Pod的怎么创建逻辑流程是怎样的?


一. 什么是pod

Pod 是 Kubernetes 项目里面一个原子调度单位

例如:Kubernetes 把 它 类 比 为 一 个 操 作 系 统 , 比 如 说 Linux。针对于容器,可以类比为进程,就是 Linux 线 程。
由此而言: Pod 相当于进程组, 也就是 Linux 里的线程组

由于容器实际上是一个“单进程”模型,所以如果 你在容器里启动多个进程,只有一个可以作为 PID=1 的进程,而这时候, 如果这个 PID=1 的进程挂了,或者说失败退出了,那么其他三个进程就 会自然而然的成为孤儿,没有人能够管理它们,没有人能够回收它们的资 源,这是一个非常不好的情况。

注意:Linux 容器的“单进程”模型,指的是容器的生命周期等 同于 PID=1 的进程(容器应用进程)的生命周期,而不是 说容器里不能创建多进程。当然,一般情况下,容器应用进 程并不具备进程管理能力,所以你通过 exec 或者 ssh 在 容器里创建的其他进程,一旦异常退出(比如 ssh 终止)是 很容易变成孤儿进程的。

在 kubernetes 里面,Pod 实际上正是 kubernetes 项目为你抽象出来 的一个可以类比为进程组

真正起来在物理上存在的东西,就是四个容器。这四个容器,或者说是多 个容器的组合就叫做 Pod。并且还有一个概念一定要非常明确,Pod 是 Kubernetes 分配资源的一个单位,因为里面的容器要共享某些资源,所 以 Pod 也是 Kubernetes 的原子调度单位

为什么 Pod 必须是原子调度单位?

假如现在有两个容器,紧密协作的,所以它们应该被部署在一个 Pod 里面。具体来说,第一个容器叫做 App,就是业务容器,它会写日 志文件;第二个容器叫做 LogCollector,它会把刚刚 App 容器写的日志 文件转发到后端的 ElasticSearch 中。

两个容器的资源需求是这样的:App 容器需要 1G 内存,LogCollector 需要 0.5G 内存,而当前集群环境的可用内存是这样一个情况:Node_A: 1.25G 内存,Node_B:2G 内存

假如说现在没有 Pod 概念,就只有两个容器,这两个容器要紧密协作、 运行在一台机器上。可是,如果调度器先把 App 调度到了 Node_A 上 面,接下来会怎么样呢?这时你会发现:LogCollector 实际上是没办法调 度到 Node_A 上的,因为资源不够。其实此时整个应用本身就已经出问 题了,调度已经失败了,必须去重新调度。

什么叫做超亲密关系呢?
Pod,它们需要运行在同一台宿主机上,那这样就属 于亲密关系,调度器一定是可以帮助去做的。但是对于超亲密关系来说, 有一个问题,即它必须通过 Pod 来解决。因为如果超亲密关系赋予不了, 那么整个 Pod 或者说是整个应用都无法启动

  1. 比如说两个进程之间会发生文件交换,一 个写日志,一个读日志;
  2. 两个进程之间需要通过 localhost 或者说是本地的 Socket 去进行 通信,这种本地通信也是超亲密关系;
  3. 这两个容器或者是微服务之间,需要发生非常频繁的 RPC 调用,出 于性能的考虑,也希望它们是超亲密关系;
  4. 两个容器或者是应用,它们需要共享某些 Linux Namespace。最简单 常见的一个例子,就是我有一个容器需要加入另一个容器的 Network Namespace。这样我就能看到另一个容器的网络设备,和它的网络信 息

如何解决超亲密关系

容器之间是被 Linux Namespace 和 cgroups 隔开的,所以现 在实际要解决的是怎么去打破这个隔离,然后共享某些事情和某些信息。 这就是 Pod 的设计要解决的核心问题所在。

解法分为两个部分:网络和存储

  1. 共享网络 : Pod 里的多个容器怎么去共享网络
    比如说现在有一个 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 里 面 , 一 定 有 一 个 IP 地 址 , 是 这 个 Pod 的 Network Namespace 对应的地址,也是这个 Infra container 的 IP 地址。所以 大家看到的都是一份,而其他所有网络资源,都是一个 Pod 一份,并且 被 Pod 中的所有容器共享。这就是 Pod 的网络实现方式。
    由于需要有一个相当于说中间的容器存在,所以整个 Pod 里面,必然是 Infra container 第一个启动。并且整个 Pod 的生命周期是等同于 Infra container 的生命周期的,与容器 A 和 B 是无关的。这也是为什么在 Kubernetes 里面,它是允许去单独更新 Pod 里的某一个镜像的,即: 做这个操作,整个 Pod 不会重建,也不会重启,这是非常重要的一个设 计。
  2. 共享存储 :Pod 怎么去共享存储?
    比如说现在有两个容器,一个是 Nginx,另外一个是非常普通的容器,在 Nginx 里放一些文件,让我能通过 Nginx 访问到。所以它需要去 share 这个目录。我 share 文件或者是 share 目录在 Pod 里面是非常简单 的,实际上就是把 volume 变成了 Pod level。然后所有容器,就是所有 同属于一个 Pod 的容器,他们共享所有的 volume。

详解容器设计模式

k8s deployment没有pod生成 k8s pod创建流程_ide

  1. 第一种方式:可以把 WAR 包和 Tomcat 打包放进一个镜像里面。但 是这样带来一个问题,就是现在这个镜像实际上揉进了两个东西。那 么接下来,无论是我要更新 WAR 包还是说我要更新 Tomcat,都要 重新做一个新的镜像,这是比较麻烦的;
  2. 第二种方式:就是镜像里面只打包 Tomcat。它就是一个 Tomcat,但 是需要使用数据卷的方式,比如说 hostPath,从宿主机上把 WAR 包 挂载进我们 Tomcat 容器中,挂到我的 web APP 目录下面,这样把 这个容器启用起来之后,里面就能用了。
    问题
    例如:容器不在同一个宿主机启动
    使容器不管是在 A 还是在 B 上, 都可以找到这个 WAR 包,找到这个数据。
    需要一套分布式存储系统 且这个容器本身必须依赖于一套持久化的存储插件

这种方式叫做 Init Container

Init Container

k8s deployment没有pod生成 k8s pod创建流程_docker_02


首先定义一个 Init Container, 它只做一件事情,就是把 WAR 包从镜像里拷贝到一个 Volume 里面, 它做完这个操作就退出了,所以 Init Container 会比用户容器先启动,并 且严格按照定义顺序来依次执行。

APP 目录,实际 上是一个 Volume。而我们前面提到,一个 Pod 里面的多个容器,它们 是可以共享 Volume 的,所以现在这个 Tomcat 容器,只是打包了一个 Tomcat 镜 像 。 但 在 启 动 的 时 候 , 要 声 明 使 用 APP 目 录 作 为 我 的 Volume,并且要把它们挂载在 Web APP 目录下面。

而这个时候,由于前面已经运行过了一个 Init Container,已经执行完拷 贝操作了,所以这个 Volume 里面已经存在了应用的 WAR 包:就是 sample.war,绝对已经存在这个 Volume 里面了。等到第二步执行启动 这个 Tomcat 容器的时候,去挂这个 Volume,一定能在里面找到前面 拷贝来的 sample.war。
这个 Pod 就是一个自包含的可以把这一个 Pod 在全世界任何一个 Kubernetes 上面都顺利启用起来。不用担心没有分布 式存储、Volume 不是持久化的,它一定是可以公布的

所以这是一个通过组合两个不同角色的容器,并且按照这样一些像 Init Container 这样一种编排方式,统一的去打包这样一个应用,把它用 Pod 来去做的非常典型的一个例子。像这样的一个概念,在 Kubernetes 里面 就是一个非常经典的容器设计模式,叫做:“Sidecar”。

容器设计模式:Sidecar

什么是 Sidecar?就是说其实在 Pod 里面,可以定义一些专门的容器, 来执行主业务容器所需要的一些辅助工作,比如我们前面举的例子,其实 就干了一个事儿,这个 Init Container,它就是一个 Sidecar,它只负责 把镜像里的 WAR 包拷贝到共享目录里面,以便被 Tomcat 能够用起 来。

例如

  1. 原本需要在容器里面执行 SSH 需要干的一些事情,可以写脚本、一 些 前 置 的 条 件 , 其 实 都 可 以 通 过 像 Init Container 或 者 另 外 像 Sidecar 的方式去解决
  2. 日志收集,日志收集本身是一个进程, 是一个小容器,那么就可以把它打包进 Pod 里面去做这个收集工作;
  3. Debug 应用,实际上现在 Debug 整 个 应 用 都 可 以 在 应 用 Pod 里 面 再 次 定 义 一 个 额 外 的 小 的 Container,它可以去 exec 应用 pod 的 namespace;
  4. 监控其他容器的工作状态,这也是它可以做的事情。不再需要去 SSH 登陆到容器里去看,只要把监控组件装到额外的小容器里面就可以了, 然后把它作为一个 Sidecar 启动起来,跟主业务容器进行协作,所以 同样业务监控也都可以通过 Sidecar 方式来去做。

Sidecar:代理容器

第二种用法Sidecar:代理容器

假如有个 Pod 需要访问一个外部系统,或者一些外部服务,但是这 些外部系统是一个集群,那么这个时候如何通过一个统一的、简单的方式, 用一个 IP 地址,就把这些集群都访问到?

方法一:修改代码。 因为代码里记录了这些集群的地址;

方法二:一种解耦法,即通过 Sidecar 代理容器。Proxy也就是说对外暴露一个ip

k8s deployment没有pod生成 k8s pod创建流程_linux_03

Sidecar:适配器容器

第三种用法 适配器容器 Adapter

现在业务暴露出来的 API,比如说有个 API 的一个格式是 API A,但是现在 有一个外部系统要去访问我的业务容器,它只知道的一种格式是 API B , 所以要做一个工作,就是把业务容器怎么想办法改掉,要去改业务代码。 但实际上,你可以通过一个 Adapter 帮你来做这层转换。

k8s deployment没有pod生成 k8s pod创建流程_docker_04


的关键还在于 Pod 之中的容器是通过 localhost 直接通信

二. 应用编排与管理:核心原理

1 . 资源元信息 ,spec,lables,selector,Annotations,Ownereference

  1. Kubernetes 的资源对象组成:主要包括了 Spec、 Status 两部分。
    Spec 部分用来描述期望的状态,
    Status 部分用来 描述观测到的状态。

另一个部分元数据部分。该部分主 要 包 括 了 用 来 识 别 资 源 的 标 签 : Labels , 用 来 描 述 资 源 的 注 解 ; Annotations, 用来描述多个资源之间相互关系的 OwnerReference。这 些元数据在 K8s 运行中有非常重要的作用。

  1. labels 第一个元数据,也是最重要的一个元数据是:资源标签。资源标签是一种 具有标识型的 Key:Value 元数据,这里展示了几个常见的标签。
    前三个标签都打在了 Pod 对象上,分别标识了对应的应用环境、发布的 成熟度和应用的版本。从应用标签的例子可以看到,标签的名字包括了一 个域名的前缀,用来描述打标签的系统和工具, 最后一个标签打在 Node 对象上,还在域名前增加了版本的标识 beta 字符串。
    标签主要用来筛选资源和组合资源,可以使用类似于 SQL 查询 select, 来根据 Label 查询相关的资源。
  2. Selector
    例子: 假设系统中有四个 Pod,每个 Pod 都有标识系统层级和环境的标签,我 们通过 Tie:front 这个标签,可以匹配左边栏的 Pod,相等型 Selector 还可以包括多个相等条件,多个相等条件之间是逻辑”与“的关系。
    通过 Tie=front,Env=dev 的 Selector,我们可以筛选 出所有 Tie=front,而且 Env=dev 的 Pod,
    另外一种 Selector 是集合型 Selector,Selector 筛选所有 环境是 test 或者 gray 的 Pod。
    除了 in 的集合操作外,还有 notin 集合操作,比如 tie notin (front,back),将会筛选所有 tie 不是 front 且不是 back 的 Pod。 另外,也可以根据是否存在某 lable 的筛选,如:Selector release,筛 选所有带 release 标签的 Pod。集合型和相等型的 Selector,也可以用 “,”来连接,同样的标识逻辑”与“的关系。

k8s deployment没有pod生成 k8s pod创建流程_ide_05

  1. Annotations 另外一种重要的元数据是
    annotations,一般是系统或者工具用来存储资 源的非标示性信息,可以用来扩展资源的 spec/status 的描述
    例子:
    第一个例子,存储了阿里云负载器的证书 ID,我们可以看到 annotations 一 样 可 以 拥 有 域 名 的 前 缀 , 标 注 中 也 可 以 包 含 版 本 信 息 。 第 二 个 annotation 存储了 nginx 接入层的配置信息,我们可以看到 annotations 中包括“,”这样无法出现在 label 中的特殊字符。第三个 annotations 一 般可以在 kubectl apply 命令行操作后的资源中看到, annotation 值是 一个结构化的数据,实际上是一个 json 串,标记了上一次 kubectl 操 作的资源的 json 的描述。
  2. k8s deployment没有pod生成 k8s pod创建流程_linux_06

  3. Ownereference
    最后一个元数据叫做 Ownereference,所谓所有者,一般 就是指集合类的资源,比如说 Pod 集合,就有 replicaset、statefulset, 集合类资源的控制器会创建对应的归属资源。
    比如:replicaset 控制器在 操 作 中 会 创 建 Pod, 被 创 建 Pod 的 Ownereference 就 指 向 了 创 建 Pod 的 replicaset,Ownereference 使得用户可以方便地查找一个创建 资源的对象,另外,还可以用来实现级联删除的效果。

怎么样对 Pod 已有的 lable 进行修改
指定 Pod 名字,在环境再加 上它的一个值 test

kubectl label pods nginx1 env=test

如果想覆盖掉它的话,得额外再加上一个覆盖的选项

kubectl label pods nginx1 env=test —overwrite

最后查看一下

kubectl get pods —show-labels

怎样对Pod 去掉一个标签

env 后就不是等号了。只加上 label 名字,后面不加等号,改成用减号表示

kubectl label pods nginx tie-
kubectl get pods —show-labels

label Selector 进行匹配?

-l 这个选项来进 行指定lable Selector

kubectl get pods —show-labels -l env=test

多个相等的条件需要指定的,用逗号隔开

kubectl get pods —show-labels -l env=dev,tie=front

怎么样用集合型的 label Selector 来进行筛选

kubectl get pods —show-labels -l ’env in (dev,test)’

怎样对 Pod 增加一个注解
把 label 命令改成 annotate 命令

kubectl annotate pods nginx1 my-annotate=‘my annotate,ok’

最后查看一下元数据

kubectl get pods nging1 -o yaml | less

2. 控制器模式

  1. 控制循环
    在控制循环中包括了控制器被控制的系统,以及能够观测系统的传感器 外界通过修改资源 spec 来控制资源,控制器 比较资源 spec 和 status,从而计算一个 diff,diff 最后会用来决定执 行对系统进行什么样的控制操作,控制操作会使得系统产生新的输出,并 被传感器以资源 status 形式上报,控制器的各个组件将都会是独立自主 地运行,不断使系统向 spec 表示终态趋近。
  2. k8s deployment没有pod生成 k8s pod创建流程_kubernetes_07

  3. Sensor
    控制循环中逻辑的传感器 主要由 Reflector、Informer、Indexer 三个组 件构成。
    Reflector 通过 List 和 Watch K8s server 来获取资源的数据。List 用 来在 Controller 重启以及 Watch 中断的情况下,进行系统资源的全量 更新;而 Watch 则在多次 List 之间进行增量的资源更新;Reflector 在 获取新的资源数据后,会在 Delta 队列中塞入一个包括资源对象信息本 身以及资源对象事件类型的 Delta 记录,Delta 队列中可以保证同一个 对象在队列中仅有一条记录,从而避免 Reflector 重新 List 和 Watch 的时候产生重复的记录。
    Informer 组件不断地从 Delta 队列中弹出 delta 记录,然后把资源对象 交给 indexer,让 indexer 把资源记录在一个缓存中,缓存在默认设置 下是用资源的命名空间来做索引的,并且可以被 Controller Manager 或 多个 Controller 所共享。之后,再把这个事件交给事件的回调函数
  4. k8s deployment没有pod生成 k8s pod创建流程_docker_08

  5. 控制循环中的控制器组件主要由事件处理函数以及 worker 组成,事件处 理函数之间会相互关注资源的新增、更新、删除的事件,并根据控制器的 逻辑去决定是否需要处理。对需要处理的事件,会把事件关联资源的命名 空间以及名字塞入一个工作队列中,并且由后续的 worker 池中的一个 Worker 来处理,工作队列会对存储的对象进行去重,从而避免多个 Woker 处理同一个资源的情况。 Worker 在处理资源对象时,一般需要用资源的名字来重新获得最新的资 源数据,用来创建或者更新资源对象,或者调用其他的外部服务,Worker 如果处理失败的时候,一般情况下会把资源的名字重新加入到工作队列 中,从而方便之后进行重试。
  6. 控制循环例子- 扩容
    ReplicaSet 是 一 个 用 来 描 述 无 状 态 应 用 的 扩 缩 容 行 为 的 资 源 , ReplicaSet controler 通过监听 ReplicaSet 资源来维持应用希望的状态 数量,ReplicaSet 中通过 selector 来匹配所关联的 Pod,在这里考虑 ReplicaSet rsA 的,replicas 从 2 被改到 3 的场景。
  7. k8s deployment没有pod生成 k8s pod创建流程_docker_09

  8. Reflector 会 watch 到 ReplicaSet 和 Pod 两种资源的变化,为 什么我们还会 watch pod 资源的变化稍后会讲到。发现 ReplicaSet 发 生变化后,在 delta 队列中塞入了对象是 rsA,而且类型是更新的记录。
    Informer 一方面把新的 ReplicaSet 更新到缓存中,并与 Namespace nsA 作为索引。另外一方面,调用 Update 的回调函数,ReplicaSet 控 制器发现 ReplicaSet 发生变化后会把字符串的 nsA/rsA 字符串塞入到 工作队列中,工作队列后的一个 Worker 从工作队列中取到了 nsA/rsA 这个字符串的 key,并且从缓存中取到了最新的 ReplicaSet 数据。
    Worker 通过比较 ReplicaSet 中 spec 和 status 里的数值,发现需要 对这个 ReplicaSet 进行扩容,因此 ReplicaSet 的 Worker 创建了一个 Pod,这个 pod 中的 Ownereference 取向了 ReplicaSet rsA。
  9. k8s deployment没有pod生成 k8s pod创建流程_Pod_10

Pod的怎么创建逻辑流程是怎样的?

(1)客户端提交创建请求,可以通过API Server的Restful API,也可以使用kubectl命令行工具。支持的数据类型包括JSON和YAML。
(2)API Server处理用户请求,存储Pod数据到etcd。
(3)调度器通过API Server查看未绑定的Pod。尝试为Pod分配主机。
(4)过滤主机 (调度预选):调度器用一组规则过滤掉不符合要求的主机。比如Pod指定了所需要的资源量,那么可用资源比Pod需要的资源量少的主机会被过滤掉。
(5)主机打分(调度优选):对第一步筛选出的符合要求的主机进行打分,在主机打分阶段,调度器会考虑一些整体优化策略,比如把容一个Replication Controller的副本分布到不同的主机上,使用最低负载的主机等。
(6)选择主机:选择打分最高的主机,进行binding操作,结果存储到etcd中。
(7)kubelet根据调度结果执行Pod创建操作: 绑定成功后,scheduler会调用APIServer的API在etcd中创建一个boundpod对象,描述在一个工作节点上绑定运行的所有pod信息。运行在每个工作节点上的kubelet也会定期与etcd同步boundpod信息,一旦发现应该在该工作节点上运行的boundpod对象没有更新,则调用Docker API创建并启动pod内的容器。