контейнеры Kubernetes и команда отладки kubectl

Эфемерные контейнеры Kubernetes и команда отладки kubectl

Эфемерные контейнеры действительно хороши и очень нужны. Самый быстрый способ начать работу — это команда kubectl debug. Однако эту команду может быть сложно использовать, если вы не разбираетесь в контейнерах.

Потребность в эфемерных контейнерах

Как отлаживать рабочие нагрузки Kubernetes?

Встраивание полноценного пользовательского пространства Linux и инструментов отладки в образы производственных контейнеров делает их неэффективными и увеличивает поверхность атаки. Копирование инструментов отладки в работающие контейнеры по запросу с помощью kubectl cp является громоздким и не всегда возможным (для этого требуется исполняемый файл tar в целевом контейнере). Но даже когда в контейнере доступны инструменты отладки, kubectl exec может мало помочь, если этот контейнер находится в цикле сбоя.

Итак, какие еще варианты отладки мы предоставили неизменности спецификации Pod’s?

Конечно, есть отладка прямо с узла кластера, но доступ SSH к кластеру для многих из нас может быть запрещен. Ну, наверное, вариантов осталось не так много…

Если только мы не сможем немного ослабить требование неизменности Pod!

Что, если бы новые (несколько ограниченные) контейнеры можно было бы добавлять к уже работающему поду без его перезапуска? Поскольку поды — это просто группы полуслитых контейнеров, а изоляция между контейнерами в поде ослаблена, такой новый контейнер можно использовать для проверки других контейнеров в (действующем) поде независимо от их состояния и содержимого.

И вот как мы подходим к идее эфемерного контейнера — «контейнера особого типа, который временно запускается в существующем поде для выполнения инициированных пользователем действий, таких как устранение неполадок».

Не делает ли Pod изменяемым в отличие от декларативной природы Kubernetes? 🤔

  • Что мешает вам начать (ab) использовать эфемерные контейнеры для запуска рабочих нагрузок? Помимо здравого смысла, следующие ограничения:
  • Им не хватает гарантий на ресурсы или исполнение.
  • Они могут использовать только уже выделенные Pod ресурсы.
  • Они не могут быть перезапущены, и никакие порты не могут быть открыты.
  • Нет живости/готовности/и т.д. зонды могут быть настроены

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

Теперь, когда теория ясна, пришло время немного практики.

Могучая команда отладки kubectl

Kubernetes добавляет поддержку эфемерных контейнеров, расширяя спецификацию Pod новым атрибутом, называемым ephemeralContainers. Этот атрибут содержит список объектов, подобных контейнеру, и это один из немногих атрибутов спецификации пода, который можно изменить для уже созданного экземпляра пода.

Подробнее о новом API Ephemeral Containers 💡

Список ephemeralContainers можно добавить, установив PATCH для нового выделенного подресурса /pods/NAME/ephemeralcontainers. Это можно сделать только после создания пода.

Попытка создать Pod с непустым списком эфемерных контейнеров завершится ошибкой:

The Pod is invalid: spec.ephemeralContainers: Forbidden: cannot be set on create.

Интересно, что kubectl edit также нельзя использовать для добавления эфемерных контейнеров даже на лету:

Pod is invalid: spec: Forbidden: pod updates may not change fields other than `spec.containers[*].image`, `spec.initContainers[*].image`, `spec.activeDeadlineSeconds`, `spec.tolerations` (only additions to existing tolerations) or `spec.terminationGracePeriodSeconds` (allow it to be set to 1 if it was previously negative)

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

Добавление элементов в список ephemeralContainers приводит к тому, что новые контейнеры (попробуйте) запускаются в существующем поде. Спецификация EphemeralContainer имеет значительное количество свойств, которые необходимо настроить. В частности, как и обычные контейнеры, эфемерные контейнеры могут быть интерактивными и управляться PTY, так что последующее выполнение kubectl attach -it предоставит вам знакомый интерфейс оболочки.

Однако большинству пользователей Kubernetes не нужно напрямую работать с API эфемерных контейнеров. Благодаря мощной команде отладки kubectl, которая пытается абстрагировать эфемерные контейнеры от рабочего процесса отладки более высокого уровня. Давайте посмотрим, как можно использовать отладку kubectl!

Требуется кластер игровой площадки Kubernetes 1.23+ 🛠️

Для своих игровых площадок я предпочитаю одноразовые виртуальные машины. Вот Vagrantfile, чтобы быстро раскрутить коробку Debian с Docker. Перетащите его в новую папку, а затем запустите vagrant up; бродячий ssh ​​оттуда

Vagrant.configure("2") do |config|

config.vm.box = "debian/bullseye64"

config.vm.provider "virtualbox" do |vb|

vb.cpus = 4

vb.memory = "4096"

end

config.vm.provision "shell", inline: <<-SHELL

apt-get update

apt-get install -y curl git cmake vim

SHELL

config.vm.provision "docker"

end

Как только машина будет запущена, вы можете использовать arkade, чтобы быстро завершить настройку:

$ curl -sLS https://get.arkade.dev | sudo sh

$ arkade get kind kubectl

# Make sure it's Kubernetes 1.23+ $

kind create cluster

Использование отладки kubectl — первая (и неудачная) попытка

Для рабочей нагрузки подопытного кролика я буду использовать базовое развертывание с двумя контейнерами без дистрибутива (Python для основного приложения и Node.js для побочного.

$ kubectl apply -f - <<EOF

apiVersion: apps/v1

kind: Deployment

metadata:

name: slim

spec:

selector:

matchLabels:

app: slim

template:

metadata:

labels: app:

slim spec:

containers: - name: app

image: gcr.io/distroless/python3-debian11

command:

- python

- -m

- http.server

-'8080'

- name: sidecar

image: gcr.io/distroless/nodejs-debian11

command:

- /nodejs/bin/node

- -e

- 'setTimeout(() => console.log("done"), 999999)'

EOF

# Save the Pod name for future reference:

$ POD_NAME=$(kubectl get pods -l app=slim -ojsonpath='{.items[0].metadata.name}') -

Если или, вернее, когда такое развертывание начнет работать неправильно, команда kubectl exec мало чем поможет, поскольку в образах без дистрибутива обычно отсутствуют даже базовые инструменты исследования.

Доказательство вышеуказанного развертывания 🤓

# app container - no `bash`

$ kubectl exec -it -c app ${POD_NAME} -- bash

error: exec: "bash": executable file not found in $PATH: unknown

# ...and very limited `sh` (actually, it's aliased `dash`)

$ kubectl exec -it -c app ${POD_NAME} -- sh

$# ls

sh: 1: ls: not found

# sidecar container - no shell at all

$ kubectl exec -it -c sidecar ${POD_NAME} -- bash

error: exec: "bash": executable file not found in $PATH: unknown

$ kubectl exec -it -c sidecar ${POD_NAME} -- sh

error: exec: "sh": executable file not found in $PATH: unknown

Итак, давайте попробуем проверить поды, используя эфемерный контейнер:

$ kubectl debug -it --attach=false -c debugger --image=busybox ${POD_NAME}

Приведенная выше команда добавляет к целевому поду новый эфемерный контейнер под названием отладчик, который использует образ busybox:latest. Я намеренно сделал его интерактивным (-i) и управляемым PTY (-t), чтобы присоединение к нему позже обеспечило типичную интерактивную оболочку.

Вот влияние приведенной выше команды на спецификацию Pod:

$ kubectl get pod ${POD_NAME} \

-o jsonpath='{.spec.ephemeralContainers}' \

| python3 -m json.tool

[

{

"image": "busybox",

"imagePullPolicy": "Always",

"name": "debugger",

"resources": {},

"stdin": true,

"terminationMessagePath": "/dev/termination-log",

"terminationMessagePolicy": "File",

"tty": true

}

]

…и о его статусе:

$ kubectl get pod ${POD_NAME} \

-o jsonpath='{.status.ephemeralContainerStatuses}' \

| python3 -m json.tool

[

{

"containerID": "containerd://049d76...",

"image": "docker.io/library/busybox:latest",

"imageID": "docker.io/library/busybox@sha256:ebadf8...",

"lastState": {},

"name": "debugger",

"ready": false,

"restartCount": 0,

"state": {

👉"running": {

"startedAt": "2022-05-29T13:41:04Z"

}

}

}

]

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

$ kubectl attach -it -c debugger ${POD_NAME}

# Trying to access the `app` container (a python webserver).

$# wget -O - localhost:8080

Connecting to localhost:8080

Connecting to localhost:8080 (127.0.0.1:8080)

writing to stdout

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

<html>

...

</html>

На первый взгляд, работает.  Но на самом деле есть проблема:

$# ps auxf

PID USER TIME COMMAND

1 root 0:00 sh

14 root 0:00 ps auxf

Вывод ps внутри контейнера отладчика показывает только процессы этого контейнера… Итак, kubectl debug только что дал мне общее сетевое (и, возможно, ipc) пространство имен, вероятно, ту же родительскую контрольную группу, что и для других контейнеров Pod, и это довольно много! Звучит слишком ограниченно для плавной отладки.

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

Использование отладки kubectl для копирования целевого пода

Хотя нацеливание на конкретный контейнер в некорректно работающем поде, вероятно, было бы моим любимым вариантом, есть еще один режим отладки kubectl, о котором стоит рассказать. Иногда может быть хорошей идеей скопировать Pod перед началом отладки. К счастью, в команде отладки kubectl для этого есть флаг —copy-to <new-name>. Новый под не будет принадлежать исходной рабочей нагрузке и не унаследует метки исходного пода, поэтому он не будет нацелен на потенциальный объект службы перед рабочей нагрузкой. Это должно дать вам тихую копию для расследования! И поскольку это копия, для нее можно установить атрибут shareProcessNamespace, не вызывая нарушения работы производственных модулей. Однако для этого потребуется другой флаг —share-processes:

$ kubectl debug -it -c debugger --image=busybox \

--copy-to test-pod \

--share-processes \

${POD_NAME}

# All processes are here!

$# ps auxf

PID USER TIME COMMAND

1 65535 0:00 /pause

7 root 0:00 python -m http.server 8080

19 root 0:00 /nodejs/bin/node -e setTimeout(() => console.log("done"), 999999)

37 root 0:00 sh

49 root 0:00 ps auxf

Вот как будет выглядеть кластер игровой площадки после применения вышеуказанной команды к новому развертыванию:

$ kubectl get all

NAME                       READY STATUS RESTARTS AGE

pod/slim-79487d6484-h4rss 2/2 Running 0 63s

pod/test-pod 3/3 Running 0 20s

NAME                     TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 11d

NAME                     READY UP-TO-DATE AVAILABLE AGE

deployment.apps/slim 1/1 1 1 64s

NAME                      DESIRED CURRENT READY AGE

replicaset.apps/slim-79487d6484 1 1 1 64s

Бонус: эфемерные контейнеры без отладки kubectl

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

Почему вы можете захотеть создать эфемерный контейнер в обход команды отладки kubectl? Потому что это дает вам больше контроля над атрибутами контейнера. Например, с отладкой kubectl по-прежнему невозможно создать эфемерный контейнер с монтированием тома, в то время как на уровне API это возможно.

Использование kubectl debug -v 8 показало, что на уровне API эфемерные контейнеры добавляются в под с помощью PATCH его подресурса /ephemeralcontainers. В этой статье от 2019 года предлагается использовать kubectl replace —raw /api/v1/namespaces/<ns>/pods/<name>/ephemeralcontainers со специальными манифестами EphemeralContainers, но этот метод, похоже, не работает в Kubernetes 1.23+.

На самом деле, я не смог найти способ создать эфемерный контейнер с помощью какой-либо команды kubectl (кроме отладки, конечно). Итак, вот пример того, как это сделать, используя необработанный запрос API Kubernetes:

$ kubectl proxy

$ POD_NAME=$(kubectl get pods -l app=slim -o jsonpath='{.items[0].metadata.name}')

$ curl localhost:8001/api/v1/namespaces/default/pods/${POD_NAME}/ephemeralcontainers \

-XPATCH \

-H 'Content-Type: application/strategic-merge-patch+json' \

-d '

{

"spec":

"ephemeralContainers":

[

{

"name": "debugger",

"command": ["sh"],

"image": "busybox",

"targetContainerName": "app",

"stdin": true,

"tty": true,

"volumeMounts": [{

"mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",

"name": "kube-api-access-qnhvv",

"readOnly": true

}]

}

]

}

}'

Вывод

Использование тяжелых образов контейнеров для рабочих нагрузок Kubernetes неэффективно (мы все проходили через эти конвейеры CI/CD, выполнение которых занимает целую вечность) и небезопасно (чем больше у вас там всего, тем выше шансы столкнуться с неприятной уязвимостью). Таким образом, добавление инструментов отладки в плохо работающие поды на лету — это очень необходимая возможность, и эфемерные контейнеры Kubernetes отлично справляются с задачей доставки ее в наши кластеры.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Click to rate this post!
[Total: 0 Average: 0]

Leave a reply:

Your email address will not be published.