Содержание статьи
Первым делом заходим на портал Azure, нажимаем «Создать ресурс» и находим сервис под названием Kubernetes Service. Выбираем имя и префикс DNS на свой вкус. Имя влияет на то, как ты будешь обращаться к своему кластеру, а вот префикс влияет на его FQDN-адрес.
Xakep #235. Возрождение эксплоит-китов
INFO
Практически сразу после релиза Google передала проект Kubernetes в созданный при сотрудничестве с The Linux Foundation фонд под названием Cloud Native Computing Foundation.
Вторым шагом предлагается создать service principal. Service principal — это своеобразный сервисный аккаунт, под которым могут выполняться какие-то определенные задачи. Плюсы в том, что права такого аккаунта можно ограничить. Кроме того, можно создать любое количество подобных аккаунтов (в то время как число обычных аккаунтов ограничено подпиской). Найти созданные аккаунты service principal можно в Active Directory среди App Registrations.
RBAC (role-based access control) — это возможность ограничить или предоставить доступ к определенным ресурсам (или группам ресурсов). То есть ты сможешь разграничить, какие пользователи подписки имеют права доступа, а какие не имеют.
На данный момент процесс занимает минут двадцать, но все может зависеть от конфигурации. Найти официальные руководства можно по ссылкам создание кластера AKS с помощью портала и создание кластера AKS с помощью CLI.
Для работы нам понадобится командная строка Azure — CLI (Command Line Interface). Ее можно установить как под Windows, так и под macOS или Linux. Лично я предпочитаю использовать Azure Cloud Shell. Это командная строка, которая запускается из загруженной в браузер страницы портала Azure. Для работы она требует созданного blob-хранилища. Его стоимость составит несколько центов в месяц, и потому я предпочитаю не париться по поводу установки CLI на свою машину.
Kubernetes поддерживает различные технологии контейнеров, но давай рассмотрим самую популярную — Docker. Docker.hub позволяет хранить один приватный образ докера бесплатно. Если нужно больше, то можно разместить их за деньги. Но за деньги приватный образ докера можно разместить и в Azure Container Registry. Сейчас цены начинаются с 5 долларов в месяц (за базовый SKU).
Я создал сервис ACR под именем myservice. Если ты тоже соберешься воспользоваться ACR, то после создания сервиса будет необходимо получить его ключи.
Затем станет возможным залогиниться, выполнив команду
docker login myservice.azurecr.io
Вводим взятые с портала имя пользователя myservice
и пароль PJSeyO9=lCMRDI7dGkz68wjhFGRGxSY3
. Теперь, зайдя в директорию с проектом, можно будет построить образ, одновременно пометив его нужным тегом. А после этого отправить его в облачный сервис:
docker build -t myservice.azurecr.io/myservice .
docker push myservice.azurecr.io/myservice
Секреты, секреты… Предоставляем доступ к образу и сохраняем настройки
При работе с развернутым AKS необходимо получить его креды. Иначе команды kubectl не будут выполняться. Получить доступ к AKS позволяет следующая команда:
az aks get-credentials --resource-group KubernetesGroup --name verycoolcluster
Для того чтобы получить доступ к образу докера, расположенному в репозитории докера в приватном контейнере, понадобится создать секрет. Если у тебя публичный образ, то этот шаг можно пропустить. Для создания файла секрета нужно выполнить команду такого вида:
kubectl create secret docker-registry regcred --docker-server=<your-registry-server> --docker-username=<your-name> --docker-password=<your-pword> --docker-email=<your-email>
Если твой образ находится в репозитории докера, то значением <your-registry-server>
будет https://index.docker.io/v1/
. Для Azure Container Registry FQDN — <registry-name>.azurecr.io
. То есть, чтобы создать секрет для контейнера в моем случае, я выполнил
kubectl create secret docker-registry regcred --docker-server="myservice.azurecr.io" --docker-username="myservice" --docker-password="PJSeyO9=lCMRDI7dGkz68wjhFGRGxSY3" --docker-email="asommer@yandex.ru"
Посмотреть содержимое созданного файла секрета теперь можно с помощью команды
kubectl get secret regcred --output=yaml
Если ты используешь AKS, то можно не создавать файл секрета, а предоставить доступ сервису AKS к сервису ACR иным способом — выполнив особый скрипт. Взять его можно со следующей странички: Authenticate with Azure Container Registry from Azure Kubernetes Service.
#!/bin/bash
AKS_RESOURCE_GROUP=KubernetesGroup
AKS_CLUSTER_NAME=verycoolcluster
ACR_RESOURCE_GROUP=MyACRGroup
ACR_NAME=myservice
# Get the id of the service principal configured for AKS
CLIENT_ID=$(az aks show --resource-group $AKS_RESOURCE_GROUP --name $AKS_CLUSTER_NAME --query "servicePrincipalProfile.clientId" --output tsv)
# Get the ACR registry resource id
ACR_ID=$(az acr show --name $ACR_NAME --resource-group $ACR_RESOURCE_GROUP --query "id" --output tsv)
# Create role assignment
az role assignment create --assignee $CLIENT_ID --role Reader --scope $ACR_ID
Можешь просто модифицировать значения переменных AKS_*
и ACR_*
, скопировать скрипт и вставить его в Azure CLI или Cloud Shell.
Kubernetes содержит безопасное хранилище учетных данных. То есть можно создать файл с настройками, и доступ к этим настройкам получить извне будет затруднительно. В этом файле обычно находятся строки подключения к базам данных и какие-то креды. Если такой информации у тебя в приложении нет (что, правда?), то этот шаг можно пропустить.
Чтобы создать файл с настройками из командной строки, нам понадобятся команды vi.
vi <имя файла>
создаст файл, если он отсутствует, или откроет существующий.- Сохранить введенные изменения — ESC и после этого ZZ.
- Просто выйти без сохранения — ESC и после :q!.
Очень сокращенное описание, но его должно хватить. Могу добавить, что может очень пригодиться использование клавиши Insert.
Итак, через Azure Cloud Shell создаешь файл с произвольным названием (допустим, appsettings.json
) и необходимым содержимым. Допустим, таким:
{
"ConnectionString": "some secret string goes there"
}
И после выполняешь команду
kubectl create secret generic secret-appsettings --from-file=/home/youraccount/appsettings.json
Эта команда создаст секрет с настройками под именем secret-appsettings
. Узнать, на какой путь заменить /home/youraccount
, можно с помощью команды pwd
.
Создание deployment
Deployments предназначены для stateless-сервисов (хорошо сказал, сразу вспоминаются шутки про билингвов, митболы и сторителлинг. 🙂 — Прим. ред.). Они описывают то, как Pods и ReplicaSets будут созданы и как они будут обновляться. Pod — это группа контейнеров (или же один контейнер), которые работают в одном окружении. ReplicaSet следит за тем, чтобы указанное количество pod было запущено и постоянно работало.
Я создаю файл deploy.yaml
, который создаст три пода. Файл содержит следующий код (напоминаю, что пробелы в YAML очень важны):
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: mydeployment
spec:
replicas: 3
minReadySeconds: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: app
image: myservice.azurecr.io/myservice:latest
ports:
- containerPort: 80
name: http
protocol: TCP
imagePullPolicy: Always
env:
- name: "ASPNETCORE_ENVIRONMENT"
value: "Production"
volumeMounts:
- name: secrets
mountPath: /app/secrets
readOnly: true
imagePullSecrets:
- name: regcred
volumes:
- name: secrets
secret:
secretName: secret-appsettings
Рассмотрим код. В начале описывается количество реплик и стратегия обновления. Затем деплойменту задается имя (myapp) и указывается ссылка на образ контейнера. Прописываются порты: 80 — это стандартный порт для HTTP. Далее идут ASP.NET Core’овские настройки environment’а. Затем монтируются креды приватного образа докера и секретные настройки приложения, которые мы не так давно создавали.
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
Этот кусок отвечает за процесс обновления. maxSurge
— количество подов, создаваемых сверх существующих при обновлении (в штуках или процентах). maxUnavailable
— максимальное количество подов, которые могут становиться недоступными во время обновления.
Deployment можно создать с помощью команды
kubectl apply -f deploy.yaml
Знакомься — ingress
Для того чтобы предоставить доступ к сервисам кластера и организовать балансировку нагрузки, используется сервис под названием ingress. Довольно популярен ingress, созданный на основании nginx. Проще всего его установить, используя пакетный менеджер Kubernetes, который называется helm. Плюсом Azure Cloud Shell будет то, что helm уже в нее установлен. Для установки nginx-ingress остается ввести
helm init
подождать немного и выполнить
helm install stable/nginx-ingress --namespace kube-system --set rbac.create=false
Создание SSL-сертификатов с помощью Let’s Encrypt
Так как SSL-сертификат привязывается к какому-то доменному имени, то зададим нашему ресурсу DNS-имя. Выполним следующую команду и возьмем внешний (external) IP:
kubectl get service -l app=nginx-ingress --namespace kube-system
Подставим IP и придуманное нами имя для субдомена в следующий скрипт:
#!/bin/bash
# Public IP address of your ingress controller
IP="168.63.19.2"
# Name to associate with public IP address
DNSNAME="myservice-ingress"
# Get the resource-id of the public ip
PUBLICIPID=$(az network public-ip list --query "[?ipAddress!=null]|[?contains(ipAddress, '$IP')].[id]" --output tsv)
# Update public ip address with DNS name
az network public-ip update --ids $PUBLICIPID --dns-name $DNSNAME
Этот скрипт просто скопируем, вставим в командную строку и таким образом выполним. В качестве имени для субдомена я задал очень оригинальное имя — myservice-ingress
.
Установим менеджер сертификатов, аналогичным способом скопировав и вставив в командную строку следующий скрипт. Здесь даже ничего особо менять не нужно.
helm install \
--name cert-manager \
--namespace kube-system \
stable/cert-manager \
--set ingressShim.defaultIssuerName=letsencrypt-prod \
--set ingressShim.defaultIssuerKind=ClusterIssuer \
--set rbac.create=false \
--set serviceAccount.create=false
Если бы у нас кластер был с RBAC, то скрипт был бы другим.
helm install stable/cert-manager --set ingressShim.defaultIssuerName=letsencrypt-staging --set ingressShim.defaultIssuerKind=ClusterIssuer
Если файл сертификата имеется в наличии, то можно его добавить как-то так:
kubectl create secret tls tls-secret --cert CERT.crt --key KEY-FOR-CERT.key
Но поскольку у нас сертификата, подписанного CA, нет, придется немного потанцевать с бубном. Мы создадим CA с помощью бесплатного сервиса под названием Let’s Encrypt. Let’s Encrypt — это Certificate Authority, который выдает сертификаты совершенно бесплатно. Такая вот альтруистическая организация, цель которой — безопасный интернет.
Итак, создаем файл cluster-issuer.yaml
. Он описывает организацию, выдавшую сертификат.
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: youeemail@yourdomain.ru
privateKeySecretRef:
name: letsencrypt-prod
http01: {}
Тебе необходимо только заменить email на свой адрес, и можно выполнять
kubectl apply -f cluster-issuer.yaml
Затем создаем файл сертификата certificate.yaml
, указав имя созданного ClusterIssuer и домен, для которого предназначен сертификат, — myservice-ingress.westeurope.cloudapp.azure.com
.
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
name: tls-prod-secret
spec:
secretName: tls-prod-secret
dnsNames:
- myservice-ingress.westeurope.cloudapp.azure.com
acme:
config:
- http01:
ingressClass: nginx
domains:
- myservice-ingress.westeurope.cloudapp.azure.com
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
Выполняем:
kubectl apply -f certificate.yaml
Создание сервиса и ingress
В Kubernetes можно создавать сервисы четырех типов.
- Сервис по умолчанию — ClusterIP. Доступ к этому сервису возможен только из кластера по внутреннему IP.
- NodePort автоматически создает сервис ClusterIP. Доступ к NodePort возможен извне по маршруту
<NodeIP>:<NodePort>
. - Балансировщик нагрузки LoadBalancer предоставляет доступ к сервису извне, автоматически создавая сервисы NodePort и ClusterIP.
- ExternalName связывает сервис со внешним именем.
Нам хватит базового сервиса:
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
type: ClusterIP
ports:
- port: 80
name: http
targetPort: http
selector:
app: myapp
Значением selector мы указываем имя нашего деплоймента. Остается создать сервис:
kubectl apply -f service.yaml
И в качестве заключительного этапа создаем ingress. В YAML мы укажем имя cluster-issuer и сертификата. Их мы создавали ранее.
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: myingress
annotations:
kubernetes.io/ingress.class: nginx
certmanager.k8s.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
tls:
- hosts:
- myservice-ingress.westeurope.cloudapp.azure.com
secretName: tls-prod-secret
rules:
- host: myservice-ingress.westeurope.cloudapp.azure.com
http:
paths:
- path: /
backend:
serviceName: myservice
servicePort: 80
Через какое-то время после создания ingress с помощью все той же команды kubectl apply наш микросервис должен стать доступным по адресу https://myservice-ingress.westeurope.cloudapp.azure.com. Кликнув на замочек в адресной строке браузера рядом с https, можно убедиться, что сертификат валидный и выдан CA.