OpenKruise x iLogtail: 管理可观测数据采集 Sidecar 容器最佳实践

余韬(迅飞)

摘要

在 Kubernetes 集群中使用 Sidecar 容器采集业务容器的可观测性数据是一种常见的数据采集部署方式,然而 Sidecar 容器对业务部署的侵入性和生命周期管理的复杂性使得这种部署模式的管理代价不仅高昂而且容易出错。本文对 Sidecar 采集容器管理的难点进行分析,使用 OpenKruise 提供的管理能力逐一解决,并以 iLogtail 为例给出基于 OpenKruise 管理可观测数据采集 Sidecar 容器的最佳实践。

Sidecar部署方式的使用场景

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

  • 稳定性:Sidecar 采集方式利用 K8s 同一个 Pod 内的容器可以共享存储和网络的特性,无容器发现过程。同时只要采集容器没有退出,共享存储卷上的文件就不会被删除,因此无论是业务容器的生命周期非常短或是日志采集出现延时的情况都可以保证采集数据的完整性。
  • 灵活性:作为在每个 Pod 单独部署的 Agent,为其分配多少资源、采集哪些数据都可以根据业务需求定制,甚至不同业务可以因不同采集需求而部署不同种类和版本的 Agent。
  • 多租户隔离性:各个 Sidecar 容器内部署的 Agent 仅能观测到各自 Pod 内的数据,无法获取其他租户的信息。Agent 之间互相独立运行,当某个 Agent 崩溃或无法正常采集数据时,同节点上的其他 Agent 仍然可以正常工作。

K8s Sidecar 模式的痛点

使用 K8s Sidecar 模式采集业务可观测性数据需要修改业务的部署声明,将采集 Agent 容器与业务 App 容器部署在同一个 Pod 内,并通过共享 Volume 和网络使 Agent 可以采集数据并上传。如果要做到数据采集在任何情况下都不发生丢失,那么还需要确保 Agent 进程在业务 App 进程启动前启动、Agent 进程在 App 进程结束后退出。对于 Job 类型的工作负载还需要考虑如何使 Agent 在 App 容器完成后主动退出的问题。因此,下面以 iLogtail 作为采集 Agent 为例,给出通用的 Sidecar 采集部署配置。

iLogtail 是阿里云开源的一款高性能的轻量级可观测性数据采集 Agent,支持多种 Logs、Traces、Metrics 可观测数据采集到 Kafka、ElasicSearch、ClickHouse 等多种下游中,其稳定性已在阿里巴巴以及数万阿里云客户生产中使用验证。

apiVersion: batch/v1
kind: Job
metadata:
name: nginx-mock
namespace: default
spec:
template:
metadata:
name: nginx-mock
spec:
restartPolicy: Never
containers:
- name: nginx
image: registry.cn-hangzhou.aliyuncs.com/log-service/docker-log-test:latest
command: ["/bin/sh", "-c"]
args:
- until [[ -f /tasksite/cornerstone ]]; do sleep 1; done;
/bin/mock_log --log-type=nginx --path=/var/log/nginx/access.log --total-count=100;
retcode=$?;
touch /tasksite/tombstone;
exit $retcode
# 通知Sidecar容器业务完成,否则Sidecar不会随业务容器退出
volumeMounts:
# 通过volumeMounts与iLogtail sidecar容器共享 log 目录
- mountPath: /var/log/nginx
name: log
# 通过volumeMounts与iLogtail sidecar容器通信进程状态
- mountPath: /tasksite
name: tasksite
# iLogtail Sidecar
- name: ilogtail
image: sls-opensource-registry.cn-shanghai.cr.aliyuncs.com/ilogtail-community-edition/ilogtail:latest
command: ["/bin/sh", "-c"]
# 第一个`sleep 10`的目的是等待iLogtail开始采集。iLogtail可能需要等从远端成功拉取配置后才开始采集。
# 第二个`sleep 10`的目的是等待iLogtail完成采集。直到iLogtail将所有收集数据发送给下游才算完成采集。
args:
# 通知业务容器Sidecar就绪
- /usr/local/ilogtail/ilogtail_control.sh start;
sleep 10;
touch /tasksite/cornerstone;
until [[ -f /tasksite/tombstone ]]; do sleep 1; done;
sleep 10;
/usr/local/ilogtail/ilogtail_control.sh stop;
volumeMounts:
- name: log
mountPath: /var/log/nginx
- name: tasksite
mountPath: /tasksite
- mountPath: /usr/local/ilogtail/ilogtail_config.json
name: ilogtail-config
subPath: ilogtail_config.json
volumes:
- name: log
emptyDir: {}
- name: tasksite
emptyDir:
medium: Memory
- name: ilogtail-config
secret:
defaultMode: 420
secretName: ilogtail-secret

