Kubernetes

Статьи в разделе

Master Node

Мастер нода представляет службы управления (Control Plane)

  • Кластер Etcd ведёт запись всех нод, размещённых на них контейнерах, запись иных данных - это СУБД для Kubernetes, заточенная на согласованности данных и их доступности;
  • Kube-scheduler: команды создания и переноса контейнеров на worker nodes. Считает число ресурсов на нодах и подбирает размещение pods на нодах в соответствии с профилем потребляемых ресурсов;
  • Kube API Server: служба обмена сообщениями в кластере k8s. Аутентификация отправителя сообщения, валидирует отправителя сообщений, регистрирует сообщения по интерфейсу API в базу Etcd; Это единственный компонент, который общается напрямую с Etcd;
  • Kube Controller Manager: содержит службы контроля Node Controller (следит за доступностью нод), Replication Controller (отслеживание распространения копий контейнеров в рамках группы репликации по нодам).

Worker Nodes

Ноды-работники размещают у себя контейнеры через Container Runtime Interface (CRI) с поддержкой containerd (через него Docker, с версии k8s 1.24+) и Rocket:

  • Для приёма команд и передачи статистики по рабочей ноде используется kubelet, служба управления нодой;
Note

Kubeadm не устанавливает автоматически Kubelet-ы. Они всегда ставятся вручную на worker nodes.

  • Для связи с нодой применяется служба Kube-proxy. Создаёт правила проброса потоков данных от служб к pods, на которых они размещены. Один из способов - создание правил iptables;

crictl

Проверка и решение проблем с рабочими нодами. В отличие от утилит Docker, crictl понимает pods.

crictl images # список образов
circtl ps -a # список контейнеров
crictl exec -i -t 288023742....aaabb392849 ls # запуск команды в контейнере
crictl logs 288023742....aaabb392849 # посмотреть лог контейнера
crictl pods 

IDE

Для написания YAML-файлов хорошо подходит редактор с плагином, понимающим k8s. Пример: VSCode + Red Hat YAML plugin

В свойствах плагина найти пункт Yaml: Schemas -> Edit in settings.json Добавить в конфиг:

{
    "yaml.schemas": {
        
    "kubernetes": "*.yaml"
    },
    "redhat.telemetry.enabled": true
}

Это позволит все файлы YAML редактировать с учётом полей, принятых для k8s.

Subsections of Kubernetes

Controller Manager

Node Controller

Отслеживает состояние нод (через kube-apiserver):

  • Отслеживает статус;
  • Реагирует на проблемы;
  • Node Monitor Period = 5 секунд на опрос всех нод через kube-apiserver;
  • Node Monitor Grace Period = 40 секунд время ожидания, если нода не отвечает - отметить её как Unreachable;
  • POD Eviction Timeout = 5 минут на ожидание возврата ноды, иначе перенос pod с этой ноды на другие ноды.

Replication Controller

Отслеживает множества replica sets, и что нужное число pods всегда доступны в каждом replica set.

Другие подсистемы

  • Deployment Controller
  • Namespace Controller
  • Job Controller
  • PV-Protection Controller
  • Endpoint Controller
  • CronJob
  • Service Account COntroller
  • Stateful-Set
  • Replica set
  • и т.д.

ETCD

Особенности

  • Служба Etcd слушает на TCP2379;
  • Клиент - etcdctl;
  •  По умолчанию, etcdctl использует API v2. Переключать на API v3 нужно явно. Команды в API v2 и v3 отличаются:
# ETCD API v2.x
./etcdctl set key1 value1
./etcdctl get key1
./etcdctl --version # важно увидеть версию API (2), от этого команды зависят

# ETCD API v3.x
./etcdctl version # в API 3.x параметр version => команда, набирать без "--"
./etcdctl put key1 value1
./etcdctl get key1
./etcdctl get / --prefix -keys-only # вывести ключи в БД, без значений 
export ETCDCTL_API=3 # поменять версию API на 3.х

Для аутентификации клиента ETCDCTL на ETCD API Server нужно указывать также сертификат:

--cacert /etc/kubernetes/pki/etcd/ca.crt     
--cert /etc/kubernetes/pki/etcd/server.crt     
--key /etc/kubernetes/pki/etcd/server.key

Отсюда полноценная команда-запрос клиента к ETCD будет выглядет вот так:

kubectl exec etcd-master -n kube-system -- sh -c "ETCDCTL_API=3 etcdctl get / --prefix --keys-only --limit=10 --cacert /etc/kubernetes/pki/etcd/ca.crt --cert /etc/kubernetes/pki/etcd/server.crt  --key /etc/kubernetes/pki/etcd/server.key"

Ingress

Ingress Controller

Объект служит в роли прокси и балансировщика L7.

Создание минимального ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata: 
  name: minimal-ingress-controller
spec:
  replicas: 1
  selector:
    matchLabels:
      name: nginx-ingress
  template:
    metadata:
      labels:
        name: nginx-ingress
    spec: 
      containers:
        - name: nginx-ingress-controller
          image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.21.0
          args: 
            - /nginx-ingress-controller
            - --configmap=$ (POD_NAMESPACE)/nginx_configuraruin
          env:            # nginx требует 2 переменные для конфигурации
            - name: POD_NAME
              valueFrom: 
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom: 
                fieldRef:
                  fieldPath: metadata.namespace
          ports:
            - name: http
              containerPort: 80
            - name: https
              containerPort: 443

Для конфигурации ingress на nginx также нужен ConfigMap. В него закладывается конфигурация nginx, которая в обычном варианте nginx как reverse-proxy вписывалась в config самого nginx:

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-configuration

Также необходимо создать Service, который публикует ingress вовне:

apiVersion: v1
kind: Service
metadata: 
  name: nginx-ingress
spec: 
  type: NodePort
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
    name: http
  - port: 443
    targetPort: 443
    protocol: TCP
    name: https
  selector:
    name: nginx-ingress

И нужен Service Account для аутентификации:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: nginx-ingress-serviceaccounts

Ingress Resource

Набор правил ingress называются Ingress Resource.

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-rules
spec:
  backend:
    serviceName: app-service
    servicePort: 80

Определение нахождения правил ingress resource:

kubectl get ingress -A # найти Ingress Resource среди Namespaces 

Редактирование правил ingress resource:

kubectl edit ingress <имя ingress resource> -n <namespace>

Nginx rewrite rules

Настройка rewrite-target нужна, чтобы правильно транслировать сетевой путь.

# Без rewrite-target:
http://<ingress-service>:<ingress-port>/watch --> http://<watch-service>:<port>/path

# С rewrite-target типа replace("/path","/"):
http://<ingress-service>:<ingress-port>/watch --> http://<watch-service>:<port>/

Для включения правил rewrite, нужно добавить в манифест annotations:

1. apiVersion: extensions/v1beta1
2. kind: Ingress
3. metadata:
4.   name: test-ingress
5.   namespace: critical-space
6.   annotations:               # применение rewrite правил
7.     nginx.ingress.kubernetes.io/rewrite-target: /
8. spec:
9.   rules:
10.   - http:
11.       paths:
12.       - path: /pay
13.         backend:
14.           serviceName: pay-service
15.           servicePort: 8282

Jobs & CronJobs

Jobs

Это объект для выполнения однократных служебных задач.

apiVersion: batch/v1
kind: Job
metadata:
  name: my-job
spec:
  completions: 3 # сколько Pod-ов запускать под задачу
  parallelism: 3 # запускать Pod-ы не последовательно, а сразу пачками по 3
  # если 1 из 3 Pod завершится с ошибкой, k8s будет 1 оставшийся перезапускать,    # пока тот не закончит работу корректно
  template:
    spec:
      containers:
      - name: job-container
        image: busybox
        command: [ "/run/job" ]
      restartPolicy: Never

Команды для работы с Jobs:

kubectl create -f <имя job.yaml>
kubectl get jobs
kubectl logs <имя Pod с Job> # вывод результата
kubectl delete job <имя Pod>

CronJobs

Объект для создания периодической задачи:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: my-cronjob
spec:
  schedule: "*/1 * * * *" # работает как Cron в Linux, см ниже Cron Parameters
  jobTemplate: # ниже описание spec обычного Job
    spec:
      completions: 3
      parallelism: 3 
      template:
        spec:
          containers:
          - name: job-container
            image: busybox
            command: [ "/run/job" ]
          restartPolicy: Never

Cron parameters:

# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12)
# │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
# │ │ │ │ │                                   7 is also Sunday on some systems)
# │ │ │ │ │
# │ │ │ │ │
# * * * * * <command to execute>

Kube-apiserver

Описание kube-apiserver

  • При работе с кластером k8s через инстумент kubectl, по сути работа идёт именно с kube-apiserver;
  • Kube-apiserver забирает данные из БД Etcd и возвращает их пользователю kubectl.

Работа kube-apiserver по созданию pod:

  1. Аутентификация пользователя;
  2. Валидация запроса о создании pod;
  3. Получение данных;
  4. Обновление Etcd;
  5. kube-scheduler мониторит kube-apiserver, узнаёт о появлении pod без привязки к worker-node. Он находит node, куда положить pod и сообщает в kube-apiserver;
  6. kube-apiserver обновляет данные в Etcd;
  7. kube-apiserver передаёт данные на kubelet выбранного worker-node;
  8. Kubelet создаёт на своей worker-node нужный pod и раскатывает там образ контейнера через CRE.

Настройки

  • Если k8s ставился через kubeadm, то они находятся в /etc/kubernetes/manifests/kube-apiserver.yaml
  • Если k8s ставился вручную, то они находятся в /etc/systemd/system/kube-apiserver.service

Kubernetes Security

Kubernetes Security

Link: https://habr.com/ru/companies/vk/articles/730158/

Корректная конфигурация кластера k8s

  • K8s CIS Benchmark => kube-bench, проверка конфигов узлов кластера;
  • Ограничить сетевой доступ к k8s API (white lists, VPN);
  • Каждый пользователь имеет уникальный ID при доступе к k8s API; после развертывания кластера никто не должен в нем аутентифицироваться как system:masters:
    • если кто-то украдет у меня этот kubeconfig, придется перевыпускать все сертификаты в кластере, потому что отозвать доступ у группы system:masters без этого невозможно.
  • Настроить RBAC. Например, некий оператор в кластере получает список всех подов. В этом случае не нужно выдавать ему возможность видеть все секреты и тем более редактировать их.

Сканирование образов

  • Разбор уязвимостей: не каждая из них ведёт к эксплуатации;
  • Использовать общие базовые образы - при нахождении в таких уязов, их моно заменить и перезапустить CI/CD;
  • Уменьшение числа зависимостей; Инструменты debug не должны оказываться в образах для prod;
  • Собираем артефакт отдельно и копируем в итоговый контейнер, который будет использоваться в кластере.

Сетевая безопасность

  • CNI с сетевыми политиками; Если stage + prod в 1 кластере, разрабы попадают в stage, а из него - в prod, если нет сетевой изоляции; “политики как код” - развёртываются вместе с приложениями из Git-репозитория; Внедрять политики со старта, пока всё просто;
  • Ingress + egress политики настроены для всех компонентов кластера;
  • Все соединения должны быть явно разрешены.

Контроль над запускаемыми приложениями

  • Pod Security Admisson закрывает базовые потребности
  • Pod Security Standards запрещают пускать поды от root, использовать host network и т.д.
  • Если этого недостаточно, тогда:
  • Точки контроля:
    • Запрет на использование образов с DockerHub;
    • Обязательно указывать priorityClass;

Аудит и регистрация событий

  • Самый простой из коробки - аудит Kubernetes API;
  • Логирование в файл или stdout, отправка в SIEM и анализ.
  • Часть команд (пример: crictl exec) не отобразятся в логе Kubernetes API. Нужен сквозной аудит хостовая ОС + k8s узел. Например, с помощью Falco:
    • системные вызовы на хосте;
    • аудит-лог от Kubernetes API с помощтю Webhook backend;
    • проверка потока событий с помощю сконфигурированных правил;
    • отправка алертов в случае нарушений правил.
  • Технология eBPF в ядре - для событий хоста + контейнеров одновременно.

Расширение защиты

  • Аутентификация и авторизация
  • Аудит RBAC
  • Управление секретами
  • Защита цепочек поставок:
    • Запрет запуска образов с уязами;
    • Запуск только подписанных образов (cosign).
  • Режим обучения для создания политик;
  • Авто-реагирование на события аудита.

История ИБ K8s

  • 2016 - на kubelet не было механизма аутентификации. Надо было через SSH-Tunneling защищать. 120 вариаций k8s в 2024. В 1 до сих пор осталась эта проблема;
  • --insecure-port=8080 - даёт cluster-admin без авторизации. Можно нацелить kubectl на него и получить все права. Убрали только в 1.20; Облачный пров повесил этот порт на container network, клиенты не могли его выключить;
  • Irrevocable credentials. Нет поддержки по удалению клиентских сертификатов; Заказчик даёт аудитору k8s файл такого серта, из группы system:masters, и далее в течение года он действителен; irrevocable secrets - вплоть до 1.25, никогда не протухают, убиваются только вместе с service account. Они рано или поздно утекают - в git repo, в тикете к поддержке и т.д. Взамен пришли expiring token requests;
  • RBAC появился не сразу. Было --authorization-mode=AlwaysAllow. В 1 вариации k8s до сих пор так осталось.
  • Helm 2 + служба Tiller (у которой по gRPC TCP44134 нет аутентфикации, и он обычно cluster-admin), которую можно найти по DNS и компрометировать кластер.

Что сегодня

  • До сих пор можно выдавать client certs, а также long-lived tokens - до 1 года; По-умолчанию нигде (кроме OpenShift) вот это всё не включено сразу:
  • Pod Security Admission GA 1.25
  • Validating Admission Policy GA 1.30
  • Внешние опции (Kyverno, OPA и т.д.)

“Unpatchable 4”

CVE-2020-8554

Перехват трафика в multi-tenant кластерах. Кроме тех, где Cillium работает БЕЗ kube-proxy (потому что этот баг зависит от kube-proxy); Либо с помощью Kyverno и т.д. заблокировать создание clusterIP на внешних service with external ip.

CVE-2020-8561

Server-Side-Forgery (SSRF). ValidatingAdmissionWebhook + Remote Debug Level = Debug (MAX)

CVE-2020-8562

SSRF + Time-of-Check-Time-of-Use (TOCTOU) K8s API = HTTP-Proxy по сути своей. Но в нём есть hardcoded список адресов IP, куда он не проксирует (например, localhost). При наличии подконтрольного DNS, можно кидать к нему запрос на подключение на случайный IP, а DNS транслирует его в сторону API-сервера как localhost.

Лечится с помощью службы konnectivity, защищающей k8s Control Plane от запросов от Pod Network. https://kubernetes.io/docs/tasks/extend-kubernetes/setup-konnectivity/ https://stackoverflow.com/questions/61706923/what-is-the-konnectivity-service-for-kubernetes

CVE-2021-25740

Multi-tenant environment. Load-balancer allows requests from endpoint and passes commands to other namespace.

Kaspersky Container Security

Скрипт для скачивания продукта (версия 1.2.2) локально:

#!/bin/bash 
 
# Securely obtain credentials (replace with your actual method) 
read -s -p "Docker Password: " docker_password 
docker login repo.kcs.kaspersky.com -u <USER_LOGIN> -p "$docker_password" 
 
