kubernetes pod 和 pause容器

158 阅读4分钟

POD

Pod 是 K8S 的最小工作单元。每个 Pod 包含一个或多个容器,多个容器以“边车”模式运行。K8S 管理的也是 Pod 而不是直接管理容器,Pod 中的容器会作为一个整体被 Master 调度到一个 Node 上运行。

Pod分为普通pod和静态pod,静态pod信息不储存在etcd中,直接存放在某个node的配置文件中。

pod多容器之间网络共享:

每个 Pod 都会被分配一个唯一的 IP 地址。Pod 中的所有容器共享网络空间,包括 IP 地址和端口。**Pod 内部的容器可以使用 localhost 互相通信。**Pod 中的容器与外界通信时,必须分配共享网络资源(例如使用宿主机的端口映射)。

每个pod都有一个基础容器pauseinfrastucture container底层容器),通过infra容器来实现多个容器的共享网络。

pod多容器之间存储共享:

可以为一个Pod指定多个共享的 Volume。容器之间的存储可以通过pause容器来共享文件存储。

Pause 容器

kubelet 的配置中有这样一个参数KUBELET_POD_INFRA_CONTAINER=--pod-infra-container-image=registry.access.redhat.com/rhel7/pod-infrastructure:latest

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

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

如何实现共享网络

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 不会重建,也不会重启,这是非常重要的一个设计。

跨Docker容器共享网络:--net=container:pause,共享内存:–ipc=shareable,共享pid:--pid=container:pause

# 创建一个共享内存的容器
$ docker run -d --name pause -p 8880:80 --ipc=shareable gcr.io/google_containers/pause-amd64:3.0

$ cat <<EOF >> nginx.conf
error_log stderr;
events { worker_connections  1024; }
http {
    access_log /dev/stdout combined;
    server {
        listen 80 default_server;
        server_name example.com www.example.com;
        location / {
            proxy_pass http://127.0.0.1:2368;
        }
    }
}
EOF
# 创建使用container:pause网络、内存、pid的容器,这样3个容器就像部署在同一个“节点”内
$ docker run -d --name nginx -v `pwd`/nginx.conf:/etc/nginx/nginx.conf --net=container:pause --ipc=container:pause --pid=container:pause nginx
$ docker run -d --name ghost --net=container:pause --ipc=container:pause --pid=container:pause ghost

pause_container.png

在 Kubernetes Pod 中,容器的运行方式与上述大致相同,但为每个 Pod 创建一个特殊的暂停容器。这个暂停容器运行一个非常简单的进程,它不执行任何功能,但本质上永远休眠(参见pause()下面的调用)。

源码:github.com/kubernetes/…

// 引入各种头文件

// 信号注册处理函数 (SIGINT, SIGTERM)
// 这种属于正常退出情况,所以退出码为 0
static void sigdown(int signo) {
  psignal(signo, "Shutting down, got signal");
  exit(0);
}

// 信号注册处理函数 (SIGCHLD)
// 由操作系统发送给父进程,通知子进程的状态变化
//   子进程终止:  
//   子进程暂停或恢复运行: 
static void sigreap(int signo) {
  // 等待指定的子进程退出
  
  // 函数原型:
  //   pid_t waitpid(pid_t pid, int *status, int options);
  // 参数说明:
  //   -1: 任意子进程
  //   NULL: 子进程的退出状态无需存储
  //   WNOHANG: 非阻塞,没有找到子进程时直接返回 0
  while (waitpid(-1, NULL, WNOHANG) > 0)
    ;
  // 如果子进程的数量大于 0, 无限循环
}

int main(int argc, char **argv) {
  // 打印版本号 ...

  if (getpid() != 1)
    // 如果 Pause 进程 ID 不等于 1, 输出警告信息
    // 因为进程 ID 等于 1 是 init 进程,负责接收处理僵尸进程
    // 所以如果 Pause 进程 ID 不等于 1, 可能产生僵尸进程的堆积
    fprintf(stderr, "Warning: pause should be the first process\n");

  // 注册 SIGINT 信号 (Ctrl + C)
  if (sigaction(SIGINT, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0)
    return 1;
  // 注册 SIGTERM 信号 (例如执行 kill, systemctl stop 等命令)
  if (sigaction(SIGTERM, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0)
    return 2;
  // 注册 SIGCHLD 信号
  if (sigaction(SIGCHLD, &(struct sigaction){.sa_handler = sigreap,
                                             .sa_flags = SA_NOCLDSTOP},
                NULL) < 0)
    return 3;

  // 无限循环
  for (;;)
    // pause 函数可以使当前进程陷入睡眠状态,避免浪费 CPU 资源
    // 直到捕获到一个信号后被唤醒
    pause();
  // 一切都是42...
  // https://en.wikipedia.org/wiki/Phrases_from_The_Hitchhiker%27s_Guide_to_the_Galaxy#The_Answer_to_the_Ultimate_Question_of_Life,_the_Universe,_and_Everything_is_42
  return 42;
}