В этой статье мы рассмотрим основные ресурсы Kubernetes для развертывания приложений и обеспечения доступа к ним изнутри и снаружи кластера.
Подготовка окружения
Если вы ещё не подготовили своё рабочее окружение на предыдущих этапах, сделайте это в соответствии с инструкциями статьи «Подготовка окружения».
Если ваше рабочее окружение работало, но перестало, или же последующие инструкции из этой статьи не работают — попробуйте следующее:
Запустим приложение Docker Desktop. Приложению понадобится некоторое время для того, чтобы запустить Docker. Если никаких ошибок в процессе запуска не возникло, то проверим, что Docker запущен и корректно настроен:
docker run hello-world
Результат успешного выполнения команды:
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b8dfde127a29: Pull complete
Digest: sha256:9f6ad537c5132bcce57f7a0a20e317228d382c3cd61edae14650eec68b2b345c
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
При возникновении проблем обратитесь к документации Docker для их устранения.
Запустим приложение Docker Desktop. Приложению понадобится некоторое время для того, чтобы запустить Docker. Если никаких ошибок в процессе запуска не возникло, то проверим, что Docker запущен и корректно настроен:
docker run hello-world
Результат успешного выполнения команды:
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b8dfde127a29: Pull complete
Digest: sha256:9f6ad537c5132bcce57f7a0a20e317228d382c3cd61edae14650eec68b2b345c
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
При возникновении проблем обратитесь к документации Docker для их устранения.
Запустим Docker:
sudo systemctl restart docker
Убедимся, что Docker запустился:
sudo systemctl status docker
Результат выполнения команды, если Docker успешно запущен:
● docker.service - Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2021-06-24 13:05:17 MSK; 13s ago
TriggeredBy: ● docker.socket
Docs: https://docs.docker.com
Main PID: 2013888 (dockerd)
Tasks: 36
Memory: 100.3M
CGroup: /system.slice/docker.service
└─2013888 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
dockerd[2013888]: time="2021-06-24T13:05:16.936197880+03:00" level=warning msg="Your kernel does not support CPU realtime scheduler"
dockerd[2013888]: time="2021-06-24T13:05:16.936219851+03:00" level=warning msg="Your kernel does not support cgroup blkio weight"
dockerd[2013888]: time="2021-06-24T13:05:16.936224976+03:00" level=warning msg="Your kernel does not support cgroup blkio weight_device"
dockerd[2013888]: time="2021-06-24T13:05:16.936311001+03:00" level=info msg="Loading containers: start."
dockerd[2013888]: time="2021-06-24T13:05:17.119938367+03:00" level=info msg="Loading containers: done."
dockerd[2013888]: time="2021-06-24T13:05:17.134054120+03:00" level=info msg="Daemon has completed initialization"
systemd[1]: Started Docker Application Container Engine.
dockerd[2013888]: time="2021-06-24T13:05:17.148493957+03:00" level=info msg="API listen on /run/docker.sock"
Теперь проверим, что Docker доступен и корректно настроен:
docker run hello-world
Результат успешного выполнения команды:
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b8dfde127a29: Pull complete
Digest: sha256:9f6ad537c5132bcce57f7a0a20e317228d382c3cd61edae14650eec68b2b345c
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
При возникновении проблем обратитесь к документации Docker для их устранения.
Запустим кластер minikube, уже настроенный в начале статьи “Подготовка окружения”:
minikube start
Выставим Namespace по умолчанию, чтобы не указывать его при каждом вызове kubectl
:
kubectl config set-context minikube --namespace=werf-guide-app
Результат успешного выполнения команды:
😄 minikube v1.20.0 on Ubuntu 20.04
✨ Using the docker driver based on existing profile
👍 Starting control plane node minikube in cluster minikube
🚜 Pulling base image ...
🎉 minikube 1.21.0 is available! Download it: https://github.com/kubernetes/minikube/releases/tag/v1.21.0
💡 To disable this notice, run: 'minikube config set WantUpdateNotification false'
🔄 Restarting existing docker container for "minikube" ...
🐳 Preparing Kubernetes v1.20.2 on Docker 20.10.6 ...
🔎 Verifying Kubernetes components...
▪ Using image gcr.io/google_containers/kube-registry-proxy:0.4
▪ Using image k8s.gcr.io/ingress-nginx/controller:v0.44.0
▪ Using image registry:2.7.1
▪ Using image docker.io/jettech/kube-webhook-certgen:v1.5.1
▪ Using image docker.io/jettech/kube-webhook-certgen:v1.5.1
▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🔎 Verifying registry addon...
🔎 Verifying ingress addon...
🌟 Enabled addons: storage-provisioner, registry, default-storageclass, ingress
🏄 Done! kubectl is now configured to use "minikube" cluster and "werf-guide-app" namespace by default
Убедитесь, что вывод команды содержит строку:
Restarting existing docker container for "minikube"
Если её нет, значит был создан новый кластер minikube вместо использования старого. В таком случае повторите установку рабочего окружения с minikube с самого начала.
Теперь запустите команду в фоновом PowerShell-терминале и не закрывайте его:
minikube tunnel --cleanup=true
Запустим кластер minikube, уже настроенный в начале статьи “Подготовка окружения”:
minikube start --namespace werf-guide-app
Выставим Namespace по умолчанию, чтобы не указывать его при каждом вызове kubectl
:
kubectl config set-context minikube --namespace=werf-guide-app
Результат успешного выполнения команды:
😄 minikube v1.20.0 on Ubuntu 20.04
✨ Using the docker driver based on existing profile
👍 Starting control plane node minikube in cluster minikube
🚜 Pulling base image ...
🎉 minikube 1.21.0 is available! Download it: https://github.com/kubernetes/minikube/releases/tag/v1.21.0
💡 To disable this notice, run: 'minikube config set WantUpdateNotification false'
🔄 Restarting existing docker container for "minikube" ...
🐳 Preparing Kubernetes v1.20.2 on Docker 20.10.6 ...
🔎 Verifying Kubernetes components...
▪ Using image gcr.io/google_containers/kube-registry-proxy:0.4
▪ Using image k8s.gcr.io/ingress-nginx/controller:v0.44.0
▪ Using image registry:2.7.1
▪ Using image docker.io/jettech/kube-webhook-certgen:v1.5.1
▪ Using image docker.io/jettech/kube-webhook-certgen:v1.5.1
▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🔎 Verifying registry addon...
🔎 Verifying ingress addon...
🌟 Enabled addons: storage-provisioner, registry, default-storageclass, ingress
🏄 Done! kubectl is now configured to use "minikube" cluster and "werf-guide-app" namespace by default
Убедитесь, что вывод команды содержит строку:
Restarting existing docker container for "minikube"
Если её нет, значит был создан новый кластер minikube вместо использования старого. В таком случае повторите установку рабочего окружения с minikube с самого начала.
Если вы непреднамеренно удалили Namespace приложения, то необходимо выполнить следующие команды, чтобы продолжить прохождение руководства:
kubectl create namespace werf-guide-app
kubectl create secret docker-registry registrysecret \
--docker-server='https://index.docker.io/v1/' \
--docker-username='<ИМЯ ПОЛЬЗОВАТЕЛЯ DOCKER HUB>' \
--docker-password='<ПАРОЛЬ ПОЛЬЗОВАТЕЛЯ DOCKER HUB>'
Результат успешного выполнения команды:
namespace/werf-guide-app created
secret/registrysecret created
Если ничего не помогло, то пройдите статью «Подготовка окружения» с начала, подготовив новое окружение с нуля. Если и это не помогло, тогда, пожалуйста, расскажите о своей проблеме в нашем Telegram или оставьте Issue на GitHub, и мы обязательно вам поможем.
Подготовка репозитория
Используем уже существующий репозиторий с приложением (выполните в Bash/PowerShell):
cd ~/werf-guide/app
Возникли проблемы с репозиторием? Попробуйте инструкции на вкладке «Начинаю проходить руководство с этой статьи» выше.
Подготовим новый репозиторий с приложением:
Выполним следующий набор команд в PowerShell:
# Склонируем репозиторий с примерами в ~/werf-guide/guides, если он ещё не был склонирован.
if (-not (Test-Path ~/werf-guide/guides)) {
git clone https://github.com/werf/website $env:HOMEPATH/werf-guide/guides
}
# Скопируем файлы приложения (пока без изменений) в ~/werf-guide/app.
rm -Recurse -Force ~/werf-guide/app
cp -Recurse -Force ~/werf-guide/guides/examples/basic/005_kubernetes_basics ~/werf-guide/app
# Сделаем из директории ~/werf-guide/app git-репозиторий.
cd ~/werf-guide/app
git init
git add .
git commit -m initial
Выполним следующий набор команд в Bash:
# Склонируем репозиторий с примерами в ~/werf-guide/guides, если он ещё не был склонирован.
test -e ~/werf-guide/guides || git clone https://github.com/werf/website ~/werf-guide/guides
# Скопируем файлы приложения (пока без изменений) в ~/werf-guide/app.
rm -rf ~/werf-guide/app
cp -rf ~/werf-guide/guides/examples/basic/005_kubernetes_basics ~/werf-guide/app
# Сделаем из директории ~/werf-guide/app git-репозиторий.
cd ~/werf-guide/app
git init
git add .
git commit -m initial
Шаблоны, манифесты и ресурсы
werf при деплое использует YAML-манифесты, описывающие Kubernetes-ресурсы. Эти манифесты получаются из Helm-шаблонов, которые лежат в .helm/templates
и .helm/charts
.
Helm-шаблон, описывающий Pod (один из Kubernetes-ресурсов), лежит в .helm/templates/pod.yaml
и выглядит так:
apiVersion: v1
kind: Pod
metadata:
name: standalone-pod
spec:
containers:
- name: main
image: {{ $.Values.image }} # Helm-шаблонизация, позволяет параметризовать имя образа для контейнера.
command: ["tail", "-f", "/dev/null"]
Перед деплоем этот Helm-шаблон с помощью werf преобразовывается в манифест, который выглядит так:
apiVersion: v1
kind: Pod
metadata:
name: standalone-pod
spec:
containers:
- name: main
image: alpine # если пользователь в качестве имени образа указал "alpine".
command: ["tail", "-f", "/dev/null"]
А уже во время деплоя этот манифест становится ресурсом Pod в Kubernetes-кластере. Посмотреть, как этот ресурс выглядит в кластере, можно с помощью команды kubectl get
:
kubectl get pod standalone-pod --output yaml
Так бы выглядел результат:
apiVersion: v1
kind: Pod
metadata:
name: standalone-pod
namespace: default
spec:
containers:
- name: main
image: alpine
command: ["tail", "-f", "/dev/null"]
# ...
status:
phase: Running
podIP: 172.17.0.7
startTime: "2021-06-02T13:17:47Z"
Запуск приложений
Использование ресурса Pod — простейший способ запустить в Kubernetes один или несколько контейнеров. В манифесте Pod’а описываются контейнеры и их конфигурация. Но на практике сами по себе Pod’ы стараются не деплоить — вместо них деплоят Controller’ы, которые создают и управляют Pod’ами за вас. Один из таких контроллеров — Deployment. Создавая Pod’ы с помощью Deployment, вы сильно упрощаете управление Pod’ами.
Вот некоторые возможности, которые предоставляет Deployment (и которых нет у Pod’ов самих по себе):
- При автоматическом или ручном удалении Pod’а в Deployment’е этот Pod будет перезапущен;
- Большая часть конфигурации Pod’а не может быть обновлена. Чтобы обновить его конфигурацию, Pod нужно пересоздать. Конфигурацию же Pod’а в Deployment’е можно обновлять без пересоздания Deployment’а;
- Обновление конфигурации Pod’ов происходит без простоя: при обновлении часть Pod’ов со старой конфигурацией остаётся активной до тех пор, пока новые Pod’ы не начнут успешно запускаться;
- Одним Deployment’ом можно запускать нескольких Pod’ов сразу, в том числе на разных узлах (Node’ах).
Разные контроллеры имеют разные возможности, связанные с созданием и управлением Pod’ами. Вот основные контроллеры и их практическое применение:
- Deployment — стандарт для деплоя stateless-приложений;
- StatefulSet — стандарт для деплоя stateful-приложений;
- DaemonSet — для деплоя приложений, которые должны быть запущены только по одному экземпляру на каждом узле (агенты для логирования, мониторинга);
- Job — для запуска разовых задач в Pod’ах (например, миграции базы данных);
- CronJob — для многоразового запуска задач в Pod’ах по расписанию (например, регулярная очистка чего-либо).
Получить список ресурсов определенного типа в кластере можно с помощью всё той же команды kubectl get
(в данном примере команды последовательно возвратят список всех Pod, Deployment, StatefulSet, Job и CronJob из всех namespace’ов):
kubectl get --all-namespaces pod
NAMESPACE NAME READY STATUS RESTARTS AGE
ingress-nginx ingress-nginx-admission-create-8bgk7 0/1 Completed 0 11d
ingress-nginx ingress-nginx-admission-patch-8fkgl 0/1 Completed 1 11d
ingress-nginx ingress-nginx-controller-5d88495688-6lgx9 1/1 Running 1 11d
kube-system coredns-74ff55c5b-hgzzx 1/1 Running 1 13d
kube-system etcd-minikube 1/1 Running 1 13d
kube-system kube-apiserver-minikube 1/1 Running 1 13d
kube-system kube-controller-manager-minikube 1/1 Running 1 13d
kube-system kube-proxy-gtrcq 1/1 Running 1 13d
kube-system kube-scheduler-minikube 1/1 Running 1 13d
kube-system storage-provisioner 1/1 Running 2 13d
kubectl get --all-namespaces deployment
NAMESPACE NAME READY UP-TO-DATE AVAILABLE AGE
ingress-nginx ingress-nginx-controller 1/1 1 1 11d
kube-system coredns 1/1 1 1 13d
kubectl get --all-namespaces statefulset
kubectl get --all-namespaces job
kubectl get --all-namespaces cronjob
Также можно получить полную конфигурацию любого ресурса в YAML-формате, если добавить в команду kubectl get
опцию --output yaml
, например:
kubectl -n ingress-nginx get deployment ingress-nginx-controller --output yaml
В ответ получим примерно следующее:
# ...
kind: Deployment
metadata:
name: ingress-nginx-controller
# ...
Чаще всего вам придется сталкиваться с ресурсом Deployment, поэтому рассмотрим его подробнее. Про остальные типы контроллеров можно прочитать в документации к Kubernetes.
Deployment
Рассмотрим Deployment нашего приложения:
apiVersion: apps/v1
# Тип ресурса.
kind: Deployment
metadata:
# Имя этого Deployment.
name: werf-guide-app
spec:
# Кол-во реплик этого Deployment.
replicas: 1
selector:
matchLabels:
# По этому лейблу Deployment находит обслуживаемые им Pods.
app: werf-guide-app
# Описание шаблона, на основе которого создаются Pods для этого Deployment.
template:
metadata:
labels:
# Лейблы создаваемых Pods.
app: werf-guide-app
spec:
imagePullSecrets:
# Имя секрета с Docker credentials для доступа к container registry.
- name: registrysecret
containers:
# Имя первого контейнера.
- name: app
# Имя и тег образа контейнера.
image: {{ .Values.werf.image.app }}
# Основная команда контейнера, начнёт выполняться при его запуске.
command: ["/app/start.sh"]
# Порт, на котором будет слушать запускаемое приложение.
ports:
- containerPort: 8000
Более подробное описание Deployment доступно в официальной документации.
Развернём наше приложение:
werf converge --repo <ИМЯ ПОЛЬЗОВАТЕЛЯ DOCKER HUB>/werf-guide-app
Проверим, что наш Deployment создался:
kubectl get deployment werf-guide-app
В ответ получим примерно следующее:
NAME READY UP-TO-DATE AVAILABLE AGE
werf-guide-app 1/1 1 1 25s
А теперь посмотрим на Pod, созданный нашим Deployment:
kubectl get pod -l app=werf-guide-app
В ответ отобразится примерно следующее:
NAME READY STATUS RESTARTS AGE
werf-guide-app-8b748b85d-829j9 1/1 Running 0 25h
Service и Ingress
С помощью Deployment’а мы можем развернуть наше stateless-приложение, но если пользователям или другим приложениям потребуется связываться с этим приложением изнутри или снаружи кластера, то в этом нам помогут два других ресурса: Ingress и Service.
Рассмотрим наш Ingress-ресурс:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
# Имя Ingress-ресурса.
name: werf-guide-app
spec:
rules:
# Домен, запросы на который будут обрабатываться в paths ниже.
- host: werf-guide-app.test
http:
paths:
# Запросы с префиксом / (все запросы) перенаправятся на порт 8000 нашего Service.
- path: /
pathType: Prefix
backend:
service:
name: werf-guide-app
port:
number: 8000
И рассмотрим Service-ресурс:
apiVersion: v1
kind: Service
metadata:
# Имя Service.
name: werf-guide-app
spec:
selector:
# Этот Service перенаправит трафик на Pods с этим лейблом.
app: werf-guide-app
ports:
- name: http
# Трафик с 8000-го порта Service перенаправится на 8000-й порт Pod'а.
port: 8000
Убедимся, что ресурс Service создан:
kubectl get service werf-guide-app
В ответ получим примерно следующее:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
werf-guide-app ClusterIP 10.107.19.126 <none> 8000/TCP 35s
Также убедимся, что ресурс Ingress создан:
kubectl get ingress werf-guide-app
В ответ получим примерно следующее:
NAME CLASS HOSTS ADDRESS PORTS AGE
werf-guide-app <none> werf-guide-app.test 80 3m21s
Если несколько упростить, то эти два ресурса позволят HTTP-пакетам, приходящим на NGINX Ingress Controller, у которых есть заголовок Host: werf-guide-app.test
, быть перенаправленными на 8000-й порт Service’а werf-guide-app
, а оттуда — на 8000-й порт одного из Pods нашего Deployment’а. В конфигурации по умолчанию Service будет перенаправлять запросы на все Pods Deployment’а поровну.
В общем случае схема взаимодействия между разными ресурсами внутри кластера выглядит следующим образом:
Обратимся к нашему приложению через Ingress:
curl http://werf-guide-app.test/ping
В ответ получим:
hello world
При этом Service-ресурсы нужны не только для связи Ingress и приложения. Service-ресурсы также дают возможность ресурсам внутри кластера общаться между собой. При создании Service создается доменное имя <ServiceName>.<NamespaceName>.svc.cluster.local
, доступное изнутри кластера. Также Service доступен и по более коротким доменным именам:
<ServiceName>
— при обращении из того же Namespace,<ServiceName>.<NamespaceName>
— при обращении из другого Namespace.
Создадим новый контейнер, не имеющий отношения к нашему приложению:
kubectl run werf-temporary-deployment --image=alpine --rm -it -- sh
В запустившемся контейнере обратимся к нашему приложению через Service:
apk add curl # Установим curl внутри контейнера.
curl http://werf-guide-app:8000/ping # Обратимся к одному из Pod'ов нашего приложения через Service.
В ответ получим:
hello world
Использование Ingress-ресурсов — не единственный способ получить доступ к приложению снаружи кластера. Service’ы типа
LoadBalancer
иNodePort
позволяют предоставить доступ к приложению снаружи и без Ingress’ов. Почитать подробнее про Service’ы можно в официальной документации. А про Ingress’ы — здесь.