3.1 Pod
Pod 在 Kubernetes 中是最重要的对象之一,Pod 集群中创建和管理的、最小的可部署的计算单元。
为什么这样说呢?
因为在 Kubernetes 中部署一个应用时,创建的便是 Pod,一个 Pod 中包含一个或多个容器,这些容器在 Pod 中能够共享网络、存储等环境,然后组成一个应用对外提供服务。
我们不必过于关注这些概念,我们只需要知道,在 Kubernetes 中,是不会直接操作容器的,而是通过 Pod 封装了容器,集群通过管控 Pod ,便可控制容器的存储、网络等资源,实现资源隔离或共享。
学习 Kubernetes,Pod 是最重要最基本的知识,本章将介绍什么是 Pod、Pod 的结构等,如何使用 Pod 部署应用,读者需要多加练习。
Pod 基础知识
创建 Pod
nginx 是工作中最常用的中间件之一,在本小节中,我们将尝试在 Kubernetes 中通过 Pod 创建 nginx。
创建 一个 Pod 或者说通过 Pod 部署应用很简单,示例命令如下:
kubectl run nginxtest --image=nginx:latest --port=80
nginxtest
为 Pod 名称,并且为其映射端口为 80。
如果我们使用 docker 部署一个 nginx,那么命令可以这样写:
docker run --name nginxtest -p 80:80 nginx:latest
在这个例子中,我们创建了一个 nginx 应用,并且为其暴露了 80 端口。
[Info] 提示
一般部署应用使用的是 Deployment 、Daemon 等,而不会直接部署 Pod;Deployment、Daemont 可以监控 Pod,当 Pod 故障时,会对其进行重新部署等操作。相比之下,手动创建的 Pod 不被 “托管” 起来,如果 Pod 故障或者其他原因消失了,是不会自动恢复的。
Kubernetes Pod 中的所有容器共享一个相同的 Linux 命名空间(network、UTS、IPC),而不是每个容器一个命名空间,这一点与 Docker 不同,因此 Pod 中的多个容器使用相同的网络、主机名称,看到的是同一个 IP 地址,不过进程还是按照容器进行隔离。
据说在新版本的 Kubernetes 和 Docker 中, PID 命名空间也可以设置为相同的。
由于 Mount、User 命名空间不共享,因此在容器中,文件系统和用户是隔离的。
在 Kubernetes 中,每个 Pod 的网络都是独立的,每个 Pod 都有自己的 IP 地址,在我们没有使用 Service 为 Pod 之前,Pod 只能通过专属的 IP 地址访问。
会在后面的章节中介绍 Service。
我们现在来查看 Pod 的运行状态以及一些简单的信息:
kubectl get pods -o wide
然后通过图中显示的 IP 来访问 nginx 服务:
后面我们还有很多实验要做,这里让我们先删除这个 Pod。
删除命令如下:
kubectl delete pod nginxtest
我们创建 Pod 的命令很简单,但是 Pod 能做的不止这一点,如果需要发挥 Pod 的全部能力,就需要使用 YAML 文件了。
在 Kubernetes 中,可以通过 YAML 文件定义资源,然后通过 YAML 文件中的描述信息,告诉 Kubernetes 怎么创建 Pod 、管理 Pod。
接下来,我们通过一个 YAML 文件来部署 Pod。
使用 YAML 文件定义需要的 Pod 格式示例如下:
apiVersion: v1
kind: Pod
metadata:
name: nginxtest
spec:
containers:
- image: nginx:latest
name: nginxtest
ports:
- containerPort: 80
protocol: TCP
取名为 nginx.yaml。
然后,我们将这个 YAML 应用到 Kubernetes 中:
kubectl apply -f nginx.yaml
了解 Pod
Pod 是 Kubernetes 中调度资源的最小单位,一个 Pod 中可以包含多个容器,Pod 中的容器被打包在一起作为一个整体, Pod 中的容器不会被分配到不同节点中,它们一定被部署到同一个节点中。
每个 Pod 有且只有一个唯一的 IP 地址,通过 kubectl get pod {pod名称} -o wide
可以查询到。
root@master:~# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginxtest 1/1 Running 0 12m 10.32.0.2 slave1 <none> <none>
[Info] 提示
-o wide
可以查看对象更多的信息。
每个 Pod 之间都是隔离的,也就是在 Pod 内无论怎么使用端口,都不会对另一个 Pod 产生影响。
但是在 Pod 内,因为各个容器都是共享同一个 Pod 的网络,因此一个 Pod 内,不允许有两个进程占用相同的端口。
Pod IP 可以 Ping 通,Pod 之间可以通过 Pod IP 访问。
我们可以把一个 Pod 形容为一个虚拟主机。
在 Pod 中,所有容器(进程)都在一个主机上,我们知道,同一个主机上的所有进程共享着主机网络,多个进程自然不能同时占用一个端口,否则会冲突。
容器共享 Pod 中的网络,所以 Pod 中的容器也能够通过 localhost 相互访问,因此 Pod 中的 容器(进程)不能暴露/使用相同的端口。
通过 Pod IP 和 locahost
,解决了 高度耦合的间通信问题。既实现了不同应用之间的网络隔离,也满足了应用内的通讯需求。
在 Kubernetes 中,一个应用可以只有一个进程,也可以是一个多容器组成的 Pod,也可以是多个 Pod 组成的。
其实通过 Docker ,也可以做到类似的效果。
Docker 有一个 Contaner 模式,能够让多个容器共享一个 Network Namespace,可以让一个容器和另一个容器共享 IP、端口范围。
假如容器 A 已经创建起来,那么容器 A 会创建一个虚拟网卡;然后指定 容器 B 与容器 A 共享网络,那么两者就可以直接通过 localhost 进行通讯。
前面提到,Pod 中的容器共享了同一个网络,但是进程是隔离的,也就是 Pod 中的容器是部分隔离的。
除了进程是隔离的之外,每个容器都有自己的文件系统,各自的文件都会被隔离,一个容器不能访问或修改其它容器的文件。
为了让多个 容器之间能够共享文件,可以使用卷,把同一个卷映射到容器中,当然这是后话,我们在第五章的时候才会学到。
Pod 是怎样启动的
在 Kubernetes 中,当创建 Pod 时,会先启动一个 pause 容器,然后 Pod 中我们定义的容器会以 Container 模式共享 pause 中的网络。
即是是一个小节中的 Docker Contaner 模式。
使用下面的命令查看节点中的所有容器:
docker ps | grep pause
其中有一条:
k8s_POD_nginxtest_default_dd8879d5-23b0-43db-b1e5-71ecb79268d2_0
这个 pause 容器创建了网络,然后 nginxtest 容器以 Container 模式连接到了这个 pause 容器的网络,这时就能通过 IP 访问 nginxtest 了。
[Info] 提示
当然,pause 不只是实现容器的网络互通,还有其它功能。
在第一章的 Docker 介绍中,谈到过此类容器,其目的是让其他容器联通起来, pause 在 Pod 的网络中相当于交换机。
Pod 生命周期
当 Pod 被分配到某个节点时, Pod 会一直在该节点运行,直到停止或被终止,Pod 在整个生命周期中只会被调度一次。也就是说 Pod 是一次性的,如果 Pod 故障了,那么如果需要恢复应用的运行,是需要重新创建 Pod 的,而不是重启 Pod。
Pod 的整个生命周期可能有四种状态:
- Pending,尝试启动容器,如果容器正常启动,则进入下一个阶段;
- Running,处于运行状态;
- Succeeded、Failed,正常结束或故障等导致容器结束;
- Unknown,因为某些原因无法取得 Pod 的状态。
为了观察 Pod 的生命周期中的事件,我们可以创建一个 Deployment,查看当前正在发生的事件:
这里只需要了解 Pod 的生命周期,关于 Deployment,我们后面才会学习到。
kubectl create deployment nginx --image=nginx:latest --replicas=3
查看当前命名空间(default)中发生的事件:
kubectl get events
通过 kubectl describe pod {pod名称}
查看一个 Pod 的详细信息,里面有这个 Pod 的事件:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 3m22s default-scheduler Successfully assigned default/nginx-* to instance-2
Normal Pulling 3m19s kubelet Pulling image "nginx:latest"
Normal Pulled 3m17s kubelet Successfully pulled image "nginx:latest" in 2.570763819s
Normal Created 3m17s kubelet Created container nginx
Normal Started 3m16s kubelet Started container nginx
上面查询到的事件均发生在 Pod 的 Pending
状态,我们可以看到在这个阶段中,Pod 被调度,然后拉取镜像、启动容器,如果容器启动成功,Pod 便会进入 Running
状态。
事件记录保存在 etcd 中。
在 Kubernetes 中,Pod 被认为是相对的临时性实体,而不是长期存在的。由于 Pod 本身不具有治愈能力,如果 节点故障或者节点资源耗尽、节点被维护、Pod 被驱逐等,那么 Pod 无法在节点上继续存活。无论 Pod 因为何种原因被删除,在 Pod 中的网络、存储卷等,也会被销毁,新的 Pod 被创建时,相关的网络、存储卷也会被重建。
【图来源:https://kubernetes.io/zh/docs/concepts/workloads/pods/pod-lifecycle/】
[Info] 提示
由于 Pod 是临时性的,为了保障服务能够在 Pod 挂了后自动重建,可以使用 Deployement、Daemon 等对象管理 Pod,这些对象被称为 控制器。
为了 Pod 创建创建后可以恢复服务,可以挂载卷以便持久化存储数据、创建 Service 以便对外固定 IP 访问。
不过 Pod 一般应该是无状态的。
在删除 Pod 时,Kubernetes 会终止 Pod 中的所有容器,会向容器中的进程发生 SIGTERM 信号,等待进程的正常关闭,所以 Pod 可能不会被马上删除,当然如果进程不能正常关闭,Kubernetes 最多等待 30s,然后会使用 SIGKILL 强制杀死进程。
容器重启策略
这是一个简单的 Pod 的 YAML 定义:
apiVersion: v1
kind: Pod
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:latest
imagePullPolicy: Always
name: nginx
... ...
restartPolicy: Always
在这个 YAML 中,有两个*Policy
,取值有 Always、OnFailure 和 Never 三种,它们代表了某种生命周期策略。
对于每个容器,都可以设置 imagePullPolicy
,指示在拉取镜像时如果失败,是否进行重试。
在 spec
中,有个 restartPolicy
字段,其值默认为 Always
,指示 Pod 中所有容器的重启动作,但是在 Deployment
、StatefulSet
、DaemonSet
等控制器中,restartPolicy
只支持 Always ,不支持 OnFailure 和 Never 。
[Info] 提示
如果容器启动失败,重试间隔会越来越长。 kubelet 会在 10s 后重试第一次,如果还是失败,第二次 20s 后再重试;按照 10s、20s、40s、80s ... 的间隔重试,但最长不超过 5 分钟。如果容器被成功运行且运行了 10 分钟以上,那么计时器会被重置,下次出现故障时,按照 10s、20s 的间隔时间重试。
如果单独创建 Pod,并且设置了 restartPolicy: Always
,那么 Pod 会一直停留在此节点上,如果 容器故障,Pod 可能会无限重试。
如果我们使用 Deployment
、StatefulSet
、DaemonSet
等控制器管理 Pod,这些控制器能够处理 Pod/副本 的管理、上线,并且在 Pod 失效时提供治愈能力,当,当然,这些东西后面再提。
节点上的 Pod 停止工作时,可以创建替代性的 Pod, Pod 被调度到一个健康的节点执行。
[Info] 提示
为什么要使用控制器管理 Pod 呢?
举个例子,笔者有个朋友,写了个 A 程序,这个程序能够在执行后,关闭计算机(关机)。本来是需要的时候执行一次,但是后面配置了一个守护进程,这个守护进程会自动启动 A 程序。这样出现了无限循环,开机 -> 启动守护进程 -> A 出现 -> 关机,结果这个朋友的电脑无法正常开机,一开机就被关机。
这种情况跟单独的 Pod 部署类似,很容易因为某些原因无限重试,并且一直驻留在节点上。因为重试是在原来的基础上进行重试,使用原来的文件、数据、网络等。控制器则可以将其重置,恢复
出厂设置
。如果一个节点上的 CPU、内存不够用了,那么容器有可能因为资源不足,无法启动,导致无限重试;如果使用控制器,控制器会在有充足资源的、健康的节点上重建 Pod。
Pod 的部署和管理
但是一般很少直接创建或管理 Pod,一般使用控制器来管理 Pod。下面列出一些控制器,我们来提前了解一下,在后面的学习中我们会一步步深入学习这些控制器。
- Deployment
- StatefulSet
- DaemonSet
[Error] 注意
单独创建 Pod ,一般用于临时调试、学习等目的,生产中一般不会这样玩。
创建 Pod
在 Kubernetes 中,所有对象都可以使用 YAML 表示。一般很少用命令参数来创建对象,因为命令参数没有那么灵活和那么多的选项可以用。
一般都是用 YAML 文件来描述怎么创建 Pod ,为 Pod 分配多少资源等。
下面我们来看看创建一个 Pod 的基本模板:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:latest
如果要映射网络端口,则 YAML 文件模板为:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
protocol: TCP
[INFO] 提示
由于 Pod 中的容器共享相同的网络,并且对外提供了 IP,因此不加上
ports
,通过 IP 和端口照样能够访问 Pod 中的应用。在 YAML 里面加上容器端口,主要有助于其他人快速了解此 Pod 的定义信息。
将上面的 YAML 内容复制到 pod.yaml 中,然后执行命令应用 YAML :
kubectl apply -f pod.YAML
# 或
kubectl create -f pod.YAML
使用
kubectl run
命令用于参数式的命令:kubectl run nginx-pod --image=nginx:latest
kubectl create
用于创建资源;
kubectl apply
可以创建资源,如果资源已经存在,则更新资源;
覆盖容器命令
在 Pod 中可以配置容器的一些信息,也可以替换容器的启动命令,其配置格式如下:
spec:
containers:
- name: nginx
image: nginx:latest
command: ["/bin/command"]
args: ["arg1","arg2","arg3"]
Pod 管理
输入 kubectl get pods
可以查看名为 default 命名空间的 Pod;
输入 kubectl get pods -o wide
可以查看多几个字段的 Pod 信息;
输入 kubectl describe pods
可以查看每个 Pod 的所有详细信息。
root@instance-2:~# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 1/1 Running 0 11s 192.168.56.3 instance-2 <none> <none>
可以看到 Pod 在第二个节点上部署,其 IP 为 192.168.56.3
。
在没有按照网络插件的情况下,Pod 的 IP 只能在被部署服务的节点上访问,不同节点不能访问其的 Pod。
这个会在第四章的 Service 部分说明。
nginx 默认使用了 80 端口,因此通过 192.168.56.3:80
,可以直接访问到容器中的 nginx 服务。
[Error] 注意
只能在 Pod 部署的节点上连接到此 IP。如果要在不同的节点访问 Pod,需要安装 CNI 网络插件,如 flannel、calico、weave 等,在 2.2、2.3 中有介绍。
查看日志
在 Docker 中,我们可以通过 docker logs {容器id}
来查看容器中的日志,这些日志是进程打印到控制器的标准输出,例如 C# 的 Console.Write
、C 语言的 printf
、Go 语言的 fmt.Print
,Docker 的 本地日志驱动会捕获容器的 stdout/stderr 输出记录驱动器。
Docker 日志默认限制日志大小为 10 MB ,每天会轮替一个日志文件,并使用自动压缩来减少磁盘文件大小。
Docker 日志驱动程序使用基于文件的存储。文件格式和存储机制被设计为由 Docker 守护进程独占访问,不应被外部工具使用,因为在未来的版本中实现可能会发生变化。
笔者没有明确查找到 Docker 的日志具体限制情况,以上内容来自 https://docs.docker.com/config/containers/logging/local/ 的参考资料。
在 Kubernetes 中,也可以通过类似的命令快速查看 Pod 中的容器的日志。
如果 Pod 中只有一个容器,则直接使用类似命令即可:
kubectl logs {pod名称}
如果 Pod 中有多个容器,则需要指定容器名称:
kubectl logs {pod名称} -c {容器名称}
kubectl logs -f
可以一直实时看到容器打印的日志。
kubectl logs
只能获取当前正在运行的 Pod 的日志,如果 Pod 被删除,则所有日志记录都会被删除。
查看、维护 Pod 状态,比较常用的命令有:
- kubectl get - 列出对象资源,如
kubectl get pods
;- kubectl describe - 显示有关资源的详细信息,如
kubectl describe pod nginxtest
;- kubectl logs - 打印 pod 和其中容器的日志;
- kubectl exec - 在 pod 中的容器上执行命令,格式为
kubectl exec {pod名称} -c {容器名称} -- {要执行的命令}
,--
用于分隔命令,其后面的参数均表示要传递进容器的命令。