参考书籍:[基于 Kubernetes 的容器云平台实战]
Pod
Pod 是 Kubernetes 的最基本操作单元,也是应用运行的载体,包含一个到多个密切相关的容器。整个 kubernetes 系统都是围绕着 Pod 展开的,比如如何运行 Pod,如何保证 Pod 的数量,如何访问 Pod 等。
Pod 定义文件详解
以下是一个 Pod 的定义模板文件:
1 | apiVersion: v1 // 必需 |
Pod 定义文件中描述了 Pod 的属性和行为,其中主要要素如下所示:
- apiVersion: Kubernetes 的 API 版本声明,目前是 v1;
- kind: API 对象的类型声明,当前类型是 Pod;
- metadata: 设置 Pod 的元数据信息;
- metadata.name: 指定 Pod 的名称,Pod 的名称在 namespace 中必需唯一,而却需要符合 RFC 1035 规范;
- metadata.namespace: 命名空间,不指定时将使用名为 “default” 的命名空间;
- metadata.labels: 自定义标签属性列表,可根据需要对所创建的 Pod 自定义标签,再利用 Service 或者 ReplicationController 的 Label Select 来选择自定义的 Pod;
- metadata.annotations: 注释信息列表,可以写多个,和 labels 的区别是没有格式要求;
- spec: 配置 Pod 的详细信息
- spec.RestartPolicy: 设置 Pod 的重启策略。该 Pod 内容器的重启策略可选值为 Always, OnFailure 以及 Never,默认值为 Always。
- Always: 容器一旦终止运行,无论容器时如何终止的,kubelet 都将重启它;
- OnFailure: 只有容器以非零退出码终止时,kubelet 才会重启该容器;
- Never: 容器终止后,kubelet 将退出码报告给 Master,不再重启它。
- spec.containers: Pod 中运行的容器列表。数组形式,每一项定义一个容器;
- spec.containers.name: 指定容器的名称,在 Pod 的定义中必需唯一,须符合 RFC 1035 规范;
- spec.containers.image: 设置容器的镜像名称,在 Node 上如果不存在该镜像,则 kubelet 会先下载;
- spec.containers.command: 设置容器的启动命令,相当于 Dockerfile 中的 ENTRYPOINT
- spec.containers.args: 相当于 Dockerfile 中的 CMD
Pod 基本操作
正如上一章所述,kubernetes 集群通过 API 服务器来接收对各种对象的增,删,改,查操作,而 kubernetes 提供了命令行运行工具 kubectl,用它来将 API 服务器的 API 包装成简单的命令集以供用户使用。kubectl 的实现原理很简单,就是将用户的输入转换成对 API 服务器的 Rest API 调用,然后发起远程调用并输出调用结果。因此,可以认为 kubectl 是 API 服务器的客户端开发工具。kubectl 命令格式如下:
1 | kubectl [command] [options] |
对于 Pod 对象的增,删,查操作的命令行如下(新版本的 kubernetes 不支持使用 replace 修改 Pod 资源):
1 | # 创建 |
创建 Pod
Kubernetes 中大部分 API 对象都是通过 Kubernets create
命令创建的。
使用
kubectl create
命令创建 Pod 之前,需要先定义一个 yaml 文件,如:testpod.yaml
1
2
3
4
5
6
7
8
9
10
11apiVersion: v1
kind: Pod
metadata:
name: testpod
spec:
containers:
- name: test
image: nginx
ports:
- name: web
containerPort: 80通过定义的文件创建 Pod,命令如下:
1
2# kubectl create -f testpod.yaml
pod/testpod created
查询 Pod
最常用的查询命令是 kubectl get,可以查询到多个 Pod 信息,查询指定的 Pod 命令如下:
1 | # kubectl get pod testpod |
查询显示的字段含义如下:
- NAME: 指 Pod 名称
- READY: 显示 Pod 中的容器西悉尼,”/“ 右边的数字表示 Pod 包含的容器总数目, “/“ 左边的数字显示准备就绪的容器数目;
- STATUS: 显示 Pod 的当前状态;
- RESTARTS: Pod 的重启次数
- AGE: Pod 的运行时间
默认情况下,kubectl get
仅仅显示 Pod 的简要信息。如果想要获取 Pod 的完整信息,可以使用如下命令
1 | # 以下命令将使用 yaml 格式来显示 Pod 的详细信息 |
删除 Pod
要删除一个 Pod 可以使用以下命令
1 | # kubectl delete pod testpod |
Pod 与 容器
在 Docker 中,容器是最小的处理单元,增,删,改,查的对象都是容器,容器间隔离是基于 Linux namespace 实现的。而在 Kubernetes 中,Pod 包含一个或者多个相关的容器,是容器的一种延伸扩展,一个 Pod 也是一个隔离体,而 Pod 内部包含的一组 Docker 容器又是共享的(包括 PID,Network,IPC,UTS等)
- 网络命名空间(NET namespace): 由于容器之间共享网络命名空间,并且使用同一个 IP 地址,通过 localhost 互相通信。不同 Pod 之间可以通过 IP 地址访问;
- 同一个 Pod 内的应用容器能够看到对方容器进程(PID),同一个 Pod 内的应用容器能使用 System VIPC 或 POSIX 消息队列进行通信(IPC),同一个 Pod 内的应用容器共享主机名(UTS)等。
- 存储卷(Volume),Pod 内的所有容器之间共享存储卷,即允许这些容器共享数据。Volume 还用于 Pod 中的数据持久化,以防止容器重启而导致数据丢失。
虽然 Pod 可以在其中运行一个或多个容器,但 Pod 本身可能是 Node 节点上运行的许多 Pod 之一。综上所述,Pod 为我们提供了一组容器,我们可以复制,调度和负载均衡到各个服务端点。
镜像
在 Kubernetes 中,镜像的下载策略为:
- Always: 每次都下载最新的镜像
- Never: 只使用本地镜像,从不下载
- IfNotPresent: 只有当本地没有的时候才下载镜像
Pod 被分配到 Node 之后会根据镜像下载策略进行镜像下载,因此,用户可以根据自身集群的特点来决定采用何种下载策略。无论何种策略,都要确保 Node 上有正确的镜像可用。
其他设置
通过 yaml 文件,可以在 Pod 中设置启动命令,环境变量,端口映射,数据持久化及重启策略。
启动命令 spec.containers.command
启动命令用来说明容器是如何运行的,在 Pod 的定义中可以设置容器的启动命令和参数,代码如下:
1 | apiVersion: v1 |
在使用 docker run
命令运行容器时,如果未指定容器的启动命令,则使用 Docker 镜像内默认的启动命令启动(一般是通过 Dockerfile 中的 ENTRYPOINT
和 CMD
进行设置的)。另外,CMD
命令是可以覆盖的,docker run
指定的启动命令会把镜像内 CMD
设置的命令覆盖。而 ENTRYPOINT
设置的命令只是一个入口,docker run
指定的启动命令作为参数传递给 ENTRYPOINT
设置的命令,而不是进行替换。
在 Pod 的定义中,command 和 args 都是可选项,将与 Dockers 镜像的 ENTRYPOINT 和 CMD 相互作用,生成容器的最终启动命令。具体规则如下:
- 如果容器没有指定 command 和 args,则使用镜像的 ENTRYPOINT 和 CMD 作为启动命令运行;
- 如果容器指定 command,而未指定 args,则忽略镜像中的 ENTRYPOINT 和 CMD,使用指定的 command 作为启动命令运行;
- 如果容器没有指定 command,只是指定 args,则使用镜像的 ENTRYPOINT 和 CMD 作为启动命令运行;
- 如果容器指定了 command 和 args,则使用指定的 command 和 args 作为启动命令运行。
环境变量 spec.containers.env.name/value
一般情况下,可以在 Pod 中定义 env 的 name/value
来设置容器运行时的环境变量。而在一些特殊场景下,Pod 中的容器想知道自身的一些信息,如 Pod 名称,Pod 本身的 IP 地址等,这些信息可以通过 Downward API 获得,也可以通过环境变量得知容器目前所支持的信息。比如 Pod 的名称可以通过 metadata.name
获得,Pod 的 IP 地址可以通过 status.podIP
获得,详见下面示例:
1 | apiVersion: v1 |
端口映射 spec.containers.ports.containerPort/protocol/hostIP/hostPort
在使用 docker run
运行容器时,往往通过 --publish/-p
参数设置端口映射规则,也可以在 Pod 定义文件中设置容器的端口映射规则。比如,下面示例中 Pod 设置容器 nginx 的端口映射规则为 0.0.0.0:80 --> 80/TCP
:
1 | apiVersion: v1 |
使用 hostPort 时需要注意端口冲突问题,不过 Kubernetes 在调度 Pod 的时候会检查宿主机端口是否冲突,比如当两个 Pod 均要求绑定宿主机的 80 端口,Kubernetes 会将这两个 Pod 分别调度到不同的机器上。
在 Host 网络中的一些特殊场景下,容器必需要以 host 方式进行网络设置(如接收物理机网络才能接收到的组播流),在 Pod 中也支持 host 网络的设置,如:spec.hostNetwork: true
.
数据持久化 spec.containers.volumeMounts.mountPath
要注意的一点是,容器是临时存在的,如果容器被销毁,容器中的数据将会丢失。为了能够持久化数据以及共享容器间的数据,Docker 提出了数据卷(Volume)的概念。简单地说,数据卷就是目录或者文件,它可以绕过默认的联合文件系统,而以正常的文件或者目录的形式存在于宿主机上。
在使用 docker run
运行容器的时侯,经常使用参数 --volume/-v
创建数据卷,即将宿主机上的目录或者文件挂载到容器中,这样,即使容器被销毁,数据卷中的数据仍然保存在宿主机上。
kubernetes 对 Docker 数据卷进行了扩展,支持对接第三方存储系统。另一方面,由于 kubernetes 的数据卷时 Pod 级别的,所以 Pod 中的容器可以访问共同的数据卷,实现容器间的数据共享。
下面通过实例来介绍 Pod 中数据卷的创建,代码如下
1 | apiVersion: v1 |
由以上例子可以看出,在 Pod 的定义文件中,spec.volumes 配置了一个名称为 data 的数据卷,数据卷的类型时 hostPath,使用宿主机的目录 /tmp。Pod 中的两个容器都通过 .spec.containers.volumeMounts 来设置挂在数据卷到容器中的 /data 路径。
重启策略
重启策略即当 Pod 中的容器终止退出后,重启容器的策略。这里所谓 Pod 的重启,实际上是容器的重建,因为 Pod 容器退出后,之前容器中的数据将会丢失,如果需要持久化数据,那么需要使用数据卷进行持久化设置。Pod 支持三种重启策略: Always(默认策略,当容器终止退出后总是重启容器),OnFailure(当容器终止且异常退出时重启),Never(从不重启)。
Pod 调度
Pod 调度是指将创建好的 Pod 分配到某一个 Node 上。首先,筛选出符合条件的 Node,然后选择最优 Node。对于所有 Node,首先 kubernetes 通过一系列过滤函数,去除不符合条件的 Node。
kubernetes 过滤函数如下所示
- Nodiskconflict:检查 Pod 请求的数据卷是否与 Node 上已存在 Pod 挂载的数据卷存在冲突,如果存在冲突则过滤掉该 Node;
- PodFitsResources:检查 Node 的可用资源(CPU 和内存)是否满足 Pod 的资源请求;
- PodFitsPorts:检查 Pod 设置的 Hostports 在 Node 上是否已被其他 Pod 占用;
- PodFitsHost:如果 Pod 设置了 NodeName 属性,则筛选出指定的 Node;
- PodSelectorMatches:如果 Pod 设置了 Nodeselector 属性,则筛选出符合要求的 Node;
- CheckNodeLabelPresence:检查 Node 是否存在 kubernetes Scheduler 配置的标签。
筛选出符合条件的 Node 来运行 Pod,如果存在多个符合条件的 Node,那么需要选择出最优的 Node,Kubernetes 中通过一系列优先级函数(Priority Function)来评估出最优 Node。这样一来,通过最终分数对 Node 进行排序,得分最高的 Node 即最优 Node。如果存在多个 Node 并列第一,则随机选择一个 Node。
kubernetes 优先级函数如下
- LeastRequestedPriority:优先选择有最多可用资源的 Node;
- CalculateNodeLabelPriority:优先选择含有指定 Label 的 Node;
- BalancedResourceAllocation: 优先选择资源使用均衡的 Node
在一些场景下,希望 Pod 调度到指定的 Node 上,比如,调度到专门用于测试的 Node,如何实现呢?可以为 Node 定义特殊标签,并在在定义 Pod 时指明选择含有该标签的 Node 来创建。如:
Node 定义标签
1
kubectl label nodes node1 env=test
定义 Pod 的时候,通过设置 Node Selector 来选择 Node
1
2
3
4
5
6
7
8
9
10
11
12
13apiVersion: v1
kind: Pod
metadata:
name: testpod
labels:
- env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
nodeSelector:
Nodename: node-01
Pod 创建成功后将被分配到带有 test 标签的 node-01 节点上。
除了设置 Node Selector 之外,Pod 还可以通过 Node Name 直接指定 Node,但还是建议使用 Node Selector。因为通过 Label 进行选择时一种弱绑定,而直接指定 Node Name 是强绑定, Node 失效时会导致 Pod 无法调度。
Pod 生命周期
容器状态
Pod 状态就是一组容器状态的体现和概括,容器的状态变化会影响 Pod 的状态变化,并触发 Pod 的生命周期状态发生变化。
使用 docker run 运行容器时,首先会下载镜像,成功后开始运行容器;当容器运行结束并退出后,容器终止,这是一个完整地生命周期过程。kubernetes 对 Pod 的完整生命周期状态进行了记录,每个状态所含的信息如下:
- Waiting: 容器正在等待创建,如正下载镜像;
- reason: 等待的原因
- Running: 容器已经创建,正在运行;
- startAt: 容器创建时间
- Terminated: 容器终止退出;
- exitCode: 退出码
- signal: 容器退出信号
- reason: 容器退出原因
- finishedAt: 容器退出时间
- containerID: 容器的 ID
Pod 运行后,可以通过 Pod 查询接口来查询容器的状态,命令如下:
1 | kubectl describe pod my_pod |
Pod 的生命周期
Pod 被分配到一个 Node 上之后,直到被删除前都不会离开这个 Node。一旦某个 Pod 失败,Kubernetes 会将其清理,然后 Replication Controller 将会在其他机器上(或本机)重建 Pod。重建后,Pod 的 ID将会发生变化,这边是一个新的 Pod。因此,kubernetes 中 Pod 的迁移实际是在新的 Node 上重建 Pod。
Pod 的生命周期可以简单描述为: 首先 Pod 被创建,紧接着 Pod 被调度到 Node 上进行部署运行。Pod 一旦被分配到 Node 之后就不会离开这个 Node,直到它被删除,即生命周期完结。
Pod 的生命周期被定义为以下几个相位:
- Pending: Pod 已经被创建,但是一个或多个容器还未创建,这包括 Pod 调度阶段,以及容器镜像的下载过程;
- Running: Pod 已经被调度到 Node,所有容器已经创建,并且至少一个容器在运行或者正在重启;
- Succeeded: Pod 中所有容器正常退出;
- Failed: Pod 中所有容器退出,至少有一个容器是一次退出的;
可以通过以下命令查看 Pod 处于生命周期的哪个阶段:
1 | # kubectl get pod nginx --template="{{.status.phase}}" |
Pod 被创建成功后,首先会进入 Pending 阶段,然后被调度到某个 Node 节点后运行,进入 Running 阶段。如果 Pod 中的某容器停止(正常退出或异常退出),那么 Pod 会根据重启策略的不同进入不同的阶段,举例如下:
Pod 是 Running 阶段,含有一个容器,容器正常退出:
- 如果重启策略是 Always,那么会重启容器,Pod 保持 Running 阶段;
- 如果重启策略是 OnFailure,Pod 进入 Succeeded 阶段;
- 如果重启策略是 Never,Pod 进入 Succeeded 阶段。
Pod 是 Running 阶段,含有一个容器,容器异常退出:
- 如果重启策略是 Always,那么会重启容器,Pod 保持 Running 阶段;
- 如果重启策略是 OnFailure,Pod 保持 Running 阶段;
- 如果重启策略是 Never,Pod 进入 Failed 阶段。
Pod 是 Running 阶段,含有两个容器,其中一个容器异常退出:
- 如果重启策略是 Always,那么会重启容器,Pod 保持 Running 阶段;
- 如果重启策略是 OnFailure,Pod 保持 Running 阶段;
- 如果重启策略是 Never,Pod 保持 Running 阶段。
Pod 是 Running 阶段,含有两个容器,两个容器都异常退出:
- 如果重启策略是 Always,那么会重启容器,Pod 保持 Running 阶段;
- 如果重启策略是 OnFailure,Pod 保持 Running 阶段;
- 如果重启策略是 Never,Pod 就会进入 Failed 阶段。
Pod 一旦被分配到 Node 节点,就不会离开这个 Node 节点,直到被删除。删除可能是人为删除,或者被 Replication Controller 删除,也有可能是 Pod 进入 Succeeded 或者 Failed 阶段过期,被 Kubernetes 清理掉。总之,Pod 被删除后,Pod 的生命周期就结束了,及时被 Replication Controller 进行重建,那也是新的 Pod,因为 Pod 的 ID 已经发生了变化,所以实际上关于 Pod 迁移,准确的说法是在新的 Node 上重建 Pod。
Kubernetes 提供了生命周期回调函数,在容器的生命周期的特定阶段执行调用就可以进行相关操作,比如,容器在停止前希望执行某项操作,就可以注册相应的钩子函数。目前,Kubernetes 提供的生命周期回调函数主要有:
- Poststart,在容器创建成功后调用该回调函数;
- PreStop,在容器被终止前调用该回调函数。
Label
Kubernetes 使用称为 “Label” 的键值对来标识附加到系统中的任何 API 对象(如 Pod,Service,Replication Controller等)。实际上,Kubernetes 中的任意 API 对象都可以通过 Label
进行标识。每个 API 对象可以有多个 Label,但每个 Label 的 key 只能有唯一一个值。相应的,LableSelector
则是针对匹配对象的标签来进行的查询。Label 和 LabelSelector 是 Kubernetes 中的主要分组机制用于确定操作适用的组件。例如,如果应用程序的 Pod 具有系统的标签 tier(”frontend”, “backend” 等)和一个 release(“canary”, “production” 等),那么对所有 “backend” 和 “production” 节点的操作可以使用如下所示的 LabelSelector:tier=backend AND release=production。