Layer. Каждый Docker-образ состоит из слоёв, каждый из которых описывает какую-то инструкцию. Далее — Docker объединяет информацию из каждого слоя, и создает шаблон-образ, из которого запускается контейнер, в котором выполняются инструкции из каждого слоя, который был включен в данный образ.
Registry. Хранилище Docker – это репозиторий, в котором хранятся образы Docker и который упрощает обмен этими образами между различными людьми и компьютерами. Когда вы создаете образ, вы можете либо запустить его на компьютере, на котором вы его создали, либо отправить (закачать) образ в хранилище, а затем извлечь (скачать) его на другом компьютере и запустить его там.
Image. Образ контейнера на основе Docker – это то, во что вы упаковываете свое приложение и его среду. Он содержит файловую систему, которая будет доступна приложению, и другие метаданные, такие как путь к исполняемому файлу, который должен быть исполнен при запуске образа.
Container. Контейнер на основе Docker – это обычный контейнер Linux, созданный из образа контейнера на основе Docker. Выполняемый контейнер – это процесс, запущенный на хосте, на котором работает Docker, но он полностью изолирован как от хоста, так и от всех других процессов, запущенных на нем. Процесс также ограничен ресурсами, имея в виду, что он может получать доступ и использовать только тот объем ресурсов (ЦП, ОЗУ и т. д.), который ему выделен.
Книга не про Docker, так что приведу лишь команды как собрать и запушить контейнер:
docker build . -t {name}<:optional_tagname>
docker tag {name} {docker-hub.username}/{name}<:optional_tagname>
docker push {docker-hub.username}/{name}<:optional_tagname>
docker run --name {run_name} -p 8080:8080 -d {image_name}
Инструкции Dockerfile:
FROM
— задает базовый (родительский) образ. Сообщает Docker о том, чтобы при сборке образа использовался бы базовый образ, который соответствует предоставленному имени и тегу. Базовый образ, кроме того, еще называют родительским образом.LABEL
— описывает метаданные. Например — сведения о том, кто создал и поддерживает образ. Объявление меток не замедляет процесс сборки образа и не увеличивает его размер. Они лишь содержат в себе полезную информацию об образе Docker, поэтому их рекомендуется включать в файл.ENV
— устанавливает постоянные переменные среды, которые будут доступны в контейнере во время его выполнения. Инструкция хорошо подходит для задания констант. -e some_variable_name=a_valueRUN
— выполняет команду и создает слой образа. После ее выполнения в образ добавляется новый слой, его состояние фиксируется. Инструкция часто используется для установки в образы дополнительных пакетов.COPY
— копирует в контейнер файлы и папки. Сообщает Docker о том, что нужно взять файлы и папки из локального контекста сборки и добавить их в текущую рабочую директорию образа. Если целевая директория не существует, эта инструкция ее создаст.ADD
— позволяет решать те же задачи, что и COPY. Также, с помощью этой инструкции можно добавлять в контейнер файлы, загруженные из удаленных источников, и распаковывать локальные .tar-файлы.CMD
— описывает команду с аргументами, которую нужно выполнить когда контейнер будет запущен. Аргументы могут быть переопределены при запуске контейнера. В файле может присутствовать лишь одна инструкция CMD.WORKDIR
— задает рабочую директорию для следующей инструкции. С этой директорией работают инструкции COPY, ADD, RUN, CMD и ENTRYPOINT.ARG
— задает переменные для передачи Docker во время сборки образа. В отличие от ENV-переменных, ARG-переменные недоступны во время выполнения контейнера. (--build-arg some_variable_name=a_value)ENTRYPOINT
— предоставляет команду с аргументами для вызова во время выполнения контейнера. Похожа на команду CMD, но параметры, задаваемые в ENTRYPOINT, не перезаписываются в том случае, если контейнер запускают с параметрами командной строки.EXPOSE
— указывает на то, какие порты планируется открыть для того, чтобы через них можно было бы связаться с работающим контейнером. Эта инструкция не открывает порты.VOLUME
— создает точку монтирования — место, которое контейнер будет использовать для постоянного хранения файлов и для работы с такими файлами.
Чтобы развернуть kubernetes локально можно использовать minikube.
$ brew install minikube
$ minikube start
+--------------------+ +---------------+
| Kubernetes API | <-- | kubelet |
| Scheduler | | kube-proxy |
| Controller Manager | | Docker (|rkt) |
| etcd | | |
+--------------------+ +---------------+
Master Node Cluster Node
Master:
- сервер Kubernetes API, с которым взаимодействуете вы и другие компоненты плоскости управления;
- планировщик (scheduler), который распределяет приложения (назначает рабочий узел каждому развертываемому компоненту приложения);
- менеджер контроллеров, выполняющий функции кластерного уровня, такие как репликация компонентов, отслеживание рабочих узлов, обработка аварийных сбоев узлов и т. д.;
- etcd, надежное распределенное хранилище данных, которое непрерывно сохраняет конфигурацию кластера.
Cluster Node:
- Docker, rkt или другая среда выполнения контейнеров, в которой выполняются контейнеры;
- kubelet, агент, который обменивается с сервером API и управляет контейнерами на своем узле;
- служебный прокси Kubernetes (kube-proxy), который балансирует нагрузку сетевого трафика между компонентами приложения
Pod (модуль) – это размещенная рядом группа контейнеров, которая представляет собой основной строительный блок в Kubernetes. Все контейнеры пода работают на одном узле. Контейнеры модуля используют одно и то же пространство IP-адресов и портов.
kubectl get po {podname} -o yaml
- получить конфигурацию пода.
- метаданные (metadata) – включают имя, пространство имен, метки и другую информацию о модуле;
- спецификация (spec) – содержит фактическое описание содержимого модуля, например контейнеры модуля, тома и другие данные;
- статус (status) – содержит текущую информацию о работающем модуле, такую как условие, в котором находится модуль, описание и статус каждого контейнера, внутренний IP модуля, и другую базовую информацию.
apiVersion: v1 <- описание соответствует версии v1 API Kubernetes
kind: Pod <- описывается модуль
metadata:
name: vacancy-api-doc <- имя модуля
spec:
containers:
- image: zinvapel/tsw <- образ контейнера
name: vacancy-api-doc <- имя контейнера
ports: |
- containerPort: 8080 |<- порты (носит лишь информационный характер)
protocol: TCP |
kubectl explain po
- документация по yaml.
kubectl create -f file.yml
- создает поды согласно описанию.
kubectl logs {pod-name} -c {container-name}
- прочитать логи.
kubectl port-forward {pod-name} 8888:8080
- проброс порта с локальной машины.
Label (метка) – это произвольная пара «ключ-значение», присоединяемая к ресурсу, которая затем используется при отборе ресурсов с помощью селекторов меток.
apiVersion: v1
kind: Pod
metadata:
name: vacancy-api-doc
labels: <- список меток
type: doc
env: test
spec:
containers:
- image: zinvapel/tsw
name: vacancy-api-doc
ports:
- containerPort: 8080
protocol: TCP
kubectl get po --show-labels
, kubectl get po -L creation_method,env
- вывести информацию с метками.
kubectl label po vacancy-api-doc env=debug --overwrite
- переопределить метки.
Селекторы:
kubectl get po -l env=debug
kubectl get po -l env
- все поды у которых есть метка envkubectl get po -l '!env'
kubectl get po -l env!=debug
kubectl get po -l 'env in (debug, test)'
kubectl get po -l 'env notin (debug, test)'
Можно вешать метки на ноды (узлы).
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
minikube Ready master 5h46m v1.17.2
$ kubectl label node minikube gpu=true
node/minikube labeled
$ kubectl get nodes -l gpu
NAME STATUS ROLES AGE VERSION
minikube Ready master 5h47m v1.17.2
$ kubectl get nodes -l '!gpu'
No resources found in default namespace.
Селектор нод
apiVersion: v1
kind: Pod
metadata:
name: vacancy-api-doc
labels:
type: doc
env: test
spec:
nodeSelector: |
gpu: "true" |<- Расположит только на ноде, у который label gpu=true
containers:
- image: zinvapel/tsw
name: vacancy-api-doc
ports:
- containerPort: 8080
protocol: TCP
Аннотации в отличие от меток носят только информационный характер.
kubectl annotate pod vacancy-api-doc somekey='{"type": "ann", "val": "json"}'
- добавление аннотации
kubectl describe po vacancy-api-doc
- информация о поде.
...
metadata:
annotations:
json_version: |
{"key": "value", "key2": "value2"}
...
По-умолчанию все поды привязываются к неймспейсу default. Чтобы случайно не изменить лишние поды, их можно сгруппировать в неймспейсы. Создание неймспейса:
apiVersion: v1
kind: Namespace
metadata:
name: debug-namespace
или kubectl create namespace debug-namespace
.
При создании подов используется опция --namespace {ns}
если нужно его определить в кастомный неймспейс. Поменять контекст можно командой:
$ kubectl config set-context $(kubectl config current-context) --namespace debug-namespace
Context "minikube" modified.
$ kubectl get pods
No resources found in debug-namespace namespace.
Удаление объектов в kubernetes выполняется командой kubectl delete {type} [-l ...labels]
. kubectl delete all --all
удаляет все объекты.
Для каждого контейнера в спецификации модуля можно указать проверку живучести. Kubernetes будет периодически выполнять проверку и перезапускать контейнер в случае несработки проверки. Типы:
httpGet
- проверка HTTP GET выполняет запрос HTTP GET на IP-адрес, порт и путь контейнера, которые вы укажете. Если проверка получает отклик и код ответа не представляет ошибку (другими словами, если код отклика HTTP будет 2xx или 3xx), проверка считается сработавшей. Если сервер возвращает отклик с кодом ошибки или вообще не отвечает, то проверка считается несработавшей, и в результате контейнер будет перезапущен;tcpSocket
- проверка сокета TCP пытается открыть TCP-подключение к указанному порту контейнера. Если подключение установлено успешно, то проверка сработала. В противном случае контейнер перезапускается;exec
- проверка Exec выполняет произвольную команду внутри контейнера и проверяет код состояния на выходе из команды. Если код состояния равен 0, то проверка выполнена успешно. Все остальные коды считаются несработавшими.
apiVersion: v1
kind: Pod
metadata:
name: vacancy-api-doc
labels:
type: doc
env: test
spec:
containers:
- image: zinvapel/tsw
name: vacancy-api-doc
ports:
- containerPort: 8080
protocol: TCP
livenessProbe:
httpGet: <- будет послан GET запрос
path: /health <- на этот путь
port: 8080 <- на этот порт
initialDelaySeconds: 20 <- первый раз через 20 секунд после запуска контейнера
periodSeconds: 10 <- и будет опрашивать каждые 10 секунд
failureThreshold: 10 <- контейнер будет перезапущен если проверка не будет пройдена 10 раз подряд
timeoutSeconds: 1 <- если контейнер отвечает больше 1 секунды, то проверка помечается как непройденная
kubectl logs mypod --previous
дает логи с предыдущего контейнера, там можно найти код выхода, который формируется как 128 + x
, где x
- код сигнала. Код выхода 137 сигнализирует о том, что процесс был убит внешним сигналом (код выхода 128 + 9 (SIGKILL). Аналогичным образом код выхода 143 соответствует 128 + 15 (SIGTERM).
Контроллер репликации (ReplicationController) – это ресурс Kubernetes, который обеспечивает поддержание постоянной работы его модулей. Если модуль исчезает по любой причине, например в случае исчезновения узла из кластера или потому, что модуль был вытеснен из узла, контроллер репликации замечает отсутствующий модуль и создает сменный модуль. Контроллер репликации состоит из трех основных частей:
- селектор меток, определяющий, какие модули находятся в области действия контроллера репликации;
- количество реплик, указывающее на требуемое количество модулей, которые должны быть запущены;
- шаблон модуля, используемый при создании новых реплик модуля.
apiVersion: v1
kind: ReplicationController
metadata:
name: rc-vacancy-api-doc
spec:
replicas: 4
selector:
type: doc
app: vacancy-api
template:
metadata:
name: vacancy-api-doc
labels:
type: doc
app: vacancy-api
env: prod
spec:
containers:
-
image: zinvapel/tsw
name: vacancy-api-doc
ports:
-
containerPort: 8080
protocol: TCP
При смене labels контейнеры не уничтожаются, а просто переходят из-под контроля.
$ kubectl get pods -L type
NAME READY STATUS RESTARTS AGE TYPE
rc-vacancy-api-doc-5phsj 1/1 Running 0 136m doc
rc-vacancy-api-doc-7wwnj 1/1 Running 0 136m doc
rc-vacancy-api-doc-bmpzk 1/1 Running 0 136m doc
rc-vacancy-api-doc-q2fwx 1/1 Running 0 136m doc
$ kubectl label po type=test -l type=doc --overwrite
pod/rc-vacancy-api-doc-5phsj labeled
pod/rc-vacancy-api-doc-7wwnj labeled
pod/rc-vacancy-api-doc-bmpzk labeled
pod/rc-vacancy-api-doc-q2fwx labeled
$ kubectl get pods -L type
NAME READY STATUS RESTARTS AGE TYPE
rc-vacancy-api-doc-5phsj 1/1 Running 0 137m test
rc-vacancy-api-doc-7wwnj 1/1 Running 0 137m test
rc-vacancy-api-doc-9qdtn 0/1 ContainerCreating 0 3s doc
rc-vacancy-api-doc-bmpzk 1/1 Running 0 137m test
rc-vacancy-api-doc-cxvjf 0/1 ContainerCreating 0 3s doc
rc-vacancy-api-doc-gnnwh 0/1 ContainerCreating 0 3s doc
rc-vacancy-api-doc-q2fwx 1/1 Running 0 137m test
rc-vacancy-api-doc-w4n92 0/1 ContainerCreating 0 3s doc
Аналогичны контроллерам, но имеют более выразительные селекторы модуля.
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: rs-vacancy-api-doc
spec:
replicas: 2
selector:
matchExpressions:
- key: type
operator: In
values:
- doc
- test
matchLabels:
type: doc
app: vacancy-api
template:
metadata:
name: vacancy-api-doc
labels:
type: doc
app: vacancy-api
env: prod
spec:
containers:
-
image: zinvapel/tsw
name: vacancy-api-doc
ports:
-
containerPort: 8080
protocol: TCP
matchExpressions
может быть:
- In – значение метки должно совпадать с одним из указанных значений values;
- NotIn – значение метки не должно совпадать с любым из указанных значений values;
- Exists – модуль должен содержать метку с указанным ключом (значение не важно). При использовании этого оператора не следует указывать поле values;
- DoesNotExist – модуль не должен содержать метку с указанным ключом. Свойство values не должно быть указано.
kubectl scale rs rs-vacancy-api-doc --replicas 3
- увеличить количество реплик
DaemonSet запускает ровно по одному контейнеру на каждой ноде
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: ds-vacancy-api-doc
spec:
selector:
matchLabels:
type: doc
app: vacancy-api
template:
metadata:
name: vacancy-api-doc
labels:
type: doc
app: vacancy-api
env: prod
spec:
containers:
-
image: zinvapel/tsw
name: vacancy-api-doc
ports:
-
containerPort: 8080
protocol: TCP
Job - модуль, контейнер которого не перезапускается, когда процесс, запущенный внутри, заканчивается успешно.
aapiVersion: batch/v1
kind: Job
metadata:
name: echo-job
spec:
completions: 4 <- количество заданий, которые надо выполнить
parallelism: 2 <- количество параллельных заданий
activeDeadlineSeconds: 10 <- количество секунд, выделенное на выполнение
backoffLimit: 2 <- количество попыток
template:
metadata:
labels:
env: prod
spec:
restartPolicy: OnFailure <- что делать при ошибке
containers:
- name: echo
image: busybox
command:
- echo
- "Hello world"
restartPolicy
:
OnFailure
- перезапускать при ошибкеNever
- не перезапускатьkubectl scale job rs-vacancy-api-doc --replicas 30
- увеличить количество заданий.
Выполнение периодических заданий
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: echo-cron-job
spec:
schedule: "* * * * *"
jobTemplate:
spec:
completions: 4
parallelism: 2
activeDeadlineSeconds: 10
backoffLimit: 2
template:
metadata:
labels:
env: prod
spec:
restartPolicy: OnFailure
containers:
- name: echo
image: busybox
command:
- echo
- "Hello world"
Сервисы предоставляют доступ к подам
- поды эфемерны – они могут появляться и исчезать в любое время;
- Kubernetes назначает IP-адрес модулю, после того как модуль был назначен узлу, и до момента его запуска – следовательно, клиенты не могут знать заранее IP-адрес серверного модуля;
- горизонтальное масштабирование означает, что несколько модулей могут обеспечивать одну и ту же службу – каждый из этих модулей имеет свой собственный IP-адрес. Клиенты не должны заботиться о том, сколько модулей поддерживают службу и каковы их IP-адреса.
Определение службы
apiVersion: v1
kind: Service
metadata:
name: doc
spec:
ports:
- port: 80
targetPort: 8080
selector:
type: doc
app: vacancy-api
Таким образом мы определяем сервис, у которого открыт 80 порт и который перенаправляет все запросы случайному под из набора селекторов. Если указать в spec сервиса sessionAffinity: ClientIP
(по-умолчанию None), то клиенты с одного IP адреса будут попадать на один и тот же бэкенд.
Можно использовать несколько портов и даже давать им имена.
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: rs-vacancy-api-doc-v2
spec:
selector:
matchLabels:
type: doc-v2
app: vacancy-api-v2
replicas: 2
template:
metadata:
name: vacancy-api-doc-v2
labels:
type: doc-v2
app: vacancy-api-v2
spec:
containers:
-
image: zinvapel/tsw
name: vacancy-api-doc
ports:
-
name: http <-+
containerPort: 8080 |
protocol: TCP |
- |
name: https |
containerPort: 8433 |
protocol: TCP |
|
--- |-- Именованный порт
apiVersion: v1 |
kind: Service |
metadata: |
name: doc-v2 |
spec: |
ports: |
- |
name: http |
port: 80 |
targetPort: http <-+
-
name: https
port: 443
targetPort: https
selector:
type: doc-v2
app: vacancy-api-v2
kubectl exec pod/rs-vacancy-api-doc-v2-vlqwd -- env
- выполнить команду.
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=rs-vacancy-api-doc-v2-vlqwd
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT=tcp://10.96.0.1:443
DOC_V2_PORT_443_TCP=tcp://10.108.164.135:443
DOC_V2_SERVICE_HOST=10.108.164.135
DOC_PORT_80_TCP_PROTO=tcp
DOC_V2_PORT_443_TCP_PORT=443
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
DOC_SERVICE_HOST=10.96.135.142
DOC_V2_SERVICE_PORT=80
DOC_V2_PORT_443_TCP_PROTO=tcp
DOC_PORT_80_TCP_ADDR=10.96.135.142
DOC_SERVICE_PORT=80
DOC_PORT_80_TCP=tcp://10.96.135.142:80
DOC_PORT_80_TCP_PORT=80
DOC_V2_SERVICE_PORT_HTTPS=443
DOC_V2_PORT=tcp://10.108.164.135:80
KUBERNETES_SERVICE_PORT=443
DOC_V2_PORT_80_TCP_ADDR=10.108.164.135
DOC_V2_PORT_443_TCP_ADDR=10.108.164.135
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
DOC_V2_PORT_80_TCP=tcp://10.108.164.135:80
DOC_V2_PORT_80_TCP_PROTO=tcp
DOC_V2_PORT_80_TCP_PORT=80
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
DOC_V2_SERVICE_PORT_HTTP=80
DOC_PORT=tcp://10.96.135.142:80
NGINX_VERSION=1.17.7
NJS_VERSION=0.3.7
PKG_RELEASE=1
API_KEY=**None**
SWAGGER_JSON=/usr/share/nginx/html/api-doc/swagger.yml
PORT=8080
BASE_URL=
HOME=/root
Под запускает DNS-сервер, для использования которого автоматически настраиваются все остальные модули, работающие в кластере (Kubernetes делает это, изменяя файл /etc/resolv.conf каждого контейнера). Любой DNS-запрос, выполняемый процессом, запущенным в модуле, будет обрабатываться собственным DNS-сервером Kubernetes, который знает все службы, работающие в вашей системе.
С помощью свойства dnsPolicy в поле spec ресурса каждого модуля можно настроить, будет модуль использовать внутренний DNS-сервер или нет.
Каждая служба получает DNS-запись во внутреннем DNS-сервере, и клиентские модули, которые знают имя службы, могут обращаться к ней через полностью квалифицированное доменное имя (FQDN) вместо использования переменных среды.
{service-name}.default.svc.cluster.local
- так выглядит доменное имя, причем суффикс можно опустить.
При создании сервиса неявно создается свойство Endpoints с адресами управляемых подов.
$ kubectl describe service/doc-v2
Name: doc-v2
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=vacancy-api-v2,type=doc-v2
Type: ClusterIP
IP: 10.108.164.135
Port: http 80/TCP
TargetPort: http/TCP
Endpoints: 172.17.0.6:8080,172.17.0.7:8080 <- Вот здесь
Port: https 443/TCP
TargetPort: https/TCP
Endpoints: 172.17.0.6:8433,172.17.0.7:8433
Session Affinity: None
Events: <none>
Список Endpoints можно создать самостоятельно и сделать службу для внешнего ресурса, например.
apiVersion: v1
kind: Service
metadata:
name: external-service <-+
spec: |
ports: |
- port: 80 |
|-- Одинаковые имена
--- |
apiVersion: v1 |
kind: Endpoints |
metadata: |
name: external-service <-+
subsets:
- addresses:
- ip: 11.11.11.11
- ip: 22.22.22.22
- ports:
- port: 80
Можно создать псевдоним для внешней службы (технически создается CNAME во внутреннем DNS-сервере)
apiVersion: v1
kind: Service
metadata:
name: external-service
spec:
type: ExternalName
externalName: someapi.somecompany.com <- Служба доступна по имени сервиса (external-service внутри кластера)
ports:
- port: 80
Можно пробрасывать порт с каждого узла до сервиса.
apiVersion: v1
kind: Service
metadata:
name: doc
spec:
type: NodePort
ports:
-
name: http
port: 80
targetPort: http
nodePort: 31000
selector:
type: doc
app: vacancy-api
Кластеры Kubernetes, работающие на облачных провайдерах, обычно поддерживают автоматическое резервирование балансировщика нагрузки из облачной инфраструктуры.
apiVersion: v1
kind: Service
metadata:
name: doc
spec:
type: LoadBalancer
ports:
-
name: http
port: 80
targetPort: http
selector:
type: doc
app: vacancy-api
Балансировщику будет выдан externalIp. externalTrafficPolicy: Local
делает так, чтобы трафик шел к поду, который работает на том же узле, что и получил подключение.
IP адрес клиента недоступен внутри кластера.
Одна из важных причин заключается в том, что для каждой службы LoadBalancer требуется собственный балансировщик нагрузки с собственным общедоступным IP-адресом, в то время как для Ingress’а требуется только один, даже когда предоставляется доступ к десяткам служб. Когда клиент отправляет HTTP-запрос ко входу, хост и путь в запросе определяют, к какой службе этот запрос перенаправляется.
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: vacancy-api-doc-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
rules:
-
host: vacancy.api-doc.local
http:
paths:
-
path: /other
backend:
serviceName: other-service
servicePort: 80
-
backend:
serviceName: vacancy-api-doc-http
servicePort: 80
Чтобы добавить HTTPS, нужно сгенерировать ключи, создать секрет и прописать их в секции spec.tls.
$ openssl genrsa -out tls.key 2048
$ openssl req -new -x509 -key tls.key -out tls.cert -days 360 -subj /CN=vacancy.api-doc.local
$ kubectl create secret tls tls-secret --cert=tls.cert --key=tls.key
secret "tls-secret" created
...
spec:
tls:
-
hosts:
- vacancy.api-doc.local
secretName: tls-secret
...
По аналогии с livenessProbe, существует readinessProbe. На контейнер не будут подаваться запросы с сервиса, если проверка не прошла.
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: vacancy-api-doc-rs
spec:
selector:
matchLabels:
type: doc
app: vacancy-api
replicas: 2
template:
metadata:
name: vacancy-api-doc
labels:
type: doc
app: vacancy-api
spec:
containers:
-
image: zinvapel/tsw
name: vacancy-api-doc
ports:
-
name: http
containerPort: 8080
protocol: TCP
readinessProbe:
exec:
command:
- ls
- /var/ready
Kubernetes позволяет клиентам обнаруживать IP-адреса модулей посредством поиска в DNS. Обычно, когда вы выполняете DNS-запрос, DNS-сервер возвращает единственный кластерный IP-адрес службы. Но если вы сообщите системе Kubernetes, что для вашей службы вам не нужен кластерный IP-адрес (это можно сделать, присвоив полю clusterIP значение None в спецификации службы), то вместо единственного IP-адреса службы DNS-сервер будет возвращать IP-адреса модулей.
Вместо того чтобы возвращать одну A-запись DNS, DNS-сервер будет возвращать для службы несколько A-записей, каждая с указанием на IP-адрес отдельного модуля, поддерживающего службу в данный момент.
apiVersion: v1
kind: Service
metadata:
name: vacancy-api-doc-http
annotations:
service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" <- указание этой аннотации говорит показыывать IP адреса даже неготовых сервисов
spec:
clusterIP: None
type: NodePort
ports:
-
name: http
port: 80
targetPort: http
selector:
type: doc
app: vacancy-api
Volume существует в рамках пода и может использоваться совместно для всех контейнеров пода. Список типов:
- emptyDir – простой пустой каталог, используемый для хранения временных данных;
- hostPath – используется для монтирования каталогов из файловой системы рабочего узла в модуль;
- gitRepo – том, инициализируемый в ходе проверки содержимого репозитория Git;
- nfs – общий ресурс NFS, монтируемый в модуле;
- gcePersistentDisk (Google Compute Engine Persistent Disk), awsElasticBlockStore (Amazon Web Services Elastic Block Store Volume), azureDisk (Microsoft Azure Disk Volume) – используются для монтирования систем хранения данных, специфичных для поставщика облачных служб;
- cinder, cephfs, iscsi, flocker, glusterfs, quobyte, rbd, flexVolume, vsphereVolume, photonPersistentDisk, scaleIO – используются для монтирования других типов сетевых хранилищ;
- configMap, secret, downwardAPI – специальные типы томов, используемые для предоставления модулю определенных ресурсов Kubernetes и кластерной информации;
- persistentVolumeClaim – способ использовать заранее или динамически резервируемое постоянное хранилище.
emptyDir создает пустой Volume, при этом он перетирает всё что туда пытаются положить контейнеры.
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: php-info-rs
spec:
selector:
matchLabels:
app: php-info
replicas: 2
template:
metadata:
name: php-info
labels:
app: php-info
spec:
containers:
- image: zinvapel/php-info:0.0.4
name: php-info-php
volumeMounts:
- mountPath: /var/run
name: php-socket
- mountPath: /var/www
name: php-code
- image: zinvapel/php-info-nginx:0.0.4
name: php-info-nginx
ports:
- containerPort: 8080
name: http
volumeMounts:
- mountPath: /var/run
name: php-socket
- mountPath: /var/www
name: php-code
volumes:
- name: php-socket
emptyDir: {}
- name: php-code
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: php-info-http
spec:
type: NodePort
ports:
-
name: http
port: 80
targetPort: http
selector:
app: php-info
Аналог emptyDir, но в созданную директорию выкачивается репозиторий.
gitRepo:
repository: https://github.com/owner/app.git
revision: master
directory: .
В текущий момент считается deprecated и рекомендуется использовать initController.
Том hostPath указывает на определенный файл или каталог в файловой системе узла. Модули, работающие на одном узле и использующие один и тот же путь в томе hostPath, видят одни и те же файлы.
hostPath:
path: /var/www/vacancy
Чтобы скрыть фактическую инфраструктуру хранилища как от приложения, так и от его разработчика используется PersistentVolume и PersistentVolumeClaim.
apiVersion: v1
kind: PersistentVolume
metadata:
name: test-pv
spec:
capacity:
storage: 1G
accessModes:
- ReadWriteOnce
- ReadOnlyMany
persistentVolumeReclaimPolicy: Retain
hostPath:
path: /var/storage/test
persistentVolumeReclaimPolicy
— что будет происходить с pv после удаления pvc.
Retain
— pv удален не будет.Recycle
— pv будет очищен.Delete
— pv будет удален.
accessModes
- режимы доступа.
- RWO – ReadWriteOnce – только один узел может монтировать том для чтения и записи;
- ROX – ReadOnlyMany – несколько узлов могут монтировать том для чтения;
- RWX – ReadWriteMany – несколько узлов могут монтировать том как для чтения, так и для записи.
Когда пользователю кластера необходимо использовать постоянное хранилище в одном из своих модулей, он сначала создает манифест с заявкой PersistentVolumeClaim, указывая минимальный размер и требуемый режим доступа.
Затем пользователь отправляет манифест с заявкой PersistentVolumeClaim в API-сервер Kubernetes, и Kubernetes находит соответствующий ресурс PersistentVolume и связывает его с заявкой.
Заявка PersistentVolumeClaim затем может использоваться как один из томов в модуле. Другие пользователи не могут использовать тот же том PersistentVolume до тех пор, пока он не будет высвобожден путем удаления связанной заявки PersistentVolumeClaim.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
spec:
resources:
requests:
storage: 1G
accessModes:
- ReadWriteOnce
storageClassName: ""
---
apiVersion: v1
kind: Pod
metadata:
name: test-p
spec:
containers:
- name: bb
image: busybox
command: ["sleep", "900000"]
volumeMounts:
- mountPath: /var/volumes/test
name: data
volumes:
- name: data
persistentVolumeClaim:
claimName: test-pvc
При установлении значения persistentVolumeReclaimPolicy в Retain, без администратора нельзя будет переназначить PersistentVolume, так как там могут остаться данные от предыдущего контейнера.
Администратор кластера, вместо того чтобы создавать ресурсы PersistentVolume, может развернуть поставщика (provisioner) ресурса PersistentVolume и определить один или более объектов StorageClass, чтобы позволить пользователям выбрать, какой тип ресурса PersistentVolume они хотят. Пользователи могут ссылаться на класс хранилища StorageClass в своих заявках PersistentVolumeClaim, и поставщик будет принимать это во внимание при резервировании постоянного хранилища.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
provisioner: k8s.io/minikube-hostpath <- плагин, используемый для резервирования
reclaimPolicy: Retain
parameters:
type: pd-ssd <- параметры, передаваемые плагину
Вот так выглядит заявка
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc-2
spec:
resources:
requests:
storage: 1G
accessModes:
- ReadWriteOnce
storageClassName: fast
По-умолчанию уже существует один StorageClass, к которому привязываются заявки, у которых не указан storageClassName
$ kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
fast k8s.io/minikube-hostpath Delete Immediate false 10m
standard (default) k8s.io/minikube-hostpath Delete Immediate false 169m
Именно поэтому для привязки к конкретному PersistentVolume мы указываем storageClassName: ""
.
В файле Dockerfile две инструкции определяют две части:
- ENTRYPOINT определяет исполняемый файл, вызываемый при запуске контейнера;
- CMD задает аргументы, которые передаются в точку входа.
...
ENTRYPOINT ["/bin/script.sh"]
CMD ["10"]
$ docker run image
10 was passed
$ docker run image 15
15 was passed
В Kubernetes при указании контейнера можно переопределять и инструкцию ENTRYPOINT, и инструкцию CMD.
...
kind: Pod
spec:
containers:
– image: some/image
command: ["/bin/command"]
args: ["arg1", "arg2", "arg3"]
...
Kubernetes позволяет передавать переменные окружения в контейнер.
...
kind: Pod
spec:
containers:
– image: some/image
env:
- name: FIRST_VAR
value: "foo"
- name: SECOND_VAR
value: "$(FIRST_VAR)bar"
...
Kubernetes позволяет выделить параметры конфигурации в отдельный объект, называемый ConfigMap, который представляет собой ассоциативный массив, содержащий пары ключ-значение, где значения варьируются от коротких литералов до полных файлов конфигурации.
$ kubectl create configmap my-config \
--from-file=foo.json \ # - файл
--from-file=bar=foobar.conf \ # файл с собственным ключом
--from-file=config-opts/ \ # весь каталог
--from-literal=some=thing # строка
Использование литералов
apiVersion: v1
kind: ConfigMap
metadata:
name: test-cm
data:
key.public: |
qewhoiqhweoifhoiewhf
qwehjofhqewohfoie2h3fiohqew
qwehfoiqwehofiq2h034hf9283g4f78108
1fy23f7t231f132ty912yf912
sleep: "10"
---
apiVersion: v1
kind: Pod
metadata:
name: cm-testing-pod
spec:
containers:
- name: busybox
image: busybox
command: [sleep, "99999999"]
env:
- name: SLEEP_TIME |
valueFrom: |
configMapKeyRef: |
optional: true | <- Использование в качестве env
name: test-cm |
key: sleep |
---
apiVersion: v1
kind: Pod
metadata:
name: cm-testing-pod-v2
spec:
containers:
- name: busybox
image: busybox
command: [sleep, "99999999"]
envFrom:
- prefix: CONFIG_ |
configMapRef: | <- Копируем весь Volume и задаем префикс
name: test-cm |
$ kubectl exec cm-testing-pod -- env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=cm-testing-pod
SLEEP_TIME=10
HOME=/root
$ kubectl exec cm-testing-pod-v2 -- env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=cm-testing-pod-v2
CONFIG_key.public=qewhoiqhweoifhoiewhf
qwehjofhqewohfoie2h3fiohqew
qwehfoiqwehofiq2h034hf9283g4f78108
1fy23f7t231f132ty912yf912
CONFIG_my.key=vwebiohoivrhiovrhiowerhioiohirwovrwhiovrhiovrhiovrhio
CONFIG_sleep=10
HOME=/root
Использование файлов:
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: php-info-rs
spec:
selector:
matchLabels:
app: php-info
replicas: 2
template:
metadata:
name: php-info
labels:
app: php-info
spec:
containers:
- image: zinvapel/php-info:0.0.4
name: php-info-php
volumeMounts:
- mountPath: /var/run
name: php-socket
- mountPath: /var/www/index.php
name: php-code
subPath: index.php | <- Монтируем только 1 файл
- image: nginx
name: php-info-nginx
ports:
- containerPort: 8080
name: http
volumeMounts:
- mountPath: /var/run
name: php-socket
- mountPath: /var/www/index.php
name: php-code
subPath: index.php
- mountPath: /etc/nginx/conf.d | <- Монтируем в директорию
name: nginx-config
volumes:
- name: php-socket
emptyDir: {}
- name: php-code
configMap: | Volume типа configMap
name: php-info-nginx-config | имя ConfigMap
items: | В volume помеваем только значения по ключам
- key: index.php | Значение по ключу `index.php`
path: index.php | Кладем в файл `index.php`
defaultMode: 0777 | Права
- name: nginx-config
configMap:
name: php-info-nginx-config | А здесь копируем все вместе
---
apiVersion: v1
kind: Service
metadata:
name: php-info-http
spec:
type: NodePort
ports:
-
name: http
port: 80
targetPort: http
selector:
app: php-info
---
apiVersion: v1
kind: ConfigMap
metadata:
name: php-info-nginx-config
data:
php-info.conf: |
server {
listen 8080 default_server;
server_name php-info.local;
charset utf-8;
root /var/www;
location / {
try_files $uri /index.php$is_args$args;
}
location ~ ^/index\.php(/|$) {
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
internal;
}
location ~ \.php$ {
return 404;
}
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
}
index.php: |
<?php
phpinfo();
Когда копируется вся директория, то при обновлении ConfigMap, директория обновляется автоматически. Если копируется отдельный файл (subPath), то обновления не происходит. Это достигается за счет того, что на самом деле в ConfigMap хранятся симлинки на настоящие значения.
Secrets похожи на ConfigMap, но в yaml описании содержатся base64-encoded данные.
$ kubectl create secret generic php-info --from-literal=POSTGRES_PASS=postgres
secret/php-info created
$ kubectl get secret/php-info -o yaml
apiVersion: v1
data:
POSTGRES_PASS: cG9zdGdyZXM=
kind: Secret
metadata:
creationTimestamp: "2020-02-13T17:54:12Z"
name: php-info
namespace: default
resourceVersion: "43455"
selfLink: /api/v1/namespaces/default/secrets/php-info
uid: ff97336b-e25b-4b11-99a4-9d4fbcad4289
type: Opaque
Чтобы использовать строковые данные, то используется stringData атрибут.
apiVersion: v1
kind: Secret
metadata:
name: php-info-secrets
stringData:
postgresUser: postgres
postgresPassword: postgres
data:
cert.key: c2VjcmV0Cg==
---
...
env:
- name: POSTGRES_PASS
valueFrom:
secretKeyRef:
key: postgresPassword
name: nginx-secret-config
...
volumes:
- name: nginx-secret-config
secret:
secretName: php-info-secrets
secretKeyRef не требует монтирования секрета, нужно обращаться сразу по имени
Docker Hub, помимо общедоступных репозиториев образов, также позволяет создавать приватные репозитории. Вы можете отметить репозиторий как приватный, войдя в http://hub.docker.com с помощью вашего веб-браузера, найдя репозиторий и проставив галочку. Для запуска модуля, использующего образ из приватного репозитория, необходимо выполнить два действия:
- создать секрет, содержащий учетные данные для реестра Docker;
- указать этот секрет в поле imagePullSecrets манифеста модуля.
$ kubectl create secret docker-registry mydockerhubsecret \
--docker-username=myusername --docker-password=mypassword \
--docker-email=my.email@provider.com
apiVersion: v1
kind: Pod
metadata:
name: private-pod
spec:
imagePullSecrets:
– name: mydockerhubsecret
containers:
– image: username/private:tag
name: main
Downward API позволяет предоставлять собственные метаданные пода процессам, запущенным внутри этого пода.
apiVersion: v1
kind: Pod
metadata:
name: downward
spec:
containers:
- name: main
image: busybox
command: ["sleep", "9999999"]
resources:
requests:
cpu: 15m
memory: 100Ki
limits:
cpu: 100m
memory: 4Mi
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: SERVICE_ACCOUNT
valueFrom:
fieldRef:
fieldPath: spec.serviceAccountName
- name: CONTAINER_CPU_REQUEST_MILLICORES
valueFrom:
resourceFieldRef:
resource: requests.cpu
divisor: 1m
- name: CONTAINER_MEMORY_LIMIT_KIBIBYTES
valueFrom:
resourceFieldRef:
resource: limits.memory
divisor: 1Ki
Можно также смонтировать как Volume.
apiVersion: v1
kind: Pod
metadata:
name: downward
spec:
containers:
- name: main
image: busybox
command: ["sleep", "9999999"]
volumeMounts:
- mountPath: /etc/downward
name: downward
volumes:
- name: downward
downwardAPI:
items:
- path: "podName"
fieldRef:
fieldPath: metadata.name
- path: "containerCpuRequestMilliCores"
resourceFieldRef:
containerName: main
resource: requests.cpu
divisor: 1m
Можно общаться с Kubernetes посредством kubectl proxy, либо из контейнера, с помощью переменных окружения KUBERNETES_SERVICE_*
. Всё, для коннекта лежит в директории /var/run/secrets/kubernetes.io/serviceaccount
.
$ curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://kubernetes
Unauthorized
$ export CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
Unauthorized
$ TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
$ curl -H "Authorization: Bearer $TOKEN" https://kubernetes
{
"paths": [
"/api",
"/api/v1",
"/apis",
"/apis/apps",
"/apis/apps/v1beta1",
"/apis/authorization.k8s.io",
...
"/ui/",
"/version"
]
}
Сине-зеленый деплой:
- Выкатавыем новую ReplicaSet
- Меняем В Service селектор (kubectl set selector)
- Удаляем старую версию
Скользящее обновление (rolling update) (+канареечный):
- Уменьшаем количество реплик на старой
- Добавляем на новой понемногу
kubectl поддерживает команду для автоматического выполнения rolling update, для этого нужно выполнить команду kubectl rolling-update {replication-controller-name-1} {replication-controller-name-2} --image={new/image:tag}
. Этот метод плох тем, что он выполняется не на сервере и при возникновении система окажется в недодеплоенном состоянии.
Вам следует осознавать, что принятая по умолчанию политика выгрузки образа imagePullPolicy зависит от тега образа. Если контейнер ссылается на тег latest (явно или не указывая тег вообще), по умолчанию imagePullPolicy равняется Always, но если же контейнер ссылаетс
Объект типа Deployment под собой создает ReplicaSet. Его особенностью является то, что он автоматически выполняет развертвование на основании описанной стратегии.
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo
spec:
replicas: 2
minReadySeconds: 10 | Как долго вновь созданный модуль должен быть в готовности
revisionHistoryLimit: 3 | Длина истории старых версий
progressDeadlineSeconds: 60 | Максимальное время на весь деплой
strategy:
type: RollingUpdate | Скользящее окно (Recreate заменяет все контейнеры разом)
rollingUpdate: | Настройки окна
maxSurge: 1 | Максимальное количество избыточных сервисов в процессе деплоя
maxUnavailable: 1 | Максимальное количество недостающих сервисов в процессе деплоя
selector:
matchLabels:
app: echo
template:
metadata:
labels:
app: echo
name: echo
spec:
containers:
- name: echo
image: busybox
imagePullPolicy: Always
command: ["watch"]
args: ["echo", "Hello world", "&&", "sleep", "10"]
Выполнять обновления можно с помощью kubectl apply
или kubectl set image
(а также edit
, patch
, replace
).
$ kubectl set image deployment echo nodejs=echo/echo:v2
Управление процессом развертывания:
kubectl rollout status deployment {name}
- статусkubectl rollout undo deployment {name} <--to-revision=1>
- откатить (опционально до версии)kubectl rollout history deployment {name}
- список версийkubectl rollout pause deployment {name}
- приостановитьkubectl rollout resume deployment {name}
- продолжить
Наборы ReplicaSet создают множество реплик модуля из одного шаблона модуля. Эти реплики не отличаются друг от друга, кроме как по имени и IP-адресу. Если шаблон модуля содержит том, который относится к конкретной заявке на получение тома постоянного хранения (PersistentVolumeClaim), то все реплики набора реплик ReplicaSet будут использовать ту же заявку и, следовательно, тот же том постоянного хранения PersistentVolume.
Варианты использования персональных хранилищ:
- Ручное содание подов
- Несколько ReplicaSet по одному поду в каждом
- Поддиректории в Volume
- StatefulSet
Каждому модулю, создаваемому набором StatefulSet, присваивается порядковый индекс (с отсчетом от нуля), который затем используется, чтобы произвести имя и хостнейм модуля, и закрепить за этим модулем надежное хранилище.
$ kubectl describe statefulset vacancy-api-db
Name: vacancy-api-db
Namespace: default
CreationTimestamp: Tue, 18 Feb 2020 09:48:35 +0300
Selector: kind=db
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"apps/v1","kind":"StatefulSet","metadata":{"annotations":{},"name":"vacancy-api-db","namespace":"default"},"spec":{"replicas...
Replicas: 2 desired | 2 total
Update Strategy: RollingUpdate
Partition: 824633993512
Pods Status: 2 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
Labels: kind=db
Containers:
db:
Image: postgres:10
Port: 5432/TCP
Host Port: 0/TCP
Environment:
POSTGRES_USER: <set to the key 'postgresUser' in secret 'vacancy-api-secrets'> Optional: false
POSTGRES_PASSWORD: <set to the key 'postgresPassword' in secret 'vacancy-api-secrets'> Optional: false
Mounts:
/var/lib/postgresql/data from pgdata (rw)
Volumes: <none>
Volume Claims:
Name: pgdata
StorageClass: vacancy-api-storage
Labels: <none>
Annotations: <none>
Capacity: 1G
Access Modes: [ReadWriteOnce]
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 28s statefulset-controller create Claim pgdata-vacancy-api-db-1 Pod vacancy-api-db-1 in StatefulSet vacancy-api-db success
Normal SuccessfulCreate 28s statefulset-controller create Pod vacancy-api-db-1 in StatefulSet vacancy-api-db successful
$ kubectl get po
NAME READY STATUS RESTARTS AGE
vacancy-api-db-0 1/1 Running 1 2d
vacancy-api-db-1 1/1 Running 0 59s
Заявка PersistentVolumeClaim остается после уменьшения масштаба, означает, что последующее увеличение масштаба может повторно закрепить ту же самую заявку вместе со связанным с ней постоянным томом PersistentVolume и его содержимым за новым экземпляром модуля.
Для доступа к подам используются headless-службы, таким образом каждый из подов будет доступен по адресу {podname}-{podnumber}.{service-name}.{namespace}.svc.cluster.local
apiVersion: v1
kind: Service
metadata:
name: app-db-srv
spec:
clusterIP: None <- headless служба
selector:
kind: db
ports:
- port: 5432
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: app-db
spec:
serviceName: app-db-srv <- Имя службы
replicas: 1
selector:
matchLabels:
kind: db
template:
metadata:
labels:
kind: db
spec:
containers:
- name: db
image: postgres:10
ports:
- containerPort: 5432
name: pgport
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: pgdata
env:
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: app-secrets
key: postgresUser
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: postgresPassword
volumeClaimTemplates:
- metadata:
name: pgdata
spec:
resources:
requests:
storage: 1G
accessModes:
- "ReadWriteOnce"
storageClassName: "app-storage"
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: app-storage
provisioner: k8s.io/minikube-hostpath
reclaimPolicy: Retain
parameters:
type: pd-ssd
Обнаружить все инстансы можно с помощью DNS:
$ kubectl run -it srvlookup --image=tutum/dnsutils --rm --restart=Never -- dig SRV app-db-srv.default.svc.cluster.local
; <<>> DiG 9.9.5-3ubuntu0.2-Ubuntu <<>> SRV app-db-srv.default.svc.cluster.local
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 28878
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 3
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;app-db-srv.default.svc.cluster.local. IN SRV
;; ANSWER SECTION:
app-db-srv.default.svc.cluster.local. 30 IN SRV 0 50 5432 app-db-1.vacancy-api-db-srv.default.svc.cluster.local.
app-db-srv.default.svc.cluster.local. 30 IN SRV 0 50 5432 app-db-0.vacancy-api-db-srv.default.svc.cluster.local.
;; ADDITIONAL SECTION:
app-db-1.vacancy-api-db-srv.default.svc.cluster.local. 30 IN A 172.17.0.12
app-db-0.vacancy-api-db-srv.default.svc.cluster.local. 30 IN A 172.17.0.2
;; Query time: 34 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Thu Feb 20 07:29:40 UTC 2020
;; MSG SIZE rcvd: 477
pod "srvlookup" deleted
Master Node Cluster Node
+--------------------------------------+ +------------------------+
| +------+ +---------------+ | | +------------+ |
| | etcd |<---- | Kubernetes |<-----+-----+-----| kube-proxy | |
| +------+ | API |<-----+-----+-+ +------------+ |
| +---------------+ | | | |
| ^ ^ | | | |
| +-----------+ | | +--------------+ | | | +------------+ |
| | Scheduler |--+ +-| Controller | | | +---| Kubelet | |
| +-----------+ | Manager | | | +------------+ |
| +--------------+ | | | |
+--------------------------------------+ | v |
| +----------------+ |
| | Virtualization | |
| | System | |
| | (Docker) | |
| +----------------+ |
+------------------------+
Компоненты плоскости управления, а также kube-proxy могут быть развернуты в системе напрямую или работать как модули.
$ kubectl get po -o custom-columns=POD:metadata.name,NODE:spec.nodeName --sort-by spec.nodeName -n kube-system
POD NODE
coredns-6955765f44-8dx8w minikube
coredns-6955765f44-xq9pl minikube
etcd-minikube minikube
kube-apiserver-minikube minikube
kube-controller-manager-minikube minikube
kube-proxy-kbbsp minikube
kube-scheduler-minikube minikube
storage-provisioner minikube
etcd - быстрое, распределенное и согласованное хранилище в формате ключ-значение. Поскольку хранилище etcd является распределенным, для обеспечения высокой доступности и повышения производительности вы можете запускать несколько его экземпляров.
Единственным компонентом, который напрямую взаимодействует с хранилищем etcd, является сервер API Kubernetes. Хранилище etcd является единственным местом, где Kubernetes хранит состояние кластера и метаданные. Kubernetes хранит все свои данные в etcd в /registry.
Для достижения этого в хранилище etcd используется консенсусный алгоритм RAFT, который гарантирует, что в любой момент состояние каждого узла является либо тем, что большинство узлов соглашается считать текущим состоянием, либо одним из ранее согласованных состояний. Для перехода кластера в следующее состояние консенсусному алгоритму требуется большинство (или кворум). В результате этого, если кластер разделится на две несвязанные группы узлов, состояние в этих двух группах никогда не может расходиться, так как для перехода из предыдущего состояния в новое требуется более половины узлов, принимающих участие в изменении состояния.
Оптимистическое управление параллелизмом (иногда называемое оптимистической блокировкой) – это метод, в котором вместо блокировки порции данных и предотвращения ее чтения или обновления порция данных во время блокировки содержит номер версии. При каждом обновлении данных номер версии увеличивается. При обновлении данных проверяется, увеличился ли номер версии между временем чтения данных клиентом и временем отправки им обновления. Если это происходит, то обновление отклоняется, и клиент должен повторно прочитать новые данные и попытаться обновить их снова.
В результате этого, когда два клиента пытаются обновить одну и ту же запись данных, успешно выполняется только первая. Все ресурсы Kubernetes содержат поле metadata.resourceVersion, которое клиенты должны передавать обратно на сервер API при обновлении объекта. Если эта версия не совпадает с версией, хранящейся в etcd, то сервер API обновление отклоняет.
Kubernetes API предоставляет интерфейс CRUD для запросов и изменения состояния кластера через API RESTful. Он хранит это состояние в хранилище etcd. Одним из клиентов сервера API является инструмент командной строки kubectl.
Прежде чем выполнить запрос клиента, клиент аутентифицируется и авторизовывается. Примеры плагинов контроля допуска:
- AlwaysPullImages – переопределяет политику imagePullPolicy модуля, присваивая ей значение Always и заставляя извлекать образ всякий раз, когда модуль развертывается;
- ServiceAccount – применяет принятую по умолчанию учетную запись службы к модулям, которые не задают ее явно;
- NamespaceLifecycle – предотвращает создание модулей в пространствах имен, которые находятся в процессе удаления, а также в несуществующих пространствах имен;
- ResourceQuota – гарантирует, что модули в определенном пространстве имен используют только такой объем ЦП и памяти, который был выделен пространству имен.
Сервер API ничего не делает, кроме того что мы обсуждали. Например, он не создает модули при создании ресурса ReplicaSet и не управляет конечными точками службы. Этим занимаются контроллеры в менеджере контроллеров.
Но сервер API даже не говорит этим контроллерам, что делать. Он лишь позволяет этим контроллерам и другим компонентам наблюдать за изменениями в развернутых ресурсах. Компонент плоскости управления может запросить уведомление при создании, изменении или удалении ресурса. Это позволяет компоненту выполнять любую задачу, необходимую в ответ на изменение метаданных кластера.
Клиенты следят за изменениями, открывая соединение HTTP с сервером API. Через это соединение клиент будет получать поток изменений в наблюдаемых объектах.
Работа планировщика заключается в том, чтобы на основе реализованного в сервере API механизма наблюдения ждать вновь созданных модулей и назначать узел для каждого нового модуля, для которого узел еще не был задан.
Планировщик не предписывает выбранному узлу запускать модуль. Планировщик лишь обновляет определение модуля через сервер API. Затем сервер API уведомляет Kubelet о том, что модуль назначен. Как только агент Kubelet на целевом узле увидит, что модуль назначен на его узел, он создает и запускает контейнеры модуля. Вместо выполнения одного планировщика в кластере можно выполнять несколько планировщиков. Затем для каждого модуля указывать планировщика, который должен назначать конкретный модуль, задав в спецификации модуля свойство schedulerName.
Единый процесс менеджера контроллеров в настоящее время объединяет множество контроллеров, выполняющих различные задачи согласования:
- контроллер репликации (контроллер для ресурсов ReplicationController);
- контроллер набора реплик ReplicaSet, набора демонов DaemonSet и задания Job;
- контроллер ресурса развертывания Deployment;
- контроллер набора модулей с внутренним состоянием StatefulSet;
- контроллер узла;
- контроллер службы Service;
- контроллер конечных точек Endpoints;
- контроллер пространства имен Namespace;
- контроллер постоянного тома PersistentVolume;
- другие.
Контроллеры делают много разных вещей, но все они наблюдают за изменениями ресурсов на сервере API и выполняют операции для каждого изменения, будь то создание нового объекта или обновление или удаление существующего объекта. Они никогда не обмениваются друг с другом напрямую.
Агент Kubelet – это компонент, отвечающий за все, что выполняется на рабочем узле. Его первоначальная задача – зарегистрировать узел, на котором он работает, путем создания ресурса узла на сервере API.
Затем он должен непрерывно отслеживать сервер API для модулей, которые были назначены на этот узел, и запускать контейнеры модуля. Он это делает, поручая сконфигурированной среде выполнения контейнеров (то есть платформе Docker, CoreOS платформы rkt или чему-то еще) запустить контейнер из конкретного образа контейнера.
Kubelet постоянно отслеживает запущенные контейнеры и сообщает об их статусе, событиях и потреблении ресурсов серверу API.
Агент Kubelet также является тем компонентом, который выполняет проверки живучести контейнеров, перезапуская контейнеры, когда проверки не срабатывают.
Наконец, он завершает работу контейнеров, когда их модуль удаляется из сервера API, и уведомляет сервер о том, что модуль прекратил работу.
Цель kube-proxy – убедиться, что клиенты могут подключаться к службам, которые вы определяете посредством API Kubernetes. Сетевой прокси kube-proxy гарантирует, что подключения к IP-адресу и порту службы в итоге окажутся в одном из модулей, привязанных к службе (или других, немодульных, конечных точках службы). Когда служба поддерживается несколькими модулями, прокси выполняет балансировку нагрузки между этими модулями.
Kube-proxy получил свое имя, потому что он представлял собой фактический прокси. Однако текущая, гораздо более эффективная реализация для перенаправления пакетов на случайно выбранный внутренний модуль без передачи их через фактический прокси-сервер использует только правила iptables.
Надстройки необязательны и включают такие функциональные средства, как DNS-поиск служб Kubernetes, предоставление нескольких служб HTTP через один внешний IP-адрес, веб-панель мониторинга Kubernetes и т. д. Некоторые из этих компонентов развертываются через ресурс развертывания Deployment или ресурс контроллера репликации ReplicationController, а некоторые – через набор демонов DaemonSet.
Модуль DNS-сервера предоставляется через службу kube-dns, что позволяет перемещать модуль по кластеру, как и любой другой модуль. IP-адрес службы указан в качестве nameserver в файле /etc/resolv.conf внутри каждого контейнера, развернутого в кластере. Модуль kube-dns использует механизм отслеживания сервера API для наблюдения за изменениями служб и конечных точек и обновляет свои ресурсные записи DNS с каждым изменением, позволяя своим клиентам всегда получать (относительно) актуальную информацию DNS. Здесь слово «относительно» использовано потому, что во время между обновлением ресурса службы или конечных точек и временем, когда модуль DNS получает уведомление от наблюдения, записи DNS могут быть неактуальными.
Контроллер входа Ingress запускает обратный прокси-сервер (например, Nginx) и держит его сконфигурированным в соответствии с ресурсами входа Ingress, службы Service и конечных точек Endpoints, определенными в кластере. Этот контроллер, следовательно, должен наблюдать за данными ресурсами (опять же, через механизм наблюдения) и изменять конфигурацию прокси-сервера каждый раз, когда один из них изменяется.
Пример цепочки контроллеров на примере Deployment:
- Контроллер развертывания (DeploymentController) создает набор реплик (ReplicaSet) путем обращения к REST Kubernetes.
- Контроллер набора реплик (ReplicaContoller) создает ресурсы подов (PodController) путем обращения к REST Kubernetes.
- Планировщик (scheduler) назначает узел вновь созданным подам.
- Kubelet запускает контейнеры подов.
Для того чтобы сделать Kubernetes высокодоступным, необходимо запустить несколько ведущих узлов, на которых выполняется несколько экземпляров следующих компонентов:
- etcd – распределенное хранилище данных, в котором хранятся все объекты API;
- сервер API (+ балансировщик);
- менеджер контроллеров – это процесс, в котором работают все контроллеры (first leader);
- планировщик (first leader);
Существует несколько плагинов аутентификации
- из сертификата клиента;
- из токена аутентификации, переданного в заголовке http;
- в результате обычной HTTP-аутентификации;
- другими.
Плагин аутентификации возвращает имя пользователя и группы аутентифицируемого пользователя. Kubernetes различает два вида клиентов, подключающихся к серверу API:
- реальные люди (пользователи);
- поды.
Подразумевается, что пользователи должны управляться внешней системой, такой как система единого входа (Single Sign On, SSO, см. http://kubernetes.io/docs/admin), но модули используют механизм, называемый учетными записями служб, которые создаются и хранятся в кластере как ресурсы ServiceAccount.
Возвращаемые плагином группы являются не чем иным, как строковыми значениями, представляющими произвольные имена групп, однако встроенные группы имеют особое значение:
- группа system:unauthenticated используется для запросов, где ни один из плагинов аутентификации не мог аутентифицировать клиента;
- группа system:authenticated автоматически назначается пользователю, успешно прошедшему аутентификацию;
- группа system:serviceaccounts охватывает все учетные записи ServiceAccount в системе;
- группа system:serviceaccounts:<пространство имен> включает в себя все учетные записи ServiceAccount в определенном пространстве имен.
Имена пользователей учетной записи ServiceAccount форматируются следующим образом system:serviceaccount:<пространство имен>:<имя учетной записи службы>
.
Учетные записи ServiceAccount – это ресурсы, такие же, как модули, секреты, словари конфигурации и т. д., которые ограничиваются отдельными пространствами имен. Устанавливаемая по умолчанию учетная запись ServiceAccount создается автоматически для каждого пространства имен (именно их и использовали ваши модули все время).
Секрет индивидуально настроенного токена был создан и связан с учетной записью службы. Если посмотреть на данные секрета с помощью команды kubectl describe secret {saname}-token-qzq7j, то можно увидеть, что он содержит те же элементы (сертификат CA, пространство имен и токен), что и у токена учетной записи службы по умолчанию (сам токен, очевидно, будет другим).
Учетная запись ServiceAccount также может содержать список секретов для выгрузки образов. Добавление секретов выгрузки образов в учетную запись ServiceAccount избавляет от необходимости добавлять их в каждый модуль по отдельности.
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-service-account
imagePullSecrets:
- name: my-dockerhub-secret
После создания дополнительных учетных записей ServiceAccount вам нужно назначить их модулям. Это делается путем выставления в определении модуля имени учетной записи службы в поле spec.serviceAccountName
.
Управление ролевым доступом RBAC предотвращает несанкционированный просмотр и изменение состояния кластеров. Учетная запись службы по умолчанию не может просматривать состояние кластера, не говоря уже об изменении его каким-либо образом, если только вы не предоставляете ей дополнительные привилегии.
Для Minikube может потребоваться активировать плагин RBAC, запустив Minikube с параметром --extra-config=apiserver.Authorization.Mode=RBAC.
Правила авторизации RBAC настраиваются с помощью четырех ресурсов, которые можно сгруппировать в две группы:
- роли Role и кластерные роли ClusterRole, которые задают, какие глаголы могут выполняться на ресурсах;
- привязки ролей RoleBinding и привязки кластерных ролей ClusterRoleBinding, которые привязывают вышеуказанные роли к определенным пользователям, группам или учетным записям ServiceAccount.
Роли определяют, что вообще можно делать, в то время как привязки определяют, кто может это делать. Различие между ролью и кластерной ролью или между привязкой роли и привязкой кластерной роли состоит в том, что роль и привязка роли являются ресурсами, организованными в пространство имен, тогда как кластерная роль и привязка кластерной роли являются ресурсами уровня кластера.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: foo <- namespace
name: service-reader
rules:
- apiGroups: [""] <- См. https://kubernetes.io/docs/concepts/overview/kubernetes-api/#api-groups
verbs: ["get", "list"] <- операции
resources: ["services"] <- над какими ресурсами
resourceNames: ["api-http", "admin-http"] <- только к ресурсам api-http и admin-http (опционально)
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: foo
name: test
roleRef:
apiGroup: rbac.authorization.k8s.io |
kind: Role |<- привязывает эту роль
name: service-reader |
subjects:
- kind: ServiceAccount |
name: default |<- к этому ServiceAccount
namespace: foo |
- kind: ServiceAccount
name: default
namespace: bar |<- даем роль к аккаунту из другого namespace
- kind: User |<- даем права пользователю
name: alice
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole |<- кластерная роль
metadata:
name: pv-reader
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
nonResourceURLs: |<- Доступ к URL Kubernetes API
- "/api"
verbs: ["get", "list"]
Для кластерной роли всегда нужен ClusterRoleBinding. Кластерная роль может ссылаться не на ресурсы, а на URL-пути (вместо поля resources используется поле nonResourceURLs).
Полный контроль над кластером Kubernetes может быть предоставлен путем присвоения субъекту кластерной роли cluster-admin.
К поду можно привязать порт узла с помощью hostPort: 9000
.
Определенные поды (обычно системные) должны работать в стандартных пространствах имен хоста, что позволяет им видеть и управлять ресурсами и устройствами уровня узла. Например, модулю может потребоваться использовать сетевые адаптеры узла вместо собственных виртуальных сетевых адаптеров. Это может быть достигнуто путем присвоения значения true свойству hostNetwork
в секции spec модуля. Свойства hostPID
и hostIPC
секции spec модуля аналогичны параметру hostNetwork. Если задать для них значение true, то контейнеры модуля будут использовать пространства имен PID и IPC узла, позволяя процессам, запущенным в контейнерах, соответственно видеть все другие процессы на узле или взаимодействовать с ними через IPC.
Свойства securityContext могут быть указаны непосредственно в секции spec модуля и внутри секции spec отдельных контейнеров.
runAsUser: 405
- запустить от пользователя.runAsNonRoot: true
- запретить root.privileged: true
- привелигерованный режим.readOnlyRootFilesystem: true
- запрет на запись в файловую систему контейнера.- Возможности ядра:
capabilities:
add:
– SYS_TIME
drop:
– CHOWN
- fsGroup используется, когда процесс создает файлы в томе, тогда как свойство supplementalGroups определяет список дополнительных идентификаторов групп, с которыми связан пользователь
securityContext:
fsGroup: 555
supplementalGroups: [666, 777]
PodSecurityPolicy – это ресурс кластерного уровня (без пространства имен), который определяет, какие функциональные средства безопасности пользователи могут или не могут использовать в своих модулях.
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: default
spec:
allowedCapabilities: <- какие возможности МОЖНО добавлять в контейнеры
- SYS_TIME
defaultAddCapabilities: <- какие возможности БУДУТ добавлены в контейнеры
- CHOWN
requiredDropCapabilities: <- какие возможности БУДУТ удалены из контейнеров
- SYS_ADMIN
hostIPC: false
hostPID: false
hostNetwork: false
hostPorts:
- max: 50000
min: 40000
- max: 30000
min: 20000
privileged: false
readOnlyRootFilesystem: true
runAsUser:
rule: MustRunAsNonRoot
fsGroup:
rule: RunAsAny
supplementalGroups:
rule: MustRunAs
ranges:
- max: 10
min: 1
seLinux:
rule: RunAsAny
volumes: <- можно использовать синтаксис - '*'
- emptyDir
– configMap
– secret
– downwardAPI
– persistentVolumeClaim
Политики можно привязывать к ролям.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pv-reader
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
resourceNames: ["default"]
verbs: ["get", "list"]
Можно создавать пользователей.
$ kubectl config set-credentials alice --username=alice --password=password
User "alice" set.
NetworkPolicy применяется к модулям, которые совпадают с ее селектором меток, и указывает либо на то, какие источники могут получать доступ к совпавшим модулям, либо на то, к каким целевым назначениям можно получить доступ из совпавших модулей. Это настраивается посредством правил соответственно входа (ingress) и выхода (egress).
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
spec:
podSelector:
matchLabels:
app: backend <- к этим модулям
ingress: <- могут подключаться
- from:
- podSelector: <- только поды с меткой
- matchLabels:
app: frontend
ports:
- port: 80 <- только по этому порту
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
spec:
podSelector:
matchLabels:
app: backend <- к этим модулям
ingress: <- могут подключаться
- from:
- namespaceSelector: <- только поды из неймспейса
matchLabels:
env: test
ports:
- port: 80
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
spec:
podSelector:
matchLabels:
app: backend <- эти модули
egress: <- могут подключаться
- to:
- ipBlock: <- только к адресам
cidr: 192.168.1.0/24 <- с указанным CIDR
ports:
- port: 80
При создании модуля можно указать объем ЦП и памяти, необходимый контейнеру.
apiVersion: v1
kind: Pod
metadata:
name: requests-pod
spec:
containers:
- image: busybox
command: ["dd", "if=/dev/zero", "of=/dev/null"]
name: main
resources:
requests:
cpu: 200m <- нужно 200 миллиядер (1/5 ядра)
memory: 10M <- 10 мегабайт
Если на узле не будет достаточно свободной (то есть никем не зарезервированной запросами), то модуль не будет назначен на узел. Под CPU имеется в виду процессорное время.
Kubernetes также позволяет добавлять в узел собственные настраиваемые ресурсы и запрашивать их в ресурсных запросах модуля. Прежде всего вам, очевидно, нужно поставить Kubernetes в известность о вашем, созданном пользователем ресурсе, добавив его в поле capacity объекта Node. Это можно сделать, выполнив HTTP-запрос PATCH. Имя ресурса может быть любым, например example.org/my-resource, – любым до той поры, пока он не начинается с домена kubernetes.io. Заданное количество должно быть целым числом. Это значение будет автоматически скопировано из поля capacity в поле allocatable.
Не лимитируя память, контейнер (или модуль), работающий на рабочем узле, может съесть всю доступную память и повлиять на все другие модули на узле и любые новые модули, назначаемые узлу.
apiVersion: v1
kind: Pod
metadata:
name: requests-pod
spec:
containers:
- image: busybox
command: ["dd", "if=/dev/zero", "of=/dev/null"]
name: main
resources:
requests:
cpu: 200m
memory: 10M
limits:
cpu: 400m
memory: 50M
В отличие от ресурсных запросов, ресурсные лимиты не ограничены выделяемыми объемами ресурсов узла. Сумме всех лимитов всех модулей на узле разрешено превышать 100% емкости узла.
Контейнеры всегда видят память узла, а не контейнера. Контейнеры также видят все ядра ЦП узла.
Какой контейнер должен быть уничтожен в случае переполнения ресурсов? Kubernetes классифицирует поды на три класса качества обслуживания (QoS):
- BestEffort (самый низкий приоритет);
- Burstable;
- Guaranteed (самый высокий).
Запросы и лимиты на ЦП | Запросы и лимиты на память | Класс QoS контейнера |
---|---|---|
Оба не установлены | Оба не установлены | BestEffort |
Оба не установлены | Запросы < Лимиты | Burstable |
Оба не установлены | Запросы = Лимиты | Burstable |
Запросы < Лимиты | Оба не установлены | Burstable |
Запросы < Лимиты | Запросы < Лимиты | Burstable |
Запросы < Лимиты | Запросы = Лимиты | Burstable |
Запросы = Лимиты | Запросы = Лимиты | Guaranteed |
Из двух контейнеров с одинаковым классом QoS будет уничтожен тот, который в процентном соотношении использует больше своей запрошенной памяти, чем другой.
LimitRange позволяет указывать (для каждого пространства имен) не только минимальный и максимальный лимит, который можно установить для контейнера по каждому ресурсу, но и стандартные ресурсные запросы для контейнеров, которые не устанавливают запросы явным образом.
apiVersion: v1
kind: LimitRange
metadata:
name: example
spec:
limits:
- type: Pod
min:
cpu: 50m
memory: 5Mi
max:
cpu: 1
memory: 1Gi
- type: Container
defaultRequest:
cpu: 100m
memory: 10Mi
default:
cpu: 200m
memory: 100Mi
min:
cpu: 50m
memory: 5Mi
max:
cpu: 1
memory: 1Gi
maxLimitRequestRatio: <- Максимальное соотношение между лимитом и запросом на каждый ресурс
cpu: 4
memory: 10
- type: PersistentVolumeClaim
min:
storage: 1Gi
max:
storage: 10Gi
ResourceQuota лимитирует общий объем ресурсов, доступных в пространстве имен. Объект ResourceQuota применяется к пространству имен, в котором он создан, как и объект LimitRange, но он применяется ко всем ресурсным запросам и лимитам модулей в целом, а не к каждому отдельному модулю или контейнеру по отдельности.
apiVersion: v1
kind: ResourceQuota
metadata:
name: cpu-and-mem
spec:
hard:
requests.cpu: 400m
requests.memory: 200Mi
limits.cpu: 600m
limits.memory: 500Mi
requests.storage: 500Gi
ssd.storageclass.storage.k8s.io/requests.storage: 300Gi
standard.storageclass.storage.k8s.io/requests.storage: 1Ti
pods: 10
replicationcontrollers: 5
secrets: 10
configmaps: 10
persistentvolumeclaims: 4
services: 5
services.loadbalancers: 1
services.nodeports: 2
ssd.storageclass.storage.k8s.io/persistentvolumeclaims: 2
ReqourceQuota также могут быть лимитированы набором областей действия квот.
apiVersion: v1
kind: ResourceQuota
metadata:
name: cpu-and-mem
spec:
scopes:
- BestEffort <- Класс QoS (NotBestEffort)
- NotTerminating <- с неустановленным activeDeadlineSeconds
hard:
requests.cpu: 400m
Kubelet содержит агент cAdvisor, который собирает базовый набор данных о потреблении ресурсов как для отдельных контейнеров, работающих на узле, так и для узла в целом. Для централизованного сбора этих статистических данных по всему кластеру необходимо запустить дополнительный компонент под названием Heapster.
Можно подключить InfluxDB и Grafana.
Горизонтальное автомасштабирование модуля – это автоматическое масштабирование количества реплик модуля, управляемых контроллером. Оно выполняется горизонтальным контроллером, который активируется и конфигурируется путем создания ресурса HorizontalPodAutoscaler.
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubia
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
selector:
matchLabels:
app: kubia
template:
metadata:
name: kubia
labels:
app: kubia
spec:
containers:
- image: luksa/kubia:v1
name: nodejs
resources:
requests:
cpu: 100m
---
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: kubia
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: kubia
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50 <- средняя нагрузка на процессор
- type: Pod
pods:
metric:
name: packets-per-second
target:
type: AverageValue
averageValue: 50 <- средняя количество пакетов
- type: Object
object:
metric:
name: requests-per-second
describedObject:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
name: main-route
target:
type: Value
value: 2k <- средняя количество запросов
Самым простым способом является kubectl kubectl autoscale deployment kubia --cpu-percent=30 --min=1 --max=5
.
Новый узел будет зарезервирован, если после создания нового модуля планировщик не сможет назначить его ни на один из существующих узлов. Кластерный автопреобразователь масштаба высматривает такие модули и запрашивает у поставщика облачных служб запустить дополнительный узел.
Если запросы на ЦП и память всех модулей, работающих на данном узле, ниже 50%, то узел считается ненужным. Если на узле работает системный модуль, то этот узел не будет отменен. Когда узел, который должен быть закрыт, выбран, этот узел сначала помечается как неназначаемый, и тогда все работающие на узле модули вытесняются.
Узел также может быть помечен как неназначаемый и опустошен вручную. Не вдаваясь в подробности, это делается с помощью следующих ниже команд kubectl:
- kubectl cordon <узел> помечает узел как неназначаемый (но не делает ничего с модулями, работающими на этом узле);
- kubectl drain <узел> помечает узел как неназначаемый, а затем вытесняет все модули узла. В обоих случаях никакие новые модули узлу не назначаются до тех пор, пока вы снова не разблокируете его командой kubectl uncordon <узел>.
Некоторые службы требуют, чтобы всегда работало минимальное количество модулей; это особенно верно для кластерных приложений на основе кворума. По этой причине Kubernetes предоставляет способ определения минимального количества модулей, которые должны продолжать работать при выполнении этих типов операций. Это делается путем создания ресурса PodDisruptionBudget.
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: kubia-pdb
spec:
minAvailable: 3
maxUnavailable: 1
selector:
matchLabels:
app: kubia
Kubernetes позволяет влиять на то, куда модули назначаются. Первоначально это делалось только путем задания селектора узлов в спецификации модуля, но позже были добавлены дополнительные механизмы, которые расширили эту функциональность.
На узлы можно наложить ограничения. Ограничения имеют ключ, значение и проявление и представляются в формате <ключ>=<значение>:<проявление>.
$ kubectl taint node node1.k8s node-type=production:NoSchedule
node "node1.k8s" tainted
Существует три возможных проявления:
- Noschedule, которое значит, что модули не будут назначены узлу, если они не допускают ограничения;
- PreferNoSchedule – это мягкая версия NoSchedule, которая означает, что планировщик попытается избежать назначения модуля узлу, но назначит его узлу, если он не может назначить его где-то еще;
- NoExecute, в отличие от проявлений NoSchedule и PreferNoSchedule, которые влияют только на назначение модуля узлу, также влияет на модули, уже работающие на узле. Если в узел добавить ограничение NoExecute, то модули, которые уже работают на этом узле и не допускают ограничения NoExecute, будут вытеснены из узла.
Допуски до работы на узлах определяются в spec.tolerations.
apiVersion: v1
kind: Pod
metadata:
name: requests-pod
spec:
tolerations:
- key: node-type
operator: Equal
value: production
effect: NoSchedule
containers:
- image: busybox
command: ["dd", "if=/dev/zero", "of=/dev/null"]
name: main
resources:
requests:
cpu: 200m
memory: 10M
limits:
cpu: 400m
memory: 50M
Допуски также можно использовать, чтобы указать, как долго Kubernetes должен ждать, прежде чем переназначить модуль на другой узел, если узел, на котором работает модуль, становится неготовым или недостижимым.
...
tolerations:
– effect: NoExecute
key: node.alpha.kubernetes.io/notReady
operator: Exists
tolerationSeconds: 300
– effect: NoExecute
key: node.alpha.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 300
По аналогии с nodeSelector есть более гибкий механизм назначения узлов.
apiVersion: v1
kind: Pod
metadata:
name: requests-pod
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: gpu
operator: In
values:
- "true"
- "yes"
- requiredDuringScheduling... означает, что правила, определенные в этом поле, задают метки, которые узел должен иметь, чтобы модуль был назначен этому узлу;
- ...IgnoredDuringExecution означает, что правила, определенные под этим полем, не влияют на модули, уже работающие на данном узле (все правила всегда заканчиваются на IgnoredDuringExecution);
Самое большое преимущество функционала сходства узлов – это возможность указывать на то, какие узлы планировщик должен предпочитать при назначении конкретного модуля. Это делается посредством поля preferredDuringSchedulingIgnoredDuringExecution.
apiVersion: v1
kind: Pod
metadata:
name: pod
spec:
containers:
- name: pod
image: busybox
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- preference:
matchExpressions:
- key: availability-zone
operator: In
values:
- zone1
- zone2
weight: 20
- preference:
matchExpressions:
- key: some-key
operator: In
values:
- some2
- some4
weight: 20
По аналогии с нодами можно указать сходство по подам. По подам также можно задать podAntiAffinity.
apiVersion: v1
kind: Pod
metadata:
name: pod
spec:
containers:
- name: pod
image: busybox
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution: <- жесткое требование
- topologyKey: kubernetes.io/hostname <- модули этого развертывания должны быть развернуты на том же узле, что и модули
labelSelector: <- которые совпадают с селектором
matchLabels:
app: backend
- Приложения должны ожидать, что они могут быть удалены и перемещены.
- Приложения должны ожидать, что IP-адрес и хостнейм могут быть изменены.
- Приложения должны ожидать, что данные, записанные на диск, исчезнут.
- Использование volume для сохранения данных при перезапусках контейнера.
Если контейнер модуля продолжает сбоить, то агент Kubelet будет бесконечно его перезапускать. Время между перезапусками будет увеличиваться экспоненциально, пока не достигнет пяти минут. Во время этих пятиминутных интервалов модуль, по существу, будет мертвым, потому что процесс его контейнера не работает.
$ kubectl get po
NAME READY STATUS RESTARTS AGE
crashing-pods-f1tcd 0/1 CrashLoopBackOff 5 6m
Когда Kubernetes используется для запуска приложений с несколькими модулями, нет готового способа сообщить системе Kubernetes сначала запускать определенные модули, а остальные только тогда, когда первые модули уже подняты и готовы обслуживать.
В дополнение к регулярным контейнерам поды могут также включить контейнеры инициализации (init). Как следует из названия, их можно использовать для инициализации модуля – это часто означает запись данных в тома пода, которые затем монтируются в главный контейнер пода. Под может иметь любое количество контейнеров инициализации spec.initContainers
.
...
initContainers:
- name: git-clone
image: alpine/git
args:
- 'clone'
- 'https://github.com/phpsite/site.git'
- '/var/www'
volumeMounts:
- name: php-code
mountPath: /var/www
- name: ssh
mountPath: /root/.ssh/
readOnly: true
- name: composer-install
image: composer
args:
- "composer"
- "install"
- "--ignore-platform-reqs"
env:
- name: COMPOSER_HOME
value: /tmp/.composer
...
Поды также позволяют определять два обработчика жизненного цикла:
- постстартовый обработчик (обработчик выполняется параллельно с главным процессом);
- предостановочный обработчик.
Обработчики жизненного цикла подобны проверкам живучести и готовности в том, что они могут:
- исполнять команду внутри контейнера;
- выполнять запрос HTTP GET по URL-адресу.
containers:
- name: php
image: zinvapel/php:7.4-fpm
imagePullPolicy: Always
ports:
- containerPort: 9000
name: fpm
volumeMounts:
- name: php-code
mountPath: /var/www
lifecycle:
postStart:
exec:
command:
- sh
- -c
- "echo 'Hello'"
preStop:
httpGet:
port: 8080
path: /shutdown
Завершение работы модуля инициируется удалением объекта Pod через сервер API. При получении запроса HTTP DELETE сервер API пока объект не удаляет, а только устанавливает в нем поле deletionTimestamp. Модули, в которых установлено поле deletionTimestamp, находятся в процессе терминирования.
Как только агент Kubelet замечает, что модуль должен быть терминирован, он начинает завершать работу каждого контейнера модуля. Он дает каждому контейнеру время, чтобы тот выключился корректно, но это время ограничено. Это время называется льготным периодом терминации и настраивается для каждого модуля. Таймер запускается, как только начинается процесс терминирования. Затем выполняется следующая последовательность событий.
- Запустить предостановочный обработчик, если он сконфигурирован, и дождаться его завершения.
- Отправить сигнал SIGTERM в главный процесс контейнера.
- Подождать до тех пор, пока контейнер не выключится полностью или пока не закончится льготный период терминации (
spec.terminationGracePeriodSeconds
). - Принудительно завершить процесс с помощью SIGKILL, если он еще не завершил работу корректно.
Файлом, в который процесс терминации должен по умолчанию записать сообщение, является /dev/termination-log, но его можно поменять, задав значение в поле terminationMessagePath
в определении контейнера в секции spec модуля.
Можно перенести логи на локальную машину с помощью следующей ниже команды:
$ kubectl cp foo-pod:/var/log/foo.log foo.log
Для того чтобы скопировать файл с локальной машины в модуль, указывается имя модуля во втором аргументе:
$ kubectl cp localfile foo-pod:/etc/remotefile
Чтобы определить новый тип ресурса, достаточно отправить объект CustomResourceDefinition (CRD) на сервер API Kubernetes. Объект CustomResourceDefinition – это описание своего собственного типа ресурса. После отправки CRD пользователи могут создавать экземпляры своего собственного ресурса путем отправки манифестов JSON или YAML на сервере API, как и в случае с любым другим ресурсом Kubernetes.
Пример:
kind: Website
metadata:
name: kubia
spec:
gitRepo: https://github.com/luksa/kubia-website-example.git
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
# name must match the spec fields below, and be in the form: <plural>.<group>
name: websites.extensions.example.com
spec:
scope: Namespaced
group: extensions.example.com <- Группа API
versions:
- name: v1 <- версия
# Each version can be enabled/disabled by Served flag.
served: true
# One and only one version must be marked as the storage version.
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
gitRepo:
type: string
required:
- gitRepo
names:
kind: Website
singular: website
# plural name to be used in the URL: /apis/<group>/<version>/<plural>
plural: websites
shortNames:
- ws
Для того чтобы заставить объекты Website запускать веб-серверный модуль, доступ к которому предоставляется через службу, необходимо создать и развернуть контроллер Website, который будет наблюдать за тем, как сервер API создает объекты Website, а затем для каждого из них будет создавать службу и веб-серверный модуль. (см форк https://github.com/zinvapel/k8s-website-controller)
Можно расширить API Kubernetes создав ресурс типа APIService (https://kubernetes.io/docs/tasks/access-kubernetes-api/). Одним из таких расширений является каталог сервисов (https://kubernetes.io/docs/concepts/extend-kubernetes/service-catalog/).