Руководство по поиску и устранению неисправностей K8s

ПримечаниеПримечание. Полная ментальная карта Устранение неполадок K8 доступна по адресу: «Устранение неполадок K8s ментальная карта.
Недавно наш кластер K8s столкнулся с некоторыми проблемами процесса Zombie. Поды не могут быть удалены или созданы, и даже не могут быть подключены к узлу по SSH. Мы нашли множество неработающих процессов во многих подах. Симптом в Pod выглядит так:
CPU: 0% usr 0% sys 0% nic 98% idle 0% io 0% irq 0% sirq
Load average: 0.02 0.39 0.46 4/7217 25257
PID PPID USER STAT VSZ %VSZ CPU %CPU COMMAND
25228 25085 root R 1988 0% 14 0% top
28 1 root S 21.9g 66% 14 0% /usr/bin/java -Dspring.profiles.active=PRD
1 0 root S 786m 2% 9 0% ./tools/linux/env-tools
25085 0 root S 1672 0% 5 0% sh
23278 1 root Z 0 0% 10 0% [cat]
157 1 root Z 0 0% 14 0% [ssl_client]
177 1 root Z 0 0% 10 0% [ssl_client]
196 1 root Z 0 0% 0 0% [ssl_client]
...
Если вы подсчитаете ps -ef | grep defunct | wc -l, вы увидите, что количество процессов defunct огромно:
$ ps -ef | grep defunct | wc -l 6533
Итак, как решить проблему такого рода? Прежде всего, давайте разберемся, что такое процесс Zombie.
Что такое зомби-процесс
Короче говоря, процесс azombie (несуществующий) — это процесс, который завершил выполнение, но его родительский процесс еще не прочитал код завершения этого процесса. Таким образом, даже процесс завершен, но он остается в таблице процессов. Обычно зомби-процесс не использует много системных ресурсов, но все же занимает запись в таблице процессов, которая по-прежнему использует часть памяти. Это может быть опасно, так как они могут довольно быстро заполнить таблицу процессов.
Если вы хотите узнать больше о том, что такое зомби-процесс, ознакомьтесь с моей статьей: «DevOps в Linux — зомби-процесс».
Приостановить контейнер
При создании пода процесс kubelet сначала вызывает интерфейс CRI RuntimeService.RunPodSandbox, чтобы создать среду песочницы и настроить базовую операционную среду, например сеть. Как только Pod Sandbox установлен, kubelet может создавать в нем пользовательские контейнеры. Когда придет время удалить под, kubelet сначала удалит песочницу пода, а затем остановит все контейнеры внутри.
Контейнер pause — это контейнер, который существует в каждом модуле, он похож на шаблон или родительский контейнер, от которого все новые контейнеры в модуле наследуют пространства имен. Контейнер pause запускается, а затем «засыпает».
Контейнер pause выполняет две основные функции:
- Он служит основой для совместного использования пространства имен Linux в модуле.
- Он служит PID 1 для каждого модуля и собирает процессы-зомби (с включенным общим пространством имен PID).
Исходный код контейнера паузы выглядит так (https://github.com/kubernetes-csi/driver-registrar/blob/master/vendor/k8s.io/kubernetes/build/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 в основном создает отдельное пространство имен для пода. Пока у нас запущен контейнер pause и когда мы запускаем реальный контейнер приложения, он присоединится к следующим пространствам имен контейнера pause:
- Пространство сетевых имен
- Пространство имен IPC
- Пространство имен PID

Такое совместное использование пространства имен имеет следующие преимущества:
- Позволяет контейнерам взаимодействовать напрямую с помощью локального хоста.
- Позволяет контейнерам совместно использовать свое пространство имен межпроцессного взаимодействия (IPC) с другими контейнерами, чтобы они могли напрямую взаимодействовать с другими контейнерами через общую память.
- Позволяет контейнерам совместно использовать пространство имен своего идентификатора процесса (PID) с другими контейнерами.
Обработка процессов зомби
Сбор зомби выполняется контейнером pause только в том случае, если у вас включен общий доступ к пространству имен PID. В K8s v1.8 и выше он отключен по умолчанию, если только он не включен флагом kubelet. Если совместное использование пространства имен PID не включено, то каждый контейнер в модуле K8s будет иметь свой собственный PID 1, и каждый из них должен будет сам пожинать процессы-зомби.
Если совместное использование пространства имен PID включено, PID процесса /pause будет равен 1, поэтому он может вызывать системный вызов wait после завершения дочернего процесса.
Демо
Давайте создадим образ Docker, который будет генерировать процесс zombie.
Файл Docker:
FROM python:bullseye COPY zombie.py /root/ RUN chmod +x /root/zombie.py ENTRYPOINT ["python", "/root/zombie.py"]
zombie.py:
import os
import subprocess
pid = os.fork()
if pid == 0: # child
pid2 = os.fork()
if pid2 != 0: # parent
print('The zombie pid will be: {}'.format(pid2))
else: # parent
os.waitpid(pid, 0)
subprocess.check_call(('ps', 'xawuf'))
Теперь создадим образ:
$ docker build -t <registry>/zombie:v0.0.1 . Sending build context to Docker daemon 4.096kB Step 1/4 : FROM python:bullseye ---> 63490c269128 Step 2/4 : COPY zombie.py /root/ ---> bd4184c3ad49 ... Successfully built 295088e5c98a
Развернуть с отключенным shareProcessNamespace:
zombie_pod.yml:
apiVersion: v1
kind: Pod
metadata:
name: zombie
spec:
#shareProcessNamespace: true
containers:
- name: zombie
image: <registry>/zombie:v0.0.1
imagePullPolicy: Always
Теперь разверните в кластере K8s:
$ kubectl create -f zombie_pod.yml pod/zombie created
Проверить процесс зума
$ kubectl exec -it zombie -- top
top - 19:51:52 up 66 days, 5:11, 0 users, load average: 0.55, 0.91, 0.58
Tasks: 4 total, 1 running, 2 sleeping, 0 stopped, 1 zombie
%Cpu(s): 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
MiB Mem : 7847.7 total, 4136.6 free, 759.7 used, 2951.4 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 6765.3 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 17764 14036 5636 S 0.0 0.2 0:00.15 python
7 root 20 0 17764 10992 2580 S 0.0 0.1 0:00.00 python
8 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 python
9 root 20 0 8940 3788 3312 R 0.0 0.0 0:00.00 top
$ kubectl get po
NAME READY STATUS RESTARTS AGE
zombie 0/1 CrashLoopBackOff 1 4s
Обратите внимание, что PID 8 — это процесс-зомби, а pod находится в состоянии CrashLoopBackOff. Процесс-зомби (несуществующий) не был собран, так как процесс python PID 1 (его родитель) не играл эту роль. Давайте также проверим журналы:
$ kubectl logs zombie The zombie pid will be: 9 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.1 17748 14336 ? Ss 20:02 0:00 python /root/zombie.py root 9 0.0 0.0 0 0 ? Z 20:02 0:00 [python] <defunct> root 10 0.0 0.0 8652 3400 ? R 20:02 0:00 ps xawuf
Вы можете видеть, что есть процесс <defunct>.
Развертывание с включенным shareProcessNamespace:
zombie_pod.yml:
apiVersion: v1
kind: Pod
metadata:
name: zombie
spec:
shareProcessNamespace: true
containers:
- name: zombie
image: <registry>/zombie:v0.0.1
imagePullPolicy: Always
imagePullSecrets:
- name: regcredartifactory
Разверните снова и проверьте, есть ли зомби-процесс:
$ kubectl exec -it zombie -- top
top - 19:55:31 up 66 days, 5:14, 0 users, load average: 0.19, 0.65, 0.55
Tasks: 5 total, 1 running, 3 sleeping, 0 stopped, 1 zombie
%Cpu(s): 25.0 us, 25.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 50.0 st
MiB Mem : 7847.7 total, 4137.0 free, 759.2 used, 2951.6 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 6765.8 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 972 4 0 S 0.0 0.0 0:00.18 pause
8 root 20 0 17764 13908 5504 S 0.0 0.2 0:00.12 python
15 root 20 0 17764 11048 2632 S 0.0 0.1 0:00.00 python
17 root 20 0 8940 3716 3240 R 0.0 0.0 0:00.01 top
$ kubectl get po
NAME READY STATUS RESTARTS AGE
zombie 0/1 Completed 1 3s
Обратите внимание, что модуль zombie теперь находится в статусе Completed. Давайте проверим журналы
$ kubectl logs zombie The zombie pid will be: 150 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 143 0.2 0.1 17764 14492 ? Ss 19:46 0:00 python /root/zombie.py root 151 0.0 0.0 8652 3336 ? R 19:47 0:00 \_ ps xawuf root 1 0.0 0.0 972 4 ? Ss 18:51 0:00 /pause
Мы видим, что зомби-процесс был получен без указания процесса инициализации или добавления его в контейнер. Процесс инициализации теперь представляет собой процесс pause (на самом деле это контейнер).
Заключение

Поды совместно используют много ресурсов, поэтому имеет смысл использовать совместное пространство имен процессов. Однако некоторые контейнеры могут быть изолированы от других, поэтому важно понимать различия:
- Процесс контейнера больше не имеет PID 1. Некоторые контейнеры отказываются запускаться без PID 1 (например, контейнеры, использующие systemd) или запускают такие команды, как kill -HUP 1, чтобы сигнализировать процессу контейнера. В модулях с общим пространством имен процессов kill -HUP 1 будет сигнализировать о песочнице модуля (/pause в приведенном выше примере).
- Процессы видны другим контейнерам в модуле. Сюда входит вся информация, видимая в /proc, например, пароли, которые были переданы в качестве аргументов или переменных среды. Они защищены только обычными разрешениями Unix.
- Файловые системы контейнеров видны другим контейнерам в поде по ссылке /proc/$pid/root. Это упрощает отладку, но также означает, что секреты файловой системы защищены только разрешениями файловой системы.
Ссылка: