Pod
是Kubernetes
项目里定义的最小可调度单元,是Kubernetes
对应用程序的抽象。在这篇文章里我将会介绍Kubernetes
里Pod
的基本概念,使用方式,生命周期以及如何使用Pod
部署应用。读这篇文章的朋友我会默认你已经了解Kubernete
是用来解决什么问题的,以及电脑上已经安装了Minikube
这个能试验Kubernetes
功能的工具。如果尚未做好这些准备工作,推荐先去看下面的两篇文章做好准备工作后再来学习这里的内容。
你一定要了解的Kubernetes
运行在笔记本上的Kubernetes集群
在Kubernetes
的API
对象模型中,Pod
是最小的API
对象,换一个专业点的的说法可以这样描述:Pod
,是 Kubernetes
的原子调度单位。在集群中,Pod
表示正在运行的应用进程。Pod
的内部可以有一个或多个容器,同属一个Pod
的容器将会共享:
网络资源
相同的IP
存储
应用到Pod上的自定义配置
可以看到Pod
是Kubernetes
定义出来的一个逻辑概念,可以用另外一种方式来理解Pod
:一种特定于应用程序的“逻辑主机”,其中包含一个或多个紧密协作的容器。例如,假设我们在Pod中有一个应用程序容器和一个日志记录容器。日志记录容器的唯一工作是从应用程序容器中提取日志。将两个容器放置同一个Pod
里可消除额外的通信时间,因为它们位于同一个"主机",因此所有内容都是本地的并且它们共享所有资源,就跟在同一台物理服务器上执行这些操作一样。
此外也不是所有有“关联”的容器都属于同一个Pod
。比如,应用容器和数据库虽然会发生访问关系,但并没有必要、也不应该部署在同一台机器上,它们更适合做成两个Pod
。
根据Pod
里的容器数量可以将Pod
分为两种类型:
单容器模型。由于Pod
是Kubernetes
可识别的最小对象,Kubernetes
管理调度Pod
而不是直接管理容器,所以即使只有一个容器也需要封装到Pod
里。
多容器模型。在这个模型中,Pod
可以容纳多个紧密关联的容器以共享Pod
里的资源。这些容器作为单一的,凝聚在一起的服务单元工作。
每个Pod
运行应用程序的单个实例。如果需要水平扩展/缩放应用程序(例如运行多个副本),则可以为每个实例使用一个Pod
。这与在单个Pod
中运行同一应用程序的多个容器不同。
还需要提的一点是,Pod
本身不具有调度功能。如果所在的节点发生故障或者你要维护节点,则Pod
是不会自动调度到其他节点了。Kubernetes
用一系列控制器来解决Pod
的调度问题,Deployment
就是最基础的控制器。通常我们都是在定义的控制器的配置里通过PodTemplate
定义要控制的Pod
,让控制器和所管控的Pod
一起被创建出来(这部分内容后面单独写文章讨论)。
一个Pod
的状态会告诉我们它当前正处于生命周期的哪个阶段,Pod
的生命周期有5个阶段:
Pending:等待状态表明至少有一个Pod
内的容器尚未创建。
Running:所有容器已经创建完成,并且Pod
已经被调度到了一个Node上。此时Pod
内的容器正在运行,或者正在启动或重新启动。
Succeeded:Pod
中的所有容器均已成功终止,并且不会重新启动。
Faild: 所有容器均已终止,至少有一个容器发生了故障。失败的容器以非零状态退出。
Unknown:无法获得Pod
的状态。
我们已经讨论了Pod
在理论上的含义,现在让我们看看它在实际中长什么样。我们将首先浏览一个简单的Pod
定义YAML
文件,然后部署一个示例应用程序来展示如何使用它。
Kubernetes
里所有的API
对象都由四部分组成:
apiVersion -- 当前使用的Kubernetes
的API版本。
kind -- 你想创建的对象的种类。
metadata -- 元数据,用于唯一表示当前的对象,比如name、namespace等。
spec -- 我们的Pod
的指定配置,例如镜像名称,容器名称,数据卷等。
apiVersion
,kind
和metadata
是必填字段,适用于所有Kubernetes
对象,而不仅仅是pod
。spec
里指定的内容(spec
也是必需字段)会因对象而异。下面的示例显示了Pod的YAML文件大概长什么样子。
apiVersion: "api version"
kind: "object to create"
metadata:
name: "Pod name"
labels:
app: "label value"
spec:
containers:
- name: "container name"
image: "image to use for container"
关于YAML的语法可以参考前面的文章:YAML,另一种标记语言?不止是标记语言!
理解了Pod
配置文件的模板后,接下来我们看看如何使用配置文件创建上面说的两种模型的Pod
。
下面的pod-1.yaml
是个单容器Pod
的清单文件。它会运行一个Nginx
容器。
apiVersion: v1
kind: Pod
metadata:
name: first-pod
labels:
app: myapp
spec:
containers:
- name: my-first-pod
image: nginx
接下来,我们通过运行Kubectl create -f pod-1.yaml
将清单文件部署到本地的Kubernetes
集群中。然后,我们运行kubectl get pods
以确认我们的Pod
运行正常。
kubectl get pods
NAME READY STATUS RESTARTS AGE
first-pod 1/1 Running 0 45s
可以到Pod
的Nginx
容器里执行以下service nginx status
命令确保Nginx
在正常运行。
kubectl exec first-pod -- service nginx status
nginx is running.
这会在Pod
里执行service nginx status
指令,类似docker exec
命令。
现在,我们通过运行kubectl delete pod first-pod
删除刚才创建的Pod
。
kubectl delete pod first-pod
pod "firstpod" deleted
下面我们将部署一个更复杂的Pod
:一个拥有两个容器的Pod
,这些容器相互协作作为一个实体工作。其中一个容器每10秒将当前日期写入一个文件,而另一个Nginx
容器则为我们展示这些日志。这个Pod
的YAML
如下:
apiVersion: v1
kind: Pod
metadata:
name: multi-container-pod # pod的名称
spec:
volumes:
- name: shared-date-logs # 为Pod里的容器创建一个共享数据卷
emptyDir: {}
containers:
- name: container-writing-dates # 第一个容器的名称
image: alpine # 容器的镜像
command: ["/bin/sh"]
args: ["-c", "while true; do date >> /var/log/output.txt; sleep 10;done"] # 每10秒写入当前时间
volumeMounts:
- name: shared-date-logs
mountPath: /var/log # 将数据卷挂在到容器的/var/log目录
- name: container-serving-dates # 第二个容器的名字
image: nginx:1.7.9 # 容器的镜像
ports:
- containerPort: 80 # 定义容器提供服务的端口
volumeMounts:
- name: shared-date-logs
mountPath: /usr/share/nginx/html # 将数据卷挂载到容器的/usr/share/nginx/html
上面通过volumes
指令定义了Pod内的数据卷
volumes:
- name: shared-date-logs # 为Pod里的容器创建一个数据卷
emptyDir: {}
第一个容器将数据卷挂载到了/var/log/
每隔10秒往output.txt
文件里写入时间,而第二个容器通过将数据卷挂载到/usr/share/nginx/html
伺服了这个日志文件。
执行kubectl create -f pod-2.yaml
创建这个多容器Pod
:
kubectl create -f pod-2.yaml
pod "multi-container-pod" created
然后确保Pod
已经正确部署:
kubectl get pods
NAME READY STATUS RESTARTS AGE
multi-container-pod 2/2 Running 0 1m
通过运行kubectl describe pod podName
,查看Pod的详细信息,里面会包含两个容器的信息。(下面的内容只截取了容器相关的信息)
Containers:
container-writing-dates:
Container ID: docker://e5274fb901cf276ed5d94b...
Image: alpine
Image ID: docker-pullable://alpine@sha256:621c2f39...
Port:
Host Port:
Command:
/bin/sh
Args:
-c
while true; do date >> /var/log/output.txt; sleep 10;done
State: Running
Started: Sat, 1 Aug 2020 11:31:44 +0800
Ready: True
Restart Count: 0
Environment:
Mounts:
/var/log from shared-date-logs (rw)
/var/run/secrets/Kubernetes.io/serviceaccount from default-token-8dl5j (ro)
container-serving-dates:
Container ID: docker://f9c85f3fe3...
Image: nginx:1.7.9
Image ID: docker-pullable://nginx@sha256:e3456c851...
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Sat, 1 Aug 2020 11:31:44 +0800
Ready: True
Restart Count: 0
Environment:
Mounts:
/usr/share/nginx/html from shared-date-logs (rw)
/var/run/secrets/Kubernetes.io/serviceaccount from default-token-8dl5j (ro)
两个容器都在运行,下面我们将进到Pod
里确保两个容器都在执行分配的作业。
通过运行kubectl exec -it multi-container-pod -c container-serving-dates -- bash
连接到Nginx
容器里。
在容器内运行curl'http://localhost:80/output.txt'
,它应该返回时间日志文件的内容给我们。(如果容器中未安装curl,请先运行apt-get update && apt-get install curl
,然后再次运行curl'http://localhost:80/output.txt'
。)
curl 'http://localhost:80/app.txt'
Sat Aug 1 11:31:44 CST 2020
Sat Aug 1 11:31:54 CST 2020
Sat Aug 1 11:32:04 CST 2020
除了上面说的那些之外,我们可以在一个Pod
中按照顺序启动一个或多个辅助容器,来完成一些独立于主进程(主容器)之外的工作,完成工作后这些辅助容器会依次退出,之后主容器才会启动,这种容器设计模式叫做sidecar
。
比如对于前端Web
应用,如果把构建后的Js
项目放到Nginx
镜像的/usr/share/nginx/html
目录下,Nginx
和Js
应用做成一个镜像运行容器,每次应用有更新或者Nginx
要做升级、更新配置操作都需要重新做一个镜像,非常麻烦。
有了Pod
之后,这样的问题就很容易解决了。我们可以把前端Web
应用和Nginx
分别做成镜像,然后把它们作为一个Pod
里的两个容器"组合"在一起。这个Pod
的配置文件如下所示:
apiVersion: v1
kind: Pod
metadata:
name: web-2
spec:
initContainers:
- image: kevinyan/front-app:v2
name: front
command: ["cp", "/www/application/*", "/app"]
volumeMounts:
- mountPath: /app
name: app-volume
containers:
- image: nginx:1.7.9
name: nginx
ports:
- containerPort: 80 # 定义容器提供服务的端口
volumeMounts:
- mountPath: /usr/share/nginx/html
name: app-volume
volumes:
- name: app-volume
emptyDir: {}
所有spec.initContainers
定义的容器,都会比spec.containers
定义的用户容器先启动。并且,Init
容器会按顺序逐一启动,直到它们都启动并且退出了,用户容器才会启动。所以,这个Init
类型的容器启动后,执行了一句"cp /www/application/* /app",把应用包拷贝到"/app"目录下,然后退出。这个"/app"目录,挂载了一个名叫app-volume
的Volume
。接下来Nginx
容器,同样声明了挂载app-volume
到自己的"/usr/share/nginx/html"目录下。由于这个Volume
是被Pod
里的容器共享的所以等Nginx
容器启动时,它的目录下就一定会存在前端项目的文件。这个文件正是上面的Init
容器启动时拷贝到Volume
里面的。
这就是容器设计模式里最常用的一种模式:sidecar
。顾名思义,sidecar
指的就是我们可以在一个Pod
中,启动一个辅助容器,来完成一些独立于主进程(主容器)之外的工作。
Pod
把多个紧密关联的容器组织在一起,让他们共享自己的资源,这点有些像是这些容器的"主机",只不过这个"主机"是个逻辑概念。当你需要把一个运行在虚拟机里的应用迁移到容器中时,一定要仔细分析到底有哪些进程(组件)运行在这个虚拟机里。然后,你就可以把整个虚拟机想象成为一个 Pod,把这些进程分别做成容器镜像,把有顺序关系的容器,定义为 Init Container。这才是更加合理的、松耦合的容器编排诀窍,也是从传统应用架构,到“微服务架构”最自然的过渡方式。
最后关于Docker In Docker这种把整个应用塞到一个容器里的方法的弊端请查看之前的文章:Docker容器的"单进程模型"。
- END -
关注公众号,获取更多精选技术原创文章