├── 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 ![презентация](./assets/lesson_3/3.Application_abstractions_02.png) 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 ![презентация](./assets/lesson_3/3.Application_abstractions_03.png) 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 | - Выдача ресурсов настраивается двумя параметрами ![презентация](./assets/lesson_3/3.Application_abstractions_05.png): 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 | ![FailedScheduling.png](./assets/lesson_3/FailedScheduling.png) 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 | ![screenshot1](./assets/lesson_5/hostPath.png) 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 | ![screenshot1](./assets/lesson_5/emptyDir.png) 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 | ![screenshot1](./assets/lesson_5/manually_managed_PV.png) 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 | ![screenshot1](./assets/lesson_5/screenshot1.png) 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 | ![screenshot2](./assets/lesson_5/screenshot2.png) 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 | - ![externalName](./assets/lesson_6/externalName.png) 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 | - ![externalIPs](./assets/lesson_6/externalIPs.png) 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 | - ![headless](./assets/lesson_6/headless.png) 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 | - ![slurmy](./assets/lesson_6/slurmy.png) 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 | - ![iptables1](./assets/lesson_6/iptables1.png) 420 | - Тут мы видим, что трафик, приходящий на IP адрес сервиса (1.1.1.1), на порт 80, по протоколу tcp, должен отправляться в определённую цепочку iptables (KUBE-SVC-UT6...) 421 | - ![iptables2](./assets/lesson_6/iptables2.png) 422 | - А здесь, что пришедший трафик будет с вероятностью 50% отправляться в другую цепочку (KUBE-SEP-MMY...), а тот трафик, что не был распределён, если правило вероятностного распределения не сработало, пойдет в следующую цепочку (KUBE-SEP-J33...) 423 | - ![iptables3](./assets/lesson_6/iptables3.png) 424 | - Тут мы видим, что пришедший трафик будет отправляться на определённый адрес и порт. Аналогичное правило мы увидим для другой цепочки (KUBE-SEP-J33...) 425 | - Адреса, указанные в этих правилах ни что иное, как адреса подов приложения 426 | - Итого, при создании сервиса у нас образуется несколько правил iptables которые через `--mode random probability <с какой-то вероятностью>` отправляет трафик на IP адреса наших подов по тем портам и протоколам, которые в этом сервисе были указаны. Если подов больше двух, то вероятности в правилах будут указаны соответствующие 427 | - Таким образом, мы работаем с привычным iptables или ipvs, а не с чем-то принципиально новым 428 | - На практике, погружаться на этот уровень практически не требуется 429 | - Подытожим, сервис - это: 430 | - ![services](./assets/lesson_6/services.png) 431 | - Полная запись DNS имени сервиса представляет собой конструкцию вида **<имя сервиса>.<имя неймспейса>.svc.<доменное имя кластера>**, но с помощью неких механизмов, которые прописаны в resolv.conf каждого контейнера, мы можем просто обращаться по имени сервиса и попадать в нужное нам приложение или, если оно в другом неймспейсе, то просто **<имя сервиса>.<имя неймспейса>**, а полную конструкцию можно не указывать 432 | 433 | ### Ingress [01:25:29](https://www.youtube.com/watch?v=OmTYdf_uDeQ&t=5129s) 434 | - ![ingress](./assets/lesson_6/ingress.png) 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 | - ![ingressSecretTls](./assets/lesson_6/ingressSecretTls.png) 482 | - Выполнив команду внизу слайда мы создадим секрет, манифест которого описан справа, в котором в закодированном виде будут содержаться ключ и сертификат 483 | - Далее нужно указать данный секрет в секции tls Ingress (левый манифест на слайде), IngressController умеет работать с такими директивами и сделает всю грязную работу 484 | - Если же что-то подобное нужно автоматизировать (опять же, это скорее задача администраторов), можно использовать cert-manager, он регулярно ходит в центр сертификации, например letsencrypt, выписывает их оттуда и подключает в IngresController'ы. Подробнее об этом можно почитать в [документации cert-manager](https://cert-manager.io/docs/) 485 | - ![IngressCertManager](./assets/lesson_6/IngressCertManager.png) 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 | ![001_etcd.png](./assets/lesson_7/001_etcd.png) 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 | ![002_api_server.png](./assets/lesson_7/002_api_server.png) 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 | ![003_controller_manager.png](./assets/lesson_7/003_controller_manager.png) 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 | ![004_scheduler.png](./assets/lesson_7/004_scheduler.png) 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 | ![005_kubelet.png](./assets/lesson_7/005_kubelet.png) 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) --------------------------------------------------------------------------------