Kubernetes进阶实战(第2版)
上QQ阅读APP看书,第一时间看更新

4.2 在Pod中运行应用

Pod资源中可同时存在初始化容器、应用容器和临时容器3种类型的容器,不过创建并运行一个具体的Pod对象时,仅有应用容器是必选项,并且可以仅为其定义单个容器。本节重点说明如何在Pod中简单使用应用容器。

4.2.1 使用单容器Pod资源

我们知道,一个Pod对象的核心职责在于以主容器形式运行单个应用,因而定义API资源的关键配置就在于定义该容器,它以对象形式定义在Pod对象的spec.containers字段中,配置清单的基本格式如下:


apiVersion:v1
kind: Pod
metadata:
  name: …               # Pod的标识名,在名称空间中必须唯一
  namespace: …          # 该Pod所属的名称空间,省略时使用默认名称空间,例如default
spec:
  containers:           # 定义容器,它是一个列表对象,可包括多个容器的定义,至少得有一个
  - name: …             # 容器名称,必选字段,在当前Pod中必须唯一
    image: …            # 创建容器时使用的镜像
    imagePullPolicy: …  # 容器镜像下载策略,可选字段

image虽为可选字段,这只是为方便更高级别的管理类资源(例如Deployment等)能覆盖它以实现某种高级管理功能而设置,对于非控制器管理的自主式Pod(或称为裸Pod)来说并不能省略该字段。下面是一个Pod资源清单示例文件,它仅指定了运行一个由ikubernetes/demoapp:v1.0镜像启动的主容器demo,该Pod对象位于default名称空间。


apiVersion: v1
kind: Pod
metadata:
  name: pod-demo
  namespace: default
spec:
  containers:
  - name: demo
    image: ikubernetes/demoapp:v1.0
    imagePullPolicy: IfNotPresent

把上面的内容保存于配置文件中,例如pod-demo.yaml,随后即可使用kubectl apply或kubectl create命令进行资源对象创建,下面是相应的命令及响应结果。


~$ kubectl apply -f pod-demo.yaml
pod/pod-demo created

该Pod对象由调度器绑定至特定工作节点后,由相应的kubelet负责创建和维护,实时状态也将同步给API Server并由其存储至etcd中。Pod创建并尝试启动的过程中,可能会经历Pending、ContainerCreating、Running等多种不同的状态,若Pod可正常启动,则kubectl get pods/POD命令输出字段中的状态(STATUS)则显示为Running,如下面的命令及结果所示,使用默认的名称空间时,其中的-n选项及参数default可以省略。


~$ kubectl get pods/pod-demo -n default
NAME  READY   STATUS  RESTARTS  AGE
pod-demo   1/1     Running   0          5m

随后即可对Pod中运行着的主容器的服务发起访问请求。镜像demoapp默认运行了一个Web服务程序,该服务监听TCP协议的80端口,镜像可通过“/”、/hostname、/user-agent、/livez、/readyz和/configs等路径服务于客户端的请求。例如,下面的命令先获取到Pod的IP地址,而后对其支持的Web资源路径/和/user-agent分别发出了一个访问请求:


~$ demoIP=$(kubectl get pods/pod-demo -o jsonpath={.status.podIP})
~ $ curl -s http://$demoIP
iKubernetes demoapp v1.0 ! ClientIP: 10.244.0.0, ServerName: pod-demo, ServerIP: 10.244.2.3!
~$ curl -s http://$demoIP/user-agent
User-Agent: curl/7.58.0

Kubernetes系统支持用户自定义容器镜像文件的获取策略,例如在网络资源较为紧张时可以禁止从仓库中获取镜像文件,或者不允许使用工作节点本地镜像等。容器的imagePullPolicy字段用于为其指定镜像获取策略,它的可用值包括如下几个。

▪Always:每次启动Pod时都要从指定的仓库下载镜像。

▪IfNotPresent:仅本地镜像缺失时方才从目标仓库wp下载镜像。

▪Never:禁止从仓库下载镜像,仅使用本地镜像。

对于标签为latest的镜像文件,其默认的镜像获取策略为Always,其他标签的镜像,默认策略则为IfNotPresent。需要注意的是,从私有仓库中下载镜像时通常需要事先到Registry服务器认证后才能进行。认证过程要么需要在相关节点上交互式执行docker login命令,要么将认证信息定义为专有的Secret资源,并配置Pod通过imagePullSecretes字段调用此认证信息完成。第9章会介绍此功能及其实现。

删除Pod对象则使用kubectl delete命令。

▪命令式命令:kubectl delete pods/NAME。

▪命令式对象配置:kubectl delete -f FILENAME。

若删除后Pod一直处于Terminating状态,则可再一次执行删除命令,并同时使用--force和--grace-period=0选项进行强制删除。

4.2.2 获取Pod与容器状态详情

资源创建或运行过程中偶尔会因故出现异常,此时用户需要充分获取相关的状态及配置信息以便确定问题所在。另外,在对资源对象进行创建或修改完成后,也需要通过其详细的状态信息来了解操作成功与否。kubectl有多个子命令,用于从不同角度显示对象的状态信息,这些信息有助于用户了解对象的运行状态、属性详情等。

▪kubectl describe:显示资源的详情,包括运行状态、事件等信息,但不同的资源类型输出内容不尽相同。

▪kubectl logs:查看Pod对象中容器输出到控制台的日志信息;当Pod中运行有多个容器时,需要使用选项-c指定容器名称。

▪kubectl exec:在Pod对象某容器内运行指定的程序,其功能类似于docker exec命令,可用于了解容器各方面的相关信息或执行必需的设定操作等,具体功能取决于容器内可用的程序。

下面通过具体的示例来说明这3个命令的使用方式及命令结果中重要字段的意义。

1. 打印Pod对象的状态

kubectl describe pods/NAME -n NAMESPACE命令可打印Pod对象的详细描述信息,包括events和controllers等关系的子对象等,它会输出许多字段,不同的需求场景中,用户可能会关注不同维度的输出,但Priority、Status、Containers和Events等字段通常是重点关注的目标字段,各级别的多数输出字段基本都可以见名知义。

另外,也可以通过kubectl get pods/POD -o yaml|json命令的status字段来了解Pod的状态详情,它保存有Pod对象的当前状态。如下命令显示了pod-demo的状态信息,结果输出做了尽可能的省略。


~$ kubectl get pods/pod-demo -o yaml
status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: "2020-08-16T03:36:48Z"
    message: 'containers with unready status: [demo]'
    reason: ContainersNotReady
    status: "False"
    type: ContainersReady
    ……
  containerStatuses:   # 容器级别的状态信息
  - containerID: docker://……
    image: ikubernetes/demoapp:v1.0
    imageID: docker-pullable://ikubernetes/demoapp@sha256:……
    lastState: {}      # 前一次的状态
    name: demo
    ready: true        # 是否已经就绪
    restartCount: 0    # 重启次数
    started: true  
    state:             # 当前状态
      running:
        startedAt: "2020-08-16T03:36:48Z"   # 启动时间
  hostIP: 172.29.9.12  # 节点IP
  phase: Running       # Pod当前的相位
  podIP: 10.244.2.3    # Pod的主IP地址
  podIPs:     # Pod上的所有IP地址
  - ip: 10.244.2.3
  qosClass: BestEffort # QoS类别

上面的命令结果中,conditions字段是一个称为PodConditions的数组,它记录了Pod所处的“境况”或者“条件”,其中的每个数组元素都可能由如下6个字段组成。

▪lastProbeTime:上次进行Pod探测时的时间戳。

▪lastTransitionTime:Pod上次发生状态转换的时间戳。

▪message:上次状态转换相关的易读格式信息。

▪reason:上次状态转换原因,用驼峰格式的单个单词表示。

▪status:是否为状态信息,可取值有True、False和Unknown。

▪type:境况的类型或名称,有4个固定值;PodScheduled表示已经与节点绑定;Ready表示已经就绪,可服务客户端请求;Initialized表示所有的初始化容器都已经成功启动;ContainersReady则表示所有容器均已就绪。

另外,containerStatuses字段描述了Pod中各容器的相关状态信息,包括容器ID、镜像和镜像ID、上一次的状态、名称、启动与否、就绪与否、重启次数和状态等。随着系统的运行,该Pod对象的状态可能会因各种原因发生变动,例如程序自身bug导致的故障、工作节点资源耗尽引起的驱逐等,用户可根据需要随时请求查看这些信息,甚至将其纳入监控系统中进行实时监控和告警。

2. 查看容器日志

规范化组织的应用容器一般仅运行单个应用程序,其日志信息均通过标准输出和标准错误输出直接打印至控制台,kubectl logs POD [-c CONTAINER]命令可直接获取并打印这些日志,不过,若Pod对象中仅运行有一个容器,则可以省略-c选项及容器名称。例如,下面的命令打印了pod-demo中唯一的主容器的控制台日志:


~$ kubectl logs pod-demo
* Running on http://0.0.0.0:80/ (Press CTRL+C to quit)
172.29.9.1 - - [16/Aug/2020 03:54:42] "GET / HTTP/1.1" 200 -
172.29.9.11 - - [16/Aug/2020 03:54:50] "GET / HTTP/1.1" 200 -

需要注意的是,日志查看命令仅能用于打印存在于Kubernetes系统上的Pod中容器的日志,对于已经删除的Pod对象,其容器日志信息将无从获取。日志信息是用于辅助用户获取容器中应用程序运行状态的最有效途径之一,也是重要的排错手段,因此通常要使用集中式的日志服务器统一收集存储各Pod对象中容器的日志信息。

3. 在容器中额外运行其他程序

运行着非交互式进程的容器中缺省运行的唯一进程及其子进程启动后,容器即进入独立、隔离的运行状态。对容器内各种详情的了解需要穿透容器边界进入其中运行其他应用程序进行,kubectl exec可以让用户在Pod的某容器中运行用户所需要的任何存在于容器中的程序。在kubectl logs获取的信息不够全面时,此命令可以通过在Pod中运行其他指定的命令(前提是容器中存在此程序)来辅助用户获取更多信息。一个更便捷的使用接口是直接交互式运行容器中的某shell程序。例如,直接查看Pod中的容器运行的进程:


~$ kubectl exec pod-demo -- ps aux
PID   USER     TIME  COMMAND
    1 root      0:01 python3 /usr/local/bin/demo.py
    8 root      0:00 ps aux

注意

如果Pod对象中运行时有多个容器,运行程序时还需要使用-c <container_name>选项指定运行程序的容器名称。

有时候需要打开容器的交互式shell接口以方便多次执行命令,为kubectl exec命令额外使用-it选项,并指定运行镜像中可用的shell程序就能进入交互式接口。如下示例中,命令后的斜体部分表示在容器的交互式接口中执行的命令。


~$ kubectl -it exec pod-demo /bin/sh
[root@pod-demo /]# hostname
pod-demo
[root@pod-demo /]# netstat -tnl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address     Foreign Address     State   
tcp        0      0 0.0.0.0:80          0.0.0.0:*         LISTEN

上述3个命令输出的信息对于了解应用运行状态,以及获取资源详细信息进行故障排除等有着非常重要的提示作用,因而也是Kubernetes用户最为常用命令。

4.2.3 自定义容器应用与参数

