1. Pod

pod是一组紧密关联的容器组合,他们共享PID,IPC,NETWORK,UTS namespace,一个Pod里可以运行多个容器,一个Pod里的多个容器共享网络和文件系统,它们可以直接通过localhost口进行通信。

2. Pod实现原理

首先需要明白的是pod只是一个逻辑上的概念。实际上,kubernetes真正处理的还是宿主机操作系统上的namespace和Cgroups,而不存在一个Pod边界或者隔离环境。

2.1 网络共享

具体的说pod里的所有容器,共享的是同一个Network NameSpace,并且也可以申明共享同一个volume。 但是这并不是简单的像启动docker那样用–net和–volumes-from这样的命令实现,因为这样的命令有一个强依赖性,必须是被共享的容器先启动,这种强依赖关系是不可取的。

所以,在kubernetes项目里需要一个中间容器,这个容器叫Infra容器,在这个pod里,Infra容器永远是第一个被创建的容器,而其他的用户定义的容器则是通过join Network Namespace方式与Infra容器建立关联,如下图:

2.1Pod-infra

如上所示,pod里有两个容器A和B,还有一个Infra容器,在kubernetes里,Infra容器一定要占用很小的资源,所以它使用的是一个非常特殊的镜像,叫:k8s.gcr.io/pause。这个镜像会永远处于一个暂停的状态。在Infra容器hold住Network Namespace后,用户容器就可以加入到Infra容器的Network Namespace中,所以如果查看这些容器的namespace信息,它们指向的值是完全一样的。

这就说明,对于Pod里的容器A和B来说:

  • 它们可以直接通过local host进行通信
  • 它们看到的网络设备跟Infra看到的是一样的
  • 一个Pod只有一个IP地址,也就是Pod的Network Namespace对应的IP地址
  • Pod的生命周期跟Infra一致,和容器A,B无关
  • 其他所有的网络资源都是Pod的一份,并且被该Pod中的所有容器共享

例如:声明一个pod,yaml文件如下:

[root@master k8s]# cat pod-test.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: pod-test
  labels:
    name: redis-master
spec:
  containers:
  - name: master01
    image: redis
    ports:
    - containerPort: 6379
      hostPort: 6388
  - name: master02
    image: panubo/sshd 
    ports:
    - containerPort: 22
      hostPort: 8888

然后通过kubectl apply -f pod-test.yaml来创建pod。通过kubectl get pods -o wide查看在哪个node,我们可以在node的机器上面通过docker ps看到启动的Infra容器,启动的命令是:/pause

所以对于同一个Pod里的所有容器,它们进出的流量可以认为都是通过Infra容器完成的。所以在配置网络的时候只需要关注pod的网络而不是容器的网络,也就是说我们只需要关注Infra的Network Namespace即可。

2.2 挂载共享

同理,共享volume也只需要把volume定义在pod层。一个 volume对应的宿主机的目录对于Pod来说只有一个,Pod里的容器只需要声明挂载这个Volume,就可以共享这个volume对应的宿主机目录。比如:

apiVersion: v1
kind: Pod
metadata:
  name: two-containers
spec:
  restartPolicy: Never
  volumes:
  - name: shared-data
    hostPath:      
      path: /data
  containers:
  - name: nginx-container
    image: nginx
    volumeMounts:
    - name: shared-data
      mountPath: /usr/share/nginx/html
  - name: debian-container
    image: debian
    volumeMounts:
    - name: shared-data
      mountPath: /pod-data
    command: ["/bin/sh"]
    args: ["-c", "echo Hello from the debian container > /pod-data/index.html"]

如上这个Pod中有两个容器nginx-container和debian-container,它们都声明挂载同一个volume,而这个volume是一个hostPath类型,也就是宿主机上的目录/data,这样这两个容器都同时绑定挂载这个宿主机目录/data。

