Pod是Kubernetes中的==基本单位==。容器本身并不会直接分配到主机上,而会封装到名为Pod的对象中。
Pod通常表示单个应用程序,由一个或多个关系紧密的容器构成,这些容器拥有同样的生命周期,作为一个整体一起编排到Node上。
这些容器共享环境、存储卷(volume)和IP空间。尽管Pod基于一个或多个容器,但应将Pod视作一个单一的整体、单独的应用程序。Kubernetes以Pod为最小单位进行调度、伸缩并共享资源、管理生命周期。
1. Pod基本操作
1. 创建Pod
部署Pod
定义模版文件:
填入:
1 2 3 4 5 6 7 8 9 10 11
| apiVersion: v1 kind: Pod metadata: name: examplepod spec: containers: - name: examplepod-container image: busybox imagePullPolicy: IfNotPresent command: ['sh', '-c'] args: ['echo "Hello Kubernetes!"; sleep 3600']
|
apiVersion表示使用的API版本。v1表示使用Kubernetes API的稳定版本
kind表示要创建的资源对象,这里使用关键字Pod。
metadata表示该资源对象的元数据。一个资源对象可拥有多个元数据,其中一项是name,它表示当前资源的名称。
spec表示该资源对象的具体设置。其中containers表示容器的集合,这里只设置了一个容器,该容器的属性如下。
name:要创建的容器名称。
image:容器的镜像地址。
imagePullPolicy:镜像的下载策略,支持3种
imagePullPolicy,如下所示。
- Always:不管镜像是否存在都会进行一次拉取。
- Never:不管镜像是否存在都不会进行拉取。
- IfNotPresent:只有镜像不存在时,才会进行拉取。
command:容器的启动命令列表(不配置的话,使用镜像内部的命令)。
args:启动参数列表(在本例中是输出文字“HelloKubernetes!”并休眠3600s)
运行以下命令,通过模板创建Pod。
1
| kubectl apply -f examplepod.yml
|
*提示:apply是一种声明式对象配置命令。这里应用了之前创建的模板,-f参数表示使用文件名作为参数。相比命令式对象管理,apply既便于跟踪,又具备很好的可读性。本书将统一使用声明式对象配置来管理资源。*
创建成功后,可通过以下命令查询当前运行的所有Pod。
2. 查询Pod
Pod创建后,最常用的功能就是查询。可以用以下命令查询Pod的状态。
还可以在查询命令中带上参数-w,以对Pod状态进行持续监控。
只要Pod发生了变化,就会在控制台中输出相应信息。命令如下。
1
| kubectl get pod {Pod名称} -w
|
另外,还可以在查询命令中带上-o wide参数,输出Pod的更多概要信息(如调度到哪台机器上,Pod本身的虚拟IP等信息)。命令如下。
1
| kubectl get pod {Pod名称} -o wide
|
get命令除了可以显示简要的运行信息外,还可以输出完整信息。它支持多种格式的输出,如可以用yaml和Json方式输出,命令如下。
1 2
| kubectl get pod examplepod kubectl get pod examplepod
|
一般情况下,如果要查询Pod更详细的信息(包括状态、生命周期和执行情况等),除了将其输出为yaml或json格式,还可以用describe命令查看详情,格式如下。
1
| kubectl describe pods {Pod名称}
|
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
| [root@iZbp1f3y0av6g5d4zhb3uoZ examplepod]# kubectl get pod examplepod --output yaml apiVersion: v1 kind: Pod metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"name":"examplepod","namespace":"cert-manager"},"spec":{"containers":[{"args":["echo \"Hello Kubernetes!\"; sleep 3600"],"command":["sh","-c"],"image":"busybox","imagePullPolicy":"IfNotPresent","name":"examplepod-container"}]}} creationTimestamp: "2022-08-04T13:32:13Z" managedFields: - apiVersion: v1 fieldsType: FieldsV1 fieldsV1: f:metadata: f:annotations: .: {} f:kubectl.kubernetes.io/last-applied-configuration: {} f:spec: f:containers: k:{"name":"examplepod-container"}: .: {} f:args: {} f:command: {} f:image: {} f:imagePullPolicy: {} f:name: {} f:resources: {} f:terminationMessagePath: {} f:terminationMessagePolicy: {} f:dnsPolicy: {} f:enableServiceLinks: {} f:restartPolicy: {} f:schedulerName: {} f:securityContext: {} f:terminationGracePeriodSeconds: {} manager: kubectl-client-side-apply operation: Update time: "2022-08-04T13:32:13Z" - apiVersion: v1 fieldsType: FieldsV1 fieldsV1: f:status: f:conditions: k:{"type":"ContainersReady"}: .: {} f:lastProbeTime: {} f:lastTransitionTime: {} f:status: {} f:type: {} k:{"type":"Initialized"}: .: {} f:lastProbeTime: {} f:lastTransitionTime: {} f:status: {} f:type: {} k:{"type":"Ready"}: .: {} f:lastProbeTime: {} f:lastTransitionTime: {} f:status: {} f:type: {} f:containerStatuses: {} f:hostIP: {} f:phase: {} f:podIP: {} f:podIPs: .: {} k:{"ip":"10.42.2.25"}: .: {} f:ip: {} f:startTime: {} manager: k3s operation: Update time: "2022-08-04T13:32:39Z" name: examplepod namespace: cert-manager resourceVersion: "82803826" uid: be9afea5-2764-4a01-ba9e-33f13082e7d0 spec: containers: - args: - echo "Hello Kubernetes!"; sleep 3600 command: - sh - -c image: busybox imagePullPolicy: IfNotPresent name: examplepod-container resources: {} terminationMessagePath: /dev/termination-log terminationMessagePolicy: File volumeMounts: - mountPath: /var/run/secrets/kubernetes.io/serviceaccount name: default-token-kj5lt readOnly: true dnsPolicy: ClusterFirst enableServiceLinks: true nodeName: izbp113w9axywnhpyk1525z preemptionPolicy: PreemptLowerPriority priority: 0 restartPolicy: Always schedulerName: default-scheduler securityContext: {} serviceAccount: default serviceAccountName: default terminationGracePeriodSeconds: 30 tolerations: - effect: NoExecute key: node.kubernetes.io/not-ready operator: Exists tolerationSeconds: 300 - effect: NoExecute key: node.kubernetes.io/unreachable operator: Exists tolerationSeconds: 300 volumes: - name: default-token-kj5lt secret: defaultMode: 420 secretName: default-token-kj5lt status: conditions: - lastProbeTime: null lastTransitionTime: "2022-08-04T13:32:13Z" status: "True" type: Initialized - lastProbeTime: null lastTransitionTime: "2022-08-04T13:32:39Z" status: "True" type: Ready - lastProbeTime: null lastTransitionTime: "2022-08-04T13:32:39Z" status: "True" type: ContainersReady - lastProbeTime: null lastTransitionTime: "2022-08-04T13:32:13Z" status: "True" type: PodScheduled containerStatuses: - containerID: containerd: image: docker.io/library/busybox:latest imageID: docker.io/library/busybox@sha256:ef320ff10026a50cf5f0213d35537ce0041ac1d96e9b7800bafd8bc9eff6c693 lastState: {} name: examplepod-container ready: true restartCount: 0 started: true state: running: startedAt: "2022-08-04T13:32:38Z" hostIP: 172.19.230.67 phase: Running podIP: 10.42.2.25 podIPs: - ip: 10.42.2.25 qosClass: BestEffort startTime: "2022-08-04T13:32:13Z"
|
该命令会输出比较全面的信息,包括资源的基本信息、容器信息、准备情况、存储卷信息及相关的事件列表。在资源部署时如果遇到问题,可以使用此命令查看详情,分析部署错误的原因。
如果要查询Pod本身输出的日志信息,还可以使用logs命令,格式如下。
3. 修改Pod
可以用replace命令来修改原先设置的Pod属性,命令格式如下:
1
| kubectl replace -f {pod模板路径}
|
修改之前示例中定义的Pod,使它输出“Hello Kubernetesreplaced!”。先打开examplepod.yml文件。
在文件中填入如下内容并保存。
1 2 3 4 5 6 7 8 9 10 11
| apiVersion: v1 kind: Pod metadata: name: examplepod spec: containers: - name: examplepod-container image: busybox imagePullPolicy: IfNotPresent command: ['sh', '-c'] args: ['echo "Hello Kubernetes replaced!"; sleep 3600']
|
提示:Pod有很多属性无法修改,比如containers的image属性,spec下的activeDeadline Seconds、tolerations属性等。如果一定要修改,则需要加上—force参数,相当于重新创建Pod,命令如下。
1
| kubectl replace -f {pod模板路径}
|
4. 删除Pod
Pod的删除非常简单,只要执行以下命令即可
1
| kubectl delete pod {Pod名称}
|
另外,还可以基于模板文件删除资源,如以下命令所示:
1
| kubectl delete -f {模板文件名称}
|
2. Pod模板详解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| apiVersion: v1 kind: pod metadata: name: String namespace: String labels: - key: value annotations: - key: value spec: containers: - name: String image: String imagePullPolicy: [Always|Never|IfNotPresent] command: [String] args: [String] workingDir: String volumeMounts: - name: String mountPath: String readOnly: boolean ports: - name: String containerPort: int hostPort: int protocol: String env: - name: String value: String resources: limits: cpu: String memory: String MiB/GiB/MB/GB(1MiB=1024×1024B,#1MB=1000×1000B),将用于docker run --memory参数 requests: cpu: String docker run --cpu-shares参数 memory: String livenessProbe: exec: command: [String] httpGet: path: String port: number host: String scheme: String httpHeaders: - name: String value: String tcpSocket: port: number initialDelaySeconds: 0 timeoutSeconds: 0 (单位为秒,默认为1s) periodSeconds: 0 successThreshold: 0 failureThreshold: 0 securityContext: privileged: false restartPolicy: [Always|Never|OnFailure] nodeSelector: object imagePullSecrets: secretkey格式指定 - name: String hostNetwork: false volumes: - name: String emptyDir: {} hostPath: path: string secret: 预定义的secret对象 secretName: String items: - key: String path: String configMap: configMap对象到容器内部 name: String items: - key: String path: String
|
还可以使用$ kubectl explain pod
命令详细查看Pod资源所支持的所有字段的详细说明
可以看到图中列出了5个字段,分别是apiVersion、kind、metadata、spec、status。如果要进一步查看每个字段的详情,例如,对于spec字段可以使用命令$ kubectl explainpod.spec
进行查看,
如果要了解一个正在运行的Pod的配置,可以通过以下命令来获取。
1
| kubectl get pod {pod名称} -o yaml
|
3. Pod与容器
1. Pod创建容器的方式
之前描述的Pod模板和Docker-Compose配置非常相似,但Pod模板涉及其他部署参数的设定,相对更复杂。
先排除与容器无关的配置参数,在模板的Containers部分,指明容器的部署方式。在部署过程中,会转换成对应的容器运行时(containerruntime)命令,例如,对于Docker,会转换成类似于Docker run的命令。
在最开始的例子中,yml文件内容如下。
1 2 3 4 5 6 7 8 9 10 11
| apiVersion: v1 kind: Pod metadata: name: examplepod spec: containers: - name: examplepod-container image: busybox imagePullPolicy: IfNotPresent command: ['sh', '-c'] args: ['echo "Hello Kubernetes!"; sleep 3600']
|
在Kubernetes将Pod调度到某个节点后,kubelet会调用容器运行时(本例中为Docker),执行如下所示的命令。
1
| docker run --name examplepod-container busybox sh -c 'echo"Hello Kubernetes!"; sleep 3600'
|
提示:command和args设置会分别覆盖原Docker镜像中定义的EntryPoint与CMD,在使用时请务必注意以下规则。
- 如果没有在模板中提供command或args,则使用Docker镜像中定义的默认值运行。
- 如果在模板中提供了command,但未提供args,则仅使用提供的command。Docker镜像中定义的默认的EntryPoint和默认的命令都将被忽略。
- 如果只提供了args,则Docker镜像中定义的默认的EntryPoint将与所提供的args组合到一起运行。
- 如果只提供了args,则Docker镜像中定义的默认的EntryPoint将与所提供的args组合到一起运行。
- 如果同时提供了command和args,Docker镜像中定义的默认的EntryPoint和命令都将被忽略。所提供的command和args将会组合到一起运行。
同样,在Pod模板的Container设置中的各项信息,在运行时都会转换为类似的容器命令来执行。Container的基础信息的设置如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| containers: - name: String image: String imagePullPolicy: [Always|Never|IfNotPresent] Always表示下载镜像; 像 command: [String] 命令) args: [String] workingDir: String volumeMounts: - name: String volumes[]部分定义的卷名 mountPath: String readOnly: boolean ports: - name: String containerPort: int hostPort: int 宿主机的端口) protocol: String env: - name: String value: String
|
1.1.volumeMounts配置信息
容器运行时通常会提供一些机制来将存储附加到容器上。例如,Docker有两种容器机制:一种是数据卷(data volume),它可以将容器内的文件或目录映射到宿主机上的文件或目录中,其命令格式为
1
| $docker run -v /{主机的目录}:/{映射到容器的目录} {镜像名称}
|
另一种是数据卷容器(data volume container),不过其本质使用的还是数据卷,这种容器一般用在一组相关的容器中,用于专门处理数据存储以供其他容器挂载。
不管是数据卷还是数据卷容器,其存留时间通常超过其他容器的生命周期。由于生命周期不同步,因此实现起来非常缺乏灵活性。
为了解决这些问题,Kubernetes在数据卷的基础上,又新增加了一套自己的存储卷(volume)抽象机制。该机制不仅允许Pod中的所有容器方便地共享数据,还允许存储卷与Pod中的其他容器保持完全一致的生命周期
下面是一个简单的示例,说明如何对容器创建数据卷及存储卷,以实现数据共享。
- 首先,创建examplepodforvolumemount.yml文件。
1
| $ vim examplepodforvolumemount.yml
|
- 填入以下内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| apiVersion: v1 kind: Pod metadata: name: examplepodforvolumemount spec: containers: - name: containerforwrite image: busybox imagePullPolicy: IfNotPresent command: ['sh', '-c'] args: ['echo "test data!" > /write_dir/data; sleep 3600'] volumeMounts: - name: filedata mountPath: /write_dir - name: containerforread image: busybox imagePullPolicy: IfNotPresent command: ['sh', '-c'] args: ['cat /read_dir/data; sleep 3600'] volumeMounts: - name: filedata mountPath: /read_dir volumes: - name: filedata emptyDir: {}
|
在本例中,我们创建了两个容器。一个是containerforwrite
,它向数据卷写入数据,会向/write_dir/data
文件写入"testdata!"
文本。容器内的数据卷地址为/write_dir
,它引用的存储卷为filedata
。
另一个容器是containerforread
,TE
会从/read_dir/data
文件中读取文本,并将其输出到控制台(后续可以通过日志查询方式读取输出到控制台的文本)。容器内的数据卷地址为/read_dir
,它引用的存储卷为filedata
。
本例中还创建了一个存储卷,其名称为filedata
,这个名称会被容器设置中的数据卷所引用。
存储卷的类型是emptyDir
它是最基础的类型,表示纯净的空目录,其生命周期和所属的Pod
完全一致(后续章节会讲解更多的种类)。对于例子中的两个容器,虽然数据卷地址不同(一个是/write_dir
,一个是/read_dir
),但因为它们都是映射到同一个空目录下的,所以本质上仍在同一个文件夹内进行操作。
执行以下命令,创建Pod。
1
| $ kubectl apply -f examplepodforvolumemount.yml
|
通过以下命令,查看Pod的运行情况,READY 2/2表示两个容器都已成功运行。
1
| $ kubectl get pods examplepodforvolumemount
|
此时可以通过logs命令,查看Pod中containerforread容器的日志
1
| $ kubectl logs examplepodforvolumemount containerforread
|
此处已经可以看到containerforwrite写入的数据内容了。
1.2 ports配置信息
容器运行时通常会提供一些机制以将容器端口暴露出来,并映射到主机的端口上,以便其他人能通过“主机IP:端口”访问容器所提供的服务,例如,Docker的命令$ docker run -p {宿主机端口}:{容器端口} {镜像名称}
。同样,Pod模板中也提供了这个功能。为了通过例子进行演示,首先,创建examplepodforport.yml
文件。
1
| $ vim examplepodforport.yml
|
在文件中填入以下内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| apiVersion: v1 kind: Pod metadata: name: examplepodforport spec: containers: - name: containerfornginx image: nginx imagePullPolicy: IfNotPresent ports: - name: portfoxnginx containerPort: 80 hostPort: 8081 protocol: TCP
|
在本例中,Nginx镜像中默认定义的对外提供服务的端口为80。通过containerPort属性,我们将80端口暴露出来,再通过hostPort属性将其映射到宿主机的端口8081上,以便通过“主机IP:端口”访问容器所提供的服务,其中protocol为端口协议,支持TCP和UDP,默认为TCP。
执行以下命令,创建Pod。
1
| $ kubectl apply -f examplepodforport.yml
|
通过以下命令,查看Pod的运行情况,直到状态变为Running。
1
| $ kubectl get pods examplepodforport
|
Pod创建完成后,执行以下命令,查看Pod具体被分配到哪台Node上
1
| $ kubectl describe pods examplepodforport
|
Pod被分配到了izbp113w9axywnhpyk1525z/172.19.230.67
上
1
| Node: izbp113w9axywnhpyk1525z/172.19.230.67
|
使用wget检验端口是否开通
注意:以上案例仅为了说明Kubernetes
是如何创建容器的,这种类似于Docker直接映射到主机端口的方式,在Kubernetes中强烈不推荐。
Pod只是一个运行服务的实例,随时可能在一个Node上停止,而在另一个Node上以新的IP地址启动新的Pod,因此它不能以稳定的IP地址和端口号提供服务。若要稳定地提供服务,则需要服务发现和负载均衡能力。
3.env配置信息
容器运行时通常还会提供一些机制来输入可动态配置的一些环境变量,以供容器中的应用程序使用。如在Docker中,配置环境变量的命令为$ docker run --env {变量1}={值1} --env {变量2}={值2} ... {镜像名称}
。同样,Pod模板中也提供了这个功能。
首先,创建examplepodforenv.yml文件。
1
| $ vim examplepodforenv.yml
|
填入以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| apiVersion: v1 kind: Pod metadata: name: examplepodforenv spec: containers: - name: containerforenv image: busybox imagePullPolicy: IfNotPresent env: - name: parameter1 value: "good morning!" - name: parameter2 value: "good night!" command: ['sh','-c'] args: ['echo "${parameter1} ${parameter2}"; sleep 3600']
|
在模板中定义了一个名为containerforenv
的容器,向它传入了两个环境变量:其中一个名为parameter1
,值为goodmorning!
;另一个变量名为parameter2
,值为good night!
。在本例中,将通过在容器中执行命令的方式,将传入的两个环境变量拼接到一起并输出到日志。
执行以下命令,创建Pod
1
| $ kubectl apply -f examplepodforenv.yml
|
运行以下命令,查看Pod的运行情况,直到状态变为Running
1
| $ kubectl get pods examplepodforenv
|
通过以下命令,查看Pod中输出的日志。
1
| $ kubectl logs examplepodforenv
|
可以看到两个环境变量的值成功拼接到一起并输出到日志中
2. Pod组织容器的方式
Pod的设计初衷在于同时运行多个共同协作的进程(作为容器来运行)。Pod中的各个容器总是作为一个整体,同时调度到某台Node上。容器之间可以共享资源、网络环境和依赖,并拥有相同的生命周期。
当然,在同一个Pod中同时运行和管理多个容器,是一种相对高级的用法,只在容器必须要紧密配合进行协作的时候才使用此模式。
1. 容器如何组成一个Pod
Pod只是一种抽象,并不是一个真正的物理实体,表示一组相关容器的逻辑划分。每个Pod都包含一个或一组密切相关的业务容器,除此之外,每个Pod都还有一个称为“根容器”的特殊Pause容器。
Pause容器其实属于Kubernetes的一部分。在一组容器作为一个单位的情况下,很难对整个容器组进行判断,如一个容器挂载了能代表整个Pod都挂载了吗?
如果引入一个和业务无关的Pause容器,用它作为Pod的根容器,用它的状态代表整组容器的状态,便能解决该问题。
另外,Pod中的所有容器都共享Pause容器的IP地址及其挂载的存储卷,这样也简化了容器之间的通信和数据共享问题。另外,Pause容器还在Pod中担任Linux命名空间共享的基础,为各个容器启用pid命名空间,开启init进程。
例如,对于本章最开始的操作示例,创建Pod后可以登录对应的Node,使用以下命令查看创建的容器。
Pod中的容器可以使用Pod所提供的两种共享资源——存储和网络。
1) 存储
在Pod中,可以指定一个或多个共享存储卷。Pod中的所有容器都可以访问共享存储卷,从而让这些容器共享数据。存储卷也可以用来持久化Pod中的存储资源,以防容器重启后文件丢失。
2)网络
每个Pod都分配了唯一的IP地址。Pod中的每个容器都共享网络命名空间,包括IP地址和网络端口。Pod内部的容器可以使用localhost互相通信。当Pod中的容器与Pod外部进行通信时,还必须共享网络资源(如使用端口映射)。
Docker和Kubernetes在网络空间上的差异
要查看Pod的IP,可以使用以下命令
1
| $ kubectl get pod my-app
|
2.Pod之间如何通信
Docker其实一开始没有考虑多主机互连的网络解决方案。在实际的业务场景中,组件之间的管理十分复杂,应用部署的粒度更加细小。Kubernetes使用其独有的网络模型去解决这些问题。
Pod之间的通信主要涉及两个方面:
1)同一个Node上Pod之间的通信
因为同一个Node上的Pod使用的都是相同的Docker网桥,所以它们天然支持通信。
每一个Pod都有一个全局IP地址,同一个Node内不同Pod之间可以直接采用对方Pod的IP地址通信,而且不需要使用其他发现机制。因为它们都是通过veth连接在同一个docker0网桥上的,其IP地址都是从docker0网桥上动态获取的,并关联在同一个docker0网桥上,地址段也相同,所以它们之间能直接通信。
2)跨Node的Pod之间的通信
要实现跨Node的Pod之间的通信,首先需要保证的是Pod的IP地址在所有Node上都是全局唯一的。这其实并不复杂,因为Pod的IP地址是由Docker 网桥分配的,所以可以将不同Node机器上的Docker网桥配置成不同的IP网段来实现这个功能。
然后需要在容器集群中创建一个覆盖网络来连接各个机器。目前可以通过第三方网络插件来覆盖网络。
Flannel会配置Docker网桥(即docker0),通过修改Docker的启动参数bip来实现这一点。通过这种方式,集群中各台机器的Docker网桥就得到了全局唯一的IP网段,它所创建的容器自然也拥有全局唯一的IP。