简介: OpenKruise 是阿里云开源的云原生应用自动化管理套件,也是当前托管在 Cloud Native Computing Foundation (CNCF) 下的 Sandbox 项目。它来自阿里巴巴多年来容器化、云原生的技术沉淀,是阿里内部生产环境大规模应用的基于 Kubernetes 之上的标准扩展组件,也是紧贴上游社区标准、适应互联网规模化场景的技术理念与最佳实践。
OpenKruise 是阿里云开源的云原生应用自动化管理套件,也是当前托管在 Cloud Native Computing Foundation (CNCF) 下的 Sandbox 项目。它来自阿里巴巴多年来容器化、云原生的技术沉淀,是阿里内部生产环境大规模应用的基于 Kubernetes 之上的标准扩展组件,也是紧贴上游社区标准、适应互联网规模化场景的技术理念与最佳实践。
OpenKruise 在 2021 年 3 月 4 日发布了最新的 v0.8.0版本(ChangeLog),其中增强了 SidecarSet 的能力,特别是对日志管理类 Sidecar 有了更加完善的支持。
Sidecar 是云原生中一种非常重要的容器设计模式,它将辅助能力从主容器中剥离出来成为单独的 sidecar 容器。在微服务架构中,通常也使用 sidecar 模式将微服务中的配置管理、服务发现、路由、熔断等通用能力从主程序中剥离出来,从而极大降低了微服务架构中的复杂性。随着 Service Mesh 的逐步风靡,sidecar 模式也日益深入人心,在阿里巴巴集团内部也大量使用 sidecar 模式来管理诸如运维、安全、消息中间件等通用组件。
在 Kubernetes 集群中,Pod 不仅可以实现主容器与 sidecar 容器的构建,同时提供了许多功能强大的 workload(例如:deployment、statefulset)来对 Pod 进行管理、升级。但是随着 Kubernetes 集群上的业务日益增多,sidecar 容器的种类与规模也随之日益庞大,对线上 sidecar 容器的管理和升级成为了愈发繁杂的工作:
阿里巴巴集团内部拥有着百万级的容器数量连同上面承载的上千个业务,因此,sidecar 容器的管理与升级也就成为了亟待完善的主题。因此,我们总结了内部许多 sidecar 容器的通用化需求,并将其沉淀到 OpenKruise 上面,最终抽象为 SidecarSet 作为管理和升级种类繁多 sidecar 容器的利器。
SidecarSet 是 OpenKruise 中针对 sidecar 抽象出来的概念,负责注入和升级 Kubernetes 集群中的 sidecar 容器,是 OpenKruise 的核心 workload 之一。它提供了非常丰富的功能,用户使用 SidecarSet 可以非常方便实现 sidecar 容器的管理。主要特性如下:
注意:针对 Pod 中包含多个容器的模式,其中对外提供主要业务逻辑能力的容器称之为 主容器,其它一些如日志采集、安全、代理等辅助能力的容器称之为 Sidecar 容器。例如:一个对外提供 web 能力的 pod,nginx 容器提供主要的 web server 能力即为主容器,logtail 容器负责采集、上报 nginx 日志即为 Sidecar 容器。本文中的 SidecarSet 资源抽象也是为解决 Sidecar 容器的一些问题。
=================================
应用日志可以让你了解应用内部的运行状况,日志对调试问题和监控集群活动非常有用。应用容器化后,最简单且最广泛采用的日志记录方式就是写入标准输出和标准错误。
但是,在当前分布式系统、大规模集群的时代下,上述方案还不足以达到生产环境的标准。首先,对于分布式系统而言,日志都是分散在单个容器里面,没有一个统一汇总的地方。其次,如果发生容器崩溃、Pod 被驱逐等场景,会出现日志丢失的情况。因此,需要一种更加可靠,独立于容器生命周期的日志解决方案。
Sidecar logging architectures 是将 logging agent 放到一个独立的 sidecar 容器中,通过共享日志目录的方式,实现容器日志的采集,然后存储到日志平台的后端存储。
阿里巴巴以及蚂蚁集团内部同样也是基于这种架构实现了容器的日志采集,下面我将介 绍OpenKruise SidecarSet 如何助力 Sidecar 日志架构在 Kubernetes 集群中的大规模落地实践。
========
OpenKruise SidecarSet 基于 Kubernetes AdmissionWebhook 机制实现了 sidecar 容器的自动注入,因此只要将 sidecar 配置到 SidecarSet 中,不管用户用 CloneSet、Deployment、StatefulSet 等任何方式部署,扩出来的 Pod 中都会注入定义好的 sidecar 容器。
Sidecar 容器的所有者只需要配置自身的 SidecarSet,就可以在业务无感知的情况下完成 sidecar 容器的注入,这种方式极大的降低了 sidecar 容器使用的门槛,也方便了 sidecar 所有者的管理工作。为了满足 sidecar 注入的多种场景,SidecarSet 除 containers 之外还扩展了如下字段:
# sidecarset.yaml apiVersion: apps.kruise.io/v1alpha1 kind: SidecarSet metadata: name: test-sidecarset spec: # 通过selector选择pod selector: matchLabels: app: web-server # 指定 namespace 生效 namespace: ns-1 # container definition containers: - name: logtail image: logtail:1.0.0 # 共享指定卷 volumeMounts: - name: web-log mountPath: /var/log/web # 共享所有卷 shareVolumePolicy: disabled # 环境变量共享 transferEnv: - sourceContainerName: web-server # TZ代表时区,例如:web-server容器中存在环境变量 TZ=Asia/Shanghai envName: TZ volumes: - name: web-log emptyDir: {}
注意:Kubernetes 社区对于已经创建的 Pod 不允许修改 container 数量,所以上述注入能力只能发生在 Pod 创建阶段,对于已经创建的 Pod 需要通过重建的方式来注入。
========
SidecarSet 不仅实现 sidecar 容器的注入,而且复用了 OpenKruise 中原地升级的特性,实现了在不重启 Pod 和主容器的前提下单独升级 sidecar 容器的能力。由于这种升级方式基本上能做到业务方无感知的程度,所以 sidecar 容器的升级已不再是上下交困的难题,从而极大解放了 sidecar 的所有者,提升了 sidecar 版本迭代的速度。
注意:Kubernetes 社区对于已经创建的 Pod 只允许修改 container.image 字段,因此对于 sidecar 容器的修改包含除 container.image 的其它字段,则需要通过 Pod 重建的方式,不能直接原地升级。
为了满足一些复杂的 sidecar 升级场景,SidecarSet 除了原地升级以外,还提供了非常丰富的灰度发布策略。
========
灰度发布应该算是日常发布中最常见的一种手段,它能够比较平滑的完成 sidecar 容器的发布,尤其是在大规模集群的场景下,强烈建议使用这种方式。下面是首批暂停,后续基于最大不可用滚动发布的例子,假设一个有 1000 个 pod 需要发布:
apiVersion: apps.kruise.io/v1alpha1 kind: SidecarSet metadata: name: sidecarset spec: # ... updateStrategy: type: RollingUpdate partition: 980 maxUnavailable: 10%
上述配置首先发布(1000 - 980)= 20 个 pod 之后就会暂停发布,业务可以观察一段时间发现 sidecar 容器正常后,调整重新 update SidecarSet 配置:
apiVersion: apps.kruise.io/v1alpha1 kind: SidecarSet metadata: name: sidecarset spec: # ... updateStrategy: type: RollingUpdate maxUnavailable: 10%
这样调整后,对于余下的 980 个 pod,将会按照最大不可用的数量(10% * 1000 = 100)的顺序进行发布,直到所有的 pod 都发布完成。
Partition 的语义是保留旧版本 Pod 的数量或百分比,默认为 0。这里的 partition 不表示任何 order 序号。如果在发布过程中设置了 partition:
MaxUnavailable 是发布过程中保证的,同一时间下最大不可用的 Pod 数量,默认值为 1。用户可以将其设置为绝对值或百分比(百分比会被控制器按照 selected pod 做基数来计算出一个背后的绝对值)。
注意:maxUnavailable 和 partition 两个值是没有必然关联。举例:
=========
对于有金丝雀发布需求的业务,可以通过 strategy.selector 来实现。方式:对于需要率先金丝雀灰度的 pod 打上固定的 labels[canary.release] = true,再通过
strategy.selector.matchLabels 来选中该 pod。
apiVersion: apps.kruise.io/v1alpha1 kind: SidecarSet metadata: name: sidecarset spec: # ... updateStrategy: type: RollingUpdate selector: matchLabels: - canary.release: true maxUnavailable: 10%
上述配置只会发布打上金丝雀 labels 的容器,在完成金丝雀验证之后,通过将 updateStrategy.selector 配置去掉,就会继续通过最大不可用来滚动发布。
========
SidecarSet 对于 pod 的升级顺序,默认按照如下规则:
除了上述默认发布顺序之外,scatter 打散策略允许用户自定义将符合某些标签的 Pod 打散到整个发布过程中。比如,对于像 logtail 这种全局性的 sidecar container,一个集群当中很可能注入了几十个业务 pod,因此可以使用基于 应用名 的方式来打散 logtail 的方式进行发布,实现不同应用间打散灰度发布的效果,并且这种方式可以同最大不可用一起使用。
apiVersion: apps.kruise.io/v1alpha1 kind: SidecarSet metadata: name: sidecarset spec: # ... updateStrategy: type: RollingUpdate # 配置pod labels,假设所有的pod都包含labels[app_name] scatterStrategy: - key: app_name value: nginx - key: app_name value: web-server - key: app_name value: api-gateway maxUnavailable: 10%
注意:当前版本必须要列举所有的应用名称,我们将在下个版本支持只配置 label key 的智能打散方式。
======
阿里巴巴以及蚂蚁集团内部已经大规模的使用 SidecarSet 来管理 sidecar 容器,下面我将通过日志采集 Logtail sidecar 来作为一个示例。
# sidecarset.yaml apiVersion: apps.kruise.io/v1alpha1 kind: SidecarSet metadata: name: logtail-sidecarset spec: selector: matchLabels: app: nginx updateStrategy: type: RollingUpdate maxUnavailable: 10% containers: - name: logtail image: log-service/logtail:0.16.16 # when recevie sigterm, logtail will delay 10 seconds and then stop command: - sh - -c - /usr/local/ilogtail/run_logtail.sh 10 livenessProbe: exec: command: - /etc/init.d/ilogtaild - status resources: limits: memory: 512Mi requests: cpu: 10m memory: 30Mi ##### share this volume volumeMounts: - name: nginx-log mountPath: /var/log/nginx transferEnv: - sourceContainerName: nginx envName: TZ volumes: - name: nginx-log emptyDir: {}
apiVersion: v1 kind: Pod metadata: labels: # matches the SidecarSet's selector app: nginx name: test-pod spec: containers: - name: nginx image: log-service/docker-log-test:latest command: ["/bin/mock_log"] args: ["--log-type=nginx", "--stdout=false", "--stderr=true", "--path=/var/log/nginx/access.log", "--total-count=1000000000", "--logs-per-sec=100"] volumeMounts: - name: nginx-log mountPath: /var/log/nginx envs: - name: TZ value: Asia/Shanghai volumes: - name: nginx-log emptyDir: {}
$ kubectl get pod NAME READY STATUS RESTARTS AGE test-pod 2/2 Running 0 118s $ kubectl get pods test-pod -o yaml |grep 'logtail:0.16.16' image: log-service/logtail:0.16.16
$ kubectl get sidecarset logtail-sidecarset -o yaml | grep -A4 status status: matchedPods: 1 observedGeneration: 1 readyPods: 1 updatedPods: 1
$ kubectl edit sidecarsets logtail-sidecarset # sidecarset.yaml apiVersion: apps.kruise.io/v1alpha1 kind: SidecarSet metadata: name: logtail-sidecarset spec: containers: - name: logtail image: log-service/logtail:0.16.18
$ kubectl get pods |grep test-pod test-pod 2/2 Running 1 7m34s $ kubectl get pods test-pod -o yaml |grep 'image: logtail:0.16.18' image: log-service/logtail:0.16.18 $ kubectl describe pods test-pod Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Killing 5m47s kubelet Container logtail definition changed, will be restarted Normal Pulling 5m17s kubelet Pulling image "log-service/logtail:0.16.18" Normal Created 5m5s (x2 over 12m) kubelet Created container logtail Normal Started 5m5s (x2 over 12m) kubelet Started container logtail Normal Pulled 5m5s kubelet Successfully pulled image "log-service/logtail:0.16.18"
本次 OpenKruise v0.8.0 版本的升级,SidecarSet 特性主要是完善了日志管理类 Sidecar 场景的能力,后续我们在持续深耕 SidecarSet 稳定性、性能的同时,也将覆盖更多的场景,比如下一个版本将会增加针对 Service Mesh 场景的支持。同时,我们也欢迎更多的同学参与到 OpenKruise 社区来,共同建设一个场景更加丰富、完善的 K8s 应用管理、交付扩展能力,能够面向更加规模化、复杂化、极致性能的场景。
作者:赵明山(立衡)
原文链接
本文为阿里云原创内容,未经允许不得转载