本文讨论 K8S 1.3 的一些新功能,以及正在进行中的功能。读者应该对 kubernetes 的基本结构已经有所了解。
支持更多类型的应用
1 Init container
Init container 是1.3 中的 alpha feature,目的是支持一类需要在启动 Pod“普通容器”前,先进行 Pod 初始化的应用。执行该初始化任务的容器被成为“初始化容器”(init container)。例如,在启动应用之前,初始化数据库,或等待数据库启动等。下图是一个包含 init container 的 Pod:

对于此类 Pod,kubernetes 的运行策略如下:
-
初始化容器按顺序依次执行,即图中容器 1->2
-
若其中某一个初始化容器运行失败,则整个 Pod 失败
-
当所有初始化容器运行成功,启动普通容器,即图中容器 A 和 B
在 alpha 版本中使用 init container 需要用 annotation,下图是来自 k8s 的一个例子(略有裁剪):
![]()

可以看到,我们在启动 nginx 普通容器之前,先用 init container 来获取 index.html,之后访问 nginx 就会直接返回该文件。当 init container 功能稳定后,k8s 会直接在 pod.spec 内加上 init Containers 字段,如下所示:

init container 看起来是一个小功能,但是在实现上还是需要考虑不少问题,比如几个比较重要的点:
资源问题:当调度存在 init container 的 Pod 时,应该怎样计算所需要的资源?两个极端情况:如果对 init container 和 regular container 所需要的资源求和,那么当 init container 成功初始化 Pod 之后,就不会再使用所请求的资源,而系统认为在使用,会造成浪费;反之,不计算 init container 的资源又会导致系统不稳定(init container 所使用的资源未被算入调度资源内)。目前的方法是取折中:由于初始化容器和普通容器不会同时运行,因此 Pod 的资源请求是两者中的最大值。对于初始化容器,由于他们是依次运行,因此选择其中的最大值;对于普通容器,由于是同时运行,选择容器资源的和。
Pod Status: 目前,Pod 有 Pending, Running, Terminating 等状态。对于有初始化容器的 Pod,如果仍然使用 Pending 状态,则很难区分 Pod 当前在运行初始化容器还是普通容器。因此,理想情况下,我们需要增加一个类似于 Initializing 的状态。在 alpha 版本中暂时还未添加。
健康检查及可用性检查:有了 init container 之后,我们该如何检查容器的健康状态?alpha 版本将两个检查都关闭了,但 init container 是会在 node 上实实在在运行的容器,理论上是需要进行检查的。对于可用性检查,关闭掉是一个可行的办法,因为 init container 的可用性其实就是当它运行结束的时候。对于健康检查,node 需要知道某个 Pod 是否处在初始化阶段;如果处在初始化阶段,那么 node 就可以对 init container 进行健康检查。因此,kubernetes 很有可能在添加类似 Initializing 的 Pod 状态之后,开启 init container 的健康检查。
围绕 init container 的问题还有很多,比如 QoS,Pod 的更新等等,其中不少都是有待解决的问题,这里就不一一展开了 :)
2 PetSet
PetSet 应该算是社区期待已久的功能,其目的是支持有状态和集群化的应用,目前也是 alpha 阶段。PetSet 的应用场景很多,包括类似 zookeeper、etcd 之类的 quorum leader election 应用,类似 Cassandra 的 Decentralized quorum 等。PetSet 中,每个 Pod 都有唯一的身份,分别包括:名字,网络和存储;并由新的组件 PetSet Controller 负责创建和维护。下面依次看一看 kubernetes 是如何维护 Pod 的唯一身份。
名字比较容易理解,当我们创建一个 RC 之后,kubernetes 会创建指定副本数量的 Pod,当使用 kubectl 获取 Pod 信息时,我们会得到如下信息:

其中,5 个字符的后缀为 kubernetes 自动生成。当 Pod 重启,我们会得到不同的名字。对于 PetSet 来讲,Pod 重启必须保证名字不变。因此,PetSet 控制器会维护一个 identityMap,每一个 PetSet 中的每个 Pod 都会有一个唯一名字,当 Pod 重启,PetSet 控制器可以感知到是哪个 Pod,然后通知 API server 创建新的同名 Pod。目前的感知方法很简单,PetSet 控制器维护的 identityMap 将 Pod 从 0 开始进行编号,然后同步的过程就像报数一样,哪个编号不在就重启哪个编号。

