├── README.md
├── assets
├── lesson_3
│ ├── 3.Application_abstractions_01.png
│ ├── 3.Application_abstractions_02.png
│ ├── 3.Application_abstractions_03.png
│ ├── 3.Application_abstractions_04.png
│ ├── 3.Application_abstractions_05.png
│ ├── 3.Application_abstractions_06.png
│ ├── 3.Application_abstractions_07.png
│ ├── 3.Application_abstractions_08.png
│ ├── 3.Application_abstractions_09.png
│ └── FailedScheduling.png
├── lesson_5
│ ├── emptyDir.png
│ ├── hostPath.png
│ ├── manually_managed_PV.png
│ ├── screenshot1.png
│ └── screenshot2.png
├── lesson_6
│ ├── IngressCertManager.png
│ ├── externalIPs.png
│ ├── externalName.png
│ ├── headless.png
│ ├── ingress.png
│ ├── ingressSecretTls.png
│ ├── iptables1.png
│ ├── iptables2.png
│ ├── iptables3.png
│ ├── services.png
│ └── slurmy.png
└── lesson_7
│ ├── 001_etcd.png
│ ├── 002_api_server.png
│ ├── 003_controller_manager.png
│ ├── 004_scheduler.png
│ └── 005_kubelet.png
├── clipboardHelper.ps1
├── lesson 2.md
├── lesson 3.md
├── lesson 4.md
├── lesson 5.md
├── lesson 6.md
└── lesson 7.md
/README.md:
--------------------------------------------------------------------------------
1 | # slurmio-school-dev-k8s-notes
2 | Notes created in the learning process of [kubernetes school (by slurm & VK (ex mail.ru) cloud solutions)](https://slurm.io/kubernetes-for-developers-school)
3 |
4 | [Youtube playlist](https://www.youtube.com/playlist?list=PL8D2P0ruohOBSA_CDqJLflJ8FLJNe26K-)
5 |
6 | # Объявление
7 | В данный момент не могу стабильно продолжать вести конспекты, это отнимает много времени и сил (законспектировать 5 минут видео занимает около часа). Я постараюсь уделять этому какое-то время, но в лучшем случае это будет 1 лекция в неделю. Если есть желающие помочь - буду очень рад
8 |
--------------------------------------------------------------------------------
/assets/lesson_3/3.Application_abstractions_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_3/3.Application_abstractions_01.png
--------------------------------------------------------------------------------
/assets/lesson_3/3.Application_abstractions_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_3/3.Application_abstractions_02.png
--------------------------------------------------------------------------------
/assets/lesson_3/3.Application_abstractions_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_3/3.Application_abstractions_03.png
--------------------------------------------------------------------------------
/assets/lesson_3/3.Application_abstractions_04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_3/3.Application_abstractions_04.png
--------------------------------------------------------------------------------
/assets/lesson_3/3.Application_abstractions_05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_3/3.Application_abstractions_05.png
--------------------------------------------------------------------------------
/assets/lesson_3/3.Application_abstractions_06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_3/3.Application_abstractions_06.png
--------------------------------------------------------------------------------
/assets/lesson_3/3.Application_abstractions_07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_3/3.Application_abstractions_07.png
--------------------------------------------------------------------------------
/assets/lesson_3/3.Application_abstractions_08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_3/3.Application_abstractions_08.png
--------------------------------------------------------------------------------
/assets/lesson_3/3.Application_abstractions_09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_3/3.Application_abstractions_09.png
--------------------------------------------------------------------------------
/assets/lesson_3/FailedScheduling.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_3/FailedScheduling.png
--------------------------------------------------------------------------------
/assets/lesson_5/emptyDir.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_5/emptyDir.png
--------------------------------------------------------------------------------
/assets/lesson_5/hostPath.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_5/hostPath.png
--------------------------------------------------------------------------------
/assets/lesson_5/manually_managed_PV.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_5/manually_managed_PV.png
--------------------------------------------------------------------------------
/assets/lesson_5/screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_5/screenshot1.png
--------------------------------------------------------------------------------
/assets/lesson_5/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_5/screenshot2.png
--------------------------------------------------------------------------------
/assets/lesson_6/IngressCertManager.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_6/IngressCertManager.png
--------------------------------------------------------------------------------
/assets/lesson_6/externalIPs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_6/externalIPs.png
--------------------------------------------------------------------------------
/assets/lesson_6/externalName.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_6/externalName.png
--------------------------------------------------------------------------------
/assets/lesson_6/headless.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_6/headless.png
--------------------------------------------------------------------------------
/assets/lesson_6/ingress.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_6/ingress.png
--------------------------------------------------------------------------------
/assets/lesson_6/ingressSecretTls.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_6/ingressSecretTls.png
--------------------------------------------------------------------------------
/assets/lesson_6/iptables1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_6/iptables1.png
--------------------------------------------------------------------------------
/assets/lesson_6/iptables2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_6/iptables2.png
--------------------------------------------------------------------------------
/assets/lesson_6/iptables3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_6/iptables3.png
--------------------------------------------------------------------------------
/assets/lesson_6/services.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_6/services.png
--------------------------------------------------------------------------------
/assets/lesson_6/slurmy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_6/slurmy.png
--------------------------------------------------------------------------------
/assets/lesson_7/001_etcd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_7/001_etcd.png
--------------------------------------------------------------------------------
/assets/lesson_7/002_api_server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_7/002_api_server.png
--------------------------------------------------------------------------------
/assets/lesson_7/003_controller_manager.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_7/003_controller_manager.png
--------------------------------------------------------------------------------
/assets/lesson_7/004_scheduler.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_7/004_scheduler.png
--------------------------------------------------------------------------------
/assets/lesson_7/005_kubelet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l2aggron/slurmio-school-dev-k8s-notes/79f2e6c8ece0bfaba8a36def089acb835ddc9c75/assets/lesson_7/005_kubelet.png
--------------------------------------------------------------------------------
/clipboardHelper.ps1:
--------------------------------------------------------------------------------
1 | while ($true) {
2 | $clipboard = Get-Clipboard
3 |
4 | # Конвертируем ссылку с привязкой ко времени в markdown таймкод
5 | if ($clipboard -match "^https:\/\/youtu\.be\/.+\?t=\d+$") {
6 | $totalSeconds = ($clipboard -split "t=")[1]
7 | $timestamp = (New-TimeSpan -Start (Get-Date 0) -End (Get-Date 0).AddSeconds($totalSeconds)).ToString()
8 | "[$timestamp]($clipboard)" | Set-Clipboard
9 | }
10 |
11 | # Конвертируем вывод терминала для вставки в markdown
12 | if ($clipboard -match "^\$ k ") {
13 | $clipboard = $clipboard -replace "^\$ k ", "> kubectl "
14 | @('```shell', $clipboard[0], "", @($clipboard | select-object -skip 1), '```') | Set-Clipboard
15 | }
16 | elseif ($clipboard -match "^\$") {
17 | $clipboard = $clipboard -replace "\$", ">"
18 | @('```shell', $clipboard[0], "", @($clipboard | select-object -skip 1), '```') | Set-Clipboard
19 | }
20 |
21 | sleep -Milliseconds 200
22 | }
--------------------------------------------------------------------------------
/lesson 2.md:
--------------------------------------------------------------------------------
1 | ---
2 | date created: 2021-10-05 19:29:55 (+03:00), Tuesday
3 | ---
4 | # [Запись урока](https://www.youtube.com/watch?v=V6aGfrMXhbA) на youtube
5 | # Первые минуты
6 | - Всего понемногу, в частности, как стоит и не стоит работать с файлами в репозитории школы
7 | # Pod [00:24:35](https://youtu.be/V6aGfrMXhbA?t=1475)
8 | - Краеугольный камень всего kubernetes
9 | - [Документация](https://kubernetes.io/docs/concepts/workloads/pods/)
10 | - Pod — не аббревиатура, в переводе "стручок" (горошины — [контейнеры](https://kubernetes.io/docs/concepts/containers/))
11 | - Как правило, является одним инстансом запущенного приложения, сервиса, микросервиса
12 | - Если kubernetes воспринимать не только как систему оркестрации контейнеров, а как кластерную ОС, то pod можно воспринимать как процесс (это метафора, аналогия, не стоит воспринимать буквально, [начало обсуждения на эту тему](https://t.me/c/1540117827/17176))
13 | - Является минимальной абстракцией kubernetes (как правило, внутри него 2 контейнера, иногда больше)
14 | - Один из контейнеров - приложение, разворачиваемое из указанного образа
15 | - Второй - имеет в имени "POD", несёт в себе сетевой неймспейс (network namespace) для данного pod, в нём запущен [процесс pause](https://stackoverflow.com/questions/48651269/what-are-the-pause-containers), который не потребляет практически никаких ресурсов, с помощью данного контейнера мы создаём сеть для нашего пода
16 | - Контейнеры в рамках одного pod могут ходить к друг другу в рамках localhost
17 | - Контейнер в рамках pod существет в рамках одной ноды kubernetes
18 | - Несколько контейнеров в одном pod - антипаттерн, но могут быть нужны:
19 | - если нужно держать их на одной ноде (физической или виртуальной машине)
20 | - если масштабируются линейно, например 1 к 1
21 | - если компоненты имеют сильную связь, например:
22 | - prometheus и config reloader для него
23 | - Инструменты мониторинга, такие как экспортеры prometheus, экспортеры и приложения в одном podе
24 | - Авторизующие сервисы (прокси) для приложений
25 | - Сервис меши - в рамках всех podов добавляется сервис-меш прокся
26 |
27 | # Из ответов на вопросы:
28 | - По умолчанию волюмы для контейнеров внутри пода подключаются свои, но при желании можно общие, это отдельная история
29 | - Если перефразировать - пространство ОС между контейнерами не шарится
30 |
31 |
32 | - Q: Внутри kubernetes поды образуют свою сеть? То есть для похода из одного пода в другой нужно ходить по сети kubernetes, а не просто по локалхосту?
33 | - A: Сходить из одного контейнера в другой можно, но уже поверх сети, которую предоставляет кластер kubernetes, не через локальную петлю
34 | - [virtlet](https://github.com/Mirantis/virtlet) - проект, основанный на kubernetes, позволяет работать не с контейнерами а с qcow виртуальными машинами
35 |
36 | # Практика:
37 | - факультатив - поразбираться с yaml (например [1](https://yaml.org/), [2](https://habr.com/ru/company/rambler_and_co/blog/525498/)), прочитать документацию (4 абзаца), дойти до анхоров (anchors?) понять что они не нужны и на этом остановиться. Важно научиться на глаз отличать словарь от списка
38 | - "---" в yaml - обозначение начала документа (абстракции) в рамках файла, каждый такой набор означает начало нового документа
39 | - "..." - конец документа, как я понял, не обязателен и служит для лучшего понимания структуры документов людьми, особенно, при передаче в чатах и т.п.
40 |
41 | Центральный компонент kubernetes - API сервер, всё происходит через него, общение по протоколу REST (подсказывают, что это не протокол, в данном случае протокол это HTTP), в примере ниже идёт указание версии API. Это важно, т.к. обычно изменения внедряются в других версиях API и работа с сущностями в разных версиях API может отличаться
42 |
43 | Склонировали содержимое https://github.com/Slurmio/school-dev-k8s, работаем в practice/2.application-abstractions/1.pod, его содержимое с комментариями:
44 | ```yaml
45 | ---
46 | # file: practice/2.application-abstractions/1.pod/pod.yaml
47 | apiVersion: v1 # версия апи
48 | kind: pod # тип описываемого объекта
49 | metadata: # раздел с дополнительной информацией
50 | name: my-pod # указываем имя
51 | spec: # описываем детали че будет внутри
52 | containers:
53 | - image: quay.io/testing-farm/nginx:1.12
54 | name: nginx
55 | ports: # в данном случае это не директива, а документирование
56 | - containerPort: 80
57 | ...
58 | ```
59 |
60 | создаём pod из такого файла:
61 | ```shell
62 | kubectl create -f pod.yaml
63 | ```
64 |
65 | Команда
66 | ```shell
67 | kubectl get pod
68 | ```
69 | Выдаст следующий результат:
70 | ```shell output
71 | NAME READY STATUS RESTARTS AGE
72 | my-pod 1/1 Running 0 108s
73 | ```
74 |
75 | - READY - это количество реплик, в реалиях kubernetes не запускают поды вручную штучно, далее в вебинарах будут абстракции выше [deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/), [replicaset](https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/)
76 | - RESTARTS - kubernetes может автоматически перезапускать pod в случае проблем, данный счётчик позволяет это отслеживать
77 |
78 | - kubectl поддерживает сокращения, например pod => po
79 | - для удобства можно сделать алиас для kubectl, пример для linux:
80 | ```shell
81 | alias k="kubectl"
82 | ```
83 | - В рамках одного пространства имен в kubernetes не может существовать 2 объекта одного типа с одним именем
84 | - С точки зрения разраба не стоит думать о физических сущностях, как и где что-то работает (с точки зрения эксплуатации - стоит)
85 |
86 | команда
87 | ```shell
88 | kubectl describe
89 | ```
90 | позволяет посмотреть описание объекта, что о нейм знает kubernetes, пример:
91 | ```shell
92 | kubectl describe pod my-pod
93 | ```
94 |
95 | Удаляем созданные в ходе практики поды (в учебном кластере крайне желательно убирать за собой, убивать поды и т.п., если оно не используется):
96 | ```shell
97 | kubectl delete pod my-pod # с указанием типа и имени
98 | kubectl delete -f pod.yaml # через файл, с помощью которого производили создание объекта
99 | kubectl delete pod --all # подсказывают в чате, проверил работает
100 | ```
101 |
102 | # Вопросы:
103 | - |
104 | - Q: Что будет если указать одинаковые порты для 2х контейнеров в поде
105 | - A: Пробуйте и расскажите мне
106 | - В чате говорят - если 2 порта одинаковые - один из контейнеров не стартует
107 | - |
108 | - Q: Разница между kubectl create и kubectl apply?
109 | - A:
110 | - Ответ можно найти в [интернете](https://stackoverflow.com/questions/47369351/kubectl-apply-vs-kubectl-create), а также в [третьей](lesson%203.md#Разница-между-create-и-apply) и [шестой](lesson%206.md#Разбираем-сервис-ClusterIP) лекциях
111 | - Ответы из чата школы:
112 | - create - создаёт (вызов второй раз - под, т.к. объект уже существует), apply - применяет изменения (если пода нет - создаст)
113 | - create создаёт и при повтороном запуске с одним и тем же файлом выдаст ошибку, даже если вы что-то изменили в описании ресурса. apply применит изменения к ресурсу, если его нет, то создаст
114 | - |
115 | - Q: Как узнать что наши указания выполнены, контейнер поднялся и работает нужным образом и т.п.
116 | - A: Всё как в докере, можно войти в контейнер (kubectl exec и т.п.)
117 | - |
118 | - Q: Как локально сделать то же самое что было на практике?
119 | - A: В локальном инстансе всё будет работать так же
120 | - |
121 | - Q: Можно ли настроить приложения на работу с одними и теми же портами?
122 | - A: В рамках pod одно общее сетевое пространство и интерфейсы, то есть нельзя открыть 2 одинаковых порта или задействовать 2 IP адреса
123 | - |
124 | - Q: Как взамимодействовать с контейнерами?
125 | - A: Дебаг, логи - в след раз, но в целом всё как в докере - exec, logs
126 | - `kubectl get logs` - берет логи из stdout и sderr контейнера
127 | - `kubectl describe pod pod-name`- расширенная инфа
128 | - |
129 | - Q: openshift vs kubernetes
130 | - A: если кратко и цензурно - проприетарщина vs OSS, openshift, rancher построены на базе kubernetes (мысль была шире)
131 | - |
132 | - Q: Как настроить терминал как у ведущего?
133 | - A: В [github Павла Селиванова](https://github.com/pauljamm/dotfiles) можно это найти
134 | - |
135 | - Q: Если мы запускаем несколько контейнеров в поде, будет ли запускаться несколько POD_контейнеров_1oaj2890j?
136 | - A: Он всегда 1
137 | - |
138 | - Q: Отличия pod от ноды
139 | - A:
140 | - Pod - минимальная ШТУКА (читай абстракция) в которой может быть один или несколько контейнеров в зависимости от того, как ВАМ это потребуется в текущей задаче, запущенная на ЕДИНСТВЕННОЙ ноде в текущем моменте времени
141 | - Node (нода) - узел, на котором запускается pod. Виртуалка, железный сервер, etc. У нод бывают разные роли. Об этом будет дальше скорее всего
142 | - |
143 | - Q: как инициализация настроек произошла на тачке? Как он знает куда ходить?
144 | - A: Через конфиг который вам пришёл в личном кабинете. Рекомендую посмотреть курс бесплатный в Слёрме MKS. Будет понятнее
145 | - |
146 | - Q: А кроме портов можно еще какие-то метаданные указать в YAML для pod?
147 | - A: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#podspec-v1-core
148 | - |
149 | - Q: Не понял сейчас, сначала говорил, что ports в описании это только для документации, а теперь нельзя создать два контейнера с одинаковым портом в этом поле
150 | - A: Все правильно, в документации порты - описательные, но указанные в yaml образы используют реально для старта один 80 порт, соответственно, если нужно вдруг теоретически два nginx нужно сделать образ с другим портом
151 | # Всякое из чата:
152 | - [Kubernetes — изучаем паттерн Sidecar](https://habr.com/ru/company/nixys/blog/559368/)
153 | - [Иллюстрированное руководство по Kubernetes для детей, The Illustrated Children's Guide to Kubernetes](https://www.youtube.com/watch?v=4ht22ReBjno)
154 | - [Обзор Lens — IDE для Kubernetes](https://habr.com/ru/company/flant/blog/563422/)
155 | - Если кому-то интересны альтернативные решения для подсветки синтаксиса в vim, обратите внимание на [этот тул](https://github.com/NvChad/NvChad). Он гибкий, супербыстрый и совмещает в себе почти все функции современных IDE, при этом являясь тонким клиентом. [Скрин моего экрана NvChad](https://pasteboard.co/CVy5ESak2MQD.png). И [пример](https://github.com/saequus/mac-conf/blob/master/.vimrc) моих сеттингов для vim
156 | - [github/indrabasak/Books/Kubernetes in Action.pdf](https://github.com/indrabasak/Books/blob/master/Kubernetes%20in%20Action.pdf)
157 |
--------------------------------------------------------------------------------
/lesson 3.md:
--------------------------------------------------------------------------------
1 | ---
2 | date created: 2021-10-07 19:00:18 (+03:00), Thursday
3 | ---
4 | # Урок 3: Абстракции приложения. Вечерняя школа «Kubernetes для разработчиков» [youtube](https://www.youtube.com/watch?v=LLVfC08UVqY)
5 |
6 | - Разбираемся с replicaset и deployment
7 | - Бонусом Resources: как правильно работать с ресурсами кластера
8 |
9 | # Ответы на вопросы из чата:
10 | - 1
11 | - Q: Кто такой Developer Advocate, кого от кого он защищает, чем занимается
12 | - A: Это публичное лицо, которое представляет комьюнити для компании и наоборот. Посредник между продуктами компании и пользователями, комьюнити. Защищает разработчиков, инженеров и всех прочих людей в IT от незнания, это такое связующее звено между комьюнити какой-то технологии и компанией, группой-разработчиков, которые эту технологию предоставляют. В данном случае, Павел выступает посредником между тем, что он с коллегами делает внутри своей компании (MSC), теми продуктами, которые выходят на рынок и комьюнити пользователей этих продуктов. Одна из его задач - доносить способы пользоваться продуктами так, чтобы было хороши и пользователям и компании-провайдеру, чтобы ни у кого по ночам ничего не горело. С точки зрения комьюнити задача Developer Advocate - доносить то что думает комьюнити относительно продукта до product owner'ов, директоров, конечных разработчиков и т.д., всех людей в команде продукта.
13 | - 2
14 | - Q: По поводу сказанного о портах, что директивы в YAML файле являются фактически документацией
15 | - A: Аналогии на стримах могут упрощать реальную картину мира. В примере с портами было сказано, что если указывть в yaml файле пода порты - то это не более чем документирование и ни на что более не влияет. На самом деле кое на что влияет, если зайти на под и выполнить там команду env, можно увидеть большое количество прокинутых переменных окружения, если описывать в поде этот порт, то kubernetes будет генерить и добавлять для других подов соответствующую переменную окружения. Это сделано для сервис дискавери, но в реальности этим практически никто не пользуется. Основная мысль - могут быть опущены детали, которые сильно усложнят понимание для новичков за счёт потока ненужной на первоначальном этапе информации. При этом, ведущие стараются не врать а говорить исходя из сложившихся практик, например, в данном случаче, бОльшей правдой является, что описание порта нужно для документирования, а не то, что приложение будет работать с ним. Это один из примеров, такие упрощения еще будут встречаться
16 |
17 | По поводу споров насчёт того, можно ли под рассматривать как процесс, скорее контейнер можно рассматривать как процесс, а под - как неделимый инстанс приложения, а кубер это ОС, которая всем этим рулит, да это тоже упрощение, метафора
18 |
19 | В прошлый раз мы остановлись на том, что запустили под с приложением, узнали что если нам нужно 2 экземпляра приложения, то нам придется отредактировать файл с описанием пода, переименовать под, т.к. в одном пространстве имён не может быть 2х одинаковых сущностей одного типа, и непосредственно создать его из этого файла, пришли к выводу, что это совсем не удобно, а в kubernetes есть более удобные способы масштабировать приложения, чем поды.
20 |
21 | # ReplicaSet 
22 | - Это объект, который представляет из себя набор реплик приложения
23 | - В общем и целом внутри yaml описания ReplicaSet содержится темплейт описания подов, которые будут создаваться
24 | - При создании подов будут проставляться специальные метки, ярлыки (label) на поды, по которым ReplicaSet будет определять, что это именно его поды
25 | - У самого ReplicaSet есть информация о селекторе, т.е. он знает, его поды это те, которые удовлетворяют этому селектору
26 | - Содержит поле, указывающее, сколько мы хотим создать подов из нашего темплейта
27 |
28 | # Практика
29 | - git must have, если есть пробелы - можно пройти [бесплатный курс от слёрма](https://slurm.io/git)
30 | - работаем с каталогом ~/school-dev-k8s/practice/2.application-abstractions/2.replicaset
31 | - replicaset.yaml выглядит так:
32 |
33 | ```yaml
34 | ---
35 | # file: practice/2.application-abstractions/2.replicaset/replicaset.yaml
36 | apiVersion: apps/v1 # Which version of the Kubernetes API you're using to create this object
37 | kind: ReplicaSet # What kind of object you want to create
38 | metadata:
39 | name: my-replicaset
40 | spec:
41 | replicas: 2 # how many replicas
42 | selector: # allows to select pods with a matched label ('my-app' here)
43 | matchLabels:
44 | app: my-app
45 | template: # template of pod we want to create, you don't need a name, k8s will generate it
46 | metadata:
47 | labels: # you can use labels anywhere
48 | app: my-app
49 | spec: # to describe k8s what you want to build
50 | containers:
51 | - image: quay.io/testing-farm/nginx:1.12
52 | name: nginx # name is have to be unique
53 | ports:
54 | - containerPort: 80
55 | ...
56 | ```
57 | - у ReplicaSet, так же как и у пода, так же как и у всего в kubernetes структура описания это yaml
58 | - как и у всех объектов в kubernetes у него есть тип - kind и версия api (поднималость в первом уроке)
59 | - обращаем внимание, что у пода версия апи была v1, у ReplicaSet стабильная версия это apps/v1
60 | - тип, как ни странно, ReplicaSet
61 | - информацию о типах и всякое такое можно узнать в документации, на сайте https://kubernetes.io/
62 | - далее будет лайфак, где брать нужную инфу по kubernetes прямо из консоли
63 | - также как у пода, у ReplicaSet есть metadata, там мы указываем имя
64 | - аналогично, spec - специализация нашего объекта
65 | - отличительные относительно пода параметры ReplicaSet:
66 | - replicas - количество реплик нашего приложения, подов с одним и тем же описанием, мы хотим запустить
67 | - селектор (selector), в данном описании видно, что мы выбираем все поды с меткой `app: my-app`
68 | - темплейт (template)
69 | - содержит описание пода, при этом имя пода не указывается, т.к. оно будет генерироваться автоматически
70 | - в метаданных содержит label, соответствующий селектору
71 | - spec
72 | - такой же как в описании пода
73 | - имена контейнеров нужно указывать, в отличие от имён подов в темплейтах, причём имена в рамках пода должны быть уникальны
74 | - описание портов - для документации (но не совсем, как было сказано выше)
75 |
76 | # По темплейтам
77 | - pod это абстракция, которая хоть и является минимальной единицей, с которой умеет работать kubernetes, но в реальности мы почти никогда не создаём поды напрямую, потому что это неудобно. Можно считать pod полуслужебной абстракцией kubernetes.
78 | - Т.к. мы не хотим работать с каждым подом отдельно, мы хотим описать темплейт пода и дальшей сказать kubernetes с помощью ресурса ReplicaSet "создай N подов"
79 |
80 | # По лейблам
81 | - Они могут быть присвоены вообще любому объекту, например, мы можем присвоить метку и самому ReplicaSet
82 | - Почему лейблы это хорошо
83 | - kubernetes сам их использует (через service discovery), например, чтобы понимать, какие поды привязаны к каким ReplicaSet
84 | - по ним можно понимать, что за приложение, мы можем навешать кастомных человекочитаемых меток и по ним ориентироваться
85 | - инструменты для работы с kubernetes, тот же kubectl get, позволяют фильтровать, например, kubectl get pod -l "app=myapp" выдаст поды с соответствующей меткой
86 | - Рекомендуется везде проставлять и использовать лейблы, особенно в рамках курса, далее будут достаточно сложные, приближенные к реальности, конфигурации, там везде будут лейблы
87 |
88 | # Возвращаемся к консоли
89 | чистим за собой с прошлого раза:
90 | ```shell
91 | kubectl delete pod --all # удаляем все поды
92 | kubectl delete replicaset --all # удаляем все репликасеты
93 | ```
94 | \лайфхак для разработчиков с рут доступом к проду, которые хотят порадовать своих коллег из эксплуатации:
95 | ```shell
96 | kubectl delete all --all -A # пройтись по всем неймспейсам и удалить все объекты со всеми именами, аналог rm -rf
97 | ```
98 | \
99 | # Разница между create и apply
100 | - create - команда, которая выполняется только 1 раз, второй раз выдаст ошибку
101 | - apply
102 | - применяет изменения, идемпотентна, т.е. создат то чего нет, если всё уже есть и соответствует описанию - ничего не сделает, если есть расхождения в описании и реальности, исправит их
103 | - можно указывать для аргумента -f не только файл но и директорию
104 | - обычно используется в пайплайнах CI/CD
105 |
106 |
107 | создаём (аплаим) репликасет:
108 | ```shell
109 | kubectl apply -f replicaset.yaml
110 | ```
111 | запросим информацию по ReplicaSet, используем сокращение rs:
112 | ```shell
113 | kubectl get rs
114 | ```
115 | получим:
116 | ```shell
117 | NAME DESIRED CURRENT READY AGE
118 | my-replicaset 2 2 2 2m25s
119 | ```
120 | - DESIRED - сколько мы указывали создавать реплик
121 | - CURRENT - сколько по факту есть
122 | - READY - цифра 2 показывает что 2 пода сейчас работают и доступны. Что конкретно это значит, будет разбираться позже, т.к. затрагивает сетевые абстракции
123 |
124 | Проверяем поды:
125 | ```shell
126 | kubectl get po
127 | ```
128 | выдаст нам что-то вроде:
129 | ```shell
130 | NAME READY STATUS RESTARTS AGE
131 | my-replicaset-g8lc4 0/1 ContainerCreating 0 88s
132 | my-replicaset-n7bxt 0/1 ContainerCreating 0 88s
133 | ```
134 | - замечаем, что поды называются по имени ReplicaSet + некий кусок хэша, это сделано во избежание попыток создания одноимённых однотипных сущностей
135 | если мы посмотрим на детальную информацию по одному из подов:
136 | ```shell
137 | k describe pod my-replicaset-g8lc4 # если не указать имя пода, то получим информацию о всех подах
138 | ```
139 | то сможем увидеть строку `Labels: app=my-app`
140 | можем запросить информацию о подах с указанием этой метки:
141 | ```shell
142 | kubectl get po -l "app=my-app"
143 | ```
144 | вывод будет аналогичен тому что был выше, т.к. других подов пока нет
145 | # Про self-healing
146 | - Репликасет призван поддерживать нужное количество реплик, независимо от внешних обстоятелств, пока явно не указано другое, например, если уборщица выгнав вас из-за стола случайно введёт команду удаления пода или одна из нод кластера выйдет из строя, на свободных ресурсах будет выполнена попытка запустить недостающий под в репликасете.
147 |
148 |
149 | Например, удаляем под и с небольшой задержкой снова запрашиваем информацию:
150 | ```shell
151 | kubectl delete pod my-replicaset-g8lc4
152 | kubectl get po -l "app=my-app"
153 | ```
154 | получаем
155 | ```shell
156 | NAME READY STATUS RESTARTS AGE
157 | my-replicaset-hnwjp 1/1 Running 0 65s # это уже новый под
158 | my-replicaset-n7bxt 1/1 Running 0 16m
159 | ```
160 |
161 | # Про скейлинг (масштабирование):
162 | - 1 способ - открыть наш yaml файл и исправить там количество реплик и выполнить аплай изменений
163 | - 2 способ - kubectl scale. Данным способом можно как добавлять так и убирать целевое количество реплик
164 | - При скейлинге вниз одним из ключевых критериев является возраст пода, первыми будут удаляться самые молодые поды, т.е. созданные позже
165 | Пример со 2м способом:
166 | ```shell
167 | kubectl scale --replicas 3 rs my-replicaset
168 | kubectl get po -l "app=my-app"
169 | ```
170 | получим такую картину:
171 | ```shell
172 | NAME READY STATUS RESTARTS AGE
173 | my-replicaset-hnwjp 1/1 Running 0 12m
174 | my-replicaset-n6v8r 0/1 ContainerCreating 0 13s
175 | my-replicaset-n7bxt 1/1 Running 0 28m
176 | ```
177 | В кластере есть ограничения, 4 пода на неймспейс (пользователя?)
178 | # Про автокомплит
179 | - Павел рекомендует настроить автокомплит для kubectl и тем самым облегчить себе жизнь
180 | - Для zshell есть отдельный плагин kubectl
181 | - Для чистого bash есть kubectl completion, можно вывести справку командой kubectl completion -h, там всё описано
182 |
183 | # Экспериментируем с подом
184 | ## Пример 1
185 | - проверяем теорию о том, что кластер автоматически прибъет под, созданный руками, но с такой же меткой, как в ReplicaSet
186 | - берём конфиг из предыдущего урока и добавляем туда :
187 | ```yaml
188 | ---
189 | # file: practice/2.application-abstractions/1.pod/pod.yaml
190 | apiVersion: v1
191 | kind: pod
192 | metadata:
193 | name: my-pod
194 | labels:
195 | app: my-app # добавили метку как в ReplicaSet
196 | spec:
197 | containers:
198 | - image: quay.io/testing-farm/nginx:1.12
199 | name: nginx
200 | ports:
201 | - containerPort: 80
202 | ...
203 | ```
204 | применяем, проверяем:
205 | ```shell
206 | k apply -f pod.yaml
207 | k get po
208 | ```
209 | видим:
210 | ```shell
211 | NAME READY STATUS RESTARTS AGE
212 | my-pod 0/1 Terminating 0 4s # видим, что свежесозданный под удаляется
213 | my-replicaset-hnwjp 1/1 Running 0 55m
214 | my-replicaset-n7bxt 1/1 Running 0 70m
215 | ```
216 | - Теория работает, ReplicaSet, который отслеживает какие поды его и сколько их, по метке my-app, увидел что в этом неймспейсе появился ещё один под с таким же лейблом, он посчитал его лишним и прибил, это нормальное явление.
217 | - Обычно в реальности именно так не делают, но может произойти так, что подов в репликасете будет больше чем ожидалось, например, при выходе из строя ноды кластер через какое-то время перестанет считать приложения на этой ноде работающими, даже если они фактически работают, например, если нода упала не полностью, а отказали только некоторые компоненты. Кластер для себя пометит такие поды как недоступные и создаст новые, на доступных нодах, согласно описанию ReplicaSet. По возвращении ноды в строй может так сложиться, что мы получим больше подов чем указано в описании ReplicaSet, тогда подам на вернувшейся ноде будет отправлен сигнал на уничтожение
218 |
219 | ## Пример 2
220 | - Обновление приложения
221 | - Допустим, мы собрали новый докер образ и хотим заменить им старый
222 | - Мы можем увидеть в описании теплейта для ReplicaSet, что в названии образа (пути к образу?) содержится тег с версией, в нашем случае 1.12, допустим, мы хотим обновиться до 1.13
223 | - Самый очевидный сопосов - поправить файл с описанием и выполнить apply, делать так мы конечно не будем
224 | - Другой способ - использовать команду ниже:
225 | ```shell
226 | kubectl set image replicaset my-replicaset nginx=quay.io/testing-farm/nginx:1.13
227 | ```
228 | - В шаблоне пода может быть несколько контейнеров, поэтому мы должны указать, для какого именно мы меняем образ
229 | - Не забываем, что учебном кластере не стоит работать с докерхабом, т.к. это исчерпает лимиты на подключения, поэтому используем другие репозитории, в данном случае quay.io
230 | - Т.к. в данном случае у нас всего один контейнер, мы можем сократить команду, это часто используется в простых пайплайнах:
231 | ```shell
232 | kubectl set image replicaset my-replicaset '*=quay.io/testing-farm/nginx:1.13'
233 | ```
234 | - Если после этого выполнить kubectl describe rs, увидим, что указана новая версия образа
235 | - Если мы выполним kubectl describe po, увидим что контейнеры живы уже давно и версия образа указана старая. Так происходит, потому что командами выше мы меняем темплейт, а задача ReplicaSet - отслеживать количество подов по метке. Для реального обновления версии в поде мы можем начать прибивать существующие поды, тогда новые будут созданы с версией образа из шаблона
236 | - Вывод - ReplicaSet не решает задачу обновления приложения но...
237 |
238 | # Deployment 
239 | - Это более высокоуровневая абстракция над ReplicaSet
240 | - Позволяет решать проблему обновления приложения
241 | - Когда мы создаём Deployment, он создаёт под собой ReplicaSet, а он создаёт под собой поды в нужном количестве
242 | - Если мы меняем версию образа в Deployment, он создаёт новый ReplicaSet и с помощью Rolling Update или стратегии Recreate передеплоивается и у нас появляется приложение новой версии в нашем кластере kubernetes
243 |
244 | ## Очень сложно, ничего непонятно, идём в консоль
245 | чистим за собой:
246 | ```shell
247 | kubectl delete replicaset --all # удаляем все репликасеты
248 | ```
249 | - При удалении верхнеуровневой абстракции все относящиеся к ней объекты абстракций более низкого уровня удаляются автоматически
250 |
251 | меняем директорию:
252 | ```shell
253 | cd ../3.deployment/
254 | ```
255 |
256 | ## Q&A
257 | - |
258 | - Q: Какое кресло у спикера?
259 | - A: Expert Star GR https://www.falto.ru/catalogue/expert-star.php
260 | - |
261 | - Q: Можно ли указывать количество реплик при скейлинге через арифметические вычисления, наприер `kubectl scale --replicas +3 rs my-replicaset`
262 | - A: kubectl такое не умеет, средствами bash и т.п. традиционно можно (сначала получаем через get, обрабатываем и формируем нужную команду)
263 | - |
264 | - Q: Что будет при скейлинге вниз с работой, выполняемой подом, завершится, прервётся, будет выполнять другой под
265 | - A: Ответить на вопрос нам поможет kubectl explain
266 | - например, kubectl explain pod выдаст информацию о всех верхнеуровневых полях для pod
267 | - отвечая на вопрос
268 | - нужно заглянуть в spec пода, выполняем kubectl explain pod.spec
269 | - находим описание параметра terminationGracePeriodSeconds и читаем
270 | - это поле можно указать в подах, шаблонах ReplicaSet или Deployment, оно говорит о следующем:
271 | - когда завершается приложение в контейнере, приложению отправляется sigterm, т.е. мы просим его корректно (gracefully) завершиться.
272 | - terminationGracePeriodSeconds указывает, сколько секунд даётся на эти операции, по умолчанию 30 секунд
273 | - Если приложение так умеет, то оно принимает sigterm, обрабатывает оставшиеся запросы или другую работу и после этого самостоятельно корректно завершается
274 | - Если приложение не умеет обрабатывать sigterm, то оно завершается сразу же, все обрабатываемые им запросы прерываются, есть ряд техник для обработки таких ситуаций (pre-stop хуки, возможно об этом будет позже)
275 | - Если приложение на sigterm не реагирует, то через заданное в terminationGracePeriodSeconds время ему будет отправлена команда sigkill, т.е. оно будет мгновенно завершено
276 | - |
277 | - Q: Шелл у Павла (в чате есть ответы)
278 | - A: https://github.com/pauljamm/dotfiles
279 |
280 | ## Возвращаемся к консоли и работаем с Deployment
281 | - заглянем в yaml файл в нашей директории, видим следующее:
282 | ```yaml
283 | ---
284 | # file: practice/2.application-abstractions/3.deployment/deployment.yaml
285 | apiVersion: apps/v1 # видим что версия API такая же как у ReplicaSet
286 | kind: Deployment # Соответствующий нашей теме тип
287 | metadata:
288 | name: my-deployment
289 | spec: # описание идентичное описанию предыдущего ReplicaSet
290 | replicas: 2
291 | selector:
292 | matchLabels:
293 | app: my-app
294 | template:
295 | metadata:
296 | labels:
297 | app: my-app
298 | spec:
299 | containers:
300 | - image: quay.io/testing-farm/nginx:1.12
301 | name: nginx
302 | ports:
303 | - containerPort: 80
304 | ...
305 | ```
306 | - почти то же что и ReplicaSet, но есть отличия в поведении
307 |
308 | Применяем наше описание и смотрим что получилось:
309 | ```shell
310 | kubectl apply -f deployment.yaml
311 | k get deploy
312 | ```
313 | видим:
314 | ```shell
315 | NAME READY UP-TO-DATE AVAILABLE AGE
316 | my-deployment 2/2 2 2 81s
317 | ```
318 | смотрим поды:
319 | ```shell
320 | k get po
321 | ```
322 | видим:
323 | ```shell
324 | NAME READY STATUS RESTARTS AGE
325 | my-deployment-79788cc48d-bnqr7 1/1 Running 0 2m32s
326 | my-deployment-79788cc48d-qfnkp 1/1 Running 0 2m31s
327 | ```
328 | Видим особенности отличий именования подов ReplicaSet от подов Deployment
329 |
330 | если посмотрим теперь на ReplicaSetы, то увидим
331 | ```shell
332 | NAME DESIRED CURRENT READY AGE
333 | my-deployment-79788cc48d 2 2 2 5m13s
334 | ```
335 | - Итого, Deployment создал нам ReplicaSet, сгенерировал ему имя на базе имени деплоймента и создал 2 пода в этом ReplicaSet, везде добавляя кусочки хэшей
336 | - Хэши считаются от содержимого того, что мы применяем, возможно зависит ещё от кластера, тут Павел не уверен, можно поискать информацию отдельно
337 | - Совет из практики - не нужно помогать kubernetes вручную, например, у нас есть Deployment, не нужно пытаться скейлить ReplicaSet или добавлять/удалять поды напрямую, с бОльшей вероятностью это навредит а не поможет, лучше искать корневую причину проблем, которые заставляют вас делать такое, а kubernetes сам справится со своими задачами
338 |
339 | ## Обновляем приложение в рамках Deployment
340 | - Так же как в ReplicaSet, обновляем образ в Deployment одним из способов:
341 | - через правку yaml и apply
342 | - командой `kubectl set image deployment my-deployment '*=quay.io/testing-farm/nginx:1.13'`
343 | - новый способ - редактирование ресурса на лету с помощью kubectl edit, например:
344 |
345 | ```shell
346 | kubectl edit deployment my-deployment
347 | ```
348 | - данная команда вызовет редактор по умолчанию (в unix-like системах задаваемый с помощью переменной окружения EDITOR) и позволит редактировать именно объект в кластере, т.е. мы просим кластер прислать нам полное описание указанного деплоймента, который вот прям щас в кластере запущен, далее, кластер нам его прислал, kubectl открыл нам его в редакторе и когда мы внесём и сохраним изменения, эти изменения попадут не в какой-то локальный файл, а сразу отправится обратно в кластер kubernetes и там применится
349 | - это удобный способ быстро экспериментировать, дебажить, выстрелить себе в ногу, и т.п., но совсем не хороший способ работать с production кластером, особенно, если вы работаете с ним не один, а с коллегами, т.к. данные изменения трудно отследить
350 | - по хорошему, нужно работать через yaml файлы описаний, git, apply через CI/CD, желательно еще тестами обвязать и вот это вот всё
351 |
352 | - при работе через edit мы видим множество полей, которые мы сами не создавали, это все доступные поля для данного объекта и в них указаны значения по умолчанию для данного кластера
353 | - после изменения версии образа nginx опросив поды мы видим похожую картину:
354 | ```shell
355 | NAME READY STATUS RESTARTS AGE
356 | my-deployment-79788cc48d-bnqr7 1/1 Running 0 29m
357 | my-deployment-79788cc48d-qfnkp 0/1 Terminating 0 29m
358 | my-deployment-d7fcf87f9-vztvm 1/1 Running 0 19s
359 | my-deployment-d7fcf87f9-zk4tq 0/1 ContainerCreating 0 3s
360 | ```
361 | - т.е. старые поды удаляются, а новые создаются, по идентификаторам можно догадаться, что у нас поменялся ReplicaSet
362 | - если запросим информацию по ReplicaSet, то увидим:
363 | ```shell
364 | NAME DESIRED CURRENT READY AGE
365 | my-deployment-79788cc48d 0 0 0 33m # старый ReplicaSet
366 | my-deployment-d7fcf87f9 2 2 2 3m53s
367 | ```
368 | - Далее Павел вручную делает и показывает, что обычно происходит "под капотом", при изменениях в Deployment:
369 | - Создаёт первый ReplicaSet, где указана старая версия образа, с ожидаемым количеством подов равным 2
370 | - Проверяет, что у нас есть 2 пода, принадлежащих первому ReplicaSet
371 | - Создаёт второй ReplicaSet с новой версией образа, с ожидаемым количеством подов равным 0
372 | - Проверяет, что у нас есть 2 ReplicaSet, и первому принадлежит 2 пода, а второму 0
373 | - Далее выполняет постепенный скейлинг обоих реплик, в нашем примере с шагом 1, т.е. у нас будет 2/0, затем 1/1, затем 0/2 подов со старой и новой версиями образа соответственно
374 | - Проверяет, что в итоге у нас всё как ожидалось - есть 2 ReplicaSet и уже второму принадлежит 2 пода с новой версией приложения, а первый ReplicaSet остался без подов
375 |
376 | ## Зачем kubernetes делает обновление с использованием ReplicaSet'ов и почему старый ReplicaSet не удаляется?
377 | - Для демонстрации снова создаём Deployment из yaml файла, затем правим в Deployment (не в файле) версию образа на более новую, проверяем что произошло обновление и мы имеем 2 ReplicaSet, старый и текущий, с обновленной версией приложения
378 | - Собственно, старый ReplicaSet нужен, чтобы иметь возможность выполнить откат, это можно сделать командой:
379 | ```shell
380 | kubectl rollout undo deployment my-deployment
381 | ```
382 | - Таким образом мы производим скейлинг репликасетов в обратном направлении
383 | - Когда мы работали с Deployment через edit, мы там могли увидеть такой параметр как revisionHistoryLimit, также мы можем почитать о нем с помощью команды `kubectl explain deployment.spec.revisionHistoryLimit`
384 | - Данный параметр по умолчанию равен 10, он отвечает за глубину хранения ReplicaSet'ов для наших Deployment'ов, то есть, количество версий, на которые мы сможем откатиться
385 | - откатить откат можно той же командой, таким образом мы переключимся снова на новую версию приложения
386 | - убедиться, что каждый создаётся новая ревизия, можно командой `kubectl rollout history deployment my-deployment`, если мы проделаем откат несколько раз, то увидим увеличивающиеся значения в выводе
387 |
388 | ## Как kubernetes решает, каким именно образом скейлить ReplicaSet'ы?
389 | - Если мы заглянем в `kubectl explain deployment.spec`, то сможем там увидеть параметр **strategy**
390 | - Смотрим подробнее - `kubectl explain deployment.spec.strategy` и видим, что существует поле type, которое может принимать 2 значения - "Recreate" или "RollingUpdate", по умолчанию используется RollingUpdate
391 | - RollingUpdate мы уже видели в действии, это тот случай, когда реплики обновляются постепенно, без даунтайма. Это применимо для приложений, которые поддерживают обратную совместимость, что позволит существовать одновременно двум версиям приложений
392 | - Для данной стратегии можно настроить еще 2 поля, оба принимают значения в процентах или целых числах:
393 | - maxSurge (от слова всплеск) - говорит о том, на сколько штук или процентов мы можем поднять количество подов при обновлении, относительно их желаемого количества, т.е. значения spec.replicas в нашем yaml описании
394 | - Например, у нас указано replicas: 2 и maxSurge: 1, таким образом в момент обновления у нас может быть 3 пода
395 | - maxUnavailable - говорит о том, наскоько можно опустить количество реплик нашего приложения относительно желаемого значения
396 | - Например, у нас указано replicas: 2 и maxUnavailable: 1, таким образом в момент начала обновления 1 реплику сразу можно удалить
397 | - В случае, когда у нас оба значения равны 1 и replicas = 2, в момент начала обновления происходит примерно следующее:
398 | - уничтожается 1 старая реплика, в этот же момент создаются 2 новых реплики
399 | - kubernetes ждет, пока 2 новых реплики досоздаются, станут готовы (в дальнейших лекциях об этом будет подробнее)
400 | - уничтожается оставшаяся старая реплика
401 | - Так не принято, но в некоторых случаях приходится поддерживать приложения, которые существуют в 1 экземпляре, т.е. replicas = 1
402 | - Для таких приложений параметры RollingUpdate по умолчанию не подойдут, т.к. мы столкнемся с временной недоступностью приложения.
403 | - Чтобы избежать такой проблемы, необходимо установить maxSurge в 1, а maxUnavailable в 0, тогда сначала будет добавлена новая реплика и после её готовности будет удалена старая
404 | - Когда мы работаем с большим или динамическим количеством реплик, предпочтительней задавать данные параметры в виде процентов, по умолчанию оба параметра установлены в 25%
405 | - Recreate - стратегия, при которой сначала удаляются все старые поды, затем создаются новые. Основной минус данной страегии - она неизбежно приводит к дайнтайму, т.к. в какой-то момент старых подов уже не будет, а новых еще не будет
406 |
407 | # Namespace
408 | - Оно же пространство имён, это способ логически разделить кластер kubernetes на части
409 | - Мы можем разделить наш кластер на отдельные пространства имён
410 | - Помимо просто логического разделения, на неймспейсах еще много чего постороено в kubernetes, в частности, с помощью специальных политик можно настроить, сможем ли мы или нет, взаимодействовать с подами из соседних неймспейсов
411 | - Бывают политики доступа, сетевые политики, с помощью сетевых политик, например, можно настроить, сможем ли мы сходить из одного неймспейса по сети в другой, на какой порт какого приложения и т.д.
412 | - В рамках одного Namespace нельзя создавать объекты одного типа с одинаковыми именами, в рамках разных Namespace - можно
413 | - Что делают с помощью Namespace'ов
414 | - Организовывают совместное использование кластера, например:
415 | - Наш учебный кластер - каждый пользователь работает в своём неймспейсе
416 | - Можно разделять кластер по командам разработки, это активно применяется в [MCS](https://mcs.mail.ru/)
417 | - Можно делить кластер по неймспейсу на приложение
418 | - Можно делить кластер по окружениям - dev/test/stage/prod
419 | - Некоторые ресурсы бывают "неймспейсные", т.е. создаются и существуют в рамках неймспейса, например, pod, deployment, из того с чем еще поработаем - configmap, PVC, ingress, service
420 | - Некоторые ресурсы создаются в кластере, так называемые cluster wide, т.е. они общие для всего кластера (StorageClass, persistent volumes, ClusterRoleBinding, ClusterRole и т.д., это будет разбираться дальше)
421 |
422 |
423 | # Resources
424 | - У подов в кластере kubernetes есть 2 основных ресурса, это память и CPU
425 | - Мы можем указывать, сколько нашему приложению для работы нужно выдать ресурсов в кластере kubernetes
426 | - Выдача ресурсов настраивается двумя параметрами :
427 | - Limits
428 | - Это верхняя граница количество ресурсов, которое pod и наше приложение в нём (контейнер точнее?) может максимально использовать
429 | - Например, если разрешено использовать 2 CPU, то приложение больше не получит, а в случае, если приложение попытается выделить больше указанного объёма памяти, придёт [OOM killer](https://neo4j.com/developer/kb/linux-out-of-memory-killer/), убъет контейнер, соответственно, убъет старый и создаст новый pod
430 | - Был случай, когда из-за данной особенности работы kubernetes приложение на Java, которое текло по памяти, проработало несколько недель, пока это не заметили, т.е. инстансы приложения перезапускались в разное время и глобально всё продолжало работать
431 | - Requests
432 | - Количество ресурсов, которое резервируется для пода на ноде
433 |
434 | ## Как это всё работает
435 | - У нод есть capacity по ресурсам, рассмотрим пример с памятью и ядрами CPU
436 | - Kubernetes принимает решения, куда отправить под на базе capacity нод и значений в requests
437 | - Например у наших нод capacity 16 CPU / 16 GB
438 | - Если мы создадим под, у которого в requests будет указано `cpu: 8` и этот под будет развёрнут на одной из таких нод, то свободная ёмкость по CPU у данной ноды станет 16-8=8, если таких подов будет 2, то на данную ноду больше не смогут попасть поды с заданными requests
439 | - Важно понимать, что requests не имеют никакого отношения к реальному потреблению ресурсов подами, то есть, мы можем указать в requests пода `cpu: 2`, 2 CPU будет зарезервировано на ноде, из её capacity эти 2 CPU будут отняты, но это значит, что наш под действительно будет нагружать 2 ядра, возможно они будут работать вхолостую
440 |
441 | ## Разбираем файл с описанием deployment для лучшего понимания
442 | - переходим в каталог `~/school-dev-k8s/practice/2.application-abstractions/4.resources` содержащий интересующий нас файл
443 | - открываем его и видим:
444 |
445 | ```yaml
446 | ---
447 | # file: practice/2.application-abstractions/4.resources/deployment-with-resources.yaml
448 | apiVersion: apps/v1
449 | kind: Deployment
450 | metadata:
451 | name: my-deployment
452 | spec:
453 | replicas: 2
454 | selector:
455 | matchLabels:
456 | app: my-app
457 | template:
458 | metadata:
459 | labels:
460 | app: my-app
461 | spec:
462 | containers:
463 | - image: quay.io/testing-farm/nginx:1.12
464 | name: nginx
465 | ports:
466 | - containerPort: 80
467 | resources:
468 | requests:
469 | cpu: 10m
470 | memory: 100Mi
471 | limits:
472 | cpu: 100m
473 | memory: 100Mi
474 | ...
475 | ```
476 | - нас интересует блок resources и параметры внутри
477 | - объемы памяти указываются, судя по всему, в [мебибайтах](https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D0%B1%D0%B8%D0%B1%D0%B0%D0%B9%D1%82), то есть, в привычных всем айтишникам мегабайтах, где для получения значения мы пользуемся степенями двойки
478 | - суффикс "m" в параметре cpu означает millicpu, 1/1000, т.е. 100m = 0.1 CPU
479 | - если указывать cpu в целых значениях, без суффикса, то это будет означать целые CPU
480 | - это упрощённое представление, но оно покрывает 100% наших нужд на данный момент
481 |
482 | ## QoS Class
483 | - если мы применим наш файл и с помощью describe выведем информацию об одном из созданных подов, мы сможем увидеть поле `QoS Class: Burstable`
484 | - QoS - это сокращение от quality of service, соответственно QoS Class => класс обслуживания
485 | - QoS классы в kubernetes тоже относятся к ресурсам, к лимитам и реквестам и их сочетаниям
486 | - От класса QoS зависит то, как kubernetes будет обрабатывать нехватку ресурсов:
487 | - 1. Best Effort
488 | - Когда мы не ставим на наше приложение никаких лимитов и реквестов, оно может приехать на любую ноду кластера и использовать все доступные ресурсы, но если ресурсов не будет хватать, то приложение сможет получить только те ресурсы, которые свободны
489 | - Например, если на ноде началась нехватка ресурсов и там есть поды с QoS классом Best Effort, то это поды будут удалены с ноды в первую очередь (и пересозданы где-то еще), чтобы освободить ресурсы для оставшихся подов с остальными QoS классами, чтобы они могли продолжать работать
490 | - 2. Burstable
491 | - Когда у нас указаны реквесты, но не указаны лимиты, либо лимиты больше чем реквесты
492 | - Наш файл описания деплоймента выше это демонстрирует - мы запрашиваем у ноды 10 millicpu, но можем потреблять до 100 millicpu
493 | - Поды данного класса во 2ю очередь будут удаляться с нод, испытывающих проблемы с ресурсами и пересоздаваться где-то еще
494 | - 3. Guaranteed, наивысший QoS класс
495 | - Тот случай, когда указанные лимиты и реквесты равны
496 | - Механизм удалёния подов с QoS классами ниже сделан для того, чтобы поды с QoS данного класса работали как можно дольше на своих нодах
497 | - Для важных приложений имеет смысл использовать данный класс QoS
498 |
499 | ## Продолжаем экспериментировать с ресурсами
500 | - Еще один способ изменить наш деплоймент на лету, актуально для разного рода автоматизации, в данном случае мы увеличиваем лимиты ресурсов:
501 | ```shell
502 | kubectl patch deployment my-deployment --patch '{"spec":{"template":{"spec":{"containers":[{"name":"nginx","resources":{"requests":{"cpu":"100"},"limits":{"cpu":"100"}}}]}}}}'
503 | ```
504 | - После применения патча у Павла в списке подов появился один со статусом **Pending**, это означает, что kubernetes не может найти для пода подходящую ноду, удовлетворяющую по ресурсам, указанным в деплойменте
505 | - Посмотреть подробности данной проблемы можно с помощью describe, мы сможем увидеть что-то подобное (в учебном кластере проявляется по другому, см. ниже):
506 | 
507 | - Читаем так: нет ни одной ноды, которая может удовлетворить реквестам в 100 CPU
508 | - Это достаточно частая в реальности ситуация, когда мы пытаемся деплоить, а ресурсов кластера не хватает
509 | - Это может говорить о том, что у приложения слишком большие требования и нужно с этим что-то сделать, либо дествительно есть проблемы с выделенными ресурсами в кластере и это задачка для отдела эксплуатации
510 | - В учебном кластере проявляется иначе. После применения патча, проверяем `kubectl describe deployments.apps my-deployment`. Видим, что создается ReplicaSet, но новых подов в выводе нет
511 | ```shell
512 | OldReplicaSets: my-deployment-6d6f49d794 (2/2 replicas created)
513 | NewReplicaSet: my-deployment-7ccb8f8966 (0/1 replicas created)
514 | Events:
515 | Type Reason Age From Message
516 | ---- ------ ---- ---- -------
517 | Normal ScalingReplicaSet 5m55s deployment-controller Scaled up replica set my-deployment-6d6f49d794 to 2
518 | Normal ScalingReplicaSet 5m51s deployment-controller Scaled up replica set my-deployment-7ccb8f8966 to 1
519 | ```
520 | Проверим, что выдает `kubectl get rs` и `kubectl describe rs`. Видим, что вторая реплика создалась не полностью из-за политики безопасности
521 | ```shell
522 | >kubectl get rs
523 |
524 | NAME DESIRED CURRENT READY AGE
525 | my-deployment-6d6f49d794 2 2 2 11m
526 | my-deployment-7ccb8f8966 1 0 0 11m
527 |
528 | >kubectl describe rs my-deployment-7ccb8f8966
529 |
530 | Warning FailedCreate 11m replicaset-controller Error creating: pods "my-deployment-7ccb8f8966-2wdlg" is forbidden: exceeded quota: resource-quota, requested: limits.cpu=2,requests.cpu=2, used: limits.cpu=200m,requests.cpu=20m, limited: limits.cpu=500m,requests.cpu=500m
531 | ```
532 |
533 | # Q&A
534 | - |
535 | - Q: Что будет если я запущу двухпоточное приложение по одновременной сортировке двух массивов в каждом потоке и выдам ему меньше одного ядра (100 millicpu)
536 | - A: Ответ будет рассмотрен в теме про особенности конкретных языков программирования
537 | - |
538 | - Q: Когда приложение стартует в поде видит ли оно сколько было прописано в реквестах или лимитах?
539 | - A: Также, ответ будет рассмотрен в теме про особенности конкретных языков программирования
540 | - Если кратко, зависит от того, куда приложение смотрит... Также зависит от языка, Java современная видит, Java 7 не видела
541 | - |
542 | - Q: Что почитать о том, как поднимать с 0 кластер kubernetes
543 | - A:
544 | - У Слёрма была еще одна бесплатная школа, больше админской направленности. Там было рассказано про kubespray. [Записи на youtube](https://www.youtube.com/watch?v=Jp866ltZBSk&list=PL8D2P0ruohOA4Y9LQoTttfSgsRwUGWpu6)
545 | - Можно отдельно поискать информацию про kubespray, kubeadm
546 | - Чтобы прям погрузиться - https://github.com/kelseyhightower/kubernetes-the-hard-way. Автор тоже developer advocate, он создал инструкцию о том как поднимать кластер kubernetes, без всяких вспомогательных инструментов, прям руками выписывая все сертификаты, скачивая и запуская бинарники и т.д. Очень хорошо прочищает мозги на предмет того как это работает, но это если вы прям хотите погрузиться
547 | - |
548 | - Q: Почему kubernetes перешел с docker-контейнеров на какие-то другие
549 | - A:
550 | - Текущее руководство компании Docker Inc стали проприетизировать продукт, внедрять лимиты, платные версии и т.д.
551 | - Docker - это довольно большая система, которая умеет собирать контейнеры, запускать, управлять ими с помощью swarm, в общем, это довольно большой engine, а чем больше продукт, тем больше точек отказа. А сам docker у себя под капотом использует контейнерную технологию, технологию для запуска самих контейнеров, под названием runC. Поэтому в какой-то момент ребята из комьюнити kubernetes подумали, что им не нужен весь docker, взяли за основу runC и на его основе написали своё минималистичное решение для запуска контейнеров, потому что kubernetes не требуется собирать эти контейнеры, не нужна прочая куча функционала docker. Получились всякие штуки типа crio, containerd, project Moby, которые умеют запускать контейнеры максимально легковесно. Все эти штуки называются CRI - container runtime interface. Все эти штуки и сам docker CRI-совместимые, поэтому всё что было создано в docker, работает с любым CRI.
552 | - Если говорить о дальнейшей судьбе docker - он не умрёт, это инструмент разработчика, ops инженера, инструмент CI/CD - собрать контейнер, запустить локально, docker-compose - прекрасный инструмент и т.п.
553 | - |
554 | - Q: Что такое реплика?
555 | - A: Копия приложения, N реплик = N копий приложения
556 | - |
557 | - Q: Как управлять несколькими кластерами kubernetes одновременно, есть ли best practice, чтобы не путаться и не стрелять себе в ногу
558 | - A:
559 | - Речь о том что у вас локально 2 конфига разных кластеров?
560 | - Когда я работал со stage и prod кластерами, я сделал алиас prodctl, чтобы не путаться, он подключал конфиг от продакшн кластера. Но пару раз ошибки всё же были
561 | - Сейчас у меня везде сконфигурирован кусочек prompt, который показывает, какой контекст сейчас используется, я привык проверять контекст перед применением изменений
562 | - Для случаев, когда совсем страшно что-то сломать, для доступа к prod среде сделать отдельный jump host, adminbox - отдельную виртуалку, через которую будет происходить вся работа
563 | - |
564 | - Q: Есть ли в kubernetes стратегия деплоймента blue/green, чтобы трафик шёл только на одну версию приложения в один момент?
565 | - A: Встроенных нет, возможно, это есть в рамках этого или предыдущего курса, в любом случае, можно найти на эту тему материалы в сети, делается непринужденно
566 | - |
567 | - Q: Как меньше уставать на работе? Возможно у меня выгорание или возраст берёт своё...
568 | - A: Ответ Павла: Обратиться к психологу, это прям правильно и полезно для здоровья
569 | - Дополнение от меня: Стоит обратить внимание на материалы Максима Дорофеева, они могут помочь либо улучшить ситуацию, либо убедиться, что нужно обращаться к специалистам
570 | - |
571 | - Q: Как что округляется, когда значения заданы в процентах
572 | - A: Коллеги, так же как в прошлый раз, рекомендую поэкспериментировать
573 | - |
574 | - Q: Есть ли где-то бесплатный/пробный managed kubernetes
575 | - A:
576 | - MCS (mail.ru), если вы не майнер и не спамер, вам могут начислить бонусы и вы сможете получить пробный период
577 | - Наверняка, существуют другие провайдеры с такой услугой, можно найти в гугле
578 | - |
579 | - Q: По поводу книг
580 | - A:
581 | - Книги успевают устаревать быстрее чем выходят, поэтому самое стоящее - документация, https://kubernetes.io. Там и статьи в блогах есть и инструкции для начинающих и много чего еще
582 | - Также, есть интерактивные курсы https://www.katacoda.com/, там есть kubernetes
583 |
584 | # Из чата:
585 | - [Programmatically generated handy kubectl aliases.](https://github.com/ahmetb/kubectl-aliases)
586 |
--------------------------------------------------------------------------------
/lesson 4.md:
--------------------------------------------------------------------------------
1 | ---
2 | date created: 2021-10-12 19:01:26 (+03:00), Tuesday
3 | ---
4 |
5 | # Урок 4: Хранение конфигураций. Вечерняя школа «Kubernetes для разработчиков» [youtube](https://www.youtube.com/watch?v=-xZ02dEF6kU)
6 |
7 |
8 | ## Вступление ([00:01:45](https://youtu.be/-xZ02dEF6kU?t=105))
9 | - Каким образом в кластере k8s можно передавать конфигурации в наши приложения
10 | - Самый простой и неправильный вариант - захардкодить конфигурацию в контейнер и запускать приложение в таком неизменном виде. Не нужно так делать!
11 | - Более цивилизованные варианты будут представлены далее
12 |
13 | ## Представление преподавателя ([00:02:31](https://youtu.be/-xZ02dEF6kU?t=151))
14 | - Сергей Бондарев
15 | - Архитектор Southbridge
16 | - Инженер с 25-летним стажем
17 | - Certified Kubernetes Administrator
18 | - Внедрения Kubernetes: все куб-проекты Southbridge, включая собственную инфраструктуру
19 | - Один из разработчиков kubespray с правами на принятие pull request
20 |
21 | ## Env, переменные окружения ([00:03:49](https://youtu.be/-xZ02dEF6kU?t=228))
22 | - Работаем в каталоге `~/school-dev-k8s/practice/4.saving-configurations/1.env`
23 | - Открываем манифест `deployment-with-env.yaml`
24 | ```yaml
25 | # deployment-with-env.yaml
26 | ---
27 | apiVersion: apps/v1
28 | kind: Deployment
29 | metadata:
30 | name: my-deployment
31 | spec:
32 | replicas: 1
33 | selector:
34 | matchLabels:
35 | app: my-app
36 | strategy:
37 | rollingUpdate:
38 | maxSurge: 1
39 | maxUnavailable: 1
40 | type: RollingUpdate
41 | template:
42 | metadata:
43 | labels:
44 | app: my-app
45 | spec:
46 | containers:
47 | - image: quay.io/testing-farm/nginx:1.12
48 | name: nginx
49 | env: # Описываем желаемые переменные окружения
50 | - name: TEST # Имя переменной окружения
51 | value: foo # Значение переменной окружения
52 | ports:
53 | - containerPort: 80
54 | resources:
55 | requests:
56 | cpu: 10m
57 | memory: 100Mi
58 | limits:
59 | cpu: 100m
60 | memory: 100Mi
61 | ...
62 | ```
63 | - В этом манифесте мы указываем вариант доставки конфигураций внутрь нашего приложения так как это советует 12-factor applications, то есть, доставляем конфигурацию в виде переменных окружения
64 | - У нас среда исполнения (docker, containerD, crio) позволяет при запуске процесса внутри контейнера создать этому процессу переменные окружения
65 | - Самый простой вариант в kubernetes - описать все наши переменные окружения, который должны быть в контейнере, в манифесте (пода, репликасета, но как правило, это деплоймент)
66 | - Смотрим в манифест, комментариями отмечен раздел с переменными окружения в описании контейнера
67 | - Когда мы применим такой манифест в нашем кластере, в соответствующем контейнере появится переменная **TEST** со значением **foo**:
68 | ```shell
69 | $ kubectl apply -f deployment-with-env.yaml
70 |
71 | deployment.apps/my-deployment created
72 |
73 | $ kubectl get pod
74 |
75 | NAME READY STATUS RESTARTS AGE
76 | my-deployment-7b54b94746-blvpg 1/1 Running 0 26s
77 |
78 | $ kubectl describe pod my-deployment-7b54b94746-blvpg
79 |
80 | ... # в выводе будет много информации, нас интересует данный блок, относящийся к контейнеру
81 | Environment:
82 | TEST: foo # мы видим, что в контейнере создана переменная "TEST" со значением "foo"
83 | ...
84 | ```
85 | - С помощью переменных окружения можно передавать различные конфигурации в наши приложения, соответственно, приложение будет считывать переменные окружения и применять их на своё усмотрение, например, таким образом можно передавать различные данные, как правило, конфигурационные
86 | - Единственный, но достаточно большой минус такого подхода - если у вас есть повторяющиеся настройки для различных деплойментов, то нам придётся все эти настройки придется повторять для каждого деплоймента, например, если поменяется адрес БД, то его придётся изменить, скажем, в десятке деплойментов
87 | - Как этого избежать? Очень просто:
88 |
89 | ## ConfigMap ([00:08:19](https://youtu.be/-xZ02dEF6kU?t=499))
90 | - ConfigMap, как и всё в kubernetes, описывается в yaml файле, в котором, в формате key:value описаны настройки, которые можно использовать в нашем приложении
91 | - в ConfigMap имеется раздел data, собственно там мы и задаём наши настройки, а потом этот словарь, этот ConfigMap, целиком указать в манифесте деплоймента, чтобы из него создать соответствующие переменные окружения в нашем контейнере
92 | - Таким образом, за счёт ConfigMap мы можем уменьшить дублирование кода и упростить настройку однотипных приложений
93 |
94 | ### Идём в терминал ([00:09:27](https://youtu.be/-xZ02dEF6kU?t=567))
95 | - В том же каталоге `~/school-dev-k8s/practice/4.saving-configurations/1.env` открываем манифест
96 | ```yaml
97 | # configmap.yaml
98 | ---
99 | apiVersion: v1
100 | kind: ConfigMap
101 | metadata:
102 | name: my-configmap-env
103 | data: # интересующий нас раздел с настройками
104 | dbhost: postgresql # параметр 1
105 | DEBUG: "false" # параметр 2
106 | ...
107 | ```
108 | - Применяем наш ConfigMap и смотрим что получилось
109 | ```shell
110 | $ kubectl apply -f configmap.yaml
111 |
112 | configmap/my-configmap-env created
113 |
114 | $ kubectl get cm # cm - сокращение для configmap
115 |
116 | NAME DATA AGE
117 | kube-root-ca.crt 1 11d # служебный configmap, созданный автоматически
118 | my-configmap-env 2 79s # результат наших действий
119 | ```
120 | - DATA со значением 2 означает, что в данном конфигмапе находится 2 ключа
121 |
122 | ### Как использовать ConfigMap ([00:10:56](https://youtu.be/-xZ02dEF6kU?t=656))
123 | - Традиционно, через специально подготовленный манифест
124 | ```yaml
125 | # deployment-with-env-cm.yaml
126 | ---
127 | apiVersion: apps/v1
128 | kind: Deployment
129 | metadata:
130 | name: my-deployment
131 | spec:
132 | replicas: 1
133 | selector:
134 | matchLabels:
135 | app: my-app
136 | strategy:
137 | rollingUpdate:
138 | maxSurge: 1
139 | maxUnavailable: 1
140 | type: RollingUpdate
141 | template:
142 | metadata:
143 | labels:
144 | app: my-app
145 | spec:
146 | containers:
147 | - image: quay.io/testing-farm/nginx:1.12
148 | name: nginx
149 | env:
150 | - name: TEST
151 | value: foo
152 | envFrom: # раздел для загрузки переменных окружения извне
153 | - configMapRef: # указываем, что будем брать их по ссылке на конфигмап
154 | name: my-configmap-env # указываем наш объект ConfigMap, из которого будут загружаться данные
155 | ports:
156 | - containerPort: 80
157 | resources:
158 | requests:
159 | cpu: 10m
160 | memory: 100Mi
161 | limits:
162 | cpu: 100m
163 | memory: 100Mi
164 | ...
165 | ```
166 | - Результатом применения данного файла будет деплоймент, содержащий в себе контейнер с переменными окружения, прописанными в разделе DATA конфигмапа **my-configmap-env**
167 | - Но если мы заглянем в информацию о созданном таким образом поде, то увидим похожую картину:
168 | ```shell
169 | ...
170 | Environment Variables from:
171 | my-configmap-env ConfigMap Optional: false
172 | Environment:
173 | TEST: foo
174 | ...
175 | ```
176 | - То есть, переменные, заданные через env мы можем видеть, а через envFrom - нет, только название объекта, из которого они загружаются
177 | - Мы можем заглянуть в ConfigMap, чтобы увидеть, что должно быть записано в переменных окружения:
178 | ```shell
179 | $ kubectl get cm my-configmap-env -o yaml
180 |
181 | apiVersion: v1
182 | data: # интересующий нас раздел
183 | DEBUG: "false"
184 | dbhost: postgresql
185 | kind: ConfigMap
186 | # далее ещё много всего, в основном, создаваемые автоматически значения по умолчанию
187 | ```
188 | - Также, мы можем сходить внутрь контейнера и через его шелл посмотреть переменные окружения, примерно так:
189 | ```shell
190 | $ kubectl exec -it my-deployment-7d7cff784b-rjb55 -- bash
191 | # "--" - это разделитель, обозначающий конец команды по отношению к kubectl и начало команды для пода
192 | # в реультате выполнения увидим приглашение:
193 | root@my-deployment-7d7cff784b-rjb55:/#
194 | # выйти можно по сочетанию ctrl+d или командой exit
195 |
196 | # также, мы можем сразу же обратиться к переменным окружения:
197 | $ kubectl exec -it my-deployment-7d7cff784b-rjb55 -- env
198 | # на выходе будет список всех переменных окружения
199 | ```
200 |
201 | ### Что будет, если данные в ConfigMap поменяются? ([00:15:22](https://youtu.be/-xZ02dEF6kU?t=922))
202 | - У запущенного контейнера переменные окружения поменять не так просто и kubernetes этим не занимается
203 | - Если контейнер или под, уже запущены, то изменения ConfigMap на его переменных окружения не отразятся
204 | - Таким образом, если мы хотим, чтобы приложения стало работать с новыми переменными окружения, для этого необходимо убить поды и создать новые
205 |
206 | ### Какой приоритет env и envFrom? ([00:16:13](https://youtu.be/-xZ02dEF6kU?t=973))
207 | - Сергей предлагает поэкспериментировать
208 |
209 | ### Что будет, если разные ConfigMap будут содержать одинаковые переменные ([00:16:38](https://www.youtube.com/watch?v=-xZ02dEF6kU&t=998s))
210 | - Вероятно, один из конфигмапов "победит"
211 |
212 | #### Ответ на оба вопроса
213 | - Можно найти в [документации](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#envfromsource-v1-core), ctrl+f по "EnvFromSource array", также, разжевано на [SO](https://stackoverflow.com/questions/54398272/override-env-values-defined-in-container-spec)
214 | - Значится там следующее - значения заданные в env не могут быть перезаписаны, а если ключ содержится в разных источниках (таких как ConfigMap), будет использовано значение из последнего
215 |
216 | ## Secret ([00:17:16](https://youtu.be/-xZ02dEF6kU?t=1036))
217 | - Используется для работы с чувствительными данными, такими как токены доступа, пароли, приватные ключи сертификатов
218 | - Имеет структуру аналогичную ConfigMap, данные задаются в разделе data
219 | - Бывает нескольких типов:
220 | - generic - самый распространенный тип, обычно используется для токенов и логинов с паролями
221 | - docker-registry - данные для авторизации в docker registry
222 | - фактически, является секретом с заранее определённым списком ключей в массиве data, содержит, в частности:
223 | - ключ, отвечающий за адрес репозитория
224 | - ключи, отвечающий за логин, пароль и почту
225 | - tls - предназначен для хранения сертификатов для шифрования данных, для HTTPS TLS протокола
226 | - как правило, используется в Ingress
227 | - имеет 2 предопределённых поля:
228 | - приватный ключ
229 | - сам подписанный сертификат
230 |
231 | ### Экспериментируем
232 | #### Создаём секрет ([00:20:53](https://youtu.be/-xZ02dEF6kU?t=1252))
233 | - Переходим в `~/school-dev-k8s/practice/4.saving-configurations/2.secret`
234 | - Подсматриваем в **README.MD** и выполняем команды оттуда:
235 | ```shell
236 | # создаём секрет
237 | $ kubectl create secret generic test --from-literal=test1=asdf --from-literal=dbpassword=1q2w3e
238 |
239 | secret/test created
240 |
241 | $ kubectl get secret
242 |
243 | NAME TYPE DATA AGE
244 | default-token-wgc7r kubernetes.io/service-account-token 3 11d
245 | s024713-token-8vmmm kubernetes.io/service-account-token 3 11d
246 | test Opaque 2 8s
247 |
248 | $ kubectl get secret test -o yaml
249 |
250 | apiVersion: v1
251 | data:
252 | dbpassword: MXEydzNl
253 | test1: YXNkZg==
254 | kind: Secret
255 | metadata:
256 | creationTimestamp: "2021-10-12T17:43:10Z"
257 | managedFields:
258 | - apiVersion: v1
259 | fieldsType: FieldsV1
260 | fieldsV1:
261 | f:data:
262 | .: {}
263 | f:dbpassword: {}
264 | f:test1: {}
265 | f:type: {}
266 | manager: kubectl-create
267 | operation: Update
268 | time: "2021-10-12T17:43:10Z"
269 | name: test
270 | namespace: s024713
271 | resourceVersion: "31214172"
272 | selfLink: /api/v1/namespaces/s024713/secrets/test
273 | uid: 9a98d624-1a40-40d0-a347-9feb852373ab
274 | type: Opaque
275 | ```
276 | - Важно! Секреты типа **kubernetes.io/service-account-token** удалять не нужно, т.к. они отвечают за работу с учебным кластером, они автоматически пересоздадутся, но доступ будет утерян
277 | - При создании generic секрета мы получаем на выходе секрет с типом **Opaque**, что означает "непрозрачный". Данное расхождение в именовании сложилось исторически, все об этом знают и живут с этим.
278 | - Заглядывая в вывод `kubectl get` мы видим, что значения секретов в разделе data отличаются от заданных, это они же, закодированные base64
279 | - Важно понимать, что кодирование - это не шифрование и данные приводятся к исходному виду очень просто, без всяких ключей шифрования
280 | - Зачем это нужно, если механизм декодирования так прост?
281 | - Одна из причин - обработанные таким образом строки хорошо воспринимаются yaml'ом, не нужно думать об экранировании
282 | - В kubernetes есть механизм RBAC, позволяющий ограничивать права пользователей на доступ к различным объектам kubernetes
283 | - По умолчанию, возможность просматривать и редактировать секреты через edit есть только у роли администраторов
284 | - Однако, не стоит забывать, что настройки, доставленные в приложение, могут быть обработаны этим самым приложением, так что, при желании, разработчики могут их получить. Обычно это уже головная боль специалистов по безопасности
285 |
286 | #### Посмотрим на деплоймент, который будет использовать наш секрет ([00:28:38](https://youtu.be/-xZ02dEF6kU?t=1718))
287 | ```yaml
288 | # deployment-with-secret.yaml
289 | ---
290 | apiVersion: apps/v1
291 | kind: Deployment
292 | metadata:
293 | name: my-deployment
294 | spec:
295 | replicas: 1
296 | selector:
297 | matchLabels:
298 | app: my-app
299 | strategy:
300 | rollingUpdate:
301 | maxSurge: 1
302 | maxUnavailable: 1
303 | type: RollingUpdate
304 | template:
305 | metadata:
306 | labels:
307 | app: my-app
308 | spec:
309 | containers:
310 | - image: quay.io/testing-farm/nginx:1.12
311 | name: nginx
312 | envFrom:
313 | - configMapRef:
314 | name: my-configmap-env
315 | env:
316 | - name: TEST
317 | value: foo
318 | - name: TEST_1 # здесь мы можем указать название переменной, отличное от имени ключа в секрете или конфигмапе
319 | valueFrom: # таким образом можно получать значения конкретных ключей из конфигмапов и секретов
320 | secretKeyRef:
321 | name: test
322 | key: test1
323 | ports:
324 | - containerPort: 80
325 | resources:
326 | requests:
327 | cpu: 10m
328 | memory: 100Mi
329 | limits:
330 | cpu: 100m
331 | memory: 100Mi
332 | ...
333 | ```
334 | - Применяем и смотрим что получилось:
335 | ```shell
336 | $ kubectl apply -f deployment-with-secret.yaml
337 |
338 | deployment.apps/my-deployment configured
339 |
340 | $ kubectl get pod
341 |
342 | NAME READY STATUS RESTARTS AGE
343 | my-deployment-57b8cc674c-qc9rk 1/1 Running 0 22s
344 |
345 | $ kubectl describe pod my-deployment-57b8cc674c-qc9rk
346 |
347 | # нас интересует данная секция:
348 | ...
349 | Environment Variables from:
350 | my-configmap-env ConfigMap Optional: false
351 | Environment:
352 | TEST: foo
353 | TEST_1: Optional: false
354 | ...
355 |
356 | ```
357 | - Видим, что в describe нашего объекта значение секрета скрыто
358 | - Но если мы сходим внутрь пода через exec, то мы сможем увидеть содержимое переменных окружений (если такая возможность не отключена, как правило, именно так и поступают)
359 |
360 | ### stringData в секретах ([00:34:37](https://youtu.be/-xZ02dEF6kU?t=2077))
361 | - Пример манифеста:
362 | ```yaml
363 | # secret.yaml
364 | ---
365 | apiVersion: v1
366 | kind: Secret
367 | metadata:
368 | name: test
369 | stringData:
370 | test: updated
371 | ...
372 | ```
373 | - Мы видим, что здесь значение ключа test незакодировано
374 | - Данный раздел был придуман для упрощения работы с секретами и их перекодировкой из/в base64
375 | - Данные, занесенные в раздел stringData будут перенесены в секреты и закодированы соответствующим образом
376 | ```shell
377 | $ kubectl apply -f secret.yaml
378 | Warning: resource secrets/test is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically.
379 | secret/test configured
380 | ```
381 | - В предупреждении речь идёт о том, что для корректной работы, а именно, для обработки мерджей конфигураций (активной и применяемой по apply), рекомендуется выполнять создание объектов определённым образом, иначе могут возникнуть побочные эффекты, а в нашем конкретном случае kubectl сам исправит данный недочёт
382 | - В конце урока обещали скинуть ссылки на почитать
383 | - После применения данного манифеста, если мы посмотрим на наш секрет, увидим следующее:
384 | ```shell
385 | $ kubectl get secret test -o yaml
386 | apiVersion: v1
387 | data:
388 | dbpassword: MXEydzNl
389 | test: dXBkYXRlZA==
390 | test1: YXNkZg==
391 | kind: Secret
392 | metadata:
393 | annotations:
394 | kubectl.kubernetes.io/last-applied-configuration: |
395 | {"apiVersion":"v1","kind":"Secret","metadata":{"annotations":{},"name":"test","namespace":"s024713"},"stringData":{"test":"updated"}}
396 | # далее инфа, нам не актуальная
397 | ```
398 | - В секции data добавился секрет test
399 | - В аннотации добавился ключи last-applied-configuration, с информацией о применённых нами изменениях
400 |
401 | - Почему это важно? Потому что, если мы попробуем исправить наш yaml (в нашем случае мы меняем имя ключа test на test1) и повторно выполнить apply, то мы увидим странное:
402 | ```shell
403 | $ kubectl get secrets test -o yaml
404 | apiVersion: v1
405 | data:
406 | dbpassword: MXEydzNl
407 | test: ""
408 | test1: dXBkYXRlZA==
409 | kind: Secret
410 | metadata:
411 | annotations:
412 | kubectl.kubernetes.io/last-applied-configuration: |
413 | {"apiVersion":"v1","kind":"Secret","metadata":{"annotations":{},"name":"test","namespace":"s024713"},"stringData":{"test1":"updated"}}
414 | # далее инфа, нам не актуальная
415 | ```
416 | - Наш секрет записался в ключ test1, а значение ключа test обнулилось, т.к. за счёт данных в last-applied-configuration kubernetes понял, что раньше мы работали с ключом test, а теперь его нет
417 | - Далее ведущий демонстрирует, что изменение секрета не затрагивает запущенный под, затем убивает его и проверяет, что в новом поде секрет обновился
418 |
419 | ## Q&A ([00:44:28](https://youtu.be/-xZ02dEF6kU?t=2668))
420 | - |
421 | - Q: А если надо передать IP новой POD'ы? Например, заскейлили новый memcached - приложению надо знать все IPs POD'ов memcached'а?
422 | - A: Это делается по другому, через сущность под названием "сервисы", будет позже, в лекции Марселя, конкретно про memcached, там будут безголовые, так называемые headless сервисы
423 | - |
424 | - Q: Как из vault передавать пароли в kubernetes
425 | - A: Это есть в курсе Слёрм-Мега, если вкратце, в vault есть модуль для интеграции с kubernetes и можно научить приложение ходить в vault с токеном, который ему даст kubernetes и по этому токену брать данные из vault
426 | - |
427 | - Q: Зачем "--" перед env в команде типа `kubectl exec -it my-deployment-7d7cff784b-rjb55 -- env`
428 | - A: Отвечает за разделение между окончанием команды kubectl и началом команды к поду
429 |
430 | ## Volumes ([00:47:04](https://youtu.be/-xZ02dEF6kU?t=2824))
431 | - Если вспомнить про docker, docker compose, то мы знаем, что системы исполнения контейнеров позволяют монтировать внутрь контейнера не просто переменные окружения, но еще и файлы, причём, монтировать файлы с диска внутрь контейнера
432 | - В kubernetes пошли дальше и сделали возможность монтировать в контейнер содержимое секретов и конфигмапов в качестве файлов
433 | - В ConfigMap можно указывать многострочные значения, затем к ним можно будет обратиться через секцию манифеста volumes, таким образом, в контейнер можно монтировать файлы, со значениями из ConfigMap, обычно там хранят целые конфигурации
434 |
435 | ### Экспериментируем ([00:48:32](https://youtu.be/-xZ02dEF6kU?t=3756))))
436 | - Переходим в `~/school-dev-k8s/practice/4.saving-configurations/3.configmap`
437 | - видим манифест ConfigMap с куском конфига nginx:
438 | ```yaml
439 | # configmap.yaml
440 | ---
441 | apiVersion: v1
442 | kind: ConfigMap
443 | metadata:
444 | name: my-configmap
445 | data:
446 | default.conf: |
447 | server {
448 | listen 80 default_server;
449 | server_name _;
450 |
451 | default_type text/plain;
452 |
453 | location / {
454 | return 200 '$hostname\n';
455 | }
456 | }
457 | ...
458 | ```
459 | - В данном случае, всё что после символа | - это многострочное значение ключа default.conf
460 | - Многострочное значение оканчивается там, где отступ будет меньше чем в начале этого значения, т.е. на том же уровне, что и у ключа, в нашем случае default.conf
461 | - Про многострочный текст через символ | можно почитать [здесь](https://habr.com/ru/post/270097/). Официальная документация для меня достаточно трудочитаема, кажется, что-то из этого - [1](https://yaml.org/spec/1.2.2/#8111-block-indentation-indicator), [2](https://yaml.org/spec/1.2.2/#812-literal-style)
462 | - Далее смотрим на манифест деплоймента:
463 | ```yaml
464 | # deployment-with-configmap.yaml
465 | ---
466 | apiVersion: apps/v1
467 | kind: Deployment
468 | metadata:
469 | name: my-deployment
470 | spec:
471 | replicas: 1
472 | selector:
473 | matchLabels:
474 | app: my-app
475 | strategy:
476 | rollingUpdate:
477 | maxSurge: 1
478 | maxUnavailable: 1
479 | type: RollingUpdate
480 | template:
481 | metadata:
482 | labels:
483 | app: my-app
484 | spec:
485 | containers:
486 | - image: quay.io/testing-farm/nginx:1.12
487 | name: nginx
488 | ports:
489 | - containerPort: 80
490 | resources:
491 | requests:
492 | cpu: 10m
493 | memory: 100Mi
494 | limits:
495 | cpu: 100m
496 | memory: 100Mi
497 | volumeMounts: # Здесь мы описываем точки монтирования томов внутри контейнера
498 | - name: config # Указываем имя тома для монтирования
499 | mountPath: /etc/nginx/conf.d/ # Здесь мы указываем точку монтирования
500 | volumes: # Здесь (на уровень выше!) мы описываем тома
501 | - name: config # Задаём имя тому
502 | configMap: # Указываем, из какого конфигмапа создать том
503 | name: my-configmap
504 | ...
505 | ```
506 | - Зачем делать это в 2 этапа, сначала формировать вольюм, затем подключать, почему сразу не подключить данные из конфигмапа в контейнер?
507 | - Потому что обычно один и тот же том монтируют сразу в несколько контейнеров
508 | - Аналогичные манипуляции можно применять не только к конфигмапам, но и к секретам
509 | - Если мы применим данный конфигмап и деплоймент, то сможем увидеть в нашем контейнере файл, и его содержимое:
510 | ```shell
511 | $ kubectl exec -it my-deployment-5dbbd56b95-xdb2j -- ls /etc/nginx/conf.d/
512 |
513 | default.conf
514 |
515 | $ kubectl exec -it my-deployment-5dbbd56b95-xdb2j -- cat /etc/nginx/conf.d/default.conf
516 |
517 | server {
518 | listen 80 default_server;
519 | server_name _;
520 |
521 | default_type text/plain;
522 |
523 | location / {
524 | return 200 '$hostname\n';
525 | }
526 | }
527 | ```
528 | - Причём, мы видим, что в созданном файле никаких лишних отступов, как при записи в yaml файле, нет
529 | - Есть ограничения на размер создаваемых файлов (не понятно, техническое или речь о здравом смысле), не стоит создавать файлы по 20МБ
530 | - Очень часто таким образом монтируются файлы с tls сертификатами из секретов
531 | - Далее имеет смысл перемотать на время следующего заголовка
532 | ### Кое что ещё про монтирование конгфигмапов в качестве файлов ([01:21:40](https://youtu.be/-xZ02dEF6kU?t=4900))
533 | - Если мы в поде перейдём в директорию /etc/nginx/conf.d и набёрём ls -la, мы увидим что часть содержимого это линки, обозначены как "l"
534 | ```shell
535 | $ k exec -it my-deployment-5dbbd56b95-lcztg -- bash
536 | # cd /etc/nginx/conf.d
537 | # ls -la
538 |
539 | total 12
540 | drwxrwxrwx. 3 root root 4096 Oct 13 20:49 .
541 | drwxr-xr-x. 3 root root 4096 Apr 30 2018 ..
542 | drwxr-xr-x. 2 root root 4096 Oct 13 20:49 ..2021_10_13_20_49_15.281823334
543 | lrwxrwxrwx. 1 root root 31 Oct 13 20:49 ..data -> ..2021_10_13_20_49_15.281823334
544 | lrwxrwxrwx. 1 root root 19 Oct 13 20:49 default.conf -> ..data/default.conf
545 | ```
546 | - Мы видим следующее:
547 | - файл default.conf является линком на файл \..data/default.conf
548 | - Каталог \..data, в свою очередь, тоже является линком на каталог \..2021_10_13_20_49_15.281823334
549 | - Каталог \..2021_10_13_20_49_15.281823334 уже дествительно является каталогом
550 | - Если мы перейдем в него, то увидим наш файл default.conf, он действительно является файлом а не линком
551 | - Таким образом, мы видим, что файл конфигмапа монтируется в специальный каталог, в точку монтирования пробрасывается симлинк
552 | - Это нужно, чтобы атомарно переключать содержимое файлов
553 | - Когда меняется ConfigMap, kubernetes монтирует в контейнер новый каталог с данными из ConfigMap, а после этого происходит подмена симлинка на новый каталог
554 | - Операция смены симлинка атомарна и намного быстрее операции монтирования, таким образом приложение может продолжать работать с файлом без каких либо прерываний
555 | - После успешной смены линка, старый каталог, старая точка монтирования, удаляется
556 | - Далее имеет смысл перемотать на время следующего заголовка
557 |
558 | ## kubectl port-forward ([00:54:48](https://youtu.be/-xZ02dEF6kU?t=3528))
559 | - Позволяет настроить перенаправления порта извне кластера в под
560 | - Полезно при отладке, тестировании, в процессе разработке, в общем, не для боевого использования
561 | - Выполняется следущим образом:
562 | ```shell
563 | $ kubectl port-forward my-deployment-5b47d48b58-l4t67 8080:80 &
564 | # амперсанд в конце нужен, чтобы отправить команду в фон. Вернуться к ней можно через команду %, завершить, как обычно, по ctrl+c
565 | # возможно, нужно будет поиграть с подбором порта, если 8080 будет занят
566 | # видим на выходе что-то подобное:
567 | [1] 26503
568 | Forwarding from 127.0.0.1:8080 -> 80
569 |
570 | $ curl 127.0.0.1:8080
571 | # стучимся на данный порт и видим ответ:
572 | Handling connection for 8080
573 | my-deployment-5dbbd56b95-xdb2j
574 | ```
575 | - Причём тут port-forward? Чтобы проверить, что наш конфиг из предыдущего шага применился и отработал
576 | - Далее меняем в нашем конфигмапе строку с возвратом, например, добавляем туда `OK\n` в конце, чтобы увидеть разницу
577 | - В отличие от переменных окружения, изменение конфигмапа, который мы подключаем как том, отразится на содержимом файла в контейнере (не мгновенно, через несколько секунд)
578 | - Далее снова пробуем стучаться на проброшенный порт, но ответ неизменен
579 | - Это происходит потому что nginx сам по себе не перечитывает конфиг
580 |
581 | ## Downward API ([01:06:54](https://youtu.be/-xZ02dEF6kU?t=4014))
582 | - Документация:
583 | - [Expose Pod Information to Containers Through Environment Variables](https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/)
584 | - [Expose Pod Information to Containers Through Files](https://kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/)
585 | - Позволяет передать приложению некоторые параметры манифестов как переменные окружения или как файлы
586 | - Позволяет передать приложению различную информацию о том, где оно запущено, например:
587 | - адрес узла
588 | - название узла
589 | - название неймспейса
590 | - название пода
591 | - адрес пода
592 | - реквесты и лимиты, которые описаны в манифесте пода
593 | - на прошлой лекции был вопрос, как приложение может узнать, какие ему заданы реквесты и лимиты, есть 2 варианта:
594 | - если приложение или джава там всякая (чиво ?) уже умеет смотреть в [cgroups]() и брать значения оттуда
595 | - взять значения из переменных окружения, которые мы можем передать туда с помощью Downward API из манифеста нашего контейнера
596 |
597 | ### Экспериментируем ([01:08:30](https://youtu.be/-xZ02dEF6kU?t=4110))
598 | - Идем в каталог `~/school-dev-k8s/practice/4.saving-configurations/4.downward`
599 | - Заглядываем в деплоймент:
600 | ```yaml
601 | # deployment-with-downward-api.yaml
602 | ---
603 | apiVersion: apps/v1
604 | kind: Deployment
605 | metadata:
606 | name: my-deployment
607 | spec:
608 | replicas: 1
609 | selector:
610 | matchLabels:
611 | app: my-app
612 | strategy:
613 | rollingUpdate:
614 | maxSurge: 1
615 | maxUnavailable: 1
616 | type: RollingUpdate
617 | template:
618 | metadata:
619 | labels:
620 | app: my-app
621 | spec:
622 | containers:
623 | - image: quay.io/testing-farm/nginx:1.12
624 | name: nginx
625 | env:
626 | - name: TEST
627 | value: foo
628 | - name: TEST_1
629 | valueFrom:
630 | secretKeyRef:
631 | name: test
632 | key: test1
633 | - name: __NODE_NAME # Начало первого интересующего нас блока
634 | valueFrom:
635 | fieldRef:
636 | fieldPath: spec.nodeName # Название узла, ноды, где запущен под
637 | - name: __POD_NAME
638 | valueFrom:
639 | fieldRef:
640 | fieldPath: metadata.name # Имя пода из нашего манифеста
641 | - name: __POD_NAMESPACE
642 | valueFrom:
643 | fieldRef:
644 | fieldPath: metadata.namespace # Неймспейс пода
645 | - name: __POD_IP
646 | valueFrom:
647 | fieldRef:
648 | fieldPath: status.podIP # IP-адрес пода
649 | - name: __NODE_IP
650 | valueFrom:
651 | fieldRef:
652 | fieldPath: status.hostIP # IP-адрес ноды
653 | - name: __POD_SERVICE_ACCOUNT
654 | valueFrom:
655 | fieldRef:
656 | fieldPath: spec.serviceAccountName # Задаём сервис-аккаунт, будем разбираться далее, в лекции по RBAC
657 | ports: # Конец первого интересующего нас блока
658 | - containerPort: 80
659 | resources:
660 | requests:
661 | cpu: 10m
662 | memory: 100Mi
663 | limits:
664 | cpu: 100m
665 | memory: 100Mi
666 | volumeMounts: # Начало второго интересующего нас блока
667 | - name: config
668 | mountPath: /etc/nginx/conf.d/
669 | - name: podinfo # Монтируем том downwardAPI, аналогично монтированию тома конфигмапы
670 | mountPath: /etc/podinfo
671 | volumes:
672 | - name: config
673 | configMap:
674 | name: my-configmap
675 | - name: podinfo # Описываем создание тома downwardAPI
676 | downwardAPI:
677 | items:
678 | - path: "labels" # Указываем название создаваемого файла
679 | fieldRef: # Указываем, что мы заполняем наш файл на базе ссылки на поля манифеста
680 | fieldPath: metadata.labels # Указываем раздел манифеста, из которого мы будем забирать информацию
681 | - path: "annotations"
682 | fieldRef:
683 | fieldPath: metadata.annotations
684 | ... # Конец второго интересующего нас блока
685 | ```
686 | - Нам интересны блоки подобные этому:
687 | ```yaml
688 | - name: __POD_IP
689 | valueFrom:
690 | fieldRef:
691 | fieldPath: status.podIP
692 | ```
693 | - В поле fieldPath мы указываем интересующие нас поля, которые мы хотим передать внутрь контейнера, это путь согласно yaml манифесту, структура обращения такая же как при вызове команды kubectl explain
694 | - Некоторые значения берутся из манифеста, некоторые подставляет kubernetes, например, те что в секции status. О каждом имеет смысл читать отдельно в документации
695 | - Аннотации (annotations) - кастомные поля для манифеста, применяются для данных, под которые еще не придумали специальных полей (к данной теме особо не относится)
696 |
697 | ## Q&A
698 | - [01:14:35](https://youtu.be/-xZ02dEF6kU?t=4475s)
699 | - Q: Для чего сделана такая реализация (подтягивание изменений из configmap/secret)? Стоит ли использовать этот механизм для обновления конфига своих приложений "налету"?
700 | - A: Собственно, для этого такая реализвация и придумана, чтобы обновлять конфиг приложений на лету. Стоит ли использовать - вопрос архитектору приложения. По мнению Сергея лучше выполнять передеплой приложения в случаях, когда изменилась конфигурация, git как единая точка правды, IaC.
701 | - [01:15:37](https://youtu.be/-xZ02dEF6kU?t=4537)
702 | - Q: Как port-forward проксирует запрос в кластере? как-то настраивает ингрес контролер?
703 | - A: Нет, не нужно никаких ингрес контроллеров, просто kubectl создаёт соединение с API сервером, остаётся запущенным в бэкграунде, слушает указанный для форварда порт, в нашем примере 8080, получает запрос, оборачивает его в HTTP запрос, передаёт его в kubernetes API, API дальше передаёт запрос это конкретному поду, получает ответ и обратно через такой вот туннель передаёт
704 |
705 | ### Возвращаемся к консоли ([01:16:39](https://youtu.be/-xZ02dEF6kU?t=4599))
706 | - Сергей примененяет деплоймент и смотрит в переменные окружения пода, демонстрирует, что переменные, начинающиеся с `__` заполнены
707 | - Далее Сергей сверяет полученные данные с теми, что можно увидеть по команде `kubectl get pod -o wide`, данная команда даёт расширенную информацию, в том числе IP адрес пода, имя ноды
708 |
709 | #### Смотрим в полученный том podinfo ([01:20:10](https://youtu.be/-xZ02dEF6kU?t=4810))
710 | - Идём внутрь пода и выводим содержимое файлов в папке /etc/podinfo:
711 | - annotations содержит различные автоматически сгенерированные данные, в частности:
712 | - Когда под был создан
713 | - Как он был создан, в нашем случае, через API
714 | - Какая применена политика безопасности, в нашем случае restricted
715 | - labels показывает метки на нашем поде, в нашем случае это `app="my-app"`, если вспомнить прошлую лекцию, по этим меткам деплоймент определяет принадлежность подов
716 | - Если в блоке по ConfigMap перематывали, стоит блок [01:21:40](https://youtu.be/-xZ02dEF6kU?t=4900) - [01:24:54](https://youtu.be/-xZ02dEF6kU?t=5094) пропустить
717 | ## Q&A
718 | - [01:24:54](https://youtu.be/-xZ02dEF6kU?t=5094)
719 | - Q: Container creating 4 минуты
720 | - A: Это не очень нормально, смотрите describe, что там происходит, почему он не может создаться
721 | - [01:25:08](https://youtu.be/-xZ02dEF6kU?t=5108)
722 | - Q: \_\_POD_NAMESPACE=default в кластере mcs
723 | - A: Не может быть такого, у учеников нет прав на данный неймспейс
724 | - [01:25:42](https://youtu.be/-xZ02dEF6kU?t=5142)
725 | - Q: По поводу того, что пока в школе идёт упор на особенности работы kubernetes, а не на use cases взаимодействия разработчиков с kubernetes
726 | - A: У нас сейчас идут лекции, которые рассчитаны на базовое знакомство с основными абстракциями kubernetes, чтобы можно было на этой основе строить дальнейшее обучение
727 | - [01:27:15](https://youtu.be/-xZ02dEF6kU?t=5235)
728 | - Q: Почему на учебном стенде нет подов в статусе pending, при увеличении реквестов и лимитов (3я лекция, добавить ссылку на конспект)?
729 | - A:
730 | - Так происходит, потому что в kubernetes есть несколько механизмов, которые позволяют ограничивать аппетиты разработчиков и не позволяют запускать больше определённого количества подов в одном неймспейсе или запрашивать под свои поды больше определённого количества ядер процессора
731 | - Кластер создавался под 14000 человек, были выставлены жесткие ограничения на неймспейсы студентов, поэтому в данном случае до создания подов даже не доходит
732 | - Об этом будет позже, механизмы называются [Resource Quotas](https://kubernetes.io/docs/concepts/policy/resource-quotas/) и [Limit Ranges](https://kubernetes.io/docs/concepts/policy/limit-range/)
733 | - Resource Quotas отвечает за общее количество ресурсов в неймспейсе, поэтому в нашем случае не доходило до создания подов, кажется, там задано 0.5 или 1 CPU на неймспейс, а мы просили сильно больше
734 | - kubectl get events replicaset позволял посмотреть события и увидеть причины, по которой поды не могли создаться
735 | - Если бы данных ограничений не было, то поды создались бы, но проблема возникла бы на следующем этапе, когда происходит поиск подходящей ноды для запуска пода, т.е.
736 | - Limit Ranges позволяет ограничивать потребление ресурсов для конкретных контейнеров в подах
737 | - Мы можем указать, например, что контейнер не может использовать больше определённого объема CPU и памяти
738 | - Также, с их помощью можно указывать значения по умолчанию для ресурсов, в манифесте которых не указаны лимиты и реквесты
739 | - [01:31:08](https://youtu.be/-xZ02dEF6kU?t=5468)
740 | - Q: Hashicorp Vault как хранилище секретов, его преимущества по сравнению со встроенным механизмом
741 | - A:
742 | - Всё хранится в зашифрованном виде
743 | - Можно использовать различные возможности Vault, такие как:
744 | - Аудит доступа
745 | - Разграничение доступа
746 | - Доступ к информации можно осуществлять различными способами
747 | - Информация хранится централизовано
748 | - Из минусов - появляется дополнительный сервис, который требует обслуживания и является дополнительной потенциальной точкой отказ, напрмер, после перезапуска Vault, всё зависящее от Vault будет простаивать, пока не будет произведён unseal
749 | - [01:31:50](https://youtu.be/-xZ02dEF6kU?t=5510)
750 | - Q: Где можно хранить сертификаты и закрытые ключи ЭЦП?
751 | - A: В сейфе у директора, в Hashicorp Vault и т.д., вопрос не особо относится к kubernetes
752 | - [01:32:08](https://youtu.be/-xZ02dEF6kU?t=5528)
753 | - Q: Как сделать динамическое изменение переменных окружения без перезапуска пода?
754 | - A: Сделать pull request в ядро линукса
755 | - [01:32:34](https://youtu.be/-xZ02dEF6kU?t=5554)
756 | - Q: Можно ли описать переменные окружения, общие для всех контейнеров в поде?
757 | - A:
758 | - Нет, они описываются отдельно для каждого контейнера
759 | - Но можно смонтировать в каждый контейнер один и тот же ConfigMap, т.е. описываем в нём все требуемые переменные окружения и с помощью envFrom подгружаем оттуда переменные окружения во все контейнеры
760 | - [01:33:08](https://youtu.be/-xZ02dEF6kU?t=5588)
761 | - Q: Почему false в кавычках
762 | - A: В данном случае мы явно указываем, что передаём строку, стоит почитать документацию по yaml
763 | - [01:33:43](https://youtu.be/-xZ02dEF6kU?t=5623)
764 | - Q: Как автоматически обновлять Deployments/StatefulSets при обновлении ConfigMap/Secrets?
765 | - A:
766 | - Никак, если вы обновляете конфигмапы руками, нужно сделать тот же самый rollout restart для деплоймента, чтобы он перезапустился
767 | - Другое дело, если манифесты лежат в каком-то шаблонизаторе, например Helm (который мы будем проходить), Kustomize, там есть возможность вычислять контрольную сумму ConfigMap и прописывать её в аннотации к деплойменту, таким образом, если config map изменился изменится annotation, произойдет rolling update. (Видимо, нужно почитать или дождаться урока про Helm, чтобы лучше понять всю механику. В чате вроде как полезный [комментарий](https://t.me/c/1540117827/21268), но мне он пока до конца не понятен)
768 | - [01:35:00](https://youtu.be/-xZ02dEF6kU?t=5700)
769 | - Q: Зачем нужен Downward API?
770 | - A:
771 | - Чтобы передать в наше приложение информацию из самого kubernetes (данные о поде, ноде, неймспейсе и т.п.)
772 | - Например, можно проверять по неймспейсу окружение, в котором запущено приложение и в зависимости от этого настраивать потребление ресурсов
773 | - Можно отправлять дополнительную информацию с помощью [jaeger](https://github.com/jaegertracing/jaeger)
774 | - [01:36:23](https://youtu.be/-xZ02dEF6kU?t=5783)
775 | - Q: Зачем добавлять содержимое файла в yaml-конфиг а не монтировать файл непосредственно
776 | - A: Так проще
777 | - [01:37:24](https://youtu.be/-xZ02dEF6kU?t=5844)
778 | - Q: Будем ли изучать Vault
779 | - A: Не будем, по волту у Слёрм либо есть, либо готовится отдельный курс
780 | - [01:37:39](https://youtu.be/-xZ02dEF6kU?t=5859)
781 | - Q: Нужно ли в ConfigMap указывать labels, если да, то для чего?
782 | - A:
783 | - Не нужно, но можно, как и у любого объекта в kubernetes
784 | - Зачем? - Чтобы можно было их выбирать по какому-то признаку
785 | - Например, созданные Helm'ом объекты автоматически получают метки, что объект был создан Helm'ом, из такого-то чарта, в такое-то время и т.п.
786 | - [01:38:10](https://youtu.be/-xZ02dEF6kU?t=5890)
787 | - Q: Что такое stateful set?
788 | - A: Альтернативный способ запуска приложений, которые хранят своё состояние, например, баз данных, по этой теме будет отдельная лекция
789 | - [01:38:58](https://youtu.be/-xZ02dEF6kU?t=5938)
790 | - Q: Дефолтные переменные окружения kubernetes_* можно не передавать в контейнер?
791 | - A: Кажется, они по умолчанию все передаются
792 | - [01:39:52](https://youtu.be/-xZ02dEF6kU?t=5992)
793 | - Q: Любые поля манифеста можно передавать в переменные?
794 | - A: Не любые, обещают скинуть ссылку на документацию, список полей достаточно ограничен
795 | - [01:40:11](https://youtu.be/-xZ02dEF6kU?t=6011)
796 | - Q: Если хочу видеть лимиты ресурсов по GPU - то нужно пробросить их в переменные среды? Или есть иной способ мониторить лимиты из описания подов?
797 | - A: Сложно, сказать, т.к. это какие-то кастомные ресурсы, нужно смотреть. Но весь раздел c реквестами и лимитами может быть проброшен в виде файлов или через переменные среды с помощью Downward API
798 | - [01:40:57](https://youtu.be/-xZ02dEF6kU?t=6057)
799 | - Q: Почему в kubernetes не рекомендуется запускать базы данных
800 | - A:
801 | - У Сергея есть отдельный доклад, также есть [выступление](https://youtu.be/7CR5eH6a8Fo) Дмитрия Столярова из Фланта
802 | - Если кратко, есть 2 проблемы:
803 | - Скорость, в основном работы с дисковыми устройствами, как правило, из-за того что хранилищем обычно выступает нечто сетевое, а не локальные SSD/NVMe накопители
804 | - Сама суть kubernetes такова, что база данных может быть убита и перезапущена в другом месте
805 | - Kubernetes не предоставляет инструментов, упрощающих настройку и администрирование кластеров БД
806 | - В общем, для продакшн баз слишком много проблем, для тестовых баз это несущественно, поэтому не возбраняется
807 | - [01:43:44](https://youtu.be/-xZ02dEF6kU?t=6224)
808 | - Q: Что произошло, когда обновляли переменную окружения (из секрета) и она стала равна пустой строке?
809 | - A:
810 | - Произошли те самые коллизии, которые происходят, когда объект сначала создан командо kubectl create, а потом применили изменения командой kubectl apply
811 | - У kubernetes возникли сомнения, что же ему делать с переменной, которая в нашем первоначальном манифесте была, а в том манифесте который мы зааплаили её не было. Не очень логично, но об этом надо знать
812 | - [01:44:21](https://youtu.be/-xZ02dEF6kU?t=6261)
813 | - Q: Как передать в nginx или flask изображение или html, неужели содержимое придётся описывать в yaml?
814 | - A: Всё просто, файлы, картинки стоит хранить в S3-совместимом хранилище, а статику, html, при сборке контейнера кладите внутрь
815 | - [01:44:44](https://youtu.be/-xZ02dEF6kU?t=6284)
816 | - Q: Зачем ConfigMap, если можно в Deployment прописать значения для переменных?
817 | - A: Для удобства, чтобы не дублировать описание повторяющихся переменных
818 | - [01:45:26](https://youtu.be/-xZ02dEF6kU?t=6326)
819 | - Q: При изменении ConfigMap Deployment обновляет переменные?
820 | - A: Проходили, не обновляет
821 | - [01:45:38](https://youtu.be/-xZ02dEF6kU?t=6338)
822 | - Q: Что делать, если не удаётся подменить файл монтированием из ConfigMap, если он уже есть в docker image, как это делать правильно?
823 | - A:
824 | - Вероятно, из-за того, что файл монтируется с использованием системы с симлинками и папками, происходит следущее:
825 | - Точка монтирования, которую вы указали, в неё прилетает не файл, а каталог и всё что было в docker image по этому пути, оно пропадает, таким образом могут пропасть необходимые для запуска файлы и приложение может работать с ошибками или не стартовать
826 | - [01:47:52](https://youtu.be/-xZ02dEF6kU?t=6472)
827 | - Q: Как правильно прокинуть бинарный файл в контейнер? Сейчас прокидываем бинарные файлы (jks) в base64, можно ли как-то иначе?
828 | - A: Ответ искать в 5й лекции
829 | - [01:48:08](https://youtu.be/-xZ02dEF6kU?t=6488)
830 | - Q: Можно ли считать StatefulSet аналогом ReplicaSet?
831 | - A: Нельзя
832 | - [01:48:20](https://youtu.be/-xZ02dEF6kU?t=6500)
833 | - Q: Версии файлов конфига для rollback - хранятся на подах и сначала монтируются или уже примонтированы и меняется симлинк на них?
834 | - A:
835 | - Версии файлов конфига для rollback нигде не хранятся, т.е., когда вы делаете rollback, происходит откат на предыдущий ReplicaSet, а конфигмапы остаются теми же самыми, старыми
836 | - Таким образом, если при откате нужно возвращать не только образы, но и настройки из конфигмапов, то деплой из yaml манифестов не подойдет, нужно использовать другие приёмы, например helm, gitOps
837 |
--------------------------------------------------------------------------------
/lesson 5.md:
--------------------------------------------------------------------------------
1 | ---
2 | date created: 2021-10-15 16:02:06 (+03:00), Friday
3 | ---
4 |
5 | # Урок 5: Хранение данных. Вечерняя школа «Kubernetes для разработчиков» [youtube](https://youtu.be/8Wk1iI8mMrw)
6 |
7 | ## Техническая пауза, опрос [00:00:20](https://youtu.be/8Wk1iI8mMrw?t=20)
8 |
9 | ## Вступление [00:04:15](https://youtu.be/8Wk1iI8mMrw?t=255)
10 | - Сергей приводит в пример новичков, которые развернув "Hello World!" приложение в kubernetes радуются его неубиваемости и пробуют добавить в деплоймент образ какого нибудь MySQL, получают 3 отдельных базы а не какой-то кластер и спрашивают, почему так происходит.
11 | - Происходит это потому, что kubernetes не знает ничего о базах данных и не умеет их "готовить"
12 | - Зато kubernetes умеет предоставлять приложению место, где оно сможет хранить свои данные
13 |
14 | ## Хранение данных [00:06:48](https://youtu.be/8Wk1iI8mMrw?t=408)
15 | - В kubernetes работает принцип good state - dead state (stateless)
16 | - Kubernetes изначально разрабатывался и предназначался для запуска микросервисов
17 | - Микросервисы, как правило, маленькие, быстрые, потребляющие мало ресурсов, их можно запускать в нужное количество реплик и вся нагрузка будет более или менее равномерно распределяться по всем репликам, отказ небольшой части реплик практически не скажется на работе сервиса, упавшие реплики будут восстановлены
18 | - С приложениями, которые хранят состояние (state), вышеописанный подход не работает в полной мере, т.к. мы будем получать уникальные реплики
19 | - Например, приложение обслуживает бэкэнд личного кабинета и хранит состояние залогинившихся пользователей, если какая-то реплика умрёт, то пользователи, которых она обслуживала, вылетят из ЛК и им будет необходимо снова залогиниться
20 | - Таким образом, хранить состояние внутри реплики - это плохая идея, есть более подходящие варианты:
21 | - Данные стоит хранить в базе данных
22 | - Данные, к которым нужен оперативный доступ стоит хранить в соответствующих решениях, например memcached, redis, tarantool и т.п.
23 | - Для файлов стоит использовать, например, S3-совместимое хранилище ([1](https://en.wikipedia.org/wiki/Amazon_S3), [2](https://habr.com/ru/post/318086/))
24 | - Хороший S3 может работать как CDN, например, вы загрузили в S3 файлик, отдали пользователю ссылку и пользователь сможет получить файл по этой ссылке прямо из S3 мимо основного бэкэнда
25 | - Если всё таки очень хочется хранить состояние в репликах, то есть несколько способов, о них ниже
26 |
27 | ### HostPath [00:09:29](https://youtu.be/8Wk1iI8mMrw?t=569)
28 | 
29 | - Начнём разбирать с тома (volume), под названием HostPath
30 | - Когда на прошлых занятиях мы работали с ConfigMap, Secrets, мы их монтировали как файлы внутрь контейнера, мы столкнулись с двумя понятиями:
31 | - volumeMounts - точки монтирования внутри контейнера
32 | - volumes - тома, которые мы монтируем в эти точки
33 | - С томами, на которых можно хранить данные, всё работает так же:
34 | - у нас будет точка монтирования, volumeMount, где мы укажем, куда монтировать наш том
35 | - в разделе volumes мы будем указывать тип тома, в нашем случае это hostPath
36 | - HostPath - аналог механизма из docker compose - мы берем каталог, который находится на ноде и монтируем этот каталог внутрь контейнера, таким образом приложение внутри контейнера получает доступ к каталогу, который находится на ноде
37 | - Вопрос в зрительский зал - насколько это хорошо с точки зрения безопасности?
38 | - Общее настроение - вариант плохой
39 | - Почему это плохой вариант?
40 | - Потому что такой механизм позволяет получить доступ к каталогам на уровне ноды, в некоторых сценариях этим могут воспользоваться злоумышленники, получившие доступ к контейнеру
41 | - В связи с этим в продакшн кластерах часто запрещают к использованию данный тип томов с помощью политик безопасности (pod security policy) или с помощью внешних валидаторов манифестов типа [gatekeeper](https://kubernetes.io/blog/2019/08/06/opa-gatekeeper-policy-and-governance-for-kubernetes/), [kubeval](https://kubeval.instrumenta.dev/)
42 |
43 | #### Посмотрим, как это выглядит в yaml манифестах [00:12:25](https://youtu.be/8Wk1iI8mMrw?t=745)
44 | - работаем в каталоге `~/school-dev-k8s/practice/5.saving-data/1.hostpath`
45 | - смотрим содержимое манифеста деплоймента
46 | ```yaml
47 | # deployment.yaml
48 | ---
49 | apiVersion: apps/v1
50 | kind: Deployment
51 | metadata:
52 | name: my-deployment
53 | spec:
54 | replicas: 1
55 | selector:
56 | matchLabels:
57 | app: my-app
58 | strategy:
59 | rollingUpdate:
60 | maxSurge: 1
61 | maxUnavailable: 1
62 | type: RollingUpdate
63 | template:
64 | metadata:
65 | labels:
66 | app: my-app
67 | spec:
68 | containers:
69 | - image: quay.io/testing-farm/nginx:1.12
70 | name: nginx
71 | ports:
72 | - containerPort: 80
73 | resources:
74 | requests:
75 | cpu: 10m
76 | memory: 100Mi
77 | limits:
78 | cpu: 100m
79 | memory: 100Mi
80 | volumeMounts: # раздел для указание точек монтирования
81 | - name: data # имя тома
82 | mountPath: /files # путь к точке монтирования (внутри пода)
83 | volumes: # перечисление томов, которые будут смонитированы
84 | - name: data # имя тома
85 | hostPath: # тип тома
86 | path: /data_pod # путь к каталогу (внутри узла)
87 | ...
88 | ```
89 | - применяем деплоймент
90 | ```shell
91 | $ kubectl apply -f deployment.yaml
92 |
93 | deployment.apps/my-deployment created
94 |
95 | $ kubectl get pod
96 |
97 | No resources found in s024713 namespace.
98 |
99 | $ kubectl get all
100 |
101 | NAME READY UP-TO-DATE AVAILABLE AGE
102 | deployment.apps/my-deployment 0/1 0 0 64s
103 |
104 | NAME DESIRED CURRENT READY AGE
105 | replicaset.apps/my-deployment-f9c7845d9 1 0 0 64s
106 | ```
107 | - видим, что что-то пошло не так - деплоймент создан, поды не появились, репликасет не перешёл в статус READY
108 | - разбираемся
109 | ```shell
110 | $ kubectl describe deployments.apps my-deployment
111 |
112 | # часть вывода скрыта
113 | # видим, что есть проблемы, но что именно не так, не понятно
114 | Conditions:
115 | Type Status Reason
116 | ---- ------ ------
117 | Progressing True NewReplicaSetCreated
118 | Available True MinimumReplicasAvailable
119 | ReplicaFailure True FailedCreate
120 | OldReplicaSets:
121 | NewReplicaSet: my-deployment-f9c7845d9 (0/1 replicas created)
122 | Events:
123 | Type Reason Age From Message
124 | ---- ------ ---- ---- -------
125 | Normal ScalingReplicaSet 4m10s deployment-controller Scaled up replica set my-deployment-f9c7845d9 to 1
126 |
127 | $kubectl describe replicasets.apps my-deployment-f9c7845d9
128 |
129 | # часть вывода скрыта
130 | # видим сообщение от replicaset-controller о том что hostPath запрещён к использованию посредством PodSecurityPolicy
131 | Conditions:
132 | Type Status Reason
133 | ---- ------ ------
134 | ReplicaFailure True FailedCreate
135 | Events:
136 | Type Reason Age From Message
137 | ---- ------ ---- ---- -------
138 | Warning FailedCreate 3m43s (x17 over 9m14s) replicaset-controller Error creating: pods "my-deployment-f9c7845d9-" is forbidden: PodSecurityPolicy: unable to admit pod: [spec.volumes[0]: Invalid value: "hostPath": hostPath volumes are not allowed to be used]
139 | ```
140 |
141 | #### Q&A
142 | - [01:16:23](https://youtu.be/8Wk1iI8mMrw?t=983)
143 | - Q: На какой ноде будет лежать каталог HostPath
144 | - A: Будет происходить попытка смонтировать каталог с той ноды, где запущен под
145 | - [01:16:42](https://youtu.be/8Wk1iI8mMrw?t=1002)
146 | - Q: Где управлять PodSecurityPolicy?
147 | - A: Рассмотрим позже, это отдельная трехчасовая лекция, эта тема есть в курсе "Мега", но здесь тоже будет обзорная лекция
148 | - [01:17:03](https://youtu.be/8Wk1iI8mMrw?t=1023)
149 | - Q: По умолчанию все политики открыты?
150 | - A: Да, по умолчанию никаких psp не включено в kubernetes, их нужно специально включать для того чтобы можно было применять какие либо ограничения
151 | - [00:17:12](https://youtu.be/8Wk1iI8mMrw?t=1032)
152 | - Q: Кажется, это ответ на эту ссылку ([PodSecurityPolicy Deprecation: Past, Present, and Future](https://kubernetes.io/blog/2021/04/06/podsecuritypolicy-deprecation-past-present-and-future/)), но она была воспринята как информация о прекращении поддержки hostPath
153 | - A: Такой тип в deprecated перейти не может, потому что он нужен для системных компонентов kubernetes, чтобы они запускались. Там решается проблема курицы и яйца, Мюнгхаузена, который вытаскивает себя из болота и т.д., грубо говоря, ему (кому?) нужно запуститься, пока еще не все компоненты запущены, поэтому приходится использовать hostPath для таких решений
154 |
155 | ### [EmptyDir](https://kubernetes.io/docs/concepts/storage/volumes/#emptydir) [00:17:47](https://youtu.be/8Wk1iI8mMrw?t=1067)
156 | 
157 | - Еще один вариант тома, т.н. [Ephemeral](https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/)
158 | - В перводе с английского - пустой каталог
159 | - Создаёт временный диск и монтирует его внутрь контейнера
160 | - Т.е., это не заранее обозначенный каталог на ноде, а специальный каталог, создаваемый посредством container runtime interface, который будет использоваться в нашем контейнере всё время, пока живёт под
161 | - После того, как под закончит свою работу (его выключат, обновят и т.п.), emptyDir будет удалён вместе с подом
162 | - Таким образом, данные хранимые в emptyDir живут столько же, сколько и под, удалён под - удалены данные
163 | - Если контейнер внутри пода упадёт, сохранность данных это не затронет
164 | - Можно провести аналогию emptyDir со стандартным docker volume, при условии, что в манифесте docker compose мы не указываем, какой каталог монтировать, но указываем имя, будет создан volume, с тем отличием, что emptyDir будет удален при завершении работы пода
165 |
166 | #### Зачем нужен EmptyDir? [00:19:23](https://youtu.be/8Wk1iI8mMrw?t=1163)
167 | - emptyDir применяется для работы с данными, которые не имеет смысл хранить постоянно, например:
168 | - для временных баз данных, например для автотестов
169 | - при тестировании приложений и сервисов, требующих работы с диском
170 |
171 | ##### Пробуем применить EmptyDir в учебном кластере [00:20:50](https://youtu.be/8Wk1iI8mMrw?t=1250)
172 | - Переходим в каталог `~/school-dev-k8s/practice/5.saving-data/2.emptydir`, видим манифест:
173 | ```yaml
174 | # deployment.yaml
175 | ---
176 | apiVersion: apps/v1
177 | kind: Deployment
178 | metadata:
179 | name: my-deployment
180 | spec:
181 | replicas: 1
182 | selector:
183 | matchLabels:
184 | app: my-app
185 | strategy:
186 | rollingUpdate:
187 | maxSurge: 1
188 | maxUnavailable: 1
189 | type: RollingUpdate
190 | template:
191 | metadata:
192 | labels:
193 | app: my-app
194 | spec:
195 | containers:
196 | - image: quay.io/testing-farm/nginx:1.12
197 | name: nginx
198 | ports:
199 | - containerPort: 80
200 | resources:
201 | requests:
202 | cpu: 10m
203 | memory: 100Mi
204 | limits:
205 | cpu: 100m
206 | memory: 100Mi
207 | volumeMounts:
208 | - name: data
209 | mountPath: /files
210 | volumes: # перечисление томов, которые будут смонитированы
211 | - name: data # имя тома
212 | emptyDir: {} # тип тома и пустой словарь в качестве значения, чтобы манифест прошел валидацию
213 | ...
214 | ```
215 | - Видим знакомую картину, в разделе volumes подключаем том типа emptyDir
216 | - Фигурные скобки в качестве значения переданы для того чтобы манифест прошел валидацию (непонятно только чью, попробовал убрать их, ошибок не встретил, подставил вместо них `~` - аналогично)
217 | - Далее Сергей применяет манифест и демонстрирует, что мы можем писать в каталог /files, а также что после перезахода в контейнер, он остаётся на месте
218 |
219 | #### Q&A
220 | - [00:24:43](https://youtu.be/8Wk1iI8mMrw?t=1483)
221 | - Q: Ограничения на размер emptyDir?
222 | - A: Такие же как на том, где фактически расположен emptyDir, обычно это каталог `/var/lib/kubelet/pods/_POD_ID_/volumes/kubernetes.io~empty-dir` или оперативная память
223 | - [00:25:18](https://youtu.be/8Wk1iI8mMrw?t=1518)
224 | - Q: Можно ли создать emptyDir для нескольких подов?
225 | - A: Можно, но для каждого пода это будет отдельный emptyDir
226 | - [00:25:30](https://youtu.be/8Wk1iI8mMrw?t=1530)
227 | - Q: А если apply - тоже пропадёт emptyDir?
228 | - A: Если произошли изменения, которые привели к созданию новых подов, то, как следствие, emptyDir пропадёт
229 | - [00:25:57](https://youtu.be/8Wk1iI8mMrw?t=1557)
230 | - Q: Какой смысл использовать emptyDir, если можно положить данные в любую папку в контейнере и они так же не сохранятся при удалении пода
231 | - A:
232 | - В связи со "слоёной" системой устройства хранилища в докер контейнере мы имеем большой оверхед по производительности, поэтому для работы используют механизм монтирования томов
233 | - Коллега подсказывают, что для обмена данными между разными контейнерами внутри одного пода тоже могут применяться emptyDir
234 |
235 | ### PV/PVC/Storage class [00:27:32](https://youtu.be/8Wk1iI8mMrw?t=1652)
236 | - Если кратко - более современные абстракции для работы с томами, подробнее в видео
237 | - [Документация по Persistent Volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/)
238 |
239 | #### PVC, persistentVolumeClaim [00:29:20](https://youtu.be/8Wk1iI8mMrw?t=1760)
240 | - Это наша заявка на то, какой диск нам нужен [00:33:15](https://youtu.be/8Wk1iI8mMrw?t=1995)
241 | ```yaml
242 | # пример описания подключения такого тома к поду
243 | volumes: # раздел объявления томов
244 | - name: mypd # задаём имя
245 | persistentVolumeClaim: # указываем тип тома
246 | claimName: myclaim # название клэйма
247 | ```
248 | - [00:29:43](https://youtu.be/8Wk1iI8mMrw?t=1783) - как это всё устроено
249 | - [00:30:31](https://youtu.be/8Wk1iI8mMrw?t=1831) - обзор типов доступа к диску, то же в [документации](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes)
250 |
251 | #### [Storage class](https://kubernetes.io/docs/concepts/storage/storage-classes/) [00:31:24](https://youtu.be/8Wk1iI8mMrw?t=1884)
252 | - В kubernetes для хранения данных используются внешние системы хранения данных, такие как:
253 | - [Ceph](https://ceph.io/en/)
254 | - [Gluster](https://www.gluster.org/)
255 | - [LINSTOR](https://linbit.com/linstor/)
256 | - Различные аппаратные решения
257 | - Облачные решения - gcp, aws
258 | - Storage class - это третий параметр, который учитывается в PVC
259 | - В storage class мы можем описать подключение к таким системам и указать данные для подключения, такие как:
260 | - адреса
261 | - логины/пароли/токены
262 | - различные другие настройки для взаимодействия с СХД
263 |
264 | #### Persistent Volume [00:33:24](https://youtu.be/8Wk1iI8mMrw?t=2004)
265 | - Абстракция, которая создаётся и в которой записывается информация о том диске, который был выдан нашему приложению, в PVC заявка, в PV хранится информация о том, какой диск в СХД был выдан нашему приложению, как правило, там ID диска хранится, или что-то подобное, например адрес. Storage class - это информация для kubernetes для получения диска, адрес API, креды и т.п.
266 | - Откуда берутся PV? [00:34:47](https://youtu.be/8Wk1iI8mMrw?t=2087)
267 | - Самый простой вариант - системный администратор СХД руками создаёт диски и с данными этих дисков создаёт манифесты PV в kubernetes. Таким образом формируется список свободных PV в kubernetes, который описывает какие-то диски в СХД
268 | - Второй вариант - это использование PV Provisioner (см. ниже)
269 |
270 | #### Пример монтирования PV в под [00:35:29](https://youtu.be/8Wk1iI8mMrw?t=2129)
271 | - Имеем набор PV:
272 | - NFS PV 1 50GB
273 | - NFS PV 2 100GB
274 | - RBD PV 3 100GB (3)
275 | - PVС на диск в 50GB, storageClass'а NFS
276 | - Pod, с описанием тома типа PVC с именем, указанным в предыдущем пункте
277 |
278 | - Что произойдёт при применении манифестов:
279 | - PVC отработает и подключит к ноде диск в 50GB, переведя это PV в состояние BOUND, связанный с нашим PVC
280 | - К поду будет подключен наш PV
281 | - Грубо говоря, СХД подключается к диску ноды, где запущен наш под, данные прокидываются в контейнер, приложение начинает работать, kubernetes у себя записывает, что первый том (NFS PV 1 50GB) был занят (BOUND) таким-то подом, после этого его никто не сможет использовать, приложение его заняло, с ним работают, PV имеет статус BOUND
282 |
283 | - К исходным данным добавляется еще один PVС на диск в 50GB, storageClass'а NFS и запрашивающий его под
284 | - После применения манифеста будет выдан PV 2, т.к. он удовлетворяет нашему запросу, не важно что он больше чем нужно
285 | - Происходит аналогичная история, PV 2 становится BOUND, диск подключается по той же схеме к другому поду
286 | - Из 100GB места на PV 50GB места будут незадействованы
287 | - Такая ситуация типична, бывают случаи, когда на запросы небольшие, а PV нарезаны неудачным образом и на PVC небольшого объема выдаётся огромный PV. Во избежание таких ситуаций используют механизм PV Provisioner
288 | 
289 |
290 | #### PV Provisioner [00:39:09](https://youtu.be/8Wk1iI8mMrw?t=2349)
291 | - Если у нас нет свободных PV, но есть PVC, они будут висеть в состоянии PENDING, т.е. ждать, пока появятся PV, которые удовлетворяют запросу (PVC) - пока проснётся админ, нарежет СХД на PV, напишет и применит манифесты для подключения PV в kubernetes и только после этого приложение сможет запуститься
292 | - Чтобы не заниматься ручной нарезкой хранилищ на PV, придумали PV Provisioner
293 | - Это такая программа, которая ходит в облако или СХД, нарезает PV и создаёт нужные манифесты, причем диски создаются в соответствии запросам PVC
294 | - Таким образом мы получаем полную автоматизацию и целевое расходование дискового пространства
295 |
296 | #### Манифесты, с которыми будем работать
297 | - Всё в каталоге `~/school-dev-k8s/practice/5.saving-data/3.pvc`
298 |
299 | ##### PVC [00:41:27](https://youtu.be/8Wk1iI8mMrw?t=2487)
300 | ```yaml
301 | # pvc.yaml
302 | ---
303 | kind: PersistentVolumeClaim
304 | apiVersion: v1
305 | metadata:
306 | name: fileshare # Имя PVC
307 | spec:
308 | storageClassName: csi-ceph-hdd-ms1 # Имя класса, оно формируется отдельно администратором
309 | accessModes:
310 | - ReadWriteMany # Режим работы с диском, ниже будет об этом
311 | resources:
312 | requests:
313 | storage: 10Mi # Запрашиваем диск размером в 10 мегабайт (если точнее - мебибайт, см. ниже в вопросах)
314 | ```
315 | - storageClassName можно не указывать, если в кластере создан и задан storageClass по умолчанию
316 |
317 | ##### ConfigMap [00:43:42](https://youtu.be/8Wk1iI8mMrw?t=2622)
318 | ```yaml
319 | # configmap.yaml
320 | ---
321 | apiVersion: v1
322 | kind: ConfigMap
323 | metadata:
324 | name: fileshare
325 | data:
326 | default.conf: |
327 | server {
328 | listen 80 default_server;
329 | server_name _;
330 | default_type text/plain;
331 | location / {
332 | return 200 '$hostname\n';
333 | }
334 | location /files {
335 | alias /data;
336 | autoindex on;
337 | client_body_temp_path /tmp;
338 | dav_methods PUT DELETE MKCOL COPY MOVE;
339 | create_full_put_path on;
340 | dav_access user:rw group:rw all:r;
341 | }
342 | }
343 | ```
344 |
345 | ##### Deployment [00:44:16](https://youtu.be/8Wk1iI8mMrw?t=2656)
346 | ```yaml
347 | # deployment.yaml
348 | ---
349 | apiVersion: apps/v1
350 | kind: Deployment
351 | metadata:
352 | name: fileshare
353 | spec:
354 | replicas: 2
355 | selector:
356 | matchLabels:
357 | app: fileshare
358 | strategy:
359 | rollingUpdate:
360 | maxSurge: 1
361 | maxUnavailable: 1
362 | type: RollingUpdate
363 | template:
364 | metadata:
365 | labels:
366 | app: fileshare
367 | spec:
368 | initContainers:
369 | - image: busybox
370 | name: mount-permissions-fix
371 | command: ["sh", "-c", "chmod 777 /data"]
372 | volumeMounts:
373 | - name: data
374 | mountPath: /data
375 | containers:
376 | - image: centosadmin/reloadable-nginx:1.12
377 | name: nginx
378 | ports:
379 | - containerPort: 80
380 | resources:
381 | requests:
382 | cpu: 10m
383 | memory: 100Mi
384 | limits:
385 | cpu: 100m
386 | memory: 100Mi
387 | volumeMounts:
388 | - name: config
389 | mountPath: /etc/nginx/conf.d
390 | - name: data
391 | mountPath: /data
392 | volumes:
393 | - name: config
394 | configMap:
395 | name: fileshare
396 | - name: data
397 | persistentVolumeClaim:
398 | claimName: fileshare
399 | ```
400 | - Немного грустного - в учебном кластере воспроизвести эксперименты с данными манифестами не получится [00:45:52](https://youtu.be/8Wk1iI8mMrw?t=2752)
401 |
402 | ##### Работаем в консоли
403 | - Применяем манифест PVC [00:49:17](https://youtu.be/8Wk1iI8mMrw?t=2957)
404 | 
405 | - Видим что создался PVC с именем fileshare, с состоянием Bound
406 | - Иногда состояние может быть Pending, т.к. монтирования диска происходит не мгновенно, иногда это может занимать несколько секунд, много зависит от скорости СХД
407 | - В столбце VOLUME мы видим имя объекта PVC, созданного провижинером под нас
408 | - CAPACITY (размер) - 1Gi, хотя мы просили 10Mi, это зависит от настроек провижинера, видимо в нашем случае минимальный размер составляет 1Gi
409 | - Смотрим вывод k get pv [00:50:50](https://youtu.be/8Wk1iI8mMrw?t=3050)
410 | ```shell
411 | NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
412 | pvc-3fc6a2fa-46df-4134-8b6e-3e93ea8413b7 10Gi RWX Delete Bound s000001/fileshare csi-ceph-hdd-ms1 4d2h
413 | pvc-59a16cc0-d96c-4760-afa3-1c12e255743c 1Gi RWO Delete Bound prometheus-monitoring/alertmana~ csi-ceph-hdd-ms1 15d
414 | pvc-d1a0fad4-a977-4160-a4c3-1de9d264dd18 1Gi RWO Delete Bound prometheus-monitoring/kube-prom~ csi-ceph-hdd-ms1 15d
415 | pvc-daf05a58-57cd-432b-874d-58dd2c86173e 2Gi RWX Retain Released s022208/csi-ceph-hdd-dp1-claim csi-ceph-hdd-dp1-retain 16d
416 | pvc-e87abf3f-53bd-415a-a8c9-c6ea6f6f7901 2Gi RWX Retain Released s022208/csi-ceph-hdd-dp1-claim csi-ceph-hdd-dp1-retain 16d
417 | pvc-ece71893-ead4-4ec1-a10d-a65fdf52b87f 50Gi RWO Delete Bound prometheus-monitoring/prometheu~ csi-ceph-ssd-ms1 15d
418 | ```
419 | - Видим все PV в учебном кластере
420 | - Claim s000001/fileshare соответстует нашему манифесту PVC
421 | - STATUS Released означает, что PV больше не задействован PVC
422 | - Что будет с данными, если мы удалим под? Сейчас узнаем, но для этого нужно его создать
423 | - [00:52:37](https://youtu.be/8Wk1iI8mMrw?t=3157) Сергей применяет все манифесты в каталоге и демонстирует, что PVC остался без изменений
424 | - Далее Сергей в ожидании окончания деплоя выводит различную информацию и комментирует это, в основном, ссылаясь на то, что мы рассмотрим позже
425 | - Дождались, видим что у нас развернулся деплоймент fileshare и один из подов успешно стартовал, а второй нет
426 | 
427 | - Сергей говорит о том, что согласно манифесту мы можем удалять под, но данные хранятся на PV и не потеряются пока существует соответствующий объект PVC, любой под может подключить этот PV, на который ссылается PVC и работать с этими данными
428 | - [00:55:06](https://youtu.be/8Wk1iI8mMrw?t=3306) Что будет c PV если мы удалим PVC - это зависит от политики удаления (RECLAIMPOLICY), это указывается в storage class:
429 | - Retain - оставить как есть, если сделать запрос `k get pv` мы увидим этот диск в состоянии Released
430 | - Delete - удалить вместе с pvc
431 | - [00:58:03](https://youtu.be/8Wk1iI8mMrw?t=3483) Если удалить PVС, то данные остаются непосредственно на СХД, их можно вытащить уже оттуда
432 | - Если мы уже удалили PVC, но RECLAIMPOLICY у нас Retain, мы можем отредактировать PV через `kubectl edit` и сменить Released на Available, тогда он снова будет доступен для подключения, после этого нужно создать PVC с запросом подключения этого конкретного PV
433 | - Смотрим на наши storage classes
434 | ```shell
435 | $ k get sc
436 | NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
437 | csi-ceph-hdd-dp1 cinder.csi.openstack.org Delete Immediate true 19d
438 | csi-ceph-hdd-dp1-retain cinder.csi.openstack.org Retain Immediate true 19d
439 | csi-ceph-hdd-ms1 cinder.csi.openstack.org Delete Immediate true 19d
440 | csi-ceph-hdd-ms1-retain cinder.csi.openstack.org Retain Immediate true 19d
441 | csi-ceph-ssd-dp1 cinder.csi.openstack.org Delete Immediate true 19d
442 | csi-ceph-ssd-dp1-retain cinder.csi.openstack.org Retain Immediate true 19d
443 | csi-ceph-ssd-ms1 cinder.csi.openstack.org Delete Immediate true 19d
444 | csi-ceph-ssd-ms1-retain cinder.csi.openstack.org Retain Immediate true 19d
445 | csi-high-iops-dp1 cinder.csi.openstack.org Delete Immediate true 19d
446 | csi-high-iops-dp1-retain cinder.csi.openstack.org Retain Immediate true 19d
447 | csi-high-iops-ms1 cinder.csi.openstack.org Delete Immediate true 19d
448 | csi-high-iops-ms1-retain cinder.csi.openstack.org Retain Immediate true 19d
449 | ```
450 | - По поводу таблицы вывода storage classes [00:59:04](https://youtu.be/8Wk1iI8mMrw?t=3544)
451 | - Речь идёт об учебном кластере, но общие принципы везде одни и те же
452 | - Суффиксы dp1 и ms1 отвечают за зону доступности, территориальное расположение, в каждом отдельном ДЦ есть своя СХД, обслуживающая этот ДЦ, т.е. узлы из определённого ДЦ будут иметь приоритет на работу с СХД в этом же ДЦ, т.к. это будет существенно быстрее
453 | - Подключение к СХД в другом ДЦ будет существенно медленнее, за счёт административных или технических ограничений может быть невозможно в принципе (в AWS реализовано такое ограничение)
454 | - hdd, ssd, high-iops - суффиксы, отвечающие за тип и/или производительность выделенных блоков дисков в СХД
455 | - стоит учитывать не только о производительность, но и о стоимость работы с такими хранилищами
456 | - PROVISIONER в даном случае используется от openstack
457 | - Провижинеры бывают встроенные, а бывают внешние, в выводе мы видим один из внешних
458 | - [01:03:00](https://youtu.be/8Wk1iI8mMrw?t=3780) Последнее время активно внедряется технология CSI - container storage interface, в будущем планируется переход CSI и отказ от встроенных в kubernetes провижинеров
459 | - С помощью CSI можно делать с томами разные вещи, например, менять размеры дисков
460 | - [01:02:31](https://youtu.be/8Wk1iI8mMrw?t=3751) Идёт рассказ о концепции CSI
461 | - [01:03:31](https://youtu.be/8Wk1iI8mMrw?t=3811) Сергей показывает как увеличить размер подключённого PV
462 | - Редактируем `k edit pvc ` PVC и указываем желаемый размер, в нашем случае 10Mi=>10Gi
463 | ```yaml
464 | apiVersion: v1
465 | kind: PersistentVolumeClaim
466 | metadata:
467 | ...
468 | spec:
469 | accesModes:
470 | - ReadWriteMany
471 | resources:
472 | requests:
473 | storage: 10Gi # производим замену тут
474 | ...
475 | ```
476 | - После этого `k get pvc` показывает, что размер PVC остался старый, а `k get pv`, что размер PV увеличился
477 | - Если посмотреть в describe PVC `k describe pvc `, то в событиях сообщение от провижинера о том, что требуется resize файловой системы на ноде, наш CSI не умеет на лету менять размер файловой системы
478 | - В нашем случае достаточно пересоздать под, использующий данный PVC
479 | - Сергей убивает под в состоянии Running и ждёт пока поднимется новый
480 | - Новый долго не поднимается, Сергей идет в describe пода и видит в событиях, что resize успешно выполнен
481 | - Вывод `k get pvc` теперь показывает новый размер диска
482 | - Далее мы заходим внутрь пода через `k exec --bash`, наблюдаем вывод `df -h` и видим наш диск нужного размера
483 | - [01:08:27](https://youtu.be/8Wk1iI8mMrw?t=4107) Некоторые системы позволяют делать подобное на лету, зависит от СХД и CSI для неё
484 | - [01:08:38](https://youtu.be/8Wk1iI8mMrw?t=4118) Уменьшить размер диска, к сожалению, нельзя
485 |
486 | ### initContainers [01:09:07](https://youtu.be/8Wk1iI8mMrw?t=4147)
487 | - Обычный контейнер, который может запускаться перед запуском основного приложения, обычно его применяют для каких-то доп. настроек
488 | - В примере выше
489 | - запускался контейнер busybox и в нём выполнялся скрипт по установке прав 777 на каталог /data
490 | - также в контейнер монтировался том с именем data в точку монтирования /data
491 | - data - это том, полученный из pvc fileshare
492 | - в нашем случае это нужно потому что процесс nginx в основном контейнере работает под ограниченным пользователем, а когда мы создаём файловую систему, владельцем являет root, поэтому, нам нужно от имени root поменять права таким образом, чтобы процесс nginx мог писать в данный каталог
493 | - таких контейнером может быть несколько, они выполняются по порядку описания в манифесте
494 | - можно монтировать те же тома, что в основном контейнере
495 | - пример - загрузка дампа БД из бэкапа
496 | - можно запускать процессы от root, как в нашем примере, чтобы приложения в основных контейнерах работали от ограниченного пользователя, т.н. [Rootless mode](https://medium.com/@k8spin/rootless-containers-on-kubernetes-part-1-eca737fa7a81), это применяется для уменьшения потенциальной площади атаки в случае эксплуатации различных уязвимостей в контейнере
497 | - После выполнения действия такие контейнеры останавливаются
498 |
499 | #### Q&A
500 | - [01:15:16](https://youtu.be/8Wk1iI8mMrw?t=4516)
501 | - Q: Почему в манифестах kubernetes принято писать МБ как Mi а ГБ как Gi?
502 | - A: Можно сходить в википедию, там об этом прекрасно написано, если грубо, приставка мега означает 10^2, а Mi - Меби - 2^10, т.е. те самые мегабайты, привычные нам из информатики
503 | - [01:16:01](https://youtu.be/8Wk1iI8mMrw?t=4561)
504 | - Q: initContainer только для модификации данных на дисках?
505 | - A: initContainer можно использовать для чего угодно, например для получения настроек из сервера конфигураций, волта того же и подсовывания их вашему приложению
506 | - [01:16:21](https://youtu.be/8Wk1iI8mMrw?t=4581)
507 | - Q: exec -it \[pod_name] \-- bash //это мы зашли в контейнер в поде, а если у меня в поде больше 1го контейнера то как мне попасть в нужный?
508 | - A: Для этого есть ключ -c и нужно указать имя контейнера
509 | - [01:16:41](https://youtu.be/8Wk1iI8mMrw?t=4601)
510 | - Q: Можно ли использовать PV для сохранения данных базы данных сервиса в pod? Таким образом если pod со своей базой данных удалился/упал, то сохранятся данные базы данных в PV?
511 | - A: Да, конечно, для подобных сценариев всё и предназначено
512 | - [01:17:08](https://youtu.be/8Wk1iI8mMrw?t=4628)
513 | - Q: initContainers выполняет действие и останавливается (и висит потушенным) или удаляется?
514 | - A: Да, удаляется (судя во всему, с помощью механизма [Garbage Collection](https://kubernetes.io/docs/concepts/architecture/garbage-collection/#containers-images))
515 | - [01:17:19](https://youtu.be/8Wk1iI8mMrw?t=4639)
516 | - Q: Стоит ли рассматривать init container как 1 команду?
517 | - A: Не стоит, лучше рассматривать его как контейнер, в котором что-то выполняется
518 | - [01:17:47](https://youtu.be/8Wk1iI8mMrw?t=4667)
519 | - Q: initContainer может модифицировать основной контейнер?
520 | - A:
521 | - В теории можно этого добиться, например, обратиться к системе управления контейнерами из initContainer, но никто так не делает
522 | - Это не модификация контейнера, но возможно подразумавалось что-то подобное - можно, например, подключить emptyDir к initContainer, наполнить его данными и затем подключить к основному контейнеру, ну и вариации на эту тему, как пример выше с настройкой прав доступа для дальнейшей работы nginx
523 | - [01:18:50](https://youtu.be/8Wk1iI8mMrw?t=4730)
524 | - Q: Как система делает так, чтобы при пересоздании Pod'a был подключен именно тот PV, который использовался раньше?
525 | - A: В поде указывается claimName для pvc, а pvc соответствует определённому тому, диску на СХД
526 |
527 |
528 | #### Разбираемся, почему не стартовал один из подов [01:19:57](https://youtu.be/8Wk1iI8mMrw?t=4797)
529 | - [01:20:08](https://youtu.be/8Wk1iI8mMrw?t=4808) Если в `k get pod` мы видим в STATUS что-то типа Init:X/Y - это означает, что из Y initContainers успешно отработали X.
530 | - Таким образом можно понимать, на каком этапе находится запуск приложения
531 | - [01:20:36](https://youtu.be/8Wk1iI8mMrw?t=4836) Смотрим describe, видим различные ошибки, ключевые звучат как "Invalid input received: Invalid volume: Volume status must be available to reserve, but status is in-use. (HTTP 400)" и "timed out waiting for condition"
532 | - Это говорит нам о том, что статус диска не тот который нужн и мы не дождались нужного состояния
533 | - Смотрим вывод `k get pvc fileshare -o yaml`, вспоминаем, что у нас указан accessModes: ReadWriteMany, а также, что мы берём диск определённого storageClass
534 | - Проблема в том, что СХД, которая описана в этом storageClass, не позволяет создавать диски, которые могут работать одновременно с несколькими узлами кластера, доступно только одно подключение
535 | - Таким образом мы понимаем, что первый под запустился и захватил диск, а второй под не может этого сделать
536 |
537 | ### ReadWriteMany [01:23:35](https://youtu.be/8Wk1iI8mMrw?t=5015)
538 | - А теперь про те СХД, которые в таком режиме работать умеют
539 | - Для примера берём:
540 | - Кластер kubernetes из 3х нод
541 | - СХД на основе cephFS, это кластерная файловая система, которая как раз умеет обеспечивать множественный доступ к своим ресурсам, т.е. мы можем подключить её одновременно на 3 наших узла
542 | - Приложение, которое будет запускаться в 3 реплики, по одной на каждую ноду
543 | - Как всё в нашем примере должно работать:
544 | - Наше приложение будет использовать диск, который хранится на нашем СХД
545 | - СХД подключается ко всем нодам
546 | - kubernetes прокидывает точки монтирования с узлов в контейнеры наших реплик приложения
547 | - Таким образом, каждый контейнер подключается не напрямую к СХД, а работает с точкой монтирования на своём узле, а подключением дисков занимается та часть kubernetes, которая работает на каждом узле нашего кластера, которая собственно и отвечает за запуск контейнеров, их работоспособность и т.д.
548 |
549 | ### ReadWriteOnce [01:25:34](https://youtu.be/8Wk1iI8mMrw?t=5134)
550 | - Когда нам нужен диск данного типа либо система хранение не поддерживает ReadWriteMany
551 | - Для примера берём
552 | - Кластер kubernetes из 3х нод, где на 2й ноде запущено 2 реплики приложения
553 | - СХД на основе ceph, но отдающая блочные устройства RBD, по факту диски
554 | - Такой диск можно смонтировать только на один узел, в у нас это будет узел №2
555 | - Если попытаться смонтировать этот диск на другие узлы, мы получим ошибки, подобные тем, что видели раньше
556 | - Но те 2 реплики, которые запущены на 2м узле, смогут без проблем работать с нашим диском, т.к. они просто используют точку монтирования на узле
557 | - Это всё работает без проблем, потому что с точки зрения ОС это 2 процесса, которые работают под управлением одного ядра и соответственно, они просто используют диск, подключенный к нашему серверу, никаких кластерных ухищрений в данном случае не происходит
558 | - Недавно в kubernetes добавили [ReadWriteOncePod](https://kubernetes.io/blog/2021/09/13/read-write-once-pod-access-mode-alpha/), который запрещает совместный доступ из разных подов к точке монтирования
559 |
560 | ## Заключение [01:29:00](https://youtu.be/8Wk1iI8mMrw?t=5340)
561 | - На этом всё, что касалось хранения данных в kubernetes
562 | - По факту, kubernetes никакие данные не хранит, но он может подключать внешние СХД к своим узлам и точки монтирования этих устройств прокидывать в контейнеры
563 | - Еще одна распространенная ошибка, когда говорят "я себе поставил кластер kubernetes с помощью kubeadm, kubespray и у меня не запускается манифест из гитхаба, в котором PVC создаётся" - естественно, он не запустится, т.к. в свежем кластере не настроены СХД, не настроены storageClasses, которые описывают доступ к этим СХД, люди почему-то думают, что при установке kubernetes автоматически будет организована какая-то СХД волшебным образом из воздуха
564 |
565 | ## Минутка рекламы вводного курса по Apache Kafka [01:30:09](https://youtu.be/8Wk1iI8mMrw?t=5409)
566 |
567 | ## Q&A [01:30:52](https://youtu.be/8Wk1iI8mMrw?t=5452)
568 | - [01:31:28](https://youtu.be/8Wk1iI8mMrw?t=5488)
569 | - Q: Политики безопасности (для hostPath) прописаны вами или уже присутствуют в kubernetes
570 | - A: Нами, по умолчанию ничего не прописано, можно сказать, что присутствует дыра в безопасности
571 | - [01:31:49](https://youtu.be/8Wk1iI8mMrw?t=5509)
572 | - Q: Что делать, если приложение течёт по памяти за лимиты, а под не убивается, где почитать подробнее?
573 | - A:
574 | - Ни разу не было такого
575 | - Если мы указываем для контейнера определённый лимит, то как только приложение попытается запросить больше, то придёт OOM killer и убъет этот процесс
576 | - Есть 2 варианта прихода OOM killer
577 | - когда кончается общая память на узле, killer приходит к самому невезучему процессу, у которого самый низкий score, который был запущен совсем недавно и убивает его, а затем ищет следующих претендентов
578 | - если лимит в контейнере, то есть, по факту установлены cgroup лимиты для процесса и процесс исчерпал свой лимит, то убивается такой процесс в контейнере, а kubernetes видит, что процесс в контейнере был убит и перезапускает контейнер
579 | - Возможно, ситуация такая, что вы не замечаете, что контейнер перезапускался
580 | - [01:33:46](https://youtu.be/8Wk1iI8mMrw?t=5626)
581 | - Q: Какой на ваш взгляд лучший GUI для kubernetes? Lens?
582 | - A:
583 | - Лучший GUI - его отсутствие, т.е. kubectl
584 | - Я пробовал Lens под виндой, он жутко тормозил и запускался через раз
585 | - [01:34:48](https://youtu.be/8Wk1iI8mMrw?t=5688)
586 | - Q: Что за скрипт, который следит за конфигом nginx? Ведь это уже 2 процесса, кто обеспечивает их работу?
587 | - A:
588 | - Это плохие практики, лучше так не делать, 3 года назад, когда это писалось, это было еще нормально
589 | - В скрипте бесконечный цикл и процесс nginx, который отправили в фон, таким образом 2 процесса работают внутри контейнера, цикл следит за входящими в контейнер сигналами и попутно проверяет контрольные суммы файлов конфигурации nginx, если они изменились, выполняется nginx reload (вероятно, `nginx -s reload`)
590 | - [01:36:07](https://youtu.be/8Wk1iI8mMrw?t=5767)
591 | - Q: Есть ли политика у Provisioner по удалению (освобождению дисков) или администратор должен сам следить за дисками, которые долгое время не использовались?
592 | - A: Нет, администратор должен сам следить, Provisioner только создаёт диски, правда, можно его еще заставить их расширять
593 | - [01:36:45](https://youtu.be/8Wk1iI8mMrw?t=5805)
594 | - Q: Какие есть кейсы использования hostPath, если он не рекомендуется к использованию
595 | - A:
596 | - hostPath используется в агентах мониторинга, node exporter монтирует файловую систему узла, чтобы узнавать, сколько места на диске осталось
597 | - также, штука, ответственная за сеть (будет позже) использует hostPath, чтобы создавать сеть в подах
598 | - иногда hostPath используется в целях разработки, для простого обмена данными с подом, например, подбросить исправленный код на веб-сервер
599 | - в общем и целом - для системных задач
600 | - [01:37:57](https://youtu.be/8Wk1iI8mMrw?t=5877)
601 | - Q: Нормальным ли решением будет запускать СУБД в kubernetes с хранением данных на PV?
602 | - A: Для тестов - да, для продакшн - нет, будет слишком много проблем
603 | - [01:39:46](https://youtu.be/8Wk1iI8mMrw?t=5986)
604 | - Q: Что ещё можно сделать через initContainer? Можно, например, ssh туннель открыть?
605 | - A: Можно сделать всё что угодно, и shh туннель открыть, но непонятно, зачем это нужно, т.к. такой контейнер имеет ограниченный срок жизни
606 | - [01:40:09](https://youtu.be/8Wk1iI8mMrw?t=6009)
607 | - Q: PV Provisioner - это уже не часть kubernetes, правильно?
608 | - A: В текущем виде - это часть kubernetes, но в будущем планируется переход на container storage interface, а это уже будет внешняя система расширения
609 | - [01:41:09](https://youtu.be/8Wk1iI8mMrw?t=6069)
610 | - Q: Вначале презентации было сказано, что для файлов лучше юзать S3 и т.д., тогда зачем было то, о чём рассказывалось?
611 | - A: Чтобы мы знали о такой опции, в некоторых случаях это приемлемо
612 | - [01:43:14](https://youtu.be/8Wk1iI8mMrw?t=6194)
613 | - Q: Где всё таки лучше держать БД?
614 | - A: На виртуалках или bare metal, в зависимости от потребностей
615 | - [01:43:23](https://youtu.be/8Wk1iI8mMrw?t=6203)
616 | - Q: Если надо сохранить дамп JVM, при OOM например, как это сделать?
617 | - A: Нужно думать
618 | - [01:44:26](https://youtu.be/8Wk1iI8mMrw?t=6266)
619 | - Q: Где создаётся emptyDir? На файловой системе ноды или где-то еще?
620 | - A: По умолчанию да, но, при желании, можно и в оперативной памяти создать
621 | - [01:44:59](https://youtu.be/8Wk1iI8mMrw?t=6299)
622 | - Q: Ресайз без потери данных произойдёт?
623 | - A: Да
624 | - [01:45:07](https://youtu.be/8Wk1iI8mMrw?t=6307)
625 | - Q: Можете что-то рассказать про https://github.com/rancher/local-path-provisioner, если использовали?
626 | - A:
627 | - Конретно этот провижинер не использовал, но обычно провижинер создаёт PV из подключенных устройств, если мы добавляем какой-то диск, то провижинер создаёт под этот диск манифест PV, это некая альтернатива hostPath, так называемые local volumes, мы создаём манифест PV и в нём мы указываем, что это диск типа "локальный" и указываем, что он ссылается на конкретный каталог на узле
628 | - Отличия от hostPath в том, что в случае с hostPath, человек, который создал деплоймент, может указать любой путь, даже подключить корень, а в случае с дисками типа local, этот путь задаёт администратор и обычно это безопасный путь, где нет ничего, что может подвергнуть систему проблемам
629 | - [01:46:35](https://youtu.be/8Wk1iI8mMrw?t=6395)
630 | - Q: initContainer можно использовать для ожидания старта других подов?
631 | - A:
632 | - Можно, но не нужно, зависимости запуска - плохая идея
633 | - Нужно строить систему таким образом, чтобы она была готова к тому, что соседи могут падать и подниматься
634 | - [01:48:37](https://youtu.be/8Wk1iI8mMrw?t=6517)
635 | - Q: Какой смысл делать несколько initContainers, если можно в одном выполнить несколько команд?
636 | - A:
637 | - Может быть 5 разных задач под разные образы, например:
638 | - busybox - выполнить простой скрипт
639 | - MySQL - получить дамп базы и развернуть его
640 | - Такой вот docker-way
641 | - [01:49:08](https://youtu.be/8Wk1iI8mMrw?t=6548)
642 | - Q: Что будет, если поменять лимиты PVC, что он перестанет влазить в конкретные хранилища?
643 | - A: Зависит от того, как СХД обрабатывает такие ситуации, тот же ceph упадёт и будет лежать, пока в него не добавят дисков
644 | - [01:50:31](https://youtu.be/8Wk1iI8mMrw?t=6631)
645 | - Q: Отличия ванильного kubernetes от openshift?
646 | - A:
647 | - В openshift упор на безопасность и куча дополнительных типов ресурсов и куча вещей, которые делаются иначе
648 | - В openshift есть сценарии, одобренные Redhat, по которым стоит действовать
649 | - В kubernetes больше свободы, но не для всего есть готовые решения
650 | - В целом, зная одно, проще будет работать с другим
651 | - [01:51:35](https://youtu.be/8Wk1iI8mMrw?t=6695)
652 | - Q: Что такое /dev/vda1 с точкой монтирования /etc/hosts?
653 | - A: Это всякие служебные точки монтирования, которые монтируют hosts с узла, resolv.conf с узла и т.п.
654 |
--------------------------------------------------------------------------------
/lesson 6.md:
--------------------------------------------------------------------------------
1 | ---
2 | date created: 2021-10-19 19:12:20 (+03:00), Tuesday
3 | ---
4 |
5 | # Урок 6: Сетевые абстракции. Probes
6 | - [Запись](https://www.youtube.com/watch?v=OmTYdf_uDeQ) на youtube
7 | - [Презентация](https://github.com/Slurmio/school-dev-k8s/blob/main/lecture/6.Network_abstractions.pdf) лекции в [github репозитории](https://github.com/Slurmio/school-dev-k8s) школы
8 |
9 | ## Освежаем знания по базовым абстракциям [00:00:16](https://youtu.be/OmTYdf_uDeQ?t=16)
10 |
11 | ### Pod
12 | - В kubernetes минимальная абстракция - это не контейнер, а pod
13 | - Pod может состоять минимум из 2х контейнеров
14 | - один контейнер - это непосредственно рабочая нагрузка, внутри запущено наше приложение
15 | - второй контейнер - так называемый служебный контейнер, он держит в себе сетевой неймспейс, [процесс pause](https://stackoverflow.com/questions/48651269/what-are-the-pause-containers), его мы особо не касаемся на практике
16 | - Более того, сама абстракция pod тоже служебная, запуская свое приложение с помощью пода мы ставим себя в очень неудобное положение, т.к. лишаем себя гибкости а также двух важных вещей в контексте управления приложением:
17 | - первое - это обновление приложения, допустим, у нас вышла новая версия приложения, нам нужно каким-то образом сделать рестарт пода, чтобы он пересоздался, и на уровне пода это неудобно
18 | - второе - это уменьшение/увеличение количества реплик то есть скейлинг - все приходится делать вручную, это неудобно
19 | - Таким образом, pod это служебная абстракция, на её уровень приходится спускаться, когда дело касается какого-то дебага, например, посмотреть логи
20 |
21 | ### ReplicaSet [00:03:16](https://youtu.be/OmTYdf_uDeQ?t=196)
22 | - Эта абстракция с помощью которой достаточно удобно можно приложение скейлить
23 | - На уровне репликасетов мы можем удобно, централизовано, с помощью одной команды увеличивать и уменьшать количество реплик одного приложения, это здорово, это хорошо и решает одну из двух таких ключевых задач по управлению нашим приложением - скейлинг
24 | - Но с помощью репликасетов неудобно обновлять наше приложение, нам приходится все равно руками как-то там лезть, в общем неудобно, поэтому репликасетами мы тоже не пользуемся, это тоже, скорее, служебная абстракция, ею пользуемся наверное еще реже даже чем подом, потому что это прям совсем такая служебная-служебная, отвечает она только за управление скейлингом
25 |
26 | ### Deployment [00:04:17](https://youtu.be/OmTYdf_uDeQ?t=257)
27 | - Это действительно та абстракция с помощью которой можно запускать приложение, это действительно так, потому что deployment решает две вот эти самые задачи управления приложением - управления репликами и обновления вашего приложения, все можно делать с помощью деплоймента, всё описывается в yaml манифесте, всё управляется с помощью kubectl, всё достаточно удобно
28 |
29 | ## Про вступление [00:04:45](https://youtu.be/OmTYdf_uDeQ?t=285)
30 | - Вот это небольшое вступление к теме было нужно, потому что помимо этих двух задач, обновление приложения и скейлинг, есть еще ряд задач, которые не менее важны
31 | - Одна из этих задач - это понимать, а действительно ли приложение которое сейчас запущено в кластере, оно работает и с ним все хорошо
32 | - Это очень хороший вопрос, очень важный вопрос, нам нужно понимать что действительно, трафик, который идет на наше приложение, он там обрабатывается и с ним всё хорошо
33 | - Может быть такое чисто технически, что приложение висит, процесс как бы есть, он с PID 1 где-то в контейнере крутится, но по факту функциональность не осуществляется, то есть приложение не работает, это может быть случай, например, какого нибудь дедлока, и чтобы такие моменты контролировать kubernetes нам предлагает такое решение как пробы, это возможность как раз понимать, что происходит с приложением, действительно ли оно работает, действительно ли оно готово принимать трафик и действительно ли оно запустилось
34 | - Для такого контроля у нас есть три вида этих проб - это лайвнес проба, рединес проба и стартап проба
35 |
36 | ## Probes [00:07:09](https://youtu.be/OmTYdf_uDeQ?t=429)
37 | - Синоним хелсчеков
38 | - Позволяют проверять, что приложение действительно работает
39 |
40 | ### Liveness Probe [00:07:19](https://youtu.be/OmTYdf_uDeQ?t=439)
41 | - Контроль за состоянием приложения во время его жизни
42 | - Исполняется постоянно, с заданной периодичностью
43 | - Если такая проверка будет провалена, то приложение (под) будет перезапущено
44 |
45 | ### Readiness Probe [00:08:06](https://youtu.be/OmTYdf_uDeQ?t=486)
46 | - Проверяет, готово ли приложение применять трафик
47 | - В случае неудачного выполнения приложение убирается из балансировки, соответственно, после этого в данный инстанс приложения перестанет идти трафик
48 | - Исполняется постоянно, с заданной периодичностью
49 |
50 | ### Startup Probe [00:09:10](https://youtu.be/OmTYdf_uDeQ?t=550)
51 | - Проверяет, запустилось ли приложение вообще
52 | - Исполняется при старте, остальные типы проверок начнут выполнятся после завершения проверки данного типа
53 |
54 | ### Переходим в консоль [00:14:07](https://youtu.be/OmTYdf_uDeQ?t=847)
55 | - Работаем в каталоге `~/school-dev-k8s/practice/7.network-abstractions/1.probes`
56 | - Смотрим манифест деплоймента:
57 | ```yaml
58 | ---
59 | # file: practice/1.kube-basics-lecture/4.resources-and-probes/deployment-with-stuff.yaml
60 | apiVersion: apps/v1
61 | kind: Deployment
62 | metadata:
63 | name: my-deployment
64 | spec:
65 | replicas: 2
66 | selector:
67 | matchLabels:
68 | app: my-app
69 | strategy:
70 | rollingUpdate:
71 | maxSurge: 1
72 | maxUnavailable: 1
73 | type: RollingUpdate
74 | template:
75 | metadata:
76 | labels:
77 | app: my-app
78 | spec:
79 | containers:
80 | - image: quay.io/testing-farm/nginx:1.12
81 | name: nginx
82 | ports:
83 | - containerPort: 80
84 | readinessProbe:
85 | failureThreshold: 3
86 | httpGet:
87 | path: /
88 | port: 80
89 | periodSeconds: 10
90 | successThreshold: 1
91 | timeoutSeconds: 1
92 | livenessProbe:
93 | failureThreshold: 3
94 | httpGet:
95 | path: /
96 | port: 80
97 | periodSeconds: 10
98 | successThreshold: 1
99 | timeoutSeconds: 1
100 | initialDelaySeconds: 10
101 | startupProbe:
102 | httpGet:
103 | path: /
104 | port: 80
105 | failureThreshold: 30
106 | periodSeconds: 10
107 | resources:
108 | requests:
109 | cpu: 10m
110 | memory: 100Mi
111 | limits:
112 | cpu: 100m
113 | memory: 100Mi
114 | ...
115 | ```
116 | - Probes описываются в спецификации внутри контейнеров
117 | - Для каждого контейнера своё описание probes
118 | - Рассматриваем настройки probes
119 | - readinessProbe
120 | - failureThreshold
121 | - допустимое количество проваленных попыток подряд, прежде чем приложение будет выведено из балансировки
122 | - применяется для того, чтобы из-за каких-то небольших проблем, например с сетью, приложение не останавливалось полностью
123 | - httpGet
124 | - сама проверка, в данном случае мы идём в корневой локейшн нашего приложения по 80 порту, для проверки, что nginx готов принимать трафик, этого достаточно
125 | - успешными считаются коды ответа в диапазоне от 200 до 399
126 | - например, 301 - это ОК, а 400, 404 - уже не ОК
127 | - помимо httpGet есть еще проверки:
128 | - exec
129 | - с помощью неё мы можем выполнить внутри контейнера какую-то команду, например, если это база данных, мы можем выполнить SELECT 1, таким образом убедиться, что БД поднялась и готова принимать запросы
130 | - tcpSocket
131 | - это самая простая проверка, которая позволяет проверять TCP сокет, стучаться в него, если он открыт и работает - значит проверка прошла, всё ОК
132 | - periodSeconds
133 | - означает, с какой периодичностью выполнять проверку
134 | - successThreshold
135 | - сколько успешных проверок сбросит счётчик failureThreshold
136 | - timeoutSeconds
137 | - ограничение на время выполнения проверки, собственно, таймаут
138 | - livenessProbe
139 | - большинство настроек аналогичны описанным выше
140 | - initialDelaySeconds
141 | - отсрочка выполнения первой проверки, использовали до появления startupProbe
142 | - startupProbe
143 | - основное отличие от предыдущих проверок - большие значения, т.е. мы даём сервису 5 минут на запуск
144 | - При выводе k get pod, в колонке READY выводится результат readinessProbe
145 | - Если сходить в логи пода, развёрнутого из этого манифеста деплоймента, можно увидеть информацию о запросах probes к нашему nginx
146 |
147 | ## Способы публикации [00:28:32](https://youtu.be/OmTYdf_uDeQ?t=1712)
148 | - Как опубликовать приложение, запущенное в кластере kubernetes, чтобы клиенты, находящиеся вне кластера, могли получить к нему доступ?
149 | - Как настроить внутрикластерное взаимодействие своих приложений, фронтенда, бэкенда?
150 | - Унас есть 2 абстракции, с помощью которых можно решать данные задачи, о них ниже
151 |
152 | ### Service [00:29:23](https://youtu.be/OmTYdf_uDeQ?t=1763)
153 | - Ключевые моменты почти для всех сервисов
154 | - Важно, чтобы селектор совпадал с лейблами подов
155 | - Поды и сервисы должны находиться в одном неймспейсе
156 | - ClusterIP [00:29:42](https://youtu.be/OmTYdf_uDeQ?t=1782)
157 | - Используется для того, чтобы наладить внутрикластерное взаимодействие частей приложения (например, связать фронтенд с бэкендом)
158 | - В теории, можно отказаться от использования данного сервиса и работать с IP адресами подов, передавать их, например, через переменые окружения, но это будет работать до тех пор, пока поды не начнут перезапускаться и, соответственно, менять адреса
159 | - Также, с помощью данного сервиса мы можем пробрасывать приложение через kubectl port-forward на хост, с которго запускается данная команда, это удобно для разработки или отладки. Таким образом удобно работать с зависимостями, можно обращаться к тем компонентам кластера, которые требуются в данный момент
160 | - Команда `kubectl port-forward service/my-service 10000:80` пробросит 80 порт сервиса my-service на 10000 порт хоста, с которого запускается эта команда
161 | - Раньше подобное можно было делать командой kubectl proxy, но сейчас этот метод считается устаревшим
162 | - NodePort [00:34:22](https://youtu.be/OmTYdf_uDeQ?t=2062)
163 | - [Документация](https://kubernetes.io/docs/concepts/services-networking/service/#nodeport)
164 | - Позволяет опубликовать приложение наружу, для этого он и используется, правда, с оговорками
165 | - Может публиковать приложение только на определённом диапазоне портов, по умолчанию это 30000-32767
166 | - При необходимости можно явно указать нужный порт в манифесте
167 | - Создаёт на каждой ноде кластера соответствующее правило трансляции
168 | - Подходит для использования, если есть внешний балансировщик, в котором можно указать соответствие
169 | - Может использоваться для работы с LoadBalancer [00:38:09](https://youtu.be/OmTYdf_uDeQ?t=2289)
170 | - [00:46:43](https://youtu.be/OmTYdf_uDeQ?t=2803) Реальный кейс применения NodePort - переносили монолитные приложения, их пилили на микро сервисы, затаскивали всё в kubernetes, получалось что часть приложения в монолите, а часть приложения в kubernetes, при этом есть некий центральный nginx, который часть трафика продолжает отправлять на монолит, а часть трафика отправляет уже в kubernetes, на порты, опубликованные наружу через NodePort. Например, \/api отправляли в kubernetes а \/cabinet отправляли еще в монолит и вот таким образом можно решать подобные проблемы
171 | - LoadBalancer [00:38:20](https://youtu.be/OmTYdf_uDeQ?t=2300)
172 | - Тип LoadBalancer используется, преимущественно, у облачных провайдеров и как раз этот тип сервиса нам позволяет действительно легко и прозрачно предоставлять доступ извне кластера к нашему приложению
173 | - Данный тип сервиса используется преимущественно в облаках, потому что у облачных провайдеров есть контроллер, например openstack контроллер, который постоянно смотрит в kubernetes и контролирует создание сервисов типа LoadBalancer и когда данный тип сервиса создается в кластере, контроллер у себя создает балансировщик, например elastic LoadBalancer, и трафик отправляется в этот балансировщик и уже соответственно с этого балансировщика отправляются внутрь кластера на наше приложение
174 | - И вот этот балансировщик, он создается контроллером и если контроллера нет, соответственно, нет функциональности, которая позволяет смотреть в API кубернетеса и контролировать создание сервисов типа LoadBalancer, то никакой магии не произойдет и сервис типа LoadBalancer не будет работать, то есть, на bare metal по умолчанию это не работает, он просто будет висеть
175 | - Но в VK Cloud это будет работать, поэтому всё хорошо, единственное у нас всё-таки кластер немного кастомизирован и всё такое, поэтому могут всплыть некоторые особенности
176 | - Вообще, в облаках это работает, это очень удобно, более того, есть возможность в манифесте указать конкретный статический IP адрес, который будет использоваться как входная точка в ваш балансировщик
177 | - LoadBalancer отлично подходит чтобы опубликовать свое приложение и в том числе TCP, например, если надо выставить наружу кластера какой-нибудь postgres или rabbitmq, то LoadBalancer отличный вариант, но с учётом того, что работает это в облаках
178 | - ExternalName [00:40:58](https://youtu.be/OmTYdf_uDeQ?t=2458)
179 | - 
180 | - Позволяет создать алиас для DNS имени в качестве имени сервиса
181 | - Посмотрите внимательно на стрелки, если раньше у нас стрелки шли от сервиса к поду, то теперь это происходит немножко наоборот и это не ошибка это специфика работы этого сервиса
182 | - Когда мы создаем вот такой сервис, как видите, он достаточно минималистичный, создается правило и создается dns-запись и, в таком случае, когда мы из пода обращаемся по имени сервиса, например через `curl my-service` или `nslookup my-service` то мы по факту попадаем на example.com, адрес который мы прописали в поле ExternalName, то есть, это некая такая хитрая переадресация
183 | - Сложно придумать кейсы где это можно действительно применить, потому что обычно ничто не мешает сразу же из пода напрямую обращаться к example.com, но это может быть, к примеру, какой то закрытый продукт где вы ничего не можете править и вам нужно там что-то поменять или какие-то значения в приложении захардкожены и вам нужно их сменить
184 | - ExternalIPs [00:42:39](https://youtu.be/OmTYdf_uDeQ?t=2559), технические шоколадки и повтор с [00:44:58](https://youtu.be/OmTYdf_uDeQ?t=2698)
185 | - 
186 | - Если вы посмотрите на схему то увидите, что тут почти то же самое что и у сервисов NodePort, только вместо портов здесь указаны IP
187 | - Схема того как работает сервис типа ExternalIPs очень похожа на то, как работает сервис типа NodePort, c тем исключением, что в NodePort у нас создается правило трансляции которые работают с каким-то конкретным портом, а в ExternalIPs у нас создается правило которое работает с IP адресом
188 | - Когда мы создаем сервис ExternalIP, у нас на каждой ноде кластера создаются правила трансляции и трафик, приходящий на IP адрес, указанный в этом поле, будет пробрасываться в наше приложение.
189 | - Кластер kubernetes такой умный и понимает где именно этот IP адрес находится? Не понимает и это не очень удобно
190 | - Один из примеров применения данного сервиса - у нас есть какой-то внешний балансировщик и есть какой-то виртуальный IP-адрес (VIP) или, как принято говорить, протокол VRRP, keepalived настроен, допустим наш VIP изначально находится на первой ноде, вдруг с ней что-то произошло и тогда keepalived по VRRP этот адрес переносит на вторую ноду и мы можем быть уверены что на этой ноде все необходимые правила трансляции тоже есть, то есть, трафик будет приходить и сразу попадать в нужный под
191 | - Headless [00:47:58](https://youtu.be/OmTYdf_uDeQ?t=2878), техническая заминка и повтор с [00:50:56](https://youtu.be/OmTYdf_uDeQ?t=3056)
192 | - 
193 | - Этот сервис не про внутрикластерное взаимодействие и не про публикацию, поэтому он немножко так особняком стоит
194 | - Отличительной особенностью Headless сервиса является то что у него не указан ClusterIP (см. слайд)
195 | - Для такого сервиса не будет создан IP адрес, но будет создана DNS запись, которая будет отдавать IP адреса всех подов для этого сервиса
196 | - Используя данную DNS запись мы также можем обращаться напрямую к подам
197 | - Будет выглядеть примерно так: my-app-1.my-headless-service.my-cluster.my-zone
198 | - Данный сервис используется в конкретном случае, а именно со StatefulSet
199 | - [00:53:30](https://youtu.be/OmTYdf_uDeQ?t=3210) StatefulSet является заменой деплойменту для stateful приложений, например баз данных, эта тема будет рассмотрена в следующих лекциях
200 | - У подов в StatefulSet имена подов формируются добавлением номера в конце, таким образом, можно настроить репликацию, чтобы отправлять запросы на чтение в slave, а запросы на запись в master, определяя, какой запрос куда отправлять, опираясь на имена подов и DNS запись, сформированную headless сервисом
201 |
202 | ### Практика [00:54:13](https://youtu.be/OmTYdf_uDeQ?t=3253)
203 | - Работаем в каталоге `~/school-dev-k8s/practice/6.network-abstractions/2.ingress-and-services`
204 |
205 | #### Разбираем сервис ClusterIP
206 | - Смотрим на манифест сервиса ClusterIP
207 | ```yaml
208 | apiVersion: v1
209 | kind: Service
210 | metadata:
211 | name: my-service # имя сервиса
212 | spec:
213 | ports:
214 | - port: 80 # порт, на котором сервис будет принимать трафик
215 | targetPort: 80 # порт, на который сервис будет перенаправлять трафик
216 | selector:
217 | app: my-app # значение метки, по которой селектор будет производить выборку
218 | type: ClusterIP # тип сервиса
219 | ```
220 | - В данном случае тип сервиса указан, но если тип не указывать, то по умолчанию будет использоваться ClusterIP
221 | - Важно корректно указывать имя сервиса, т.к. на его основе будет создана DNS запись в кластере kubernetes
222 | - В селекторе мы описываем метки, по которым будут выбираться поды, чтобы перенаправлять на них трафик
223 | - Чтобы наш трафик все-таки в какое-то приложение отправлялся, давайте запустим наше приложение:
224 | ```shell
225 | > kubectl create -f app
226 |
227 | deployment.apps/my-deployment created
228 | Error from server (AlreadyExists): error when creating "app/configmap.yaml": configmaps "my-configmap" already exists
229 | ```
230 | - Т.к. configmap уже существует, можно добиться нужного результата удалив созданные сущности, например через `kubectl delete -f app/` и затем повторив создание через команду `kubectl create -f app/`
231 | - Также можно выполнить `kubectl apply -f app/`
232 | - Если вдруг кто забыл, у нас есть две возможности создавать что-то в кластере kubernetes из файла
233 | - С помощью `kubectl create` и тогда объект в кластере создастся, если его ещё не было, а если этот объект уже был в кластере с этим именем, то он там не создастся
234 | - С помощью `kubectl apply` мы можем эти объекты конфигурировать, то есть если `kubectl apply -f` применить на какой-то объект, если этого объекта в кластере еще нет, то он создастся а если он уже есть, то сконфигурируется
235 | - Посмотрим что у нас всё действительно запустилось:
236 | ```shell
237 | > kubectl get pod
238 |
239 | NAME READY STATUS RESTARTS AGE
240 | my-deployment-5f979b576b-6shcg 1/1 Running 0 20m
241 | my-deployment-5f979b576b-mc4r6 1/1 Running 0 20m
242 | ```
243 | - Чтобы лучше разобраться с ClusterIP, давайте воссоздадим соответствующие условия
244 | - Мы развернули nginx, допустим что это приложение с каким-то фронтендом
245 | - Запустим тестовое приложение, мы будем обращаться к основному приложению из этого пода с помощью установленных в нём утилит, таких как curl, nslookup
246 | ```shell
247 | > kubectl run test --image=centosadmin/utils:0.3
248 |
249 | # Если мы хотим сразу "провалиться" в командную строку в созданном поде, то это можно добавить в конце команды "-it -- bash"
250 | ```
251 | - Мы можем узнать IP адрес пода с приложеним и постучаться по нему с помощью curl из пода с утилитами и получить ответ, но это будет работать до тех пор, пока под с приложением работает, как только под переедет или будет перезапущен, адрес изменится и такая возможность пропадёт
252 | - Создаём сервис из манифеста, рассмотренного выше и посмотрим к чему это приведет
253 | ```shell
254 | > kubectl apply -f clusterip.yaml
255 |
256 | service/my-service created
257 |
258 | > kubectl get svc
259 |
260 | NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
261 | my-service ClusterIP 10.254.115.220 80/TCP 2m48s
262 | ```
263 | - При создании сервиса происходит следующее:
264 | - Ему присваивается некий IP адрес из заранее определенного пула (у нас кластере kubernetes есть как минимум два пула адресов, для подов и для сервисов и они не должны пересекаться)
265 | - В DNS кластера kubernetes создаётся DNS запись которая по имени сервиса будет отправлять нас в поды нашего приложения
266 | - Итак, сервис создан, ему были присвоены IP адрес и DNS запись
267 | - Не забываем про то, что сервис работает с подами используя селектор и метки на подах, они должны совпадать, смотрим что это так:
268 | ```shell
269 | > kubectl describe svc my-service | grep Selector
270 |
271 | Selector: app=my-app
272 |
273 | > kubectl get pod --show-labels
274 |
275 | NAME READY STATUS RESTARTS AGE LABELS
276 | my-deployment-5f979b576b-6shcg 1/1 Running 0 66m app=my-app,pod-template-hash=5f979b576b
277 | my-deployment-5f979b576b-mc4r6 1/1 Running 0 66m app=my-app,pod-template-hash=5f979b576b
278 | test 1/1 Running 1 26m run=test
279 | ```
280 | - Таким образом, сервис в рамках неймспейса будет перенаправлять запросы к нему на поды с соответствующей меткой
281 | - Теперь мы можем снова зайти в наше под с утилитами и попробовать повзаимодействовать через сервис с nginx который запущен в нашем деплойменте
282 | - Мы можем взять и использовать IP адрес сервиса, который мы видели выше в столбце CLUSTER-IP
283 | ```shell
284 | > kubectl exec -it test -- bash
285 | # тут мы провалились в под с утилитами и будем дёргать адрес балансироващика сервиса
286 |
287 | > curl 10.254.115.220
288 |
289 | my-deployment-5f979b576b-6shcg
290 |
291 | > curl 10.254.115.220
292 |
293 | my-deployment-5f979b576b-mc4r6
294 | ```
295 | - Видим что всё работает, у нас две реплики нашего приложения, отдается то один хостнейм, то другой, работает это рандомно, как подбрасывание монетки
296 | - Но адрес сервиса тоже может смениться, хотя шанс этого и меньше
297 | - Вспоминаем, что вместе с сервисом создаётся DNS запись, которая соответстует названию сервиса и не будет меняться, пока не изменится сам сервис, а если IP адрес сервиса изменится, то DNS запись обновится, это то что нам нужно, проверяем
298 | ```shell
299 | # мы всё ещй в поде с утилитами и теперь будем дёргать уже имя сервиса
300 |
301 | > curl my-service
302 |
303 | my-deployment-5f979b576b-6shcg
304 |
305 | > curl my-service
306 |
307 | my-deployment-5f979b576b-mc4r6
308 | ```
309 | - Вспоминаем 12 factor app, конфигурирование приложений через переменые окружения, мы просто прописываем имена сервисов в переменных окружения, куда-то в db_host, frontend_host, во что нибудь такое и больше не заморачиваемся, вообще никаких айпи адресов, сервис-discovery у нас целиком лежит на kubernetes
310 | - Удаляем за собой под с утилитами, он нам больше не нужен
311 | ```shell
312 | > kubectl delete pod test
313 |
314 | pod "test" deleted
315 | ```
316 | - В рамках одного неймспейса мы можем обращаться к приложению через сервис указывая просто имя сервиса
317 | - Eсли нужно обратиться к приложению в другом неймспейсе, то после имени сервиса через точку указываем имя неймспейса, вот так: `my-service.my-namespace`
318 |
319 | #### Разбираем сервис NodePort [01:09:48](https://youtu.be/OmTYdf_uDeQ?t=4188)
320 | - Смотрим в манифест:
321 | ```yaml
322 | apiVersion: v1
323 | kind: Service
324 | metadata:
325 | name: my-service-np
326 | spec:
327 | ports:
328 | - port: 80
329 | targetPort: 80
330 | selector:
331 | app: my-app
332 | type: NodePort
333 | ```
334 | - Отличительная особенность - указание типа, а именно NodePort
335 | - Также мы можем явно указать порт ([документация](https://kubernetes.io/docs/concepts/services-networking/service/#nodeport))
336 | - Всё остальное как у ClusterIP
337 | - Пробуем применить манифест, получаем ошибку:
338 | ```shell
339 | > kubectl apply -f nodeport.yaml
340 |
341 | # часть ошибки не видна за аватаром ведущего, в момент написания конспекта ошибки уже не было
342 | Error from server (Forbidden): error when creating "nodeport.yaml": services "my-service-np" ...
343 | : exceeded quota: resource-quota, requested: service:nodeports=1, used: services.nodepo...
344 | services.nodeports=0
345 | ```
346 | - Учебный кластер готовили неглупые люди, ранее об этом упоминалось, есть инструменты администрирования кластера, это возможность обезопасить и себя и разработчиков от ошибок человеческого фактора, если мы приняли решение что в кластере нельзя создавать какой-то тип объекта или объектов определённого типа должно быть ограниченное количество, то с помощью таких абстракций как ресурс квоты и лимит ренджи мы можем это достаточно жестко регламентировать
347 | - Вот прекрасный пример, мы захотели сервисов с типом NodePort в количестве 1, но у нас лимит на сервисы с типомNodePort в размере 0
348 | - Ограничения сняты, пробуем применить манифест ещё раз:
349 | ```shell
350 | > kubectl apply -f nodeport.yaml
351 |
352 | service/my-service-np created
353 | ```
354 | - Смотрим на сервисы:
355 | ```shell
356 | > kubectl get svc
357 |
358 | NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
359 | my-service ClusterIP 10.254.115.220 80/TCP 102m
360 | my-service-np NodePort 10.254.240.139 80:32405/TCP 18m
361 | ```
362 | - Видим наш сервис, ему выданы IP адрес и DNS имя, которые работают так же как и в случае с сервисом типа ClusterIP
363 | - Мы видим у нашего сервиса порт с номером больше 30000 и что он транслируется в 80 порт нашего приложения
364 | - Если мы обратимся на адрес любой ноды в кластере на этот порт, то мы будем перенаправлены на наше приложение
365 | ```shell
366 | > kubectl get nodes -o wide | (head -1 && tail -1)
367 |
368 | NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
369 | kubernetes-cluster-8675-master-2 Ready master 36d v1.20.4 10.0.0.5 213.219.212.215 CentOS Linux 8 4.18.0-305.7.1.el8_4.x86_64 docker://19.3.15
370 |
371 | # а это уже с личного ПК
372 | > curl 213.219.212.215:32405
373 |
374 | my-deployment-5f979b576b-6shcg
375 |
376 | > curl 213.219.212.215:32405
377 |
378 | my-deployment-5f979b576b-mc4r6
379 | ```
380 | - Это еще один повод запретить бездумное использование сервиса типа NodePort, потому что вот таким образом мы на всех эндпоинтах, на всех внешних IP адресах нашего кластера взяли и выставили свое приложение наружу, в реальности там мог бы быть дашборд какой-то или внутренняя система аутентификации или еще что-то, что могло бы послужить для хакеров входной точкой в наш проект, поэтому вполне логично, что подобные вещи надо как-то регламентировать, но это уже ближе к администрированию
381 |
382 | #### Разбираем сервис LoadBalancer [01:15:21](https://youtu.be/OmTYdf_uDeQ?t=4521)
383 | ```yaml
384 | apiVersion: v1
385 | kind: Service
386 | metadata:
387 | name: my-service-lb
388 | spec:
389 | ports:
390 | - port: 80
391 | targetPort: 80
392 | selector:
393 | app: my-app
394 | type: LoadBalancer
395 | ```
396 | - Манифест практически идентичен предыдущим, отличия в типе сервиса
397 | - Аналогичная предыдущим сервисам механика создания кластерного IP адреса и DNS имени
398 | - Если мы попробуем применить данный манифест, то увидим, подобную картину:
399 | ```shell
400 | > kubectl get svc
401 |
402 | NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
403 | my-service ClusterIP 10.254.115.220 80/TCP 102m
404 | my-service-lb LoadBalancer 10.254.206.240 80:32465/TCP 8s
405 | my-service-np NodePort 10.254.240.139 80:32405/TCP 18m
406 | ```
407 | - В кластере вне облака, без специального облачного контроллера, pending будет висеть вечно, потому что никто извне не увидит что у нас создан сервис LoadBalancer и не создаст для него балансировщик с каким-то внешним IP адресом, чтобы мы через него могли попадать в наше приложение
408 | - Когда шла речь про NodePort, упоминалось, что данный сервис используется в служебных целях с сервисом LoadBalancer. В списке портов мы видим трансляцию порта из диапазона выдачи портов NodePort, фактически, сервис LoadBalancer автоматизирует реализацию концепции с внешним балансировщиком и сервисом NodePort, за счёт применения облачного контроллера, который создаёт внешний балансировщик и настраивает его нужным образом для перенаправления трафика на транслируемые порты
409 |
410 | ### Резюме по сервисам [01:18:28](https://www.youtube.com/watch?v=OmTYdf_uDeQ&t=4708s)
411 | - 
412 | - В результате полученной информации может сложится понимание, что сервис это разновидность прокси
413 | - Действительно, у сервиса есть какой-то IP адрес и DNS-запись, если мы обращаемся к сервису, например через curl или из браузера, то он каким-то образом распределяет трафик на наши поды
414 | - Таким образом, может сложится впечатление, что когда мы создаём сервис, то в кластере создаётся какой-то объект, например процесс балансировщика (haproxy или что-то на go), который всё это балансирует, но ничего такого не происходит, сервисы работают по другому
415 | - Фактически, сервис из себя представляет правила в iptables или ipvs, при установке кластера можно выбрать, какой из этих бэкендов использовать
416 | - Далее будет демонстрация на примере правил iptables, принципы в ipvs такие же
417 | - Когда мы применяем манифест сервиса, например ClusterIP, на самом деле, в этот момент на всех нодах кластера создаются несколько правил iptables
418 | - Если мы выполним на одной из нод соответствующую команду, например `iptables -t nat -A | grep my-service`, то мы сможем увидеть список из нескольких правил, давайте попробуем разобраться в этом и поймём, что сервис это не прокси
419 | - 
420 | - Тут мы видим, что трафик, приходящий на IP адрес сервиса (1.1.1.1), на порт 80, по протоколу tcp, должен отправляться в определённую цепочку iptables (KUBE-SVC-UT6...)
421 | - 
422 | - А здесь, что пришедший трафик будет с вероятностью 50% отправляться в другую цепочку (KUBE-SEP-MMY...), а тот трафик, что не был распределён, если правило вероятностного распределения не сработало, пойдет в следующую цепочку (KUBE-SEP-J33...)
423 | - 
424 | - Тут мы видим, что пришедший трафик будет отправляться на определённый адрес и порт. Аналогичное правило мы увидим для другой цепочки (KUBE-SEP-J33...)
425 | - Адреса, указанные в этих правилах ни что иное, как адреса подов приложения
426 | - Итого, при создании сервиса у нас образуется несколько правил iptables которые через `--mode random probability <с какой-то вероятностью>` отправляет трафик на IP адреса наших подов по тем портам и протоколам, которые в этом сервисе были указаны. Если подов больше двух, то вероятности в правилах будут указаны соответствующие
427 | - Таким образом, мы работаем с привычным iptables или ipvs, а не с чем-то принципиально новым
428 | - На практике, погружаться на этот уровень практически не требуется
429 | - Подытожим, сервис - это:
430 | - 
431 | - Полная запись DNS имени сервиса представляет собой конструкцию вида **<имя сервиса>.<имя неймспейса>.svc.<доменное имя кластера>**, но с помощью неких механизмов, которые прописаны в resolv.conf каждого контейнера, мы можем просто обращаться по имени сервиса и попадать в нужное нам приложение или, если оно в другом неймспейсе, то просто **<имя сервиса>.<имя неймспейса>**, а полную конструкцию можно не указывать
432 |
433 | ### Ingress [01:25:29](https://www.youtube.com/watch?v=OmTYdf_uDeQ&t=5129s)
434 | - 
435 | - Если вспомнить всё, о чём говорилось ранее, то становится понятно, что есть определённые способы публикации приложения, в том числе наружу, но это не очень удобно, т.к. есть различные оговорки - определённый диапазон для публикации, нужен внешний балансировщик либо это применимо только в облаках
436 | - А если у нас простое приложение, например интернет-магазин, чисто технически, мы можем создать специальный под с nginx, с помощью configmap пробросить конфигурацию, настроить нужные права доступа, чтобы у этого контейнера с nginx была возможность пробрасывать трафик извне в нужные поды с приложением
437 | - На самом деле, именно так и работает IngressController, о котором дальше пойдет речь
438 | - Если нам нужно опубликовать веб приложение наружу, то нам нужно использовать такой тип объекта как Ingress, а также запустить в кластере такой тип приложения как IngressController
439 | - Здесь важно не путать:
440 | - Ingress - это манифест, абстракция
441 | - IngressController - это приложение, обычно это nginx, haproxy, envoy, traefic или что-то подобное, можно использовать то что удобно, либо то что подходит для реализации определённых специфических задач. Обычно установка и настройка IngressController ложится на плечи администраторов, поэтому мы не будем в это углубляться. Основное, что нужно понимать, что это такое же приложение, работающее в кластере, хорошо, если в несколько реплик, в это него приходят запросы и оно перенаправляет их дальше, в публикуемые приложения, к которым мы хотим предоставить доступ, пример на слайде демонстрирует, что разные эндпоинты перенаправят нас на разные приложения
442 | - Как уже было сказано, IngressController необходимо как-то конфигурировать, под капотом работает какой нибудь nginx, у него есть конфиг и каждый раз его менять руками не удобно и вообще, так никто не делает. Для этого есть специальный объект Ingress, где мы в привычном формате yaml можем конфигурировать IngressController и явно указывать, какие запросы куда отправлять, давайте посмотрим на манифест такого объекта
443 | ```yaml
444 | apiVersion: networking.k8s.io/v1
445 | kind: Ingress
446 | metadata:
447 | name: my-ingress
448 | annotations:
449 | ngingx.ingress.kubernetes.io/backend-protocol: "HTTPS"
450 | spec:
451 | rules:
452 | - host: foo.domain.com
453 | http:
454 | paths:
455 | - pathType: Prefix
456 | path: "/"
457 | backend:
458 | service:
459 | name: my-service
460 | port:
461 | number: 80
462 | ```
463 | - В разделе rules подраздела spec описано, как именно будет ходить трафик
464 | - host - хост, который будет обслуживать данный Ingress
465 | - pathType - используемый тип пути, их есть три типа, 2 из них самые распространённые
466 | - prefix
467 | - exact
468 | - В нашем случае все запросы, приходящие на корень, нужно адресовать на поды, которые стоят за сервисом my-service
469 | - В манифесте мы указываем имя сервиса, но только для того, чтобы IngressController определил, какие поды стоят за этим сервисом и сразу напрямую отправлял трафик в эти поды, сам сервис в этом не участвует
470 | - В рамках одного Ingress можно задавать несколько хостов
471 | - В рамках одного хоста мы можем задавать несколько путей
472 | - Может быть Ingress без хоста, тогда безадресные запросы будут попадать в этот Ingress
473 | - Это может быть опасно, подобным образом настроенный Ingress может быть использован в качестве входной точки для атакующих, подробнее здесь => [Дыры и заборы: безопасность в Kubernetes](https://www.youtube.com/watch?v=koTqZS-ThZ8)
474 | - Про аннотации - в манифесте есть соответствующий раздел с длинной строкой
475 | - В манифестах kubernetes всё довольно строго типизировано, есть определённые наборы ключей и значений, с которым умеет работать API kubernetes, при этом, приложения, которые мы используем в кластере могут требовать дополнительной гибкости в настройке и стандартных директив манифестов нам не всегда хватает
476 | - Для решения вышеописанной проблемы и используются аннотации, мы можем записывать туда необходимые сведения для конфигурации наших приложений, в нашем примере мы указываем, что трафик будет работать по HTTPS
477 | - Варианты используемых аннотаций для nginx IngressController можно найти в [его документации](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#annotations)
478 | - При этом, иногда необходимо сконфигурировать нечто, для чего нет готовой аннотации, при этом сам nginx позволяет такое конфигурировать, например какой-то хитрый составной rewrite. Для подобного существует специальные аннотации snippet, [server snippet](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#server-snippet), позволяет передать в IngressController кусочек конфига, а [configuration snippet](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#configuration-snippet) позволяет сделать то же самое на уровне локейшна nginx
479 | - По поводу безопасности соединения - HTTPS, TLS и т.п., в наше время важно обеспечивать безопасность соединения с помощью сертификатов
480 | - Такие манипуляции можно делать вручную:
481 | - 
482 | - Выполнив команду внизу слайда мы создадим секрет, манифест которого описан справа, в котором в закодированном виде будут содержаться ключ и сертификат
483 | - Далее нужно указать данный секрет в секции tls Ingress (левый манифест на слайде), IngressController умеет работать с такими директивами и сделает всю грязную работу
484 | - Если же что-то подобное нужно автоматизировать (опять же, это скорее задача администраторов), можно использовать cert-manager, он регулярно ходит в центр сертификации, например letsencrypt, выписывает их оттуда и подключает в IngresController'ы. Подробнее об этом можно почитать в [документации cert-manager](https://cert-manager.io/docs/)
485 | - 
486 | - Можно это делать через спец. объект Certificate, который появляется в кластере после установки cert-manager (левая часть слайда)
487 | - Либо через аннотации Ingress (правая часть слайда)
488 |
489 | #### Запускаем Ingress [01:41:56](https://youtu.be/OmTYdf_uDeQ?t=6116)
490 | ```shell
491 | $ cd ~/school-dev-k8s
492 |
493 | $ git pull
494 | ...
495 | 17 files changed, 741 insertions(+)
496 | ...
497 |
498 | $ cd practice/6.network-abstractions/2.ingress-and-services
499 |
500 | $ vi host-ingress.yaml
501 |
502 | $ cat host-ingress.yaml
503 |
504 | apiVersion: networking.k8s.io/v1
505 | kind: Ingress
506 | metadata:
507 | name: my-ingress-nginx
508 | spec:
509 | rules:
510 | - host: my.s024713.mcs.slurm.io
511 | http:
512 | paths:
513 | - pathType: Prefix
514 | path: "/"
515 | backend:
516 | service:
517 | name: my-service
518 | port:
519 | number: 80
520 |
521 | $ k apply -f host-ingress.yaml
522 |
523 | ingress.networking.k8s.io/my-ingress-nginx created
524 | ```
525 | - После этого мы можем увидеть, что наше приложение доступно извне по адресу, указанному в поле host
526 | - Мог возникнуть вопрос, можно ли с помощью Ingress опубликовывать приложения, которые работают поверх TCP/UDP, но не HTTP(S) - нет, в Ingress нет соответствующих полей, он задуман только для публикации приложений, работающих через http/https, мы даже можем это увидеть в документации. Если нужно реализовать что-то подобное, то можно использовать NodePort или LoadBalancer, о них было выше
527 | - Но, чисто технически, то что работает под капотом у IngressController, позволяет нам балансировать не только HTTP(S) трафик, так что при желании, с помощью специальных конфигмапов и аннотаций мы можем это реализовать. Но так делать не рекомендуется, это плохая практика, не Jedi-way
528 |
529 | ### Ещё кое что [01:44:54](https://youtu.be/OmTYdf_uDeQ?t=6294)
530 | - Если что-то осталось непонятным, можно и нужно почитать официальную документацию по данным темам:
531 | - [ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/)
532 | - [service](https://kubernetes.io/docs/concepts/services-networking/service/)
533 | - При желании, можно найти переведённые на русский разделы, но стоит иметь в виду, что перевод может быть не самым удачным
534 | - [Подробнее про PathType](https://kubernetes.io/docs/concepts/services-networking/ingress/#path-types) можно почитать в подразделе ingress, если кратко, то prefix менее, а exact более строгий
535 | - Рекомендация на почитать: [Запуск проекта в Kubernetes за 60 минут](https://habr.com/ru/company/vk/blog/565250/)
536 |
537 | ## Вопросы [01:48:54](https://youtu.be/OmTYdf_uDeQ?t=6534)
--------------------------------------------------------------------------------
/lesson 7.md:
--------------------------------------------------------------------------------
1 | # Урок 7: Устройство кластера.
2 |
3 | [Урок 7: Устройство кластера. Вечерняя школа Kubernetes](https://youtu.be/0rN_tUIZx7Y)
4 |
5 | # [Agenda лекции](https://youtu.be/0rN_tUIZx7Y?t=87)
6 |
7 | - Из чего состоит кластер
8 | - Что происходит в кластере, когда запускаются все абстракции
9 | - Есть абстракции и есть некоторый физический уровень, на котором всё происходит: запускается докер, контейнеры и прочая.
10 | - Чего можно ожидать от k8s и чего ожидать не стоит
11 | - Если что-то пошло не так, то по каким причинам это могло произойти
12 |
13 | # [План](https://youtu.be/0rN_tUIZx7Y?t=146)
14 |
15 | Дефолтные стандартные компоненты кластера, без которых кластер не может полноценно работать. И фактически нет выбора других компонентов - чтобы запустить кластер k8s нужно использовать конкретно эти компоненты:
16 |
17 | - Etcd
18 | - API server
19 | - Controller-manager
20 | - Scheduler
21 | - Kubelet
22 | - Kube-proxy
23 |
24 | Также есть компоненты, без которых кластер работать не будет, но в лекции они не рассматриваются. K8s в целом может запускаться. Вдобавок эти компоненты не являются регламентировано жесткими.
25 |
26 | - Контейнеризация: [Docker](https://youtu.be/TJg7QpqCH70), [Docker Compose и Co](https://youtu.be/Hz7fkXQABNo)
27 | Для контейниризации может использоваться как [Docker](https://www.docker.com/) (который, несмотря на депрекейшн, все еще остается дефолтной опцией в k8s), так и [Podman](https://podman.io/), [Containerd](https://containerd.io/), [Cri-O](https://cri-o.io/) и прочая.
28 | - Сеть: [Сети Кубернетес изнутри](https://youtu.be/6QKAt4TjxF8), [Сеть k8s - сетап кластера](https://youtu.be/JNUD9j9QAnA)
29 | Можно использовать: [Calico](https://docs.projectcalico.org/about/about-calico), [Weave Net](https://www.weave.works/oss/net/), [Cillium](https://cilium.io/), [Flannel](https://github.com/flannel-io/flannel)
30 | [Дока k8s](https://kubernetes.io/docs/concepts/cluster-administration/networking/)
31 | - [DNS](https://youtu.be/MLp8_qVLkzc)
32 | Сейчас дефолтным является [Core dns](https://coredns.io/). В более старых версия использовалась связка [dnsmasq](https://thekelleys.org.uk/dnsmasq/doc.html) и go'шного контроллера к нему. В принципе каким должен быть сам dns в кластере k8s не регламентировано, главное, чтобы он был совместим с самим кластером.
33 |
34 | В лекции будет рассмотрен каждый компонент и будет разобран на практических примерах: что каждый компонент будет делать с запускаемым ReplicaSet в кластере.
35 |
36 | # Мастер компонеты
37 |
38 | ## [Etcd](https://youtu.be/0rN_tUIZx7Y?t=362)
39 |
40 | 
41 |
42 | Хранит всю информацию о кластере. Это БД кластера k8s.
43 |
44 | Характеристики etcd:
45 |
46 | 1. key-value
47 | 2. изначально кластерная и хорошо работает в кластере
48 | 3. использует [raft](https://raft.github.io/). Обычно кластер etcd представляет собой несколько нод, которых нечетное количество (обычно 3 или 5). Raft обеспечивает кворум (quorum), что в свою очередь обеспечивает надежность хранения данных - чтобы данные считались записанными в кластере:
49 | 1. они ВСЕГДА должны быть записаны большинством реплик == нод кластера
50 | 2. ВСЕГДА у реплик должен быть мастер == "главная" нода
51 | 3. если мастер куда-то пропал, оставшиеся реплики большинством выбирают нового мастера
52 |
53 | Более подробно по ссылке выше.
54 |
55 |
56 | [8:03](https://youtu.be/0rN_tUIZx7Y?t=483) Обычно в отказоустойчивом кластере k8s существует 3 разных ноды с etcd. Это то, что мы называем мастернодами etcd (не путать с мастером для достижения кворума). На эти же ноды потом бцдцт ставиться другие компненты, которые занимаются именно управлением кластеров.
57 |
58 | В общем и целом можно работать с одной нодой. Dev установки обычно работают с одной.
59 |
60 |
64 |
65 | [Raft](http://thesecretlivesofdata.com/raft/)
66 |
67 | С consul'ом k8s не работает, только с etcd. Поддержки других хранилищь у k8s нет, ему это, впрочем, и не нужно.
68 |
69 | ## [API server](https://youtu.be/0rN_tUIZx7Y?t=572)
70 |
71 | - Центральный компонент k8s
72 | - Единственный, кто общается с etcd
73 | - Работает по REST API - мы с ним взаимодействуем с помощью команды `kubectl` - на самом деле под капотом шлются нормальные http запросы к API Server.
74 | Если хочется посмотреть, как это работает, можно запустить команду `kubectl apply -v=10 -f <имя-файла>` с ключом `-v=10` - это полный=verbose mode для команды `kubectl` и посмотреть полный вывод того, что происходит, как происходит обращение в кластер и так далее.
75 | Пример вывода
76 |
77 | ```bash
78 | ...
79 | I1107 02:51:23.247699 13959 round_trippers.go:443] GET https://5.188.142.58:6443/api/v1/namespaces/ 200 OK in 63 milliseconds
80 | I1107 02:51:23.247783 13959 round_trippers.go:449] Response Headers:
81 | I1107 02:51:23.247836 13959 round_trippers.go:452] Cache-Control: no-cache, private
82 | I1107 02:51:23.247866 13959 round_trippers.go:452] Content-Type: application/json
83 | I1107 02:51:23.247892 13959 round_trippers.go:452] X-Kubernetes-Pf-Flowschema-Uid: 211233d5-bef2-4121-9c8f-40d25ccae82e
84 | I1107 02:51:23.247922 13959 round_trippers.go:452] X-Kubernetes-Pf-Prioritylevel-Uid: 07b62685-8e2d-4ab3-bbc1-4745e33a5d11
85 | I1107 02:51:23.247951 13959 round_trippers.go:452] Content-Length: 469
86 | I1107 02:51:23.247972 13959 round_trippers.go:452] Date: Sat, 06 Nov 2021 23:51:23 GMT
87 | I1107 02:51:23.248024 13959 request.go:968] Response Body: {"kind":"Namespace","apiVersion":"v1","metadata":{"name":"","selfLink":"/api/v1/namespaces/","uid":"ad1ecf95-f7c5-467b-b6e1-76676ea62a5a","resourceVersion":"2270162","creationTimestamp":"2021-10-01T00:59:35Z","managedFields":[{"manager":"kubectl-create","operation":"Update","apiVersion":"v1","time":"2021-10-01T00:59:35Z","fieldsType":"FieldsV1","fieldsV1":{"f:status":{"f:phase":{}}}}]},"spec":{"finalizers":["kubernetes"]},"status":{"phase":"Active"}}
88 | I1107 02:51:23.248522 13959 request.go:968] Request Body: {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"annotations\":{},\"name\":\"my-deployment\",\"namespace\":\"\"},\"spec\":{\"replicas\":2,\"selector\":{\"matchLabels\":{\"app\":\"my-app\"}},\"strategy\":{\"rollingUpdate\":{\"maxSurge\":1,\"maxUnavailable\":1},\"type\":\"RollingUpdate\"},\"template\":{\"metadata\":{\"labels\":{\"app\":\"my-app\"}},\"spec\":{\"containers\":[{\"image\":\"quay.io/testing-farm/nginx:1.12\",\"livenessProbe\":{\"failureThreshold\":3,\"httpGet\":{\"path\":\"/\",\"port\":80},\"initialDelaySeconds\":10,\"periodSeconds\":10,\"successThreshold\":1,\"timeoutSeconds\":1},\"name\":\"nginx\",\"ports\":[{\"containerPort\":80}],\"readinessProbe\":{\"failureThreshold\":3,\"httpGet\":{\"path\":\"/\",\"port\":80},\"periodSeconds\":10,\"successThreshold\":1,\"timeoutSeconds\":1},\"resources\":{\"limits\":{\"cpu\":\"100m\",\"memory\":\"100Mi\"},\"requests\":{\"cpu\":\"10m\",\"memory\":\"100Mi\"}},\"startupProbe\":{\"failureThreshold\":30,\"httpGet\":{\"path\":\"/\",\"port\":80},\"periodSeconds\":10}}]}}}}\n"},"name":"my-deployment","namespace":""},"spec":{"replicas":2,"selector":{"matchLabels":{"app":"my-app"}},"strategy":{"rollingUpdate":{"maxSurge":1,"maxUnavailable":1},"type":"RollingUpdate"},"template":{"metadata":{"labels":{"app":"my-app"}},"spec":{"containers":[{"image":"quay.io/testing-farm/nginx:1.12","livenessProbe":{"failureThreshold":3,"httpGet":{"path":"/","port":80},"initialDelaySeconds":10,"periodSeconds":10,"successThreshold":1,"timeoutSeconds":1},"name":"nginx","ports":[{"containerPort":80}],"readinessProbe":{"failureThreshold":3,"httpGet":{"path":"/","port":80},"periodSeconds":10,"successThreshold":1,"timeoutSeconds":1},"resources":{"limits":{"cpu":"100m","memory":"100Mi"},"requests":{"cpu":"10m","memory":"100Mi"}},"startupProbe":{"failureThreshold":30,"httpGet":{"path":"/","port":80},"periodSeconds":10}}]}}}}
89 | I1107 02:51:23.248817 13959 round_trippers.go:423] curl -k -v -XPOST -H "User-Agent: kubectl/v1.16.13 (linux/amd64) kubernetes/fd22db4" -H "Accept: application/json" -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjdqS2pSd2RFYzUtMVNlUmVTbm11aHFkTDRqSkw0TjVSbWx0MDBVWnpqMFEifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJzMDAwOTk4Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6InMwMDA5OTgtdG9rZW4tZGxiZGYiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiczAwMDk5OCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImNmNGExYWYxLTkxZTAtNDliNy04YTk4LTE1ZDc0Zjc5YmNkZSIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpzMDAwOTk4OnMwMDA5OTgifQ.VT-zl-kRQPbn8V5X_JOH2kZ4u4ElZRbkxOjJoy5pYKnrt51bFo49gzdx9u-7CW75wmxF2Wu5Jf10OXFvTmiO8nSlOyZpe7Rv0__53udW1hLqew9vZ226L-Qv3rV99S8iz1d42Zldk1UJf_Hbn7t_GkACKRde8NQpCwhHuSnUKPp9yqSVtd4RI_BJWoUZs8l0xn6g2SM59m8mxON3sqf15hDCvz87TYiSZ8JYqybPmUgVZM2m80ILSaRX-Pv2vmwotf3PtHuLAnmpheqiTj7T3GI84ISjQ4BhWXYxyIGPXnWnY7u66jiYCzEveJFjFNnAWZsohhdMDthkdtnVc0-rhw" 'https://5.188.142.58:6443/apis/apps/v1/namespaces//deployments'
90 | I1107 02:51:23.337906 13959 round_trippers.go:443] POST https://5.188.142.58:6443/apis/apps/v1/namespaces//deployments 201 Created in 89 milliseconds
91 | I1107 02:51:23.337955 13959 round_trippers.go:449] Response Headers:
92 | I1107 02:51:23.337991 13959 round_trippers.go:452] Cache-Control: no-cache, private
93 | I1107 02:51:23.338032 13959 round_trippers.go:452] Content-Type: application/json
94 | I1107 02:51:23.338058 13959 round_trippers.go:452] X-Kubernetes-Pf-Flowschema-Uid: 211233d5-bef2-4121-9c8f-40d25ccae82e
95 | I1107 02:51:23.338083 13959 round_trippers.go:452] X-Kubernetes-Pf-Prioritylevel-Uid: 07b62685-8e2d-4ab3-bbc1-4745e33a5d11
96 | I1107 02:51:23.338112 13959 round_trippers.go:452] Date: Sat, 06 Nov 2021 23:51:23 GMT
97 | I1107 02:51:23.338334 13959 request.go:968] Response Body: {"kind":"Deployment","apiVersion":"apps/v1","metadata":{"name":"my-deployment","namespace":"","selfLink":"/apis/apps/v1/namespaces//deployments/my-deployment","uid":"d7930050-a35e-46b1-8606-2897ca984617","resourceVersion":"92966724","generation":1,"creationTimestamp":"2021-11-06T23:51:23Z","annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"annotations\":{},\"name\":\"my-deployment\",\"namespace\":\"\"},\"spec\":{\"replicas\":2,\"selector\":{\"matchLabels\":{\"app\":\"my-app\"}},\"strategy\":{\"rollingUpdate\":{\"maxSurge\":1,\"maxUnavailable\":1},\"type\":\"RollingUpdate\"},\"template\":{\"metadata\":{\"labels\":{\"app\":\"my-app\"}},\"spec\":{\"containers\":[{\"image\":\"quay.io/testing-farm/nginx:1.12\",\"livenessProbe\":{\"failureThreshold\":3,\"httpGet\":{\"path\":\"/\",\"port\":80},\"initialDelaySeconds\":10,\"periodSeconds\":10,\"successThreshold\":1,\"timeoutSeconds\":1},\"name\":\"nginx\",\"ports\":[{\"containerPort\":80}],\"readinessProbe\":{\"failureThreshold\":3,\"httpGet\":{\"path\":\"/\",\"port\":80},\"periodSeconds\":10,\"successThreshold\":1,\"timeoutSeconds\":1},\"resources\":{\"limits\":{\"cpu\":\"100m\",\"memory\":\"100Mi\"},\"requests\":{\"cpu\":\"10m\",\"memory\":\"100Mi\"}},\"startupProbe\":{\"failureThreshold\":30,\"httpGet\":{\"path\":\"/\",\"port\":80},\"periodSeconds\":10}}]}}}}\n"},"managedFields":[{"manager":"kubectl","operation":"Update","apiVersion":"apps/v1","time":"2021-11-06T23:51:23Z","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:kubectl.kubernetes.io/last-applied-configuration":{}}},"f:spec":{"f:progressDeadlineSeconds":{},"f:replicas":{},"f:revisionHistoryLimit":{},"f:selector":{},"f:strategy":{"f:rollingUpdate":{".":{},"f:maxSurge":{},"f:maxUnavailable":{}},"f:type":{}},"f:template":{"f:metadata":{"f:labels":{".":{},"f:app":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"nginx\"}":{".":{},"f:image":{},"f:imagePullPolicy":{},"f:livenessProbe":{".":{},"f:failureThreshold":{},"f:httpGet":{".":{},"f:path":{},"f:port":{},"f:scheme":{}},"f:initialDelaySeconds":{},"f:periodSeconds":{},"f:successThreshold":{},"f:timeoutSeconds":{}},"f:name":{},"f:ports":{".":{},"k:{\"containerPort\":80,\"protocol\":\"TCP\"}":{".":{},"f:containerPort":{},"f:protocol":{}}},"f:readinessProbe":{".":{},"f:failureThreshold":{},"f:httpGet":{".":{},"f:path":{},"f:port":{},"f:scheme":{}},"f:periodSeconds":{},"f:successThreshold":{},"f:timeoutSeconds":{}},"f:resources":{".":{},"f:limits":{".":{},"f:cpu":{},"f:memory":{}},"f:requests":{".":{},"f:cpu":{},"f:memory":{}}},"f:startupProbe":{".":{},"f:failureThreshold":{},"f:httpGet":{".":{},"f:path":{},"f:port":{},"f:scheme":{}},"f:periodSeconds":{},"f:successThreshold":{},"f:timeoutSeconds":{}},"f:terminationMessagePath":{},"f:terminationMessagePolicy":{}}},"f:dnsPolicy":{},"f:restartPolicy":{},"f:schedulerName":{},"f:securityContext":{},"f:terminationGracePeriodSeconds":{}}}}}}]},"spec":{"replicas":2,"selector":{"matchLabels":{"app":"my-app"}},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"my-app"}},"spec":{"containers":[{"name":"nginx","image":"quay.io/testing-farm/nginx:1.12","ports":[{"containerPort":80,"protocol":"TCP"}],"resources":{"limits":{"cpu":"100m","memory":"100Mi"},"requests":{"cpu":"10m","memory":"100Mi"}},"livenessProbe":{"httpGet":{"path":"/","port":80,"scheme":"HTTP"},"initialDelaySeconds":10,"timeoutSeconds":1,"periodSeconds":10,"successThreshold":1,"failureThreshold":3},"readinessProbe":{"httpGet":{"path":"/","port":80,"scheme":"HTTP"},"timeoutSeconds":1,"periodSeconds":10,"successThreshold":1,"failureThreshold":3},"startupProbe":{"httpGet":{"path":"/","port":80,"scheme":"HTTP"},"timeoutSeconds":1,"periodSeconds":10,"successThreshold":1,"failureThreshold":30},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File","imagePullPolicy":"IfNotPresent"}],"restartPolicy":"Always","terminationGracePeriodSeconds":30,"dnsPolicy":"ClusterFirst","securityContext":{},"schedulerName":"default-scheduler"}},"strategy":{"type":"RollingUpdate","rollingUpdate":{"maxUnavailable":1,"maxSurge":1}},"revisionHistoryLimit":10,"progressDeadlineSeconds":600},"status":{}}
98 | ```
99 |
100 | Более того, т.к. это REST, никто не мешает использовать `curl`, писать собственные скрипты или использовать готовые библиотеки для общения с API Servre k8s и делать с ним все, что захочется
101 |
102 | - Authentication and authorization. Вся авторизация и аутентификация происходит через API Server.
103 | Если, например, у пользователя есть неймспейс, то его доступ к этому неймспейсу разрешается через API Server. Если этот пользователь захочет подключиться к чужому неймспейсу, то получить ошибку "forbidden ... нет прав на взаимодействие ..." - собственно говоря, API Server тот, кто разрешает/запрещает доступ к ресурсам. В дальнейших лекциях будут изучаться политики доступа и механизм [RBAC (Role-Based Access Control)](https://kubernetes.io/docs/reference/access-authn-authz/rbac/).
104 |
105 | API Server не только центральный компонент для пользователя (потому что мы с ним взаимодействуем), это центральный компонент для кластера: все компоненты так или иначе берут информацию из API Server'а, складывают информацию в API Server - обновляя свой статус, обновляя статус контейнера, который запущен в кластере и так далее.
106 |
107 | Тут важно понимать, что API Server - это единственный компонет, который общается с etcd. Т.е. API Server сам по себе информацию не хранит - он ее передает в etcd, он ее из etcd читает, т.е. фактически является stateless компонентом, как и на самом деле все остальные компненты в кластере k8s, кроме непосредственно самого etcd.
108 |
109 | API Server единственный, кто общается с etcd.
110 |
111 | etcd единственный, кто хранит информацию о кластере.
112 |
113 | **По поводу количества API Server:**
114 | он не хранит состояния, у него нет понятия мастер-ведомый. Обычно для отказоустойчивых кластеров запускают 3 ноды с мастером, т.е. просто 3 сервера на виртуалке/физических серверах. На каждую ноду устанавливают по etcd и по API Server'е. При этом API Server'а смотрят как бы в кластер etcd, общаются с etcd.
115 |
116 | Тут есть разные подходы:
117 |
118 | - либо каждый API Server смотри в свой etcd
119 | - либо API Server'а смотрят во все инстансы etcd и общаются с ним как с кластером
120 |
121 | на самом деле это особой роли не играет.
122 |
123 | Но API Server'а работают в параллели для отказоустойчивости. Помним, что никто не запрещает запуск только одного API Server'а - просто если он выйдет из строя, кластером управлять будет нельзя.
124 |
125 | ### Работа API Server на практике
126 |
127 | 
128 |
129 | Для упрощения примера используется создание ReplicaSet, а не Deployement, как это практически в 100% случаях бывает в жизни. Позже будет показано, где и что поменяется в случае Deployment.
130 |
131 | Помним, что в описании ReplicaSet есть template, по которому в дальнейшем будут создаваться Pod'ы. В template есть указание, сколько реплик из шаблона Pod'а нужно запустить.
132 |
133 | Как это работает
134 |
135 | 1. Пользователь берет yaml с описанием ReplicaSet, например, replicaset.yaml
136 | 2. С помощью команды `kubectl create/apply -f replicaset.yaml` запускается процесс создания ReplicaSet и всех нижележащих компонентов
137 | 3. kubectl подключается к API Server (помним, что можно попросить kubectl вывести информацию о взаимодействии с API Server с помощью ключа `-v=10`, подробнее можно прочитать в справке `kubectl options`)
138 | 4. передает туда информацию о том, что создается ReplicaSet
139 | 5. передается описание из yaml, которое в процессе конвертиться в json
140 | 6. API Server в ответ на это складывает информацию о том, что в кластере появился ReplicaSet, который хочет создать пользователь в etcd
141 | 7. etcd по достижению кворума отвечает API Server'y, что информация сохранена
142 | 8. После чего API Server возвращает ответ kubectl (а тот в свою очередь выводить сообщение пользователю), что ReplicaSet в кластере создался
143 |
144 |
149 |
150 | 9. На слайде выше указана группа Others - это, собственно говоря некоторые другие компоненты кластера k8s, которые смотрят в API Server, видят информацию о том, что там происходит и пытаются как-то дальше с этой информацией работать. Про тех, кто входит в группу Others ниже.
151 |
152 | ## [Controller-manager](https://youtu.be/0rN_tUIZx7Y?t=1003)
153 |
154 | Фактически представляет из себя один бинарник, ровно так же как и все остальные компоненты k8s: API Server, etcd и другие.
155 |
156 | Есть практики, когда его запускают вне кластера, вне docker'а, вне контенера, просто, как, например, systemd unit.
157 | Но обычно это такой же контейнер, такой же компонент кластера как и всё остальное. И etcd запускают как часть кластера, т.е. как Pod в кластере, и API менеджер, и остальные.
158 |
159 | Тем не менее внутри контейнера для Controller-manager находится один бинарь, который в свою очередь содержит n'ое количество контроллеров.
160 |
161 | **Что такое контроллер?**
162 |
163 | Контроллер для кластера k8s - это фактически то, что реализует сами абстракции k8s, как-то с ними взаимодействует, создает Pod'ы из ReplicaSet'ов. Создает CronJob'ы, Job'ы, из них что-то генерит. Занимается GarbageCollector. И так далее.
164 |
165 | - Набор контроллеров
166 | - [Node controller](https://youtu.be/0rN_tUIZx7Y?t=1105). Отслеживает информацию о том, что ноды кластера постят о себе информацию. Т.е. он не ходит непосредственно на каждую ноду опрашавиать о ее состоянии, он просто смотри в API Server и видит, что какая-то нода перестала постить свой статус.
167 | - Все ноды кластера k8s раз в какое-то время пишут информацию о себе в API Server, говоря "я все еще живой, я на месте, я тут".
168 | - Соответственно, если все ноды, зарегистрированные в кластере пишут о себе эту информацию, значит с кластером все хорошо.
169 | - Если какая-то нода перестанет о себе писать информацию на какой-то срок (который задан в настройках), то node controller
170 | - заметит эту ситуцию,
171 | - пометит в кластере k8s, т.е. фактически передаст в API Server информацию о том, что эта нода недоступна,
172 | - а далее запустит процесс переноса Pod'ов, которые есть на этой ноде, на другие ноды кластеров
173 | - [Replicaset controller](https://youtu.be/0rN_tUIZx7Y?t=1191). Когда в кластере создается ReplicaSet, у ReplicaSet есть темплейт Pod'ов, созданием Pod'ов по этому описанию как раз и занимается Replicaset controller.
174 | - Т.е. контроллер смотрит в API Server и,
175 | - когда видит, что там появляется описание ReplicaSet с темплейтов для Pod'ов, плюс есть информация о количестве необходимых реплик,
176 | - то создает в кластере нужные Pod'ы
177 | - и записывает информацию о них в API Server
178 | - [Endpoints controller](https://youtu.be/0rN_tUIZx7Y?t=1268). В k8s есть такая сущность как Endopoint'ы, которые автоматически создаются для каждого сервиса.
179 | - И другие ...
180 |
181 |
186 |
187 | - [GarbageCollector](https://youtu.be/0rN_tUIZx7Y?t=1308) - "сборщик мусора".
188 | Возьмем, к примеру, работу Deployment controller
189 | - при изменении описания ReplicaSet будут созданы новые объекты ReplicaSet
190 | - при этом старые ReplicaSet удалены не будут,
191 | - за количество "старых" ReplicaSet, который будут хранится, отвечает параметр [revisionHistoryLimit](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#revision-history-limit) (по умолчанию = 10).
192 | - Как только у Deployment'а количество сохраненных ReplicaSet превышает этот параметр, задача Controller-manager найти самый старый ReplicaSet и удалить его из кластера, т.е. он подчитащает тот мусор, который больше уже не нужен.
193 |
194 | **[Количество Controller Manager в кластере](https://youtu.be/0rN_tUIZx7Y?t=1629):**
195 |
196 | Controller Manager, как и любого другого компонента k8s, в кластере может быть один и более.
197 |
198 | Т.к. это мастеровой компонент, т.е. тот компонент, который управляет жизнедеятельностью кластера, точно так же как в той или иной степени etcd и API Server, они обычно стоят на отдельных нодах, т.е. там же где API Server и etcd, но не там же, где запускается наше приложение в кластере.
199 |
200 | Для dev в маленьком простом кластере это может быть один Controller Manager, но обычно запускают так же 3 - по одному на каждый из master node.
201 |
202 | У Controller Manager есть как раз таки понятие мастер-ведомый и одновременно работу в кластере может выполнять только один Controller Manager.
203 |
204 | Для того, чтобы выбирать себе мастера и понимать, кто из них сейчас должен выполнять работу, они используют механизм [Lease](https://medium.com/michaelbi-22303/deep-dive-into-kubernetes-simple-leader-election-3712a8be3a99):
205 |
206 | **[Вкратце о Lease](https://youtu.be/0rN_tUIZx7Y?t=1680):**
207 |
208 | Когда наши 3 Controller Manager'а стартуют в кластере, каждый из них пытается в API Server записать информацию о том, что он мастер и будет выполнять работу.
209 |
210 | Первый, у кого получилось записать такую информацию и становится мастером.
211 |
212 | Остальные видят, что в кластере уже есть информация о том, что есть мастер controller manager и дальше просто следят за тем, что master регулярно обновляет информацию о себе, т.е. он живой и занимается работой.
213 |
214 | Если мастер в течении какого-то времени не обновил информацию о себе, это означает, что его больше нет в кластере и оставшиесь Controller Manager ровно по тому же алгоритму будут пытаться прописать себя мастером. Первый у кого это получилось, становится новым мастером.
215 |
216 | Если через какое-то время старый мастер оживает, то он видит, что он больше не мастер и начинает просто следить за состоянием текущего мастера.
217 |
218 | ### [Q&A](https://youtu.be/0rN_tUIZx7Y?t=1398)
219 |
220 | 1. **Q:** Откуда node manager знает о содержимом упавшей ноды для переноса, если вся эта информация хранится в etcd
221 | **A:** Все верно, вся информация хранится в etcd.
222 | 1. Controller manager обращается к API Server'y.
223 | Т.е. если controller manager'y нужна какая-та информация, которую он хотел бы знать о ноде или о чем-то еще, оно делает запросы к API Server'y точно так же, как может сделать пользователь с помощью `kubectl`, например, `kubectl get pod ...` На самом деле, запросы идут в апи, но суть та же.
224 | 2. И API Server лезет в etcd, собирает там всю нужную информацию и отдает обратно.
225 | 2. **Q:** Какой процесс на ноде отправляет инфу node-controller'y?
226 | **A:** kubelet отправляет всю информацию в API Server (и больше никому). Все заинтересованные в этой информации, могут запросить ее у API Server'а. Т.е. node-controller спрашивает API Server о статусе нод: что там kubelet писал в последний раз, когда и т.д.
227 |
228 | ### [Работа Controller-manager на практике](https://youtu.be/0rN_tUIZx7Y?t=1512)
229 |
230 | 
231 |
232 | Сереньким обозначены этапы, которые уже были пройдены (см. выше в разделе про API Server).
233 |
234 | 1. Процесс Controller-manager всегда висит запущенный в кластере
235 | 2. Смотри в API Server - это называется "подписан на события"
236 | Расмотрим ситуацию на примере Replication Controller.
237 | 1. Replication Controller подписан к API Server'y на событие "Создание нового ReplicaSet"
238 | 2. Как только такое событие происходит, Controllare Manager, а точнее Replica Controller внутри него, получает от API Server данное событие.
239 | 3. Controller Manager берет манифест нужного ReplicaSet у API Server'а
240 | 4. На основании поля templates генерит описание Pod'ов в формате json
241 | 5. Отправляет это описание обратнов API Server.
242 | 6. API Server складывает это в etcd
243 | 7. и возвращает ответ Contreller Manager'y, что была создана запись о Pod'е.
244 |
245 |
249 |
250 |
251 | ## [Scheduler](https://youtu.be/0rN_tUIZx7Y?t=1768)
252 |
253 | Также относится к группе компонентов мастеров, которые занимаются управление кластера k8s.
254 |
255 | Назначает Pod'ы на ноды (которые у нас были нагенерены Controller-manager, см. предыдущий раздел), учитывая:
256 |
257 | - [QoS (Quolity of Service)](https://youtu.be/0rN_tUIZx7Y?t=1823) политика. В зависиомсти от соотношения ресурсов limit и request scheduler принимает решение, куда назначать данный Pod.
258 | Например, если на каких-то нодах есть нехватка оперативной памяти, то понятно, что туда не будут назначаться Pod'ы.
259 | Если на каких-то нодах есть много свободных ресурсов, то Pod'ы best effort, у которых не указано ни лимитов, ни реквестов, скорее всего будут назначаться на такие ноды.
260 | - [Affinity / anti-affinity](https://youtu.be/0rN_tUIZx7Y?t=1867). Фактически это механихм в кластере k8s, который позволяет сказать, что "я бы хотел, чтобы вот эти Pod'ы запускались на определенной группе нод". Например на нодах, у которых есть gpu (графические процессоры и т.д.). Или сказать, что в эта группа Pod'ов (например, Pod'ов одного деплоймента) никогда не могли запуститься на одной и тоже же ноде, т.е. чтобы кластер k8s всегда их размазывал по разным нодам.
261 | - [Requested resources](https://youtu.be/0rN_tUIZx7Y?t=1904). K8s старается ваши нагрузки равномерно распределять по нодам, т.е. если на одной ноде занято 2 cpu, а на другой 0, то scheduler это видит, он всю эту информацию может читать из API Server'а (точно так же как и Controller Manager и все остальные в кластере) и он примет решение о том, что кажется этот Pod более приоритетный и его надо отправить на ноду, на которой нет занятых ресурсов.
262 | Если подробнее об этом говорить, то на самом деле все эти механизмы - это механизмы фильтрации:
263 | - Сначала scheduler убирает из списка всех нод в кластере те, которые точно не подойдут
264 | - Затем включается механизм scoring'а (от score=балл) Т.е. он каждой из оставшихся ноды (которые подходят по параметрам) на основании всяческих разных политик, выставляет баллы по каждому из параметров, на которые он умеет смотреть.
265 | - Нода, которая получает больше всего баллов, в итоге становится той нодой, на которуюбудет назначен Pod - на которой он будет запускаться по итогу.
266 | - Priority Class
267 | У нас есть какие-то поды более приоритетные, какие-то менее приоритетные.
268 | Например, у нас есть сервис, который занимается рассылками и, если он не будет работать полчаса и рассылки придут позже, то никто не умрет.
269 |
270 | Пример из реальной жизни лектора:
271 |
272 | Одна из политик scoring'а, которая есть в scheduler - это скачанный образ из которого хочет запуститься ваш контейнер.
273 |
274 | Т.е. scheduler предпочитает среди прочего ту ноду, на которой уже есть образ из которого хочет запуститься ваш контейнер - Pod.
275 |
276 | При прочих равных, если на ноде ваше приложение уже запускалось, то scheduler выберет эту ноду.
277 |
278 | В приведенном лектором примере, данный механизм мешался и они принудительно его отключали (делается при помощи конфигов).
279 |
280 |
284 |
285 | ### Работа Scheduler на практике
286 |
287 | 
288 |
289 | 1. Controller manager создал в кластере описание пода
290 | 2. и сложил в формате json в api server
291 | 3. scheduler так же как и controller manager подписан на определенные событие в k8s: события, которые его интересуют - это создание новых подов
292 | 4. как только scheduler видит в k8s api serever событие создание нового пода, которые еще не был назначен на конкретную ноду
293 | 5. scheduler берет описание этого пода
294 | 6. понимает, на какую ноду можно назначить этот под
295 | 7. и дописывает в описание пода кусок о том, что этот под д.б. запущен на такой-то ноде
296 | 8. после чего отправляет обновленное описание обратно в api server.
297 | 9. Api server складывает обновленное описание в etcd и говорит scheduler'y "всё хорошо, я записал"
298 |
299 |
304 |
305 | Касательно количества Scheduler'ов - ситуация ровно такая же, как и с Controller Manager'ом:
306 | Может работать один, может быть кластер с одним scheduler'ом и это нормально.
307 |
308 | Но по факту в кластерах запускаю 3 штуки - по одному на каждой мастер ноде. Точно так же как и Controller manager есть понятие мастера - в каждый конкретный момент времени работу выполняет только один scheduler. Если мастер пропадает, оставшиеся scheduler'ы конкурируют за право быть мастером. Тот, у кого получилось, становится мастером. У кого не получилось, висит и ждет, когда мастер пропадет.
309 |
310 | ### Q&A
311 |
312 | **Q:** Если работает один мастер, а второй проснувшийся ранее бывший мастером - уже не в статусе мастера, то второй работает вхолостую?
313 |
314 | **A:** Да. Один работает, остальные ничего не делают и ничего не жрут, просто смотрят в api server и ждут, когда отвалится мастер.
315 |
316 | **Q:** Как реализован watch
317 |
318 | **A:** Это реализовано на механизмах http2 - на long polling и т.д.
319 |
320 |
324 |
325 | # [Worker компоненты](https://youtu.be/0rN_tUIZx7Y?t=2290)
326 |
327 | Не управляют кластером k8s, управляют своими локальными нодами
328 |
329 | ## [Kubelet](https://youtu.be/0rN_tUIZx7Y?t=2295)
330 |
331 | - Работает на каждой ноде
332 | Правда на 100% - работает абсолютно на всех нодах кластера: как на worker'ах, так и на master нодах. Master-нода ровно такая же нода калстера, как и все остальные. Единсттвенное, что ее отличает от других нод кластера, что она специальным образом обозначена и всем остальным подам запрещено на этих нодах запускать. Хотя это ограничение можно снять.
333 | - Обычно единственный компонент, работающий не в Docker.
334 | Потому что это единственный компонент, который общается с docker'ом или другой системой контейниризации (например, kryo и т.д.). Обменивается информацией о том, какие контейнеры нужно запускать, какие нужно останавливать и т.д.
335 | Работает просто как процесс на хосте.
336 | - Отдает команды Docker daemon
337 | Т.е. по факту общается с docker daemon'ом посредством api docker, чтобы манипулировать контейнерами.
338 | - Создает POD'ы
339 | - Выплоняет проверки: liveness, readiness and startup probes.
340 | Это означает, что нужно учитываеть, что kublet эти запросы выполняет с той же ноды, на которой запущено приложение (грубо говоря по localhost) и у него есть возможность да него достучаться, даже если сама нода отвалилась от кластера, и kubelet об этом не узнает. Но при этом сам кластер k8s поймет, что нода отвалилась и уберет ее из балансировки.
341 | - Отправляет в Api Server информацию о своей ноде:
342 | - я жив, мы с моей нодой на месте
343 | - у меня запущены такие-то контейнеры, в таком-то статусе они находятся
344 | - сколько ресурсов на ноде осталось, сколько было запрошено, сколько было изначально
345 |
346 | Соответственно, если до Controller manager перестанет доходить вся эта информация, Controller manager пометит всю ноду как недоступную и все приложения этой ноды он также пометит как недоступные и уберет их из балансировки, выкинет из сервисов, чтобы сервисы не вели на недоступные поды. И, соответственно, весь трафик перераспределится на все доступные ноды в рамках сервисов.
347 |
348 |
349 | ### [Работа kubelet на практике](https://youtu.be/0rN_tUIZx7Y?t=2595)
350 |
351 | 
352 |
353 | После того как Scheduler выбрал ноду для пода и сохранил эту информацию в описании пода в игру вступает kubelet.
354 |
355 | - kubelet (который висит на каждой ноде) смотри в api server и ждет информацию о том, что на его ноду был назначен под.
356 | - соответствено, kubelet той ноды, на которую Scheduler назначил контейнер, видит, что появилась информаци о поде, назначенном на его ноду, но у него еще не запущенном
357 | - kubelet информирует докер о том, что нужно скачать образ и запустить под контейнер с сетью и сам процесс.
358 | - docker выполняет всю работу по запуску, о которой его попросил kubelet, а kubelet периодически пересылает информацию о статусе котейнера в api server.
359 | - Api server сохраняет всю эту информацию в etcd
360 |
361 |
366 |
367 |
374 |
375 | ## [Kube-proxy](https://youtu.be/0rN_tUIZx7Y?t=3013)
376 |
377 | kube-proxy - компонент, который занимается реализацией некоторых сетевых абстракций.
378 |
379 | Важный момент: kube-proxy не занимается непостредственно реализацией сети - этим занимается отдельный компонент (calico и прочее).
380 |
381 | - Смотрит в Kube-API
382 | - Стоит на всех серверах (в том числе и на мастер нодах)
383 | - Управляет сетевыми правилами на нодах
384 | - Фактически реализует Service (ipvs и iptables)
385 |
386 | # [To be continued ...](https://youtu.be/0rN_tUIZx7Y?t=3042)
--------------------------------------------------------------------------------