pod的这种设计模式就是希望如果用户希望在一个容器里跑多个不相关的应用时,就可以考虑是否可以将它们定义为一个pod里的多个容器。

例子1:我们想在一个容器里运行WAR包,它需要和tomcat一起运行。如果用docker来做这件事会有一下两个方案:

  • 将war包和tomcat做成一个镜像;
  • 不重做tomcat镜像,将war通过挂载的方式挂载到tomcat容器中运行起来;

这是两种常规的思想,现在有Pod,我们就可以在一个Pod里定义两个容器,一个用来专门管理war包,一个用来管理tomcat应用,只是这两个容器声明挂载同一个volume,如下:

apiVersion: v1
kind: Pod
metadata:
  name: javaweb-2
spec:
  initContainers:
  - image: geektime/sample:v2
    name: war
    command: ["cp", "/sample.war", "/app"]
    volumeMounts:
    - mountPath: /app
      name: app-volume
  containers:
  - image: geektime/tomcat:7.0
    name: tomcat
    command: ["sh","-c","/root/apache-tomcat-7.0.42-v2/bin/start.sh"]
    volumeMounts:
    - mountPath: /root/apache-tomcat-7.0.42-v2/webapps
      name: app-volume
    ports:
    - containerPort: 8080
      hostPort: 8001 
  volumes:
  - name: app-volume
    emptyDir: {}
  • volumes: 这一部分定义了一个名为 app-volume 的存储卷。这里使用了 emptyDir 类型,意味着这是一个临时目录,它的生命周期与 Pod 相同。当 Pod 被创建时,这个卷也就被创建,Pod 删除时,卷中的数据也会被删除。

  • volumeMounts: 这一部分在容器配置中使用,它告诉 Kubernetes 如何将上面定义的 app-volume 卷挂载到容器内的特定路径上。

对于 initContainers 中的 war 容器,app-volume 被挂载到容器的 /app 路径。这意味着,当 war 容器执行复制操作 cp /sample.war /app 时,它实际上是将文件复制到了 app-volume 卷中。对于 containers 中的 tomcat 容器,相同的 app-volume 卷被挂载到 /root/apache-tomcat-7.0.42-v2/webapps 路径。这意味着,tomcat 容器在 /root/apache-tomcat-7.0.42-v2/webapps 路径下看到的内容实际上是 app-volume 卷中的数据,包括由 war 容器复制过来的 sample.war 文件。

上面定义的两个容器,第一个容器只有一个war包放在根目录下,然后申明一个initContainers将war包拷贝到/app目录下,第二个容器的作用是启动一个tomcat,它和initContainers挂载了同一个volume,而且initContainers一定是比containers里的容器先启动,所以在启动tomcat的时候它的webapps下面一定会有一个war包,这样每次在发布新版本的时候就只需要制作那个最小的拷贝war包的镜像,而不需要制作包含tomcat和war包这个大的镜像,解决了tomcat和war的耦合性。

因此,当 tomcat 容器启动并运行时,它能够直接访问由 war 容器放置在共享卷中的 sample.war 文件。这种方式非常适用于需要初始化准备数据或配置文件的场景,初始化容器可以先运行完成这些任务,主应用容器随后启动并直接使用这些预备的数据或配置。

例子2:容器的日志收集 假如我们有一个应用会不断的输出日志到/var/log下,这个时候我们就可以借助上面的思想,把pod里的一个volume挂载到/var/log下,然后这个pod里再启动另外一个容器,它们挂载同一个volume,这个容器里运行的是日志收集应用,它可以不断收集/var/log下面的日志到日志平台或者日志存储服务器上。

由上,凡是调度、网络、存储已经安全相关的属性,都是Pod级别。这些属性的共同特征是描述整个“机器”的属性,而非“机器”里应用的属性。

2.3 Infra和Kubelet

