Learning Platform
Глоссарий Troubleshooting
Урок 16.03 · 22 мин
Средний
kubectl execport-forwardkubectl cpSPDYWebSocketstreamingkubelet

Exec, port-forward, cp: streaming через apiserver

kubectl exec и kubectl port-forward — это не просто «удобные команды». Это streaming-API Kubernetes, которое работает поверх длинной цепочки прокси. Понять, как оно устроено, важно по двум причинам: на CKAD ты будешь использовать kubectl exec -it десятки раз за экзамен, а в production-ах эти ручки часто становятся бутылочными горлышками (медленная сеть, тайм-ауты, SSL inspection ломает SPDY).


docker exec в контейнер: диагностика live container

kubectl exec: запустить команду внутри контейнера

# Одна команда
kubectl exec my-pod -- ls /app
kubectl exec my-pod -- cat /etc/config.yaml

# Интерактивный shell
kubectl exec -it my-pod -- sh
kubectl exec -it my-pod -- bash    # если в образе bash есть

# Multi-container
kubectl exec -it my-pod -c sidecar -- sh

# С env / working dir — НЕЛЬЗЯ задать через флаги kubectl
# Только через sh -c
kubectl exec my-pod -- sh -c 'cd /app && DEBUG=1 ./run.sh'

Флаги -i (stdin attached) и -t (TTY allocation) почти всегда идут вместе для интерактивных сессий. Без -t shell не показывает prompt и spinner не работает. Без -i ты не можешь печатать в shell.


Что происходит под капотом

Запрос kubectl exec -it my-pod -- sh проходит четыре проксирования:

Цепочка streaming exec от kubectl до процесса
kubectlДелает HTTPS-запрос POST на /api/v1/namespaces/<ns>/pods/<name>/exec с query params: command=sh, stdin=true, stdout=true, stderr=true, tty=true. Upgrade: SPDY/3.1 или (с v1.30) WebSocket. RBAC verb=create resource=pods/exec.
kube-apiserverПринимает upgrade-запрос. Использует subresource pods/exec. Проксирует TCP на kubelet ноды, где Pod запущен (узнаёт ноду из spec.nodeName, endpoint kubelet — :10250). apiserver не разбирает payload — пассивный bidirectional tunnel.
kubeletEndpoint /exec/<token> на :10250. kubelet вызывает CRI gRPC метод Exec на container runtime (containerd / CRI-O). Runtime даёт streaming URL, kubelet проксирует SPDY/WebSocket поток дальше.
container runtimecontainerd shim или CRI-O conmon. Использует runc / crun, чтобы войти в namespaces целевого контейнера (mnt, pid, net, ipc, uts) и запустить процесс. Streaming поток связан с stdin/stdout/stderr нового процесса.
процесс shЗапускается ВНУТРИ namespaces контейнера, но это НОВЫЙ процесс, не существующий sh. Если контейнер distroless — sh нет, kubectl exec получит exec failed: container_linux.go: ... no such file or directory.
bidirectional streamStream имеет минимум три канала: stdin (client→process), stdout (process→client), stderr (process→client). При TTY — ещё канал resize для propagation SIGWINCH. SPDY использует multiplex поверх одного TCP.
NOTE

Раньше Kubernetes использовал SPDY/3.1 (Google-овый предшественник HTTP/2). Это нестандартный протокол, и многие SSL-проксики его ломали. KEP-4006 (translation между SPDY и WebSocket для exec/attach): beta в v1.30, GA в v1.31WebSocketStreaming feature gate включён по умолчанию. Современный kubectl сначала пробует WebSocket, при отсутствии поддержки — fallback на SPDY.


port-forward: локальный порт → Pod-порт

# Через Pod
kubectl port-forward pod/my-pod 8080:80

# Через Deployment / ReplicaSet — pf на один из его Pods
kubectl port-forward deployment/web 8080:80

# Через Service — pf на ОДИН Pod (выбранный из endpoints, не balanced!)
kubectl port-forward svc/web 8080:80

# Любой свободный локальный порт
kubectl port-forward pod/my-pod :80
# kubectl выведет "Forwarding from 127.0.0.1:54321 -> 80"

# Bind на 0.0.0.0 (доступ из локальной сети)
kubectl port-forward --address 0.0.0.0 pod/my-pod 8080:80
WARNING

kubectl port-forward svc/web НЕ балансирует через Service-VIP. Он выбирает один Pod из endpoints и форвардит на него напрямую. Если этот Pod упадёт — pf оборвётся, новый не выберется автоматически. Для load-balanced доступа — отдельный LoadBalancer/NodePort или kubectl proxy для cluster URL access.

Под капотом port-forward

Использует тот же subresource pattern: POST /api/v1/namespaces/<ns>/pods/<name>/portforward. Это streaming endpoint:

  • kubectl открывает SPDY/WebSocket к apiserver.
  • apiserver проксирует на kubelet.
  • kubelet через runtime входит в network namespace Pod-а и устанавливает прокси на target port внутри контейнера.
  • Исторически использовался socat, теперь — встроенный proxy в kubelet через nsenter-like механизм.
  • Каждое новое TCP-соединение на локальный порт — отдельный stream в SPDY/WebSocket.

Не путать с kubectl proxy — это совсем другая штука: локальный HTTP-прокси на apiserver, удобный для доступа к Services через cluster URLs (/api/v1/namespaces/<ns>/services/<name>:port/proxy).


kubectl cp: копирование файлов

# Из локального в Pod
kubectl cp ./local-file.txt my-pod:/app/file.txt

# Из Pod на локальный
kubectl cp my-pod:/app/logs.txt ./logs.txt

