管理日志采集sidecar容器最佳实践
Kubernetes容器日志收集
日志作为任一系统不可或缺的部分,在K8S 官方文档 中也介绍了多种的日志采集形式,总结起来主要有下述3种:原生方式、DaemonSet方式和Sidecar方式。 三种方式都有利有弊,没有哪种方式能够完美的解决100%问题的,所以要根据场景来贴合。其中Sidecar方式为每个POD单独部署日志agent,相对资源占用较多,但灵活性以及多租户隔离性较强,建议大型的K8S集群或作为PAAS平台为多个业务方服务的集群使用该方式。
EFK整体架构
EFK(ElasticSearch, FileBeat, Kibana)是社区非常流行的、使用非常广泛的日志采集方案,架构如下: 
K8S Sidecar模式弊端
如上图所示,FileBeat容器以Sidecar模式与业务app容器部署在同一个Pod内,通过共享volume的方式采集日志上传到ElasticSearch,配置如下:
spec:containers:- name: nginximage: nginx:latestvolumeMounts:# 通过 volumeMounts 与filebeat sidecar容器共享 log 目录- mountPath: /var/log/nginxname: log- name: filebeatimage: docker.elastic.co/beats/filebeat:7.16.2volumeMounts:- mountPath: /var/log/nginxname: logvolumes:- name: logemptyDir: {}
Pod Sidecar模式:通过在Pod里定义专门容器,来执行主业务容器需要的辅助工作(比如:日志采集容器,流量代理容器)。优势:将辅助能力同业务容器解耦,实现独立发布和能力重用。但是也有一些弊端,如下:
- 业务Pod耦合(运维、代理)多种sidecar容器,增加配置的复杂性以及业务开发人员的学习成本
- Sidecar容器升级将导致业务Pod重建,由于Sidecar容器一般是独立的中间件团队负责,如果升级会存在极大的业务方阻力
SidecarSet管理sidecar容器的利器
SidecarSet是OpenKruise中针对sidecar容器管理抽象出来的概念,负责注入和升级k8s集群中的sidecar容器,是OpenKruise的核心workload之一,详细可参考:SidecarSet文档。
- 自动注入Sidecar:将sidecar容器配置与业务Workload(Deployment、CloneSet等)配置解耦,简化用户使用成本
- 独立升级Sidecar容器:不重建Pod,单独升级Sidecar容器,对业务无感
EFK + SidecarSet(FileBeat)实践
安装EFK(ElasticSearch、Kibana)
社区中有很多安装部署EFK的文档,本文主要是通过Helm的方式进行部署,参考 Elastic Helm Charts。 首先K8S集群中需要 StorageClass 用于ElasticSearch PVC,本文使用已经定义好的 alibabacloud-cnfs-nas 如下:
helm-charts% kubectl get storageclassNAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGEalibabacloud-cnfs-nas nasplugin.csi.alibabacloud.com Delete Immediate true 6d2h
Helm一键部署ElasticSearch、Kibana,如下:
helm-charts% helm repo add elastic https://helm.elastic.cohelm-charts% helm repo update## 安装 ElasticSearch,设置storage-class为 alibabacloud-cnfs-nashelm-charts% helm install elasticsearch elastic/elasticsearch --version 7.16.3 --set persistence.annotations."volume.beta.kubernetes.io/storage-class"=alibabacloud-cnfs-nas -n elastic-system## 安装 Kibana,即可通过kibana service externalIp进行访问helm-charts% helm install kibana elastic/kibana --version 7.16.3 --set service.type=LoadBalancer -n elastic-system
FileBeat SidecarSet CRD
创建FileBeat采集配置(此ConfigMap需要创建到业务Namespace下面),如下:
apiVersion: v1data:filebeat.yml: |filebeat.inputs:- type: logpaths:- /var/log/*output.elasticsearch:host: '${NODE_NAME}'hosts: '${ELASTICSEARCH_HOSTS:elasticsearch-master.elastic-system:9200}'kind: ConfigMapmetadata:name: filebeat-config
定义FileBeat SidecarSet配置,如下:
apiVersion: apps.kruise.io/v1alpha1kind: SidecarSetmetadata:name: filebeat-sidecarsetspec:selector:# 需要注入 sidecar 容器的 pod labelsmatchLabels:kruise.io/inject-filebeat: "true"# sidecarSet默认是对整个集群生效,可以通过namespace字段指定生效的范围#namespace: ns-xxxcontainers:- args:- -e- -E- http.enabled=trueenv:- name: POD_NAMESPACEvalueFrom:fieldRef:apiVersion: v1fieldPath: metadata.namespaceimage: docker.elastic.co/beats/filebeat:7.16.2livenessProbe:exec:command:- sh- -c- |#!/usr/bin/env bash -ecurl --fail 127.0.0.1:5066name: filebeatreadinessProbe:exec:command:- sh- -c- |#!/usr/bin/env bash -efilebeat test outputresources:limits:cpu: "1"memory: 200Mirequests:cpu: 100mmemory: 100MivolumeMounts:- mountPath: /usr/share/filebeatname: filebeat-config# 通过 volumeMounts 与业务容器共享 log 目录- mountPath: /var/logname: logvolumes:- configMap:name: filebeat-configname: filebeat-config- name: logemptyDir: {}
针对机器资源不太充足的场景,为减少Pod资源的申请,可以将sidecar container request.cpu=0,此种情况下Pod的Qos将会是 Burstable。
自动注入FileBeat Sidecar容器
定义Nginx服务Deployment,只包含 nginx 相关配置,如下:
apiVersion: apps/v1kind: Deploymentmetadata:labels:app: nginxname: nginxspec:replicas: 1selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginx# 注入 filebeat sidecar 容器的labelkruise.io/inject-filebeat: "true"spec:containers:- name: nginximage: nginx:latestvolumeMounts:# 通过 volumeMounts 与filebeat sidecar容器共享 log 目录- mountPath: /var/log/nginxname: logvolumes:- name: logemptyDir: {}
将nginx deployment apply到k8s集群后,发现创建的Pod都被注入了 filebeat sidecar 容器,如下:
helm-charts% kubectl get pods nginx-5674976569-zdr7l -o yamlstatus:containerStatuses:- containerID: containerd://5330c2b32262de83ed387e5a932f61acc52e3896ddfcb22d626c43d82638faf3image: docker.elastic.co/beats/filebeat:7.16.2name: filebeatstate:running:startedAt: "2022-03-02T12:17:15Z"- containerID: containerd://1ad335f39c134f7a66a0370a275dd95f67f5fd3d3f1fe523c955408b14887229image: docker.io/library/nginx:latestname: nginxstate:running:startedAt: "2022-03-02T12:17:16Z"
独立升级FileBeat Sidecar容器(Version 7.16.2 -> 7.16.3)
下面是两个窗口,右边是访问nginx服务的client请求。此时将filebeat sidecarSet中的镜像地址从7.16.2变更为7.16.3后,发现Pod并没有重建, 且filebeat sidecar容器镜像升级7.16.3完成过程中,nginx服务并没有中断(该nginx服务只有一个Pod实例),如下:

该特性依赖Kruise原地升级的能力实现,详情参考文档:Kruise原地升级。 不过独立升级sidecar容器也存在一定的风险性,如果sidecar容器升级过程中失败,则将导致Pod Not Ready,进而影响业务,因此SidecarSet本身提供了非常丰富的灰度发布能力来尽量规避该风险, 详情参考文档:Kruise SidecarSet,如下:
apiVersion: apps.kruise.io/v1alpha1kind: SidecarSetmetadata:name: sidecarsetspec:# ...updateStrategy:type: RollingUpdate# 最大不可用数量maxUnavailable: 20%# 分批发布partition: 90# 金丝雀发布,通过pod labelsselector:matchLabels:# Some Pods contain canary labels,# or any other labels where a small number of pods can be selecteddeploy-env: canary
另外,如果是类似于ServiceMesh Envoy Mesh类容器则需要借助于SidecarSet热升级特性,详情请参考:SidecarSet热升级。
Argo-cd部署SidecarSet(Optional)
如果使用Argo-cd发布Kruise SidecarSet,则需要配置 SidecarSet Custom CRD Health Checks。 Argo-cd根据该配置能够实现SidecarSet自定义资源的检查,如SidecarSet是否发布完成,以及Pod是否ready等,如下:
apiVersion: v1kind: ConfigMapmetadata:labels:app.kubernetes.io/name: argocd-cmapp.kubernetes.io/part-of: argocdname: argocd-cmnamespace: argocddata:resource.customizations.health.apps.kruise.io_SidecarSet: |hs = {}-- if pausedif obj.spec.updateStrategy.paused thenhs.status = "Suspended"hs.message = "SidecarSet is Suspended"return hsend-- check sidecarSet statusif obj.status ~= nil thenif obj.status.observedGeneration < obj.metadata.generation thenhs.status = "Progressing"hs.message = "Waiting for rollout to finish: observed sidecarSet generation less then desired generation"return hsendif obj.status.updatedPods < obj.spec.matchedPods thenhs.status = "Progressing"hs.message = "Waiting for rollout to finish: replicas hasn't finished updating..."return hsendif obj.status.updatedReadyPods < obj.status.updatedPods thenhs.status = "Progressing"hs.message = "Waiting for rollout to finish: replicas hasn't finished updating..."return hsendhs.status = "Healthy"return hsend-- if status == nilhs.status = "Progressing"hs.message = "Waiting for sidecarSet"return hs
总结
Pod包含多个container的方式将越来越被更多的开发者接受,进而K8S生态里面急需一种能够有效管理Sidecar容器的方式。 Kruise SidecarSet是在sidecar容器管理上面的一些探索,目前社区也有很多的公司在使用Kruise SidecarSet管理不同类型的sidecar容器。
SidecarSet在带来便利的同时,其实也带来了一些管理上面的成本,例如:Sidecar容器与业务app容器同时发布怎么办?Pod中container属于多个团队,那Pod的所属权到底属于谁? 所以,我们也希望能够与社区的更多开发者一起探索,同时也欢迎大家都能提供一些思路,共同繁荣K8S生态。