2. Pod

2.1 Pod中的基本概念

 Pod 是K8S中的重要组成部分,也是K8S对象模型中最小、最简单的可部署对象,Pod代表集群中运行的进程,一个Pod中包含了一个或多个容器、存储资源、唯一的网络IP以及容器运行运行方式的选项(Pod中的内容总是共同定位和安排的)。一个 Pod 是一个部署单元:K8S中一个一应用程序的单个实例(这可能由单个容器或少量紧密耦合并共享资源的容器组成)。Docker 是在Pod中最常见的运行时容器(但K8S不只支持docker),集群中的Pod可以有2中使用方式:

  1. 运行单个容器的Pod:“每个Pod运行一个容器”是K8S中最常见的使用模型,此时Pod就像是该容器的一个外层包裹,但K8S管理的还是Pod而不是直接去管理容器;
  2. 运行多个容器的Pod:Pod可以封装由多个紧密耦合且需要共享资源的位于同一位置的容器组成的应用程序,这些位于同一位置的容器可能形成一个统一的服务单元——一个容器将文件从共享卷提供给公众,而一个单独的“sidecar”容器刷新或更新这些文件,Pod将这些容器和存储资源作为一个单一的可管理实体包裹在一起;

 每个Pod 意味着运行所给应用的单个实例,如果需要对应用进行水平扩容(如运行多个实例),应该使用多个Pod,秉持“一个Pod一个实例”的思想,这种情况其实就是复制,复制的pods通常由一个称为控制器(Controller)的抽象东西来创建和管理,并作为一个组进行管理。Pod支持耦合性较强的多个进程(表现为一个Pod中多个容器)协作,同一个物理机/虚拟机上的Pod中的容器是自动定位和编排的,容器之间可以共享资源和依赖、通信、协作。一个Pod中对多个容器进行共同定位和管理是一个相对比较高端的用例,使用场景是容器之间高耦合,比如有一个容器充当共享卷中文件的Web服务器,还有一个单独的“sidecar”容器从远程源更新这些文件。

Pod图,Pod 为它内部的容器提供了2类共享资源——网络和存储(networking and storage):

  • 网络(networking):每个Pod都被分配了一个唯一IP地址,Pod中的每个容器共享网络命名空间(network namespace),包括IP地址和网络端口。Pod内部的容器可以使用localhost相互通信(也可以使用标准进程通信,像SystemV semaphores和POSIX共享内存),但如果Pod内的容器想和Pod外部的实体对象通信,那它们必须协调好如何使用共享的网络资源(比如端口),不同Pod中的容器拥有不同IP地址,没有特别配置的话是不能通过IPC进行通信的,这种场景下的容器通常是通过Pod的IP地址进行通信。;
  • 存储(storage):Pod可以指定一组共享存储卷(storage volumes),被定义为Pod中的一部分且被安装在每个应用的文件系统中。Pod中的所有容器都可以使用这些存储卷、分享数据,如果其中一些容器需要被重启,那存储卷允许持久化Pod中的数据是依然存活。

 在随后的过程中,Pod 共享的上下文(context)是一系列的Linux的namespace、cgroups以及其他隔离的东西(就是隔离Docker容器的东西),在Pod的上下文中,独立的应用可能进一步运用了子隔离。

2.2 Pod中的管理

  • 声明周期短;
  • Controller 创建;

 Pod是多进程协作的模式,这些进程形成一个相对黏合的服务单元。和Pod的工作,一般情况很少会在K8S中直接创建独立的Pod(即使是"1个Pod1个容器"的模式),因为Pod的生命周期是相对较短的、即用即废的实体。当Pod被创建后(无论是你自己直接创建的、还是通过Controller间接创建),会被分配到一个唯一的ID(UID),然后将会被安排运行在集群中的某个节点上,Pod 将保持存活在对应的节点上,直到进程被终止、Pod被删除、由于资源缺乏被驱逐或者节点失败(比如某个节点挂了,那安排到该节点上的Pod将会在超时后被删除)。一个已经给出的Pod(由UID标识)不能被重新安排到一个新的节点上,但它可以被另一个一模一样的Pod替换(名字可以一样,但UID不一样)。重启Pod中的容器时应该不需要担心Pod的重启,Pod本身并不会运行,它只是容器运行的一个环境。Pod本身是不能自愈的,如果Pod被安排的那个节点失败了,或者安排(调度)本身的行为失败了,那Pod将会被删除,同样的,由于缺乏资源或节点维护,POD无法在驱逐中存活。K8S使用了更高维度的抽象(即Controller),简化了应用的部署和管理,解决了管理相对可释放的Pod实例,因此,即使可以直接去使用Pod,但使用Controller去管理Pod是更加常见的作法。

 Controller 可以创建和管理多个Pod,在集群中解决副本推出和提供自愈能力,比如:如果节点失败了,Controller可能会在不同的节点上自动安排一个完全相同的替代品,包含一个或多个Pod的Controller的示例有:Deployment、StatefulSet、DaemonSet,总而言之,Controller 使用你提供的Pod模板去创建它负责的Pod。

 Pod可以用来承载垂直集成的应用堆,但是它们的主要动机是支持共同定位、共同管理的协助程序,比如:

  • 内容管理系统、文件和数据加载程序、本地缓存管理器等;
  • 日志和检查点备份、压缩、旋转、快照等;
  • 代理、网桥和适配器;
  • 控制器、管理器、配置器和更新程序;

通常来说,单独的Pod并不会用来运行同一个应用的多个实例。

Pod模板(Pod Templates)是包含在其他对象中的Pod规格,比如 Replication Controllers、Jobs、DaemonSets,Controller 使用Pod Template 去创建实际的 Pod,下面是一个简单 Pod 清单(manifest):

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
spec:
  containers:
  - name: myapp-container
    image: busybox
    command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600']

Pod Template 就像切割工具,并不是指定所有副本特定的期望状态,一旦被切割了,那饼干就和切割工具没啥关系了,即使之后再去更改 template 甚至转用另一个新的模板,这些都和已经创建的Pod没有任何关系。相似的,通过 replication controller 创建的 Pod 可能随后会直接被更新,这和Pod形成对比,Pod将会指定当前Pod中所有容器的当前期望状态,这种方式从根本上简化了系统语义、增加了原始的灵活度。

 Pod代表着集群中运行的进程,所以允许这些进程被优雅的停止也很重要,用户可以请求去删除并且知晓什么时候这些进程停止、甚至可以确保删除行为的最终完成。当请求删除某个Pod时,系统会记录允许被强制杀掉Pod所需要的预估时间,然后这个信号将会被发送至每个容器的主进程,一旦超过之前的预估时间,那Kill信号将被发送到这些进程中,然后Pod将会从API Server上被删除,如果在等待进程停止的过程中Kubelet或容器管理者(应该就是抽象的Controller)被重启,那终止的行为将会在预估时间内进行重试。下面是一个示例:

  1. 用户发送命令去删除Pod,默认的预估时间花费为30秒;
  2. API Server中的Pod会随着时间的推移而更新,同时会有宽限期,超过这个时间,pod会被认为是“死的”;
  3. Pod 在客户端命令中列出时显示为“正在终止”(Terminating);
  4. 和第3步同时进行,当 Kubelet 看到一个Pod被标记为“Terminating”,因为时间在2中已经被设置了,它开始停止Pod的进程:
  5. 如果Pod中某个容器已经被定义为 preStop hook,它将会被容器内被调用,如果 preStop hook 在超时后(grace period)仍然在运行,然后用一个小的(2秒)延长的宽限期调用第2步;
  6. 容器被发送 TERM 信号,注意并不是Pod中所有容器都会同时收到 TERM 信号,如果关闭的顺序很重要,每个都可能需要一个 preStop 钩子;
  7. 和第3步同时进行,从服务的端节点列表中删除的Pod将不会被视作复制控制器(replication Controller)的运行Pod集的一部分,缓慢删除Pod将无法继续提供流量,因为负载均衡器(比如Service proxy)将会把Pod从旋转中移除;
  8. 宽限期超时(grace period expires),任何在Pod中仍然运行的进程将使用 SIGKILL 杀掉;
  9. Kubelet将会把宽限期设置为0(立即删除),在API Server完成Pod的删除,Pod 从 API 消失客户端将不会再看到;

默认情况下,所有的删除行为的宽限期为30秒, Kubectl delete 命令用户使用--grace-period=<seconds>参数自定义宽限时间(0表示强制删除Pod),如果使用0值,必须在设置--grace-period=0时一起添加一个额外的标识--force以便强制删除。强制删除Pod被定义为立即从集群状态中和etcd(k8s依赖的数据库)中删除该pod,当一个强制删除行为执行时,apiserver并不会等待kubelet的确认对应节点上的该Pod是否正在运行或停止,他将会在API中立刻移除Pod以便创建一个新的同名的Pod,在节点上,设置为立即终止的Pod在被强制杀死之前仍将得到一个小的宽限期,强制删除有一些潜在危险(谨慎操作)。

Pod容器的特权模式

 在容器的spec中SecurityContext使用privileged标志可以开启特权模式,这个功能可以让容器使用Linux的能力(比如操作网络堆栈、访问设备),容器内的进程获得的权限与容器外的进程几乎相同,这种模式下,将网络和卷插件作为独立的pods编写应该更容易,而不需要编译到kubelet中。

2.3 Pod初始容器(Init Containers)

 一个Pod可以有多个运行着的APP的容器,但也可以有1个或多个初始化容器,初始化容器必须在App容器之前启动,初始化容器和普通容器完全相同(普通App容器支持的字段和特性它都支持,但不包括就绪探测),特殊的地方主要有2点:

  1. 它们总是跑完全程;
  2. 每个在下一个开始之前必须完全跑完(意味着如果一个Pod有多个初始化容器,那这些容器一定是配置的顺序一次运行一个全部运行完毕);

 如果一个初始化容器失败了,k8s将反复重启直到初始化容器成功,但如果Pod有一个Never的restartPolicy,那就不会重启。如果需要指定一个初始化容器,需要在PodSpec中containers字段中添加一个initContainers对象(一个JSON数组对象,内部元素时 Container 类型),初始化容器的状态返回到.status.initContainerStatuses字段作为容器状态的数组。

初始化容器的应用场景包括:

  • 出于安全考虑,一些不靠包含在App容器镜像内的公共应用程序,可以放在 init container 中运行;
  • 一些应用程序或者自定义的设置代码(不包含在App镜像中),比如:没必要在设置的过程中包含诸如从另一个镜像使用sedawkpythondig等工具制造出一个镜像这样的步骤;
  • 应用程序映像的构建器和部署器角色可以独立工作,无需共同构建单个应用程序映像;
  • 它们使用Linux的namespaces,这样可以让它们和App容器有不同的文件系统,因此它们可以访问App容器无法访问的机密信息;
  • init container 在App容器之前启动,而App容器是并行运行的,因此初始化容器提供了一种简便的方式去阻断或延缓App容器的启动,直到一些前置的条件工作做完;

示例,下面是一个具备2个初始化容器(第一个myservice,第二个mydb)的Pod(新版语法可能略有不同,以新语法为准):

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;']

myservice和mydb服务的yaml文件:

kind: Service
apiVersion: v1
metadata:
  name: myservice
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9376
---
kind: Service
apiVersion: v1
metadata:
  name: mydb
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9377

Pod可以使用下面的命令进行启动和debug:

kubectl apply -f myapp.yaml

pod/myapp-pod created

kubectl get -f myapp.yaml

NAME        READY     STATUS     RESTARTS   AGE
myapp-pod   0/1       Init:0/2   0          6m

kubectl describe -f myapp.yaml

Name:          myapp-pod
Namespace:     default
[...]
Labels:        app=myapp
Status:        Pending
[...]
Init Containers:
  init-myservice:
[...]
    State:         Running
[...]
  init-mydb:
[...]
    State:         Waiting
      Reason:      PodInitializing
    Ready:         False
[...]
Containers:
  myapp-container:
[...]
    State:         Waiting
      Reason:      PodInitializing
    Ready:         False
[...]
Events:
  FirstSeen    LastSeen    Count    From                      SubObjectPath                           Type          Reason        Message
  ---------    --------    -----    ----                      -------------                           --------      ------        -------
  16s          16s         1        {default-scheduler }                                              Normal        Scheduled     Successfully assigned myapp-pod to 172.17.4.201
  16s          16s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Pulling       pulling image "busybox"
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Pulled        Successfully pulled image "busybox"
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Created       Created container with docker id 5ced34a04634; Security:[seccomp=unconfined]
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Started       Started container with docker id 5ced34a04634

