揭秘Pod状态与生命周期管理的秘密(中)

发布时间:2023年12月26日

上一篇文章中主要介绍了Pod的基础概念与使用、删除。本文将带你一起学习Pod的几种容器(Init、Pause)

点击 这里 可以查看所有相关文章。

Init 容器

本文讲解 Init 容器的基本概念,这是一种专用的容器,在应用程序容器启动之前运行,用来包含一些应用镜像中不存在的实用工具或安装脚本。

理解 Init 容器

Pod 能够具有多个容器,应用运行在容器里面,但是它也可能有一个或多个先于应用容器启动的 Init 容器。

Init 容器与普通的容器非常像,除了如下两点:

  • Init 容器总是运行到成功完成为止。
  • 每个 Init 容器都必须在下一个 Init 容器启动之前成功完成。

如果 Pod 的 Init 容器失败,Kubernetes 会不断地重启该 Pod,直到 Init 容器成功为止。然而,如果 Pod 对应的 restartPolicy 为 Never,它不会重新启动。

指定容器为Init容器,在PodSpec中添加initContainers字段,以v1.Container类型对象的 JSON 数组的形式,还有app的containers数组。Init 容器的状态在status.initContainerStatuses字段中以容器状态数组的格式返回(类似 status.containerStatuses 字段)。

与普通容器的不同之处

Init 容器支持应用容器的全部字段和特性,包括资源限制、数据卷和安全设置。 然而,Init 容器对资源请求和限制的处理稍有不同。 而且 Init 容器不支持 Readiness Probe,因为它们必须在 Pod 就绪之前运行完成。

如果为一个 Pod 指定了多个 Init 容器,那些容器会按顺序一次运行一个。只有当前面的 Init 容器必须运行成功后,才可以运行下一个 Init 容器。当所有的 Init 容器运行完成后,Kubernetes 才初始化 Pod 和运行应用容器。

Init 示例

下面列举了 Init 容器的一些用途:

  • 等待一个 Service 创建完成,通过类似如下 shell 命令:
for i in {1..100}; do sleep 1; if dig myservice; then exit 0; fi; exit 1
  • 将 Pod 注册到远程服务器,通过在命令中调用 API,类似如下:
curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register -d 'instance=$(<POD_NAME>)&ip=$(<POD_IP>)'
  • 在启动应用容器之前等一段时间,使用类似 sleep 60 的命令。

  • 克隆 Git 仓库到数据卷。

  • 将配置值放到配置文件中,运行模板工具为主应用容器动态地生成配置文件。例如,在配置文件中存放 POD_IP 值,并使用 Jinja 生成主应用配置文件。

使用 Init 容器

下面展示了一个具有 2 个 Init 容器的简单 Pod。 第一个等待 myservice 启动,第二个等待 mydb 启动。 一旦这两个 Service 都启动完成,Pod 将开始启动。

myapp.yaml

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
spec:
  containers:
  - name: myapp-container
    image: busybox
    command: ['sh', '-c', 'echo The app is running! && sleep 3600']
  initContainers:
  - name: init-myservice
    image: busybox
    command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;']
  - name: init-mydb
    image: busybox
    command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']

下面的 YAML 文件展示了 mydb 和 myservice 两个 Service:

services.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

首先,执行

kubectl apply -f myapp.yaml
kubectl get pods

输出类似以下内容:

NAME        READY   STATUS     RESTARTS   AGE
myapp-pod   0/1     Init:0/2   0          28s

可以看到pod not ready 并且 STATUS中显示 两个Init容器在执行。

继续执行kubectl describe pod myapp-pod


可以看到,此时Events中打印的日志。正在执行container init-myservice。

第二步,执行kubectl apply -f services.yaml

一旦我们启动了 mydb 和 myservice 这两个 Service,我们能够看到 Init 容器完成,并且 myapp-pod 被创建:

NAME        READY     STATUS    RESTARTS   AGE
myapp-pod   1/1       Running   0          9m

Pause 容器

Pause 容器特点

  • 镜像非常小,目前在 700KB 左右
  • 永远处于 Pause (暂停) 状态

Pause 容器背景

像 Pod 这样一个东西,本身是一个逻辑概念。那在机器上,它究竟是怎么实现的呢?这就是我们要解释的一个问题。

既然说 Pod 要解决这个问题,核心就在于如何让一个 Pod 里的多个容器之间最高效的共享某些资源和数据。

因为容器之间原本是被 Linux Namespace 和 cgroups 隔开的,所以现在实际要解决的是怎么去打破这个隔离,然后共享某些事情和某些信息。这就是 Pod 的设计要解决的核心问题所在。

所以说具体的解法分为两个部分:网络和存储。

Pause 容器就是为解决 Pod 中的网络问题而生的。

Pause 容器实现

Pod 里的多个容器怎么去共享网络?下面是个例子:

比如说现在有一个 Pod,其中包含了一个容器 A 和一个容器 B,它们两个就要共享 Network Namespace。在 Kubernetes 里的解法是这样的:它会在每个 Pod 里,额外起一个 Infra container 小容器来共享整个 Pod 的 Network Namespace。

Infra container 是一个非常小的镜像,大概 700KB 左右,是一个 C 语言写的、永远处于 “暂停” 状态的容器。由于有了这样一个 Infra container 之后,其他所有容器都会通过 Join Namespace 的方式加入到 Infra container 的 Network Namespace 中。

所以说一个 Pod 里面的所有容器,它们看到的网络视图是完全一样的。即:它们看到的网络设备、IP 地址、Mac 地址等等,跟网络相关的信息,其实全是一份,这一份都来自于 Pod 第一次创建的这个 Infra container。这就是 Pod 解决网络共享的一个解法。

在 Pod 里面,一定有一个 IP 地址,是这个 Pod 的 Network Namespace 对应的地址,也是这个 Infra container 的 IP 地址。所以大家看到的都是一份,而其他所有网络资源,都是一个 Pod 一份,并且被 Pod 中的所有容器共享。这就是 Pod 的网络实现方式。

由于需要有一个相当于说中间的容器存在,所以整个 Pod 里面,必然是 Infra container 第一个启动。并且整个 Pod 的生命周期是等同于 Infra container 的生命周期的,与容器 A 和 B 是无关的。这也是为什么在 Kubernetes 里面,它是允许去单独更新 Pod 里的某一个镜像的,即:做这个操作,整个 Pod 不会重建,也不会重启,这是非常重要的一个设计。

Pause 容器的作用

我们检查 node 节点的时候会发现每个 node 上都运行了很多的 pause 容器,例如如下。

docker exec -it {container id} /bin/bash

进入容器后,执行docker ps

kubernetes 中的 pause 容器主要为每个业务容器提供以下功能:

  • 在 pod 中担任 Linux 命名空间共享的基础;
  • 启用 pid 命名空间,开启 init 进程。

  1. 在其中一个节点执行
docker run -d --name ghost-pause -p 8888:80 --ipc=shareable registry.k8s.io/pause:3.9
  1. 然后再运行一个 nginx 容器,nginx 将为 localhost:2368 创建一个代理。
docker run -d --name nginx -v `pwd`/nginx.conf:/etc/nginx/nginx.conf --net=container:ghost-pause --ipc=container:ghost-pause --pid=container:ghost-pause nginx
  1. 然后再为 ghost 创建一个应用容器,这是一款博客软件。
docker run -d --name ghost --net=container:ghost-pause --ipc=container:ghost-pause --pid=container:ghost-pause ghost

此时在节点中执行curl localhost:8888已经可以访问到ghost了。

root@minikube-m02:/# curl localhost:8888
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.25.3</center>
</body>
</html>

出现502 的原因是因为ghost容器中并没有部署mysql,访问不到本地的3306端口。
可以通过执行docker logs ghost进行查看具体原因。

下文中将继续介绍Pod的两种Hook

未完待续…

文章来源:https://blog.csdn.net/efheoihfe/article/details/135228592
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。