在 Kubernetes 中,每个 Pod 内部包含了一个特殊的容器,称为 “Infra 容器”“暂存容器”(Pause Container)。这个容器是 Pod 中首先启动的容器,它的主要职责是为其他容器(业务容器和初始化容器)设置网络和挂载点,并保持 Pod 的网络命名空间和 IPC 命名空间。

2.3.1 Infra 容器的职责

  1. 网络命名空间:Infra 容器创建并持有整个 Pod 的网络命名空间。这意味着所有其他容器都加入到这个已创建的网络命名空间中,从而实现网络资源(如 IP 地址和端口)的共享。

  2. IPC 命名空间:Infra 容器同样管理着 Pod 的 IPC 命名空间,允许在同一个 Pod 内的不同容器间进行进程间通信。

  3. PID 命名空间(可选):如果启用了 PID 命名空间共享,Infra 容器还将管理 PID 命名空间,使得 Pod 内的容器可以看到其他容器的进程。

  4. 挂载点:Infra 容器也被用来设置和持有一些关键的挂载点,比如 /dev/proc 等,其他业务容器将共享这些挂载点。这些是操作系统级别的重要资源,需要在所有容器中保持一致和同步。

2.3.2 Kubelet 的角色

Kubelet 是运行在每个节点上的 Kubernetes 组件,负责容器的生命周期管理,包括 Pod 和容器的创建、更新和销毁。在处理数据卷方面,Kubelet 执行以下任务:

  • 挂载卷(Volumes):Kubelet 根据 Pod 的定义,将指定的数据卷挂载到容器的相应目录。这些卷可以是 emptyDirconfigMappersistentVolumeClaim 等,它们可能用于存储业务数据、配置文件等。
  • 配置卷访问:Kubelet 确保卷的权限和访问控制符合 Pod 的安全策略和需求。
  • 卷的生命周期管理:对于持久卷(Persistent Volumes),Kubelet 还需要处理卷的挂载和解挂,以及与存储后端的交互。

2.3.3 Volumes和 Infra 容器

虽然 Infra 容器负责维护和管理网络和某些 IPC 资源,但数据卷的共享并不直接依赖于 Infra 容器。数据卷(例如 emptyDirconfigMappersistentVolumeClaim 等)的挂载是由 Kubernetes 的 Kubelet(节点上的代理程序)处理的,它负责在容器文件系统中挂载相应的卷。

2.3.4 数据卷共享的工作原理

当 Kubelet 在 Pod 中创建容器时,它会根据 Pod 定义中的 volumes 和每个容器的 volumeMounts 配置,将指定的卷挂载到容器的指定路径上。这个过程是独立于 Infra 容器存在的,虽然所有的容器(包括 Infra 容器)都在同一个网络命名空间内,但数据卷的挂载是通过 Kubelet 直接操作完成的,不经过 Infra 容器。

2.3.5 结论

因此,虽然 Infra 容器在 Pod 中扮演着管理和调度网络、IPC 命名空间的角色,但数据卷的共享是通过 Kubernetes 的存储管理子系统独立处理的。这意味着即使是在 Infra 容器不存在的特殊情况下,只要 Kubelet 能够正确处理卷的挂载,数据共享依然可以正常工作。

3. Pod重要字段

3.1 NodeSelector

它是提供一个Node和Pod绑定的字段,用法如下:

apiVersion: v1
kind: Pod
spec:
	nodeSelector:
  	disktype: ssd
  ...

这样,意味着pod只能运行在disktype是ssd的node上

3.2 NodeName

在 Kubernetes 系统中,NodeName 是 Pod 配置的一个字段,用于指定 Pod 应当运行在哪一个节点上。如果 NodeName 字段被赋值,Kubernetes 会认为该 Pod 已经完成了调度阶段,并将其部署到指定的节点上。