检查初始化服务:

# 检查第一个初始化容器
kubectl logs myapp-pod -c init-myservice

# 检查第二个初始化容器
kubectl logs myapp-pod -c init-mydb

当启动上述的2个服务后,就可以看到初始化容器完成了并且myapp-pod被创建:

kubectl apply -f services.yaml

service/myservice created
service/mydb created

kubectl get -f myapp.yaml

kubectl get -f myapp.yaml
NAME        READY     STATUS    RESTARTS   AGE
myapp-pod   1/1       Running   0          9m

2.4 Pod的生命周期

 虽然Pod的生命周期很短,但也是一个阶段性的过程(Pod中是一个PodStatus对象),主要阶段(或者状态)有:

说明
Pending当前Pod已经被k8s所接受,但至少有一个容器镜像还未被创建(容器还未完全创建)
RunningPod已经被绑定到具体节点上且所有容器都已经被创建,此外,且至少有一个容器处于运行或启动或重启状态
SucceededPod中所有容器都已经成功停止且不会被重启
FailedPod中的容器被停止,但至少有一个容器未被成功停止,也就是说,容器要么以非零状态退出,要么被系统终止
Unknown即未知状态,典型原因是由于和主机的通信发生错误
Completedpod已经运行完成,因为没有什么可以让它继续运行,例如完成作业
CrashLoopBackOffPod中某个容器异常退出

2.5 Pod Preset

 Pod预置对象(Pod Preset)是为了在创建时向Pod中注入特定的信息,这些信息可以包括机密、卷、卷装载和环境变量,它是一种API资源,可以使用标签选择器来选择该Pod Preset应用在哪个Pod上。使用pod预设可以允许pod模板作者不必显式地为每个pod提供所有信息。这样,使用特定服务的pod模板的作者就不需要知道该服务的所有细节。

 K8S提供了一个许可控制器,它允许将Pod Preset应用到将要到来的创建Pod的请求中,当Pod创建请求发生时,系统会做下面的一些事情:

  1. 检索所有可用的PodPresets
  2. 校验PodPreset的标签选择器是否和待创建Pod的标签匹配;
  3. 尝试将PodPreset中定义的各种各样资源合并到将要创建的Pod中;
  4. 如果发生错误,抛出在记录Pod合并出错、创建Pod但未从PodPreset中注入资源的异常事件;
  5. 对修改后的Pod规约(pod spec)进行注释,以表明它已被podpreset修改,格式为:podpreset.admission.kubernetes.io/podpreset-<pod-preset name>: "<resource version>"

 每个Pod可以被0个或多个PodPreset匹配,每个PodPreset可以应用在0个或多个Pod上,当PodPreset应用在1个或多个Pod上,K8S修改Pod规约(Pod Spec)。比如对于修改EnvEnvFromVolumeMounts,K8S修改Pod中所有容器的spec;再如修改Volume,K8S会修改Pod的Spec。

注:Pod Preset是可以修改Pod规约(Pod Spec)中的.spec.containers字段的,但POD Preset中的资源定义不会应用于initcontainers字段。

【要求Pod Preset不应用于某个指定的Pod】

 某些场景下,可能希望某个Pod不应用Pod Preset,即不希望Pod Preset来修改某个Pod的规约,需要在对应的Pod规约(Pod Spec)中指定:podpreset.admission.kubernetes.io/exclude: "true"

【开启Pod Preset】

 要想在集群中使用Pod Preset,需要做如下的设置:

  1. 开启settings.k8s.io/v1alpha1/podpreset类型的API。可以通过在API Server中的--runtime-config添加settings.k8s.io/v1alpha1=true。如果使用的是minikube搭建的集群,启动集群时添加--extra-config=apiserver.runtime-config=settings.k8s.io/v1alpha1=true标识也行。
  2. 开启PodPreset允许控制器。可以通过在API Server中的--enable-admission-plugins选项包含PodPreset。如果使用的是minikube搭建的集群,启动集群时添加--extra-config=apiserver.enable-admission-plugins=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota,PodPreset也行。
  3. 在对应的命名空间中通过创建PodPreset对象来定义PodPreset。

2.6 中断(Disruptions)

待续。。。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