容器镜像启动容器时运行的默认应用程序由其Dockerfile文件中的ENTRYPOINT指令进行定义,传递给程序的参数则通过CMD指令设定,ETRYPOINT指令不存在时,CMD可同时指定程序及其参数。例如,要了解镜像ikubernetes/demoapp:v1.0中定义的ENTRYPOINT和CMD,可以在任何存在此镜像的节点上执行类似如下命令来获取:


~# docker inspect ikubernetes/demoapp:v1.0 -f {{.Config.Entrypoint}}
[/bin/sh -c python3 /usr/local/bin/demo.py]
~# docker inspect ikubernetes/demoapp:v1.0 -f {{.Config.Cmd}}
[]

Pod配置中,spec.containers[].command字段可在容器上指定非镜像默认运行的应用程序,且可同时使用spec.containers[].args字段进行参数传递,它们将覆盖镜像中默认定义的参数。若定义了args字段,该字段值将作为参数传递给镜像中默认指定运行的应用程序;而仅定义了command字段时,其值将覆盖镜像中定义的程序及参数。下面的资源配置清单保存在pod-demo-with-cmd-and-args.yaml文件中,它把镜像ikubernetes/demoapp:v1.0的默认应用程序修改为/bin/sh -c,参数定义为python3 /usr/local/bin/demo.py -p 8080,其中的-p选项可修改服务监听的端口为指定的自定义端口。


apiVersion: v1
kind: Pod
metadata:
  name: pod-demo-with-cmd-and-args
  namespace: default
spec:
  containers:
  - name: demo
    image: ikubernetes/demoapp:v1.0
    imagePullPolicy: IfNotPresent
    command: ['/bin/sh','-c']
    args: ['python3 /usr/local/bin/demo.py -p 8080']

下面将上述清单中定义的Pod对象创建到集群上,验证其监听的端口是否从默认的80变为了指定的8080:


~$ kubectl create -f pod-demo-with-cmd-and-args.yaml 
pod/pod-demo-with-cmd-and-args created
~$ kubectl exec pod-demo-with-cmd-and-args -- netstat -tnl  
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address    Foreign Address     State 
tcp        0      0 0.0.0.0:8080     0.0.0.0:*          LISTEN

自定义args参数,也是向容器中应用程序传递配置信息的常用方式之一,对于非云原生的应用程序,这几乎是最简单的配置方式了。另一个常用配置方式是使用环境变量。

4.2.4 容器环境变量

非容器化的传统管理方式中,复杂应用程序的配置信息多数由配置文件进行指定,用户借助简单的文本编辑器完成配置管理。然而,对于容器隔离出的环境中的应用程序,用户就不得不穿透容器边界在容器内进行配置编辑并进行重载,复杂且低效。于是,由环境变量在容器启动时传递配置信息就成了一种受到青睐的方式。

注意

这种方式需要应用程序支持通过环境变量进行配置,否则用户要在制作Docker镜像时通过entrypoint脚本完成环境变量到程序配置文件的同步。

向Pod对象中容器环境变量传递数据的方法有两种:env和envFrom,这里重点介绍第一种方式,第二种方式将在介绍ConfigMap和Secret资源时进行说明。

通过环境变量的配置容器化应用时,需要在容器配置段中嵌套使用env字段,它的值是一个由环境变量构成的列表。每个环境变量通常由name和value字段构成。

▪name <string>:环境变量的名称,必选字段。

▪value <string>:传递给环境变量的值,通过$(VAR_NAME)引用,逃逸格式为$$(VAR_NAME)默认值为空。

示例中使用镜像demoapp中的应用服务器支持通过HOST与PORT环境变量分别获取监听的地址和端口,它们的默认值分别为0.0.0.0和80,下面的配置保存在清单文件pod-using-env.yaml中,它分别为HOST和PORT两个环境变量传递了一个不同的值,以改变容器监听的地址和端口。


apiVersion: v1
kind: Pod
metadata:
  name: pod-using-env
  namespace: default