配置中的 volumeMounts 部分声明了共享存储,其中 name 为 log 的用于共享数据,name 为 tasksite 的用于进程协调。args 部分大量代码用于控制 Sidecar 和业务容器间进程的启动及退出顺序。通过以上配置代码可以看到,Sidecar 模式存在如下一些弊端:

  • 业务 Pod 耦合(运维、代理)多种 Sidecar 容器,增加业务开发人员的学习成本。
  • Pod 中的多个 Sidecar 容器和业务需要考虑运行依赖关系,增加了配置的复杂性。
  • Sidecar 容器升级将导致业务 Pod 重建,由于 Sidecar 容器一般是独立的中间件团队负责,如果升级会存在极大的业务方阻力。

OpenKruise 管理 Sidecar 容器的利器

SidecarSet

SidecarSet 是 OpenKruise 中针对 Sidecar 容器管理抽象出来的概念,负责注入和升级 K8s 集群中的 Sidecar 容器,是 OpenKruise 的核心 workload 之一,详细可参考:SidecarSet文档

  • 自动注入 Sidecar:将 Sidecar 容器配置与业务 Workload(Deployment、CloneSet 等)配置解耦,简化用户使用成本。
  • 独立升级 Sidecar 容器:不重建 Pod,单独升级 Sidecar 容器,对业务无感。

Container Launch Priority

Container Launch Priority 提供了控制一个 Pod 中容器启动顺序的方法,详细可参考:Container Launch Priority文档

  • 控制一个 Pod 中容器启动顺序:按声明中的顺序启动或根据自定义顺序启动,简化配置。

Job Sidecar Terminator

对 Job 类型的 Workload 提供业务主容器完成任务并退出后,通知日志收集等 Sidecar 容器退出的能力,详细可参考:Job Sidecar Terminator文档

  • 提供 Job Sidecar 主动退出能力:主容器退出后通知 Sidecar容器退出,从而使得 Job Controller 能够正确完成,简化配置。

iLogtail Sidecar 部署实践

社区中有很多应用 iLogtail 部署可观测方案的案例,参考Kubernetes使用iLogtail社区版使用案例。本文聚焦如何使用 OpenKruise 的上述能力简化 iLogtail Sidecar 的管理,如果遇到 iLogtail 方面的问题请到 GitHub Discussions 提问。

OpenKruise Feature Gate

在 kruise-controller-manager Deployment 中启用 SidecarTerminator 特性,另外两项特性默认打开。

spec:
containers:
- args:
- '--enable-leader-election'
- '--metrics-addr=:8080'
- '--health-probe-addr=:8000'
- '--logtostderr=true'
- '--leader-election-namespace=kruise-system'
- '--v=4'
- '--feature-gates=SidecarTerminator=true'
- '--sync-period=0'

iLogtail SidecarSet

ConfigServer开源版 中创建 iLogtail 采集配置,如下:

enable: true
inputs:
- Type: file_log
LogPath: /var/log/nginx
FilePattern: access.log
flushers:
- Type: flusher_kafka
Brokers:
- <kafka_host>:<kafka_port>
- <kafka_host>:<kafka_port>
Topic: nginx-access-log

关联到机器组

定义 iLogtail SidecarSet 配置,如下:

apiVersion: apps.kruise.io/v1alpha1
kind: SidecarSet
metadata:
name: ilogtail-sidecarset
spec:
selector:
# 需要注入 sidecar 容器的 pod labels
matchLabels:
kruise.io/inject-ilogtail: "true"
# SidecarSet默认是对整个集群生效,可以通过namespace字段指定生效的范围
namespace: default
containers:
- command:
- /bin/sh
- '-c'
- '/usr/local/ilogtail/ilogtail_control.sh start_and_block 10'
# 参数10的目的是等待10秒数据发送完毕后退出
image: sls-opensource-registry.cn-shanghai.cr.aliyuncs.com/ilogtail-community-edition/ilogtail:edge
livenessProbe:
exec:
command:
- /usr/local/ilogtail/ilogtail_control.sh
- status
name: ilogtail
env:
- name: KRUISE_CONTAINER_PRIORITY
value: '100'
- name: KRUISE_TERMINATE_SIDECAR_WHEN_JOB_EXIT
value: 'true'
- name: ALIYUN_LOGTAIL_USER_DEFINED_ID
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: 'metadata.labels[''ilogtail-tag'']'
volumeMounts:
- mountPath: /var/log/nginx
name: log
- mountPath: /usr/local/ilogtail/checkpoint
name: checkpoint
- mountPath: /usr/local/ilogtail/ilogtail_config.json
name: ilogtail-config
subPath: ilogtail_config.json
volumes:
- name: log
emptyDir: {}
- name: checkpoint
emptyDir: {}
- name: ilogtail-config
secret:
defaultMode: 420
secretName: ilogtail-secret

注意,此处仅写了 livenessProbe,请勿使用 readinessProbe,否则后续 Sidecar 容器升级时会导致 Pod 状态先变成 Not Ready。

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

自动注入 iLogtail Sidecar 容器

定义 Nginx Mock Job,只包含 nginx 相关配置,如下:

apiVersion: batch/v1
kind: Job
metadata:
name: nginx-mock
namespace: default
spec:
template:
metadata:
name: nginx-mock
labels:
# 注入 ilogtail sidecar 容器的label
kruise.io/inject-ilogtail: "true"
spec:
restartPolicy: Never
containers:
- name: nginx
image: registry.cn-hangzhou.aliyuncs.com/log-service/docker-log-test:latest
command: ["/bin/sh", "-c"]
args:
- /bin/mock_log --log-type=nginx --path=/var/log/nginx/access.log --total-count=100
volumeMounts:
# 通过volumeMounts与iLogtail sidecar容器共享 log 目录
- mountPath: /var/log/nginx
name: log
volumes:
- name: log
emptyDir: {}
- name: tasksite
emptyDir:
medium: Memory

将 Nginx Mock Job Apply 到 K8s 集群后,发现创建的 Pod 都被注入了 iLogtail Sidecar 容器,说明 kruise.io/inject-ilogtail: “true” 起到了效果,如下:

若没有此项配置,将只有 nginx container 而没有 ilogtail container。

通过查阅 K8s 事件可知,ilogtail container 优先于 nginx container 创建,说明 KRUISE_CONTAINER_PRIORITY 起到了效果,如下:

若没有此项配置,ilogtail container 可能晚于 nginx container 启动,导致采集数据不完整。

通过查阅 K8s 事件还可知,ilogtail container 在 nginx container 完成后退出,说明 KRUISE_TERMINATE_SIDECAR_WHEN_JOB_EXIT 起到了效果,如下:

若没有此项配置,Pod 将一直处于 Running 状态,因为 Sidecar 不会随业务容器退出,除非手动 Delete Job。

以上两项 Sidecar 容器启动和退出的正确顺序保证了 iLogtail 采集数据的完整性。查询采集到 Kafka 的数据,从点位计算可知,数据采集完整,共100条(92-40+106-58=100),如下:

独立升级 iLogtail Sidecar 容器(edge -> latest)

我们将上面 iLogtail SidecarSet 的配置更新为 latest 并 apply。

image: sls-opensource-registry.cn-shanghai.cr.aliyuncs.com/ilogtail-community-edition/ilogtail:latest

通过 K8s 事件可以发现 Pod 并没有重建,ilogtail 容器由于声明发生了变化进行了重建,而同时 nginx 容器并没有受影响中断。如下:

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

apiVersion: apps.kruise.io/v1alpha1
kind: SidecarSet
metadata:
name: sidecarset
spec:
# ...
updateStrategy:
type: RollingUpdate
# 最大不可用数量
maxUnavailable: 20%
# 分批发布
partition: 90
# 金丝雀发布,通过pod labels
selector:
matchLabels:
# Some Pods contain canary labels,
# or any other labels where a small number of pods can be selected
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 等,如下:

apiVersion: v1
kind: ConfigMap
metadata:
labels:
app.kubernetes.io/name: argocd-cm
app.kubernetes.io/part-of: argocd
name: argocd-cm
namespace: argocd
data:
resource.customizations.health.apps.kruise.io_SidecarSet: |
hs = {}
-- if paused
if obj.spec.updateStrategy.paused then
hs.status = "Suspended"
hs.message = "SidecarSet is Suspended"
return hs
end
-- check sidecarSet status
if obj.status ~= nil then
if obj.status.observedGeneration < obj.metadata.generation then
hs.status = "Progressing"
hs.message = "Waiting for rollout to finish: observed sidecarSet generation less then desired generation"
return hs
end
if obj.status.updatedPods < obj.spec.matchedPods then
hs.status = "Progressing"
hs.message = "Waiting for rollout to finish: replicas hasn't finished updating..."
return hs
end
if obj.status.updatedReadyPods < obj.status.updatedPods then
hs.status = "Progressing"
hs.message = "Waiting for rollout to finish: replicas hasn't finished updating..."
return hs
end
hs.status = "Healthy"
return hs
end
-- if status == nil
hs.status = "Progressing"
hs.message = "Waiting for sidecarSet"
return hs

总结

Pod 包含多个 container 的方式将越来越被更多的开发者接受,进而 K8S 生态里面急需一种能够有效管理 Sidecar 容器的方式。 Kruise SidecarSet、Container Launch Priority、Job Sidecar Terminator 等功能是在 Sidecar 容器管理上面的一些探索,

使用 OpenKruise 管理 iLogtail 日志采集大幅降低了管理 Sidecar 的难度,解耦了 Sidecar 和业务容器的配置,使得容器间的启动顺序维护变得清晰,并且支持不重建 Pod 更新 Sidecar 容器。但是我们也看到比如 Sidecar 容器应该分配多少资源、日志路径挂载如何规划、不同业务 Pod 中的 Sidecar 的配置如何区分等问题并没有完全解决。 所以,我们也希望能够与社区的更多开发者一起探索,同时也欢迎大家都能提供一些思路,共同繁荣 K8S 生态。


observability.cn Authors 2024 | Documentation Distributed under CC-BY-4.0
Copyright © 2017-2024, Alibaba. All rights reserved. Alibaba has registered trademarks and uses trademarks.
浙ICP备2021005855号-32