管理日志采集sidecar容器最佳实践

Kubernetes容器日志收集

日志作为任一系统不可或缺的部分,在K8S 官方文档 中也介绍了多种的日志采集形式,总结起来主要有下述3种:原生方式、DaemonSet方式和Sidecar方式。 三种方式都有利有弊,没有哪种方式能够完美的解决100%问题的,所以要根据场景来贴合。其中Sidecar方式为每个POD单独部署日志agent,相对资源占用较多,但灵活性以及多租户隔离性较强,建议大型的K8S集群或作为PAAS平台为多个业务方服务的集群使用该方式。

EFK整体架构

EFK(ElasticSearch, FileBeat, Kibana)是社区非常流行的、使用非常广泛的日志采集方案,架构如下: k8s log sidecar

K8S Sidecar模式弊端

如上图所示,FileBeat容器以Sidecar模式与业务app容器部署在同一个Pod内,通过共享volume的方式采集日志上传到ElasticSearch,配置如下:

  1. spec:
  2. containers:
  3. - name: nginx
  4. image: nginx:latest
  5. volumeMounts:
  6. # 通过 volumeMounts 与filebeat sidecar容器共享 log 目录
  7. - mountPath: /var/log/nginx
  8. name: log
  9. - name: filebeat
  10. image: docker.elastic.co/beats/filebeat:7.16.2
  11. volumeMounts:
  12. - mountPath: /var/log/nginx
  13. name: log
  14. volumes:
  15. - name: log
  16. emptyDir: {}

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 如下:

  1. helm-charts% kubectl get storageclass
  2. NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
  3. alibabacloud-cnfs-nas nasplugin.csi.alibabacloud.com Delete Immediate true 6d2h

Helm一键部署ElasticSearch、Kibana,如下:

  1. helm-charts% helm repo add elastic https://helm.elastic.co
  2. helm-charts% helm repo update
  3. ## 安装 ElasticSearch,设置storage-class为 alibabacloud-cnfs-nas
  4. helm-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
  5. ## 安装 Kibana,即可通过kibana service externalIp进行访问
  6. helm-charts% helm install kibana elastic/kibana --version 7.16.3 --set service.type=LoadBalancer -n elastic-system

FileBeat SidecarSet CRD