# С указанием container
kubectl cp ./config.yaml my-pod:/etc/config.yaml -c app

# Целая директория
kubectl cp my-pod:/var/log ./pod-logs

Под капотом — это kubectl exec + tar:

  • При копировании в Pod: kubectl делает tar cf - <local-files> и пишет в stdin exec my-pod -- tar xf - -C <target>.
  • При копировании из Pod: kubectl делает exec my-pod -- tar cf - <remote-files> и читает stdout, разворачивая через tar xf - локально.

Требование: в контейнере должен быть бинарь tar. На distroless / scratch images его нет — kubectl cp упадёт с error: cannot exec into container without 'tar' binary. Решение: ephemeral container с busybox (см. следующий урок).


CKAD-killer: kubectl exec -it для inspection

На экзамене типовой workflow:

# 1. Залезаем внутрь Pod
kubectl exec -it my-pod -- sh

# 2. Внутри: env, конфиги, монтированные volumes
$ env | grep DB_
$ cat /etc/config/app.yaml
$ ls /mnt/data
$ wget -qO- localhost:8080/health

# 3. Network debug изнутри Pod
$ nslookup my-service
$ wget -qO- http://my-service.production:80/api
$ cat /etc/resolv.conf

# 4. Проверка прав
$ id
$ ls -la /var/run/secrets/kubernetes.io/serviceaccount
TIP

Если в Pod-е sh или bash нет (distroless, scratch, многие production-images) — kubectl exec бесполезен. Это главный кейс для kubectl debug (следующий урок), который инжектит ephemeral container с любым shell-ом, не трогая основной контейнер.


Killer-моменты

  • kubectl exec запускает НОВЫЙ процесс в namespaces контейнера, не присоединяется к существующему. Поэтому переменные окружения, заданные в Dockerfile / Pod spec — да, видны (они в namespace), но set или cd от main процесса — нет.
  • exec -it без -- часто работает, но -- — это стандарт для разделения kubectl-флагов и команды. Если команда имеет свои флаги (kubectl exec my-pod -- ls -la /tmp), без -- ls -la попадёт в kubectl.
  • port-forward svc/<name> выбирает один Pod, не балансирует. Это не замена Service.
  • kubectl cp требует tar в контейнере. На distroless — не работает, нужен ephemeral container.
  • WebSocket fallback с v1.31 — современные kubectl + apiserver больше не ломаются на корпоративных SPDY-стерильных прокси.
  • RBAC для exec: verb=create, resource=pods/exec. Для read-only role giving get pods НЕ даёт права на exec — это разные subresources.

Проверка знанийKnowledge check
kubectl exec -it my-pod -- bash возвращает 'error: cannot exec: no such file or directory'. Что произошло и как чинить?
ОтветAnswer
В контейнере нет /bin/bash. Скорее всего, образ построен на distroless, scratch, alpine (где bash отсутствует — есть только sh), или busybox. Решения по порядку: (1) Попробовать sh: kubectl exec -it my-pod -- sh. (2) Если и sh нет — это distroless. Использовать kubectl debug: kubectl debug -it my-pod --image=busybox --target=<container>. Это инжектирует ephemeral container с busybox в shared PID namespace — можно видеть процессы целевого контейнера, диагностировать без модификации production image.
Проверка знанийKnowledge check
kubectl port-forward svc/api 8080:80 — что выбирается как target и что произойдёт, если выбранный Pod упадёт?
ОтветAnswer
kubectl выбирает ОДИН Pod из endpoints Service (не балансирует через VIP, не использует kube-proxy). Выбор обычно — первый Pod из endpoints. Если он упадёт, port-forward соединение прервётся: kubectl выдаст error и тебе нужно перезапустить команду — она выберет другой Pod из обновлённых endpoints. Это значит, что pf не для load-test или production — это debug-tool. Для balanced доступа: kubectl proxy (через apiserver), либо expose Service через NodePort / LoadBalancer / Ingress.
Проверка знанийKnowledge check
kubectl cp my-pod:/var/log/app.log ./local.log падает с error: cannot exec into container without 'tar' binary. Какие есть варианты решения?
ОтветAnswer
kubectl cp под капотом — это exec + tar. Без tar в контейнере не работает. Варианты: (1) Скопировать через временный helper: kubectl debug --image=busybox --target=<c> + shareProcessNamespace, потом busybox-инструменты могут читать /proc/<pid>/root/... — но это сложно. (2) Если файл на shared emptyDir/PV — смонтировать тот же volume в helper Pod и kubectl cp оттуда. (3) Проще всего — pipe: kubectl exec my-pod -- cat /var/log/app.log > local.log (работает без tar, если cat есть). (4) Если контейнер вообще не имеет shell — ephemeral container с inotify-инструментами и shared volume.
Проверка знанийKnowledge check
Что такое --address 0.0.0.0 у kubectl port-forward и в чём риск использования?
ОтветAnswer
--address 0.0.0.0 говорит kubectl bind-ить локальный listening порт не на 127.0.0.1 (default — только localhost), а на все интерфейсы машины. Это значит, что любой в той же сети (Wi-Fi, корпоративная LAN) может подключиться к этому порту и иметь доступ к Pod. Это полностью обходит cluster-level NetworkPolicy и Authentication — потому что pf аутентифицируется по твоему kubeconfig, а порт открыт всем. Использовать только в изолированных dev-средах. Production-доступ — через Ingress / LoadBalancer с правильной auth.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. kubectl exec -it my-pod -- sh — какой протокол использует поток между kubectl и apiserver для bidirectional streaming?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5