date created |
---|
2021-10-12 19:01:26 (+03:00), Tuesday |
Урок 4: Хранение конфигураций. Вечерняя школа «Kubernetes для разработчиков» youtube
Вступление (00:01:45)
- Каким образом в кластере k8s можно передавать конфигурации в наши приложения
- Самый простой и неправильный вариант - захардкодить конфигурацию в контейнер и запускать приложение в таком неизменном виде. Не нужно так делать!
- Более цивилизованные варианты будут представлены далее
Представление преподавателя (00:02:31)
- Сергей Бондарев
- Архитектор Southbridge
- Инженер с 25-летним стажем
- Certified Kubernetes Administrator
- Внедрения Kubernetes: все куб-проекты Southbridge, включая собственную инфраструктуру
- Один из разработчиков kubespray с правами на принятие pull request
Env, переменные окружения (00:03:49)
- Работаем в каталоге
~/school-dev-k8s/practice/4.saving-configurations/1.env
- Открываем манифест
deployment-with-env.yaml
# deployment-with-env.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
spec:
replicas: 1
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
name: nginx
env: # Описываем желаемые переменные окружения
- name: TEST # Имя переменной окружения
value: foo # Значение переменной окружения
ports:
- containerPort: 80
resources:
requests:
cpu: 10m
memory: 100Mi
limits:
cpu: 100m
memory: 100Mi
...
- В этом манифесте мы указываем вариант доставки конфигураций внутрь нашего приложения так как это советует 12-factor applications, то есть, доставляем конфигурацию в виде переменных окружения
- У нас среда исполнения (docker, containerD, crio) позволяет при запуске процесса внутри контейнера создать этому процессу переменные окружения
- Самый простой вариант в kubernetes - описать все наши переменные окружения, который должны быть в контейнере, в манифесте (пода, репликасета, но как правило, это деплоймент)
- Смотрим в манифест, комментариями отмечен раздел с переменными окружения в описании контейнера
- Когда мы применим такой манифест в нашем кластере, в соответствующем контейнере появится переменная TEST со значением foo:
$ kubectl apply -f deployment-with-env.yaml
deployment.apps/my-deployment created
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
my-deployment-7b54b94746-blvpg 1/1 Running 0 26s
$ kubectl describe pod my-deployment-7b54b94746-blvpg
... # в выводе будет много информации, нас интересует данный блок, относящийся к контейнеру
Environment:
TEST: foo # мы видим, что в контейнере создана переменная "TEST" со значением "foo"
...
- С помощью переменных окружения можно передавать различные конфигурации в наши приложения, соответственно, приложение будет считывать переменные окружения и применять их на своё усмотрение, например, таким образом можно передавать различные данные, как правило, конфигурационные
- Единственный, но достаточно большой минус такого подхода - если у вас есть повторяющиеся настройки для различных деплойментов, то нам придётся все эти настройки придется повторять для каждого деплоймента, например, если поменяется адрес БД, то его придётся изменить, скажем, в десятке деплойментов
- Как этого избежать? Очень просто:
ConfigMap (00:08:19)
- ConfigMap, как и всё в kubernetes, описывается в yaml файле, в котором, в формате key:value описаны настройки, которые можно использовать в нашем приложении
- в ConfigMap имеется раздел data, собственно там мы и задаём наши настройки, а потом этот словарь, этот ConfigMap, целиком указать в манифесте деплоймента, чтобы из него создать соответствующие переменные окружения в нашем контейнере
- Таким образом, за счёт ConfigMap мы можем уменьшить дублирование кода и упростить настройку однотипных приложений
Идём в терминал (00:09:27)
- В том же каталоге
~/school-dev-k8s/practice/4.saving-configurations/1.env
открываем манифест
# configmap.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
name: my-configmap-env
data: # интересующий нас раздел с настройками
dbhost: postgresql # параметр 1
DEBUG: "false" # параметр 2
...
- Применяем наш ConfigMap и смотрим что получилось
$ kubectl apply -f configmap.yaml
configmap/my-configmap-env created
$ kubectl get cm # cm - сокращение для configmap
NAME DATA AGE
kube-root-ca.crt 1 11d # служебный configmap, созданный автоматически
my-configmap-env 2 79s # результат наших действий
- DATA со значением 2 означает, что в данном конфигмапе находится 2 ключа
Как использовать ConfigMap (00:10:56)
- Традиционно, через специально подготовленный манифест
# deployment-with-env-cm.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
spec:
replicas: 1
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
name: nginx
env:
- name: TEST
value: foo
envFrom: # раздел для загрузки переменных окружения извне
- configMapRef: # указываем, что будем брать их по ссылке на конфигмап
name: my-configmap-env # указываем наш объект ConfigMap, из которого будут загружаться данные
ports:
- containerPort: 80
resources:
requests:
cpu: 10m
memory: 100Mi
limits:
cpu: 100m
memory: 100Mi
...
- Результатом применения данного файла будет деплоймент, содержащий в себе контейнер с переменными окружения, прописанными в разделе DATA конфигмапа my-configmap-env
- Но если мы заглянем в информацию о созданном таким образом поде, то увидим похожую картину:
...
Environment Variables from:
my-configmap-env ConfigMap Optional: false
Environment:
TEST: foo
...
- То есть, переменные, заданные через env мы можем видеть, а через envFrom - нет, только название объекта, из которого они загружаются
- Мы можем заглянуть в ConfigMap, чтобы увидеть, что должно быть записано в переменных окружения:
$ kubectl get cm my-configmap-env -o yaml
apiVersion: v1
data: # интересующий нас раздел
DEBUG: "false"
dbhost: postgresql
kind: ConfigMap
# далее ещё много всего, в основном, создаваемые автоматически значения по умолчанию
- Также, мы можем сходить внутрь контейнера и через его шелл посмотреть переменные окружения, примерно так:
$ kubectl exec -it my-deployment-7d7cff784b-rjb55 -- bash
# "--" - это разделитель, обозначающий конец команды по отношению к kubectl и начало команды для пода
# в реультате выполнения увидим приглашение:
root@my-deployment-7d7cff784b-rjb55:/#
# выйти можно по сочетанию ctrl+d или командой exit
# также, мы можем сразу же обратиться к переменным окружения:
$ kubectl exec -it my-deployment-7d7cff784b-rjb55 -- env
# на выходе будет список всех переменных окружения
Что будет, если данные в ConfigMap поменяются? (00:15:22)
- У запущенного контейнера переменные окружения поменять не так просто и kubernetes этим не занимается
- Если контейнер или под, уже запущены, то изменения ConfigMap на его переменных окружения не отразятся
- Таким образом, если мы хотим, чтобы приложения стало работать с новыми переменными окружения, для этого необходимо убить поды и создать новые
Какой приоритет env и envFrom? (00:16:13)
- Сергей предлагает поэкспериментировать
Что будет, если разные ConfigMap будут содержать одинаковые переменные (00:16:38)
- Вероятно, один из конфигмапов "победит"
- Можно найти в документации, ctrl+f по "EnvFromSource array", также, разжевано на SO
- Значится там следующее - значения заданные в env не могут быть перезаписаны, а если ключ содержится в разных источниках (таких как ConfigMap), будет использовано значение из последнего
Secret (00:17:16)
- Используется для работы с чувствительными данными, такими как токены доступа, пароли, приватные ключи сертификатов
- Имеет структуру аналогичную ConfigMap, данные задаются в разделе data
- Бывает нескольких типов:
- generic - самый распространенный тип, обычно используется для токенов и логинов с паролями
- docker-registry - данные для авторизации в docker registry
- фактически, является секретом с заранее определённым списком ключей в массиве data, содержит, в частности:
- ключ, отвечающий за адрес репозитория
- ключи, отвечающий за логин, пароль и почту
- фактически, является секретом с заранее определённым списком ключей в массиве data, содержит, в частности:
- tls - предназначен для хранения сертификатов для шифрования данных, для HTTPS TLS протокола
- как правило, используется в Ingress
- имеет 2 предопределённых поля:
- приватный ключ
- сам подписанный сертификат
Создаём секрет (00:20:53)
- Переходим в
~/school-dev-k8s/practice/4.saving-configurations/2.secret
- Подсматриваем в README.MD и выполняем команды оттуда:
# создаём секрет
$ kubectl create secret generic test --from-literal=test1=asdf --from-literal=dbpassword=1q2w3e
secret/test created
$ kubectl get secret
NAME TYPE DATA AGE
default-token-wgc7r kubernetes.io/service-account-token 3 11d
s024713-token-8vmmm kubernetes.io/service-account-token 3 11d
test Opaque 2 8s
$ kubectl get secret test -o yaml
apiVersion: v1
data:
dbpassword: MXEydzNl
test1: YXNkZg==
kind: Secret
metadata:
creationTimestamp: "2021-10-12T17:43:10Z"
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:dbpassword: {}
f:test1: {}
f:type: {}
manager: kubectl-create
operation: Update
time: "2021-10-12T17:43:10Z"
name: test
namespace: s024713
resourceVersion: "31214172"
selfLink: /api/v1/namespaces/s024713/secrets/test
uid: 9a98d624-1a40-40d0-a347-9feb852373ab
type: Opaque
- Важно! Секреты типа kubernetes.io/service-account-token удалять не нужно, т.к. они отвечают за работу с учебным кластером, они автоматически пересоздадутся, но доступ будет утерян
- При создании generic секрета мы получаем на выходе секрет с типом Opaque, что означает "непрозрачный". Данное расхождение в именовании сложилось исторически, все об этом знают и живут с этим.
- Заглядывая в вывод
kubectl get
мы видим, что значения секретов в разделе data отличаются от заданных, это они же, закодированные base64 - Важно понимать, что кодирование - это не шифрование и данные приводятся к исходному виду очень просто, без всяких ключей шифрования
- Зачем это нужно, если механизм декодирования так прост?
- Одна из причин - обработанные таким образом строки хорошо воспринимаются yaml'ом, не нужно думать об экранировании
- В kubernetes есть механизм RBAC, позволяющий ограничивать права пользователей на доступ к различным объектам kubernetes
- По умолчанию, возможность просматривать и редактировать секреты через edit есть только у роли администраторов
- Однако, не стоит забывать, что настройки, доставленные в приложение, могут быть обработаны этим самым приложением, так что, при желании, разработчики могут их получить. Обычно это уже головная боль специалистов по безопасности
Посмотрим на деплоймент, который будет использовать наш секрет (00:28:38)
# deployment-with-secret.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
spec:
replicas: 1
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
name: nginx
envFrom:
- configMapRef:
name: my-configmap-env
env:
- name: TEST
value: foo
- name: TEST_1 # здесь мы можем указать название переменной, отличное от имени ключа в секрете или конфигмапе
valueFrom: # таким образом можно получать значения конкретных ключей из конфигмапов и секретов
secretKeyRef:
name: test
key: test1
ports:
- containerPort: 80
resources:
requests:
cpu: 10m
memory: 100Mi
limits:
cpu: 100m
memory: 100Mi
...
- Применяем и смотрим что получилось:
$ kubectl apply -f deployment-with-secret.yaml
deployment.apps/my-deployment configured
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
my-deployment-57b8cc674c-qc9rk 1/1 Running 0 22s
$ kubectl describe pod my-deployment-57b8cc674c-qc9rk
# нас интересует данная секция:
...
Environment Variables from:
my-configmap-env ConfigMap Optional: false
Environment:
TEST: foo
TEST_1: <set to the key 'test1' in secret 'test'> Optional: false
...
- Видим, что в describe нашего объекта значение секрета скрыто
- Но если мы сходим внутрь пода через exec, то мы сможем увидеть содержимое переменных окружений (если такая возможность не отключена, как правило, именно так и поступают)
stringData в секретах (00:34:37)
- Пример манифеста:
# secret.yaml
---
apiVersion: v1
kind: Secret
metadata:
name: test
stringData:
test: updated
...
- Мы видим, что здесь значение ключа test незакодировано
- Данный раздел был придуман для упрощения работы с секретами и их перекодировкой из/в base64
- Данные, занесенные в раздел stringData будут перенесены в секреты и закодированы соответствующим образом
$ kubectl apply -f secret.yaml
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.
secret/test configured
- В предупреждении речь идёт о том, что для корректной работы, а именно, для обработки мерджей конфигураций (активной и применяемой по apply), рекомендуется выполнять создание объектов определённым образом, иначе могут возникнуть побочные эффекты, а в нашем конкретном случае kubectl сам исправит данный недочёт
- В конце урока обещали скинуть ссылки на почитать
- После применения данного манифеста, если мы посмотрим на наш секрет, увидим следующее:
$ kubectl get secret test -o yaml
apiVersion: v1
data:
dbpassword: MXEydzNl
test: dXBkYXRlZA==
test1: YXNkZg==
kind: Secret
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","kind":"Secret","metadata":{"annotations":{},"name":"test","namespace":"s024713"},"stringData":{"test":"updated"}}
# далее инфа, нам не актуальная
-
В секции data добавился секрет test
-
В аннотации добавился ключи last-applied-configuration, с информацией о применённых нами изменениях
-
Почему это важно? Потому что, если мы попробуем исправить наш yaml (в нашем случае мы меняем имя ключа test на test1) и повторно выполнить apply, то мы увидим странное:
$ kubectl get secrets test -o yaml
apiVersion: v1
data:
dbpassword: MXEydzNl
test: ""
test1: dXBkYXRlZA==
kind: Secret
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","kind":"Secret","metadata":{"annotations":{},"name":"test","namespace":"s024713"},"stringData":{"test1":"updated"}}
# далее инфа, нам не актуальная
- Наш секрет записался в ключ test1, а значение ключа test обнулилось, т.к. за счёт данных в last-applied-configuration kubernetes понял, что раньше мы работали с ключом test, а теперь его нет
- Далее ведущий демонстрирует, что изменение секрета не затрагивает запущенный под, затем убивает его и проверяет, что в новом поде секрет обновился
Q&A (00:44:28)
- |
- Q: А если надо передать IP новой POD'ы? Например, заскейлили новый memcached - приложению надо знать все IPs POD'ов memcached'а?
- A: Это делается по другому, через сущность под названием "сервисы", будет позже, в лекции Марселя, конкретно про memcached, там будут безголовые, так называемые headless сервисы
- |
- Q: Как из vault передавать пароли в kubernetes
- A: Это есть в курсе Слёрм-Мега, если вкратце, в vault есть модуль для интеграции с kubernetes и можно научить приложение ходить в vault с токеном, который ему даст kubernetes и по этому токену брать данные из vault
- |
- Q: Зачем "--" перед env в команде типа
kubectl exec -it my-deployment-7d7cff784b-rjb55 -- env
- A: Отвечает за разделение между окончанием команды kubectl и началом команды к поду
- Q: Зачем "--" перед env в команде типа
Volumes (00:47:04)
- Если вспомнить про docker, docker compose, то мы знаем, что системы исполнения контейнеров позволяют монтировать внутрь контейнера не просто переменные окружения, но еще и файлы, причём, монтировать файлы с диска внутрь контейнера
- В kubernetes пошли дальше и сделали возможность монтировать в контейнер содержимое секретов и конфигмапов в качестве файлов
- В ConfigMap можно указывать многострочные значения, затем к ним можно будет обратиться через секцию манифеста volumes, таким образом, в контейнер можно монтировать файлы, со значениями из ConfigMap, обычно там хранят целые конфигурации
Экспериментируем (00:48:32)))
- Переходим в
~/school-dev-k8s/practice/4.saving-configurations/3.configmap
- видим манифест ConfigMap с куском конфига nginx:
# configmap.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
name: my-configmap
data:
default.conf: |
server {
listen 80 default_server;
server_name _;
default_type text/plain;
location / {
return 200 '$hostname\n';
}
}
...
- В данном случае, всё что после символа | - это многострочное значение ключа default.conf
- Многострочное значение оканчивается там, где отступ будет меньше чем в начале этого значения, т.е. на том же уровне, что и у ключа, в нашем случае default.conf
- Про многострочный текст через символ | можно почитать здесь. Официальная документация для меня достаточно трудочитаема, кажется, что-то из этого - 1, 2
- Далее смотрим на манифест деплоймента:
# deployment-with-configmap.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
spec:
replicas: 1
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
name: nginx
ports:
- containerPort: 80
resources:
requests:
cpu: 10m
memory: 100Mi
limits:
cpu: 100m
memory: 100Mi
volumeMounts: # Здесь мы описываем точки монтирования томов внутри контейнера
- name: config # Указываем имя тома для монтирования
mountPath: /etc/nginx/conf.d/ # Здесь мы указываем точку монтирования
volumes: # Здесь (на уровень выше!) мы описываем тома
- name: config # Задаём имя тому
configMap: # Указываем, из какого конфигмапа создать том
name: my-configmap
...
- Зачем делать это в 2 этапа, сначала формировать вольюм, затем подключать, почему сразу не подключить данные из конфигмапа в контейнер?
- Потому что обычно один и тот же том монтируют сразу в несколько контейнеров
- Аналогичные манипуляции можно применять не только к конфигмапам, но и к секретам
- Если мы применим данный конфигмап и деплоймент, то сможем увидеть в нашем контейнере файл, и его содержимое:
$ kubectl exec -it my-deployment-5dbbd56b95-xdb2j -- ls /etc/nginx/conf.d/
default.conf
$ kubectl exec -it my-deployment-5dbbd56b95-xdb2j -- cat /etc/nginx/conf.d/default.conf
server {
listen 80 default_server;
server_name _;
default_type text/plain;
location / {
return 200 '$hostname\n';
}
}
- Причём, мы видим, что в созданном файле никаких лишних отступов, как при записи в yaml файле, нет
- Есть ограничения на размер создаваемых файлов (не понятно, техническое или речь о здравом смысле), не стоит создавать файлы по 20МБ
- Очень часто таким образом монтируются файлы с tls сертификатами из секретов
- Далее имеет смысл перемотать на время следующего заголовка
Кое что ещё про монтирование конгфигмапов в качестве файлов (01:21:40)
- Если мы в поде перейдём в директорию /etc/nginx/conf.d и набёрём ls -la, мы увидим что часть содержимого это линки, обозначены как "l"
$ k exec -it my-deployment-5dbbd56b95-lcztg -- bash
# cd /etc/nginx/conf.d
# ls -la
total 12
drwxrwxrwx. 3 root root 4096 Oct 13 20:49 .
drwxr-xr-x. 3 root root 4096 Apr 30 2018 ..
drwxr-xr-x. 2 root root 4096 Oct 13 20:49 ..2021_10_13_20_49_15.281823334
lrwxrwxrwx. 1 root root 31 Oct 13 20:49 ..data -> ..2021_10_13_20_49_15.281823334
lrwxrwxrwx. 1 root root 19 Oct 13 20:49 default.conf -> ..data/default.conf
- Мы видим следующее:
- файл default.conf является линком на файл ..data/default.conf
- Каталог ..data, в свою очередь, тоже является линком на каталог ..2021_10_13_20_49_15.281823334
- Каталог ..2021_10_13_20_49_15.281823334 уже дествительно является каталогом
- Если мы перейдем в него, то увидим наш файл default.conf, он действительно является файлом а не линком
- Таким образом, мы видим, что файл конфигмапа монтируется в специальный каталог, в точку монтирования пробрасывается симлинк
- Это нужно, чтобы атомарно переключать содержимое файлов
- Когда меняется ConfigMap, kubernetes монтирует в контейнер новый каталог с данными из ConfigMap, а после этого происходит подмена симлинка на новый каталог
- Операция смены симлинка атомарна и намного быстрее операции монтирования, таким образом приложение может продолжать работать с файлом без каких либо прерываний
- После успешной смены линка, старый каталог, старая точка монтирования, удаляется
- Далее имеет смысл перемотать на время следующего заголовка
kubectl port-forward (00:54:48)
- Позволяет настроить перенаправления порта извне кластера в под
- Полезно при отладке, тестировании, в процессе разработке, в общем, не для боевого использования
- Выполняется следущим образом:
$ kubectl port-forward my-deployment-5b47d48b58-l4t67 8080:80 &
# амперсанд в конце нужен, чтобы отправить команду в фон. Вернуться к ней можно через команду %, завершить, как обычно, по ctrl+c
# возможно, нужно будет поиграть с подбором порта, если 8080 будет занят
# видим на выходе что-то подобное:
[1] 26503
Forwarding from 127.0.0.1:8080 -> 80
$ curl 127.0.0.1:8080
# стучимся на данный порт и видим ответ:
Handling connection for 8080
my-deployment-5dbbd56b95-xdb2j
- Причём тут port-forward? Чтобы проверить, что наш конфиг из предыдущего шага применился и отработал
- Далее меняем в нашем конфигмапе строку с возвратом, например, добавляем туда
OK\n
в конце, чтобы увидеть разницу - В отличие от переменных окружения, изменение конфигмапа, который мы подключаем как том, отразится на содержимом файла в контейнере (не мгновенно, через несколько секунд)
- Далее снова пробуем стучаться на проброшенный порт, но ответ неизменен
- Это происходит потому что nginx сам по себе не перечитывает конфиг
Downward API (01:06:54)
- Документация:
- Позволяет передать приложению некоторые параметры манифестов как переменные окружения или как файлы
- Позволяет передать приложению различную информацию о том, где оно запущено, например:
- адрес узла
- название узла
- название неймспейса
- название пода
- адрес пода
- реквесты и лимиты, которые описаны в манифесте пода
- на прошлой лекции был вопрос, как приложение может узнать, какие ему заданы реквесты и лимиты, есть 2 варианта:
- если приложение или джава там всякая (чиво ?) уже умеет смотреть в cgroups и брать значения оттуда
- взять значения из переменных окружения, которые мы можем передать туда с помощью Downward API из манифеста нашего контейнера
- на прошлой лекции был вопрос, как приложение может узнать, какие ему заданы реквесты и лимиты, есть 2 варианта:
Экспериментируем (01:08:30)
- Идем в каталог
~/school-dev-k8s/practice/4.saving-configurations/4.downward
- Заглядываем в деплоймент:
# deployment-with-downward-api.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
spec:
replicas: 1
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
name: nginx
env:
- name: TEST
value: foo
- name: TEST_1
valueFrom:
secretKeyRef:
name: test
key: test1
- name: __NODE_NAME # Начало первого интересующего нас блока
valueFrom:
fieldRef:
fieldPath: spec.nodeName # Название узла, ноды, где запущен под
- name: __POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name # Имя пода из нашего манифеста
- name: __POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace # Неймспейс пода
- name: __POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP # IP-адрес пода
- name: __NODE_IP
valueFrom:
fieldRef:
fieldPath: status.hostIP # IP-адрес ноды
- name: __POD_SERVICE_ACCOUNT
valueFrom:
fieldRef:
fieldPath: spec.serviceAccountName # Задаём сервис-аккаунт, будем разбираться далее, в лекции по RBAC
ports: # Конец первого интересующего нас блока
- containerPort: 80
resources:
requests:
cpu: 10m
memory: 100Mi
limits:
cpu: 100m
memory: 100Mi
volumeMounts: # Начало второго интересующего нас блока
- name: config
mountPath: /etc/nginx/conf.d/
- name: podinfo # Монтируем том downwardAPI, аналогично монтированию тома конфигмапы
mountPath: /etc/podinfo
volumes:
- name: config
configMap:
name: my-configmap
- name: podinfo # Описываем создание тома downwardAPI
downwardAPI:
items:
- path: "labels" # Указываем название создаваемого файла
fieldRef: # Указываем, что мы заполняем наш файл на базе ссылки на поля манифеста
fieldPath: metadata.labels # Указываем раздел манифеста, из которого мы будем забирать информацию
- path: "annotations"
fieldRef:
fieldPath: metadata.annotations
... # Конец второго интересующего нас блока
- Нам интересны блоки подобные этому:
- name: __POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- В поле fieldPath мы указываем интересующие нас поля, которые мы хотим передать внутрь контейнера, это путь согласно yaml манифесту, структура обращения такая же как при вызове команды kubectl explain
- Некоторые значения берутся из манифеста, некоторые подставляет kubernetes, например, те что в секции status. О каждом имеет смысл читать отдельно в документации
- Аннотации (annotations) - кастомные поля для манифеста, применяются для данных, под которые еще не придумали специальных полей (к данной теме особо не относится)
- 01:14:35
- Q: Для чего сделана такая реализация (подтягивание изменений из configmap/secret)? Стоит ли использовать этот механизм для обновления конфига своих приложений "налету"?
- A: Собственно, для этого такая реализвация и придумана, чтобы обновлять конфиг приложений на лету. Стоит ли использовать - вопрос архитектору приложения. По мнению Сергея лучше выполнять передеплой приложения в случаях, когда изменилась конфигурация, git как единая точка правды, IaC.
- 01:15:37
- Q: Как port-forward проксирует запрос в кластере? как-то настраивает ингрес контролер?
- A: Нет, не нужно никаких ингрес контроллеров, просто kubectl создаёт соединение с API сервером, остаётся запущенным в бэкграунде, слушает указанный для форварда порт, в нашем примере 8080, получает запрос, оборачивает его в HTTP запрос, передаёт его в kubernetes API, API дальше передаёт запрос это конкретному поду, получает ответ и обратно через такой вот туннель передаёт
Возвращаемся к консоли (01:16:39)
- Сергей примененяет деплоймент и смотрит в переменные окружения пода, демонстрирует, что переменные, начинающиеся с
__
заполнены - Далее Сергей сверяет полученные данные с теми, что можно увидеть по команде
kubectl get pod -o wide
, данная команда даёт расширенную информацию, в том числе IP адрес пода, имя ноды
Смотрим в полученный том podinfo (01:20:10)
- Идём внутрь пода и выводим содержимое файлов в папке /etc/podinfo:
- annotations содержит различные автоматически сгенерированные данные, в частности:
- Когда под был создан
- Как он был создан, в нашем случае, через API
- Какая применена политика безопасности, в нашем случае restricted
- labels показывает метки на нашем поде, в нашем случае это
app="my-app"
, если вспомнить прошлую лекцию, по этим меткам деплоймент определяет принадлежность подов
- annotations содержит различные автоматически сгенерированные данные, в частности:
- Если в блоке по ConfigMap перематывали, стоит блок 01:21:40 - 01:24:54 пропустить
- 01:24:54
- Q: Container creating 4 минуты
- A: Это не очень нормально, смотрите describe, что там происходит, почему он не может создаться
- 01:25:08
- Q: __POD_NAMESPACE=default в кластере mcs
- A: Не может быть такого, у учеников нет прав на данный неймспейс
- 01:25:42
- Q: По поводу того, что пока в школе идёт упор на особенности работы kubernetes, а не на use cases взаимодействия разработчиков с kubernetes
- A: У нас сейчас идут лекции, которые рассчитаны на базовое знакомство с основными абстракциями kubernetes, чтобы можно было на этой основе строить дальнейшее обучение
- 01:27:15
- Q: Почему на учебном стенде нет подов в статусе pending, при увеличении реквестов и лимитов (3я лекция, добавить ссылку на конспект)?
- A:
- Так происходит, потому что в kubernetes есть несколько механизмов, которые позволяют ограничивать аппетиты разработчиков и не позволяют запускать больше определённого количества подов в одном неймспейсе или запрашивать под свои поды больше определённого количества ядер процессора
- Кластер создавался под 14000 человек, были выставлены жесткие ограничения на неймспейсы студентов, поэтому в данном случае до создания подов даже не доходит
- Об этом будет позже, механизмы называются Resource Quotas и Limit Ranges
- Resource Quotas отвечает за общее количество ресурсов в неймспейсе, поэтому в нашем случае не доходило до создания подов, кажется, там задано 0.5 или 1 CPU на неймспейс, а мы просили сильно больше
- kubectl get events replicaset позволял посмотреть события и увидеть причины, по которой поды не могли создаться
- Если бы данных ограничений не было, то поды создались бы, но проблема возникла бы на следующем этапе, когда происходит поиск подходящей ноды для запуска пода, т.е.
- Limit Ranges позволяет ограничивать потребление ресурсов для конкретных контейнеров в подах
- Мы можем указать, например, что контейнер не может использовать больше определённого объема CPU и памяти
- Также, с их помощью можно указывать значения по умолчанию для ресурсов, в манифесте которых не указаны лимиты и реквесты
- 01:31:08
- Q: Hashicorp Vault как хранилище секретов, его преимущества по сравнению со встроенным механизмом
- A:
- Всё хранится в зашифрованном виде
- Можно использовать различные возможности Vault, такие как:
- Аудит доступа
- Разграничение доступа
- Доступ к информации можно осуществлять различными способами
- Информация хранится централизовано
- Из минусов - появляется дополнительный сервис, который требует обслуживания и является дополнительной потенциальной точкой отказ, напрмер, после перезапуска Vault, всё зависящее от Vault будет простаивать, пока не будет произведён unseal
- 01:31:50
- Q: Где можно хранить сертификаты и закрытые ключи ЭЦП?
- A: В сейфе у директора, в Hashicorp Vault и т.д., вопрос не особо относится к kubernetes
- 01:32:08
- Q: Как сделать динамическое изменение переменных окружения без перезапуска пода?
- A: Сделать pull request в ядро линукса
- 01:32:34
- Q: Можно ли описать переменные окружения, общие для всех контейнеров в поде?
- A:
- Нет, они описываются отдельно для каждого контейнера
- Но можно смонтировать в каждый контейнер один и тот же ConfigMap, т.е. описываем в нём все требуемые переменные окружения и с помощью envFrom подгружаем оттуда переменные окружения во все контейнеры
- 01:33:08
- Q: Почему false в кавычках
- A: В данном случае мы явно указываем, что передаём строку, стоит почитать документацию по yaml
- 01:33:43
- Q: Как автоматически обновлять Deployments/StatefulSets при обновлении ConfigMap/Secrets?
- A:
- Никак, если вы обновляете конфигмапы руками, нужно сделать тот же самый rollout restart для деплоймента, чтобы он перезапустился
- Другое дело, если манифесты лежат в каком-то шаблонизаторе, например Helm (который мы будем проходить), Kustomize, там есть возможность вычислять контрольную сумму ConfigMap и прописывать её в аннотации к деплойменту, таким образом, если config map изменился изменится annotation, произойдет rolling update. (Видимо, нужно почитать или дождаться урока про Helm, чтобы лучше понять всю механику. В чате вроде как полезный комментарий, но мне он пока до конца не понятен)
- 01:35:00
- Q: Зачем нужен Downward API?
- A:
- Чтобы передать в наше приложение информацию из самого kubernetes (данные о поде, ноде, неймспейсе и т.п.)
- Например, можно проверять по неймспейсу окружение, в котором запущено приложение и в зависимости от этого настраивать потребление ресурсов
- Можно отправлять дополнительную информацию с помощью jaeger
- A:
- Q: Зачем нужен Downward API?
- 01:36:23
- Q: Зачем добавлять содержимое файла в yaml-конфиг а не монтировать файл непосредственно
- A: Так проще
- 01:37:24
- Q: Будем ли изучать Vault
- A: Не будем, по волту у Слёрм либо есть, либо готовится отдельный курс
- 01:37:39
- Q: Нужно ли в ConfigMap указывать labels, если да, то для чего?
- A:
- Не нужно, но можно, как и у любого объекта в kubernetes
- Зачем? - Чтобы можно было их выбирать по какому-то признаку
- Например, созданные Helm'ом объекты автоматически получают метки, что объект был создан Helm'ом, из такого-то чарта, в такое-то время и т.п.
- 01:38:10
- Q: Что такое stateful set?
- A: Альтернативный способ запуска приложений, которые хранят своё состояние, например, баз данных, по этой теме будет отдельная лекция
- 01:38:58
- Q: Дефолтные переменные окружения kubernetes_* можно не передавать в контейнер?
- A: Кажется, они по умолчанию все передаются
- 01:39:52
- Q: Любые поля манифеста можно передавать в переменные?
- A: Не любые, обещают скинуть ссылку на документацию, список полей достаточно ограничен
- 01:40:11
- Q: Если хочу видеть лимиты ресурсов по GPU - то нужно пробросить их в переменные среды? Или есть иной способ мониторить лимиты из описания подов?
- A: Сложно, сказать, т.к. это какие-то кастомные ресурсы, нужно смотреть. Но весь раздел c реквестами и лимитами может быть проброшен в виде файлов или через переменные среды с помощью Downward API
- 01:40:57
- Q: Почему в kubernetes не рекомендуется запускать базы данных
- A:
- У Сергея есть отдельный доклад, также есть выступление Дмитрия Столярова из Фланта
- Если кратко, есть 2 проблемы:
- Скорость, в основном работы с дисковыми устройствами, как правило, из-за того что хранилищем обычно выступает нечто сетевое, а не локальные SSD/NVMe накопители
- Сама суть kubernetes такова, что база данных может быть убита и перезапущена в другом месте
- Kubernetes не предоставляет инструментов, упрощающих настройку и администрирование кластеров БД
- В общем, для продакшн баз слишком много проблем, для тестовых баз это несущественно, поэтому не возбраняется
- 01:43:44
- Q: Что произошло, когда обновляли переменную окружения (из секрета) и она стала равна пустой строке?
- A:
- Произошли те самые коллизии, которые происходят, когда объект сначала создан командо kubectl create, а потом применили изменения командой kubectl apply
- У kubernetes возникли сомнения, что же ему делать с переменной, которая в нашем первоначальном манифесте была, а в том манифесте который мы зааплаили её не было. Не очень логично, но об этом надо знать
- 01:44:21
- Q: Как передать в nginx или flask изображение или html, неужели содержимое придётся описывать в yaml?
- A: Всё просто, файлы, картинки стоит хранить в S3-совместимом хранилище, а статику, html, при сборке контейнера кладите внутрь
- 01:44:44
- Q: Зачем ConfigMap, если можно в Deployment прописать значения для переменных?
- A: Для удобства, чтобы не дублировать описание повторяющихся переменных
- 01:45:26
- Q: При изменении ConfigMap Deployment обновляет переменные?
- A: Проходили, не обновляет
- 01:45:38
- Q: Что делать, если не удаётся подменить файл монтированием из ConfigMap, если он уже есть в docker image, как это делать правильно?
- A:
- Вероятно, из-за того, что файл монтируется с использованием системы с симлинками и папками, происходит следущее:
- Точка монтирования, которую вы указали, в неё прилетает не файл, а каталог и всё что было в docker image по этому пути, оно пропадает, таким образом могут пропасть необходимые для запуска файлы и приложение может работать с ошибками или не стартовать
- Вероятно, из-за того, что файл монтируется с использованием системы с симлинками и папками, происходит следущее:
- 01:47:52
- Q: Как правильно прокинуть бинарный файл в контейнер? Сейчас прокидываем бинарные файлы (jks) в base64, можно ли как-то иначе?
- A: Ответ искать в 5й лекции
- 01:48:08
- Q: Можно ли считать StatefulSet аналогом ReplicaSet?
- A: Нельзя
- 01:48:20
- Q: Версии файлов конфига для rollback - хранятся на подах и сначала монтируются или уже примонтированы и меняется симлинк на них?
- A:
- Версии файлов конфига для rollback нигде не хранятся, т.е., когда вы делаете rollback, происходит откат на предыдущий ReplicaSet, а конфигмапы остаются теми же самыми, старыми
- Таким образом, если при откате нужно возвращать не только образы, но и настройки из конфигмапов, то деплой из yaml манифестов не подойдет, нужно использовать другие приёмы, например helm, gitOps