Инструменты пользователя

Инструменты сайта


learning:k8s

Различия

Показаны различия между двумя версиями страницы.

Ссылка на это сравнение

Предыдущая версия справа и слеваПредыдущая версия
Следующая версия
Предыдущая версия
learning:k8s [08.12.2022 07:53] – [С помощью Secrets] viacheslavlearning:k8s [25.03.2025 15:27] (текущий) – [certificate has expired or is not yet valid] viacheslav
Строка 1: Строка 1:
 +====== K8s ======
 +[[https://kubernetes.io/ru/docs/reference/kubectl/cheatsheet/|Шпаргалка по kubectl]]
 +===== Основные понятия =====
 +https://kube.academy/courses/kubernetes-core-concepts-part-1/lessons/kubernetes-fundamentals
 +
 +Принципы Cloud Native:
 +  - Контейнеризация (докер)
 +  - Динамическое управление – Кубернетис
 +  - Ориентация на микросервисы – Кубернетис
 +В Кубернетисе мы задаём желаемое состояние (desired state), т. е., система сама подстраивается и предпринимает конкретные действия, чтобы этого достичь.
 +
 +==== Pod ====
 +**Pods** – один или несколько контейнеров. Все контейнеры в одном поде:
 +  - запускаются на одном хосте
 +  - взаимодействуют друг с другом через localhost
 +  - имеют одинаковый доступ к томам
 +Иными словами, pod – это аналог сервера или виртуальной машины. Это помогает определиться в каждом случае, запускать ли 2 контейнера в 1 поде или в разных, планирование такое же, как действовать в случае с ВМ. 
 + 
 +Sidecars – сопутствующие контейнеры в одном поде с основным. К примеру, есть контейнер веб-сервера, и туда при запуске копируется контент. Чтобы поправить какую-то ошибку в контенте, нужно перезапускать контейнер. Вместо этого в под помещается другой контейнер, который копирует контент со стороннего ресурса (например, с git), и кладёт на общий том (единый в рамках одного пода), а веб-сервер просто отображает содержимое.
 +<code yaml>
 +apiVersion: v1
 +kind: Pod (с чем работаем)
 +metadata:
 +  name: mypod1 (уникальный идентификатор)
 +  labels:
 +    app: blog
 +spec: (всё в разделе spec характерно для выбранного типа, в данном случае пода)
 +  containters:
 +  -  name: nginx
 +    Image: nginx:1.13.1
 +  -  name: www-syncer
 +    Image: syncer:1.2
 +</code>
 +
 +Как запустить эту конструкцию? 
 +<code bash>
 +# Предпочтительный путь (declarative):
 +kubectl apply –f [file | dir | url]
 +# imperative:
 +kubectl create # создать новый ресурс из файла
 +kubectl replace # обновить существующий ресурс из файла
 +kubectl edit # обновить существующий ресурс, используя редактор
 +kubectl patch # обновить существующий ресурс слиянием code snippet
 +</code>
 +
 +=== Binding ===
 +
 +Чтобы жёстко привязать под к ноде, используется ''spec.nodeName''. Если под уже существует, то можно изменить имя ноды в свойствах пода или создать объект ''kind: Binding''.
 +<code yaml>
 +apiVersion: v1
 +kind: Binding
 +metadata:
 +  name: nginx
 +target:
 +  apiVersion: v1
 +  kind: Node
 +  name: node02
 +</code>
 +, а потом послать запрос POST в API в формате json, что имитирует работу планировщика.
 +<code bash>
 +# пример
 +$ curl --header "Content-Type:application/json" --request POST --data '{"apiVersion":"v1", "kind":"Binding" ...}'
 +http://$SERVER/api/v1/namespaces/default/pods/$PODNAME/binding/
 +</code>
 +
 +==== Service ====
 +Для сетевого доступа и балансировки существует тип объекта **Service.** Для определения целевых подов используются ярлыки (labels).
 +
 +{{:learning:pasted:20211201-123722.png}}
 +
 +<code yaml>
 +apiVersion: v1
 +kind: Service
 +metadata:
 +  name: my-blog
 +  labels:
 +    app: blog
 +spec:
 +  selector:
 +    app: blog
 +  ports:
 +  - protocol: TCP
 +    port: 80 (порт сервиса)
 +    targetPort: 80 (порт подов сервиса)
 +</code>
 +Вместо имен или IP-адресов подов используется селектор, работающий как запрос для поиска подходящих подов для доступа.
 +
 +==== Deployment ====
 +А что, если нужно, к примеру, 20 одинаковых подов, чтобы справиться с нагрузкой? 20 одинаковых yml с одной различающейся строкой "name:"! Т. к. это явный идиотизм, существует более высокоуровневый объект **Deployment,** который занимается оркестровкой подов, создавая и убивая их по необходимости.
 +<code yaml>
 +apiVersion: apps/v1
 +kind: Deployment
 +metadata:
 +  name:my-blog
 +spec:
 +  replicas: 3 (копий подов)
 +  selector:
 +    matchLabels:
 +      app: blog
 +  template:
 +    metadata:
 +      labels:
 +        app: blog
 +    spec:
 +      containers:
 +      - name: nginx
 +        Image: nginx:1.7.9
 +        Ports:
 +        - containerPort: 80
 +</code>
 +В данном случае раздел template является фактически описанием пода, за исключением имени, т. к. подов может быть много и имена будут присваиваться автоматически.
 +
 +Ярлыки (labels) выполняют как организационную, так и функциональную работу (как селекторы сервисов), они индексированы и по ним можно искать. Лучше не использовать составных ярлыков типа app: blog-frontend, потому что нет частичного или regexp-сравнения по ярлыкам. Гораздо лучше app: blog / tier: frontend. Также очень желательно иметь некий стандарт написания ярлыков в рамках кластера.
 +
 +Документация:\\
 +https://docs.kubernetes.io\\
 +https://kubernetes.io/docs/reference\\
 +https://kubectl.docs.kubernetes.io
 +
 +<code bash>
 +kubectl explain pods # встроенная справка по разным разделам, в данном случае о подах
 +kubectl api-resources # список доступных типов ресурсов
 +kubectl apply -f service.yaml # запустить сервис или деплой
 +kubectl get service -l "app=blog" # проверить, что сервис создан
 +kubectl get deployment -l "app=blog" # проверить, что деплой создан
 +</code>
 +===== Решение проблем =====
 +https://kube.academy/courses/kubernetes-core-concepts-part-1/lessons/kubernetes-architecture-troubleshooting
 +
 +Ноды делятся на 2 типа – worker nodes и control plane. На рабочих ставится контейнерный движок (например, Докер), kubelet, взаимодействующий с докером, и kubeproxy. Control plane состоит из API, с которым взаимодействует админ. API работает с etcd – хранилищем конфигураций. Scheduler – определяет, где поды запускаются. Controller manager – управляет объектами (подами, сервисами и т. д.).
 +
 +{{:learning:pasted:20211115-142809.png}}
 +
 +<code bash>
 +Kubectl get events [-w] # события кластера. По умолчанию хранит данные за час. [-w] – показ в реальном времени. Полезно при проблемах с cubectl apply.
 +Kubectl describe pod gowebapp-3498323ff-348hf # описание объекта вместе с его событиями
 +Kubectl get pods [–o wide] # список объектов, wide – более подробно.
 +Kubectl logs gowebapp-3498323ff-348hf # смотреть логи. Добавить –c <container_name>, если под содержит больше 1 контейнера.
 +Kubectl exec gowebapp-3498323ff-348hf – ls –l /opt/gowebapp # запустить команду. Добавить –c <container_name>, если под содержит больше 1 контейнера.
 +</code>
 +
 +===== Управление развертыванием =====
 +Deployment создаёт промежуточную сущность ReplicaSets, которая уже создаёт поды. Соответственно, есть две стандартные стратегии развёртывания:
 +
 +**RollingUpdate** - стратегия по умолчанию,когда старые поды постепенно замещаются новыми. К примеру, в deployment.yaml была заменена версия image. После применения обновлённого файла создаётся ещё одна ReplicaSet, там создаётся один под из нового образа (в этот момент общее число подов становится на один больше и трафик между ними балансируется, т. к. у них общие ярлыки). Если под создался успешно, гасится один под из старой реплики, и так далее, пока все поды не заменятся на новые в новой реплике. Старая пустая реплика удалится через некоторое время.
 +
 +**Recreate** - сначала гасятся все поды, удаляется старая реплика, а затем создаётся новая и там разворачиваются поды. Это нужно для баз MySQL, т. к. недопустима балансировка между старыми и новыми версиями БД.
 +
 +Также, имеются дополнительные стратегии, они не встроенные, но их можно выстраивать самостоятельно:
 +
 +**Canary** - создаётся ещё один файл deployment, где в labels: указан дополнительный track: canary. В основном деплое указывается track: stable. Для canary указывается, например, одна реплика. После применения получается так, что трафик балансируется между всеми подами, т. е., часть трафика попадает на новую версию. Если всё нормально и нужно полностью перейти на новую версию, новый образ прописывается в stable deployment, а в canary кол-во реплик ставится 0.
 +
 +**Blue/Green** - популярно в облачной среде, где трафик по щелчку переключается со старой версии на новую. Так же, как и в Canary, создаётся второй деплой - в blue в labels: указан дополнительный color: blue, а в деплое green, соответственно, color: green. В то же самое время, нужно иметь два сервиса - blue и green, где в selector: указаны соответствующие ярлыки, чтобы иметь возможность тестирования деплоя green. Получается, что одновременно работает и прод, и тест, каждый со своим трафиком.
 +
 +Когда тесты закончились и нужно ввести тестовый вариант (green) в прод, редактируется //сервис,// где меняется ярлык c color: blue на color: green. Если никто не жалуется и всё нормально, в деплое blue кол-во реплик меняется на 0.
 +
 +В следующий раз тестовой средой будет уже blue, т. к. green перешёл в прод.
 +
 +<code bash>
 +# Развёртывание можно приостанавливать и снова продолжать
 +kubectl rollout [pause | resume] deployment <name>
 +# Откат изменений (declarative, рекомендуется) - ред. деплой и применение
 +kubectl apply -f deployment.yaml
 +# Откат изменений (imperative)
 +kubectl rollout undo deployment <name>
 +</code>
 +
 +Сам объект Deployment имеет доп. опции, вот некоторые из них:
 +  * progressDeadlineSeconds - макс. время развёртывания в секундах
 +  * minReadySeconds - мин. время готовности пода в секундах без падения какого-либо из входящих в него контейнеров (станд. значение - 0)
 +  * revisionHistoryLimit - кол-во хранимых реплик для возможности отката изменений (10)
 +  * paused - приостановка развёртывания. Если true, то любые изменения в разделе spec не приведёт к развёртыванию.
 +
 +Лабораторная (https://kube.academy/courses/kubernetes-core-concepts-part-2/lessons/lab-deployment-management).
 +  * Сделали образ docker новой версии, внеся изменения в код, собрали, поставили тэг и залили в каталог (push)
 +  * Для пробы отредактировали основной деплой на новый образ и через 1 сек. поставили развёртывание на паузу - получилось 3 пода: 1 новый и 2 старых. Describe deployment показал, что есть 2 реплики.
 +  * ''kubectl rollout resume deployment <name>'' - продолжили, потом проверили - все поды принадлежат новой реплике.
 +  * Теперь канареечный деплой. Создаётся соответствующий файл деплоя, где во все labels и matchLabels добавляется ''track: canary''. Образ, конечно, новый, реплика одна.
 +  * Канареечный деплой применяется. Теперь стало три реплики приложения, одна из которых канареечная, и трафик ходит между всеми, что можно видеть на странице приложения, обновляя её.
 +  * Теперь, если создать доп. сервис для канареечного деплоя, где в раздел ''selector:'' добавить ярлык ''track: canary'' и запустить этот сервис, то трафик пойдёт только на канареечный сервис, т. к. доступ там тот же, что и у прода.
 +  * Чтобы полностью перейти на новую версию в проде, надо обновить версию образа в прод. деплое, а в канареечном поставить ''replicas: 0''. Потом применить все .yaml в папке путём ''kubectl apply -f ~/folder''
 +  * Удалить канареечные сущности: ''kubectl delete service canary'', ''kubectl delete deployment canary''.
 +
 +===== Конфигурация подов и контейнеров =====
 +==== Проверки ====
 +Есть 2 встроенных типа проверок состояния контейнера:
 +  - Liveness - запущен ли контейнер. При сбое он будет перезапущен согласно политике.
 +  - Readiness - готов ли контейнер принимать запросы. При сбое под удаляется из сервиса, так что запросы на него приходить не будут.
 +
 +Способы проверки (probe handlers):
 +  * Exec - запуск команды, даёт код возврата, 0 - успешно.
 +  * TCPSocket - TCP-проверка. Порт открыт и принимает соединения - успешно.
 +  * HTTPGet - HTTP GET, коды 2xx-3xx - успешно.
 +
 +Примеры применения проверок: Readiness probe - когда веб-процесс в контейнере начинает отвечать меньше заданного времени, чтобы при его инициализации не пускать туда трафик, во избежание тормозов на стороне юзера. Для JVM - Liveness probe для URL или для определённых строк в логах (я занят, не шлите на меня трафик). Контейнер может включить режим обслуживания, если он не прошёл Readiness probe.
 +
 +О самих проверках нужно позаботиться на стороне контейнера/образа, не в K8s!
 +
 +Настройки проверок:
 +  * initialDelaySeconds - задержка запуска проверки после старта контейнера
 +  * periodSeconds - частота проверок (10 сек)
 +  * timeoutSeconds - тайм-аут при проверке (1 сек)
 +  * successThreshold - кол-во успешных проверок для признания контейнера исправным (1)
 +  * failureThreshold - кол-во неуспешных проверок для признания контейнера сбойным (3)
 +
 +Пример
 +<code yaml>
 +apiVersion: v1
 +kind: Pod
 +metadata:
 +  labels:
 +    test: readiness
 +  name: exec-readiness
 +spec:
 +  securityContext:
 +    runAsUser: 1000
 +  containers:
 +  - name: readiness
 +    image: busybox
 +    args:
 +    - /bin/sh
 +    - -c
 +    - touch /tmp/healthy; sleep infinity
 +    readinessProbe:
 +      exec:
 +        command:
 +        - cat
 +        - /tmp/healthy
 +      initialDelaySeconds: 5
 +      periodSeconds: 5
 +</code>
 +
 +==== Ресурсы ====
 +Указываются для каждого контейнера.
 +
 +Процессор - это эквивалент 1 AWS vCPU / 1 GCP Core / 1 Azure vCore / 1 Hyperthread on Intel CPU w/Hyperthreading. Если в процентах, то 0.1 или 100m - это 10% (100 миллипроцессоров). Самое минимальное значение - 1m.
 +
 +Память и хранилище - десятичные значения (K, M, G, T, P, E) и двоичные (Ki, Mi, Gi, Ti, Pi, Ei).
 +
 +Требования к ресурсам (Resource requests) - нужно для более эффективного и понятного распределения по серверам кластера. Т. е., указание, сколько нужно контейнеру, чтобы быть размещённым на том или ином хосте, иначе либо контейнер не будет размещён на хосте с недостаточными ресурсами, либо будет тормозить. По умолчанию - 0.5 vCPU и 256 Mi памяти.
 +<code yaml>
 +containers:
 +  - name: db
 +    image: mysql
 +    resources:
 +      requests:
 +        memory: "512Mi"
 +        cpu: "0.5"
 +</code>
 +
 +Предел ресурсов (Resource limits) - чтобы контейнер не сожрал слишком много ресурсов и не завалил всю систему. При попытке превысить ресурсы CPU контейнер будет ограничиваться в потреблении (throttling), при превышении лимита памяти он будет убит и пересоздан. По умолчанию - 1 vCPU и 512 Mi памяти.
 +<code yaml>
 +containers:
 +  - name: db
 +    image: mysql
 +    resources:
 +      limits:
 +        memory: "1Gi"
 +        cpu: "1"
 +</code>
 +
 +Когда ноды кластера не имеют достаточно ресурсов для размещения доп. подов, планировщик не может выполнить требований деплоя и поды будут помечены как Pending, пока не призойдут какие-либо изменения ситуации для достижения желаемого состояния, например
 +  * Будут добавлены новые хосты в кластер
 +  * Будут пересмотрены требования к ресурсам в деплое
 +  * Другие поды и деплои будут удалены или уменьшены в размере
 +  * Кол-во желаемых подов будет уменьшено в текущем деплое
 +
 +===== Сеть =====
 +Трафик в K8s можно поделить на 4 сегмента: внутри пода, между подами, между сервисом и подами, извне к кластеру.
 +
 +Т. к. под всегда находится на одном хосте, то его внутренний трафик не идёт "по проводу". Внутри пода для связи контейнеров используется localhost, разделяют общий IP-адрес в рамках кластера - в целом, так же, как и в случае виртуальной машины.
 +
 +У каждого пода есть свой уникальный маршрутизируемый IP-адрес в рамках внутренней виртуальной сети кластера. Также, есть подобие DNS-имени на базе ''metadata: name:''. Внешние устройства напрямую к подам получить доступ не могут.
 +
 +Сервис работает как NLB 4-го уровня. Есть 3 типа сервисов:
 +  - ClusterIP - публикация внутри кластера, т. е., поды за сервисом публикуются для других подов в том же кластере. Внешнего доступа нет. Используется, например, для сервисов БД. Это стандартный тип сервиса, если не указано иначе.
 +  - NodePort - публикация как порт на всех рабочих нодах. Т. е., попасть к сервису можно, обратившись на любую рабочую ноду кластера на указанный порт. Это не отменяет необходимости внешнего балансировщика, который уже будет балансировать сами ноды.
 +  - LoadBalancer - создание и управление внешним балансировщиком, где NodePort используется для вхождения трафика, а ClusterIP для балансировки между подами. Есть разные плагины для разных реализаций внешнего балансировщика.
 +
 +Ingress Controller - это NLB 7-го уровня. Ему, в отличие от NodePort, не нужно открывать кучу портов на нодах. Есть множество реализаций контроллера Ingress, такие, как Nginx, Contour, Traefik, Amazon ALB, Google Layer-7 Load Balancer. Запускается он как поды в кластере, опубликованные на NodePort, которые пробрасывают внешний трафик на ClusterIP сервисов. Для Ingress-контроллера нужно создать объект ''kind: Ingress''.
 +
 +https://kubernetes.io/docs/concepts/services-networking/ingress/
 +
 +===== Организация ресурсов =====
 +Наивысший уровень изоляции сервисов - это **разнесение их по разным кластерам** - prod, qa, dev и т. п. Также это имеет смысл при каких-то требованиях безопасности или при географическом распределении датацентров. В этом случае имеются некоторые доп. усилия на управление.
 +
 +Следующий - это **namespace** в рамках одного кластера. Каждый объект кластера входит в своё пространство, и в рамках этого пространства должен иметь уникальное имя. Это даёт возможность запускать один и тот же сервис в разных пространствах имён без риска перезаписать соседние настройки. Также это ограничивает внутрикластерные DNS-запросы, которые выглядят как поддомен - <service-name>.<namespace-name>.cluster.local. Если нужно подключиться к сервису в другом пространстве имён, необходимо использовать FQDN (<service-name>.<namespace-name>.cluster.local). Можно разграничивать права - одному можно дать namespace только на чтение, другому - полный доступ, а также namespaces задают квоты ресурсов. Хороший пример использования пространства имён - по одному на проект. 
 +
 +<code bash>
 +# Вывести пространства имён
 +kubectl get namespaces
 +# Вывести поды другого пространства
 +kubectl get pods -n namespace-name
 +</code>
 +
 +
 +**Ярлыки (labels)** - могут быть привязаны почти к любому объекту в k8s, даже к нодам (кроме встроенных). Они необязательны, но выполняют также и функциональную роль - использование в селекторах и т. п. Необходимо вести список ярлыков и делать отчёты по ним, избегать составных ярлыков (лучше ''app: web'' ''tier: frontend'', чем ''app: web-frontend''). Также, нужна система именования ярлыков - такая, как если бы всё нужно было бы разместить в одном пространстве имён, пользуясь только ярлыками для организации ресурсов.
 +
 +<code bash>
 +# ФИльтровать вывод по ярлыку
 +kubectl get pods -l tier=frontend
 +</code>
 +
 +kubectl знает, с каким кластером он работает, из файла ''~/.kube/config''. Там 3 секции - ''clusters:'', где перечислены кластеры, ''contexts:'', где соединяются сведения о кластерах и пользователях, и ''users:'' - пользователи. ''name:'' в ''clusters:'' - это просто локальное отображаемое имя. ''users:'' - там имя тоже просто для отображения локально. Если, положим, есть 3 кластера и во всех них одинаковый пользователь, то будет 3 записи в кластерах, 1 в пользователях и 3 в контекстах.
 +
 +<code bash>
 +# добавить namespace в контекст конфига
 +kubectl config set-context my-context --user poweruser --cluster cluster1 --namespace my-namespace
 +# Переключиться на новый контекст
 +kubectl config use-context my-context
 +# см. какой контекст сейчас используется
 +kubectl config current-context
 +# все контексты
 +kubectl config get-contexts
 +
 +# переключиться на другое пространство имён в текущем контексте
 +kubectl config set-context $(kubectl config current-context) --namespace=another
 +</code>
 +
 +https://kube.academy/courses/kubernetes-core-concepts-part-3/lessons/lab-resource-organization
 +<code bash>
 +# jsonpath - фильтр сырого вывода json. Ещё опции вывода - yaml, json, wide
 +kubectl get deployment nginx -o jsonpath='{.status.availableReplicas}'
 +# Перенаправить порт из пода (8080) на локальную машину (8000). Потом можно делать запросы, например, curl-ом - localhost:8000
 +kubectl port-forward <pod_id> 8000:8080
 +</code>
 +https://kubernetes.io/docs/reference/kubectl/jsonpath
 +
 +Квота ресурсов для namespace задаётся объектом ResourceQuota.
 +<code yaml>
 +apiVersion: v1
 +kind: ResourceQuota
 +metadata:
 +  name: dev-quota
 +  namespace: dev
 +spec:
 +  hard:
 +    pods: "10"
 +    requests.cpu: "4"
 +    requests.memory: 5Gi
 +    limits.cpu: "10"
 +    limits.memory: 10Gi
 +</code>
 +===== Stateful apps =====
 +Хранилище для таких приложений может быть разных типов. Работает на уровне пода, подобно хранилищу для виртуалки.
 +
 +Особый тип хранилища - **EmptyDir.** Временный каталог, существует только когда под запущен. Нужен для организации общего каталога между контейнерами внутри пода.
 +
 +**Persistent Volume** - не зависит от времени жизни пода, кочует вместе с ним с хоста на хост. Есть опция hostPath, указывающая путь на ноде, но без крайней необходимости её лучше не применять.
 +
 +**Persistent Volume Claims (PVC)** - запрос на организацию хранилища с заданными параметрами (размер, режим доступа и т. д.)
 +
 +Хранилища могут быть созданы статически (руками), и динамически (по запросу к API провайдера типа Amazon EBS).
 +
 +Для PVC создаётся объект ''kind: PersistentVolumeClaim'' - файлик с описанием (некий стандартизованный аналог служебной записки в ИТ-отдел на предоставление места на диске), а в ''spec:'' пода пишется раздел ''volumes:'', который ссылается на соответствующий объект PVC, а в описании контейнеров пишется подраздел ''volumeMounts:'', монтирующий нужные тома к нужным контейнерам. Если при применении файла PVC такое хранилище уже есть, то просто идёт привязка хранилища к PVC, если нет - хранилище создаётся (если используется динамическое создание).
 +
 +Чтобы у каждого пода в деплое было своё хранилище, а не одно общее, используется ''kind: statefulSet'' - объект, как бы являющийся расширенной версией деплоя. Он создаёт более стабильные имена подов ''${имя_statefulset}-${порядковый_номер}.${имя_сервиса}.${namespace}'', чтобы подходить для систем со своей кластерной организацией, например, БД, которые не любят случайных часто меняющихся имён. StatefulSet также умеет создавать и PVC, которые привязываются к каждому задекларированному поду, создавая уникальные комбинации PVC-Pod. Порядок развёртывания в StatefulSet последовательный: поды создаются от меньшего номера к большему (1->2->3->4), а удаляются от большего к меньшему (4->3->2->1) - это также связано с кластерами БД (см. MariaDB Galera cluster). Перед развёртыванием все поды должны быть запущены или готовы (Running or Ready).
 +
 +https://kube.academy/courses/kubernetes-core-concepts-part-4/lessons/lab-stateful-applications
 +
 +Пример объекта StatefulSet
 +<code yaml>
 +apiVersion: apps/v1
 +kind: StatefulSet
 +metadata: 
 +  name: gowebapp-mysql
 +  labels:
 +    app: gowebapp-mysql
 +    tier: backend
 +spec:
 +  serviceName: gowebapp-mysql
 +  replicas: 1
 +  selector:
 +    matchLabels:
 +      app: gowebapp-mysql
 +      tier: backend
 +  template:
 +    metadata:
 +      labels:
 +        app: gowebapp-mysql
 +        tier: backend
 +    spec:
 +      containers:
 +      - name: gowebapp-mysql
 +        env:
 +        - name: MYSQL_ROOT_PASSWORD
 +          value: mypassword
 +        image: gowebapp-mysql:v1
 +        ports:
 +        - containerPort: 3306
 +        livenessProbe:
 +          tcpSocket:
 +            port: 3306
 +          initialDelaySeconds: 20
 +          periodSeconds: 5
 +          timeoutSeconds: 1
 +        readinessProbe:
 +          exec:
 +            command: ["mysql", "-uroot", "-pmypassword", "-e", "use gowebapp; select count(*) from user"]
 +          initialDelaySeconds: 25
 +          periodSeconds: 10
 +          timeoutSeconds: 1
 +        volumeMounts:
 +        - name: mysql-pv
 +          mountPath: /var/lib/mysql
 +  volumeClaimTemplates:
 +  - metadata:
 +      name: mysql-pv
 +    spec:
 +      accessModes: [ "ReadWriteOnce" ]
 +      resources:
 +        requests:
 +          storage: 5Gi
 +</code>
 +
 +===== Динамическая конфигурация приложения =====
 +Когда что-то в конфигурации занесено жёстко и в явном виде, это потенциальный источник проблем. Для решения используется объект ''ConfigMaps'', где перечислены настройки подов и на которые можно ссылаться
 +  - как на переменные окружения - в разделе ''spec.containers'' пода либо полностью (''configMapRef:''), либо дёргать оттуда ключи по отдельности (''configMapKeyRef:'')
 +  - как на том - том задаётся как configMap и в контейнере он монтируется по определённому пути. Также можно монтировать configMap как полностью, так и отдельные ключи.
 +
 +Разница в подходах в том, что в случае переменных окружения надо перезапускать контейнер для изменения настроек, в то время как со смонтированным томом настройки в каталоге (там 2 файла) будут обновлены сразу же.
 +
 +<code yaml>
 +        volumeMounts:
 +        - name: config-volume 
 +          mountPath: '/opt/gowebapp/config'
 +      volumes:
 +      - name: config-volume
 +        configMap:
 +          name: gowebapp 
 +          items:
 +          - key: webapp-config-json
 +            path: config.json
 +</code>
 +
 +Секретные данные (''kind: Secret'') должны быть закодированы в Base64 и могут так же быть представлены подам, как и configMaps - в виде переменных или томов. Secret необязателен к использованию, если уже есть какой-то альтернативный способ хранения секретных данных.
 +
 +https://kube.academy/courses/kubernetes-core-concepts-part-4/lessons/lab-dynamic-application-configuration
 +
 +<code bash>
 +kubectl create secret generic mysql --from-literal=password=mypassword
 +</code>
 +
 +Потом в файле StatefulSet для mysql заменить
 +<code yaml>
 +        env:
 +        - name: MYSQL_ROOT_PASSWORD
 +          value: mypassword
 +# на
 +        env:
 +        - name: MYSQL_ROOT_PASSWORD
 +          valueFrom:       
 +            secretKeyRef:
 +              name: mysql
 +              key: password
 +              
 +# в разделе readinessProbe: заменить пароль на переменную
 +        readinessProbe:
 +          exec:
 +            command: ["mysql", "-uroot", "-p${MYSQL_ROOT_PASSWORD}", "-e", "use gowebapp; select count(*) from user"]
 +</code>
 +
 +===== Дополнительная нагрузка =====
 +Помимо сервисов, работающих, пока они не будут остановлены или не упадут сами, существуют задачи, которые должны работать временно. Это ''kind: Job''. Они работают до получения результата, основаны на подах/контейнерах и будут пытаться выполнить задачу неограниченное кол-во раз (если не задано иное). Так как после подов и задач остаются логи, результаты их работы и т. п., админ решает, как чистить всё это, не k8s. Задачи бывают непараллельные (один под) и параллельные (несколько).
 +
 +<code yaml>
 +apiVersion: batch/v1
 +kind: Job
 +metadata:
 +  name: pi
 +spec:
 +  template:
 +    metadata:
 +      name: pi
 +    spec:
 +      securityContext:
 +        runAsUser: 1000
 +      containers:
 +      - name: pi
 +        image: perl
 +        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
 +      restartPolicy: Never
 +</code>
 +
 +Посмотреть результаты вышеописанного задания можно командой ''kubectl logs <pod-name>''.
 +
 +Параллельные задания с очередью (parallel jobs with a work queue) - запуск нескольких подов, каждый из которых берёт на себя часть очереди на обработку - как многопоточная обработка процессорными ядрами. Для  этого типа задания нужно
 +  * оставить ''.spec.completions'' незаданным
 +  * каждый под должен иметь возможность проверить, пуста ли очередь и нужно ли выйти
 +  * когда под завершил работу успешно, новый не создаётся
 +  * когда хотя бы один под завершил работу успешно и остальные были уничтожены, задание также считается завершённым успешно
 +  * когда любой под завершил работу успешно, остальные уже не должны ничего делать и также начинают завершать работу
 +
 +<code yaml>
 +apiVersion: batch/v1
 +kind: Job
 +metadata:
 +  name: primes-parallel-wq
 +  labels:
 +    app: primes
 +spec:
 +  parallelism: 3
 +  template:
 +    metadata:
 +      name: primes
 +      labels:
 +        app: primes
 +    spec:
 +      securityContext:
 +        runAsUser: 1000
 +      containers:
 +      - name: primes
 +        image: debian:stable-slim
 +        command: ["bash"]
 +        args: ["-c",  "current=0; max=110; echo 1; echo 2; for((i=3;i<=max;)); do for((j=i-1;j>=2;)); do if [  `expr $i % $j` -ne 0 ] ; then current=1; else current=0; break; fi; j=`expr $j - 1`; done; if [ $current -eq 1 ] ; then echo $i; fi; i=`expr $i + 1`; done"]
 +      restartPolicy: Never
 +</code>
 +https://kubernetes.io/docs/tasks/job/fine-parallel-processing-work-queue/
 +
 +Также существуют CronJobs - аналог cron в линуксе. Для каждого запуска создаётся новый job. Хранится 3 успешных и 1 неуспешный job. Jobs должны быть идемпотентными, т. е., их можно вызывать повторно для получения того же результата.
 +
 +<code>
 +apiVersion: batch/v1beta1
 +kind: CronJob
 +metadata:
 +  name: hello
 +spec:
 +  schedule: "*/1 * * * *"
 +  jobTemplate:
 +    spec:
 +      template:
 +        spec:
 +          securityContext:
 +            runAsUser: 1000
 +          containers:
 +          - name: hello
 +            image: busybox
 +            args:
 +            - /bin/sh
 +            - -c
 +            - date; echo Hello from the Kubernetes cluster
 +          restartPolicy: OnFailure
 +</code>
 +
 +DaemonSet - объект, следящий за тем, чтобы копия конкретного пода была запущена на всех хостах. Это нужно чаще всего для задач администрирования кластера, агентов логирования и мониторинга.
 +
 +https://kube.academy/courses/kubernetes-core-concepts-part-5/lessons/lab-additional-workloads
 +
 +===== Безопасность =====
 +https://kube.academy/courses/kubernetes-core-concepts-part-5/lessons/security
 +
 +Объект **NetworkPolicy** задаёт, как группы подов могут соединяться друг с другом и с другими сетевыми конечными точками. Для выбора подов используются ярлыки и задаются правила, какой тип трафика разрешён на эти поды.
 +
 +Изначально все поды внутри пространства имён могут обмениваться любым трафиком. Когда внутри пространства имён применяется сетевая политика, то поды, которые в ней выбраны, будут отбрасывать любые подключения, которые не указаны в политике. Остальные поды, не охваченные политикой, продолжат принимать любой трафик.
 +
 +Пример политики:
 +<code yaml>
 +apiVersion: networking.k8s.io/v1
 +kind: NetworkPolicy
 +metadata:
 +  name: test-network-policy
 +  namespace:default
 +spec:
 +  podSelector:
 +    matchLabels:
 +      tier: backend
 +  policyTypes:
 +  - Ingress
 +  - Egress
 +</code>
 +
 +Правила Ingress-Egress, на которые идёт ссылка в политике. Логика: если несколько ''from:'' - то это логика И, если один с несколькими селекторами - ИЛИ. В данном случае "трафик из фронтенда проекта gowebapp". Egress - исходящий трафик, в данном случае вовне кластера на контроллер домена для аутентификации.
 +<code yaml>
 +ingress:
 +  - from:
 +    - namespaceSelector:
 +        matchLabels:
 +          project: gowebapp
 +  - from:
 +    - podSelector:
 +        matchLabels:
 +          tier: frontend
 +    ports:
 +    - protocol: TCP
 +      port: 3306
 +egress:
 +  - to:
 +    - ipBlock:
 +        cidr: 10.0.0.0/24
 +    ports:
 +    - protocol: TCP
 +      port: 389
 +
 +</code>
 +
 +**SecurityContext** - часть ''spec:'' пода, где описывается контекст безопасности, например, runAsUser/runAsGroup, SELinux, пути на ноде, которые могут быть смонтированы и т. д.
 +<code yaml>
 +apiVersion: v1
 +kind: Pod
 +metadata:
 +  name: myapp
 +spec:
 +  securityContext:
 +    runAsUser: 1000
 +  containers:
 +  - name: myapp
 +    image: myapp:v1
 +    securityContext:
 +      allowPrivilegeEscalation: false
 +  - name: sidecar
 +    image: mysidecar:v1
 +    securityContext:
 +      runAsUser: 2000
 +</code>
 +В примере выше ''runAsUser:'' на уровне контейнера переопределяет значение, заданное на уровне пода, применяемое ко всем контейнерам в нем.
 +
 +**Права доступа в кластере.** Есть несколько способов аутентификации - клиентские сертификаты (используются для кластерных компонентов), токены (сервисные учётки), внешняя аутентификация (пользователи). Сервисные учётки создаются объектом ServiceAccount, и в кластере появляются файлы с описанием той учётки - принадлежность к пространству имён, URL API-сервера, какой сертификат и учётные данные нужно использовать, что избавляет от указания учётных данных в явном виде в приложении. Большинство клиентских библиотек Python, Java и т. п. смотрят в каталог кластера с сервисными учётными данными по умолчанию.
 +
 +**Ролевая модель доступа (RBAC).** Строится на основе групп или ролей - ''kind: Role''. Роль - это набор разрешений (правил). Есть стандартные роли - cluster-admin, admin, edit, view.
 +
 +Пример RBAC, привязанный к пространству имён и позволяющий только get secrets.
 +<code yaml>
 +kind: Role
 +apiVersion: rbac.authorization.k8s.io/v1
 +metadata:
 +  namespace: default
 +  name: my-app-secret-reader
 +rules:
 +- apiGroups: [""]
 +  resources: ["secrets"]
 +  verbs: ["get"]
 +</code>
 +
 +Есть также ''kind: ClusterRole'' - то же самое, но область действия - кластер в целом. Здесь позволены операции, перечисленные в verbs, только к подам с именами, перечисленными в resourceNames.
 +<code yaml>
 +kind: ClusterRole
 +apiVersion: rbac.authorization.k8s.io/v1
 +metadata:
 +  name: pod-reader
 +rules:
 +- apiGroups: [""]
 +  resources: ["pods"]
 +  verbs: ["get", "watch", "list"]
 +  resourceNames: ["nginx", "mariadb"]
 +</code>
 +
 +Вывести список общекластерных ресурсов и привязанных к пространствам имён
 +<code bash>
 +kubectl api-resources --namespaced=false
 +kubectl api-resources --namespaced=true
 +</code>
 +
 +Роли привязываются к пользователям, группам и сервисным учёткам с помощью ''kind: RoleBinding'' (действие в рамках пространства имён). В рамках кластера ''kind: ClusterRoleBinding''
 +
 +<code yaml>
 +kind: RoleBinding
 +apiVersion: rbac.authorization.k8s.io/v1
 +metadata:
 +  name: my-app-read-secret
 +  namespace: default
 +subjects:
 +- kind: ServiceAccount
 +  name: my-app
 +  apiGroup: rbac.authorization.k8s.io
 +roleRef:
 +  kind: Role
 +  name: my-app-secret-reader
 +  apiGroup: rbac.authorization.k8s.io
 +</code>
 +
 +Проверить возможность выполнять команды
 +<code bash>
 +kubectl auth can-i create deployments [--as user --namespace test]
 +kubectl auth can-i delete nodes [--as user --namespace test]
 +</code>
 +===== Taints & tolerations =====
 +Политика запуска подов на хостах. Taints применяются к хостам, например, ''app=web'', а tolerations - к подам. Разместиться на такой ноде с указанным тейнтом может только под, у которого соответствует настройка tolerations.
 +<code bash>
 +kubectl taint nodes node1 app=web:NoSchedule
 +# убрать
 +kubectl taint nodes node1 app=web:NoSchedule-
 +</code>
 +Эффект - как действовать, если под нетолерантен к указанному тейнту на хосте.
 +  * NoSchedule - не размещать
 +  * PreferNoSchedule - стараться не размещать
 +  * NoExecute - не размещать и выгнать уже существующие
 +
 +''spec.tolerations'' в определении пода:
 +<code yaml>
 +spec:
 +  tolerations:
 +  - key: "app"
 +    operator: "Equal"
 +    value: "web"
 +    effect: "NoSchedule"
 +</code>
 +Двойные кавычки в данном случае обязательны.
 +
 +Taints & tolerations применяется на мастер-нодах, чтобы там не размещалась рабочая нагрузка - ''kubectl describe node <master> |grep Taint''.
 +
 +Taints & tolerations - это только средство ограничения. **Если они настроены, то это не значит, что толерантные поды всегда будут размещаться на соответствующих хостах. Эти поды могут размещаться и на хостах, где нет ограничений.** А для именно привязки к нодам нужен другой параметр - ''NodeAffinity''.
 +
 +===== Node selectors & affinity =====
 +Иногда нужно, чтобы под запускался на конкретном хосте, например, более производительном. Для этого нужно присвоить хосту ярлык
 +<code bash>
 +kubectl label nodes <nodename> <key=value>
 +</code>
 +и потом в ''spec.nodeSelector'' пода прописать соответствующий селектор.
 +
 +Но если условия более сложные, например, запускаться на нескольких типах нод (OR) или запускаться на всех нодах, кроме указанных (NOT), тут вступает в действие ''spec.affinity'' в свойствах пода.
 +<code yaml>
 +spec:
 +  affinity:
 +    nodeAffinity:
 +      requiredDuringSchedulingIgnoredDuringExecution:
 +        nodeSelectorTerms:
 +        - matchExpressions:
 +          - key: power
 +            operator: In
 +            values:
 +            - high
 +            - medium
 +</code>
 +  * requiredDuringSchedulingIgnoredDuringExecution - не размещать новые, не трогать уже работающие
 +  * preferredDuringSchedulingIgnoredDuringExecution - стараться не размещать новые, не трогать уже работающие
 +  * requiredDuringSchedulingRequiredDuringExecution - сейчас такой опции нет, есть в планах - не размещать новые, выгонять работающие
 +
 +https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/
 +
 +**Если настроено nodeAffinity, то это не гарантирует, что на этих хостах не будут размещаться другие поды.** Чтобы гарантировать жёсткую привязку хостов и подов, нужно сочетать taints и affinities.
 +
 +===== Static pods =====
 +В отсутствие мастер-ноды, kubelet на рабочей ноде остаётся наедине с собой, и взять инструкции, что ему запускать, неоткуда. Тем не менее, есть каталог, который он регулярно проверяет, куда можно положить манифесты подов. При изменении манифеста под перезапускается с соответствующими параметрами, при удалении - под удаляется. Кроме подов, ничего больше запустить нельзя, т. к. для этого нужны контроллеры на мастер-ноде.
 +
 +Путь к каталогу манифестов задаётся параметром ''--pod-manifest-path=/etc/kubernetes/manifests'' как аргумент запуска kubelet.service (путь к каталогу может быть любым). Другой вариант - вписать в аргумент запуска ''--config=kubeconfig.yaml'' и задавать этот параметр уже в файле как ''staticPodPath: /etc/kubernetes/manifests''.
 +
 +Kubelet, будучи в кластере, запускает и статические, и распределённые на него кластером поды. С мастер-ноды видно все поды, в т. ч. и статические, но статические не могут быть изменены или удалены - для этого нужно работать с манифестами непосредственно на ноде. У статических подов имя родительского хоста прописано в конце их имени.
 +
 +Статические поды нужны для работы компонентов мастер-хостов, таких как etcd, api и т. п. Они запускаются как поды, а если поды удаляются, то в соответствии с наличием манифестов в соответствующем каталоге, они запускаются вновь. См. пространство имён ''kube-system''.
 +
 +^ Static PODs      ^ DaemonSets       ^
 +| Создаются kubelet-ом непосредственно на узле    | Создаётся сервером API (DaemonSet controller)    |
 +| Пример применения: компоненты Control Plane    | Пример применения: агенты мониторинга, логирования  |
 +|  Игнорируются Kube-scheduler-ом  ||
 +===== Multiple schedulers =====
 +Помимо основного планировщика (или вместо него) можно установить дополнительные, указав имя при запуске аргументом ''--scheduler-name=''. Или с помощью kubeadm, который запускает планировщик как под, описание которого будет в каталоге манифестов.
 +
 +Чтобы запустить доп. планировщик, нужно скопировать исходный kube-scheduler.yaml и изменить его под свои нужды, добавив в ''spec.command'' параметр ''--scheduler-name=''. Также, нужно настроить планировщика-лидера ''--leader-elect=true'', если мастер-нод несколько. Также нужно настраивать <del>''--lock-object-name=<имя планировщика>''</del> (устарело) ''leader-elect-resource-name=<имя планировщика>''.
 +
 +После настройки планировщика (его под должен быть в состоянии Running) его имя нужно указывать в ''spec.schedulerName'' пода, которому требуется использовать этот планировщик. При проблемах в работе планировщика состояние пода, который его использует, будет Pending.
 +
 +Проверить, что за планировщик использовался для размещения пода, можно с помощью ''kubectl get events''. Там будет SOURCE - имя планировщика, REASON - Scheduled и в сообщении будет написано об успешном размещении.
 +
 +Логи самого планировщика (не забыть про namespace)
 +<code bash>
 +kubectl logs <scheduler name> -n kube-system
 +</code>
 +https://kubernetes.io/docs/tasks/extend-kubernetes/configure-multiple-schedulers/
 +
 +===== Мониторинг и логирование =====
 +Есть ряд решений, как бесплатных (Metrics server, Prometheus, Elastic stack и т. д.), так и коммерческих (Datadog, Dynatrace и т. д.)
 +
 +Metrics server (ранее Heapster) - ставится один на кластер. Собирает метрики с хостов и подов и хранит их только в памяти, на диск не пишет - т. е., историю не посмотреть. Схема работы такая: у kubelet на каждой ноде есть компонент cAdvisor, который через API передаёт данные на Metrics server.
 +
 +Установить Metrics server
 +<code bash>
 +# в minikube
 +minikube addons enable metrics-server
 +# в microk8s
 +microk8s enable metrics-server
 +# для прочих
 +git clone https://github.com/kubernetes-incubator/metrics-server.git
 +kubectl create -f deploy/1.8+/
 +</code>
 +Через некоторое время будет доступна информация о производительности
 +<code bash>
 +kubectl top node
 +kubectl top pod
 +</code>
 +
 +Логи в k8s можно смотреть примерно так же, как и в докере:
 +<code bash>
 +kubectl logs -f <podName>
 +</code>
 +Но есть нюанс - если в поде несколько контейнеров, то нужно указывать имя контейнера после имени пода.
 +<code bash>
 +kubectl logs -f <podName> <containerName>
 +</code>
 +
 +===== Команды и аргументы =====
 +<code bash>
 +# В Докере команда передаётся в момент непосредственного запуска, например
 +docker run ubuntu sleep 5
 +# Чтобы указать команду в образе, используется формат
 +CMD sleep 5 # shell command
 +# или
 +CMD ["sleep", "5"] # json format
 +</code>
 +Разница в том, что в случае с форматом json первый аргумент должен быть исполняемым (sleep). Также, в случае с json, в процессах не будет дополнительно висеть shell как среда запуска. Теперь, если построить образ, например, ''docker build -t sleeper .'', то контейнер будет стартовать и через 5 сек завершать работу.
 +
 +Изменить время с 5 сек на другое можно, запустив построенный образ: ''docker run sleeper sleep 10'', но это неправильно, т. к. не использует встроенной в образ команды и повторяет её. Для этого вместо CMD используется
 +<code bash>
 +ENTRYPOINT ["sleep"]
 +</code>
 +В таком случае, образ можно запустить так: ''docker run sleeper 10'', но в этом случае нужно всегда указывать значение в командной строке. Если просто запустить ''docker run sleeper'', то будет ошибка, т. к. отсутствует необходимый аргумент. Чтобы задать значение по умолчанию, нужно добавить строку CMD к ENTRYPOINT:
 +<code bash>
 +ENTRYPOINT ["sleep"]
 +CMD ["5"]
 +</code>
 +Тогда можно запускать просто ''docker run sleeper'', и тогда будет всё по умолчанию, или ''docker run sleeper 10'', что задаст своё значение. Можно даже при большом желании переопределить и саму команду: ''docker run --entrypoint ls sleeper / -lh''
 +
 +В k8s это пишется в ''spec.containers'' как 
 +<code yaml>
 +spec:
 +  containers:
 +  - name: sleeper
 +    image: sleeper
 +    command: ["ls"]
 +    args: ["/", "-lh"]
 +</code>
 +Т. е., command - это аналог ENTRYPOINT, а args - CMD.
 +
 +===== Переменные =====
 +==== В ''spec.containers'' напрямую ====
 +<code yaml>
 +spec:
 +  containers:
 +  - name: webapp-db
 +    env:
 +    - name: MYSQL_ROOT_PASSWORD
 +      value: mypassword
 +</code>
 +
 +==== С помощью ConfigMap ====
 +
 +ConfigMaps нужны для централизованного управления парами "ключ-значение", это хорошо помогает, когда много манифестов подов и замучаешься править их все по отдельности, лучше ссылаться оттуда на ConfigMap.
 +
 +Создать ConfigMap можно, как и другие объекты в k8s, 2 способами: императивным (командой)
 +<code bash>
 +kubectl create configmap <name> --from-literal=key=value --from-literal=key2=value2
 +kubectl create configmap <name> --from-file=<path>
 +</code>
 +и декларативным, нарисовав соотв. файл (вместо раздела spec здесь data)
 +<code yaml>
 +apiVersion: v1
 +kind: ConfigMap
 +metadata:
 +  name: webapp-config
 +data:
 +  APP_COLOR: red
 +  APP_MODE: test
 +</code>
 +и применив его. Посмотреть уже существующие - ''kubectl get/describe configmaps''.
 +
 +Как сослаться на ConfigMap в описании контейнера:
 +<code yaml>
 +# целиком
 +spec:
 +  containers:
 +    envFrom:
 +    - configMapRef:
 +      name: webapp-config
 +# или только на значение
 +    env:
 +    - name: APP_COLOR
 +      valueFrom:
 +        configMapKeyRef:
 +          name: webapp-config
 +          key: APP_COLOR
 +</code>
 +
 +Также, есть возможность использовать ConfigMap как volume:
 +<code yaml>
 +volumes:
 +- name: webapp-config-vol
 +  configMap:
 +    name: webapp-config
 +</code>
 +
 +==== С помощью Secrets ====
 +Secrets нужны для хранения конфиденциальных данных. Это то же самое, что и ConfigMap, только данные там зашифрованы.
 +
 +<code bash>
 +kubectl create secret generic <name> --from-literal=key=value --from-literal=key2=value2
 +kubectl create secret generic <name> --from-file=<path>
 +</code>
 +
 +Описание объекта Secret для декларативного применения:
 +<code yaml>
 +apiVersion: v1
 +kind: Secret
 +metadata:
 +  name: webapp-secret
 +data:
 +  DB_HOST: mysql
 +  DB_USER: root
 +  DB_PASSWORD: P@ssw0rd
 +</code>
 +Вопрос только в том, что для Secret нужно указывать данные не в plain text, а закодированным в base64.
 +<code bash>
 +echo -n "mysql" |base64
 +echo -n "root" |base64
 +echo -n "P@ssw0rd" |base64
 +</code>
 +Так что описание должно выглядеть так:
 +<code yaml>
 +apiVersion: v1
 +kind: Secret
 +metadata:
 +  name: webapp-secret
 +data:
 +  DB_HOST: bXlzcWw=
 +  DB_USER: cm9vdA==
 +  DB_PASSWORD: UEBzc3cwcmQ=
 +</code>
 +Просмотр
 +<code bash>
 +kubectl get/describe secrets
 +kubectl get secret <name> -o yaml
 +</code>
 +Раскодировать значения
 +<code bash>
 +echo -n "bXlzcWw=" |base64 -d
 +echo -n "cm9vdA==" |base64 -d
 +echo -n "UEBzc3cwcmQ=" |base64 -d
 +</code>
 +
 +Как сослаться на Secret в описании контейнера
 +<code yaml>
 +# целиком
 +spec:
 +  containers:
 +    envFrom:
 +    - secretRef:
 +      name: webapp-secret
 +# или только на значение
 +    env:
 +    - name: DB_PASSWORD
 +      valueFrom:
 +        secretKeyRef:
 +          name: webapp-secret
 +          key: DB_PASSWORD
 +</code>
 +
 +Также, есть возможность использовать Secret как volume:
 +<code yaml>
 +volumes:
 +- name: webapp-secret-vol
 +  secret:
 +    secretName: webapp-secret
 +</code>
 +
 +В этом случае в контейнере появляется каталог, где секреты лежат как файлы (здесь: DB_HOST, DB_USER, DB_PASSWORD) с содержимым в виде соответствующих ключей.
 +
 +Особый секрет docker-registry для логина в приватный репозиторий Docker
 +<code bash>
 +kubectl create secret docker-registry docker-config-secret --docker-server=gitlab.example.com:5050 --docker-username=ВАШ_ЛОГИН --docker-password=ВАШ_ПАРОЛЬ --dry-run=client -o yaml > docker-config-secret.yaml
 +</code>
 +===== Обслуживание кластера =====
 +Когда хост становится недоступным, то, если он не вернулся в строй через 5 минут, кластер помечает его как умерший и запускает его поды на других хостах. Это называется ''pod eviction'' и регулируется менеджером контроллеров k8s ''kuber-controller-manager --pod-eviction-timeout=5m0s''.
 +
 +Если прошло меньше 5 минут, то поды заново запускаются на хосте. Нужно также помнить о том, что если под не управляется деплоем или репликой, то, в случае падения хоста и прошествии 5 минут этот под уже не запустится, когда хост вернётся, и нужно будет заново его создавать.
 +
 +Чтобы выгнать нагрузку с хоста, есть команда ''kubectl drain <nodename>''. Хост помечается как unschedulable, и даже когда хост возвращается в строй, он остаётся в том же статусе. DaemonSets, если они есть, не уйдут с ноды, поэтому при их наличии нужно добавлять параметр ''kubectl drain <nodename> --ignore-daemonsets''.
 +
 +Для возвращения его в работу нужно дать команду ''kubectl uncordon <nodename>''.
 +
 +Команда ''kubectl cordon <nodename>'' помечает ноду как unschedulable, но не выгоняет оттуда уже работающие поды, а лишь препятствует запуску на ней новых.
 +
 +==== Обновление ====
 +Версия API-сервера не должна быть старее, чем любая версия прочих компонентов. Controller manager и Sheduler могут отставать по версии от API на одну минорную версию, а kubelet и kube-proxy - на две. Исключение - kubectl, который может быть как новее, так и старше на одну минорную версию, чем API.
 +
 +Обновление поддерживается в пределах трёх минорных версий, например, если кластер работает на версии 1.10, то он может обновиться на 1.11 и 1.12, но не на 1.13. Рекомендуется обновляться последовательно по минорным версиям. В облаках обновление - это нажатие пары кнопок. В kubeadm - ''kubectl upgrade plan'', ''kubectl upgrade apply''. Если кластер собран вручную, то придётся обновлять все компоненты самостоятельно. Kubeadm обновляет все компоненты, кроме kubelet, который нужно обновлять самостоятельно на каждой ноде. Перед обновлением нужно обновить сам kubeadm, например, ''apt upgrade -y kubeadm=1.12.0-00''.
 +
 +Сначала нужно обновлять мастер-ноды (например, ''kubectl upgrade apply v1.12.0''), потом - рабочие. Во время обновления мастера функции управления кластером недоступны, но рабочие ноды всё равно продолжают работать как обычно. Но если на рабочих нодах что-то упадёт, то подняться оно не сможет, т. к. нет управляющих компонентов.
 +
 +Если после обновления мастер-ноды запустить ''kubectl get nodes'', то в колонке VERSION покажется прежняя версия, потому что там отображается версия kubelet, которая ещё не была обновлена.
 +<code bash>
 +# Обновить kubelet
 +apt upgrade -y kubelet=1.12.0-00
 +systemctl restart kubelet
 +</code>
 +После этого ''kubectl get nodes'' уже покажет новую версию на мастер-ноде.
 +
 +Рабочие ноды можно обновить все сразу, но тогда будет простой. Можно обновлять рабочие ноды последовательно, предварительно освобождая их от нагрузки. Есть третий вариант - добавлять ноды с новой версией k8s в кластер, переезжать на них, а ноды со старой версией выводить из кластера. Примерный алгоритм действий на рабочей ноде
 +<code bash>
 +# на мастере
 +kubectl drain <nodename>
 +# на ноде
 +apt upgrade -y kubeadm=1.12.0-00
 +apt upgrade -y kubelet=1.12.0-00
 +kubeadm upgrade node config --kubelet-version v1.12.0
 +systemctl restart kubelet
 +# на мастере
 +kubectl uncordon <nodename>
 +</code>
 +
 +===== Резервное копирование =====
 +Бэкапить надо конфиги, ETCD-кластер и постоянные тома.
 +
 +Рекомендуется использовать конфигурационные файлы и декларативный путь их применения. Даже если всё навернулось, с помощью конфигов можно быстро восстановить всё обратно. Есть способ и вытащить все созданные объекты в кластере, даже если кто-то ранее пользовался императивным способом создания объектов:
 +<code bash>
 +kubectl get all -A -o yaml > all-deploys.yaml
 +</code>
 +Тем не менее, это не охватывает все группы ресурсов кластера. Есть сторонние решения для резервного копирования через API типа Velero.
 +
 +Вместо запросов к API можно делать резервную копию ETCD-кластера. В строке его запуска есть параметр ''--data-dir=/var/lib/etcd'', указывающий на каталог с конфигурацией. ETCD умеет создавать снапшоты.
 +<WRAP round info 100%>
 +При всех командах к etcd нужно указывать данные для аутентификации
 +<code bash>
 +etcdctl snapshot save snapshot.db \
 +--endpoints=https://192.168.1.100:2379 \ # адрес кластера etcd (если etcd не локальный)
 +--cacert=/etc/etcd/ca.crt \ # сертификат УЦ
 +--cert=/etc/etcd/etcd-server.crt \ # сертификат кластера
 +--key=/etc/etcd/etcd-server.key # ключ
 +</code>
 +</WRAP>
 +
 +<code bash>
 +# сделать снапшот
 +etcdctl snapshot save snapshot.db
 +# состояние снапшота
 +etcdctl snapshot status snapshot.db
 +# чтобы восстановить, нужно сначала остановить сервер API
 +service kube-apiserver stop
 +# а потом восстановить снапшот, указав НОВЫЙ каталог с конфигурацией ETCD
 +etcdctl snapshot restore snapshot.db --data-dir=/var/lib/etcd-from-backup
 +</code>
 +Будет инициализирована новая конфигурация кластера и регистрация его членов как новых членов нового кластера, что препятствует их регистрации в старом кластере. Затем нужно прописать этот каталог в конфигурацию ''etcd.service''. Далее
 +<code bash>
 +# перечитать конфиг сервисов
 +systemctl daemon-reload
 +# перезапустить etcd
 +service etcd restart
 +# запустить API
 +service kube-apiserver start
 +</code>
 +
 +Резервное копирование конфигурации ресурсов и ETCD имеют свои достоинства и недостатки, но в случае облаков, когда нет прямого доступа к etcd-кластеру, запросы к API для резервного копирования будут лучшим выбором.
 +
 +===== Безопасность =====
 +Первая линия защиты - доступ к API.
 +  - Кто может получать доступ (аутентификация)? Способы регулирования - имя и пароль/токен, сертификаты, внешний сервис типа LDAP и сервисные учётки для машин.
 +  - Что может делать получивший доступ (авторизация)? Способы регулирования - RBAC, ABAC, Node authorization, Webhook mode.
 +
 +В k8s нельзя завести учётки пользователей, но можно создать сервисные учётки - ''kubectl create serviceaccount <name>''. Аутентификацией пользователей занимается сервер API перед обработкой запросов от них.
 +
 +==== Статические файлы-списки с паролями или токенами ====
 +Самая простая форма. Это CSV, где перечислены учётные данные пользователей (пароль, имя, ID, группа).
 +<code>
 +password,user1,u0001,group1
 +password,user2,u0002,group2
 +password,user3,u0003,group1
 +</code>
 +Путь к этому файлу нужно прописать в аргументе запуска kube-apiserver ''--basic-auth-file=user-details.csv''. Если использовать kubeadm, то тогда можно в описание статического пода ''spec.containers.command'' kube-apiserver.yaml прописать тот же аргумент. При сохранении изменений под автоматически перезапустится. Пример команды, использующей логин и пароль: ''curl -v -k https://master-node-ip:6443/api/v1/pods -u "user1:password"''.
 +
 +Файл с токенами подобен парольному, просто вместо паролей там токены и аргумент другой - ''--token-auth-file=user-details.csv''. Пример команды, использующей логин и токен: ''curl -v -k https://master-node-ip:6443/api/v1/pods --header "Authorization: Bearer weoih02939wfJfi393ruUr"''.
 +
 +Статические файлы-списки не рекомендуются к использованию, т. к. учётные данные хранятся в открытом виде.
 +
 +==== Сертификаты ====
 +Каждый компонент кластера подключается к другому по защищённому асимметричным шифрованием соединению, т. е., у каждого есть пара ключей (crt, key). Но, например, у API, который является и сервером, и клиентом (для ETCD и Kubelet) одновременно, может быть несколько пар ключей.
 +
 +Над всем этим должен быть по меньшей мере один УЦ. Можно и второй для пары ETCD - API (как клиента ETCD). УЦ должен подписывать нижестоящие сертификаты.
 +
 +Как создавать сертификаты
 +<code bash>
 +# CA key
 +openssl genrsa -out ca.key 2048
 +# CA csr
 +openssl req -new -key ca.key -subj "/CN=KUBERNETES-CA" -out ca.csr
 +# CA crt (self-signed)
 +openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt
 +# admin key
 +openssl genrsa -out admin.key 2048
 +# admin csr (в subj нужно писать реальное имя аутентификации, и, например, добавить группу)
 +openssl req -new -key admin.key -subj "/CN=kube-admin/O=system:masters" -out admin.csr
 +# admin crt (ca signed)
 +openssl x509 -req -in admin.csr -CA ca.crt -CAkey ca.key -out admin.crt
 +
 +# для системных компонентов к имени нужно добавить "SYSTEM:", например
 +openssl req -new -key kube-scheduler.key -subj "/CN=system:kube-scheduler" -out kube-scheduler.csr
 +openssl req -new -key kube-controller-manager.key -subj "/CN=system:kube-controller-manager" -out kube-controller-manager.csr
 +# наконец, kube-proxy
 +openssl req -new -key kube-proxy.key -subj "/CN=kube-proxy" -out kube-scheduler.csr
 +# далее по аналогии
 +
 +# Использовать сертификат в запросе можно так:
 +curl https://kube-apiserver:6443/api/v1/pods \
 +--key admin.key --cert admin.crt --cacert ca.crt
 +</code>
 +
 +Другой способ - в файле конфигурации
 +<file yaml kube-config.yaml>
 +apiVersion: v1
 +clusters:
 +- cluster:
 +    certificate-authority: ca.crt
 +    server: https://kube-apiserver:6443
 +  name: kubernetes
 +kind: Config
 +users:
 +- name: kubernetes-admin
 +  user:
 +    client-certificate: admin.crt
 +    client-key: admin.key
 +</file>
 +
 +Всем компонентам для проверки подлинности требуется указывать сертификат УЦ - он указывается **в каждом** клиенте и сервере.
 +
 +Если ETCD-серверов несколько, то, помимо пары ключей etcdserver (''--key-file='', ''--cert-file='') генерируется ещё одна - etcdpeer (''--peer-key-file='', ''--peer-cert-file='') для соединения между членами ETCD-кластера.
 +
 +Для API-сервера нужно добавлять все альтернативные имена, например,
 +<code>
 +kubernetes
 +kubernetes.default
 +kubernetes.default.svc
 +kubernetes.default.svc.cluster.local
 +</code>
 +
 +У каждого кублета на нодах тоже должен быть свой сертификат, названный по имени ноды. Так как ноды являются частью системы, то имена в сертификатах должны быть типа system:node:node01, system:node:node02, system:node:node03 и т. д. Также, группа должна быть system:nodes. Прописываются сертификаты в конфигурациях кублетов, естественно, с упоминанием сертификата УЦ.
 +
 +==== Как проверить сертификаты ====
 +<code bash>
 +# Если k8s установлен вручную и работает как сервисы, то поиск строчек запуска (для поиска местоположения сертификатов) нужно делать в сервисах.
 +cat /etc/systemd/system/kube-apiserver.service
 +# и смотреть в логи нужно соответственно
 +journalctl -u etcd.service -l
 +# Если же он установлен с помощью kubeadm, то k8s крутится на статических подах и нужно смотреть строчки запуска там.
 +cat /etc/kubernetes/manifests/kube-apiserver.yaml
 +# логи
 +kubectl logs etcd-master
 +# если нет возможности посмотреть логи через kubectl, тогда через докер
 +docker ps -a
 +docker logs <container name>
 +</code>
 +
 +Создаётся табличка с заголовками типа Component, Type, Cert path, CN, ALT, Org, Issuer, Expiration. Дальше просматриваются свойства сертификатов, и табличка заполняется.
 +<code bash>
 +openssl x509 -in /etc/kubernetes/pki/apiserver.crt -text -noout
 +</code>
 +
 +==== Certificate API ====
 +В Кубере есть инструмент подписи сертификатов, находящийся в Controller manager. По сути, УЦ в Кубере - это просто пара ключей, и они должны храниться в максимально безопасном месте, обычно на мастер-ноде, которая в этом случае является сервером УЦ. Для облегчения работы с сертификатами есть соответствующий API.
 +
 +Теперь, вместо того, чтобы залезать на мастер-ноду и руками подписывать сертификат, создаётся объект CertificateSigningRequest, который позволяет удобно работать с запросами.
 +  - Пользователь создаёт ключ и запрос, и отправляет запрос админу.
 +  - Админ создаёт CertificateSigningRequest, вбивая в ''spec.request'' содержимое файла запроса, закодированное в base64 (например, ''cat user.csr |base64'').
 +  - Теперь запрос можно увидеть, набрав команду ''kubectl get csr'', а одобрить - ''kubectl certificate approve <name>''.
 +
 +Вывести содержимое полученного объекта - ''kubectl get csr user -o yaml''. Из раздела ''status.certificate'' можно скопировать значение и, раскодировав его из base64, получить подписанный сертификат. ''%%echo "YfbuE..=03" |base64 -d > user.crt%%''
 +
 +==== KubeConfig ====
 +Так как на любую команду, если использовать сертификаты для аутентификации, придётся добавлять параметры, указывающие на эти сертификаты, например ''curl https://kube-apiserver:6443/api/v1/pods --key admin.key --cert admin.crt --cacert ca.crt'', есть более удобный способ прописать эти учётные данные в конфигурационный файл ''~/.kube/config''.
 +
 +Этот файл имеет 3 секции - Clusters (перечислены кластеры), Contexts, Users (перечислены учётки). Контекст - это объединение кластеров и пользователей: кто к каким кластерам подключается. В контексте сертификатов сертификат УЦ прописывается в кластерном разделе, а пользовательская пара ключей - в пользовательском.
 +
 +Контекст по умолчанию прописывается в том же файле ''current-context: userName@clusterName''.\\
 +Вывести текущий конфиг: ''kubectl config view''. Можно указать путь к конфигу с помощью параметра ''--kubeconfig=/path/customconf''.\\
 +Изменить контекст в конфиге: ''kubectl config use-context userName@clusterName''.\\
 +Помимо самого кластера, в конфиге можно указать и пространство имён для подключения параметром ''namespace: <namespaceName>'' в ''contexts.name.context''.
 +
 +Можно вместо прописывания пути к файлу (''certificate-authority: /etc/kubernetes/pki/ca.crt'') прописать закодированное в base64 содержимое этого файла (''certificate-authority-data: LKE0I3rH...*h3+='')
 +
 +==== Сервисные учётки ====
 +Нужны для работы приложений с Кубером, например, Prometheus (получение метрик) или Jenkins (создание объектов).
 +<code bash>
 +kubectl create serviceaccount <name>
 +kubectl get serviceaccount
 +</code>
 +Сервисная учётка автоматически создаётся с токеном, использующимся для аутентификации. Токен хранится как связанный с этой учёткой Secret.
 +
 +Стандартно в каждом пространстве имён существует сервисная учётка default, которая автоматически предоставляется подам через смонтированный том, давая доступ к API. Если сервисная учётка нужна другая, её можно указать с описании пода в разделе spec - ''serviceAccount: name-sa''.
 +
 +Если под создан отдельно, то для изменения сервисной учётки в нём нужно удалить его и создать заново. В деплое это делается автоматически при изменении описания пода в нём.
 +
 +Если явно не указана сервисная учётка в описании пода, то автоматически монтируется default. Чтобы отключить это, в spec нужно прописать ''automountServiceAccountToken: false''.
 +
 +==== Защита образов ====
 +В описании контейнера есть имя образа, например, ''image: nginx''. По факту, это ''image: docker.io/nginx/nginx'', т. е., если не указано, то пользователь/учётка подставляется исходя из имени образа/репозитория, а реестр образов по умолчанию это Docker Hub (docker.io). Реестр образов кубера - Google Registry (gcr.io).
 +
 +Если репозиторий приватный, то в Докере нужно сначала зайти в него, а потом уже запускать контейнеры из его образов, например,
 +<code bash>
 +# логин и пароль указываются в интерактивном режиме
 +docker login private-repo.io
 +docker run private-repo.io/apps/app
 +</code>
 +
 +В Кубере, чтобы указать учётные данные для образа из приватного репозитория, указанного в ''image:'', нужно сослаться на Secret типа docker-registry.
 +<code bash>
 +kubectl create secret docker-registry <secret name> \
 +--docker-server= --docker-username= --docker-password= --docker-email=
 +</code>
 +
 +<code yaml>
 +spec:
 +  containers:
 +  - name: app
 +    image: private-repo.io/apps/app
 +  imagePullSecrets:
 +  - name: <secret name>
 +</code>
 +
 +==== securityContext ====
 +В Докере при запуске контейнера можно указывать разные опции безопасности, например
 +<code bash>
 +docker run --user=1000 ubuntu sleep 5000
 +docker run --cap-add NET_ADMIN ubuntu
 +</code>
 +В Кубере, так как контейнеры запускаются внутри подов, опции могут указываться на уровне подов, что повлияет на все контейнеры внутри, и на уровне контейнера. Опция в контейнере перекрывает опцию пода, если она задана и там, и там.
 +<code yaml>
 +apiVersion: v1
 +kind: Pod
 +metadata:
 +  name: app
 +spec:
 +  securityContext:
 +    runAsUser: 1000
 +  containers:
 +  - name: app
 +    image: ubuntu
 +    command: ["sleep", "5000"]
 +    securityContext:
 +      runAsUser: 1000
 +      capabilities:
 +        add: ["NET_ADMIN"]
 +</code>
 +В данном случае, capabilities на уровне пода не поддерживаются.
 +
 +==== Сетевые политики ====
 +<WRAP round info 100%>
 +Не все сетевые решения для k8s поддерживают сетевые политики, например, Kube-router, Calico, Romana, Weave-net - поддерживают, а Flannel - нет.
 +</WRAP>
 +
 +
 +  * Ingress - входящий трафик
 +  * Egress - исходящий трафик
 +
 +Всем подам по умолчанию разрешён любой трафик. К примеру, если приложение состоит из веб-части, api и БД, то веб-часть может слать трафик напрямую к БД, что нежелательно. Поэтому можно нарисовать сетевую политику, которая разрешает определённый трафик с определённых подов. В данном случае, нужно сделать политику для пода БД, которая разрешает Ingress-трафик на порт 3306 c пода API.
 +
 +Привязка политики к поду делается так же, как и реплик с сервисами - через ярлыки и селекторы.
 +
 +<code yaml>
 +apiVersion: networking.k8s.io/v1
 +kind: NetworkPolicy
 +metadata:
 +  name: db-policy
 +spec:
 +  # применяется к подам с ярлыком role: db
 +  podSelector:
 +    matchLabels:
 +      role: db
 +  policyTypes:
 +  - Ingress
 +  - Egress
 +  ingress:
 +  # разрешить трафик с подов с ярлыком name: api-pod
 +  - from:
 +    - podSelector:
 +        matchLabels:
 +          name: api-pod
 +      # только для подов из пространства имён с ярлыком name: prod
 +      # не забыть прописать сначала этот ярлык для пространства имён
 +      namespaceSelector:
 +        matchLabels:
 +          name: prod
 +    # разрешить подключаться внешнему серверу
 +    - ipBlock:
 +        cidr: 192.168.1.10/32
 +    ports:
 +    - protocol: TCP
 +      port: 3306
 +  egress:
 +  # разрешить исходящий трафик на сервер по порту 80
 +  - to:
 +    - ipBlock:
 +      cidr: 192.168.1.20/32
 +    ports:
 +    - protocol: TCP
 +      port: 80
 +</code>
 +
 +Если убрать podSelector и оставить namespaceSelector, то все поды в определённом пространстве имён смогут подключаться к поду БД.
 +
 +Логика в ''spec.ingress.from'': все объекты в списке (начинающиеся на -) - ИЛИ, пункты внутри списка - И. То есть, в примере выше - podSelector И namespaceSelector. Если написать ''- namespaceSelector'', то будет podSelector ИЛИ namespaceSelector, что совершенно изменит логику.
 +
 +Далее нужно применить политику.
 +
 +
 +====== MicroK8S ======
 +https://microk8s.io/\\
 +https://microk8s.io/docs
 +<code bash>
 +# install
 +snap install microk8s --classic
 +# install add-ons
 +microk8s.enable dashboard dns ingress registry
 +
 +# status, info
 +microk8s status
 +microk8s kubectl get all
 +microk8s kubectl get nodes
 +microk8s kubectl get services
 +microk8s.kubectl cluster-info
 +microk8s.kubectl get po --all-namespaces
 +
 +# добавить юзера в группу
 +sudo usermod -a -G microk8s <user>
 +sudo chown -f -R <user> ~/.kube
 +newgrp microk8s
 +# затем перезайти в систему
 +
 +# Прописать алиас, чтобы не набирать бесконечное "microk8s kubectl"
 +echo "alias kubectl='microk8s kubectl'" >> .bash_aliases
 +echo "alias k='microk8s kubectl'" >> .bash_aliases
 +# Для того, чтобы автокомплит работал с алиасом
 +source <(kubectl completion bash) >> ~/.bashrc
 +source <(kubectl completion bash | sed s/kubectl/k/g) >> ~/.bashrc
 +</code>
 +https://kubernetes.io/docs/reference/kubectl/cheatsheet/#bash\\
 +Alias & auto-completion: https://kubernetes.io/docs/tasks/tools/included/optional-kubectl-configs-bash-linux/
 +===== Получить доступ к панели мониторинга =====
 +Геморрой начался с самого начала. Приборная панель ставится и включается без проблем, но зайти на неё можно только с localhost "[[https://github.com/ubuntu/microk8s/issues/421|по соображениям безопасности]]".
 +
 +<code bash>
 +microk8s.kubectl -n kube-system edit service kubernetes-dashboard
 +# Заменить type: clusterIP
 +# на type: NodePort
 +
 +# Узнать порт
 +microk8s.kubectl -n kube-system get services
 +NAME                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                  AGE
 +dashboard-metrics-scraper   ClusterIP   10.152.183.107   <none>        8000/TCP                 46m
 +kube-dns                    ClusterIP   10.152.183.10    <none>        53/UDP,53/TCP,9153/TCP   46m
 +kubernetes-dashboard        NodePort    10.152.183.79    <none>        443:31315/TCP            46m
 +metrics-server              ClusterIP   10.152.183.235   <none>        443/TCP                  46m
 +</code>
 +Теперь можно заходить по https://, адресу ноды и, в данном случае, порту 31315.
 +
 +Узнать [[https://microk8s.io/docs/addon-dashboard|токен входа]]:
 +<code>
 +token=$(microk8s kubectl -n kube-system get secret | grep default-token | cut -d " " -f1)
 +microk8s kubectl -n kube-system describe secret $token
 +</code>
 +https://www.thegeekdiary.com/how-to-access-kubernetes-dashboard-externally/\\
 +https://microk8s.io/docs/addon-dashboard
 +
 +===== Кластер =====
 +<code bash>
 +# На будущей мастер-ноде выполнить
 +microk8s.add-node
 +# Полученную ссылку запустить на будущей подчинённой ноде
 +</code>
 +
 +===== Развёртывание приложения =====
 +<code bash>
 +# Название, образ
 +kubectl create deployment kubernetes-bootcamp --image=gcr.io/google-samples/kubernetes-bootcamp:v1
 +# Публикация - прокси даёт доступ с локального хоста и работает, пока его не остановят (надо запускать в отдельном окне)
 +kubectl proxy
 +# проверка
 +curl http://localhost:8001/version
 +</code>
 +
 +===== Поды, узлы =====
 +Под - из чего состоит приложение: контейнеры и ресурсы (хранилище, сеть, конфиги). Все контейнеры в поде имеют один и тот же IP-адрес (адреса?) и пространство портов, выполняющиеся в общем контексте на одном и том же узле.\\
 +Узел - рабочая машина, виртуальная или физическая. Каждый узел управляется мастером (ведущим узлом). Узел может содержать несколько подов, которые мастер Kubernetes автоматически размещает на разные узлы кластера. Ведущий узел при автоматическом планировании (распределении подов по узлам) учитывает доступные ресурсы на каждом узле.
 +
 +В каждом узле Kubernetes как минимум работает:
 +  * Kubelet — процесс, отвечающий за взаимодействие между мастером Kubernetes и узлом; он управляет подами и запущенными контейнерами на рабочей машине.
 +  * Среда выполнения контейнера (например, Docker или rkt), отвечающая за получение (загрузку) образа контейнера из реестра, распаковку контейнера и запуск приложения.
 +
 +Наиболее распространенные операции:
 +  * kubectl get — вывод списка ресурсов
 +  * kubectl describe — вывод подробной информации о ресурсе
 +  * kubectl logs — вывод логов контейнера в поде
 +  * kubectl exec — выполнение команды в контейнере пода
 +
 +Перечисленные выше команды можно использовать, чтобы узнать, когда и где приложения были развернуты, их текущее состояние и конфигурацию.
 +
 +===== Создание сервиса для открытия доступа к приложению =====
 +Под - расходный материал, они создаются и уничтожаются, а механизм ReplicaSet следит, чтобы поддерживалось заданное их количество.
 +
 +Сервисы - логическое объединение подов.
 +
 +Публикация сервиса
 +<code bash>
 +root@vmls-k8s1:~# kubectl get services
 +NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
 +kubernetes   ClusterIP   10.152.183.1   <none>        443/TCP   2d1h
 +
 +root@vmls-k8s1:~# kubectl expose deployment/kubernetes-bootcamp --type="NodePort" --port 8080
 +service/kubernetes-bootcamp exposed
 +
 +root@vmls-k8s1:~# kubectl get services
 +NAME                  TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
 +kubernetes            ClusterIP   10.152.183.1     <none>        443/TCP          2d1h
 +kubernetes-bootcamp   NodePort    10.152.183.216   <none>        8080:31689/TCP   52s
 +
 +root@vmls-k8s1:~# kubectl describe services/kubernetes-bootcamp
 +Name:                     kubernetes-bootcamp
 +Namespace:                default
 +Labels:                   app=kubernetes-bootcamp
 +Annotations:              <none>
 +Selector:                 app=kubernetes-bootcamp
 +Type:                     NodePort
 +IP:                       10.152.183.216
 +Port:                     <unset>  8080/TCP
 +TargetPort:               8080/TCP
 +NodePort:                 <unset>  31689/TCP
 +Endpoints:                10.1.17.2:8080
 +Session Affinity:         None
 +External Traffic Policy:  Cluster
 +Events:                   <none>
 +</code>
 +
 +Ярлыки (labels)
 +<code bash>
 +# Узнать имена ярлыков
 +root@vmls-k8s1:~# kubectl describe deployment
 +Name:                   kubernetes-bootcamp
 +Namespace:              default
 +CreationTimestamp:      Mon, 17 Aug 2020 12:04:21 +0000
 +Labels:                 app=kubernetes-bootcamp
 +Annotations:            deployment.kubernetes.io/revision: 1
 +Selector:               app=kubernetes-bootcamp
 +Replicas:               1 desired | 1 updated | 1 total | 1 available | 0 unavailable
 +StrategyType:           RollingUpdate
 +MinReadySeconds:        0
 +RollingUpdateStrategy:  25% max unavailable, 25% max surge
 +Pod Template:
 +  Labels:  app=kubernetes-bootcamp
 +  Containers:
 +   kubernetes-bootcamp:
 +    Image:        gcr.io/google-samples/kubernetes-bootcamp:v1
 +    Port:         <none>
 +    Host Port:    <none>
 +    Environment:  <none>
 +    Mounts:       <none>
 +  Volumes:        <none>
 +Conditions:
 +  Type           Status  Reason
 +  ----           ------  ------
 +  Available      True    MinimumReplicasAvailable
 +  Progressing    True    NewReplicaSetAvailable
 +OldReplicaSets:  <none>
 +NewReplicaSet:   kubernetes-bootcamp-6f6656d949 (1/1 replicas created)
 +Events:          <none>
 +
 +# Запрос подов по имени ярлыка
 +root@vmls-k8s1:~# kubectl get pods -l app=kubernetes-bootcamp
 +NAME                                   READY   STATUS    RESTARTS   AGE
 +kubernetes-bootcamp-6f6656d949-btv4z   1/    Running            46h
 +
 +# Запрос сервисов по имени ярлыка
 +root@vmls-k8s1:~# kubectl get services -l app=kubernetes-bootcamp
 +NAME                  TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
 +kubernetes-bootcamp   NodePort   10.152.183.216   <none>        8080:31689/TCP   79m
 +
 +# Задать новый ярлык
 +root@vmls-k8s1:~# kubectl label pod kubernetes-bootcamp-6f6656d949-btv4z app=v1 --overwrite
 +pod/kubernetes-bootcamp-6f6656d949-btv4z labeled
 +
 +# Запрос подов, где виден новый ярлык
 +root@vmls-k8s1:~# kubectl describe pods kubernetes-bootcamp-6f6656d949-btv4z
 +Name:         kubernetes-bootcamp-6f6656d949-btv4z
 +Namespace:    default
 +Priority:     0
 +Node:         10.1.0.223/10.1.0.223
 +Start Time:   Mon, 17 Aug 2020 12:04:21 +0000
 +Labels:       app=v1
 +              pod-template-hash=6f6656d949
 +Annotations:  <none>
 +Status:       Running
 +IP:           10.1.17.2
 +IPs:
 +  IP:  10.1.17.2
 +Containers:
 +  kubernetes-bootcamp:
 +    Container ID:   containerd://215ec311ea17ae5b923d93be3391c4b190cf29349b4173c7d0d1790e340c4f25
 +    Image:          gcr.io/google-samples/kubernetes-bootcamp:v1
 +    Image ID:       gcr.io/google-samples/kubernetes-bootcamp@sha256:0d6b8ee63bb57c5f5b6156f446b3bc3b3c143d233037f3a2f00e279c8fcc64af
 +    Port:           <none>
 +    Host Port:      <none>
 +    State:          Running
 +      Started:      Mon, 17 Aug 2020 12:04:49 +0000
 +    Ready:          True
 +    Restart Count:  0
 +    Environment:    <none>
 +    Mounts:
 +      /var/run/secrets/kubernetes.io/serviceaccount from default-token-gzgxh (ro)
 +Conditions:
 +  Type              Status
 +  Initialized       True
 +  Ready             True
 +  ContainersReady   True
 +  PodScheduled      True
 +Volumes:
 +  default-token-gzgxh:
 +    Type:        Secret (a volume populated by a Secret)
 +    SecretName:  default-token-gzgxh
 +    Optional:    false
 +QoS Class:       BestEffort
 +Node-Selectors:  <none>
 +Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
 +                 node.kubernetes.io/unreachable:NoExecute for 300s
 +Events:          <none>
 +
 +# Запрос подов с новым ярлыком
 +root@vmls-k8s1:~# kubectl get pods -l app=v1
 +NAME                                   READY   STATUS    RESTARTS   AGE
 +kubernetes-bootcamp-6f6656d949-btv4z   1/    Running            46h
 +</code>
 +
 +Удаление сервиса
 +<code bash>
 +root@vmls-k8s1:~# kubectl delete service -l app=kubernetes-bootcamp
 +service "kubernetes-bootcamp" deleted
 +
 +# Убедиться, что сервиса нет
 +root@vmls-k8s1:~# kubectl get services
 +NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
 +kubernetes   ClusterIP   10.152.183.1   <none>        443/TCP   2d3h
 +
 +# и что он недоступен
 +root@vmls-k8s1:~# curl localhost:$NODE_PORT
 +curl: (7) Failed to connect to localhost port 31689: Connection refused
 +
 +# в то время под живой и как изнутри него всё работает
 +root@vmls-k8s1:~# kubectl exec kubernetes-bootcamp-6f6656d949-btv4z -- curl localhost:8080
 +  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
 +                                 Dload  Upload   Total   Spent    Left  Speed
 +100    84    0    84    0      15587      0 --:--:-- --:--:-- --:--:-- 16800
 +Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-6f6656d949-btv4z | v=1
 +</code>
 +https://kubernetes.io/ru/docs/tutorials/kubernetes-basics/expose/
 +
 +===== Масштабирование =====
 +В случае масштабирования развёртывания создаются новые поды, которые распределяются по узлам с доступными ресурсами. У сервисов есть встроенный балансировщик нагрузки, который распределяет сетевой трафик всех подов в открытом извне развертывании.
 +<code bash>
 +
 +# Под пока 1
 +root@vmls-k8s1:~# kubectl get deployments
 +NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
 +kubernetes-bootcamp   1/               1           2d
 +
 +# Посмотреть ReplicaSet
 +root@vmls-k8s1:~# kubectl get rs
 +NAME                             DESIRED   CURRENT   READY   AGE
 +kubernetes-bootcamp-6f6656d949                         2d
 +
 +# Размножить
 +root@vmls-k8s1:~# kubectl scale deployments/kubernetes-bootcamp --replicas=4
 +deployment.apps/kubernetes-bootcamp scaled
 +
 +# Теперь подов 4
 +root@vmls-k8s1:~# kubectl get deployments
 +NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
 +kubernetes-bootcamp   4/               4           2d
 +
 +root@vmls-k8s1:~# kubectl get pods -o wide
 +NAME                                   READY   STATUS    RESTARTS   AGE    IP          NODE         NOMINATED NODE   READINESS GATES
 +kubernetes-bootcamp-6f6656d949-2k99k   1/    Running            82s    10.1.17.4   10.1.0.223   <none>           <none>
 +kubernetes-bootcamp-6f6656d949-52l9m   1/    Running            82s    10.1.35.3   10.1.0.222   <none>           <none>
 +kubernetes-bootcamp-6f6656d949-btv4z   1/    Running            2d     10.1.17.2   10.1.0.223   <none>           <none>
 +kubernetes-bootcamp-6f6656d949-lhnkd   1/    Running            83s    10.1.17.3   10.1.0.223   <none>           <none>
 +
 +# В логах есть отражение этого факта
 +root@vmls-k8s1:~# kubectl describe deployments/kubernetes-bootcamp
 +Name:                   kubernetes-bootcamp
 +Namespace:              default
 +CreationTimestamp:      Mon, 17 Aug 2020 12:04:21 +0000
 +Labels:                 app=kubernetes-bootcamp
 +Annotations:            deployment.kubernetes.io/revision: 1
 +Selector:               app=kubernetes-bootcamp
 +Replicas:               4 desired | 4 updated | 4 total | 4 available | 0 unavailable
 +StrategyType:           RollingUpdate
 +MinReadySeconds:        0
 +RollingUpdateStrategy:  25% max unavailable, 25% max surge
 +Pod Template:
 +  Labels:  app=kubernetes-bootcamp
 +  Containers:
 +   kubernetes-bootcamp:
 +    Image:        gcr.io/google-samples/kubernetes-bootcamp:v1
 +    Port:         <none>
 +    Host Port:    <none>
 +    Environment:  <none>
 +    Mounts:       <none>
 +  Volumes:        <none>
 +Conditions:
 +  Type           Status  Reason
 +  ----           ------  ------
 +  Progressing    True    NewReplicaSetAvailable
 +  Available      True    MinimumReplicasAvailable
 +OldReplicaSets:  <none>
 +NewReplicaSet:   kubernetes-bootcamp-6f6656d949 (4/4 replicas created)
 +Events:
 +  Type    Reason             Age   From                   Message
 +  ----    ------             ----  ----                   -------
 +  Normal  ScalingReplicaSet  3m7s  deployment-controller  Scaled up replica set kubernetes-bootcamp-6f6656d949 to 4
 +</code>
 +
 +Балансировка
 +<code bash>
 +# Смотрим свойства сервиса - порт ноды
 +root@vmls-k8s1:~# kubectl describe services/kubernetes-bootcamp
 +Name:                     kubernetes-bootcamp
 +Namespace:                default
 +Labels:                   app=kubernetes-bootcamp
 +Annotations:              <none>
 +Selector:                 app=kubernetes-bootcamp
 +Type:                     NodePort
 +IP:                       10.152.183.74
 +Port:                     <unset>  8080/TCP
 +TargetPort:               8080/TCP
 +NodePort:                 <unset>  31386/TCP
 +Endpoints:                10.1.17.3:8080,10.1.17.4:8080,10.1.35.2:8080 + 1 more...
 +Session Affinity:         None
 +External Traffic Policy:  Cluster
 +Events:                   <none>
 +
 +# Теперь при каждом запросе они идут в разные поды
 +root@vmls-k8s1:~# curl localhost:31386
 +Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-6f6656d949-rztf4 | v=1
 +root@vmls-k8s1:~# curl localhost:31386
 +Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-6f6656d949-lhnkd | v=1
 +root@vmls-k8s1:~# curl localhost:31386
 +Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-6f6656d949-52l9m | v=1
 +</code>
 +
 +Свёртывание масштабирования
 +<code bash>
 +root@vmls-k8s1:~# kubectl scale deployments/kubernetes-bootcamp --replicas=2
 +deployment.apps/kubernetes-bootcamp scaled
 +
 +root@vmls-k8s1:~# kubectl get deployments
 +NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
 +kubernetes-bootcamp   2/               2           2d1h
 +
 +root@vmls-k8s1:~# kubectl get pods -o wide
 +NAME                                   READY   STATUS        RESTARTS   AGE    IP          NODE         NOMINATED NODE   READINESS GATES
 +kubernetes-bootcamp-6f6656d949-glxgv   1/    Terminating            67s    10.1.35.4   10.1.0.222   <none>           <none>
 +kubernetes-bootcamp-6f6656d949-lhnkd   1/    Running                20m    10.1.17.3   10.1.0.223   <none>           <none>
 +kubernetes-bootcamp-6f6656d949-qmct2   1/    Running                67s    10.1.60.8   vmls-k8s1    <none>           <none>
 +kubernetes-bootcamp-6f6656d949-rztf4   1/    Terminating            122m   10.1.35.2   10.1.0.222   <none>           <none>
 +</code>
 +
 +https://kubernetes.io/ru/docs/tutorials/kubernetes-basics/scale/scale-intro/
 +
 +===== Плавающие обновления (rolling updates) =====
 +Плавающие обновления позволяют обновить развёртывания без простоев, шаг за шагом заменяя старые поды на новые. Новые поды будут запущены на узлах, имеющих достаточно ресурсов.\\
 +В предыдущем модуле мы промасштабировали приложение до нескольких экземпляров. Это необходимо сделать, чтобы иметь возможность обновлять приложение, не влияя на его доступность. По умолчанию, максимальное количество подов, которое может быть недоступно во время обновления, и максимальное количество новых подов, которое можно создать, равны 1. Эти две опции могут быть определены в абсолютном (числа) или относительном соотношении (проценты). В Kubernetes обновления версионируются, поэтому любое обновление развёртывания можно откатить до предыдущей (стабильной) версии.\\
 +Балансировщик будет работать только для доступных подов.
 +<code bash>
 +# Список развёртываний
 +root@vmls-k8s1:~# kubectl get deployments
 +NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
 +kubernetes-bootcamp   4/               4           2d1h
 +
 +# Список подов
 +root@vmls-k8s1:~# kubectl get pods
 +NAME                                   READY   STATUS    RESTARTS   AGE
 +kubernetes-bootcamp-6f6656d949-lhnkd   1/    Running            35m
 +kubernetes-bootcamp-6f6656d949-qhlcg   1/    Running            17s
 +kubernetes-bootcamp-6f6656d949-qmct2   1/    Running            16m
 +kubernetes-bootcamp-6f6656d949-rvflg   1/    Running            17s
 +
 +# Описание подов (о версии см. строку Image)
 +root@vmls-k8s1:~# kubectl describe pods
 +Name:         kubernetes-bootcamp-6f6656d949-lhnkd
 +Namespace:    default
 +Priority:     0
 +Node:         10.1.0.223/10.1.0.223
 +Start Time:   Wed, 19 Aug 2020 12:45:51 +0000
 +Labels:       app=kubernetes-bootcamp
 +              pod-template-hash=6f6656d949
 +Annotations:  <none>
 +Status:       Running
 +IP:           10.1.17.3
 +IPs:
 +  IP:           10.1.17.3
 +Controlled By:  ReplicaSet/kubernetes-bootcamp-6f6656d949
 +Containers:
 +  kubernetes-bootcamp:
 +    Container ID:   containerd://3feba85a18a3e9306cd5d5d9dcb29be8fa7c173b4bb1c907449634e2437c23fc
 +    Image:          gcr.io/google-samples/kubernetes-bootcamp:v1
 +# дальнейшая простыня обрезана
 +
 +# Обновить до v2
 +root@vmls-k8s1:~# kubectl set image deployments/kubernetes-bootcamp kubernetes-bootcamp=jocatalin/kubernetes-bootcamp:v2
 +deployment.apps/kubernetes-bootcamp image updated
 +
 +# Старые поды убиваются, создаются новые свежей версии
 +root@vmls-k8s1:~# kubectl get pods
 +NAME                                   READY   STATUS        RESTARTS   AGE
 +kubernetes-bootcamp-6f6656d949-lhnkd   1/    Terminating            39m
 +kubernetes-bootcamp-6f6656d949-qhlcg   1/    Terminating            4m2s
 +kubernetes-bootcamp-6f6656d949-qmct2   1/    Terminating            20m
 +kubernetes-bootcamp-6f6656d949-rvflg   1/    Terminating            4m2s
 +kubernetes-bootcamp-86656bc875-5hmdv   1/    Running                28s
 +kubernetes-bootcamp-86656bc875-5pj79   1/    Running                17s
 +kubernetes-bootcamp-86656bc875-c8mpt   1/    Running                17s
 +kubernetes-bootcamp-86656bc875-s75z4   1/    Running                27s
 +</code>
 +
 +Проверка
 +<code bash>
 +# Узнаём порт для подключения
 +root@vmls-k8s1:~# kubectl describe services/kubernetes-bootcamp
 +Name:                     kubernetes-bootcamp
 +Namespace:                default
 +Labels:                   app=kubernetes-bootcamp
 +Annotations:              <none>
 +Selector:                 app=kubernetes-bootcamp
 +Type:                     NodePort
 +IP:                       10.152.183.74
 +Port:                     <unset>  8080/TCP
 +TargetPort:               8080/TCP
 +NodePort:                 <unset>  31386/TCP
 +Endpoints:                10.1.17.6:8080,10.1.17.7:8080,10.1.35.6:8080 + 1 more...
 +Session Affinity:         None
 +External Traffic Policy:  Cluster
 +Events:
 +  Type     Reason                        Age    From                       Message
 +  ----     ------                        ----   ----                       -------
 +  Warning  FailedToUpdateEndpointSlices  4m46s  endpoint-slice-controller  Error updating Endpoint Slices for Service default/kubernetes-bootcamp: Error updating kubernetes-bootcamp-xpgkx EndpointSlice for Service default/kubernetes-bootcamp: Operation cannot be fulfilled on endpointslices.discovery.k8s.io "kubernetes-bootcamp-xpgkx": the object has been modified; please apply your changes to the latest version and try again
 +
 +# Проверка запросом, видно, что версия 2
 +root@vmls-k8s1:~# curl localhost:31386
 +Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-86656bc875-s75z4 | v=2
 +
 +# Ещё один способ проверить
 +root@vmls-k8s1:~# kubectl rollout status deployments/kubernetes-bootcamp
 +deployment "kubernetes-bootcamp" successfully rolled out
 +
 +# Проверка версии Image подов
 +root@vmls-k8s1:~# kubectl describe pods
 +Name:         kubernetes-bootcamp-86656bc875-5hmdv
 +Namespace:    default
 +Priority:     0
 +Node:         10.1.0.222/10.1.0.222
 +Start Time:   Wed, 19 Aug 2020 13:24:47 +0000
 +Labels:       app=kubernetes-bootcamp
 +              pod-template-hash=86656bc875
 +Annotations:  <none>
 +Status:       Running
 +IP:           10.1.35.6
 +IPs:
 +  IP:           10.1.35.6
 +Controlled By:  ReplicaSet/kubernetes-bootcamp-86656bc875
 +Containers:
 +  kubernetes-bootcamp:
 +    Container ID:   containerd://d4c020babb066f3eb28f002cf96370f3b5bfdc5f3edc34dca76bb2a300e9f326
 +    Image:          jocatalin/kubernetes-bootcamp:v2
 +# дальнейшая простыня обрезана
 +</code>
 +
 +Откат обновления
 +<code bash>
 +# Проба обновиться до версии 10
 +root@vmls-k8s1:~# kubectl set image deployments/kubernetes-bootcamp kubernetes-bootcamp=gcr.io/google-samples/kubernetes-bootcamp:v10
 +deployment.apps/kubernetes-bootcamp image updated
 +
 +# Проверяем - обновление не прошло
 +root@vmls-k8s1:~# kubectl get deployments
 +NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
 +kubernetes-bootcamp   3/               3           2d1h
 +
 +
 +root@vmls-k8s1:~# kubectl get pods
 +NAME                                   READY   STATUS         RESTARTS   AGE
 +kubernetes-bootcamp-64468f5bc5-p8zb6   0/    ErrImagePull            56s
 +kubernetes-bootcamp-64468f5bc5-s2btt   0/    ErrImagePull            56s
 +kubernetes-bootcamp-86656bc875-5hmdv   1/    Running        0          13m
 +kubernetes-bootcamp-86656bc875-c8mpt   1/    Running        0          13m
 +kubernetes-bootcamp-86656bc875-s75z4   1/    Running        0          13m
 +
 +# Если запустить
 +root@vmls-k8s1:~# kubectl describe pods
 +# , то будет ясно, что такой версии нет
 +
 +# Откатываем назад, к предыдущей рабочей версии
 +root@vmls-k8s1:~# kubectl rollout undo deployments/kubernetes-bootcamp
 +deployment.apps/kubernetes-bootcamp rolled back
 +
 +# Всё снова в порядке
 +root@vmls-k8s1:~# kubectl get pods
 +NAME                                   READY   STATUS    RESTARTS   AGE
 +kubernetes-bootcamp-86656bc875-5hmdv   1/    Running            14m
 +kubernetes-bootcamp-86656bc875-786cb   1/    Running            28s
 +kubernetes-bootcamp-86656bc875-c8mpt   1/    Running            14m
 +kubernetes-bootcamp-86656bc875-s75z4   1/    Running            14m
 +
 +# В строке Image будет видно, что опять развёрнута v2
 +root@vmls-k8s1:~# kubectl describe pods
 +</code>
 +
 +https://kubernetes.io/ru/docs/tutorials/kubernetes-basics/update/update-intro/
 +
 +
 +======  LinuxFoundationX: Introduction to Kubernetes ======
 +{{ :learning:pasted:20200908-092038.png?200}} Kubernetes - Оркестратор контейнеров, автоматизация развёртывания и отказоустойчивости.
 +
 +Master node - управляющий узел (или несколько узлов в HA mode)\\
 +Worker nodes - рабочие\\
 +etcd - распределённое хранилище ключей-значений, там хранится конфигурация кластера. Stacked - на мастере, external - на выделенном хосте.
 +
 +===== Компоненты мастера =====
 +  * API server - центральный компонент, работает с etcd, перехватывает запросы от пользователей, опреаторов и внешних агентов, проверяет и обрабатывает их. Является посредником между всеми остальными компонентами. Гибко настраивается, поддерживает добавление другого сервера API, для которого первичный API становится прокси-сервером.
 +  * Scheduler - распределяет объекты, такие, как поды, по узлам. Решения зависят от состояния кластера и требований нового объекта к системе. Планировщик через API запрашивает кол-во доступных ресурсов на рабочих нодах, а также требования объекта к системе в его конфиге. Требования могут включать в себя пользовательские и операторские директивы, например, чтобы объект был размещён на ноде с ярлыком disk==ssd. Гибко настраивается. Поддерживаются сторонние планировщики, тогда в конфигурации объекта должно быть указано имя этого планировщика, в противном случае используется стандартный. Планировщик - крайне важен и довольно сложен в кластерной конфигурации.
 +  * Controller managers - запускает процессы (controllers), регулирующие состояние кластера. Сравнивают желаемое состояние кластера (desired state) с текущим состоянием, получаемым из etcd через API. kube-controller-manager работает, когда ноды становятся недоступны, приводит число подов к желаемому. cloud-controller-manager при недоступности нод управляет хранилищами, LB и маршрутизацией.
 +  * etcd - хранилище данных состояния кластера. Данные добавляются, но никогда не заменяются. Устаревшие данные архивируются для экономии места. Есть etcd CLI для резервного копирования, снапшотов и восстановления данных. В пром. использовании крайне важно настроить HA для etcd.
 +
 +===== Компоненты рабочего узла =====
 +  * Container Runtime - среда запуска контейнеров (Docker, CRI-O, containerd, rkt, rktlet)
 +  * kubelet - агент, управляемый с мастер-ноды, получает определения запускаемых подов, взаимодействует с Containter runtime с помощью Container Runtime Interface (CRI), мониторит состояние контейнеров. CRI состоит из ImageService, работающего с образами, и RuntimeService, работающего с подами и контейнерами.
 +  * kube-proxy - сетевой агент, отвечающий за динамическое обновление, обслуживание и все сетевые правила.
 +  * addons - сторонние добавления, не реализованные непосредственно в K8s, например, DNS, Dashboard, Monitoring, Logging.
 +
 +===== Сеть =====
 +Из-за особенностей архитектуры микросервисов, настройка сетевого взаимодействия крайне важна и довольно сложна. Есть 4 слоя сетевых взаимодействий:
 +  - Между контейнерами внутри пода (Container-to-container)
 +  - Между подами на одной ноде или между нодами в кластере (Pod-to-Pod)
 +  - Между подом и сервисом в одном пространстве имени (namespace) или между пространств имён в кластере (Pod-to-Service)
 +  - Между внешними клиентами и сервисом (External-to-Service)
 +Все эти вещи должны быть определены до внедрения кластера K8s.
 +
 +==== Container-to-container ====
 +При запуске каждого контейнера для него создается изолированное сетевое пространство (network namespace), которое называется сетевым пространством имён. Сетевое пространство имен совместно используется контейнерами вместе с операционной системой хоста. Когда запущен под, network namespace создаётся внутри него, и все контейнеры внутри делят это пространство и могут обращаться друг к другу через localhost.
 +
 +==== Pod-to-Pod ====
 +В K8s поды распределяются между нодами случайным образом. Независимо от расположения подразумевается, что все поды могут общаться между собой в рамках кластера без использования NAT - это принципиальное требование к строению сети в K8s.
 +В K8s поды рассматриваются как VM в сети, где каждая имеет свой IP-адрес. Эта модель называется IP-per-Pod и гарантирует то, что поды могут общаться друг с другом. Контейнеры внутри пода делят сетевое пространство имён и должны координировать назначенные порты равно как приложения внутри ОС. Тем не менее, контейнеры, равно как и вся сетевая модель K8s, использует Container Network Interface (CNI), поддерживающую плагины, некоторые из которых поддерживают сетевые политики. Container runtime разгружает систему, передавая назначение IP-адресов CNI, который соединяется с плагинами, такими как Bridge или MACvlan для назначения адреса. Как только адрес получен, CNI возвращает его обратно Container runtime.
 +
 +==== Pod-to-External World ====
 +Для нормальной работы с контейнеризованными приложениями их надо опубликовать во внешний мир. В K8s это делается через сервисы (services), комплексные сущности, включающие в себя сетевые правила на кластерных нодах. Через публикацию сервисов посредством kube-proxy, приложения становятся доступны из внешнего мира по виртуальному IP.
 +
 +===== Установка =====
 +4 основных конфигурации:
 +  - Всё в одном - мастер и рабочая нода совмещены. Подходит для тестовых, учебных и т. п. целей. Пример - Minikube.
 +  - 1-нодовый etcd, 1-мастер, много рабочих нод
 +  - 1-нодовый etcd, мультимастер, много рабочих нод
 +  - Мульти-etcd, мультимастер, много рабочих нод, рекомендован для продуктива.
 +
 +==== Установка на локальную машину ====
 +  * Minikube - single-node local Kubernetes cluster (рекомендуется)
 +  * Docker Desktop - single-node local Kubernetes cluster for Windows and Mac
 +  * CDK on LXD - multi-node local cluster with LXD containers.
 +
 +==== Локально на VM ====
 +VMs могут быть созданы Vagrant, VMware vSphere, KVM или чем-то другим. Есть разные программы автоматизации установки, такие как Ansible или kubeadm.
 +
 +==== Локально на голое железо ====
 +Поверх ОС, таких как RHEL, CoreOS, CentOS, Fedora, Ubuntu и т. д. Большинство программ автоматизации для VM подходят и здесь.
 +
 +Устанавливать можно также на хостинге, на Turnkey Cloud Solutions, на Turnkey On-Premise Solutions.
 +
 +==== Программы для установки ====
 +  * kubeadm - рекомендованный способ создания single- or multi-node Kubernetes cluster. Не поддерживает provisioning of hosts.
 +  * kubespray - AWS, GCE, Azure, OpenStack, or bare metal.
 +  * kops
 +  * kube-aws
 +  * Можно ставить from scratch, если ничего не подходит.
 +
 +==== Требования для minikube ====
 +  - kubectl - управление кластером K8s. Ставится отдельно.
 +  - Гипервизор 2-го типа, типа Hyper-V. Minikube поддерживает опцию --vm-driver=none, позволяющую ставить K8s не в гипервизор, а на локальную машину, но тогда там должен быть установлен Docker (а гипервизор - не должен), и должен быть определена bridge network for Docker. Иначе при перезапуске сети связь с кластером может потеряться.
 +  - В BIOS нужно включить VT-x/AMD-v.
 +  - При первом запуске Minikube нужен выход в интернет для скачивания необходимых пакетов, а позже - для скачивания образов.
 +
 +<code powershell>
 +# Поставить в Hyper-V, движок CRI-O
 +minikube.exe start --container-runtime=cri-o --driver=hyperv
 +# состояние
 +minikube status
 +# зайти внутрь виртуалки
 +minikube ssh
 +</code>
 +https://kubernetes.io/docs/tasks/tools/install-minikube/
 +
 +<code bash>
 +# Т. к. докер не установлен, при попытке вывести список контейнеров будет ошибка:
 +$ sudo docker container ls
 +Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
 +# В CRI-O другая команда:
 +$ sudo runc list
 +</code>
 +
 +===== Доступ к системе =====
 +  * kubectl - CLI tool
 +  * dashboard - web-based user interface
 +  * curl - access the cluster via APIs
 +
 +API делится на 3 группы:
 +  - Core Group (/api/v1) - содержит объекты: поды, сервисы, ноды, пространства имён, карты конфигурации, секреты и т. д.
 +  - Named Group (/apis/$NAME/$VERSION). Объекты содержат уровни: alpha level, beta level, stable level
 +  - System-wide (/healthz, /logs, /metrics, /ui и т. д.)
 +Доступ к API может быть непосредственным или через CLI/Web UI.
 +
 +kubectl обычно ставится до minicube, но можно ставить и после. После установки kubectl получает свою конфигурацию от minikube автоматически (в иных случаях может понадобиться настройка). Рекомендуется версию kubectl иметь ту же, то и версию minikube.
 +
 +Конфиг лежит в ~/.kube.
 +<code powershell>
 +# Вывести на экран:
 +kubectl config view
 +# Информация о кластере:
 +kubectl cluster-info
 +# Включить и открыть веб-панель
 +minikube dashboard
 +# kubectl proxy публикует веб-панель наружу, блокируя терминал до своего завершения.
 +</code>
 +
 +Если просто запустить kubectl proxy, то публикуется порт 8001, через который можно обратиться к API, например,\\
 +curl http://localhost:8001/ (выведет список API endpoints), можно обратиться к конкретной ветке:\\
 +http://localhost:8001/api/v1\\
 +http://localhost:8001/apis/apps/v1\\
 +http://localhost:8001/healthz\\
 +http://localhost:8001/metrics
 +
 +Чтобы обратиться к API без запуска kubectl proxy, нужна аутентификация через Bearer Token или предоставив набор ключей и сертификатов. Bearer Token генерируется на мастер-ноде и возвращается клиенту, который, используя его, может подключиться к ресурсам K8s без предоставления доп. информации.
 +
 +<code bash>
 +# Get the token:
 +$TOKEN=$(kubectl describe secret -n kube-system $(kubectl get secrets -n kube-system | grep default | cut -f1 -d ' ') | grep -E '^token' | cut -f2 -d':' | tr -d '\t' | tr -d " ")
 +# Get the API server endpoint:
 +$APISERVER=$(kubectl config view | grep https | cut -f 2- -d ":" | tr -d " ")
 +# Confirm that the APISERVER stored the same IP as the Kubernetes master IP by issuing the following 2 commands and comparing their outputs:
 +$ echo $APISERVER
 +https://192.168.99.100:8443
 +$ kubectl cluster-info
 +Kubernetes master is running at https://192.168.99.100:8443 ...
 +# Access the API server using the curl command, as shown below:
 +$ curl $APISERVER --header "Authorization: Bearer $TOKEN" --insecure
 +{
 + "paths": [
 +   "/api",
 +   "/api/v1",
 +   "/apis",
 +   "/apis/apps",
 +   ......
 +   ......
 +   "/logs",
 +   "/metrics",
 +   "/openapi/v2",
 +   "/version"
 + ]
 +}
 +</code>
 +
 +Помимо токена, можно извлечь клиентский сертификат, ключ и данные об удостоверяющем центре из файла .kube/config, закодировать и пустить через curl:
 +<code bash>
 +curl $APISERVER --cert encoded-cert --key encoded-key --cacert encoded-ca
 +</code>
 +
 +===== Объекты K8s =====
 +Объектная модель K8s представляет различные постоянные сущности в кластере. Эти сущности описывают:
 +  * Какие приложения запущены и на какой ноде
 +  * Потребление ресурсов
 +  * Политики приложений: перезапуск и обновление, отказоустойчивость и т. д.
 +
 +Для каждого объекта определяется секция spec, где описывается желаемое состояние (desired state). Кластер отслеживает статус объекта (status) и сравнивает текущее состояние (actual state) с желаемым. Описание идёт в формате YAML, которое kubectl конвертирует в JSON и посылает API.
 +
 +Пример:
 +<code yaml>
 +# required - API endpoint, к которому идёт подключение, должен соответствовать существующему типу
 +apiVersion: apps/v1
 +# required - тип объекта (варианты: Pod, Replicaset, Namespace, Service и т. д.)
 +kind: Deployment
 +# required - осн. информация: имя, ярлыки, пространство имён и т. д.
 +metadata:
 +  name: nginx-deployment
 +  labels:
 +    app: nginx
 +# required - desired state of the Deployment object
 +spec:
 +  replicas: 3
 +  selector:
 +    matchLabels:
 +      app: nginx
 +  # Pods template, удаление вышестоящих значений metadata и labels
 +  template:
 +    metadata:
 +      labels:
 +        app: nginx
 +    # desired state of the Pod
 +    spec:
 +      containers:
 +      - name: nginx
 +        image: nginx:1.15.11
 +        ports:
 +        - containerPort: 80
 +</code>
 +После создания объекта Deployment, K8s определяет его статус, т. е., actual state.
 +
 +==== Pods ====
 +Под - это наименьшая логическая единица, содержащая один или несколько контейнеров, которые:
 +  * Помещаются на одну ноду в рамках пода
 +  * Делят общее сетевое пространство
 +  * Имеют доступ к одним и тем же внешним хранилищам (volumes)
 +
 +Поды не умеют сами восстанавливаться (self-heal), поэтому нуждаются во внешних управляющих их состоянием контроллерах, таких как Deployments, ReplicaSets, ReplicationControllers и т. д. Выше был пример вложенной в Deployment спецификации пода, описанной в шаблоне (Template). Вот пример конфигурации пода:
 +<code yaml>
 +apiVersion: v1
 +kind: Pod
 +metadata:
 +  name: nginx-pod
 +  labels:
 +    app: nginx
 +spec:
 +  containers:
 +  - name: nginx
 +    image: nginx:1.15.11
 +    ports:
 +    - containerPort: 80
 +</code>
 +
 +==== Labels ====
 +Ярлыки нужны для организации и выбора объектов, разные объекты могут иметь одинаковые ярлыки. Имеют вид //ключ:значение.//
 +Ключей два: **app** и **env.** Например,
 +<code>
 +app=frontend
 +env=qa
 +
 +app=backend
 +env=dev
 +</code>
 +
 +Селекторы ярлыков - используются контроллерами. Есть 2 типа:
 +  * Equality-Based Selectors - позволяет фильтровать объекты по ключам и значениям ярлыков. = или == равно, != не равно. Например, env==dev или env=dev выбирает объекты, где env равен dev.
 +  * Set-Based Selectors - позволяет фильтровать объекты по наборам значений. In, notin для значений, exist/does not exist для ключей. Например, env in (dev,qa) выбираются объекты, где env равен либо dev, либо qa. !app выбирает объекты без ключа app.
 +
 +==== ReplicationControllers ====
 +ReplicationControllers регулируют требуемое кол-во запущенных подов в кластере, уничтожая лишние или запуская недостающие. Обычно поды не запускаются независимо, так как они не могут сами перезапуститься в случае ошибок. Поэтому рекомендуется использовать какой-либо тип контроллера репликации для создания подов и управления ими.\\
 +Стандартный контроллер - это Deployment, настраивающий ReplicaSet для управления жизненным циклом подов.
 +
 +==== ReplicaSets ====
 +ReplicaSet - контроллер репликации нового поколения. Поддерживает оба селектора ярлыков (equality- and set-based selectors), в то время как ReplicationControllers поддерживают только equality-based. В настоящий момент, это единственное различие между ними.
 +
 +Масштабирование подов может быть реализовано вручную или автоматически (autoscaler).
 +
 +ReplicaSets сами могут быть использованы независимо как Pod controllers, но они предоставляют ограниченный функционал. Рекомендуемый способ оркестровки подов - это Deployments, которые управляют созданием, удалением и обновлением подов. Deployment автоматически создаёт ReplicaSet, которые, в свою очередь, создают поды, поэтому нет нужды управлять ReplicaSets и подами раздельно.
 +
 +==== Deployments ====
 +Объекты Deployment занимаются обновлением (declarative updates) подов и ReplicaSets. DeploymentController - один из компонентов мастер-ноды, который обеспечивает соответствие текущего состояния желаемому. Позволяет бесшовные обновления (или их откат), и непосредственно управляет ReplicaSets для масштабирования.
 +
 +Например, новый Deployment создаёт ReplicaSet A, который создаёт 3 пода, где каждый шаблон пода настроен запускать один контейнер nginx:1.7.9. В этом случае, ReplicaSet A связан с nginx:1.7.9, представляя состояние Deployment-а. В частности, это состояние записано как Revision 1.
 +
 +Если в Deployment поменять шаблон подов, к примеру, обновить там версию на nginx:1.9.1, то Deployment создаст ReplicaSet B для новых контейнеров, созданных из образа 1.9.1, и это представление будет новой записью состояния - Revision 2. Незаметный переход между двумя ReplicaSets (ReplicaSet A v. 1.7.9 -> ReplicaSet B v. 1.9.1 или Revision 1 -> Revision 2) называется Deployment rolling update.
 +
 +Обновление запускается, когда обновляется шаблон подов в развёртывании. Масштабирование или присвоение ярлыков на запускает обновления, так как не меняет номер ревизии.
 +
 +Когда обновление завершено Deployment показывает обе ReplicaSets (A и B), где A содержит 0 подов, а B - 3. Таким образом Deployment сохраняет своё предыдущее состояние, как ревизии. Как только ReplicaSet B и его 3 пода версии 1.9.1 готовы, Deployment начинает активно ими управлять. Тем не менее, Deployment хранит своё предыдущее состояние, обеспечивая возможность отката на предыдущую ревизию.
 +
 +==== Namespaces ====
 +Если пользователей или команд, использующих кластер, много, то можно разграничить кластер на виртульные кластеры, используя пространства имён. Имена ресурсов и объектов внутри одного пространства имён уникальны, но не в рамках всех пространств имён.
 +
 +Перечислить все пространства имён:
 +<code bash>
 +$ kubectl get namespaces
 +NAME                   STATUS   AGE
 +default                Active   24h
 +kube-node-lease        Active   24h
 +kube-public            Active   24h
 +kube-system            Active   24h
 +kubernetes-dashboard   Active   16h
 +</code>
 +
 +Обычно Kubernetes создаёт 4 пространства:
 +  - kube-system - содержит объекты, созданные K8s, в основном, агентами управления мастер-ноды
 +  - kube-public - особое пространство, открытое для чтения всем, используется для предоставления открытой информации о кластере
 +  - kube-node-lease - самое новое пространство, содержит информацию об использовании нод и пульс
 +  - default - объекты и ресурсы, созданные администраторами и разработчиками. По умолчанию, подключение идёт к этому пространству
 +
 +Хорошей практикой считается создавать больше пространств имён, если требуется разграничение по пользователям и командам. Чтобы разделить кластер на пространства имён, используются ресурсные квоты ([[https://kubernetes.io/docs/concepts/policy/resource-quotas/|Resource Quotas]]).
 +
 +==== Пример обновления Deployment и его отката ====
 +<code bash>
 +# создать
 +$ kubectl create deployment mynginx --image=nginx:1.15-alpine
 +deployment.apps/mynginx created
 +
 +# вывести информацию о deployment, replicaset, pods под ярлыком app=mynginx
 +$ kubectl get deploy,rs,po -l app=mynginx
 +NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
 +deployment.apps/mynginx   1/               1           23s
 +
 +NAME                                 DESIRED   CURRENT   READY   AGE
 +replicaset.apps/mynginx-76bcb97766                         23s
 +
 +NAME                           READY   STATUS    RESTARTS   AGE
 +pod/mynginx-76bcb97766-rbnq8   1/    Running            23s
 +
 +# Масштабировать до 3 экз.
 +$ kubectl scale deploy mynginx --replicas=3
 +deployment.apps/mynginx scaled
 +
 +# инфо ещё раз - теперь будет 3 пода
 +$ kubectl get deploy,rs,po -l app=mynginx
 +NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
 +deployment.apps/mynginx   3/               3           11m
 +
 +NAME                                 DESIRED   CURRENT   READY   AGE
 +replicaset.apps/mynginx-76bcb97766                         11m
 +
 +NAME                           READY   STATUS    RESTARTS   AGE
 +pod/mynginx-76bcb97766-q9r49   1/    Running            58s
 +pod/mynginx-76bcb97766-rbnq8   1/    Running            11m
 +pod/mynginx-76bcb97766-s672f   1/    Running            58s
 +
 +# Обратите внимание на номер replicaset (в данном случае, 76bcb97766). Теперь посмотрим на образ:
 +$ kubectl describe deployment |grep "Image"
 +Image:        nginx:1.15-alpine
 +
 +# Номера ревизий деплоя nginx
 +$ kubectl rollout history deploy mynginx
 +deployment.apps/mynginx
 +REVISION  CHANGE-CAUSE
 +1         <none>
 +
 +# Детали 1-й ревизии. Видно номер replicaset и образ, с которыми ревизия связана.
 +$ kubectl rollout history deploy mynginx --revision=1
 +deployment.apps/mynginx with revision #1
 +Pod Template:
 +  Labels:       app=mynginx
 +        pod-template-hash=76bcb97766
 +  Containers:
 +   nginx:
 +    Image:      nginx:1.15-alpine
 +    Port:       <none>
 +    Host Port:  <none>
 +    Environment:        <none>
 +    Mounts:     <none>
 +  Volumes:      <none>
 +  
 +# Обновить образ деплоя
 +$ kubectl set image deployment mynginx nginx=nginx:1.16-alpine
 +deployment.apps/mynginx image updated
 +
 +# Теперь ревизий две, появилась ещё одна ReplicaSet, все три пода перешли под неё.
 +
 +# Откатить обновление обратно
 +$ kubectl rollout undo deployment mynginx --to-revision=1
 +deployment.apps/mynginx rolled back
 +
 +# Ревизия 1 стала ревизией 3
 +$ kubectl rollout history deploy mynginx
 +deployment.apps/mynginx
 +REVISION  CHANGE-CAUSE
 +2         <none>
 +3         <none>
 +</code>
 +Стандартная настройка позволяет последовательно обновиться 10 раз и откатиться на любую версию.
 +
 +===== Аутентификация, авторизация, контроль входа =====
 +Каждый запрос к API проходит эти три стадии.
 +  * Аутентификация - вход для пользователя
 +  * Авторизация - пускает API-запросы от вошедшего пользователя
 +  * Контроль входа (Admission Control) - модули, изменяющие или отклоняющие запросы на основании доп. проверок, например, квота.
 +
 +==== Аутентификация ====
 +В K8s нет объекта user, и не хранится никаких логинов (usernames) и прочих связанных с этим данных. Тем не менее, K8s может использовать логины для входа. Есть 2 типа пользователей:
 +  * Обычные пользователи (Normal Users) - управляются вне кластера, например, User/Client Certificates, a file listing usernames/passwords, Google accounts, etc.
 +  * Служебные учётки (Service Accounts) - через них внутренние процессы кластера подключаются к API для выполнения разных операций. Большинство служебных учёток создаются автоматически самим API, но они также могут быть созданы и вручную. Служебные учётки связаны с выделенным им пространством имён и получают соответствующие права подключения к API как Секреты (as Secrets).
 +
 +Поддерживается и анонимный доступ, в дополнение к вышеперечисленным. Также поддерживается и смена роли пользователя (User impersonation), что полезно при решении проблем с политикой авторизации (authorization policies).
 +
 +Для аутентификации используются следующие модули:
 +  * Клиентские сертификаты - указывается файл, содержащий один или несколько УЦ с опцией --client-ca-file=SOMEFILE для сервера API. УЦ, упомянутые в файле, должны удостоверять клиента, преставленного серверу API.
 +  * Статический токен-файл - файл с заранее заданными bearer tokens, опция --token-auth-file=SOMEFILE для сервера API. В настоящее время эти токены не имеют срока действия и не могут быть изменены без перезапуска сервера API.
 +  * Bootstrap-токены - этот способ пока на тестировании. Используется по большей части для bootstrapping нового K8s-кластера.
 +  * Статический парольный файл - то же, что и статический токен-файл. Это файл с базовой информацией об аутентификации, опция --basic-auth-file=SOMEFILE. Также не имеют срока действия и не могут быть изменены без перезапуска сервера API.
 +  * Токены служебных учёток - это автоматически включённый механизм, использующий подписанные bearer tokens для проверки запросов. Эти токены прикрепляются к подам, используя ServiceAccount Admission Controller, позволяющий процессам общаться с API.
 +  * Токены OpenID - служат для подключения к OAuth2-провайдерам типа Azure Active Directory, Salesforce, Google и т. д. для снижения нагрузки путём выноса аутентификации на сторонние сервисы.
 +  * Токены Webhook - выноспроверки bearer tokens на сторонний сервис.
 +  * Аутентифицирующий прокси - если нужна какая-то доп. логика аутентификации.
 +
 +Можно включить сразу несколько способов, и первый сработавший способ пускает внутрь (short-circuits the evaluation). Для успешной аутентификации нужно 2 компонента: the service account tokens authenticator and one of the user authenticators.
 +
 +==== Авторизация ====
 +После успешной аутентификации, пользователи могут посылать запросы к API, и эти запросы авторизуются различными модулями K8s. Атрибуты запросов к API, рассматриваемые K8s - user, group, extra, Resource, Namespace и т. д. Атрибуты запросов фильтруются политиками, и в соответствии с этим запрос выполняется или нет.
 +
 +Так же как и на этапе аутентификации, авторизация имеет несколько модулей, и для одного кластера может быть настроено сразу несколько. В таком случае, запрос будет проходить проверку поочерёдно в каждом модуле. Если какой-либо модуль одобрил или отклонил запрос, это и становится решением.
 +
 +  * [[https://kubernetes.io/docs/reference/access-authn-authz/node/|Node Authorizer]] - спецавторизация для API-запросов от кублетов (kubelets).
 +  * [[https://kubernetes.io/docs/reference/access-authn-authz/abac/|Attribute-Based Access Control (ABAC) Authorizer]] - предоставляет доступ запросам на основании политик и атрибутов. Например пользователю student доступны поды из пространства имён lfs158 только на чтение.
 +<code yaml>
 +{
 +  "apiVersion": "abac.authorization.kubernetes.io/v1beta1",
 +  "kind": "Policy",
 +  "spec": {
 +  "user": "student",
 +  "namespace": "lfs158",
 +  "resource": "pods",
 +  "readonly": true
 +  }
 +}
 +</code>
 +Чтобы включить ABAC authorizer, нужно запустить API-сервер с ключом %%--authorization-mode=ABAC%%. Также, необходимо определить политику авторизации ключом %%--authorization-policy-file=PolicyFile.json%%.
 +  * [[https://kubernetes.io/docs/reference/access-authn-authz/webhook/|Webhook Authorizer]] - авторизация через сторонние сервисы. Чтобы запустить Webhook authorizer, нужно запустить API-сервер с ключом %%--authorization-webhook-config-file=SOME_FILENAME%%, где SOME_FILENAME - это конфигурация удалённого сервиса авторизации.
 +  * [[https://kubernetes.io/docs/reference/access-authn-authz/rbac/|Role-Based Access Control (RBAC) Authorizer]] - в основном, регулируется доступ к ресурсам на основе пользовательских ролей. В K8s субъектам, таким как users, service accounts и т. д., могут быть присвоены разные роли. Путём создания ролей, можно ограничивать доступ к разным операциям (отражены глаголами), таким как create, get, update, patch и т. д.
 +
 +В RBAC существует два вида ролей:
 +  - Role - предоставление доступа к ресурсам того или иного пространства имён
 +  - ClusterRole - то же, что и Role, но в рамках всего кластера
 +
 +Пример Role:
 +<code yaml>
 +            kind: Role
 +            apiVersion: rbac.authorization.k8s.io/v1
 +            metadata:
 +              namespace: lfs158
 +              name: pod-reader
 +            rules:
 +            - apiGroups: [""] # "" indicates the core API group
 +              resources: ["pods"]
 +              verbs: ["get", "watch", "list"]
 +</code>
 +Здесь создаётся роль pod-reader, имеющая доступ на чтение подов в пространстве имён lfs158. После создания роли к ней можно привязать пользователей с помощью RoleBinding.
 +
 +У RoleBindings также есть два типа:
 +  - RoleBinding - привязывает пользователей к тому же пространству имён, как Role. Можно сослаться и на ClusterRole, в случае, если в рамках ClusterRole определены ресурсы пространства имён.
 +  - ClusterRoleBinding - доступ на уровне кластера и всех пространств имён
 +
 +Пример RoleBinding:
 +<code yaml>
 +            kind: RoleBinding
 +            apiVersion: rbac.authorization.k8s.io/v1
 +            metadata:
 +              name: pod-read-access
 +              namespace: lfs158
 +            subjects:
 +            - kind: User
 +              name: student
 +              apiGroup: rbac.authorization.k8s.io
 +            roleRef:
 +              kind: Role
 +              name: pod-reader
 +              apiGroup: rbac.authorization.k8s.io
 +</code>
 +Выдава доступа пользователю student на чтение подов в пространстве имён lfs158.
 +
 +Для включения RBAC authorizer нужно запустить API-сервер с ключом %%--authorization-mode=RBAC%%. RBAC authorizer конфигурирует политики автоматически.
 +
 +==== Контроль входа ====
 +[[https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/|Admission control]] - настройка политик гранулярного доступа (granular access control policies), включающая разрешение привилегированных контейнеров, проверка квот ресурсов и т. д. Эти политики проводятся в жизнь //контроллерами входа,// такими как ResourceQuota, DefaultStorageClass, AlwaysPullImages и т. д., и действуют только после успешной аутентификации и авторизации API-запросов.
 +
 +Для использования контроля входа, нужно запустить API-сервер в ключом %%--enable-admission-plugins%%, где контроллеры разделены запятыми:
 +<code>
 +--enable-admission-plugins=NamespaceLifecycle,ResourceQuota,PodSecurityPolicy,DefaultStorageClass
 +</code>
 +Некоторые контроллеры входа включены в K8s изначально.
 +
 +==== Пример настройки RBAC ====
 +<code bash>
 +# создаётся namespace, к которому будет дан доступ
 +$ kubectl create namespace lfs158
 +# создаётся каталог для сертификатов
 +$ mkdir rbac
 +$ cd rbac
 +# генерация ключа и сертификата
 +~/rbac$ openssl genrsa -out student.key 2048
 +~/rbac$ openssl req -new -key student.key -out student.csr -subj "/CN=student/O=learner"
 +# Закодировать сертификат в base64 и добавить поле request в файл конфигурации .yaml
 +~/rbac$ cat student.csr | base64 | tr -d '\n'
 +# создать файл конфигурации
 +~/rbac$ nano signing-request.yaml
 +</code>
 +
 +<code yaml>
 +apiVersion: certificates.k8s.io/v1beta1
 +kind: CertificateSigningRequest
 +metadata:
 +  name: student-csr
 +spec:
 +  groups:
 +  - system:authenticated
 +  request: <assign base64 value from the cat command>
 +  usages:
 +  - digital signature
 +  - key encipherment
 +  - client auth
 +</code>
 +
 +<code bash>
 +# Создать объект запроса на подпись сертификата (certificate signing request object)
 +~/rbac$ kubectl create -f signing-request.yaml
 +# Вывести список запросов, его состояние будет "ожидание" (pending)
 +~/rbac$ kubectl get csr
 +# Подтвердить запрос
 +~/rbac$ kubectl certificate approve student-csr
 +# Извлечь подтверждённый сертификат, декодировать его из base64 и сохранить в файл
 +~/rbac$ kubectl get csr student-csr -o jsonpath='{.status.certificate}' | base64 --decode > student.crt
 +# Настроить права для student присвоением ключа и сертификата: 
 +~/rbac$ kubectl config set-credentials student --client-certificate=student.crt --client-key=student.key
 +# Создать новую запись context в файле клиентской конфигурации kubectl для student, связанную с пространством имён lfs158 кластера minikube:
 +~/rbac$ kubectl config set-context student-context --cluster=minikube --namespace=lfs158 --user=student
 +# теперь, если вывести информацию, то там появится контекст, связанный со student:
 +~/rbac$ kubectl config view
 +
 +# Создать новый деплой в пространстве имён lfs158
 +~/rbac$ kubectl -n lfs158 create deployment nginx --image=nginx:alpine
 +# если сейчас попробовать вывести список подов из контекста student-context, то будет ошибка,
 +# т. к. в нём не настроены разрешения для student
 +~/rbac$ kubectl --context=student-context get pods
 +# Создать конф. файл для ролевого объекта pod-reader, который позволяет get, watch, list в lfs158 для подов.
 +~/rbac$ vim role.yaml
 +</code>
 +<code yaml>
 +apiVersion: rbac.authorization.k8s.io/v1
 +kind: Role
 +metadata:
 +  name: pod-reader
 +  namespace: lfs158
 +rules:
 +- apiGroups: [""]
 +  resources: ["pods"]
 +  verbs: ["get", "watch", "list"]
 +</code>
 +
 +<code bash>
 +# Создать ролевой объект
 +~/rbac$ kubectl create -f role.yaml
 +# Вывести его из изначального контекста minikube, но из пространства имён lfs158:
 +~/rbac$ kubectl -n lfs158 get roles
 +# Создать конф. файл YAML для объекта rolebinding, присваивающий разрешения роли pod-reader для student.
 +~/rbac$ nano rolebinding.yaml
 +</code>
 +<code yaml>
 +apiVersion: rbac.authorization.k8s.io/v1
 +kind: RoleBinding
 +metadata:
 +  name: pod-read-access
 +  namespace: lfs158
 +subjects:
 +- kind: User
 +  name: student
 +  apiGroup: rbac.authorization.k8s.io
 +roleRef:
 +  kind: Role
 +  name: pod-reader
 +  apiGroup: rbac.authorization.k8s.io
 +</code>
 +
 +<code bash>
 +# Создать объект rolebinding
 +~/rbac$ kubectl create -f rolebinding.yaml 
 +# Вывести его из изначального контекста minikube, но из пространства имён lfs158:
 +~/rbac$ kubectl -n lfs158 get rolebindings
 +# Теперь разрешения присвоены, и список подов успешно выводится
 +~/rbac$ kubectl --context=student-context get pods
 +</code>
 +===== Сервисы =====
 +{{ :learning:pasted:20200827-153847.png?500}}Хотя архитектура микросервисов нацелена на разделение компонентов приложения, эти микросервисы нуждаются в агентах, логически связывающих, группирующих их в одно целое, и балансировать трафик среди них.
 +
 +//Сервисы// используются для группировки подов и предоставления доступа к контейнеризованному приложению извне. Для доступа используется kube-proxy daemon, работающий на каждой рабочей ноде. Обнаружение сервиса (Service discovery) и типы сервиса (service types) определяют уровень доступа (access scope) к сервису.
 +
 +Чтобы получить доступ к приложению, клиент должен подключаться к подам. Так как поды эфемерны, IP-адреса у них не могут быть постоянными, т. к. поды появляются и исчезают в произвольный момент. Чтобы решить эту проблему, K8s предоставляет более высокий уровень абстракции - сервисы, которые логически группируют поды и определяет политику доступа к ним.
 +
 +Группировка осуществляется через //ярлыки// и //селекторы.// Например, есть поды с ярлыками //app:frontend// и //app:db,// и используя селекторы //select app==frontend// и //select app==db,// поды группируются в логические наборы. Логические наборы, имеющие имя, и есть сервисы, в данном случае, frontend-svc и db-svc.
 +
 +Сервисы могут представлять одиночные поды, ReplicaSets, Deployments, DaemonSets и StatefulSets.
 +
 +Пример сервиса:
 +<code yaml>
 +kind: Service
 +apiVersion: v1
 +metadata:
 +  name: frontend-svc
 +spec:
 +  selector:
 +    app: frontend
 +  ports:
 +  - protocol: TCP
 +    port: 80
 +    targetPort: 5000
 +</code>
 +
 +В данном случае создаётся сервис frontend-svc, в который входят все поды с ярлыком app:frontend. Стандартно каждый сервис получает IP-адрес, доступный только внутри кластера (ClusterIP). Здесь это 172.17.0.4 (frontend-svc) и 172.17.0.5 (db-svc).
 +
 +Теперь клиент подключается к сервису через ClusterIP, который перенаправляет трафик на один из своих подов, балансируя трафик между ними.
 +
 +Можно также указать порт пода, который получает трафик. В примере, frontend-svc получает трафик на порт 80 и затем пробрасывает его на targetPort 5000 пода. Если targetPort не указан, то трафик будет проброшен на тот же номер порта, на котором был принят.
 +
 +Логический набор IP-адресов подов вместе с targetPort называется конечной точкой сервиса (Service endpoint). В примере, у frontend-svc 3 конечных точки: 10.0.1.3:5000, 10.0.1.4:5000 и 10.0.1.5:5000. Они создаются и управляются автоматически сервисом, а не администратором кластера.
 +
 +==== kube-proxy ====
 +На всех рабочих нодах есть служба kube-proxy, которая смотрит на API-сервер мастер-ноды за добавлением или удалением сервисов и конечных точек. В примере, для каждого нового сервиса на каждой ноде kube-proxy настраивает правила iptables для захвата трафика с ClusterIP и перенаправления его на одну из конечных точек. Таким образом, любая нода может получать внешний трафик и затем маршрутизировать его внутри кластера на основе правил iptables. Когда сервис удаляется, kube-proxy удаляет связанные с ним правила iptables.
 +
 +{{:learning:pasted:20200827-215916.png?600}}
 +
 +==== Service Discovery ====
 +Так как сервисы - это первичный режим связи в K8s, нужен способ их обнаружения. Их два:
 +
 +  * Переменные окружения (Environment Variables) - так как поды стартуют на любой ноде, служба kubelet добавляет набор переменных окружения в под для всех активных сервисов. Например, если есть активный сервис под названием redis-master, опубликованный на порту 6379 и ClusterIP 172.17.0.6, то в свежесозданном поде будут следующие переменные окружения:
 +<code>
 +REDIS_MASTER_SERVICE_HOST=172.17.0.6
 +REDIS_MASTER_SERVICE_PORT=6379
 +REDIS_MASTER_PORT=tcp://172.17.0.6:6379
 +REDIS_MASTER_PORT_6379_TCP=tcp://172.17.0.6:6379
 +REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
 +REDIS_MASTER_PORT_6379_TCP_PORT=6379
 +REDIS_MASTER_PORT_6379_TCP_ADDR=172.17.0.6
 +</code>
 +В этом случае, надо быть осторожным во время настройки (ordering) сервисов, потому что поды не имеют переменных тех сервисов, которые появились //после// создания подов.
 +
 +  * DNS - в K8s есть дополнение DNS, создающее запись DNS для каждого сервиса в формате my-svc.my-namespace.svc.cluster.local. Сервисы могут найти другие сервисы в рамках пространства имён просто по имени. Если добавить сервис redis-master в пространстве имён my-ns, все поды в одном пространстве имён находят сервис по имени, redis-master. Поды из других пространств имён находят этот сервис, добавляя к имени суффикс - redis-master.my-ns. 
 +
 +Это общепринятая и рекомендуемая практика. В примере выше видно, что настроен внутренний DNS, где frontend-svc привязан к 172.17.0.4, а db-svc - к 172.17.0.5.
 +
 +==== ServiceType ====
 +Во время создания сервиса, выбирается область доступа (access scope). Варианты:
 +  * Доступен только в рамках кластера
 +  * Доступен в рамках кластера и извне
 +  * Сопоставляется с объектом (entity), который находится внутри или снаружи кластера
 +
 +Область доступа определяется ServiceType, который может быть настроен во время создания сервиса.
 +
 +Стандартный ServiceType - это ClusterIP, виртуальный адрес сервиса, который используется для связи с сервисом и доступен только в рамках кластера.
 +
 +=== NodePort ===
 +NodePort ServiceType - это динамически выбираемый порт (стандартно из диапазона 30000-32767), который присваивается сервису на всех рабочих нодах. Например, если NodePort равен 32233 для сервиса frontend-svc, значит, если подключиться к любой рабочей ноде на порт 32233, то она перенаправит трафик на ClusterIP 172.17.0.4. Если нужно указать порт вручную, его можно присвоить из стандартного диапазона.
 +
 +{{:learning:pasted:20200828-154015.png?600}}
 +
 +NodePort используется для публикации сервиса во внешний мир. Клиент подключается к любой рабочей ноде на указанный порт, и дальше его запросы пробрасываются через ClusterIP сервиса к приложениям. Чтобы получить доступ извне к нескольким приложениям, можно настроить обратный прокси - //ingress,// и задать правила для доступа к сервисам кластера.
 +
 +=== LoadBalancer ===
 +С сервис-типом балансировщика:
 +  * NodePort and ClusterIP создаются автоматически, и внешний балансировщик будет распределять трафик между ними
 +  * Сервис занимает статический порт на каждой рабочей ноде
 +  * Опубликованный сервис использует внешний балансировщик облачного провадера
 +
 +{{:learning:pasted:20200830-122820.png?600}}
 +
 +LoadBalancer ServiceType будет работать, только если нижележащая инфраструктура поддерживает автоматическое создание балансировщиков и K8s, например, Google Cloud Platform и AWS. Если эта функция на настроена, IP-адрес балансировщика не задан - сервис будет работать как NodePort type.
 +
 +=== ExternalIP ===
 +Сервис может быть привязан ко внешнему IP, если он может маршрутизировать на одну или несколько рабочих нод. Трафик, допущенный до кластера с внешнего адреса на сервис-порт, пробрасывается до одной из конечных точек. Этот тип требует использования внешних облачных провайдеров, таких как Google Cloud Platform или AWS.
 +
 +{{:learning:pasted:20200830-123640.png?600}}
 +
 +Помните, что внешние адреса (ExternalIPs) не управляются K8s. Администратор кластера должен настроить маршрутизацию, привязывающую внешний адрес к одной из нод.
 +
 +=== ExternalName ===
 +Внешнее имя - особый тип, у которого нет селекторов (Selectors) и не задаются никакие конечные точки(endpoints). Когда к кластеру идёт обращение на ExternalName, оно возвращает запись CNAME внешнего сервиса.
 +
 +Основное назначение ServiceType - сделать внешние сервисы типа my-database.example.com доступными для приложение внутри кластера. Если внешний сервис принадлежит к тому же пространству имён, для всех приложений и сервисов этого пространства имён он будет доступен просто по имени my-database.
 +
 +===== Развёртывание приложения =====
 +Используя панель управления (dashboard).
 +
 +<code bash>
 +# Запустить dashboard - стандартно оно запускается в пространстве имён default
 +minikube dashboard
 +# Если не открывается в браузере, то можно посмотреть ссылку в консоли, она будет чем-то вроде
 +# Opening http://127.0.0.1:55304/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/
 +</code>
 +В интерфейсе нажать кнопку + (create new resource), потом Create from form.
 +  * application name **webserver**
 +  * Container image **nginx:alpine**
 +  * Number of Pods **3**
 +  * Service **none** (он будет создан позже).
 +
 +Show advanced options - там можно задать  Labels, Namespace, Environment Variables и т. д. Стандартно, ярлык app совпадает с именем приложения (в данном случае будет k8s-app: webserver).
 +
 +После нажатия кнопки Deploy запустится развёртывание. Как и ожидалось, деплой webserver создал ReplicaSet (webserver-5d58b6b749), который, в свою очередь, создал три пода (webserver-5d58b6b749-xxxxx).
 +
 +Если не работает простое название образа nginx:alpine, надо добавить полный URL docker.io/library/nginx:alpine или k8s.gcr.io/nginx:alpine.
 +
 +После того, как деплой "webserver" создан, на панели слева можно посмотреть детали про Deployments, ReplicaSets и Pods в пространстве имён "default". То же можно сделать и из командной строки.
 +<code bash>
 +kubectl get deployments
 +kubectl get replicasets
 +kubectl get pods
 +# Подробности про отдельный под
 +kubectl describe pod webserver-5d58b6b749-ffrhq
 +</code>
 +В подробностях про под есть информация о ярлыках, в данном случае это k8s-app=webserver.
 +
 +<code bash>
 +# С ключом -L в вывод добавляются соответствующие колонки, здесь - k8s-app и label2
 +kubectl get pods -L k8s-app,label2
 +# В данном случае в колонке k8s-app будет значение webserver, а label2 пуста.
 +
 +# Ключ -l - использование селектора. Вывести все поды с ключом k8s-app и значением webserver
 +kubectl get pods -l k8s-app=webserver
 +</code>
 +
 +Установить приложение через CLI
 +<code bash>
 +# сначала удалим старый экземпляр, созданный через панель
 +kubectl delete deployments webserver
 +# после удаления деплоя удалятся и наборы реплик, и поды. Команды ничего не покажут
 +kubectl get replicasets
 +kubectl get pods
 +</code>
 +Теперь надо сделать файл, например, webserver.yaml
 +<code yaml>
 +apiVersion: apps/v1
 +kind: Deployment
 +metadata:
 +  name: webserver
 +  labels:
 +    app: nginx
 +spec:
 +  replicas: 3
 +  selector:
 +    matchLabels:
 +      app: nginx
 +  template:
 +    metadata:
 +      labels:
 +        app: nginx
 +    spec:
 +      containers:
 +      - name: nginx
 +        image: nginx:alpine
 +        ports:
 +        - containerPort: 80
 +</code>
 +<code bash>
 +# создать деплой из файла (можно использовать URL)
 +kubectl create -f webserver.yaml
 +# теперь снова есть и наборы реплик, и поды.
 +</code>
 +
 +==== Публикация приложения ====
 +Ранее были рассмотрены ServiceTypes, они задают способ доступа к сервису. NodePort ServiceType - статический порт на всех рабочих нодах, при подключении на который запрос проксируется на ClusterIP сервиса.
 +
 +Конфигурация, к примеру, webserver-svc.yaml
 +<code yaml>
 +apiVersion: v1
 +kind: Service
 +metadata:
 +  name: web-service
 +  labels:
 +    run: web-service
 +spec:
 +  type: NodePort
 +  ports:
 +  - port: 80
 +    protocol: TCP
 +  selector:
 +    app: nginx 
 +</code>
 +
 +<code bash>
 +# применить
 +kubectl create -f webserver-svc.yaml
 +# или опубликовать уже существующий деплой:
 +kubectl expose deployment webserver --name=web-service --type=NodePort
 +
 +# список сервисов
 +kubectl get services
 +NAME          TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
 +kubernetes    ClusterIP   10.96.0.1     <none>        443/TCP        8h
 +web-service   NodePort    10.107.77.9   <none>        80:30168/TCP   2m8s
 +</code>
 +
 +web-service теперь создан, и его ClusterIP is 10.107.77.9. В секции PORT(S) видно, что статический порт на нодах - 30168. Если подключиться на этот порт к ноде, запрос будет переадресован на ClusterIP, порт 80.
 +
 +Нет нужды сначала создавать Deployment, а затем сервис, их можно создавать в любом порядке. Сервис найдёт и подключит поды с помощью селектора. Для получения детальной информации о сервисе, например, web-service, нужно набрать команду
 +<code bash>
 +kubectl describe service web-service
 +</code>
 +В данном случае, web-service использует app=nginx как селектор для логической группировки трёх подов, которые показаны как конечные точки (endpoints). Когда запрос достигает сервиса, он обслуживается одним из подов, перечисленных в секции endpoints.
 +
 +==== Доступ к приложению ====
 +Наше приложение работает на виртуальной машине minikube. Чтобы узнать её IP, нужно выполнить команду
 +<code bash>
 +minikube ip
 +</code>
 +Затем нужно открыть браузер и вбить туда этот адрес и тот порт, который отображался при выводе списка сервисов. Например, http://192.168.224.6:30401. Есть способ открыть сервис в браузере, выполнив команду
 +<code bash>
 +minikube service web-service
 +</code>
 +В данном случае, отобразится стартовая страница nginx внутри одного из подов-конечных точек, куда сервис перенаправил запрос, действуя как балансировщик.
 +
 +==== Проверки состояния и готовности ====
 +Иногда приложения могут не отвечать на запросы или запускаться с задержкой. Добавление проверок на состояние и готовность (Readiness and Liveness Probes) позволяют сервису kubelet отслеживать состояние приложения, запущенного внутри контейнера пода и принудительно перезапускать этот контейнер, если приложение не отвечает. Если настраиваются обе проверки, то рекомендуется дать достаточно времени, чтобы Readiness Probe не прошла проверку, и только потом проверять Liveness Probe. Если и Readiness, и Liveness Probes не проходят проверку, значит есть шанс того, что контейнер никогда не достигнет состояния готовности.
 +
 +=== Liveness Probe ===
 +Когда приложение в контейнере внутри пода не отвечает на запросы, это может произойти, например, при зависании или нехватке памяти. В этом случае, рекомендуется перезапустить контейнер, чтобы приложение снова было доступно. Чтобы не перезапускать его вручную, нужно настроить Liveness Probe, которая проверяет "здоровье" приложения, и в случае чего перезапускает контейнер.
 +
 +Liveness Probes устанавливаются путём настройки:
 +  * Liveness command
 +  * Liveness HTTP request
 +  * TCP Liveness Probe
 +
 +== Liveness Command ==
 +Здесь проверяется наличие файла /tmp/healthy
 +<code yaml>
 +apiVersion: v1
 +kind: Pod
 +metadata:
 +  labels:
 +    test: liveness
 +  name: liveness-exec
 +spec:
 +  containers:
 +  - name: liveness
 +    image: k8s.gcr.io/busybox
 +    args:
 +    - /bin/sh
 +    - -c
 +    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
 +    livenessProbe:
 +      exec:
 +        command:
 +        - cat
 +        - /tmp/healthy
 +      initialDelaySeconds: 5
 +      periodSeconds: 5
 +</code>
 +Проверка наличия этого файла происходит каждые 5 сек (periodSeconds). Параметр initialDelaySeconds говорит kubelet ждать 5 сек перед первой проверкой. Командная строчка сначала создаёт файл, а через полминуты стирает его, что вызывает непрохождение проверки, и под перезапускается.
 +
 +== Liveness HTTP request ==
 +kubelet шлёт запрос HTTP GET на конечную точку /healthz приложения, на порт 8080. Если не отвечает - перезапуск контейнера.
 +<code yaml>
 +livenessProbe:
 +      httpGet:
 +        path: /healthz
 +        port: 8080
 +        httpHeaders:
 +        - name: X-Custom-Header
 +          value: Awesome
 +      initialDelaySeconds: 3
 +      periodSeconds: 3
 +</code>
 +
 +== TCP Liveness probe ==
 +kubelet пытается открыть TCP Socket контейнера с приложением. Не открывается - перезапуск контейнера.
 +<code yaml>
 +livenessProbe:
 +      tcpSocket:
 +        port: 8080
 +      initialDelaySeconds: 15
 +      periodSeconds: 20
 +</code>
 +
 +=== Readiness Probes ===
 +Иногда приложения должны соответствовать определённым требованиям, прежде чем начать работать. Например, убедиться, что зависимый сервис готов к работе, или определить необходимость загрузки большого набора данных. В этом случае используются проверки готовности. Пока они не пройдены, приложение (под с контейнерами) не получает рабочий трафик.
 +<code yaml>
 +readinessProbe:
 +  exec:
 +    command:
 +    - cat
 +    - /tmp/healthy
 +  initialDelaySeconds: 5
 +  periodSeconds: 5
 +</code>
 +
 +Readiness Probes настраиваются так же, как и Liveness Probes, см. [[https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/|документацию]].
 +
 +
 +
 +
 +===== Управление томами (Volumes) =====
 +Контейнеры могут быть и производителями, и потребителями данных. Сами контейнеры - временная сущность, но некоторые данные, с которыми они работают, обязаны храниться постоянно. Для этого K8s обязан предоставлять хранилище, и он использует тома (Volumes) нескольких типов, и несколько других форм хранилищ для работы с данными контейнера.Рассмотрим объекты PersistentVolume и PersistentVolumeClaim, которые подключают постоянно действующие тома (persistent storage Volumes) к подам.
 +
 +При перезапуске контейнера, все данные внутри него удаляются. Чnобы не терять информацию, в K8s используются тома (Volumes) - внешний каталог на устройстве хранения данных. Это хранилище данных, содержимое и режим доступа к нему определяются типом тома (Volume Type).
 +
 +Том подключается к поду и может использоваться всеми контейнерами внутри него. Том живёт столько же, сколько под, но переживает контейнеры внутри него, что позволяет сохранить данные в случае перезапуска контейнеров.
 +
 +Некоторые [[https://kubernetes.io/docs/concepts/storage/volumes/|типы томов]]
 +
 +  * emptyDir - Пустой том, создающийся во время запуска пода на рабочей ноде. Жёстко зависим от жизни пода - если под перестаёт существовать, содержимое emptyDir безвозвратно удаляется.
 +  * hostPath - Каталог на хосте. Если под удаляется, данные остаются.
 +  * gcePersistentDisk - монтирует диск Google Compute Engine (GCE) в под.
 +  * awsElasticBlockStore - монтирует AWS EBS Volume в под.
 +  * azureDisk - монтирует Microsoft Azure Data Disk.
 +  * azureFile - монтирует Microsoft Azure File Volume.
 +  * cephfs - монтирует существующий CephFS volume. Если под удаляется, том отмонтируется, и данные остаются.
 +  * nfs - монтирует NFS share.
 +  * iscsi - монтирует iSCSI share.
 +  * secret - передаёт поду секретные данные, например, пароли.
 +  * configMap - предоставляет поду данные конфигурации, консольные команды и аргументы.
 +  * persistentVolumeClaim - подключает PersistentVolume к поду.
 +
 +==== PersistentVolumes ====
 +В типичной ситуации, хранилище управляется сисадминами, а пользователь получает инструкции по его использованию и хранилищем не управляет. Тут примерно то же самое, но управление становится трудоёмким, учитывая количество типов томов. В K8s есть подсистема PersistentVolume (PV), предоставляющая различные API для пользователей и админов, чтобы пользоваться и управлять постоянным хранилищем (persistent storage). Для управления томом, существует PersistentVolume API resource type, а для его использования - PersistentVolumeClaim API resource type.
 +
 +Persistent Volume - это сетевое хранилище внутри кластера, предоставляемое администратором.
 +
 +{{:learning:pasted:20200831-143431.png?600}}
 +
 +PersistentVolumes могут динамически предоставляться на основе StorageClass resource. StorageClass содержит предустановленных "снабженцев" (provisioners) и параметры для создания PersistentVolume. Используя PersistentVolumeClaims, пользователь посылает запрос на динамическое создание PV, которое связано с ресурсом StorageClass.
 +
 +Некоторые [[https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistent-volumes|типы томов]], поддерживающих управление хранилищем с использованием PersistentVolumes:
 +
 +  * GCEPersistentDisk
 +  * AWSElasticBlockStore
 +  * AzureFile
 +  * AzureDisk
 +  * CephFS
 +  * NFS
 +  * iSCSI
 +
 +==== PersistentVolumeClaims ====
 +[[https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims|PersistentVolumeClaim]] (PVC) - это запрос на предоставление хранилища от пользователя. Запросы на PersistentVolume базируются на типе, режиме доступа и размере.
 +
 +Есть 3 режима доступа:
 +  * ReadWriteOnce (чтение-запись одной нодой)
 +  * ReadOnlyMany (только чтение многими нодами)
 +  * ReadWriteMany (чтение-запись многими нодами)
 +Как только подходящий PersistentVolume найден, он привязывается к PersistentVolumeClaim. После успешной привязки, ресурс PersistentVolumeClaim может использоваться подом.
 +
 +{{:learning:pasted:20200831-153227.png?600|Использование PersistentVolumeClaim в поде}}
 +
 +После завершения работы пользователем, прикреплённые PersistentVolumes могут быть освобождены. Нижестоящие PersistentVolumes, следовательно, могут быть снова задействованы либо для проверки или сбора данных админом, либо для удаления тома вместе с данными, либо подготовлены для будущего использования - будет удалены только данные.
 +
 +
 +
 +
 +==== Container Storage Interface (CSI) ====
 +Разные оркестраторы контейнеров - Kubernetes, Mesos, Docker или Cloud Foundry имеют собственные методы управления внешними хранилищами при использовании Volumes. Для производителей хранилищ, трудно поддерживать разные плагины томов для разных оркестраторов. Производители и участники сообществ различных оркестраторов работают над стандартизацией интерфейса Volume; плагин тома написан по стандарту CSI, разработанном для разных оркестраторов.
 +
 +К версии K8s v1.13 CSI стабилизировался, что позволяет устанавливать новые CSI-совместимые тома очень просто. С CSI можно расширять набор поддерживаемых storage providers без необходимости интегрировать их в код самого K8s.
 +
 +==== Using a Shared hostPath Volume Type ====
 +В этом примере показано, как два контейнера в поде делят один и тот же том, и как данные на томе переживают под.
 +
 +Сначала нужно создать  каталог на хосте, в данном, случае, внутри minikube VM:
 +<code bash>
 +minikube ssh
 +mkdir pod-volume
 +cd pod-volume
 +pwd
 +# Путь /home/docker/pod-volume будет использован в конфигурации hostPath volume type
 +# выйти из VM
 +exit
 +</code>
 +
 +Файл конфигурации:
 +<code yaml>
 +apiVersion: v1
 +kind: Pod
 +metadata:
 +  name: share-pod
 +  labels:
 +    app: share-pod
 +spec:
 +  volumes:
 +  - name: host-volume
 +    hostpath: 
 +      path: /home/docker/pod-volume
 +  containers:
 +  - image: nginx
 +    name: nginx
 +    ports:
 +    - containerPort: 80
 +    volumeMounts:
 +    - mountPath: /usr/share/nginx/html
 +      name: host-volume
 +  - image: debian
 +    name: debian
 +    volumeMounts:
 +    - mountPath: /host-volume
 +      name: host-volume
 +    command: ["/bin/sh", "-c", "echo Testing hostPath Volume Type > /host-vol/index.html; sleep 3600"]
 +</code>
 +Два контейнера (nginx и debian) подключаются к заданному здесь же тому hostPath, и с контейнера debian создаётся содержимое index.html, которое отобразит nginx вместо своей стандартной страницы.
 +
 +<code bash>
 +# запустить конфигурацию
 +kubectl create -f share-pod.yaml
 +# поглядеть, запустилось ли
 +kubectl get pods
 +# опубликовать на NodePort 80
 +kubectl expose pod share-pod --type=NodePort --port=80
 +# посмотреть инфу
 +kubectl get services,endpoints
 +# открыть в браузере, страница nginx должна быть изменена
 +minikube service share-pod
 +# грохнуть share-pod
 +kubectl delete pod share-pod
 +# Под удалился,
 +kubectl get pods
 +# но сервис живой, хоть и без IP-адреса, т. к. под удалён
 +kubectl get services,endpoints
 +</code>
 +
 +Рисуем новую конфигурацию - один контейнер nginx, который будет читать данные с того же тома:
 +<code yaml>
 +apiVersion: v1
 +kind: Pod
 +metadata:
 +  name: check-pod
 +  labels:
 +    app: share-pod
 +spec:
 +  volumes:
 +  - name: check-volume
 +    hostPath: 
 +      path: /home/docker/pod-volume
 +  containers:
 +  - image: nginx
 +    name: nginx
 +    ports:
 +    - containerPort: 80
 +    volumeMounts:
 +    - mountPath: /usr/share/nginx/html
 +      name: check-volume
 +</code>
 +
 +<code bash>
 +# Запуск
 +kubectl create -f check-pod
 +# поглядеть, запустилось ли
 +kubectl get pods
 +# посмотреть инфу
 +kubectl get services,endpoints
 +# Сервис share-pod всё так же отображается и снова получил IP-адрес, т. к. в сервисе появился новый под.
 +# Запустить сервис (он уже был ранее опубликован наNodePort) в браузере
 +minikube service share-pod
 +</code>
 +Результат - nginx прочитал данные с тома и снова показывает изменённую на первом этапе страницу приветствия.
 +===== ConfigMaps и Secrets =====
 +Во время развёртывания приложения бывает нужно передать ему параметры конфигурации, разрешения, пароли, токены и т. д. К примеру, необходимо развернуть 10 приложений для заказчиков, и для каждого заказчика в интерфейсе должно отображаться название компании. Вместо того, чтобы задавать 10 разных докер-образов, можно просто использовать шаблонный образ и передать названия компаний заказчиков как параметры запуска (runtime parameters). В таких случаях используется ConfigMap API resource. Если нужно передать секретную информацию, используется Secret API resource.
 +
 +==== ConfigMaps ====
 +[[https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/|ConfigMaps]] позволяет отделить конфигурацию от образа контейнера. Конфигурация передаётся как пары "ключ-значение", воспринимаемые подами или другими компонентами или контроллерами в форме переменных окружения, наборов команд и аргументов или томов. Можно создавать ConfigMaps из буквальных значений, из конфигурационных файлов, из одного или нескольких файлов или каталогов.
 +
 +=== Создание ConfigMap из буквальных значений (Literal Values) и отображение подробностей ===
 +ConfigMap может быть создан командой kubectl create, а подробности можно посмотреть командой kubectl get.
 +
 +<code bash>
 +# Создать
 +kubectl create configmap my-config --from-literal=key1=value1 --from-literal=key2=value2
 +# Подробности
 +kubectl get configmaps my-config -o yaml
 +
 +apiVersion: v1
 +data:
 +  key1: value1
 +  key2: value2
 +kind: ConfigMap
 +metadata:
 +  creationTimestamp: 2019-05-31T07:21:55Z
 +  name: my-config
 +  namespace: default
 +  resourceVersion: "241345"
 +  selfLink: /api/v1/namespaces/default/configmaps/my-config
 +  uid: d35f0a3d-45d1-11e7-9e62-080027a46057
 +</code>
 +Опция //-o yaml// - вывод в YAML-формате. Как можно видеть, объект имеет тип ConfigMap, и внутри него есть пары "ключ-значение". Имя и прочие детали являются частью поля metadata.
 +
 +=== Создание ConfigMap из файла конфигурации ===
 +Сначала нужно создать файл конфигурации, где указаны тип, метаданные, поля данных, нацеленные на конечную точку v1 на API-сервере:
 +<code yaml>
 +apiVersion: v1
 +kind: ConfigMap
 +metadata:
 +  name: customer1
 +data:
 +  TEXT1: Customer1_Company
 +  TEXT2: Welcomes You
 +  COMPANY: Customer1 Company Technology Pct. Ltd.
 +</code>
 +
 +Если имя файла конфигурации customer1-configmap.yaml создать ConfigMap можно следующей командой:
 +<code bash>
 +kubectl create -f customer1-configmap.yaml
 +</code>
 +
 +=== Создание ConfigMap из файла ===
 +Создаётся файл permission-reset.properties следующего содержания:
 +<code>
 +permission=read-only
 +allowed="true"
 +resetCount=3
 +</code>
 +Затем создаётся ConfigMap:
 +<code bash>
 +kubectl create configmap permission-config --from-file=<path/to/>permission-reset.properties
 +</code>
 +
 +=== Использование ConfigMaps внутри подов ===
 +== Как переменные окружения ==
 +Внутри контейнера можно запрашивать данные "ключ-значение" всей ConfigMap или значения отдельных ключей как переменные окружения.
 +
 +В следующем примере все переменные окружения контейнера myapp-full-container получают значения ключей full-config-map:
 +<code yaml>
 +...
 +  containers:
 +  - name: myapp-full-container
 +    image: myapp
 +    envFrom:
 +    - configMapRef:
 +      name: full-config-map
 +...
 +</code>
 +
 +
 +А здесь все переменные окружения контейнера myapp-specific-container получают значения от отдельных пар "ключ-значение" разных ConfigMaps:
 +<code yaml>
 +...
 +  containers:
 +  - name: myapp-specific-container
 +    image: myapp
 +    env:
 +    - name: SPECIFIC_ENV_VAR1
 +      valueFrom:
 +        configMapKeyRef:
 +          name: config-map-1
 +          key: SPECIFIC_DATA
 +    - name: SPECIFIC_ENV_VAR2
 +      valueFrom:
 +        configMapKeyRef:
 +          name: config-map-2
 +          key: SPECIFIC_INFO
 +...
 +</code>
 +Таким образом, переменной SPECIFIC_ENV_VAR1 присвоено значение ключа SPECIFIC_DATA из config-map-1, а переменной SPECIFIC_ENV_VAR2 значение ключа SPECIFIC_INFO из config-map-2.
 +
 +== Как тома ==
 +Мы можем смонтировать ConfigMap (например, vol-config-map) как том внутри пода. Для каждого ключа ConfigMap там создаётся одноимённый файл, и содержимое файла - это значение.
 +<code yaml>
 +...
 +  containers:
 +  - name: myapp-vol-container
 +    image: myapp
 +    volumeMounts:
 +    - name: config-volume
 +      mountPath: /etc/config
 +  volumes:
 +  - name: config-volume
 +    configMap:
 +      name: vol-config-map
 +...
 +</code>
 +
 +[[https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#define-container-environment-variables-using-configmap-data|документация]]
 +
 +==== Secrets ====
 +Положим, имеется приложение Wordpress, где он подключается к базе MySQL, используя пароль. Во время создания деплоя для Wordpress, можно захардкодить пароль прямо в YAML, но это плохая практика, т. к. пароль будет доступен любому, кто получит доступ к конфиг. файлу.
 +
 +Объект Secret позволяет закодировать секретную информацию перед тем, как использовать. Кодировать можно пароли, токены или пары "ключ-значение", подобно ConfigMaps; к тому же, можно контролировать, как эта информация будет использоваться, сокращая риск случайного раскрытия. В деплоях (Deployments) или других ресурсах, объект Секрет будет указываться без раскрытия содержимого.
 +
 +Важно помнить, что данные Секрета хранятся в виде обычного текста (plain text) внутри etcd, поэтому админы должны ограничивать доступ к API-серверу и etcd. Недавно появилась созможность шифрования этих данных в etcd, эту возможность нужно включить на уровне API.
 +
 +=== Создание секрета из непосредственного ввода (from Literal) и показ подробностей ===
 +<code bash>
 +# создание
 +kubectl create secret generic my-password --from-literal=password=mysqlpassword
 +
 +# детали
 +kubectl get secret my-password
 +NAME          TYPE     DATA   AGE 
 +my-password   Opaque        8m
 +
 +# ещё детали
 +kubectl describe secret my-password
 +Name:          my-password
 +Namespace:     default
 +Labels:        <none>
 +Annotations:   <none>
 +
 +Type  Opaque
 +
 +Data
 +====
 +password:  13 bytes
 +</code>
 +При выводе деталей нигде не показывается содержимое секрета. Тип показан как Opaque ("непрозрачный").
 +
 +=== Создание секрета вручную ===
 +Секрет можно создать вручную из конфига YAML. Следующий пример называется mypass.yaml. Там есть 2 типа карт (maps) для приватной информации внутри секрета: //data// и //stringData.//
 +
 +С инфокартами (data maps), каждое значение приватной информации должно быть закодировано base64. Если нужно использовать конфиг-файл для секрета, надо закодировать пароль base64:
 +<code bash>
 +echo mysqlpassword | base64
 +bXlzcWxwYXNzd29yZAo=
 +</code>
 +И затем уже использовать полученные значение в конфиге:
 +<code yaml>
 +apiVersion: v1
 +kind: Secret
 +metadata:
 +  name: my-password
 +type: Opaque
 +data:
 +  password: bXlzcWxwYXNzd29yZAo=
 +</code>
 +Помните, что кодирование base64 - это не шифрование, и любой может легко декодировать данные:
 +<code bash>
 +echo "bXlzcWxwYXNzd29yZAo=" | base64 --decode
 +mysqlpassword
 +</code>
 +
 +Поэтому убедитесь, что вы не поместили конфиг секрета в исходный код.
 +
 +Со картами данных строк (stringData maps), нет надобности кодировать значение каждого поля приватной информации. Это значение будет закодировано, когда будет создан секрет my-password: 
 +<code yaml>
 +apiVersion: v1
 +kind: Secret
 +metadata:
 +  name: my-password
 +type: Opaque
 +stringData:
 +  password: mysqlpassword
 +</code>
 +
 +Создать секрет, используя mypass.yaml: 
 +<code bash>
 +kubectl create -f mypass.yaml
 +</code>
 +
 +=== Создание секрета из файла и отображение подробностей ===
 +Для этого используется команда kubectl create secret.
 +<code bash>
 +# Сначала закодировать и записать в файл
 +echo mysqlpassword | base64
 +echo -n 'bXlzcWxwYXNzd29yZAo=' > password.txt
 +# Теперь создать секрет из файла
 +kubectl create secret generic my-file-password --from-file=password.txt
 +
 +# После создания можно посмотреть подробности с помощью команд get и describe.
 +# Серкртная информация не отображается, тип - Opaque.
 +kubectl get secret my-file-password
 +kubectl describe secret my-file-password
 +</code>
 +
 +=== Ипользование секретов внутри подов ===
 +Секреты используются контейнерами как [[https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets|смонтированные тома или переменные окружения]] и берутся как целиком, так и специфические "ключи-значения".
 +
 +== Использование сектретов как переменных окружения ==
 +Ссылка только на ключ password секрета my-password и привязка значения к переменной WORDPRESS_DB_PASSWORD:
 +<code yaml>
 +....
 +spec:
 +  containers:
 +  - image: wordpress:4.7.3-apache
 +    name: wordpress
 +    env:
 +    - name: WORDPRESS_DB_PASSWORD
 +      valueFrom:
 +        secretKeyRef:
 +          name: my-password
 +          key: password
 +....
 +</code>
 +
 +== Использование секретов как файлов ==
 +Можно смонтировать секрет как том. В примере создаётся файл для каждого ключа секрета my-password, где файлы имеют те же названия, что и ключи. Внутри файлов - значения.
 +
 +<code yaml>
 +....
 +spec:
 +  containers:
 +  - image: wordpress:4.7.3-apache
 +    name: wordpress
 +    volumeMounts:
 +    - name: secret-volume
 +      mountPath: "/etc/secret-data"
 +      readOnly: true
 +  volumes:
 +  - name: secret-volume
 +    secret:
 +      secretName: my-password
 +....
 +</code>
 +
 +==== Пример использования ConfigMaps ====
 +web-config.yaml - пространство имён default, две строки с данными - STRING и PATH.
 +<code yaml>
 +apiVersion: v1
 +kind: ConfigMap
 +metadata:
 +  name: web-config
 +  namespace: default
 +data:
 +  STRING: Welcome to example of ConfigMaps!
 +  PATH: /usr/share/nginx/html/index.html
 +</code>
 +
 +<code bash>
 +# Применить конфиг
 +kubectl create -f web-config.yaml
 +# Проверить, создалось ли
 +kubectl get configmaps
 +# или kubectl get cm
 +# можно так:
 +kubectl describe cm web-config
 +</code>
 +
 +app-config.yaml
 +<code yaml>
 +apiVersion: v1
 +kind: Pod
 +metadata:
 +  name: app-config
 +spec:
 +  containers:
 +  - image: nginx
 +    name: nginx
 +    command: ["/bin/sh", "-c", "echo $(DATA_STRING) > $(DATA_PATH); sleep 3600"]
 +    env:
 +      - name: DATA_STRING
 +        valueFrom:
 +          configMapKeyRef:
 +            name: web-config
 +            key: STRING
 +            optional: true
 +      - name: DATA_PATH
 +        valueFrom:
 +          configMapKeyRef:
 +            name: web-config
 +            key: PATH
 +            optional: true
 +  restartPolicy: Never
 +</code>
 +
 +<code bash>
 +# Создать под на основе конфига
 +kubectl create -f app-config.yaml
 +# т.к. сервис не создавался и не публиковался, лезем внутрь контейнера
 +kubectl exec app-config -- /bin/sh -c 'cat /usr/share/nginx/html/index.html'
 +# Welcome to example of ConfigMaps!
 +</code>
 +===== Ingress =====
 +{{ :learning:pasted:20200907-151026.png?400}} В прошлых главах мы получали доступ к приложению через публикацию сервиса, для чего использовались ServiceTypes - чаще всего, NodePort и LoadBalancer. Для LoadBalancer ServiceType необходимо иметь поддержку нижележащей инфраструктуры, но даже если такая поддержка есть, то использовать её для всех сервисов накладно и по ресурсам, и по деньгам. Управление NodePort ServiceType порой сложно, т. к. нужно держать настройки прокси в актуальном состоянии и отслеживать номера присвоенных портов. Ingress API - ещё один уровень абстракции перед сервисами, предоставляющий универсальный метод управления доступом к приложениям извне.
 +
 +Сервисы определяют правила маршрутизации для конкретных сервисов по отдельности и существуют, пока существует сервис. Так как сервисов много, то много и правил. Но если разделить правила маршрутизации и приложение и централизовать управление этими правилами, то можно обновлять приложение, не беспокоясь о доступе к нему извне. Этим и занимается [[https://kubernetes.io/docs/concepts/services-networking/ingress/|Ingress]] - "набор правил, разрешающий входящие подключения к сервисам кластера".
 +
 +Ingress - балансировщик HTTP/HTTPS 7-го уровня со следующими возможностями:
 +  * TLS (Transport Layer Security)
 +  * Name-based virtual hosting
 +  * Fanout routing
 +  * Loadbalancing
 +  * Custom rules
 +
 +При использовании Ingress, пользователь не подключается непосредственно к сервису, а к Ingress, который переправляет его запрос к желаемому сервису. Пример конфига:
 +<code yaml>
 +apiVersion: networking.k8s.io/v1beta1
 +kind: Ingress
 +metadata:
 +  name: virtual-host-ingress
 +  namespace: default
 +spec:
 +  rules:
 +  - host: blue.example.com
 +    http:
 +      paths:
 +      - backend:
 +          serviceName: webserver-blue-svc
 +          servicePort: 80
 +  - host: green.example.com
 +    http:
 +      paths:
 +      - backend:
 +          serviceName: webserver-green-svc
 +          servicePort: 80
 +</code>
 +В этом примере, запросы к blue.example.com и green.example.com идут к Ingress и оттуда направляются соответственно на webserver-blue-svc и webserver-green-svc. Это пример правила //Name-Based Virtual Hosting//, т. е., запросы к поддоменам.
 +
 +//Fanout Ingress rules// - когда запросы к example.com/blue и example.com/green направляются соответственно на webserver-blue-svc и webserver-green-svc, т. е., меняется URL.
 +<code yaml>
 +apiVersion: networking.k8s.io/v1beta1
 +kind: Ingress
 +metadata:
 +  name: fan-out-ingress
 +  namespace: default
 +spec:
 +  rules:
 +  - host: example.com
 +    http:
 +      paths:
 +      - path: /blue
 +        backend:
 +          serviceName: webserver-blue-svc
 +          servicePort: 80
 +      - path: /green
 +        backend:
 +          serviceName: webserver-green-svc
 +          servicePort: 80
 +</code>
 +
 +==== Ingress Controller ====
 +Ingress Controller - приложение, следящее за изменениями ресурсов Ingress на API-сервере мастер-ноды и обновляет балансировщик в соответствии с ними. K8s поддерживает несколько контроллеров Ingress, и если нужно, можно сделать свой собственный. Самые популярные -  GCE L7 Load Balancer Controller и Nginx Ingress Controller. Прочие - Istio, Kong, Traefik и т. д.
 +<code bash>
 +# Запустить Ingress Controller в Minikube
 +minikube addons enable ingress
 +</code>
 +
 +==== Развёртывание Ingress resource ====
 +После Ingress Controller нужно создать Ingress resource. Например, если был создан файл конфигурации virtual-host-ingress.yaml с правилом Name-Based Virtual Hosting, то нужно выполнить команду:
 +<code bash>
 +kubectl create -f virtual-host-ingress.yaml
 +</code>
 +
 +Как только Ingress resource создан, можно получить доступ к сервисам webserver-blue-svc или webserver-green-svc через адреса blue.example.com и green.example.com. Т. к. текущая установка - это Minikube, надо отредактировать файл hosts, добавив туда строку
 +<code>
 +<ip-address>   blue.example.com green.example.com 
 +</code>
 +
 +===== Углублённые темы =====
 +
 +==== Annotations ====
 +Аннотации можно прицепить к любому объекту, они имеют формат "ключ-значение", например, в деплое
 +<code yaml>
 +apiVersion: extensions/v1beta1
 +kind: Deployment
 +metadata:
 +  name: webserver
 +  annotations:
 +    description: Deployment based PoC dates 2nd May'2019
 +    developer: Vasya
 +....
 +</code>
 +
 +В отличие от ярлыков, аннотации не используются для распознавания и выбора объектов. Они используются для доп. информации, такой, как
 +  * Store build/release IDs, PR numbers, git branch
 +  * Phone/pager numbers of people responsible, or directory entries specifying where such information can be found
 +  * Pointers to logging, monitoring, analytics, audit repositories, debugging tools
 +и т. п.
 +
 +Будут видны в деталях деплоя
 +<code bash>
 +kubectl describe deployment webserver
 +</code>
 +
 +==== Jobs и CronJobs ====
 +Job (задача) создаёт поды для конкретной задачи и несёт ответственность за сбои пода, обеспечивает выполнение задачи. Когда задача выполнена, поды уничтожаются автоматически. Настройки конфигурации Job-а включают в себя:
 +  * parallelism - кол-во подов, запускаемых параллельно
 +  * completions - число ожидаемых выполнений
 +  * activeDeadlineSeconds - время, отведённое на задачу
 +  * backoffLimit - число попыток выполнения, после которых задача помечается как неуспешная
 +  * ttlSecondsAfterFinished - отсрочка чистки после завершения задач
 +
 +Начиная с версии K8s 1.4, появилась возможность  выполнять задачи по расписанию (CronJobs), в этом случае каждый запланированный раз будет создаваться новая задача. Настройка конфигурации задачи по расписанию включает:
 +  * startingDeadlineSeconds - интервал для запуска пропущенных задач
 +  * concurrencyPolicy - разрешение или запрет на одновременное выполнение задач или замещения старых задач новыми
 +
 +==== Quota Management ====
 +Когда кластером пользуется множество людей, возникает вопрос распределения ресурсов. ResourceQuota API resource ограничивает потребляемые ресурсы в рамках пространства имён.
 +
 +Типы квот:
 +  * Compute Resource Quota - ограничение общего кол-ва ресурсов
 +  * Storage Resource Quota - ограничение ресурсов хранения (PersistentVolumeClaims, requests.storage и т. п.)
 +  * Object Count Quota - ограничение кол-ва объектов определённого типа (pods, ConfigMaps, PersistentVolumeClaims, ReplicationControllers, Services, Secrets и т. п.)
 +
 +==== Autoscaling ====
 +Несмотря на то, что масштабировать объекты вручную достаточно просто, но когда в кластере их сотни и тысячи, нужна автоматизация - динамическое масштабирование, добавляющее или удаляющее объекты из кластера, основываясь на потреблении ресурсов, их доступности и требованиях приложений.
 +
 +Автомасштабирование может быть задействовано в кластере через контроллеры, которые периодически регулируют кол-во запущенных объектов, сверяясь с разными метриками. Ниже приведены некоторые типы контроллеров автомасштабирования. Они могут использоваться как отдельно, так и совместно с другими для более тонкой настройки:
 +  * [[https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#algorithm-details|Horizontal Pod Autoscaler]] (HPA) - регулирует число реплик в ReplicaSet, Deployment или Replication Controller на основе загрузки процессора.
 +  * [[https://github.com/kubernetes/community/blob/master/contributors/design-proposals/autoscaling/vertical-pod-autoscaler.md|Vertical Pod Autoscaler]] (VPA) - регулирует требования контейнера к ресурсам (CPU и память) в поде и динамически регулируют их по ходу работы (in runtime - в контейнерном движке?) на основе истории использования ресурсов, текущей доступности и событий реального времени.
 +  * [[https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler|Cluster Autoscaler]] - автоматически меняет размер кластера, если есть неэффективно используемые ресурсы или какие-то ноды недостаточно нагружены.
 +
 +==== DaemonSets ====
 +Если нужно собирать данные мониторинга со всех нод или запускать на них сервис хранилища (storage daemon), на всех нодах должен постоянно работать особый под, и это делается объектом [[https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/|DaemonSet]]. DaemonSet управляет агентами kube-proxy, запускающимися на каждой ноде кластера.
 +
 +Когда нода добавляется в кластер, под с DaemonSet запускается на ней с помощью стандартного кластерного планировщика. Когда нода выключается или выходит из состава кластера или удаляется DaemonSet, соответствующие поды собираются как мусор.
 +
 +Новая функция ресурса DaemonSet - запуск подов только на указанных нодах путём настройки nodeSelectors и node affinity rules. Подобно Deployment resources, DaemonSets поддерживают rolling updates и rollbacks.
 +
 +Конфиг DaemonSet идентичен ReplicaSet, за исключением kind.
 +<code yaml>
 +apiVersion: apps/v1
 +kind: DaemonSet
 +metadata:
 +  name: monitoring
 +spec:
 +  selector:
 +    matchLabels:
 +      app: monitoring-agent
 +  template:
 +    metadata:
 +      labels:
 +        app: monitoring-agent
 +    spec:
 +      containers:
 +      - name: monitoring-agent
 +        image: monitoring-agent
 +</code>
 +
 +==== StatefulSets ====
 +Контроллер [[https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/|StatefulSet]] используется для stateful-приложений, требующих уникальных данных типа имени, сетевых идентификаторов, строгой очерёдности и т. п., например, кластер MySQL, кластер etcd.
 +
 +StatefulSet controller предоставляет уникальные данные (identity) и гарантирует очерёдность деплоя и масштабирования подов. Подобно Deployments, StatefulSets используют ReplicaSets как промежуточное звено для управления подами и поддерживают rolling updates и rollbacks.
 +
 +==== Kubernetes Federation ====
 +Даёт возможность управления несколькими кластерами из одной панели управления. Общая синхронизация ресурсов и обнаружение, что позволяет развёртывать географически распределённые приложения, обеспечивать доступ к ним через глобальный DNS и строить высокодоступную инфраструктуру.
 +
 +Хотя это пока альфа-версия, [[https://kubernetes.io/docs/concepts/cluster-administration/federation/|федерация]] полезна при создании гибридной системы, когда одна часть вертится внутри собственной сети, а вторая часть в облаке, что даёт возможность не привязываться к поставщику услуг. Настраивается вес каждого кластера для распределения нагрузки.
 +
 +==== Custom Resources ====
 +В Kubernetes, ресурс - это конечная точка API, хранящая набор объектов API. Например, ресурс Pod содержит все Pod-объекты.
 +
 +Хотя в большинстве случаев существующие ресурсы Kubernetes достаточны для выполнения наших требований, можно создавать новые, используя настраиваемые ресурсы (custom resources). Custom resources по природе динамические и могут появляться и исчезать в существующем кластере в любое время.
 +
 +Для создания своего ресурса необходимо создать и установить custom controller, который может интерпретировать структуру ресурса и выполнять необходимые действия. Custom controllers могут быть установлены и управляться в уже работающем кластере. Есть 2 способа добавления настраиваемого ресурса:
 +  * [[https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/|Custom Resource Definitions]] (CRDs) - самый простой, не требующий большого знания программирования.
 +  * [[https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/apiserver-aggregation/|API Aggregation]] - чтобы получить более гибкое управление, можно написать API-агрегаторы. Это подчинённые API-сервера, находящиеся за первичным, который является для них прокси-сервером для запросов.
 +
 +==== Helm ====
 +Чтобы развернуть приложение, нужно написать кучу манифестов - Deployments, Services, Volume Claims, Ingress и т. д., что иногда бывает трудно. Есть способ объединить их все в одном формате, называемом схемой (Chart). Эти схемы распространяются через репозитории подобно пакетам rpm и deb. [[https://helm.sh/|Helm]] - это пакетный менеджер для K8s, который ставит/обновляет/удаляет схемы в кластере, по типу apt или yum в линуксе. k8s здесь рассматривается как операционная система.
 +
 +Клиент подключается к серверу для управления [[https://github.com/helm/charts|чартами]].
 +
 +Установить Helm: https://helm.sh/docs/intro/install/
 +
 +  * Чарт — формат пакета в Helm, это каталог определённой стуктуры с текстовыми файлами (набор шаблонов, файл с дефолтными значениями переменных и метаданные).
 +  * Пакетом в Helm называется чарт упакованный в .tgz архив, у которого есть версия.
 +  * Релиз - экземпляр чарта, который работает в кластере Kubernetes
 +  * Приложение для Kubernetes — это набор манифестов.
 +  * Шаблон (шаблонизированный манифест) — это манифест, часть значений в котором заменена на переменные.
 +
 +Структура чарта
 +<code bash>
 +<chart_name>/
 +    .helmignore # список файлов, игнорируемых при создании чарта
 +    Chart.yaml # метаинформация: зависимости, разработчики, описание, версия и т. д.
 +    values.yaml # значения переменных по умолчанию
 +    charts/ # каталог для чартов, от которых зависит этот чарт
 +    crds/ # собственные определения ресурсов
 +    templates/ # шаблоны .yaml (манифесты k8s), куда подставляются переменные из values.yaml
 +        _helpers.tpl # Вспомогательные шаблоны
 +        NOTES.txt # Текст, выводимый после применения чарта (инстукция по подключению и т. п.)
 +</code>
 +
 +Для шаблонизации в Helm используются go templates, в котором, подобно jinja2, есть свои правила использования. Все инструкции шаблона заключаются в символы ''%%{{%%'' и ''%%}}%%''. Текст вне этих символов будет простым текстом. Простой текст копируется из шаблона в вывод без какого-либо изменения.
 +В ''%%{{ }}%%'' могут быть записаны выражение/функция/обращение к переменной.
 +Часто можно встретить такие конструкции:
 +
 +''%%{{ .Values.<имя переменной> }}%%'' — значения берутся из файла с переменными values.yaml.\\
 +''%%{{ .Chart.<имя переменной> }}%%'' — значения переменных ищутся в файле Chart.yaml.\\
 +''%%{{ .Release.Name }}%%'' — имя, которое передаётся первым параметром команде helm install.
 +    
 +https://helm.sh/docs/chart_template_guide/builtin_objects/
 +
 +Пример Chart.yaml
 +<code yaml>
 +apiVersion: v2 # версия api чарта
 +name: nginx # имя чарта
 +type: application
 +version: 0.1.0 # версия чарта, должна быть по SemVer2
 +appVersion: "latest" # версия приложения
 +description: A Helm chart
 +
 +dependencies:
 +  - name: backend
 +    version: 0.1.0
 +  - name: frontend
 +    version: 0.1.0
 +</code>
 +
 +Пример values.yaml
 +<code yaml>
 +image:
 +  repository: nginx
 +  tag: 1.21.6
 +imagePullPolicy: IfNotPresent
 +replicas: 2
 +service:
 +  port: 80
 +resources:
 +  requests:
 +    memory: "64Mi"
 +    cpu: "100m"
 +  limits:
 +    memory: "128Mi"
 +    cpu: "200m"
 +</code>
 +Пример templates/deploy.yaml
 +<code yaml>
 +---
 +apiVersion: apps/v1
 +kind: Deployment
 +metadata:
 +  name: {{ .Release.Name }}
 +  labels:
 +    app: {{ .Chart.Name }}
 +spec:
 +  replicas: {{ .Values.replicas }}
 +  selector:
 +    matchLabels:
 +      app: {{ .Chart.Name }}
 +  template:
 +    metadata:
 +      labels:
 +        app: {{ .Chart.Name }}
 +    spec:
 +      containers:
 +      — name: nginx
 +        image: "{{ .Values.image.repository }}:{{.Values.image.tag }}"
 +        imagePullPolicy: {{ .Values.imagePullPolicy }}
 +        ports:
 +          — containerPort: {{.Values.service.port }}
 +        resources:
 +{{ toYaml .Values.resources | indent 10 }}
 +</code>
 +
 +<code bash>
 +# Проверить чарт
 +helm lint ./<chart_name>
 +
 +# Создать пакет (Chart.Name-Chart.Version.tgz)
 +helm package ./<chart_name>
 +
 +# Запустить в Кубере
 +helm install <name> ./<chart_name>
 +
 +# Переопределить версию образа при запуске
 +helm install --set image.tag=1.21.5 <name> ./<chart_name>
 +
 +# посмотреть информацию о шаблонах и значениях переменных, которые отправил Helm
 +helm get all <name>
 +
 +# Список версий чартов.
 +helm search repo bitnami/rabbitmq -l
 +# Chart version - версия чарта, это не версия приложения, там меняются параметры, добавляются функции и т. п.
 +# App version - версия приложения.
 +
 +# Установить определённую версию чарта
 +helm install myrabbitmq bitnami/rabbitmq --version 6.18.3
 +# Показать readme чарта
 +helm show readme bitnami/rabbitmq --version 6.18.3 |less
 +# readme уже установленного чарта (релиза)
 +helm status myrabbitmq
 +
 +# Обновление. Реплики также задаются через переменную Helm, а не через кластер напрямую.
 +helm гupgrade myrabbitmq bitnami/rabbitmq --version 6.18.3 --set replicas=3 --set rabbitmq.password=pass --set rabbitmq.erlangCookie=cookie
 +# Обновление на новую версию
 +helm гupgrade myrabbitmq bitnami/rabbitmq --version 6.27.0 --set replicas=3 --set rabbitmq.password=pass --set rabbitmq.erlangCookie=cookie
 +
 +# История релизов
 +helm history myrabbitmq
 +# Откатиться на пред. версию
 +helm rollback myrabbitmq
 +# Деинсталляция
 +helm uninstall myrabbitmq
 +
 +# Показать манифест с подставленными переменными
 +helm template ./<chart_name>
 +# Конкретный шаблон
 +helm template ./<chart_name> -s templates/jenkins-deploy.yaml
 +</code>
 +
 +У имени деплоя должно быть уникальное имя в рамках пространства имён, поэтому в metadata.name нужно указывать изменяющееся имя, например
 +<code yaml>
 +apiVersion: apps/v1
 +kind: Deployment
 +metadata:
 +  name: {{ .Release.Name }}-{{ .Chart.Name }}
 +</code>
 +Можно заменить эту конструкцию на функцию
 +<code yaml>
 +metadata:
 +  name: {{ printf "%s-%s" .Release.Name .Chart.Name }}
 +# Так же можно поступить и с образом (только разделитель будет ":")
 +  image: {{ printf "%s:%s" .Values.image.repository .Values.image.tag }}
 +</code>
 +
 +Значение по умолчанию
 +<code yaml>
 +env:
 +  - name: USERNAME
 +    value: {{ default "test" .Values.username | quote }}
 +</code>
 +Quote нужен для того, чтобы нормально обрабатывались значения с пробелами
 +В это время в values.yaml: ''%%username: ""%%''
 +
 +Условие if/else: если пароль предоставлен ''--set password='', использовать его, иначе значение по умолчанию.\\
 +Дефис перед открывающими фигурными скобками удаляет всю пустоту в начале строки (типа trimStart). Можно делать это и в конце ''-}}''
 +<code yaml>
 +env:
 +  - name: PASSWORD
 +    {{- if .Values.password }}
 +    value: {{ .Values.password }}
 +    {{- else }}
 +    value: testPassword
 +    {{- end }}
 +</code>
 +В values.yaml: ''%%password: ""%%''
 +
 +Использовать дочерние значения
 +<code yaml>
 +ports:
 +  {{- range .Values.containerPorts }}
 +  - name: {{ .name }}
 +    containerPort: {{ .port }}
 +    {{- end }}
 +</code>
 +В values.yaml:
 +<code yaml>
 +containerPorts:
 +  - name: http
 +    port: 8080
 +</code>
 +
 +Макрос шаблона позволяет избавиться от повторяющихся инструкций в разных шаблонах. Макросы содержатся в _helpers.tpl.
 +Вместо повторяющихся в каждом шаблоне
 +<code yaml>
 +metadata:
 +  name: {{ printf "%s-%s" .Release.Name .Chart.Name }}
 +</code>
 +пишем
 +<code yaml>
 +metadata:
 +  name: {{ template "nginx.fullname" . }}
 +</code>
 +
 +Вариант с кодированием base64: если значения даны, то кодировать их, если нет - сгенерировать и кодировать.
 +<code yaml>
 +apiVersion: v1
 +kind: Secret
 +metadata:
 +  name: {{ include "minio.fullname" . }}
 +  labels:
 +    {{- include ""minio.labels" . |nindent 4 }}
 +data:
 + {{- if empty .Values.accessKey }}
 + access-key: {{ randAlphaNum 10 | b64enc | quote }}
 + {{- else }}
 + access-key: {{ .Values.accessKey | b64enc | quote }}
 + {{- end }}
 + {{- if empty .Values.secretKey }}
 + access-key: {{ randAlphaNum 10 | b64enc | quote }}
 + {{- else }}
 + access-key: {{ .Values.secretKey | b64enc | quote }}
 + {{- end }}
 +</code>
 +
 +==== Security Contexts and Pod Security Policies ====
 +Время от времени нужно задавать специфические разрешения и настраивать доступ к подам и контейнерам. Security Contexts позволяют задать Discretionary Access Control for object access permissions, privileged running, capabilities, security labels, etc. Но их эффект ограничен отдельными подами и контейнерами, у которых заданы соответствующие настройки в разделе spec. 
 +
 +Если нужно задать настройки безопасности на многих подах или контейнерах в рамках всего кластера, надо настраивать Pod Security Policies. Они позволяют задавать тонкие настройки безопасности to control the usage of the host namespace, host networking and ports, file system groups, usage of volume types, enforce Container user and group ID, root privilege escalation, etc.
 +
 +==== Network Policies ====
 +Изначально в Kubernetes все поды свободно общаются без ограничений, но иногда необходимо ограничить доступ. Сетевые политики - это наборы правил, определяющие, как поды могут общаться с другими подами и ресурсами в кластере и вне его. Поды, находящиеся вне этой политики, продолжают общение без ограничений. Network Policies очень похожи на типичный межсетевой экран.
 +
 +Network Policy API resource specifies podSelectors, Ingress and/or Egress policyTypes, and rules based on source and destination ipBlocks and ports. Very simplistic default allow or default deny policies can be defined as well. As a good practice, it is recommended to define a default deny policy to block all traffic to and from the Namespace, and then define sets of rules for specific traffic to be allowed in and out of the Namespace. 
 +
 +Let's keep in mind that not all the networking solutions available for Kubernetes support Network Policies. Review the Pod-to-Pod Communication section from the Kubernetes Architecture chapter if needed. By default, Network Policies are namespaced API resources, but certain network plugins provide additional features so that Network Policies can be applied cluster-wide. 
 +
 +==== Monitoring and Logging ====
 +Сбор данных по использованию подов, нод, сервисов и т. д. нужен, чтобы понимать потребление ресурсов и на этой основе принимать решения по масштабированию приложения. Вот 2 популярных решения по мониторингу:
 +  * [[https://kubernetes.io/docs/tasks/debug-application-cluster/resource-metrics-pipeline/#metrics-server|Metrics Server]] - агрегатор данных использования ресурсов со всего кластера
 +  * [[https://prometheus.io/|Prometheus]] - собирает данные от разных компонентов и объектов.
 +
 +Другой важный компонент - логи. Можно собирать логи разных кластерных компонентов, объектов, нод и т. д., но Kubernetes не предоставляет логирование по всему кластеру, так что для этого нужны сторонние решения. Популярное решение сбора логов - это [[https://kubernetes.io/docs/tasks/debug-application-cluster/logging-elasticsearch-kibana/|Elasticsearch]], который использует [[http://www.fluentd.org/|fluentd]] как агент на нодах.
 +
 +
 +
 +===== What's Next on Your Kubernetes Journey? =====
 +Now that you have a better understanding of Kubernetes, you can continue your journey by:
 +  * Participating in activities and discussions organized by the Kubernetes community
 +  * Attending events organized by the Cloud Native Computing Foundation and The Linux Foundation
 +  * Expanding your Kubernetes knowledge and skills by enrolling in the self-paced LFS258 - Kubernetes Fundamentals,  LFD259 - Kubernetes for Developers, or the instructor-led LFS458 - Kubernetes Administration and LFD459 - Kubernetes for App Developers, paid courses offered by The Linux Foundation
 +  * Preparing for the Certified Kubernetes Administrator or the Certified Kubernetes Application Developer exams, offered by the Cloud Native Computing Foundation
 +And many other options.
 +
 +===== Ошибки =====
 +==== certificate has expired or is not yet valid ====
 +Кластер долго не обновлялся, после любой команды пишет\\
 +<color #ed1c24>[authentication.go:64] Unable to authenticate the request due to an error: [x509: certificate has expired or is not yet valid, x509: certificate has expired or is not yet valid]</color>
 +<code bash>
 +kubeadm certs renew all
 +reboot
 +sudo cp ~/.kube/config ~/.kube/.old-$(date --iso)-config
 +sudo cp /etc/kubernetes/admin.conf ~/.kube/config
 +</code>
 +https://stackoverflow.com/questions/49885636/kubernetes-expired-certificate
  

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki