阿里妹导读
iLogtail作为开源可观测数据采集器,对Kubernetes环境下日志采集有着非常好的支持,本文跟随iLogtail的脚步,了解容器运行时与K8s下日志数据采集原理。
如今,Kubernetes在业界几乎已经成为了容器管理的标准。在Kubernetes架构中,容器运行时(如Docker、containerd等)充当了基础层的角色,负责容器的创建、执行和管理。由于Kubernetes的设计是高度模块化的,它支持多种容器运行时,这给开发者带来了灵活性。开发者可以根据不同的需求选择最合适的运行时,甚至在同一个集群内使用多种运行时,这是Kubernetes生态的一个显著优势。
iLogtail作为开源可观测数据采集器,对Kubernetes环境下日志采集有着非常好的支持,下面就跟随iLogtail的脚步,了解容器运行时与K8s下日志数据采集原理。
K8s与容器运行时简介
正如前面所说,Kubernetes的设计是高度模块化的,那么模块化的基础就是统一的接口或者标准。容器运行时这个概念比较广泛,我们平常所听到的Docker、Containerd、Runc、Kata等都可以叫做运行时,但是他们又明显地处于不同层次上,比如Containerd会依赖Runc去真正地拉起容器。因此在这里做了一些概念划分,在Kubernetes架构下,像Containerd这种跟CRI进行交互的,叫做高级容器运行时;像RunC这种跟OCI交互的,叫做基础容器运行时。
接下来我们先了解一下CRI和OCI的概念。
CRI与高级容器运行时
CRI(ContainerRuntimeInteface容器运行时接口)本质上就是Kubernetes定义的一组与容器运行时进行交互的接口,所以只要实现了这套接口的容器运行时都可以对接到Kubernetes平台上来。
Kubelet通过gRPC框架与容器运行时或shim进行通信,其中kubelet作为客户端,CRIshim(也可能是容器运行时本身)作为服务器。通过CRI的接口定义可以看到,大概分了两套接口:
1.针对container和sandbox的接口;
2.针对镜像操作的接口;
比方说我们通过kubectl命令来运行一个Pod,那么Kubelet就会通过CRI执行以下操作:
首先调用RunPodSandbox接口来创建一个Pod容器,Pod容器是用来持有容器的相关资源的,比如说网络空间、PID空间、进程空间等资源;
然后调用CreatContainer接口在Pod容器的空间创建业务容器;
再调用StartContainer接口启动容器,相对应的销毁容器的接口为StopContainer与RemoveContainer。
DockerDocker的历史可以追溯到2010年,当时由容器技术的先驱SolomonHykes在法国巴黎的dotCloud公司创立。最初,Docker并不是一个独立的项目,而是dotCloud平台的一部分,旨在提供一种轻量级的虚拟化解决方案。Docker的核心思想是通过使用Linux内核的命名空间和控制组(cgroups)技术,来实现轻量级的容器化,这使得应用程序可以在隔离的环境中运行而不需要整个操作系统的虚拟化。这种方式大大减少了资源开销,提高了应用的部署速度。
在Kubernetes的早期,也只支持一个容器运行时,那就是Docker。Kubernetes推出CRI这套标准的时候,比如Docker本身并没有实现CRI接口,于是就有了shim(垫片),一个shim的职责就是作为适配器将各种容器运行时本身的接口适配到Kubernetes的CRI接口上,其中dockershim就是Kubernetes对接Docker到CRI接口上的一个垫片实现。
当Kubelet想要使用Docker运行时创建一个容器时,它需要以下几个步骤:
Kubelet通过CRI接口(gRPC)调用dockershim,请求创建一个容器,CRI(容器运行时接口,ContainerRuntimeInterface)。在这一步中,Kubelet可以视作一个简单的CRIClient,而dockershim就是接收请求的Server。
dockershim收到请求后,它会转化成DockerDaemon的请求,发到DockerDaemon上,并请求创建一个容器。
DockerDaemon在1.12版本中将针对容器的操作移到另一个守护进程containerd中了。因此DockerDaemon请求containerd创建一个容器。
containerd收到请求后,创建一个containerd-shim的进程,让containerd-shim去操作容器。containerd-shim在这一步需要调用Runc这个命令行工具,来真正启动容器。
Runc启动完容器后,它会直接退出,containerd-shim则会成为容器进程的父进程,负责收集容器进程的状态,上报给containerd。
Containerd从前面Kubelet使用Docker启动容器的过程可以看到,从Kubelet到容器启动的过程中,有非常长的链路和非常多的步骤。但是最核心的步骤是Containerd-Containerd-shim-Runc,
DockerDaemon和Docker-shim仿佛是可以忽略的。
因此在Kubernetes提出CRI之后,Containerd迅速完成了适配。在中,对CRI的适配通过一个单独的进程CRI-containerd来完成:
中做的又更漂亮一点,砍掉CRI-containerd进程,直接把适配逻辑作为插件放进containerd主进程中:
CRI-OKubernetes的主流运行时经历了从Docker到Containerd的转变,流程变得愈加简化。此外,还有一个比Containerd更为纯粹且专注的Kubernetes运行时解决方案—CRI-O,它专为Kubernetes设计,并且兼容CRI和OCI,提供了更为简单的运行时体验。
Containerd是一个更通用的容器运行时,旨在提供一个高效的容器管理系统。它支持构建、运行和存储容器镜像,适用于各种编排工具,如Kubernetes和DockerSwarm。Containerd提供了丰富的API和插件机制,使其能够与多种环境和体系结构兼容。
相对而言,CRI-O是专为Kubernetes设计的轻量级容器运行时。它遵循Kubernetes的CRI(容器运行时接口)标准,专注于提供与Kubernetes生态系统的无缝集成。CRI-O旨在简化对Kubernetes的容器管理,去掉了不必要的功能,力求在安全性、性能和资源占用上做到极致,减少了额外的复杂性。
OCI与基础容器运行时
开放容器标准(OpenContainerInitiative,OCI)是一个由多个技术公司、开发者和社区成员组成的行业合作项目,旨在为容器技术建立开放标准。OCI于2015年成立,目的是为了推动容器生态系统的发展,减少不同容器运行时及映像格式之间的不兼容性。
OCI目前包含三个规范:
运行时规范(runtime-spec):定义了如何运行容器,包括配置、生命周期和沙箱环境。它提供了一种标准的方法,使得容器能够在任何遵循该标准的运行时中被启动。简单来说,它规定的就是“容器”要能够执行“create”“start”“stop”“delete”这些命令,并且行为要规范。
镜像规范(image-spec):规定了容器映像的格式和内容结构,确保镜像可以在不同的容器运行时之间顺利传输和使用。
分发规范(distribution-spec):是关于如何在不同的容器注册中心之间分发和获取容器映像的标准。
CRI和OCI一起,构成了Kubernetes容器领域的事实规范。任何实现了CRI的高级容器运行时,都可以成为Kubernetes的运行时。任何实现了OCI的低级运行时,也可以成为高级容器运行时的基础依赖。
RuncRunc的前身实际上是Docker的libcontainer项目演化而来。Runc实际上就是libcontainer配上了一个轻型的客户端。
Runc是一个轻量级的容器运行时,用于根据OCI(OpenContainerInitiative)标准运行容器。它是最基础的容器运行时解决方案,可以在没有额外服务管理的情况下启动和运行容器。Docker和其他较高级的容器管理工具在内部使用Runc来实际启动和运行容器。Runc的目标是提供一个通用的容器运行时,侧重于简单、可移植和可扩展。
隔离机制:它直接在宿主机上运行容器进程,并使用Linux的namespace和cgroups等内核特性来提供隔离和资源限制。
安全性:虽然Runc通过Linux的安全特性(如SELinux、AppArmor、seccomp等)提供基本的安全保障,但它仍然共享同一内核,与其他在同一宿主机上运行的容器相比,隔离性并不是最强的。对于需要强隔离的场景,可能需要其他解决方案。
性能:Runc为容器提供了接近原生Linux性能,启动速度快,资源开销小,适合大规模部署和微服务架构。
使用场景:广泛用于开发、测试和生产环境,是Docker和许多Kubernetes部署的默认容器运行时。
Runc作为一种容器运行时,使用共享内核的方式来管理容器的隔离和资源分配。然而,这种共享内核的架构也带来了一些安全隐患,主要体现在以下几个方面:
1.内核漏洞:所有容器共享同一内核,因此如果内核出现漏洞,攻击者可以利用这些漏洞从一个容器突破到宿主机或其他容器。这使得容器之间的隔离不如完全虚拟化的方式安全。
2.系统调用滥用:容器内的进程可以通过系统调用与内核交互。一些系统调用可能被恶意容器滥用以获取更高的权限或访问宿主机的资源。
3.特权容器:如果某个容器被配置为特权容器,它将拥有更高的权限,可能直接访问宿主机的硬件或敏感资源,从而导致安全风险。
4.配置错误:容器的配置不当可能导致安全漏洞,如未正确配置的网络或存储,可能使容器之间能够互相访问,从而影响隔离性。
Kata/RunD正如前面所说的,Runc在安全方面的各种问题,因此需要一种更加安全的运行时。
Kata安全容器和普通容器相比有2个主要的区别:
1.容器运行在虚拟机里;
2.镜像要从宿主机上传递给虚拟机;
Shim-v2会为Pod启动一个虚拟机作为PodSandbox提供隔离性,其中运行着一个Linux内核,通常这个Linux内核是一个裁剪过的内核,不会支持没有必要的设备。
这里用的虚拟机可以是Qemu或是Firecracker,它只是支撑容器应用运行的基础设施没有完整操作系统。
RunD是阿里提出的新型安全容器,简言之就是增强版的Kata,在资源占用、网络、存储等方面均有优化。
K8s下容器日志数据采集难点
在Kubernetes(K8s)环境下,容器日志数据采集面临着多重挑战:
1.生命周期短容器的短暂性和高度动态特性使得它们的生命周期极其短暂,这意味着许多容器可能在运行期间生成重要的日志信息,但一旦容器被销毁,这些信息就会随之消失。日志的瞬时性给日志采集带来了极大的挑战。
2.分布式架构容器通常部署在多个节点上,这种分布式架构导致日志数据存储在不同的位置。数据的分散性不仅增加了管理的复杂性,而且可能导致实时数据采集和分析的困难。
3.存储方式多样容器的存储方式各异,包括卷、挂载等。这些存储方式的多样性使得日志的写入、读取和管理变得更加复杂。
4.数据与容器元信息关联容器日志作为可观测数据,必须与容器的元信息(如容器ID、名称、标签等)进行精准关联,以便于后续的查询与分析,有效地匹配数据与容器信息有助于快速定位问题源头。然而,由于容器的频繁创建和销毁,以及其动态变化的特性,在数据采集时及时、准确地建立这种关联,依然是一大挑战。
开源采集器容器日志采集方案
Filebeat
容器标准输出采集通过官方文档[1]可以看到,Filebeat推荐是以daemonset的方式部署在K8s集群中的,然后这里需要把节点宿主机上的:/var/log和/var/lib/docker/containers挂载进filebeat的pod中。这个采集原理跟前面运行时就有关系了。
当Docker作为K8s容器运行时,容器日志的落盘将由docker来完成,保存在:
/var/lib/docker/containers/目录下。Kubelet会在/var/log/pods和/var/log/containers下建立软链接,指向/var/lib/docker/containers/目录下的容器日志文件。
当Containerd作为k8s容器运行时,容器日志的落盘由Kubelet来完成,保存至/var/log/pods/目录下,同时在/var/log/containers目录下创建软链接,指向日志文件。
所以,总结下来:
1./var/lib/docker/containers/:Docker日志落盘路径,文件路径格式/var/lib/docker/containers/${container_id}/${container_id}-
2./var/log/containers/:一般都是软连接,文件路径格式/var/log/containers/${pod_name}_${namespace}_${container_name}-${container_id}.log
3./var/log/pods/:kubelet日志落盘路径,文件路径格式/var/log/pods/${namespace}_${pod_name}_${pod_uid]}/${containername}/0.log
所以一般采集标准输出文件,都是采集/var/log/containers路径,因为这个路径下有完整的所有容器的标准输出文件。
从官方文档[1],可以看到,Filebeat支持在K8s环境自动发现容器并采集容器数据。
K8s配置样例:
:providers:-type:kubernetestemplates:-condition:equals::kube-systemconfig:-type:containerpaths:-/var/log/containers/*-${}.logexclude_lines:["^\\s+[\\-`('.|_]"]应用容器-name:nginximage:lizhenliang/nginx-php日志采集器容器-name:filebeatimage:elastic/filebeat:7.10.1args:["-c","/etc/","-e",]resources:requests:cpu:100mmemory:100Milimits:memory:500MisecurityContext:runAsUser:0volumeMounts:将数据卷挂载到日志目录-name:nginx-logsmountPath:/usr/local/nginx/logsforcontainerruntimesocketname:run-mountPath:/logtail_host在这里添加Job元信息,比如name和namespacename:${job_name}namespace:${namespace}spec:template:spec:restartPolicy:Nevercontainers:替换为业务容器的实际启动命令${container_start_cmd};retcode=$?;touch/tasksite/tombstone;exit$retcodevolumeMounts:与Logtail容器交互的挂载点-mountPath:/tasksitename:tasksite等待Logtail配置下载完成touch/tasksite/cornerstone;until[[-f/tasksite/tombstone]];dosleep1;done;sleep10;设置时区。请根据kubernetes集群所在地域,配置时区,格式为"地区/城市"。如果是中国大陆,可以设置时区为Asia/Shanghai。追加Pod环境信息作为日志标签-name:"ALIYUN_LOG_ENV_TAGS"value:"_pod_name_|_pod_ip_|_namespace_|_node_name_|_node_ip_"Logtail容器的日志目录挂载到共享存储卷-name:${shared_volume_name}mountPath:${dir_containing_your_files}定义空的共享存储卷用于日志存储-name:${shared_volume_name}emptyDir:{}#定义存储卷用于容器间通信-name:tasksiteemptyDir:medium:MemoryiLogtail的SideCar模式用于日志数据采集,适用于多种特定场景,具体包括:
1.单节点Pod数据量大:当一个节点上的Pod数据量异常庞大,远超出Daemonset的采集性能上限时,使用SideCar模式是非常推荐的。这种模式允许我们为iLogtail分配特定的资源,从而提升其日志采集的性能和稳定性,确保关键Pod的日志能够被及时高效地收集,为后续的监控和分析提供坚实的数据基础。
2.Serverless容器日志采集:在Serverless容器架构中,由于缺乏节点的概念,传统的Daemonset部署模式无法应用。此时,SideCar模式显得尤为重要,它能够有效地与无服务器架构结合,保证日志采集过程的灵活性和适应性。
3.K8s+安全容器运行时的日志采集:在使用安全容器运行时的Kubernetes环境中,Daemonset无法访问宿主机的其他容器的标准输出文件或日志。这时,SideCar模式便成为唯一可行的解决方案。通过在同一Pod内集成iLogtail,能够直观地获取和处理容器里的日志,确保即使在安全限制下,重要的日志数据依然能够被实时采集。
SiderCar模式+容器元信息文件
iLogtail商业版与阿里云的容器团队紧密合作,推出了一种优化版本的Sidecar模式。具体而言,当容器团队启动iLogtailSidecar容器时,会通过挂载的方式将业务容器的相关信息提供给iLogtail。这一机制使得iLogtail在进行日志数据上报时,不仅能传递基本的日志内容,还能够附带业务容器的丰富字段信息,如容器名称、标签、环境变量等。这种深度集成的方式极大地增强了日志的上下文信息,提高了后续数据分析的效率,为用户提供了更为全面、直观的日志信息。
目前该模式支持的容器场景有弹性容器ECI[9]和容器计算服务ACS[10]。
总结
在Daemonset模式下,与开源的APIserver请求的解决方案有显著的不同,iLogtail选择了与运行时进行直接交互。这种与运行时的交互方式带来了多个显著优势:
1.获取容器内的rootfs挂载信息:通过与运行时的交互,iLogtail能够深入挖掘容器内的文件系统信息,从而成功采集节点上Pod内自定义日志文件。这一能力是通过APIserver交互无法实现的。
2.实时获取容器元信息:与运行时的直接交互方式使得iLogtail能够更快速地获取节点上正在运行的容器的元数据信息,这种实时性对日志监控的实时性至关重要。
3.减轻APIServer压力:Daemonset通过APIServer进行交互时,往往会对APIserver造成显著的压力,尤其是在大规模集群中。iLogtail的设计减少了这种依赖,从而使APIServer能够更专注于处理其他重要的请求,提升整体系统的稳定性和响应能力。
4.兼容非K8s场景:iLogtail的设计不仅限于Kubernetes环境,也兼顾到纯Docker等其他容器运行环境。这种灵活性使得iLogtail能够在更广泛的场景中应用,不论是在微服务架构下的Kubernetes部署,还是在传统的Docker容器中,都能够提供强大的日志采集能力。
在Sidecar模式下,iLogtail更是与阿里云的Serverless容器实现了深度集成。通过这种集成,iLogtail能够轻松获取容器的元信息,为日志数据提供更精准的字段丰富化。这一特性进一步增强了日志分析的效果,使得用户能够通过更为详尽的信息做出快速响应。
参考文章:[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]
[10]
[11]一文搞懂容器运行时Containerd:
[12]OCIRuntimeSpecification介绍:
[13]cri-o文档:
[14]k3s文档:
[15]microk8s文档:
[16]K8SRuntime种类多,使用复杂?那是你没明白其中的门道:
[17]从零开始入门K8s:理解容器运行时接口CRI:
[18]FluentBit文档:
免责声明:本文章如果文章侵权,请联系我们处理,本站仅提供信息存储空间服务如因作品内容、版权和其他问题请于本站联系