假设你有一个 Kubernetes 集群,其中包括名为 node1, node2, 和 node3 的三个节点。你需要在测试环境中验证某个 Pod 是否能在特定的节点(比如 node1)上正常运行。

  1. 正常的调度流程:通常,当你创建一个 Pod 时,不需要手动指定 NodeName。Kubernetes 的调度器会自动选择一个合适的节点,根据资源需求、亲和性设置等因素进行调度。

  2. 手动指定 NodeName:为了测试,你可以在 Pod 配置中直接指定 NodeName。例如,创建一个 Pod 配置文件 pod.yaml,如下所示:

    apiVersion: v1
    kind: Pod
    metadata:
      name: test-pod
    spec:
      containers:
      - name: test-container
        image: nginx
      nodeName: node1
    

    在这个例子中,nodeName: node1 告诉 Kubernetes 不需要经过调度器的调度过程,直接把这个 Pod 部署在 node1 节点上。

  3. 应用配置:使用 kubectl 命令应用这个配置文件:

    kubectl apply -f pod.yaml
    

    这个命令会创建一个 Pod,并且因为 NodeName 已经指定,Kubernetes 不会再进行调度,直接尝试在 node1 节点上启动这个 Pod。

这种手动指定 NodeName 的做法,主要用于测试或特定的场景,因为它绕过了 Kubernetes 的正常调度过程。在生产环境中,通常不建议这样做,因为它可能会导致资源分配不均等问题。

3.3 HostAliases

定义Pod的hosts文件里的内容,也就是/etc/hosts里的内容。用法如下:

apiVersion: v1
kind: Pod
spec:
	hostAliases:
  - ip: "10.1.10.100"
  	hostnames:
    - "foo.joker.com"

这样在pod启动后就会在/etc/hosts里看到如下内容:

cat /etc/hosts
# Kubernetes-managed hosts file.
127.0.0.1 localhost
...
10.244.135.10 hostaliases-pod
10.1.10.100 foo.joker.com

3.4 shareProcessNamespace

这个字段如果设置为True,就表示开启共享Pid NameSpace,例如:

apiVersion: v1
kind: Pod
metadata:
  name: pod-pid
  labels:
    name: pid-namespace
spec:
  shareProcessNamespace: True
  containers:
  - image: nginx
    name: nginx-container
  - image: busybox
    name: busybox-container
    stdin: true
    tty: true

我们通过kubectl apply -f pod-shareProcessNamespace.yaml启动Pod。 然后通过kubectl attach -it pod-pid -c busybox-container查看进程。 我们可以看到busybox-container这个容器中除了有自己的shell进程还有nginx的进程。这说明它们共享了同一个Pid NameSpace.

类似的,在Pod中的容器共享宿主机的namespace,也一定是Pod级别。例如:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  hostNetwork: true
  hostIPC: true
  hostPID: true
  containers:
  - name: nginx
    image: nginx
  - name: shell
    image: busybox
    stdin: true
    tty: true

在 Kubernetes 的 Pod 配置中,hostNetwork、hostIPC 和 hostPID 是与 Pod 容器如何与宿主机交互相关的设置。这些设置会影响 Pod 容器的网络、进程间通信 (IPC) 和进程标识符 (PID) 命名空间的共享情况。

hostNetwork: true 当你设置 hostNetwork: true,意味着这个 Pod 使用宿主机的网络命名空间。这表示 Pod 内的容器不会使用 Kubernetes 网络命名空间隔离,容器里的网络将直接映射到宿主机的网络环境上。容器能够访问宿主机的网络接口,包括能够使用宿主机的 IP 地址和端口。

hostIPC: true 设置 hostIPC: true 时,Pod 将使用宿主机的 IPC 命名空间。IPC 命名空间负责控制进程间的通信机制,如信号量、消息队列和共享内存等。这样,Pod 内的容器可以与宿主机以及其他使用同一 IPC 命名空间的容器共享这些 IPC 资源。

hostPID: true 通过设置 hostPID: true,Pod 会使用宿主机的 PID 命名空间。这意味着 Pod 内的容器可以看到和交互宿主机上的所有进程(包括其他容器的进程)。容器内的进程可以像宿主机上的本地进程一样,通过进程ID(PID)来查看和发送信号。

4. 健康检查和恢复机制

在Kubernetes里,可以为Pod里的容器配置健康检查探针(Probe)。这样,kubelet就会根据这个Probe的返回值决定这个Pod的健康状态,而不是以容器是否运行作为依据(这种以容器是否运行并不能有效的判断应用是否正常)。

4.1 LivenessProbe

在 Kubernetes 中,LivenessProbe 是用于检查容器是否还在正常运行。如果检测失败(即容器不再响应),Kubernetes 将会重启该容器。这是一种自动恢复服务的机制,确保服务的稳定性和可用性。

LivenessProbe 可以通过几种方式来检测:

  • HTTP GET:向容器的 IP 地址发送 HTTP GET 请求。如果返回的状态码不在成功范围内(通常是 200-399),则认为检测失败。
  • TCP Socket:尝试建立 TCP 连接到容器的特定端口。如果连接失败,认为检测失败。
  • Exec:在容器中执行指定命令。如果命令退出代码为 0,则认为检测成功;否则,认为失败。

参数配置

  • initialDelaySeconds:容器启动后延迟多少秒开始进行存活检查。
  • periodSeconds:执行检查的频率(秒数)。
  • timeoutSeconds:等待响应的超时时间。
  • successThreshold:被视为成功前所需的最小连续成功次数。
  • failureThreshold:当检测失败达到一定次数后,容器将被重启。可以从RESTARS次数看到其重启次数。值得一提的是Kubernetes其实没有RESTART这么一说,其实也就是重新创建一个Pod,然后删除老的Pod。

Http 示例

假设你有一个运行 web 应用的容器,你想通过 HTTP GET 请求检查它的健康状态:

apiVersion: v1
kind: Pod
metadata:
  name: my-web-app
spec:
  containers:
  - name: web-container
    image: my-web-app:latest
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
      initialDelaySeconds: 10
      periodSeconds: 5
      timeoutSeconds: 1
      failureThreshold: 3

在这个例子中:

  • httpGet: 使用 HTTP GET 请求检查 /healthz 路径,端口是 8080。
  • initialDelaySeconds: 在容器启动后等待 10 秒钟后开始进行健康检查。
  • periodSeconds: 每 5 秒执行一次健康检查。
  • timeoutSeconds: 如果 1 秒内没有响应,认为本次检查失败。
  • failureThreshold: 如果连续 3 次检查失败,Kubernetes 会重启该容器。

TCP Socket 示例

假设你有一个运行数据库服务的容器,该服务监听在 TCP 端口 3306 上。你可以使用 TCP Socket 检查方法来确认该服务是否响应:

apiVersion: v1
kind: Pod
metadata:
  name: my-database
spec:
  containers:
  - name: database-container
    image: mysql
    livenessProbe:
      tcpSocket:
        port: 3306
      initialDelaySeconds: 15
      periodSeconds: 10
      timeoutSeconds: 5
      failureThreshold: 3

在这个配置中:

  • tcpSocket: 检测容器的 3306 端口是否能成功建立 TCP 连接。
  • initialDelaySeconds: 容器启动 15 秒后开始进行存活检查。
  • periodSeconds: 每 10 秒检查一次。
  • timeoutSeconds: 等待连接成功的最长时间为 5 秒。
  • failureThreshold: 连续 3 次检查失败后,Kubernetes 会尝试重启该容器。

Exec 示例

假设你有一个应用容器,需要通过执行容器内的脚本来检查其健康状态,可以使用 Exec 方法:

apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  containers:
  - name: app-container
    image: my-custom-app
    livenessProbe:
      exec:
        command:
        - /usr/local/bin/check-health
      initialDelaySeconds: 5
      periodSeconds: 5
      timeoutSeconds: 1
      failureThreshold: 3

