Pods

Articles

PODs

Pod - наименьшая сущность в k8s. Обычно, pod = контейнер по принципу 1:1. Однако, можно несколько контейнеров разместить в 1 pod, при условии, что они функционально разные. Обычно это главный контейнер приложения и вспомогательные контейнеры, которые с ним связаны.

В обычном Docker, если развернуть множество копий “контейнер приложения” + “вспомогательный контейнер”, то нужно будет иметь карту взаимосвязей между ними всеми. Более того, в случае выхода из строя контейнера с приложением, нужно будет вручную удалять сопутствующий вспомогательный контейнер. От этого всего избавляют pod-ы, в рамках которых всё размещается, обеспечивается внутренняя связность, и далее k8s размножает готовые копии pod-ов в рамках кластера.

Pod-ы добавляют функционал к контейнерам:

  • Labels and annotations
  • Restart policies
  • Probes (startup probes, readiness probes, liveness probes, and potentially more)
  • Affinity and anti-affinity rules
  • Termination control
  • Security policies
  • Resource requests and limits

Работа с pod-ами ведётся с помощью API или инструмента kubectl:

kubectl run nginx --image nginx # образ nginx будет скачан с DockerHub
kubectl get pods # список всех pod-ов и их статусов
kubectl get pods --selector app=App1 # отфильтровать вывод по заданному label

Создание Pod через файл YAML

Создадим pod-definition.yml:

apiVersion: v1
kind: Pod       
metadata: 
  name: myapp-pod
  labels:
    app: myapp
    type: front-end
spec:
  containers:
    - name: nginx-container
      image: nginx

Далее создаём pod командой:

kubectl create -f pod-definition.yml
kubectl get pods

Посмотреть доступные поля, подробную информацию о поле у pod:

kubectl explain pods --recursive
kubectl explain pod.spec.restartPolicy

Посмотреть конкретное поле у всех pod, например, образ image, из которого он сделан:

kubectl get pods -o jsonpath={.items[*].spec.containers[*].image}

Можно у работающего Pod получить спецификацию в YAML, из которой он сделан:

kubectl get pod <имя pod> -o yaml > pod-definition.yaml

Удалить Pod

kubectl delete pod <имя Pod> --now 

Зайти внутрь Pod и выполнить команды:

kubectl exec -it <имя pod> -- /bin/sh

Обновить Pod

В конфигурацию pod можно добавить период обновления (например, 30 секунд) и установить “imagePullPolicy: “Always”. Удалить Pod с помощью kubectl delete pod pod_name. Новый контейнер будет создан на последней версии образа, после чего старый контейнер будет удалён.

spec:
  terminationGracePeriodSeconds: 30
  containers:
  - name: my_container
    image: my_image:latest
    imagePullPolicy: "Always"

Есть вариант “дёргать” за Deployment, вызывая тем самым обновление:

kubectl patch deployment <имя deployment> -p \
  '{"spec":{"template":{"spec":{"terminationGracePeriodSeconds":31}}}}'

Выполнение задач в Pod

Если необходимо, чтобы Pod поработал и выключился, без перезапуска, то необходимо поменять его restartPolicy, которая по умолчанию стоит в Always - то есть перезапуск всегда по завершении работы.

spec:
  containers:
  - name: my_container
    image: my_image:latest
  restartPolicy: Never # ещё вариант OnFailure

Императивные команды

В отличие от декларативных, такие команды позволяют быстро решить однократную задачу.

kubectl run nginx --image=nginx --dry-run=client -o yaml # --dry-run=client - не создаёт объект, сообщает о возможности его создания

kubectl run httpd --image=httpd:alpine --port=80 --expose=true # создать Pod из образа httpd:alpine и к нему сразу создать ClusterIP с публикацией порта

Multi-Container PODs

Несколько контейнеров в 1 POD делят один адрес IP (между собой они общаются через адаптер localhost), хранилище. Есть несколько типовых сценариев:

  • Sidecar pattern - самый популярный случай, один контейнер отрабатывает задачу (например, выгрузки данных на веб-сайт), а другой решает вспомогательную задачу (например, синхронизация данных для последующей выгрузки);
  • Init pattern - перед запуском контейнера с основным ПО сначала стартует вспомогательный контейнер, который производит настройку окружения;
  • Adapter pattern - ПО в основном контейнере обрабатывает данные, а вспомогательный контейнер передаёт эти данные в другое приложение в понятном ему формате. Например, система SIEM не понимает формат логов приложения, и вспомогательный модуль парсит и транслирует логи в понятный для SIEM формат;
  • Ambassador pattern - ПО в основном контейнере отрабатывает задачи, а вспомогательный контейнер вызывает через API внешние системы, чтобы собрать с них данные для обработки, либо передать данные в эти системы.

