实验介绍
本节将介绍 kubernetes 中的最基本和核心的概念:Pod。它与 docker 中的 container 概念比较类似,但比 container 的概念更为丰富。本实验将通过对 Pod 基本概念的介绍以及练习来掌握 Pod 的使用方法。
Pod 的概念
我们知道 Docker 容器(container)的基本概念,那么简单来讲,Pod 是一组(也可以为一个)container 的集合,这些 container 一起调度,视为一个基本单元。那么为什么要有 Pod 这个概念呢?
首先来讲,kubernetes 为了提供服务,需要有这么一个基本的计算单元,但它对这个“基本单元”的定位,Docker 的 container 并不十分适合。kubernetes 的需求是:
下面以 unit 代指 “基本单元”这个概念
- 这个 unit 需要有一个唯一的 IP,并且可以跨节点、在集群内能互相访问。
- 最好不能太依赖于 docker (也有商业上的考量),而要支持多种 container runtime。
- 用户最终使用 kubernetes 都是将其应用部署于集群内。这个 unit 最好贴近于应用的概念,也就是说更抽象一点,贴近于业务,而不是底层系统。
- 在使用 container 时我们经常会发现有一个两难的处境,就是我们希望在容器里运行多个程序,但 container 对此支持不太好,放多个容器又太麻烦了。Pod 的概念就完美地支持了这种场景,在 Pod 中的多个容器虽然各自独立,但是默认共享网络和存储,并且还可以定制其它的共享资源。
结合以上的考量,Kubernetes 将 Pod 作为其最基本的运算单元。
本实验中,也会大量地使用应用/服务的概念。这是一个业务上的概念,泛指用户想要在容器平台(kubernetes) 运行的程序,比如 MySQL、Nginx…最终这些应用/服务都会以容器(Pod)的形式存在。
Pod 基本结构
在上一个实验中,我们已经介绍了 Resource 的基本概念以及如何用 yaml/json 文件来创建 Resource。Pod 也是一种 Resource。
在 /home/shiyanlou
目录下新建 pod.yaml
文件并向其中写入如下的内容,下面的内容是用于创建一个 Pod:
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp-container
image: busybox
command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600']
这是一个非常简单的 Pod 样例。它的主要字段解释如下:
-
apiVersion/kind
:这是所有资源共有的基本字段,表示 api 版本号以及类型。
apiVersion
:表示资源所属于的 group 以及 version。结构一般为<group/<version>
,其中 v1 是特例,其 group 为 “”, 省去了中间的/
。一般核心的资源都是 v1,比如 Namespace / Pod / ConfigMap 等。其它的资源各有各自的 group 以及 version。kind
:资源类型,开头大写。
-
metadata
name
:这个 pod 的名字。labels
:pod 的标签。namespace
:可选字段。Kubernetes 中的资源分为两类,一类属于 namespace,一类不属于。Pod 属于 namespace,如果 yaml 里没有写 namespace,表示属于 default namespace。
-
spec
:pod 的主要信息部分。
-
containers
:一个列表,因为可以有包含多个 container。
name
:这个 container 的名字,一个 pod 下面的多个 container 名字不能冲突。image
:这个 container 的镜像信息。command
:启动命令。是可选项,因为一般镜像都有默认值。
-
这个 yaml 展示了 Pod 的基本结构,虽然信息不多,但是已经足够运行一个 Pod 了。在命令行执行:
kubectl create -f pod.yaml
我们可以看到创建的结果:
接下来我们看一下新创建的 myapp-pod 的详细信息,在命令中执行如下命令(截图不完整):
kubectl get pod myapp-pod -o yaml
可以看到,我们使用的 yaml 在创建之后包含了更多的字段。这些都是 kubernetes 帮助填写的默认值;metadata 字段前面实验已经介绍过。下面主要说下 pod 的 spec 和 status 字段。
一般来说,很多资源都有这两个字段,而且含义类似。spec 是具体的属性描述,status 是状态信息,会在创建后不断变化。Pod 的 spec 字段是一个 containers 列表,因为它支持多容器。每个 container 内部的信息与 docker 和 docker compose 包含的信息是类似的,只是字段不同。因为最终目的都是要配置应用、运行应用,在这方面二者的目的是一致的。所以表现的主要差别只体现在语法上。
container 里主要包含的基本信息有:
-
启动命令
:主要是 command。很好理解,下面也会详细介绍。 -
镜像信息
:
image
:镜像地址。imagePullPolicy
:镜像拉取策略。因为有时候机器上已经有了 image,我们就可以不用去远端仓库拉取,这时候可以在 pod yaml 里设置这个值为 IfNotPresent。Always 表示不管机器上存不存在都会重新 pull 镜像,适用于镜像 tag 不变但是镜像内容会变化的场景。
-
名称
:容器的名称,kubernetes 中的所有资源的查找和使用主要都是靠名称,容器也是。比如我们用 kubectl 去 exec 到一个容器中时,就会用到这个名称。 -
resources
:因为我们使用的 yaml 里没有这部分信息,所以创建出来的 yaml 里默认值为{}
。它具体描述了这个 Pod 对于计算资源的需求信息。下面也会详细介绍。 -
terminationMessagePath
:用于记录容器退出时的最后信息(成功退出或者异常退出),这些信息可以用于监控展示或者 kubernetes 计算 container 以及 pod 的状态。 -
terminationMessagePolicy
:从哪些地方取容器最终的状态信息。
File
: 默认值,表示只从上面terminationMessagePath
所在的位置取状态信息。FallbackToLogsOnError
:如果上面的文件里没有内容,那么就从容器的日志里取一部分数据作为状态信息(一般是 stdout 的输出)。
Pod 的状态
Pod 创建完之后,一直到持久运行起来,中间有很多步骤,也就有很多出错的可能,因此会有很多不同的状态。一般来说,pod 这个过程包含以下几个步骤:
- 调度到某台机器上。kubernetes 根据一定的优先级算法选择一台机器将其作为 pod 运行的机器
- 拉取镜像
- 挂载存储配置等
- 运行起来。如果有健康检查,会根据检查的结果来设置其状态。
把刚才的输出拉到最下方,我们看下刚才的 pod 的状态结果:
分别包含了 pod 级别的信息以及各个 container 的信息。pod 部分的信息如下:
-
hostIP
: pod 所在节点的 ip -
phase
: pod 的状态,目前是 Running。其它的可能状态有
Pending
: 一般表示还没有开始调度到某台机器上。如果没有符合条件的主机,就会一直处于 Pending 状态Running
: 运行中Succeeded
:有些 pod 不是长久运行的,比如 cronjob,一段时间就结束了,需要反馈任务执行的结果。Failed
:pod 的 container 异常退出,比如 command 写的有问题。Unknown
:未知。比如 pod 所在的机器无法连接。
-
podIP
: pod 所分配到的集群内的 ip,这个 ip 是全集群内唯一的。 -
qosClass
: 资源分配相关,在后面小节介绍 -
startTime
: Pod 启动时间
conditions 部分的信息比较多,包含了每个 container 的运行信息。比较重要的有:
-
probe 信息
: 是一列状态检查信息,表明 pod 是否到达某个状态。注意 type 字段,它有一些固定的值,大体代表了部署的一个过程。而 status 字段就具体表明是否达到这个状态。我们将所有的 type 列出如下:
PodScheduled / Unschedulable
: 已经调度到某台机器了 / 无法调度到某台机器Initialized
:所有的 init containers 都成功启动(后续小节会详细介绍)ContainersReady
:所有的 containers 都已经 readyReady
: pod 完全可以对外提供服务
至于 containerStatuses 部分,则提供了 pod 下各个容器的基本信息。比较重要的信息有:
restartCount
: 重启次数。因为 kubernetes 对于资源的处理不是一次性的,比如这一次部署出错了,它会一直重试,直到达到目标状态。比如 command 写的不对,启动后容器退出,kubernetes 会一直重启。restartCount 会记录这个次数state
: pod 下每个 container 的状态。这个状态比 pod.phase 更加准时和精确。最终 pod 的 phase 也是根据此计算出来的。
一般来说,这些信息并不需要特别关注,pod 的 phase 字段大部分时间都能比较明确地提供大致的状态信息,但出错的时候,我们就需要综合各种信息来判断问题出在哪里。
kubectl 在展示状态的时候,就做了一个特殊处理,将 pod 的 phase 字段以及 container 的状态信息结合起来计算出一个状态展示出来。我们可以通过创建一个有问题的 pod 来看下。
在 /home/shiyanlou
目录下新建 bad-pod.yaml
文件,并向其中写入如下代码:
apiVersion: v1
kind: Pod
metadata:
name: bad-pod # 换了名字,避免与之前的pod名字冲突
labels:
app: myapp
spec:
containers:
- name: myapp-container
image: busybox:error-tag # 加了一个不存在的tag
command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600']
在命令行中执行如下命令:
kubectl create -f bad-pod.yaml # 创建 bad-pod
kubectl get pods -w # 执行
-w
的意思是 watch,就是监听的意思。加了这个参数后, kubectl 不会退出,会一直监听 pods 的变化。这样 pods 的状态变化我们都能看到。
结果如下:
可以看到,过了一小会之后,kubernetes 发现镜像拉取失败, kubectl 展示的状态是 ImagePullBackOff
,这个状态不在 pod 的 yaml 中,是 kubectl 根据 pod 以及 containers 的状态综合计算出来的。我们可以使用 Ctrl + c 来退出当前命令。kubectl 作为一个使用频率非常高的交互工具,用这样的状态能极大地增强易用性。
这里可以回想一下删除的命令,把这个错误的 Pod 删除。
资源申请
我们知道使用容器有一个优点,可以自行控制每个容器使用资源的大小。docker 提供了参数来控制 cpu 和内存的使用,pod 也同样,但仍然是容器级别的,需要对 pod 的每个容器做设置。在之前的例子中,我们没有设置这个字段,表示默认不限制资源,但是在正式的环境中使用,还是建议对资源进行限制,防止某个 Pod 超量使用资源,影响其他 Pod 甚至主机运行。
Requests and Limits
Pod 中的资源限制也主要是针对 cpu 和 内存。与 docker 不同的是,它提供了 requests 和 limits 两个设置。具体的含义为:
requests
:pod 运行所需要的最少资源。例如 kubernetes 在调度 pod 时,就是以这个设置来挑选 node。limits
:pod 运行的资源上限。也就说,超过这个 limits,pod 会被 kill 掉。
注意,limits 的数值不能小于 requests,否则 Pod 会启动失败。
我们找一个例子来测试下,在 /home/shiyanlou
目录下新建 wp.pod
文件,并向其中写入如下代码:
apiVersion: v1
kind: Pod
metadata:
name: wp
spec:
containers:
- name: wp
image: wordpress
resources:
requests:
memory: '64Mi'
cpu: '250m'
limits:
memory: '128Mi'
cpu: '500m'
注: cpu 仅支持数字(如:1)或者 milli CPU(如:500m) 这样的写法。其中 500m = 0.5(核),且这两个数值都是绝对数值,即无论是在 2 核的主机还是 10 核的主机上,Pod 都只占用 0.5 个核心,而不是所有核心数量的一半。
内存分配有很多单位,我们常用的有 KB,MB,GB 等,注意在这里我们的单位是 Ki,Mi,Gi。1Mi = 1024 x 1024,而 1M = 1000 x 1000,其他单位以此类推。具体有哪些可用在使用时查看官方文档即可。
执行如下命令创建:
kubectl create -f wp.pod
这个 pod 明确设置了 requests 和 limits,并且 requests <= limits。这里我们要介绍一个 kubectl 的命令,叫 describe
。 它与 get 类似,都是查看某个资源的信息,但是包含的信息更多。它重新排版了资源的 yaml,凸显了重要信息,并且将这个资源创建以来的事件信息也展示出来了,在排查问题时很有用。
我们看下运行结果(等 Pod 变为运行中):
kubectl describe pods wp
我们可以在这里以更直观的方式看到 pod 的信息以及 events。
pod 在调度到某个节点上之后,我们也可以在这个节点的状态信息中看到资源的占用情况:
node 的名字从上面 describe pods 的结果中可以看到,也可以使用命令
kubectl get pod --output=wide
查看 Pod 被调度到哪个节点上了。不同环境被调度的节点可能不同,请根据实际情况执行。
kubectl describe node kubernetes-worker
这里面我们可以看到当前 Non-terminated Pods 一共有三个,两个是集群的,还有我们刚才创建的 wp。后面也列出了它的资源申请量,以及所占百分比。下图中的 Allocated Resources 部分也显示此机器目前的资源使用情况。这些信息在我们查看集群状态,机器状态以及排查问题时非常有用。
Qos
刚才我们创建的 Pod 在 describe 的信息里有一个字段叫 Qos Class
,它的值是 Burstable
。这是由 Pod 的 requests 和 limits 设计所决定的一个字段。表示了 Kubernetes 对不同的 Pod 因其 requests/limits 设置而对其运行情况的保障。
Qos 全称是 Quality of Service,服务质量的意思。熟悉计算机网络的同学应该也看到过 Qos 的概念,K8S 里的 Qos 名称一致,但是表示的具体内容不同。
K8S 的 Qos 具体分为以下几类:
Guaranteed
: requests = limits ,二者是一样的值。高优先级,Kubernetes 保证只要 Pod 资源使用不超过 limits 就不会被 kill 掉。Burstable
: requests < limits,Pod 可以使用 requests 到 limits 之间数量的资源,中优先级。但是当机器资源紧张的时候,如果这些 Pod 对资源的使用超过了 requests,在没有 BestEffort 的 pod 的情况下就很有可能会被 kill 掉。BestEffort
: 没有设置具体的 requests 和 limits。低优先级,如果节点资源紧张,这些 Pod 会优先被 kill 掉。
由此可见,K8S 的 Qos 其实是类似于 Linux 中进程 priority 的一个概念,通过 requests 值和 limits 值的设置,让用户自己选择去将其应用划分为不同的优先级。我们上面使用的例子就是一个 Burstable
的 pod。下面我们看看其他的例子。
Guaranteed
在 /home/shiyanlou
目录下新建 qos-demo.yaml
文件,并向其中写入如下代码:
apiVersion: v1
kind: Pod
metadata:
name: qos-demo
spec:
containers:
- name: qos-demo-ctr
image: nginx
resources:
limits:
memory: '200Mi'
cpu: '700m'
requests:
memory: '200Mi'
cpu: '700m'
这个示例中,requests = limits,在命令行中执行如下命令进行创建:
kubectl create -f qos-demo.yaml
kubectl describe pods qos-demo
可以看到其 Qos Class 是 Guaranteed。
BestEffort
在 /home/shiyanlou
目录下新建 qos-demo-2.yaml
文件,并向其中写入如下代码:
apiVersion: v1
kind: Pod
metadata:
name: qos-demo-2
spec:
containers:
- name: qos-demo-3-ctr
image: nginx
在命令行中执行如下命令进行创建:
kubectl create -f qos-demo-2.yaml
kubectl describe pods qos-demo-2
可以看到其 QosClass 为 BestEffort,当资源不足时这个 Pod 会被优先杀死。
启动命令
我们在使用 docker run 的时候可以指定启动命令,在 Dockerfile 里也可以设置 ENETRYPOINT 和 RUN 指令。在 pod 中,kubernetes 使用了更加简单的 command / args 参数来设置,它与 docker 使用的参数对应如下:
之所以要介绍这部分,是因为本身镜像层面的 entrypoint 和 cmd 就容易混淆。再加上 pod 又抽象出了新的参数,很容易误用出 bug。上面的几条规则大概意思如下:
- pod 不设置任何参数,直接用镜像里面自带的参数
- pod 的 command 和 args 都设置,使用设置的命令和参数
- pod 只设置 command ,完全忽略镜像里的参数
- pod 只设置 args,镜像中的命令与新参数一起使用
一般来说,我们最好将启动命令在镜像里设置好,这样就不用在 pod 里设置。如果要在 pod 里面设置,最好填上 command,这样就完全以 pod 的参数为准,方便理解。下面看个例子:
在 /home/shiyanlou
目录下新建 commands.yaml
文件,并向其中写入如下代码:
apiVersion: v1
kind: Pod
metadata:
name: command-demo
labels:
purpose: demonstrate-command
spec:
containers:
- name: command-demo-container
image: debian
command: ['printenv']
args: ['HOSTNAME', 'KUBERNETES_PORT']
restartPolicy: OnFailure
在命令行中执行如下命令:
kubectl create -f commands.yaml
# 查看 command-demo 是否是 Running 或 Completed 状态
kubectl get pods -w
kubectl logs command-demo
这个例子中我们就是将 command 和 args 都设置了,默认的 debian 的启动命令是 bash,在 pod 里面没有什么实际的用途。这里我们使用了 printenv
命令来打印出来 HOSTNAME
和 KUBERNETES_PORT
两个环境变量的值。
健康检查
早期的 Docker 是没有原生的健康检查的,在 1.12 版本之后也加入了健康检查的配置,可见它也意识到了健康检查的重要性,而 pod 在一开始就引入了对健康检查的支持。通常我们会使用容器来运行长时间运行的服务,比如 http 服务、cache 等,如果没有健康检查,很有可能容器仍在运行,但是服务已经不能正常工作了。要知道,容器运行正常并不等于容器里运行的应用或服务也正常,所以我们需要健康检查来统一两者的状态。
在基础的健康检查的概念之上,kubernetes 提供了更加精细的概念。分别是存活性检查和可用性检查,分别介绍如下:
存活性检查
(liveness probe): 用于判断容器是否需要重启可用性检查
(readiness probe): 用于判定容器是否可以正常提供服务。
因为在一般的应用场景下,都会用负载均衡后面挂上多个实例来达到分担流量以及稳定性保障的目的。可用性检查就是用来保证这个 Pod 是否可以提供服务,并被挂载在负载均衡后面。
一般的应用不会有这么精细的区分,这时候存活性检查和可用性检查用同样的配置即可。
通常来讲,应用对外提供服务都是通过 tcp 端口或者 http 端口,比如 Nginx 提供 http 服务,Redis 通过 6379 端口提供服务。对于这样的服务,我们可以通过检查其端口是否开启,http endpoint 是否可以访问来判断其健康状态。
有的服务只提供集群内部访问,不需要对提供对外的网络端口,这时候可以通过 exec
命令进去 Pod 之后执行相应的命令来检查。kuberentes 提供了对这几种方式的支持,通过 pod 所在节点的 kubelet 组件来执行这些检查,下面我们可以逐一实验。
EXEC
exec 就是指通过 exec 到容器中执行命令来进行健康检查,我们看一个示例。
在 /home/shiyanlou
目录下新建 liveness-exec.yaml
文件,并向其中写入如下代码:
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-exec
spec:
containers:
- name: liveness
image: busybox
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
注意:
- 例子中为了演示,让 command 不断地创建删除文件,健康检查去检查这个文件。
- 这个例子中只配置了 livenessProbe。
- command: 就是健康检查执行的命令。如果执行结果状态码为 0,就认为通过,其它的认为失败。
- periodSecounds: command 执行的间隔,健康检查是一个持续性的过程,需要反复执行。
- initialDelaySeconds:因为好多程序启动时有初始化时间。比如 java 程序,初始化一般就比较慢。这时候如果立马做健康检查就不太合适,initialDelaySeconds 就是设置了一个合理的时间,等过了这个时间再做检查。
在这个例子中,容器启动后,创建 /tmp/healthy
这个文件。30 秒内,健康检查是 ok 的,之后文件被删,健康检查就会出问题了。
在命令行中执行如下命令进行创建:
kubectl create -f liveness-exec.yaml
然后反复执行:
kubectl describe pods liveness-exec
可以看到,刚开始的 event 都是 Normal 的,在删除文件之后,变成 Warning,因为 Livenss probe 失败了。失败之后,Pod 也会被重启,我们可以看下:
kubectl get pods | grep liveness
这个是过了一段时间之后看到的,已经重启了 3 次了。等的时间越久,重启的次数也就越多。
HTTP
http 检查即是通过调用 http 服务的某个路径,然后根据错误码来判定。 http status code 的 200-400 代表成功,其它代表失败。
在 /home/shiyanlou
目录下新建 liveness-http.yaml
文件,并向其中写入如下代码:
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-http
spec:
containers:
- name: liveness
image: cnych/liveness
args:
- /server
livenessProbe:
httpGet:
path: /healthz
port: 8080
httpHeaders:
- name: X-Custom-Header
value: Awesome
initialDelaySeconds: 3
periodSeconds: 3
依然是 livenessProbe:
- httpGet: 表示这是一个 http 类型的健康检查
- path: http 的路径
- httpHeaders: 发送请求所带的 headers 字段,不同的场景可能有不能的需求。
这个镜像里,我们依然动态修改了返回结果,用来演示 healtcheck 的不同效果。其实现代码如下图所示:
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
duration := time.Now().Sub(started)
if duration.Seconds() > 10 {
w.WriteHeader(500)
w.Write([]byte(fmt.Sprintf("error: %v", duration.Seconds())))
} else {
w.WriteHeader(200)
w.Write([]byte("ok"))
}
})
前 10s 返回 200, 之后返回 500。
在命令行中执行如下命令进行创建:
kubectl create -f liveness-http.yaml
kubectl describe pods liveness-http
看下 pod 的 event:
event 中通过事件就可以看到健康检查已经失败了,kubelet 在重启 pod。
TCP
对于监听 tcp 端口的服务,我们可以尝试与这个端口建立连接。如果成功,则认为服务正常。
在 /home/shiyanlou
目录下新建 liveness-tcp.yaml
文件,并向其中写入如下代码:
apiVersion: v1
kind: Pod
metadata:
name: goproxy
labels:
app: goproxy
spec:
containers:
- name: goproxy
image: googlecontainer/goproxy:0.1
ports:
- containerPort: 8080
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
这个实例中配置了 livenessProbe 和 readinessProbe,从具体的配置上来看,与 http 的配置是很像的。只是其配置中需要指明的是端口。
在命令行中执行如下命令进行创建查看:
kubectl create -f liveness-tcp.yaml
kubectl describe pods goproxy
这个地方因为镜像配置的是一直运行,所以结果 healthcheck 会一直过的。这里着重看下 liveness 和 readiness 的一些其他默认值:
- timeout: 超时时间,如果超时了就认为失败了
- success/failure: 图中的配置是如果成功一次了就认为正常,如果失败了三次才认为失败。这样可以有效地避免因为偶然的偏差导致容器被标记为异常。
多容器 Pod
刚才我们的例子中都是单容器的 pod,也是最常用的。在某些情况下,多容器的 Pod 更能适应需求。这样的模式通常是一个容器用来主要提供服务,另一个做一些其他的零碎工作。比如:
- 收集日志。这样不用修改原来的服务,可以用另外的容器来适配各种日志收集系统。
- 做 Proxy。比如我们的程序需要访问外部的服务,可以固定配置为 localhost,由其他的容器来决定如何转发请求,相当于将动态配置的需求交由其他的容器来做。
综合来讲,这样做的好处就是让主要的服务容器不做修改,就能更好地适配各种系统。另外,也能较好地做到职责分离,不需要由一个容器来处理过多的任务。
下面看一个例子,在 /home/shiyanlou
目录下新建 two-containers.yaml
文件,并向其中写入如下代码:
apiVersion: v1
kind: Pod
metadata:
name: two-containers
spec:
restartPolicy: Never
volumes:
- name: shared-data
emptyDir: {}
containers:
- name: nginx-container
image: nginx
volumeMounts:
- name: shared-data
mountPath: /usr/share/nginx/html
- name: debian-container
image: debian
volumeMounts:
- name: shared-data
mountPath: /pod-data
command: ['/bin/sh']
args:
['-c', 'echo Hello from the debian container > /pod-data/index.html']
这个例子中我们使用了 volume,docker 中也有这个概念,二者是类似的。 Pod 中的 volume 是各个容器共享的,我们可以用它来让各个容器交互。在这个例子中,debian-container 往 /pod-data/ 里面写入了一个文件,而这个目录是两个 container 共享的 volume,nginx 将其挂载到其配置的目录。
在命令行中执行如下命令:
kubectl create -f two-containers.yaml
kubectl get pods -w
需要注意到 READY 下面的 0/2 ,这个 2 就是表示两个容器的意思。一般情况下,需要等到 2 个容器都是 ready 之后 Pod 才会处于运行中。在这个例子中,因为一个 container 运行完程序就结束了。所以其状态会如下所示:
这时候我们可以 exec 进去看看能否读取到 debian-container 写入的文件:
kubectl exec -it two-containers -c nginx-container -- bash
cd /usr/share/nginx/html
ls
cat index.html
kubectl 的 exec 命令非常类似于 docker 的 exec,区别之处在于其 exec 的目标是 Pod。当 Pod 是单个容器时,其结构与 docker exec 是一致的。当 Pod 有多个容器时,就需要指明具体的目标 container 是哪个。默认值是 yaml 里的第一个容器,这里为了演示,加上了明确的参数。
可以从输出的结果来看,在 nginx-container 里面,可以读取到 debian-container 写入的数据。
InitContainers
initContainers 是 Pod 提供的另外一个非常有用的功能。它的结构与普通的 container 类似,但是作用上有很大的区别:
- 它们是短期运行的程序,不是持久运行的进程。
- 顺序执行,并且每一个 initContainer 必须等待前一个执行成功才能继续执行。正常的 container 必须等待前面的 initContianer 都执行完成之后才能开始执行。
那么它的用处在哪呢?想象一下以下的业务场景:
- 程序在运行前需要等待某个条件才能执行。比如服务 A 需要等待服务 B 可用时才能开始运行
- 程序需要某些动态的配置信息生成之后才能正常运行
- 程序需要同步好某些数据之后才能正常运行,比如 mysql-slave
综合来讲,就是说某个服务的运行,需要一些如服务依赖、文件等前置条件才能正常运行。在没有 initContainers 的情况下,我们需要在正常的 container 里做一些 hack 才能做到,这样不好维护并且比较复杂。有了 initContainers,不管是结构上还是可维护性上都会好很多。
我们看一个例子,在 /home/shiyanlou
目录下新建 init.yaml
文件,并向其中写入如下代码:
apiVersion: v1
kind: Pod
metadata:
name: init-ctr-demo
spec:
volumes:
- name: data
emptyDir: {}
initContainers:
- name: init-1
image: busybox
command: ['sh', '-c', 'echo start 1 >> /data/file']
volumeMounts:
- name: data
mountPath: /data
- name: init-2
image: busybox
command: ['sh', '-c', 'echo start 2 >> /data/file']
volumeMounts:
- name: data
mountPath: /data
containers:
- name: busybox
image: busybox
command: ['sh', '-c', 'sleep 1000']
volumeMounts:
- name: data
mountPath: /data
这个例子如上面的多 container 示例一样,用 volume 来共享数据。这个 Pod 一共包含了两个 initContainers,分别往 volume 写数据。执行完之后,我们就应该能在正常的 container 里观察到这些数据。
在命令行中执行如下命令:
kubectl create -f init.yaml
# 继续 watch pods 的变更
kubectl get pods -w
在观察的过程中,我们可以看到。 READY 里面是不显示 initContainers 信息的。但是 STATUS 里面的状态,会显示出具体的进度。 Init:0/2
表示有两个 initContainers,正在执行第一个,Init:1/2
表示正在执行第二个。 PodInitializing
表示 initContainers 已经执行完毕,开始执行正常的 containers。
如果大家看不到 STATUS 的状态变化可能是因为其它的 pod 干扰了,可以执行 kubectl delete pod <pod-name>
删除之前创建的多余的 pod。
等到 Running 后,我们可以 exec 到 Pod 中去观察具体的数据是否正确:
kubectl exec -it init-ctr-demo -- sh
cd /data
ls
cat file
根据结果可以发现,在 container 中能够看到在 initContainers 里面写入的数据。
本文由 liyunfei 创作,采用 知识共享署名4.0
国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Jun 29,2022