images=( 
  "repo.kcs.kaspersky.com/images/services/clickhouse:v1.2.2-without-ssl" 
  "repo.kcs.kaspersky.com/images/services/event-broker:v1.2.2" 
  "repo.kcs.kaspersky.com/images/services/image-handler:v1.2.2" 
  "repo.kcs.kaspersky.com/images/services/panel/nginx:v1.2.2" 
  "repo.kcs.kaspersky.com/images/services/scanner-server:v1.2.2" 
  "repo.kcs.kaspersky.com/images/services/licenses:v1.2.2" 
  "repo.kcs.kaspersky.com/images/services/middleware:v1.2.2" 
  "repo.kcs.kaspersky.com/images/initer:v1.2.2" 
  "repo.kcs.kaspersky.com/images/node-agent:v1.2.2" 
  "repo.kcs.kaspersky.com/images/kube-agent:v1.2.2" 
  "repo.kcs.kaspersky.com/images/updates:v1.2" 
  "repo.kcs.kaspersky.com/images/scanner:v1.2.2-with-db" 
  "repo.kcs.kaspersky.com/images/external/minio:2023.9.30" 
  "repo.kcs.kaspersky.com/images/external/nats:2.9.17" 
) 
 
for image in "${images[@]}"; do 
  docker pull "$image" || exit 1  # Exit on failure 
done 
 
echo "All images pulled successfully."

docker save $(docker images --format '{{.Repository}}:{{.Tag}}') -o KCS122.tar

<USER_LOGIN> и пароль необходимо получить у представителя компании.

Namespaces

Create

Use manifest to create:

apiVersion: v1
kind: Namespace
metadata:
  name: dev

Commands:

kubectl create -f namespace-dev.yaml
kubectl create namespace dev

Pods in Namespace

To place pods in selected namespace add it to their manifest:

apiVersion: v1
kind: Pod       
metadata: 
  namespace: dev
  name: myapp-pod
spec:
  containers:
    - name: nginx-container
      image: nginx

Viewing Namespace Contents

Write the target namespace to get pods from:

kubectl get pods --namesapce=dev

Changing namespace in the current context of kubectl to dev:

kubectl config set-context $(kubectl config current-context) --namespace=dev 

List pods in all namespaces:

kubectl get pods --all-namespaces

Pod Commands & Configs

Commands & Arguments

Команды и аргументы команд, которые срабатывают при запуске контейнера.

apiVersion: v1
kind: Pod
metadata: 
  name: myapp
  labels:
    app: test_app
    env: productinon
spec:
  containers:
    - name: nginx-container
      image: nginx
      command: [ "python3" ]
      args: [ "app-test.py" ]

## Вариант 2
      command: 
        - "python3"
        - "app-test.py"

## Вариант 3
      command: [ "python3", "app-test.py" ]

Заменять команды, аргументы, метки и т.д. нельзя. Однако, можно вызвать ошибку, потом пересоздать Pod на лету из сохранённого промежуточного файла:

$ kubectl edit pod nginx-container # отредактировал поле command

error: pods "nginx-container" is invalid
A copy of your changes has been stored to "/tmp/kubectl-edit-1395347318.yaml"
error: Edit cancelled, no valid changes were saved.

$ kubectl replace --force -f /tmp/kubectl-edit-1395347318.yaml

Environmental Variables

Переменные среды задаются как список, похожим образом с командами.

apiVersion: v1
kind: Pod
metadata: 
  name: myapp
  labels:
    app: test_app
    env: productinon
spec:
  containers:
    - name: nginx-container
      image: nginx
      env:
        - name: APP_COLOR 
          value: green

ConfigMap

Отдельный объект, который содержит переменные среды. Можно получить их список через kubectl get configmaps

apiVersion: v1
kind: ConfigMap
metadata: 
  name: mydb
data:
  APP_COLOR: blue
  APP_MODE: testdev

Императивный способ создания ConfigMap

kubectl create configmap \ 
        <имя конфига> --from-literal=<ключ>=<значение> \
                      --from-literal=APP_USER=testuser

kubectl create configmap \
        <имя конфига> --from-file=<путь до файла>
                    # --from-file=app_config.properties

Ссылка на ConfigMap в описании Pod

Ссылка производится по именам ConfigMap в виде списка:

apiVersion: v1
kind: Pod
metadata: 
  name: myapp
  labels:
    app: test_app
    env: productinon
spec:
  containers:
    - name: nginx-container
      image: nginx
      envFrom:
        - configMapRef: 
            name: mydb
            