PODы стартуют атомарно - только после успешного старта всех контейнеров POD считается запущенным. Частичный запуск не допускается. POD целиком всеми контейнерами размещается на одной ноде worker.

apiVersion: v1
kind: Pod        
metadata: 
  name: myapp-pod
  labels:
    app: myapp
spec:
  containers:
    - name: nginx-container
      image: nginx
      ports:
        - containerPort: 8080
    - name: log-agent
      image: log-agent

InitContainer - не живёт постоянно, а выполняется ДО загрузки остальных контейнеров в Pod, поэтому его инициализация - в отдельной секции:

apiVersion: v1
kind: Pod        
metadata: 
  name: myapp-pod
  labels:
    app: myapp
spec:
  containers:
    - name: myapp-container
      image: nginx
      ports:
        - containerPort: 8080
  initContainers:
    - name: init-service
      image: busybox
      command: [ 'sh', '-c', 'git clone <some repo to be used by app>' ]

Если таких InitContainer несколько, они будут выполняться последовательно один за другим. Если любой InitContainer не сможет выполниться успешно, k8s будет перезапускать Pod, пока InitContainer успешно не завершится.

Ручное распределение (manual scheduling)

Если в кластере нет распределения, можно указать вручную параметр nodeName:

apiVersion: v1
kind: Pod       
metadata: 
  name: myapp-pod
spec:
  containers:
    - name: nginx-container
      image: nginx
  nodeName: node01

Без указания этого параметра в отсутствии распределения Pod будет висеть как Pending. K8s не даст указать этот параметр на лету, после добавления Pod надо заменить kubectl replace --force -f nginx-pod.yaml

Subsections of Pods

Readiness Probes

Readiness Probes

Определение, что ПО в контейнер действительно запустилось успешно и готово принимать данные пользователей, можно провести по-разному, добавив в манифест раздел spec -> containers поле readinessProbe.

  • Для проверки HTTP сервера:

    readinessProbe:
      httpGet:
        path: /api/ready
        port: 8080
      initialDelaySeconds: 10 # предусматриваем 10 сек задержку на старте
      periodSeconds: 5 # повторяем проверку спустя 5 секунд
      failureThreshold: 8 # повторяем проверку 8 раз (по умолчанию 3)
    
  • Для проверки открытого порта (например, у СУБД):

    readinessProbe:
      tcpSocket:
        port: 3306
    
  • Для проверки с помощью своей команды:

    readinessProbe:
      exec:
        command: 
          - cat
          - /app/is_ready
    

Liveness Probes

Периодическое определение, работает ли ПО в контейнере - для случаев, когда падение ПО не приводит к его вылету и закрытию контейнера.

  • Для проверки HTTP сервера:

    livenessProbe:
      httpGet:
        path: /api/health_status
        port: 8080
      initialDelaySeconds: 10 # предусматриваем 10 сек задержку на старте
      periodSeconds: 5 # повторяем проверку спустя 5 секунд
      failureThreshold: 8 # повторяем проверку 8 раз (по умолчанию 3)
    
  • Для проверки открытого порта (например, у СУБД):

    livenessProbe:
      tcpSocket:
        port: 3306
    
  • Для проверки с помощью своей команды:

    livenessProbe:
      exec:
        command: [ "cat", "/app/is_working" ]
    

Container Logging

Для получения логов с Pod:

kubectl logs {-f} <pod name> <container name> # -f = tail

kubectl logs -f myapp logger # пример, в случае нескольких контейнеров в Pod выбран контейнер logger 

Resources

Requests

Запрос контейнеров на гарантированные ресурсы.

apiVersion: v1
kind: Pod
metadata:
  name: myapp
  labels:
    name: myapp
spec:
  containers:
  - name: mywebapp
    image: nginx
    resources:
      requests:
        memory: "4Gi" # 4 гибибайта
        cpu: 2 # минималка 0.1 CPU

Limits

Указание ограничений. При переходе лимита по CPU скорость для Pod замедляется (throttling). При переходе лимита по RAM происходит убийство Pod с ошибкой OOM Error (Out-Of-Memory).

apiVersion: v1
kind: Pod
metadata:
  name: myapp
  labels:
    name: myapp
spec:
  containers:
  - name: mywebapp
    image: nginx
    resources:
      requests:
        memory: "2Gi"
        cpu: 2
      limits:
        memory: "4Gi"
        cpu: 2

Security Contexts

Security Contexts

В описании Pod можно указать ID пользователя, который запускает контейнеры, а также описать его возможности (capabilities).

  • Если контекст безопасности определён на уровне Pod, он действует для всех входящих в него контейнеров;
  • Если контекст безопасности определён на уровне Pod и на уровне контейнера, то настройки контейнера приоритетны перед настройками Pod.

Уровень Pod:

apiVersion: v1
kind: Pod
metadata:
  name: web-pod
spec:
  securityContext:
    runAsUser: 1001
  containers:
    - name: ubuntu
      image: ubuntu

Уровень контейнера:

apiVersion: v1
kind: Pod
metadata:
  name: web-pod
spec:
  containers:
    - name: ubuntu
      image: ubuntu
      securityContext:
        runAsUser: 1001
        capabilities:   
          add: ["MAC_ADMIN"]
# возможности можно определить ТОЛЬКО на уровне контейнера

Selectors and Affinity

Node Selectors

Добавить пометки к node можно командой:

kubectl label nodes <node-name> <label-key>=<label-value>

kubectl label nodes node-01 size=Large # пример

После этого в описании Pod можно указать Node Selector:

apiVersion: v1
kind: Pod
metadata: 
  name: myapp
spec:
  containers:
  - name: data-processor
    image:: data-processor
  nodeSelector:
    size: Large

Node Selectors работают по принципу 1:1 совпадения метки Node и Pod. Для более сложных сценариев применяют Node Affinity.

Node Affinity

Для создание свойств Node Affinity нужно поменять свойства в манифесте Pod:

apiVersion: v1
kind: Pod
metadata: 
  name: myapp
spec:
  containers:
  - name: data-processor
    image:: data-processor
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: size
            operator: In # условие, может быть наоборот NotIn, или Exists - есть ли вообще такой label, необязательно имеющий значение?
            values:
            - Large # условие действует на любое из значений списка
            - Medium

В случае, если нужные labels отсутствуют на Nodes кластера, есть 2 типа поведения, которое задаётся свойством Pod:

  • requiredDuringSchedulingIgnoredDuringExecution - если Nodes с нужными labels нет, вообще не размещать данный Pod на кластере;
  • preferredDuringSchedulingIgnoredDuringExecution - если Nodes с нужными labels нет, всё равно разместить данный Pod где-нибудь на кластере.

Если Pod уже запущен на Node в момент, когда добавили label, то в версии 1.27 ничего не произойдёт в обоих случаях. В плане добавить третий тип поведения:

  • requiredDuringSchedulingRequiredDuringExecution - если во время работы Pod произойдёт изменение affinity - удалить Pod с Node.

Taints & Tolerations

Для распределения Pods по Nodes применяется сочетание покраски (taint) и восприимчивости (toleration) к ней.

Taints

Покраска Node говорит kube-scheduler, что есть 1 из 3 эффектов:

  • NoSchedule - не назначать сюда Pods без toleration;
  • PreferNo Schedule - назначать Pods без toleration с наименьшим приоритетом, если больше некуда;
  • NoExecute - не назначать сюда Pods без toleration, уже имеющиеся тут Pods удалить и перенести куда-то ещё.

Покраска node:

kubectl taint nodes <имя node> key=value:effect

kubectl taint nodes node01 app=myapp:NoSchedule # пример
kubectl taint nodes node01 app=myapp:NoSchedule- # минус в конце снимает покрас

Tolerations

Поменять восприимчивость Pod к покраске:

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
spec:
  containers:
  - name: nginx-controller
    image: nginx
  tolerations:
  - key: "app"
    operator: "Equal"
    value: "blue"
    effect: "NoSchedule"