今天,我们聊一下Kubernetes Container相关话题,什么是Container?
容器和虚拟机器一样,可以让我们将应用程序与依赖库及其他依附元件封装在一起,提供隔离的环境来执行软件服务。但是,对比VM而言,容器提供的是一种逻辑封装机制,能够将应用程序从实际执行所在环境中抽取出来。无论目标环境是私有云、公用云端还是我们自己的本地环境,这种分离的方式让我们可以轻松、一致地部署容器型应用程序。
容器化提供了一种便利的分工模式,当我们开发人员专注于应用程序逻辑与依附元件时,IT 运维团队可将注意力集中到部署与管理上,而不用担心诸如特定软体版本与应用程式特定设定之类的应用程式细节。
本文主要针对K8S生态栈中(注:我的环境为Kubernetes-1.18.15)所支持的容器类型进行解析,以及详细介绍一下不同类型容器的特性以及其使用场景。具体我们先了解下当前所支持的容器类型,具体如下所示:
基于上述图中,我们可以看出,目前在Kubernetes 1.18 version中,已经支持4种不同类型的容器,分别为:标准容器(主容器)、Sidecar容器、Init 容器以及Ephemeral 容器等。
下面我们针对此4种类型的容器进行简要的解析,在解析之前,我们简要了解下Pod的生命周期,毕竟,Container与Pod息息相关,相互关联,一个Pod的生命活动涉及到Container相关活动事件,具体如下所示:
上图展示了一个Pod的完整生命周期,其中包含Init Container、Pod Hook、健康检查等 三个核心组成部分,整个Pod生命周期围绕 “ 挂起 (Pending) ”、“ 运行中 (Running) ”、“ 成功(Succeeded) ”、“ 失败(Failed) ”以及“ 未知 (Unknown) ”等状态运行,具体细节后续将会解析。我们注意到有个被称为“Pod Hook”的事件,由Kubelet发起,其在容器中的进程启动前或者容器的进程终止之前执行,我们可以同时为Pod 中的所有容器都配置Hook(钩子)。
K8S中生态为我们提供了2种Hook函数:
PostStart:此函数在容器创建后立即执行。主要用于资源部署、环境准备等。但是,并不能保证钩子将在容器ENTRYPOINT之前运行,因为没有参数传递给处理程序。不过需要注意的是如果钩子花费太长时间以至于不能运行或者挂起,容器将不能达到Running状态。
PreStop:此函数在容器终止之前立即被调用。主要用于优雅关闭应用程序、通知其他系统等。其基于阻塞模型, 因此必须在删除容器的调用发出之前完成。如果钩子在执行期间挂起,Pod阶段将停留在Running状态并且永不会达到Failed状态。
了解了Pod状态后,首先来了解下Pod中最新启动的Init Container,也就是我们平时常说的初始化容器。
Init Container-初始化容器
Init Container是一种特殊容器,在 Pod 内的应用容器启动之前运行。Init Container 可以包括一些应用镜像中不存在的实用工具及脚本。
Init Container作为预先型容器,主要做容器运行前的初始化工作,其可以为一个或者多个,若为多个的话,这些容器会按依据定义的规则顺序依次执行。我们知道一个Pod里面的所有容器是共享数据卷和Network Namespace的,所以Init Container里面产生的数据可以被主容器使用到。从上面的Pod生命周期的图中可以看出初始化容器是独立于主容器之外的,只有所有的初始化容器执行完之后,主容器才会被启动。
以下为简单的示例,本例中我们将创建一个包含一个应用容器和一个 Init 容器的 Pod。Init 容器在应用容器启动前运行完成。其配置文件init-container-demo.yaml如下所示:
apiVersion: v1 kind: Pod metadata: name: init-demo spec: containers: - name: nginx image: nginx ports: - containerPort: 80 volumeMounts: - name: workdir mountPath: /usr/share/nginx/html # These containers are run during pod initialization initContainers: - name: install image: busybox command: - wget - "-O" - "/work-dir/index.html" - http://info.cern.ch volumeMounts: - name: workdir mountPath: "/work-dir" dnsPolicy: Default volumes: - name: workdir emptyDir: {}
Init 容器能做什么?
Init 容器作为一个与应用容器分离的单独容器镜像,其天生具有如下优势:
1、Init 容器可以包含一些安装过程中应用容器不存在的实用工具。例如,在容器运行过程中需要使用类似 sed、awk、lsof、netstat或 dig 这样的工具,以便我们能够在容器内进行各种Debug操作,那么我们可以将其放到Init容器去安装这些工具;除此之外,针对应用容器需要一些必要的目录或者配置文件甚至涉及敏感信息,就需要放到Init容器去执行,而不是在主容器执行。
2、Init 容器可以安全地运行指定的工具,从而避免因此类工具的运行使得应用镜像的安全性降低。
3、应用镜像的创建者和部署者可以各自独立工作,减少不必要的应用镜像的制作。
4、Init 容器能以不同于Pod内应用容器的文件系统视图运行。因此,相对于应用容器,Init容器能够访问 Secrets 。
5、由于Init 容器必须在应用容器启动之前运行完成,因此 Init 容器提供了一种机制来阻塞或延迟应用容器的启动,直到满足了一组先决条件。一旦前置条件满足,Pod内的所有的应用容器会并行启动。
Ephemeral Container-临时容器
临时容器,顾名思义,在当前的Pod中临时运行,完成使命后自动销毁。由于缺少对资源或执行的保证,且永远不会自动重启,因此不适用于构建应用程序,通常可借助其进行调试、问题排查等。临时容器虽然使用与常规容器相同的 ContainerSpec 节进行描述,但许多字段是不兼容且不允许。
- 临时容器没有端口配置选项,类似Ports,LivenessProbe,ReadinessProbe 字段是不
允许的。
- Pod 资源分配不可变,因此无法进行Resources 。
- 允许字段的信息,可参考EphemeralContainer文档
临时容器是使用 API 中的一种特殊的Ephemeral Containers处理器进行创建的, 而不是直接添加到 Pod.spec 段,因此无法使用 Kubectl edit 来添加一个临时容器。具体可参考:
{ "apiVersion": "v1", "kind": "EphemeralContainers", "metadata": { "name": "demo-pod" }, "ephemeralContainers": [{ "command": [ "sh" ], "image": "busybox", "imagePullPolicy": "IfNotPresent", "name": "debugger", "stdin": true, "tty": true, "terminationMessagePolicy": "File" }] }
我们可借助如下命令来更新已运行的临时容器 demo-pod:
[administrator@JavaLangOutOfMemory ~ ]% kubectl replace --raw /api/v1/namespaces/default/pods/demo-pod/ephemeralcontainers -f ec.json
基于返回临时容器的新列表,我们执行以下命令可查看新创建的临时容器的状态,具体如下所示:
[administrator@JavaLangOutOfMemory ~ ]% kubectl describe pod demo-pod
Ephemeral 容器能做什么?
1、故障排除,若容器崩溃或镜像中未安装调试工具而导致 Kubectl Exec失效时,此时Ephemeral 容器派上用场。
2、Debug功能,借助Ephemeral 容器,我们可以对自己构建的镜像文件或者基于当前容器的需求进行调试,以帮助我们能够更快的进行监测与部署。
Sidecar Container-边车容器
作为K8S 1.8版本内置支持SideCar容器,其所有问题都与容器生命周期相关性有关。由于Pod中的标准容器之间没有区别,因此无法控制哪个容器首先启动或最后终止,但是先正确运行Sidecar容器通常是应用程序容器正确运行的前提。
让我们看一个Istio服务网格场景。Envoy边车负责将所有传入和传出流量代理到应用程序容器。因此,在代理启动并运行之前,应用程序应该无法发送或接收流量。此时,如果应用程序尝试出站访问,则K8S的就绪性探针便形同虚设。如果应用容器先启动,您会在日志中看到很多莫名的错误消息,明明应用已启动了,为什么还报503呢?但如果代理容器正常启动,但业务容器遭遇CrashLoopBackoffs时,应用容器根本启动失败,此时代理容器该何去何从?具体可参考如下示图:
为了彻底解决上述痛点,从K8S 1.18版本开始,K8S内置的Sidecar功能将确保边车在正常业务流程开始之前就启动并运行,即通过更改Pod的启动生命周期,在Init容器完成后启动Sidecar容器,在Sidecar容器就绪后启动业务容器,基于启动流程保证其顺序性。具体可参考如下:
关于Sidecar Container使用规范,可参考以下:
apiVersion: v1 kind: Pod metadata: name: bookings-v1-b54bc7c9c-v42f6 labels: app: demoapp spec: containers: - name: bookings image: banzaicloud/allspark:0.1.1 ... - name: istio-proxy image: docker.io/istio/proxyv2:1.4.3 lifecycle: type: Sidecar ...
基于上述配置,我们需要将Pod规范中的container.lifecycle.type将容器标记为边车类型:Sidecar,默认为Standard。目前,在K8S的1.18版本,Sidecar模式仅仅作为支撑功能,故需要通过Api Server显示启用。
Sidecar容器能做什么呢?
1、加速应用开发,基于敏捷模式。
2、关注点分离,并专注于特定功能。
对于Sidecar Container,目前主要应用于“完全的微服务”服务网格体系中,其主要体现在以下4种角色,具体如下图所示:
- 代理,以Istio中的Envoy组件为例。
通过这种Sidercar 模式,代理可以拦截进出主容器的流量从而Istio可以提取有关流量行为的大量信号作为属性。Istio可以使用这些属性来执行策略决策,并将其发送到监视系统以提供有关整个网格行为的信息。
- 适配器。适配器容器对输出进行标准化。考虑监视N个不同应用程序的任务。可以使用
不同的导出监视数据的方式来构建每个应用程序。
- 增强主容器功能。Sidecar容器扩展并增强了“主”容器,它们可以使用现有的容器并使
它们变得更好。
- 实现辅助功能 。这种场景一般出现在DevOps中。比如将收集日志的组件以Sidecar的
方式部署,实现收集日志的用途,或是部署一个Sidecar组件从配置中心监听配置变化,实时更新本地配置。
Standard Container-标准容器
在K8S生态系统1.18版本之前,这两种容器从管理角度来讲,并没有本质区别,只不过基于不同的场景而定义相关属性及行为。具体可参考如下所示:
至此,关于Kubernetes 1.8版本中的4种类型的Container解析到此为止,大家有什么问题或者建议,欢迎随时留言沟通。