# Вариант взять только конкретную переменную: 
      env: 
        - name: APP_COLOR 
          valueFrom: 
            configMapKeyRef:
              name: mydb
              key: APP_COLOR

Secrets

Секреты - это ConfigMap, значения которых кодируются по base64. Можно получить их список через:

kubectl get secrets # список секретов
kubectl describe secret <имя секрета> # не отображает значения
kubectl get secret <имя секрета> -o yaml # отображает значения в файле 

Декларативное описание

apiVersion: v1
kind: Secret
metadata: 
  name: mydb
data:
  APP_PWD: dmVyeXNlY3JldA== # base64 Encode
  APP_TOKEN: dGVzdGRldg==

Императивный способ создания Secret

kubectl create secret generic \ 
        <имя конфига> --from-literal=<ключ>=<значение> \
                      --from-literal=APP_USER=testuser

kubectl create secret generic \
        <имя конфига> --from-file=<путь до файла>
                    # --from-file=app_config.properties

Ссылка на Secret в описании Pod

apiVersion: v1
kind: Pod
metadata: 
  name: myapp
  labels:
    app: test_app
    env: productinon
spec:
  containers:
    - name: nginx-container
      image: nginx
      envFrom:
        - secretRef: 
            name: mydb

# Вариант взять только конкретное значение: 
      env: 
        - name: APP_COLOR 
          valueFrom: 
            secretKeyRef:
              name: mydb
              key: APP_PWD
              
# Вариант смонтировать как файлы (каждый пароль - отдельный файл)
      volumes:
      - name: app-secret-volume
        secret: 
          secretName: app-secret

Service Accounts

Специальные учётные записи для доступа к k8s. При создании вместе с ними создаётся объект secret.

  • С версии k8s 1.22 объект Secret имеет время жизни;
  • С версии k8s 1.24 секрет не создаётся на автомате, нужно его отдельно создать:
kubectl create serviceaccount dashboard-sa
kubectl create token dashboard-sa # с k8s 1.24+ необходимо создать токен, у которого время жизни (по умолчанию) =1 час с момента создания

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"

Replicasets & Deployments

ReplicaSets

ReplicaSet следит за тем, чтобы количество Pod всегда было заданным числом штук (параметр replicas) - не больше и не меньше. ReplicaSet Controller является более современным аналогом ReplicationController:

  • ReplicationController apiVersion = v1
  • ReplicaSetController apiVersion = apps/v1

Отличие ReplicaSet в том, что в нём обязательным параметром есть selectors, который отслеживает контейнеры, в том числе созданные ДО создания самого ReplicaSet. Отслеживание можно производить по их меткам labels. Для совпадающих меток работает алгоритм приведения к нужному количеству.

Создание ReplicaSet:

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myreplicaset
  labels:
    name: myapp
spec:
  selector:
    matchLabels:
      env: production ### метка должна совпадать
  replicas: 3
  template:
    metadata:  ### шаблон контейнера берётся из описания Pod
      name: myapp
      labels:
        env: production ### метка должна совпадать
    spec:
      containers:
        - name: nginx-container
          image: nginx
    

Шаблон Pod всегда должен быть описан в части template, чтобы ReplicaSet знал, какие Pod создавать.

Команды для работы с ReplicaSets

kubectl create -f <имя файла с описанием replicaset>
kubectl get rs # вывести все ReplicaSet в кластере
kubectl describe rs <имя replicaset> # подробности о ReplicaSet
kubectl delete rs <имя replicaset> # удаляет все Pods и сам ReplicaSet
kubectl edit rs <имя replicaset> # отредактировать описание Replicaset
kubectl scale rs <имя replicaset> --replicas=<новое количество копий> 
kubectl replace -f <имя файла с описанием replicaset> # заменить ReplicaSet 

Deployments

Deployment - это надстройка над ReplicaSet, добавляет логику обновления Pod с одной версии на другую. При обновлении Deployment имеет 2 стратегии:

  • Rolling Update - (по умолчанию) при обновлении Pods, делает это поштучно: один Pod старой версии кладёт (Terminated), сразу поднимает заместо него новый Pod;
  • Recreate - при обновлении сначала удаляются все Pods, после чего взамен поднимаются новые. Сопровождается падением/отключением приложения для потребителей.
