Kubernetes Pod 全面知识
Pod 是在 Kubernetes 中创建和管理的、最小的可部署的计算单元,是最重要的对象之一。一个 Pod 中包含一个或多个容器,这些容器在 Pod 中能够共享网络、存储等环境。
学习 Kubernetes,Pod 是最重要最基本的知识,本章将介绍什么是 Pod、Pod 的结构等,并练习创建 Pod。
Pod 基础知识
创建 Pod
创建一个 Pod 很简单,示例命令如下:
kubectl run nginxtest --image=nginx:latest --port=80
nginxtest
为 Pod 名称,并且为其映射端口为 80。
但是一般不会直接创建 Pod,因为手动创建的 Pod 不被 “托管” 起来,如果 Pod 故障或者其他原因消失了,不会自动恢复,而且 kubectl run
提供的参数有限。
Kubernetes Pod 中的所有容器共享一个相同的 Linux 命名空间(network、UTS、IPC),而不是每个容器一个命名空间,因此 Pod 中的容器,网络、主机名称相同、IP 地址相同。据说在新版本的 Kubernetes 和 Docker 中, PID 命名空间也可以设置为相同的。由于 Mount、User 命名空间不共享,因此在容器中,文件系统和用户是隔离的。
另外我们也可以使用 yaml 方式定义 Pod,然后创建它。使用 YAML 文件定义需要的 Pod 格式示例如下:
apiVersion: v1
kind: Pod
metadata:
name: nginxtest
spec:
containers:
- image: nginx:latest
name: nginxtest
ports:
- containerPort: 80
protocol: TCP
但是描述 Pod 的 YAML 文件,包含哪些内容?格式如何?每个字段是什么意思?我们不可能记住所有 Kubernetes 对象的 YAML 文件的每个字段吧?
还好,Kuberentes 提供了 kubectl explain
命令,可以帮助我们快速了解创建一个对象的 YAML 需要哪几部分内容。例如创建 Pod 的 YAML 定义如下:
oot@master:~# kubectl explain pod
KIND: Pod
VERSION: v1
DESCRIPTION:
Pod is a collection of containers that can run on a host. This resource is
created by clients and scheduled onto hosts.
FIELDS:
apiVersion <string>
APIVersion defines the versioned schema of this representation of an
object. Servers should convert recognized schemas to the latest internal
value, and may reject unrecognized values. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
kind <string>
Kind is a string value representing the REST resource this object
represents. Servers may infer this from the endpoint the client submits
requests to. Cannot be updated. In CamelCase. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
metadata <Object>
Standard object's metadata. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
spec <Object>
Specification of the desired behavior of the pod. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
status <Object>
Most recently observed status of the pod. This data may not be up to date.
Populated by the system. Read-only. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
如果要查更深一层的字段,则可以:
root@master:~# kubectl explain pod.kind
KIND: Pod
VERSION: v1
FIELD: kind <string>
DESCRIPTION:
Kind is a string value representing the REST resource this object
represents. Servers may infer this from the endpoint the client submits
requests to. Cannot be updated. In CamelCase. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
[Info] 提示
此方式查询到的文档,其列举的字段不一定都是 Pod 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 之间可以通过 IP 访问,这个 IP 可以 Ping 通。
Pod 共享网络和存储
我们可以把一个 Pod 形容为一个虚拟主机。在 Pod 中,所有容器(进程)都在一个主机上,我们知道,同一个主机上的所有进程共享着主机网络,多个进程自然不能同时占用一个端口,否则会冲突。容器共享 Pod 中的网络,所以 Pod 中的容器也能够通过 localhost 相互访问,因此 Pod 中的 容器(进程)不能暴露/使用相同的端口。
通过 Pod 和 locahost
,解决了 高度耦合的容器间通信问题。
Docker 有一个 Contaner 模式,能够让多个容器共享一个 Network Namespace,可以让一个容器和另一个容器共享 IP、端口范围。
假如容器 A 已经创建起来,那么容器 A 会创建一个虚拟网卡;然后指定 容器 B 与容器 A 共享网络,那么两者就可以直接通过 localhost 进行通讯。
在 Kubernetes 中,当创建 Pod 时,会先启动一个 pause 容器,然后 Pod 中我们定义的容器会以 Container 模式共享 pause 中的网络。
docker ps | grep pause
当然,pause 不只是实现容器的网络互通,还有其它功能。
在第一章的 Docker 介绍中,谈到过此类容器,其目的是让其他容器联通起来, pause 在 Pod 的网络中相当于交换机。
Pod 中的容器是部分隔离的,每个容器都有自己的文件系统,各自的文件被隔离,容器不能访问或修改其它容器的文件。
为了让多个 容器之间能够共享文件,可以使用卷,把同一个卷映射到容器中。
划分 Pod 和容器
容器中应只包含一个进程,或进程和创建的子进程。如果在同一个容器中包含多个进程,那么需要同时管理进程的启动、日志等,一个进程崩溃时,容易影响到另一个进程。由于多个进程都会记录信息到标准输出中(如控制台输出),容器日志会合在一起,可能会导致出现问题难以排查。
一个容器只应该运行一个进程,但是他们放到一个 Pod 中就行了吗?例如程序和数据库,在设计时应该放到同一个 Pod,还是单独不同的 Pod?接下来我们简单讨论一下这个问题,限于经验和技术水平,笔者的论点可能不到位,读者可以多参考一下别的文章,了解如何设计 这些架构。
下面以 Web 程序和数据库举例。
耦合
使用 Pod/容器 的原因,是为了让不同服务能够降低耦合,能够隔离环境,如果程序跟数据库放在一起,是否能够有足够的隔离程度?如果 Web 跟 数据库放在同一个 Pod,此时 web 跟数据库的实例(容器)数量是 1:1。对于 Kubernests 来说,Pod 是最小单位,Kbernetes 不能横向扩容单个容器,因此扩容的最小单位是 Pod,多个容器必须捆绑在一起。同时 Pod 中的所有容器都使用同一机器的资源。在同一个 Pod 中的容器,在生命周期、计算机资源(内存、CPU)、实例数量、网络等都会耦合在一起。
请参考 https://kubernetes.io/zh/docs/concepts/workloads/pods/pod-lifecycle/
访问压力
一般来说,Web 是要被外界访问的,但是数据库为了安全,应当避免能够公网访问,只有处于集群中的程序或客户端才能访问数据库。同时Web的访问是直接面向用户的,访问量肯定比数据库的访问量大得多,而且数据库需要的存储空间比web大得多,那么两者使用的计算资源并不相近。
Pod 可以使用服务器资源,当服务器压力过大时,当太多用户访问 Web 时,Web就要考虑扩容实例,可以在其它节点上部署相同的 Pod(扩容),降低单节点访问压力。而一个数据库实例能够支持多个 Web 程序同时访问,那么数据库实例有必要跟 Web 放在同一个 Pod 中,保持 1:1的实例数量?
故障恢复
在 Kubernetes 中,容器应当是无状态的,也就是说容器或容器中的进程挂了,Kubernetes 可以快速在其它地方再创建一个 Pod ,启动容器,维持一定数量的 Pod 实例。对于 Web 来说,只要配置文件和数据库数据在,再启动一个 Web 容器,结果是一样的,流水的程序铁打的数据,只要数据在,可以随时启动 Web 程序,很容易恢复服务。但是数据库却不一定,数据库的运维比 Web 程序复杂得多,我们要考虑数据的安全性和可用性,当容器甚至节点服务器挂了后、磁盘损坏等,如何恢复数据库。数据库的维护不觉得。
两者的维护难度不在同一水平上,此时我们要考虑两者放在不同的 Pod 中。(实际上很少将数据库放在容器中,一般都是裸机部署)。
其中负载均衡是通过 Ingress 和 Service 实现的,后面的章节会学习到。
何时使用多个容器
前面提到 Web 跟数据库,应当划分在不同的 Pod 中,类似地,对于微服务中的不同服务或模块,也应当放在不同的 Pod 中。微服务架构、容器化,并不是那么容易,例如,对于前后端分离的项目,前后端文件放在同一个 容器中还是同一个 Pod 中还是不同 Pod 中?在设计中我们要考虑很多问题。
对于单体 Web 来说,一个程序中包含了所有服务,那么 Web 完全可以托管前端静态文件,前端文件跟后端程序打包在一起即可。例如 PHP、ASP.NET Core 等使用 wwwroot、www 等 目录存储静态文件。
如果是一个较大的网站,网站使用了多个微服务,则前端更可能放到一个 Pod 中,用户访问前端页面,然后前端根据访问的模块,自动访问不同的服务。
如果前端和后端文件需要频繁发布,两者的发布版本分开工作,则为了避免一方等待另一方发布,或者从 Devops 角度,前端和和后端文件可以放在不同容器中,然后通过存储卷,两个容器共享文件。
如果一个 Pod 中,包含一个主进程和多个辅助进程,则可以使用一个 Pod 部署多个 容器,多个容器之间紧密联系。
具体怎么设计,需要根据实际情况考虑。
Pod 生命周期
当 Pod 被分配到某个节点时, Pod 会一直在该节点运行,直到停止或被终止,Pod 在整个生命周期中只会被调度一次。
Pod 的整个生命周期可能有四种状态:
- Pending,尝试启动容器,如果容器正常启动,则进入下一个阶段;
- Running,处于运行状态;
- Succeeded、Failed,正常结束或故障等导致容器结束;
- Unknown,因为某些原因无法取得 Pod 的状态。
我们可以创建一个 Deployment,查看当前正在发生的事件:
kubectl create deployment nginx --image=nginx:latest --replicas=3
kubectl get events
通过 kubectl describe 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 时,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
单独创建 Pod ,一般用于临时调试的等。
创建 Pod
在 Kubernetes 中,所有对象都可以使用 YAML 表示。我们可以使用 YAML 来定义 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
由于 Pod 中的容器共享网络,技术不加上
ports
,照样能够访问。在 YAML 中多写一些,有助于其他人快速了解此 Pod 的定义信息。
将上面的 YAML 内容复制到 pod.yaml 中,然后执行命令应用 YAML :
kubectl apply -f pod.yaml
# 或
kubectl create -f pod.yaml
使用
kubectl run
命令也可以创建 pod,命令示例:kubectl run nginx-pod --image=nginx:latest
覆盖容器命令
在 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。
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
只能获取当前正在运行的 Pod 的日志,如果 Pod 被删除,所有日志记录都会被删除。
查看、维护 Pod 状态,比较常用的命令有:
- kubectl get - 列出对象资源,如
kubectl get pods
;- kubectl describe - 显示有关资源的详细信息,如
kubectl describe pod nginxtest
;- kubectl logs - 打印 pod 和其中容器的日志;
- kubectl exec - 在 pod 中的容器上执行命令,格式为
kubectl exec {pod名称} -c {容器名称} -- {要执行的命令}
,--
用于分隔命令,其后面的参数均表示要传递进容器的命令。