k8s - зачем нужен pause контейнер

Вольный перевод с источника

Когда вы проверяете контейнеры, запущенные на вашем кластере K8s, вы часто видите pause контейнеры, как в следующем примере:
Pause Example
Вы когда-нибудь задумывались, почему существуют контейнеры pause? Когда мы создавали поды, мы не помним, что когда-либо создавали эти контейнеры pause, так откуда же они берутся? Вы можете подумать, что раз мы не создали эти контейнеры сами, возможно, кластер K8s создал их автоматически?

Это так, эти контейнеры pause создаются K8s, и вы можете рассматривать их как бесплатные контейнеры, предоставленные K8s.

Так называемый контейнер pause в K8s иногда называют инфра-контейнером. Он "скомпонован" с пользовательским контейнером и работает в том же поде.

Контейнер pause - это суть сетевой модели пода. Понимание контейнера pause может помочь вам лучше понять первоначальный замысел дизайна пода K8s.

Контейнер pause

При создании пода процесс kubelet сначала вызывает CRI интерфейс RuntimeService.RunPodSandbox для создания среды песочницы и настройки базовой операционной среды, такой как сеть.

Как только песочница пода создана, kubelet может создавать в ней пользовательские контейнеры. Когда придет время удалить под, kubelet сначала удалит Pod Sandbox, а затем остановит все контейнеры внутри него.

Контейнер pause - это контейнер, который существует в каждом поде, это как шаблон или родительский контейнер, от которого все новые контейнеры в поде наследуют пространства имен. Контейнер pause запускается, а затем переходит в режим "сна".

Это "шаблонный" контейнер, который резервирует пространства имен, общие для всех контейнеров внутри пода.

Как видно из следующего файла pause.c

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define STRINGIFY(x) #x
#define VERSION_STRING(x) STRINGIFY(x)
#ifndef VERSION
#define VERSION HEAD
#endif
static void sigdown(int signo) {
  psignal(signo, "Shutting down, got signal");
  exit(0);
}
static void sigreap(int signo) {
  while (waitpid(-1, NULL, WNOHANG) > 0)
    ;
}
int main(int argc, char **argv) {
  int i;
  for (i = 1; i < argc; ++i) {
    if (!strcasecmp(argv[i], "-v")) {
      printf("pause.c %s\n", VERSION_STRING(VERSION));
      return 0;
    }
  }
if (getpid() != 1)
    /* Not an error because pause sees use outside of infra containers. */
    fprintf(stderr, "Warning: pause should be the first process\n");
if (sigaction(SIGINT, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0)
    return 1;
  if (sigaction(SIGTERM, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0)
    return 2;
  if (sigaction(SIGCHLD, &(struct sigaction){.sa_handler = sigreap,
                                             .sa_flags = SA_NOCLDSTOP},
                NULL) < 0)
    return 3;
for (;;)
    pause();
  fprintf(stderr, "Error: infinite loop terminated\n");
  return 42;
}

Вы можете видеть, что контейнер pause делает следующие две вещи.

  • регистрирует различные функции обработки сигналов, которые в основном обрабатывают два типа информации: сигналы выхода и дочерние сигналы. Когда он получает сигнал SIGINT или SIGTERM, он выходит напрямую. При получении сигнала SIGCHLD вызывает waitpid и перерабатывает выходящий процесс.
  • главный процесс для цикла вызывает функцию pause(), которая переводит процесс в спящий режим до тех пор, пока он не завершится или не получит сигнал.
    Таким образом, даже если последний контейнер в поде разрушится, общее пространство имен все равно будет существовать, поскольку контейнер pause удерживает пространство имен.

Демо контейнера pause

Когда вы запускаете новый процесс в системе Linux, процесс наследует свое пространство имен от родительского процесса. Способ запуска процесса в пространстве имен заключается в создании нового пространства имен путем отмены общего пространства имен с родительским процессом. Ниже приведен пример запуска оболочки в новом пространстве имен PID, UTS, IPC и mount с помощью инструмента unshare.

$ unshare --pid --uts --ipc --mount -f chroot rootfs /bin/sh

Как только процесс запущен, вы можете добавить другие процессы в пространство имен этого процесса, чтобы сформировать под, где контейнеры в поде разделяют пространство имен.

Используя docker в качестве примера, давайте посмотрим, как можно создать под с нуля, используя контейнеры pause и общее пространство имен.

Создание контейнера pause
$ docker run -d --name pause gcr.io/google_containers/pause-amd64:3.0
Unable to find image 'gcr.io/google_containers/pause-amd64:3.0' locally
3.0: Pulling from google_containers/pause-amd64
a3ed95caeb02: Pull complete
f11233434377: Pull complete
Digest: sha256:163ac025575b775d1c0f9bf0bdd0f086883171eb475b5068e7defa4ca9e76516
Status: Downloaded newer image for gcr.io/google_containers/pause-amd64:3.0
aa603afaba05b18f8e53844f216d965fb2487feddd32937f56611499af24c0a1
Запуск контейнера с nginx

Далее мы запускаем контейнер nginx:

$ docker run -d --name nginx --net=container:pause  --pid=container:pause nginx
Проверяем сетевое пространство имен

Сначала давайте получим PID для обоих контейнеров pause и nginx:

$ ps -ef | grep pause
root      9377  9353  0 15:47 ?        00:00:00 /pause
$ ps -ef | grep nginx
root      9932  9910  0 15:53 ?        00:00:00 nginx: master process nginx -g daemon off;

Теперь проверим идентификатор сетевого пространства имен:

$ lsns | grep pause | grep net
4026532388 net         4  9377 root   /pause

Как видно, идентификатор сетевого пространства имен для контейнера pause равен 4026532388.

Теперь давайте узнаем идентификатор сетевого пространства имен для nginx :

$ readlink /proc/9932/task/9932/ns/net
net:[4026532388]

Таким образом, мы можем подтвердить, что контейнер nginx использует одно и то же сетевое пространство имен с контейнером pause.