kubectl apply -f <имя Deployment> # запустить процесс rollout после внесения изменений в манифест
kubectl rollout status <имя Deployment> # узнать о статусе выкатывания

kubectl rollout history <имя Deployment> # узнать о всех ревизиях и причинах их перехода
kubectl rollout history <deployment> --revision=1 # узнать статус конкретной версии

kubectl rollout undo <имя Deployment> # откатить назад обновление Deployment
kubectl rollout undo <имя Deployment> --to-revision=1 # откатить до версии 1
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mydeployment
  labels:
    name: myapp
spec:
  selector:
    matchLabels:
      env: production ### метка должна совпадать
  replicas: 3
  strategy: 
    type: RollingUpdate ### стратегия замены Pods
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:  ### шаблон контейнера берётся из описания Pod
      name: myapp
      labels:
        env: production ### метка должна совпадать
    spec:
      containers:
        - name: nginx-container
          image: nginx
    

Команды для работы с Deployments

kubectl create -f <имя файла с описанием Deployment>
kubectl get deploy # вывести все Deployment в кластере
kubectl describe deploy <имя Deployment> # подробности о Deployment
kubectl delete rs <имя Deployment> # удаляет все Pods и сам Deployment
kubectl edit rs <имя Deployment> # отредактировать описание Deployment и произвести его обновление
kubectl edit rs <имя Deployment> --record # отредактировать описание, вызвав обновление и записать команду как причину обновления в список ревизий
kubectl set image deploy <имя Deployment> nginx=nginx:1.18 # пример обновления без редактирования YAML

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

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

kubectl create deploy nginx --image=nginx --replicas=4
kubectl scale deploy nginx --replicas=4

kubectl create deployment nginx --image=nginx --dry-run=client -o yaml > nginx-deployment.yaml # вывести манифест YAML для последующего редактирования 

Services

Services

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

Service делятся на 3 вида:

  • NodePort - публикация порта с Pod наружу на уровень Worker Node (причём, если Pods размазаны лишь по части от всех нод кластера k8s, то всё равно они доступны при обращении К ЛЮБОЙ ноде кластера);
  • ClusterIP - по сути внутренний балансировщик для обращения, например, части frontend приложения к множеству Pods, реализующих backend;
  • LoadBalancer - NodePort с заданием стратегии балансировки (в NodePort - случайный алгоритм балансировки), поддерживается лишь на ряде облачных площадок (AWS, GCM и т.д.). В остальных площадках ведёт себя как NodePort.

NodePort

apiVersion: v1
kind: Service
metadata:
  name: myservice
spec: 
  type: NodePort
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30004 # порты ограничены диапазоном 30000-32767
  selector:
    app: myapp

ClusterIP

apiVersion: v1
kind: Service
metadata:
  name: myservice
spec: 
  type: ClusterIP
  ports:
    - port: 80
      targetPort: 80
  selector:
    app: myapp

LoadBalancer

apiVersion: v1
kind: Service
metadata:
  name: myservice
spec: 
  type: LoadBalancer # only works with a supported platform
  ports:
    - port: 80
      targetPort: 80
  selector:
    app: myapp

Команды работы с Service

kubectl create -f <имя файла с описанием Service>
kubectl get svc # вывести все Service в кластере
kubectl describe svc <имя service> # подробности о service
kubectl delete svc <имя service> # удаляет все объект service
kubectl edit svc <имя service> # отредактировать описание service и произвести его обновление

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

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

kubectl create service clusterip redis --tcp=6379:6379 --dry-run=client -o yaml # нельзя подавать selectors в команду, следует вывести YAML и отредактировать, что небыстро

kubectl expose pod redis --port=6379 --name=redis-service --dry-run=client -o yaml # такой вариант CLusterIP использует labels самого pod как selectors, что намного эффективнее

kubectl expose pod nginx --port=80 --name=nginx-service --type=NodePort --dry-run=client -o yaml # такой вариант NodePort использует labels самого pod как selectors