1 什么是pod?
1.1 官方说明
Pod是Kubernetes应用程序的最基本执行单元—是你创建或部署Kubernetes对象模型中的最小和最简单的单元。 Pod表示在集群上运行的进程。Pod封装了应用程序的容器(或者在某些情况下是多个容器)、存储资源、唯一的网络标识(IP地址)以及控制容器应该如何运行的选项。 Pod表示一个部署单元:Kubernetes中的应用程序的单个实例,该实例可能由单个容器或少量紧密耦合并共享资源的容器组成。Docker是Kubernetes Pod中最常见的容器,但Pods也支持其他容器。 Kubernetes集群中的Pod是如何管理容器的:
1)pod里运行单个容器: pod里只运行一个容器是最常见的Kubernetes使用案例。在这种情况下,你可以将Pod视为单个容器的封装,Kubernetes直接管理Pod,而不是直接管理容器。
2)pod里运行多个需要协同工作的容器:Pod可能封装了一个应用程序,该应用程序由紧密关联并且需要共享资源的多个共同协作的容器组成。这些共同协作的容器可能形成一个统一的服务单元-一个容器将文件从共享卷提供给所有容器使用,而一个单独的“ sidecar”容器则刷新或更新这些文件。Pod将这些容器和存储资源包装在一起,成为一个可管理的实体。
一个Pod就相当于一个共享context的配置组,在同一个context下,应用可能还会有独立的cgroup隔离机制,一个Pod是一个容器环境下的“逻辑主机”,它可能包含一个或者多个紧密相连的应用,这些应用可能是在同一个物理主机或虚拟机上。
Pod 的context可以理解成多个linux命名空间的联合:
- PID 命名空间(同一个Pod中应用可以看到其它进程);
- 网络 命名空间(同一个Pod的中的应用对相同的IP地址和端口有权限);
- IPC 命名空间(同一个Pod中的应用可以通过VPC或者POSIX进行通信);
- UTS 命名空间(同一个Pod中的应用共享一个主机名称);
Pod和相互独立的容器一样,Pod是一种相对短暂的存在,而不是持久存在的,正如我们在Pod的生命周期中提到的,Pod被安排到节点上,并且保持在这个节点上直到被终止(根据重启的设定)或者被删除,当一个节点死掉之后,节点上运行的所有Pod均会被删除。
1.2 Pod 的特征
- 包含多个共享 IPC、Network 和 UTC namespace 的容器,可直接通过 localhost 通信
- 所有 Pod 内容器都可以访问共享的 Volume,可以访问共享数据
- 无容错性:直接创建的 Pod 一旦被调度后就跟 Node 绑定,即使 Node 挂掉也不会被重新调度(而是被自动删除),因此推荐使用 Deployment、Daemonset 等控制器来容错
- 优雅终止:Pod 删除的时候先给其内的进程发送 SIGTERM,等待一段时间(grace period)后才强制停止依然还在运行的进程
- 特权容器(通过 SecurityContext 配置)具有改变系统配置的权限(在网络插件中大量应用)
Kubernetes v1.8 还支持容器间共享 PID namespace,需要 docker >= 1.13.1,并配置 kubelet --docker-disable-shared-pid=false。
1.3 为什么不直接在一个容器上运行所有的程序?
1) 透明:Pod中的容器对基础设施可见,使得基础设施可以给容器提供服务,例如线程管理和资源监控,这为用户提供很多便利;
2) 解耦:解除软件依赖关系,独立的容器可以独立的进行重建和重新发布,Kubernetes 甚至会在将来支持独立容器的实时更新;
3) 易用:用户不需要运行自己的线程管理器,也不需要关心程序的信号以及异常结束码等;
4) 高效:因为基础设施承载了更多的责任,所以容器可以更加高效;
1.4 pod是如何管理多个容器的?
Pod中可以同时运行多个容器。同一个Pod中的容器会自动的分配到同一个 node 上。同一个Pod中的容器共享资源、网络环境,它们总是被同时调度,在一个Pod中同时运行多个容器是一种比较高级的用法,只有当你的容器需要紧密配合协作的时候才考虑用这种模式。例如,你有一个容器作为web服务器运行,需要用到共享的volume,有另一个“sidecar”容器来从远端获取资源更新这些文件,如下图所示:
一些Pods有init容器和应用容器。 在应用程序容器启动之前,运行初始化容器。Pods为它组成的容器提供两种共享资源:网络和存储。
网络:
每个pod都被分配唯一的IP地址,POD中的每个容器共享网络名称空间,包括IP地址和网络端口。 Pod内部的容器可以使用localhost相互通信。 当POD中的容器与POD之外的实体通信时,它们必须使用共享网络资源(如端口)。
存储:
Pod可以指定一组共享存储卷。 POD中的所有容器都可以访问共享卷,允许这些容器共享数据。 卷也允许Pod中的持久数据在需要重新启动的情况下存活。 有关Kubernetes如何在POD中实现共享存储的更多信息,可参考Volumes | Kubernetes
1.5 Pod怎么工作
我们很少在Kubernetes中直接创建单个Pod。这是因为Pods被设计成相对短暂的、一次性的实体。 当一个POD被创建(直接创建,或间接由控制器创建)时,它被安排在集群中的节点上运行。 在进程终止、pod对象被删除、pod由于缺乏资源而被驱逐或节点失败之前,POD仍然位于该节点上。
注意:不要将重新启动Pod中的容器与重新启动Pod混淆。POD不是一个进程,而是一个运行容器的环境。Pod一直存在直到被删除为止。
pod本身无法自我修复。如果将Pod调度到发生故障的节点,或者调度操作本身失败,则将Pod删除;同样,由于缺乏资源或Node维护,Pod也被删除。Kubernetes使用称为控制器的更高级别的抽象来统一处理相对一次性的Pod实例的生命周期相关工作。因此,虽然可以直接使用Pod,但在Kubernetes中使用控制器来管理Pod更为常见。
1.6 pod和控制器关系
你可以使控制器创建和管理多个pod。控制器在pod失败的情况下可以处理副本、更新以及自动修复。例如,如果某个节点发生故障,则控制器会注意到该节点上的Pod已停止工作,并创建了一个替换Pod。调度程序将替换的Pod放置到健康的节点上。可以使用deployment、statefulset、daemonset等控制器管理pod。
1.7 pod运用场景
Pod 可以用于托管垂直集成的应用程序栈(例如,LAMP),但最主要的目的是支持位于同一位置的、共同管理的程序,例如:
- 内容管理系统、文件和数据加载器、本地缓存管理器等。
- 日志和检查点备份、压缩、旋转、快照等。
- 数据更改监视器、日志跟踪器、日志和监视适配器、事件发布器等。
- 代理、桥接器和适配器
- 控制器、管理器、配置器和更新器
通常,不会用单个 Pod 来运行同一应用程序的多个实例。
1.8 pod的持久性
一般来说,用户不需要直接创建 Pod。他们几乎都是使用控制器进行创建,即使对于单例的 Pod 创建也一样使用控制器,例如Deployments控制器提供集群范围的自修复以及副本数和滚动管理。 像StatefulSet这样的控制器还可以提供支持有状态的Pod。
1.9 Docker 镜像支持
目前,Kubernetes 仅支持使用 Docker 镜像来创建容器,但并非支持 Dockerfile 定义的所有行为。如下表所示:
Dockerfile 指令 | 描述 | 支持 | 说明 |
---|---|---|---|
ENTRYPOINT | 启动命令 | 是 | containerSpec.command |
CMD | 命令的参数列表 | 是 | containerSpec.args |
ENV | 环境变量 | 是 | containerSpec.env |
EXPOSE | 对外开放的端口 | 否 | 使用 containerSpec.ports.containerPort 替代 |
VOLUME | 数据卷 | 是 | 使用 volumes 和 volumeMounts |
USER | 进程运行用户以及用户组 | 是 | securityContext.runAsUser/supplementalGroups |
WORKDIR | 工作目录 | 是 | containerSpec.workingDir |
STOPSIGNAL | 停止容器时给进程发送的信号 | 是 | SIGKILL |
HEALTHCHECK | 健康检查 | 否 | 使用 livenessProbe 和 readinessProbe 替代 |
SHELL | 运行启动命令的 SHELL | 否 | 使用镜像默认 SHELL 启动命令 |
1.10 Pod容器的分类
1.10.1 基础容器( infrastructure. container)
维护整个Pod网络和存储空间。
启动一个容器时,k8s会自动启动一个基础容器
代码语言:javascript复制cat /opt/kubernetes/cfg/kubelet
......
--pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0"
每次创建Pod 时候就会创建,运行的每一个 容器都有一个pause-amd64 的基础容器自动会运行,对于用户是透明的:
代码语言:javascript复制docker ps -a
registry .cn-hangzhou .aliyuncs. com/ google-containers/pause- amd64:3.0 "/pause "
1.10.2 初始化容器( Init Container)
Pod 能够具有多个容器,应用运行在容器里面,但是它也可能有一个或多个先于应用容器启动的 Init 容器。Init 容器在所有容器运行之前执行(run-to-completion),常用来初始化配置。
如果为一个 Pod 指定了多个 Init 容器,那些容器会按顺序一次运行一个。 每个 Init 容器必须运行成功,下一个才能够运行。 当所有的 Init 容器运行完成时,Kubernetes 初始化 Pod 并像平常一样并行运行应用容器,所以Init容器能够提供了一种简单的阻塞或延迟应用容器的启动的方法。
1.10.2.1 Init Container的特点
Init容器与普通的容器非常像,除了以下两点:
- Init容器总是运行到成功完成为止
- 每个Init容器都必须在下一个Init容器启动之前成功完成启动和退出
Init 容器的资源计算,选择一下两者的较大值:
- 所有 Init 容器中的资源使用的最大值
- Pod 中所有容器资源使用的总和
Init 容器的重启策略:
- 如果 Init 容器执行失败,Pod 设置的 restartPolicy 为 Never,则 pod 将处于 fail 状态。否则 Pod 将一直重新执行每一个 Init 容器直到所有的 Init 容器都成功。
- 如果 Pod 异常退出,重新拉取 Pod 后,Init 容器也会被重新执行。所以在 Init 容器中执行的任务,需要保证是幂等的。
1.10.2.2 Init的容器作用
因为init容器具有与应用容器分离的单独镜像,其启动相关代码具有如下优势:
- Init容器可以包含一些安装过程中应用容器中不存在的实用工具或个性化代码。例如,没有必要仅为了在安装过程中使用类似sed、awk、 python 或dig这样的工具而去FROM.一个镜像来生成一个新的镜像。
- Init容器可以安全地运行这些工具,避免这些工具导致应用镜像的安全性降低。
- 应用镜像的创建者和部署者可以各自独立工作,而没有必要联合构建–个单独的应用镜像。
- 它们使用 Linux Namespace,所以对应用容器具有不同的文件系统视图。因此,Init容器可具有访问Secrets的权限,而应用容器不能够访问。
- 由于Init容器必须在应用容器启动之前运行完成,因此Init容器提供了一种机制来阻塞或延迟应用容器的启动,直到满足了一组先决条件。一旦前置条件满足,Pod内的所有的应用容器会并行启动。
1.10.2.3 Init Container示例
下面是一个 Init 容器的示例:
代码语言:javascript复制apiVersion: v1
kind: Pod
metadata:
name: init-demo
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: workdir
mountPath: /usr/share/nginx/html
# These containers are run during pod initialization
initContainers:
- name: install
image: busybox
command:
- wget
- "-O"
- "/work-dir/index.html"
- http://kubernetes.io
volumeMounts:
- name: workdir
mountPath: "/work-dir"
dnsPolicy: Default
volumes:
- name: workdir
emptyDir: {}
1.10.3 应用容器(Maincontainer)
并行启动
busybox轻量级linux内核
官网示例:
https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
代码语言:javascript复制cd /optdemo/
vim demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp-container
image: busybox:1.28
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
initContainers:
- name: init-myservice
image: busybox:1.28
command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;']
- name: init-mydb
image: busybox:1.28
command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']
这个例子是定义了一个具有 2 个 Init 容器的简单 Pod。 第一个等待 myservice 启动, 第二个等待 mydb 启动。 一旦这两个 Init容器都启动完成,Pod 将启动 spec 中的应用容器。
代码语言:javascript复制kubectl describe pod myapp-pod
kubectl logs myapp-pod -c init-myservice
代码语言:javascript复制vim myservice.yaml
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
kubectl create -f myservice.yaml
kubectl get svc
kubectl get pods -n kube-system
kubectl get pods
---------------------------------------------------------------------------------------
vim mydb.yaml
apiVersion: v1
kind: Service
metadata:
name: mydb
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9377
kubectl create -f mydb.yaml
kubectl get pods
特别说明:.
- 在Pod启动过程中,Init容器会按顺序在网络和数据卷初始化之后启动。每个容器必须在下一个容器启动之前成功退出。
- 如果由于运行时或失败退出,将导致容器启动失败,它会根据Pod的restartPolicy指定的策略进行重试。然而,如果Pod的restartPolicy设置为Always, Init容器失败时会使用RestartPolicy策略。
- 在所有的Init容器没有成功之前,Pod将不会变成Ready状态。Init容器的端口将不会在Service中进行聚集。正在初始化中的Pod处于Pending状态,但应该会将Initializing状态设置为true。
- 如果Pod重启,所有Init容器必须重新执行。
- 对Init容器spec的修改被限制在容器image字段,修改其他字段都不会生效。更改Init容器的image字段,等价于重启该Pod。
- Init容器具有应用容器的所有字段。除了readinessProbe, 因为Init容器无法定义不同于完成( completion) 的就绪( readiness) 之外的其他状态。这会在验证过程中强制执行。
- 在Pod中的每个app和Init容器的名称必须唯一; 与任何其它容器共享同-一个名称,会在验证时抛出错误。
2 Pod生命周期
2.1 Pod生命周期简述
我们一般将pod对象从创建至终的这段时间范围称为pod的生命周期,它主要包含下面的过程:
- pod创建过程
- 运行初始化容器(init container)过程
- 运行主容器(main container)
- 容器启动后钩子(post start)、容器终止前钩子(pre stop)
- 容器的存活性探测(liveness probe)、就绪性探测(readiness probe)
- pod终止过程
pod的终止过程:
- 用户向apiServer发送删除pod对象的命令
- apiServcer中的pod对象信息会随着时间的推移而更新,在宽限期内(默认30s),pod被视为dead
- 将pod标记为terminating状态
- kubelet在监控到pod对象转为terminating状态的同时启动pod关闭过程
- 端点控制器监控到pod对象的关闭行为时将其从所有匹配到此端点的service资源的端点列表中移除
- 如果当前pod对象定义了preStop钩子处理器,则在其标记为terminating后即会以同步的方式启动执行
- pod对象中的容器进程收到停止信号
- 宽限期结束后,若pod中还存在仍在运行的进程,那么pod对象会收到立即终止的信号
- kubelet请求apiServer将此pod资源的宽限期设置为0从而完成删除操作,此时pod对于用户已不可见
2.2 Pod的5种状态
Kubernetes 以 PodStatus.Phase 抽象 Pod 的状态(但并不直接反映所有容器的状态)。可能的 Phase 包括:
- 挂起(Pending): API Server已经创建该Pod,但一个或多个容器还没有被创建,包括通过网络下载镜像的过程。
- 运行中(Running): Pod中的所有容器都已经被创建且已经调度到 Node 上面,但至少有一个容器还在运行或者正在启动。
- 成功(Succeeded): Pod 调度到 Node 上面后均成功运行结束,并且不会重启。
- 失败(Failed): Pod中的所有容器都被终止了,但至少有一个容器退出失败(即退出码不为 0 或者被系统终止)。
- 未知(Unknown): 状态未知,因为一些原因Pod无法被正常获取,通常是由于 apiserver 无法与 kubelet 通信导致。
可以用 kubectl 命令查询 Pod Phase:
代码语言:javascript复制$ kubectl get pod reviews-v1-5bdc544bbd-5qgxj -o jsonpath="{.status.phase}" Running
2.3 restartPolicy
PodSpec 中的 restartPolicy 可以用来设置是否对退出的 Pod 重启,可选项包括 Always、OnFailure、以及 Never。比如:
- 单容器的 Pod,容器成功退出时,不同 restartPolicy时的动作为:
- Always: 重启 Container; Pod phase保持 Running.
- OnFailure: Pod phase变成 Succeeded.
- Never: Pod phase变成 Succeeded.
- 单容器的 Pod,容器失败退出时,不同restartPolicy时的动作为:
- Always: 重启 Container; Pod phase保持 Running.
- OnFailure: 重启 Container; Pod phase保持 Running.
- Never: Pod phase变成 Failed.
- 2个容器的 Pod,其中一个容器在运行而另一个失败退出时,不同restartPolicy时的动作为:
- Always: 重启 Container; Pod phase保持 Running.
- OnFailure: 重启 Container; Pod phase保持 Running.
- Never: 不重启 Container; Pod phase保持 Running.
- 2个容器的 Pod,其中一个容器停止而另一个失败退出时,不同restartPolicy时的动作为:
- Always: 重启 Container; Pod phase保持 Running.
- OnFailure: 重启 Container; Pod phase保持 Running.
- Never: Pod phase变成 Failed.
- 单容器的 Pod,容器内存不足(OOM),不同restartPolicy时的动作为:
- Always: 重启 Container; Pod phase保持 Running.
- OnFailure: 重启 Container; Pod phase保持 Running.
- Never: 记录失败事件; Pod phase变成 Failed.
- Pod 还在运行,但磁盘不可访问时
- 终止所有容器Pod phase变成 Failed
- 如果 Pod 是由某个控制器管理的,则重新创建一个 Pod 并调度到其他 Node 运行
- Pod 还在运行,但由于网络分区故障导致 Node 无法访问
- Node controller等待 Node 事件超时
- Node controller 将 Pod phase设置为 Failed.
- 如果 Pod 是由某个控制器管理的,则重新创建一个 Pod 并调度到其他 Node 运行
2.4 容器生命周期钩子
容器生命周期钩子:对于pod资源来说,容器生命周期钩子是在pods.spec.containers.lifecycle下定义的,监听容器生命周期的特定事件,并在事件发生时执行已注册的回调函数。初始化容器启动之后,开始启动主容器,在主容器启动之前有一个post start hook(容器启动后钩子)和pre stop hook(容器结束前钩子)。
postStart:该钩子在容器被创建后立刻触发,通知容器它已经被创建。如果该钩子对应的hook handler执行失败,则该容器会被杀死,并根据该容器的重启策略决定是否要重启该容器,这个钩子不需要传递任何参数。注意由于是异步执行,它无法保证一定在 ENTRYPOINT 之前运行。如果失败,容器会被杀死,并根据 RestartPolicy 决定是否重启。
preStop:该钩子在容器被删除前触发,其所对应的hook handler必须在删除该容器的请求发送给Docker daemon之前完成。在该钩子对应的hook handler完成后不论执行的结果如何,Docker daemon会发送一个SGTERN信号量给Docker daemon来删除该容器,这个钩子不需要传递任何参数。
而钩子的回调函数支持两种方式:
- exec:在容器内执行命令,如果命令的退出状态码是0表示执行成功,否则表示失败;
- httpGet:向指定 URL 发起 GET 请求,如果返回的 HTTP 状态码在[200, 400)之间表示请求成功,否则表示失败;
查看postStart怎么定义的,可以用如下命令:
代码语言:javascript复制kubectl explain pods.spec.containers.lifecycle.postStart
查看preStop怎么定义的,可以用如下命令:
代码语言:javascript复制kubectl explain pods.spec.containers.lifecycle.preStop
postStart 和 preStop 钩子示例:
代码语言:javascript复制apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: lifecycle-demo-container
image: nginx
lifecycle:
postStart:
httpGet:
path: /
port: 80
preStop:
exec:
command: ["/usr/sbin/nginx","-s","quit"]
参考链接
pod详解 - 快乐嘉年华 - 博客园
Kubernetes中pod详解_人间不值得-的博客-CSDN博客
Pod详解_我的紫霞辣辣的博客-CSDN博客_pod方法
k8s之pod与Pod控制器 - woaiyitiaochai - 博客园
kubernetes 实践四:Pod详解 - xingyys - 博客园