此外,该编号还有另外一个作用,PetSet 控制器通过编号来确保 Pod 启动顺序,只有 0 号 Pod 启动之后,才能启动 1 号 Pod。
网络身份的维护主要通过稳定的 hostname 和 domain name 来维护,他们通过 PetSet 的配置文件指定。例如,下图是一个 PetSet 的 Yaml 文件(有裁剪),其中 metadata.name 指定了 Pod 的 hostname 前缀(后缀即前面提到的从 0 开始的索引),spec.ServiceName 指定了 domain name。

![]()
通过上面的 Yaml 文件创建出来两个 Pod:web-0 和 web-1。其完整的域名为 web-0.nginx.default.svc.cluster.local,其中 web-0 为 hostname,nginx 为 Yaml 中指定的 domain name,剩下的部分与普通 service 无异。当创建请求被下发到节点上时,kubelet 会通过 container runtime 设置 UTS namespace,如下图所示(省略了部分组件如 apiserver)。

此时,hostname 已经在容器层面设置完成,剩下还需要为 hostname 增加集群层面的解析,以及添加 domain name 的解析,这部分工作理所当然就交给了 kube dns。了解 Kubernetes 的读者应该知道,要添加解析,我们需要创建 service;同理,这里也需要为 PetSet 创建 service。不同的是,普通的 service 默认后端的 Pod 是可替换的,并采用诸如 roundrobin,client ip 的方式选择后端的 Pod,这里,由于每个 Pod 都是一个 Pet,我们需要定位每一个 Pod,因此,我们创建的 service 必须要能满足这个要求。在 PetSet 中,利用了 kubernetes headless service。Headless service 不会分配 cluster IP 来 load balance 后端的 Pod,但会在集群 DNS 服务器中添加记录:创建者需要自己去利用这些记录。下图是我们需要创建的 headless service,注意其中的 clusterIP 被设置为 None,表明这是一个 headless service。

Kube dns 进行一番处理之后,会生成如下的记录:

可以看到,访问 web-0.nginx.default.svc.cluster.local 会返回 pod IP,访问 nginx.default.svc.cluster.local 会返回所有 Pet 中的 pods IP。一个常见的方式是通过访问 domain 的方式来获取所有的 peers,然后依次和单独的 Pod 通信。
存储身份这块采用的是 PV/PVC 实现,当我们创建 PetSet 时,需要指定分配给 Pet 的数据卷,如下图:

![]()
这里,volumeClaimTemplates 指定每个 Pet 需要的存储资源。注意目前所有 Pet 都得到相同大小和类型的数据卷。当 PetSet 控制器拿到请求时,会为每一个 Pet 创建 PVC,然后将每个 Pet 和对应的 PVC 联系起来:

之后的 PetSet 只需要确保每个 Pet 都与相对应的 PVC 绑定在一起即可,其他工作,类似于创建数据卷,挂载等工作,都交给其他组件。
通过名字,网络,存储,PetSet 能够 cover 大多数的案例。但是,目前还存在很多需要完善的地方,感兴趣的读者可以参考:https://github.com/kubernetes/kubernetes/issues/28718
3 Scheduled Job
Scheduled Job 本质上是集群 cron,类似 mesos chronos,采用标准的 cron 语法。遗憾的是在 1.3 中还并未达到发布的标准。Scheduled Job 其实在很早就提出来过,但当时 kubernetes 的重点还在 API 层面,并且即使有很大需求,也计划在 Job(1.2GA)之后实现。当 scheduled job 在之后的版本发布之后,用户可以用一条简单的命令在 kubernetes 上运行 Job,例如:kubectl run cleanup -image=cleanup --runAt="0 1 0 0 *" -- /scripts/cleanup.sh一些关于 scheduled job 的更新可以参考:https://github.com/kubernetes/kubernetes/pull/25595
4 Disruption Budget
Disruption Budget 的提出是为了向 Pod 提供一个反馈机制,确保应用不会被集群自身的变动而受影响。例如,当集群需要进行重调度时,应用可以通过 Disruption Budget 来说明 Pod 能不能被迁移。Disruption Budget 只负责集群自身发起的变动,不负责突发事件比如节点突然掉线,或者应用本身的问题比如不断重启的变动。Disruption Budget 同样没有在 1.3 中发布。
与 kubernetes 大多数资源类似,我们需要通过 Yaml 文件创建一个 PodDisruptionBudget 资源,例如,下图所示的 Disruption Budget 选中了所有带有 app:nginx 标签的 pod,并且要求至少有 3 个 Pod 在同时运行。