spec:
  containers:
  - name: demo
    image: ikubernetes/demoapp:v1.0
    imagePullPolicy: IfNotPresent
    env:
    - name: HOST
      value: "127.0.0.1"
    - name: PORT
      value: "8080"

下面将清单文件中定义的Pod对象创建至集群中,并查看应用程序监听的地址和端口来验证配置结果:


~$ kubectl apply -f pod-using-env.yaml
pod/pod-using-env created
~$ kubectl exec pod-using-env -- netstat -tnl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address      Foreign Address    State       
tcp        0      0 127.0.0.1:8080     0.0.0.0:*         LISTEN

无论它们是否真正被用到,传递给容器的环境变量都会直接注入容器的shell环境中,使用printenv一类的命令就能在容器中获取到所有环境变量的列表。另外,Kubernetes上负责向容器传递配置的是ConfigMap资源,这将在后续章节中展开介绍。

4.2.5 Pod的创建与删除过程

Pod是Kubernetes的基础单元,理解它的创建过程对于了解系统运作大有裨益。图4-9描述了一个Pod资源对象的典型创建过程。

图4-9 Pod资源创建过程

1)用户通过kubectl或其他API客户端提交Pod Spec给API Server。

2)API Server尝试着将Pod对象的相关信息存入etcd中,待写入操作执行完成,API Server即会返回确认信息至客户端。

3)Scheduler(调度器)通过其watcher监测到API Server创建了新的Pod对象,于是为该Pod对象挑选一个工作节点并将结果信息更新至API Server。

4)调度结果信息由API Server更新至etcd存储系统,并同步给Scheduler。

5)相应节点的kubelet监测到由调度器绑定于本节点的Pod后会读取其配置信息,并由本地容器运行时创建相应的容器启动Pod对象后将结果回存至API Server。

6)API Server将kubelet发来的Pod状态信息存入etcd系统,并将确认信息发送至相应的kubelet。

另一方面,Pod可能曾用于处理生产数据或向用户提供服务等,Kubernetes可删除宽限期确保终止操作能够以平滑方式优雅完成,从而用户也可以在正常提交删除操作后获知何时开始终止并最终完成。删除时,用户提交请求后系统即会进行强制删除操作的宽限期倒计时,并将TERM信号发送给Pod对象中每个容器的主进程。宽限期倒计时结束后,这些进程将收到强制终止的KILL信号,Pod对象也随即由API Server删除。如果在等待进程终止的过程中kubelet或容器管理器发生了重启,则终止操作会重新获得一个满额的删除宽限期并重新执行删除操作。

Pod的终止过程如图4-10所示。

图4-10 Pod对象的终止过程

如图4-10所示,一个典型的Pod对象终止流程如下。

1)用户发送删除Pod对象的命令。

2)API服务器中的Pod对象会随着时间的推移而更新,在宽限期内(默认为30秒),Pod被视为dead。

3)将Pod标记为Terminating状态。

4)(与第3步同时运行)kubelet在监控到Pod对象转为Terminating状态的同时启动Pod关闭过程。

5)(与第3步同时运行)端点控制器监控到Pod对象的关闭行为时将其从所有匹配到此端点的Service资源的端点列表中移除。

6)如果当前Pod对象定义了preStop钩子句柄,在其标记为terminating后即会以同步方式启动执行;如若宽限期结束后,preStop仍未执行完,则重新执行第2步并额外获取一个时长为2秒的小宽限期。

7)Pod对象中的容器进程收到TERM信号。

8)宽限期结束后,若存在任何一个仍在运行的进程,Pod对象即会收到SIGKILL信号。

9)Kubelet请求API Server将此Pod资源的宽限期设置为0从而完成删除操作,它变得对用户不再可见。

默认情况下,所有删除操作的宽限期都是30秒,不过kubectl delete命令可以使用--grace-period=<seconds>选项自定义其时长,使用0值则表示直接强制删除指定的资源,不过此时需要同时为命令使用--force选项。