在这个配置中:

  • exec: 执行容器内的 /usr/local/bin/check-health 脚本。如果脚本的退出状态码是0,则认为检查成功;非0则认为失败。
  • initialDelaySeconds: 容器启动后 5 秒钟开始执行脚本进行健康检查。
  • periodSeconds: 每 5 秒执行一次脚本检查。
  • timeoutSeconds: 脚本执行的最长等待时间为 1 秒。
  • failureThreshold: 如果连续 3 次检查失败,Kubernetes 会重启该容器。

4.2 restartPolicy

这个就是容器的恢复机制,也叫restartPolicy。它是Pod的Spec部分的标准字段,默认是Always,就是任何时候这个容器发生异常,它就会被重新创建一次。

值得注意的是Pod的恢复过程,永远都是发生在当前节点,而不会跑到其他节点去,事实上,一旦一个Pod和Node绑定,它就会永远在这个Node上,除非这个绑定发生了变化,否则它永远不会离开这个节点。这就意味着,如果这个宿主机宕机了,这个Pod也不会迁移到其他节点上去,如果想要让这个Pod出现在其他Node上,就要用类似于Deployment这样的控制器来管理Pod。

作为用户,可以自定义restartPolicy的的恢复策略,它有如下几种恢复策略:

  • Always:任何情况下,只要容器不在运行状态,就会自动重启容器;
  • OnFailure:只在容器异常的情况下才会重启容器;
  • Never:从来不重启容器;

其restartPolicy和Pod里容器状态的对应关系的设计基本原理如下:

  • 只要这个Pod的restartPolicy的策略允许重启容器(比如Always/OnFailure),那么这个Pod就会保持在running状态,并进行容器重启,否则就是Failed状态;
  • 对于包含所有容器的Pod,只有其里面全部容器都进入异常状态后,Pod才会进入Failed状态,在此之前Pod都是running状态,此时Pod的READY的字段会显示正常的容器个数。

所以,假如一个Pod里只有一个容器,然后这个容器异常退出了,只有当restartPolicy=Nerver的时候,这个Pod才会进入Failed状态,而其他情况下,因为有这个重启策略存在,这个Pod会一直保持在running状态 。而如果这个Pod有多个容器,如果只有一个容器异常退出,哪怕restartPolicy=Never,只有当这个Pod里的所有容器都异常的情况下,这个Pod才会进入Failed状态。

4.3 ReadinessProbe

在Pod中,livenessProbe是做存活性检查,而ReadinessProbe是做就绪性检查,所谓的就绪性检查就是检查我们应用是否就绪,比如我们的tomcat服务,在启动tomcat服务的时候需要解压war包,然后将其加入JVM,然后启动,这个过程需要时间就不止10秒,在应用完整启动后,我们kubectl get pods的时候查看READY状态才变成可用,如果没有做就绪性检测,只要容器起来后READY状态就变为可用了,在这个时候实际上我们的应用是并不可用的。

在我们定义Pod的时候定义就绪性检测是很有必要的。 ReadinessProbe的健康检测和LivenessProbe一样,也有以下三种:

  • TCPSocket
  • httpGet
  • exec

下面我们定义一个简单的YAML文件:

apiVersion: v1
kind: Pod
metadata:
  name: httpget-readinessprobe
  namespace: default
spec:
  containers:
  - name: httpget-pod
    image: nginx
    ports:
    - name: nginx-port
      containerPort: 80
    readinessProbe:
      httpGet:
        path: /index.html
        port: nginx-port
      initialDelaySeconds: 3