Controller manager 内有一个新的组件 Disruption Budget Controller,来负责维护所有 Budget 的状态,例如上图中的 status 表明当前共有 4 个健康的 Pod(currentHealthy),应用要求至少有 3 个(desiredHealthy),总共有 5 个Pod(expectedPods)。为了维护这个状态,Disruption Budget Controller 会遍历所有的 Budget 和所有的 Pod。有了 Budget 的状态,需要改变 Pod 状态的组件都要先查询之。若其操作导致最小可用数低于应用要求,则操作会被拒绝。Disruption Budget 与 QoS 联系很紧密。例如,如果一个 QoS level 很低的应用有着非常严格的 Disruption Budget,系统应该如何处理?目前,kubernetes 还没有严格的处理这个问题,一个可行的办法是对 Disruption Budget 做优先级处理,确保高优先级的应用拥有高优先级的 Disruption Budget;此外,Disruption Budget 可以加入 Quota 系统,高优先级的应用可以获得更多 Disruption Budget Quota。关于 Disruption Budget 的讨论可以参考:https://github.com/kubernetes/kubernetes/issues/12611
支持更好的集群管理
1 Cascading Deletion
在 kubernetes 1.2 之前,删除控制单元都不会删除底层的资源。例如,通过 API 删除 RC 之后,其管理的 Pod 不会被删除(使用 kubectl 可以删除,但 kubectl 里面有 reaper 逻辑,会依次删除底层的所有 Pod,本质上是客户端逻辑)。另外一个例子,当删除下图中的 Deployment 时,ReplicaSet 不会被自动删除,当然,Pod 也不会被回收。

![]()
Cascading deletion 指的就是在删除控制单元后,将被管理单元也同时回收。但是,kubernetes 1.3 中的 cascading deletion 并不是简单地讲 kubectl 中的逻辑复制到 server 端,而是做了更高层次的工作:垃圾回收。简单来讲,garbagecollector controller 维护了几乎所有集群资源的列表,并接收资源修改的事件。controller 根据事件类型更新资源关系图,并将受影响的资源放入 Dirty Queue 或者 Orphan Queue 中。具体实现可以参考官方文档和 garbage collector controller 实现:https://github.com/kubernetes/kubernetes/blob/master/docs/proposals/garbage-collection.md
![]()

