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 проходит четыре проксирования:
Раньше Kubernetes использовал SPDY/3.1 (Google-овый предшественник HTTP/2). Это нестандартный протокол, и многие SSL-проксики его ломали. KEP-4006 (translation между SPDY и WebSocket для exec/attach): beta в v1.30, GA в v1.31 — WebSocketStreaming 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
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>и пишет в stdinexec 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
Если в 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 givingget podsНЕ даёт права на exec — это разные subresources.