配置解释 这里的 readinessProbe 配置如下:

  • httpGet: 这种类型的探针通过向容器内部的指定端口发送 HTTP GET 请求来检查容器的健康状态。在这个例子中,请求发送到容器的 nginx-port 端口(实际值为 80),路径为 /index.html。

  • path: 指定 HTTP GET 请求的路径,这里是 /index.html。这意味着探针会请求这个 URL 来判断容器的状态。

  • port: 指定接收 HTTP GET 请求的端口。在此配置中使用了端口名 nginx-port,根据之前的端口定义,实际对应容器端口 80。

  • initialDelaySeconds: 设置容器启动后等待 3 秒才开始执行首次探测。这样做可以给容器足够的时间初始化。

工作原理

  • 初始化阶段: 当 Pod 启动后,容器开始初始化。在这个阶段,readinessProbe 还未开始工作。

  • 延迟检查: 根据配置,readinessProbe 会在容器启动后的第 3 秒开始工作。

  • 执行探测: 探针开始通过发送 HTTP GET 请求到 /index.html 来检查容器的状态。这个请求是对容器内运行的 Nginx 服务器的正常请求。

  • 评估响应: 如果请求成功(即 HTTP 状态码为 200-399 范围内),则认为容器已准备好接受流量。如果请求失败(服务器返回错误状态码或请求超时等),则认为容器尚未准备好。

  • 流量分配: 只有当 readnessProbe 返回成功状态时,Kubernetes 的服务(Service)才会开始将流量路由到此 Pod。如果探测失败,Pod 将不会接收到新的流量,直到探测再次成功。

通过这种方式,readinessProbe 确保只有当 Nginx 服务器准备好接受请求时,Pod 才会开始处理入来的流量。这是维护服务稳定性和可靠性的重要机制,特别是在动态环境中,如滚动更新或服务扩展时。

然后我们创建这个YAML文件,观察起Pod状态:

[root@VM-12-196-centos:test]$ kubectl apply -f readlinesspprobe-pod.yaml 
pod/httpget-readinessprobe created
[root@VM-12-196-centos:test]$ kubectl get po -ntest
NAME                     READY   STATUS    RESTARTS   AGE
httpget-readinessprobe   1/1     Running   0          65s

可以看到pod成功启动,并且pod中只有一个容器READY是1/1。 我们删除index.html文件试一下:

[root@VM-12-196-centos:test]$ kubectl exec -it httpget-readinessprobe -ntest -- /bin/bash
root@httpget-readinessprobe:/# ls /usr/share/nginx/html/
50x.html  index.html
root@httpget-readinessprobe:/# rm -rf /usr/share/nginx/html/index.html 
root@httpget-readinessprobe:/# exit
exit
[root@VM-12-196-centos:test]$ kubectl get po -ntest
NAME                     READY   STATUS    RESTARTS   AGE
httpget-readinessprobe   0/1     Running   0          9m33s

可以看到删除index.html,等待一会后,查看pod状态READY是0,但是STATUS还是running状态,这就是容器运行是正常的,但是应用是异常的。我们再恢复回来:

[root@VM-12-196-centos:test]$ kubectl exec -it httpget-readinessprobe -ntest -- /bin/bash
root@httpget-readinessprobe:/# echo "aaa" >> /usr/share/nginx/html/index.html
root@httpget-readinessprobe:/# exit
exit
[root@VM-12-196-centos:test]$ kubectl get po -ntest
NAME                     READY   STATUS    RESTARTS   AGE
httpget-readinessprobe   1/1     Running   0          11m

4.4 lifecycle

lifecycle用于容器启动后,停止前的操作,其主要对象有:

  • postStart:容器启动后操作,在容器启动后会立即执行的操作
  • preStop:容器停止前操作,在容器停止前会立即执行的操作

其内的对象和livenessProbe一样,我们定义一个YAML文件如下:

apiVersion: v1
kind: Pod
metadata:
  name: lifecycle-test
  namespace: default
spec:
  containers:
  - name: lifecycle-test
    image: busybox
    lifecycle:
      postStart:
        exec:
          command: ["/bin/sh", "-c", "mkdir -p /data/web/html; echo 'lifecycle test' >> /data/web/html/index.html "]
    command:
    - "/bin/sh"
    - "-c"
    - "sleep 3600"
[root@VM-12-196-centos:test]$ kubectl apply -f lifycycle.yaml 
error: no objects passed to apply
[root@VM-12-196-centos:test]$ kubectl apply -f lifycycle.yaml 
pod/lifecycle-test created
[root@VM-12-196-centos:test]$ kubectl exec -it lifecycle-test -ntest -- /bin/sh
/ # 
/ # cat /data/web/html/index.html 
lifecycle test
/ # exit

可以看到在容器启动之后,执行了postStart的命令

5. PodPreset

PodPreset叫做Pod预设值功能,它可以自动为Pod填充一些定义好的字段。 要使用PodPreset,需要满足一下几点:

  • 确保已经开启了这个API对象:settings.k8s.io/v1alpha1/podpreset;
  • 确保开启准入PodPreset;

比如定义了一下一个简单的YAML:

apiVersion: v1
kind: Pod
metadata:
  name: website
  labels:
    app: website
    role: frontend
spec:
  containers:
    - name: website
      image: nginx
      ports:
        - containerPort: 80

然后定义一个PodPreset的YAML文件:

apiVersion: settings.k8s.io/v1alpha1
kind: PodPreset
metadata:
  name: allow-database
spec:
  selector:
    matchLabels:
      role: frontend
  env:
    - name: DB_PORT
      value: "6379"
  volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
    - name: cache-volume
      emptyDir: {}

在这个Preset的YAML文件中,selector字段对应的标签表示这个Preset只会对该标签的Pod有效。其后面的spec字段就定义了一些环境变量和挂载等。

现在我们先执行preset.yaml,在执行pod.yaml:

kubectl create -f preset.yaml
kubectl create -f pod.yaml

这是可以查看这个Pod的API:

kubectl get pod website -o yaml
apiVersion: v1
kind: Pod
metadata:
  name: website
  labels:
    app: website
    role: frontend
  annotations:
    podpreset.admission.kubernetes.io/podpreset-allow-database: "resource version"
spec:
  containers:
    - name: website
      image: nginx
      volumeMounts:
        - mountPath: /cache
          name: cache-volume
      ports:
        - containerPort: 80
      env:
        - name: DB_PORT
          value: "6379"
  volumes:
    - name: cache-volume
      emptyDir: {}

从上可以看到这个Pod里面已经加了我们preset中定义的字段了。

需要注意的是:PodPreset中定义的内容,只会在这个Pod API创建之前追加到这个对象本身上,而不会影响这个Pod的任何的控制器的定义。比如我们创建一个nginx-deployment的Deployment对象,这个Deployment对象本身不会被PodPreset对象改变,只有通过这个Deployment创建出来的Pod并且标签是PodPreset中定义的才会被改变。

  • 仅在 Pod 创建前应用:PodPreset 中定义的内容仅在 Pod 创建过程中应用到 Pod 对象。这意味着,任何已经存在的 Pod 不会受到任何后来创建的 PodPreset 的影响。

  • 不影响控制器定义:如果你使用如 Deployment、StatefulSet 等控制器来管理 Pod,那么这些控制器的定义不会因为 PodPreset 而改变。PodPreset 只影响由这些控制器创建的、符合特定标签选择器条件的 Pod 实例。

  • 作用于符合条件的 Pod:PodPreset 适用于那些在其标签与 PodPreset 定义的标签选择器匹配的 Pod。在这种情况下,只有新创建并且标签符合条件的 Pod 会被 PodPreset 所修改。

举个例子,如果你创建了一个 PodPreset,它指定了所有带有标签 role: webserver 的 Pod 需要添加一个特定的环境变量。接着,你创建了一个带有相应标签的 Deployment。这个 Deployment 本身的定义不会被改变,但是通过这个 Deployment 创建的每个新 Pod(带有 role: webserver 标签)都会自动包含那个环境变量。