2 Node eviction
Node/kubelet eviction 指的是在节点将要超负荷之前,提前将 Pod 剔除出去的过程,主要是为了内存和磁盘资源。在 kubernetes 1.3 之前,kubelet 不会“提前”感知节点的负荷,只会对已知的问题进行处理。当内存吃紧时,kubernetes 依靠内核 OOM killer;磁盘方面则定期对 image 和 container 进行垃圾回收。但是,这种方式有局限性,OOM killer 本身需要消耗一定资源,并且时间上有不确定性;回收容器和镜像不能处理容器写日志的问题:如果应用不断写日志,则会消耗掉所有磁盘,但不会被 kubelet 处理。
Node eviction 通过配置 kubelet 解决了以上问题。当启动 kubelet 时,我们通过指定 memory.available, nodefs.available, nodefs.inodesFree 等参数来确保节点稳定工作。例如,memory.available < 200Mi 表示当内存少于 200Mi时,kubelet 需要开始移除 Pod(可以配置为立即移除或者延迟移除,即 hard vs soft)。kubernetes 1.3 中,node eviction 的特性是 opt-in,默认关闭,可以通过配置 kubelet 打开相关功能。
尽管 node eviction 是 kubelet 层面采取的措施,我们也必须考虑与整个集群的交互关系。其中最重要的一点是如何将这个问题反馈给 scheduler,不然被剔除的 Pod 很有可能会被重新调度回来。为此,kubernetes 添加了新的 node condition:MemoryPressure, DiskPressure。当节点的状态包含其中任意一种时,调度器会避免往该节点调度新的 Pod。这里还有另外一个问题,即如果节点的资源使用刚好在阈值附近,那么节点的状态可能会在 Pressure 和 Not Pressure 之间抖动。防抖动的方法有很多种,例如平滑滤波,即将历史数据也考虑在内,加权求值。k8s 目前采用较为简单的方法:即如果节点处于 Pressure 状态,为了转变成 Not Pressure 状态,资源使用情况必须低于阈值一段时间(默认 5 分钟)。这种方法会导致 false alarm,比如,若一个应用每隔一段时间请求一块内存,之后很快释放掉,那么可能会导致节点一直处于 Pressure 状态。但大多数情况下,该方法能处理抖动的情况。
说到 eviction pod,那么另外一个不得不考虑的问题就是找一个倒霉的 Pod。这里 kubernetes 定义了不少规则,总结下来主要是两点:1. 根据 QoS 来判断,QoS 低的应用先考虑;2. 根据使用量判断,使用量与总请求量比例大的 Pod 优先考虑。具体细节可以参考:https://github.com/kubernetes/kubernetes/blob/master/docs/proposals/kubelet-eviction.md
3 Network Policy
Network policy 的目的是提供 Pod 之间的隔离,用户可以定义任意 Pod 之间的通信规则,粒度为端口。例如,下图的规则可以解释成:拥有标签“db”的 Pod,只能被拥有标签“frontend”的 Pod 访问,且只能访问 tcp 端口 6379。

Network policy 目前处于 beta 版本,并且只是 API。也就是说,kubernetes 不会真正实现网络隔离:如果我们将上述 Yaml 文件提交到 kubernetes,并不会有任何反馈,kubernetes 只是保存了这个 Policy 内容。真正实现 policy 功能需要其他组件,比如 calico 实现了一个 controller,会读取用户创建的 Policy 来实现隔离,可以参考:https://github.com/projectcalico/k8s-policy/。关于 Network Policy 的细节,可以参考:https://github.com/kubernetes/kubernetes/blob/master/docs/proposals/network-policy.md
4 Federation
Federation cluster 翻译成中文叫“联合集群”,即将多个 kubernetes 集群联合成一个整体,并且不改变原始 kubernetes 集群的工作方式。根据 kubernetes 官方设计文档,federation 的设计目的主要是满足服务高可用,混合云等需求。在 1.3 版本之前,kubernetes 实现了 federation-lite,即一个集群中的机器可以来自于相同 cloud 的不同 zone;1.3 版本中,federation-full 的支持已经是 beta 版本,即每个集群来自不同的 cloud(或相同)。
Federation的核心组件主要是 federation-apiserver 和 federation-controller-manager,以 Pod 形式运行在其中一个集群中。如下图所示,外部请求直接与 Federation Control Panel 通信,由 Federation 分析请求并发送至 kubernetes 集群。

![]()
在应用层面,Federation 目前支持 federated services,即一个应用跨多个集群的访问,具体细节可以参考:http://blog.kubernetes.io/2016/07/cross-cluster-services.html 以及http://kubernetes.io/docs/admin/federation/
结束语
kubernetes 1.3 带来了非常多的特性,这里只 cover 了其中一部分。在安全方面,kubernetes 已经支持 RBAC,实现更好的权限控制;PodSecurityContext 也进入 beta 版本,支持运行部分需要特权的 Pod 等。在性能方面,由于 protocol buffere serialization 的引入,是性能提高了几倍,并且正在酝酿中的 etcd3 会将性能提升更进一步。相信之后的版本会带给我们更多的惊喜。
本文探讨了Kubernetes 1.3版本中引入的多项新功能,包括Initcontainer用于Pod初始化,PetSet支持有状态应用,ScheduledJob实现定时任务,DisruptionBudget保障应用稳定性,以及Cascading Deletion、Node Eviction、Network Policy和Federation等增强集群管理能力的特性。

被折叠的 条评论
为什么被折叠?