创建FileBeat采集配置(此ConfigMap需要创建到业务Namespace下面),如下:

  1. apiVersion: v1
  2. data:
  3. filebeat.yml: |
  4. filebeat.inputs:
  5. - type: log
  6. paths:
  7. - /var/log/*
  8. output.elasticsearch:
  9. host: '${NODE_NAME}'
  10. hosts: '${ELASTICSEARCH_HOSTS:elasticsearch-master.elastic-system:9200}'
  11. kind: ConfigMap
  12. metadata:
  13. name: filebeat-config

定义FileBeat SidecarSet配置,如下:

  1. apiVersion: apps.kruise.io/v1alpha1
  2. kind: SidecarSet
  3. metadata:
  4. name: filebeat-sidecarset
  5. spec:
  6. selector:
  7. # 需要注入 sidecar 容器的 pod labels
  8. matchLabels:
  9. kruise.io/inject-filebeat: "true"
  10. # sidecarSet默认是对整个集群生效,可以通过namespace字段指定生效的范围
  11. #namespace: ns-xxx
  12. containers:
  13. - args:
  14. - -e
  15. - -E
  16. - http.enabled=true
  17. env:
  18. - name: POD_NAMESPACE
  19. valueFrom:
  20. fieldRef:
  21. apiVersion: v1
  22. fieldPath: metadata.namespace
  23. image: docker.elastic.co/beats/filebeat:7.16.2
  24. livenessProbe:
  25. exec:
  26. command:
  27. - sh
  28. - -c
  29. - |
  30. #!/usr/bin/env bash -e
  31. curl --fail 127.0.0.1:5066
  32. name: filebeat
  33. readinessProbe:
  34. exec:
  35. command:
  36. - sh
  37. - -c
  38. - |
  39. #!/usr/bin/env bash -e
  40. filebeat test output
  41. resources:
  42. limits:
  43. cpu: "1"
  44. memory: 200Mi
  45. requests:
  46. cpu: 100m
  47. memory: 100Mi
  48. volumeMounts:
  49. - mountPath: /usr/share/filebeat
  50. name: filebeat-config
  51. # 通过 volumeMounts 与业务容器共享 log 目录
  52. - mountPath: /var/log
  53. name: log
  54. volumes:
  55. - configMap:
  56. name: filebeat-config
  57. name: filebeat-config
  58. - name: log
  59. emptyDir: {}

针对机器资源不太充足的场景,为减少Pod资源的申请,可以将sidecar container request.cpu=0,此种情况下Pod的Qos将会是 Burstable

自动注入FileBeat Sidecar容器

定义Nginx服务Deployment,只包含 nginx 相关配置,如下:

  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4. labels:
  5. app: nginx
  6. name: nginx
  7. spec:
  8. replicas: 1
  9. selector:
  10. matchLabels:
  11. app: nginx
  12. template:
  13. metadata:
  14. labels:
  15. app: nginx
  16. # 注入 filebeat sidecar 容器的label
  17. kruise.io/inject-filebeat: "true"
  18. spec:
  19. containers:
  20. - name: nginx
  21. image: nginx:latest
  22. volumeMounts:
  23. # 通过 volumeMounts 与filebeat sidecar容器共享 log 目录
  24. - mountPath: /var/log/nginx
  25. name: log
  26. volumes:
  27. - name: log
  28. emptyDir: {}

将nginx deployment apply到k8s集群后,发现创建的Pod都被注入了 filebeat sidecar 容器,如下:

  1. helm-charts% kubectl get pods nginx-5674976569-zdr7l -o yaml
  2. status:
  3. containerStatuses:
  4. - containerID: containerd://5330c2b32262de83ed387e5a932f61acc52e3896ddfcb22d626c43d82638faf3
  5. image: docker.elastic.co/beats/filebeat:7.16.2
  6. name: filebeat
  7. state:
  8. running:
  9. startedAt: "2022-03-02T12:17:15Z"
  10. - containerID: containerd://1ad335f39c134f7a66a0370a275dd95f67f5fd3d3f1fe523c955408b14887229
  11. image: docker.io/library/nginx:latest
  12. name: nginx
  13. state:
  14. running:
  15. 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实例),如下:

k8s log sidecar

该特性依赖Kruise原地升级的能力实现,详情参考文档:Kruise原地升级。 不过独立升级sidecar容器也存在一定的风险性,如果sidecar容器升级过程中失败,则将导致Pod Not Ready,进而影响业务,因此SidecarSet本身提供了非常丰富的灰度发布能力来尽量规避该风险, 详情参考文档:Kruise SidecarSet,如下:

  1. apiVersion: apps.kruise.io/v1alpha1
  2. kind: SidecarSet
  3. metadata:
  4. name: sidecarset
  5. spec:
  6. # ...
  7. updateStrategy:
  8. type: RollingUpdate
  9. # 最大不可用数量
  10. maxUnavailable: 20%
  11. # 分批发布
  12. partition: 90
  13. # 金丝雀发布,通过pod labels
  14. selector:
  15. matchLabels:
  16. # Some Pods contain canary labels,
  17. # or any other labels where a small number of pods can be selected
  18. deploy-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等,如下:

  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4. labels:
  5. app.kubernetes.io/name: argocd-cm
  6. app.kubernetes.io/part-of: argocd
  7. name: argocd-cm
  8. namespace: argocd
  9. data:
  10. resource.customizations.health.apps.kruise.io_SidecarSet: |
  11. hs = {}
  12. -- if paused
  13. if obj.spec.updateStrategy.paused then
  14. hs.status = "Suspended"
  15. hs.message = "SidecarSet is Suspended"
  16. return hs
  17. end
  18. -- check sidecarSet status
  19. if obj.status ~= nil then
  20. if obj.status.observedGeneration < obj.metadata.generation then
  21. hs.status = "Progressing"
  22. hs.message = "Waiting for rollout to finish: observed sidecarSet generation less then desired generation"
  23. return hs
  24. end
  25. if obj.status.updatedPods < obj.spec.matchedPods then
  26. hs.status = "Progressing"
  27. hs.message = "Waiting for rollout to finish: replicas hasn't finished updating..."
  28. return hs
  29. end
  30. if obj.status.updatedReadyPods < obj.status.updatedPods then
  31. hs.status = "Progressing"
  32. hs.message = "Waiting for rollout to finish: replicas hasn't finished updating..."
  33. return hs
  34. end
  35. hs.status = "Healthy"
  36. return hs
  37. end
  38. -- if status == nil
  39. hs.status = "Progressing"
  40. hs.message = "Waiting for sidecarSet"
  41. return hs

总结

Pod包含多个container的方式将越来越被更多的开发者接受,进而K8S生态里面急需一种能够有效管理Sidecar容器的方式。 Kruise SidecarSet是在sidecar容器管理上面的一些探索,目前社区也有很多的公司在使用Kruise SidecarSet管理不同类型的sidecar容器。

SidecarSet在带来便利的同时,其实也带来了一些管理上面的成本,例如:Sidecar容器与业务app容器同时发布怎么办?Pod中container属于多个团队,那Pod的所属权到底属于谁? 所以,我们也希望能够与社区的更多开发者一起探索,同时也欢迎大家都能提供一些思路,共同繁荣K8S生态。