Доброго времени суток. Очередная заметка из моего опыта. В этот раз поверхностно о базовой инфраструктуре, которую использую, если надо что-то выгрузить, а рядом нет devOps ребят. Но текущий уровень абстракции, в технологиях, позволяет уже около года жить с этой инфраструктурой, поднятой за ночь, используя интернет и готовые вещи.
Ключевые слова — AWS + Terraform + kops . Если это полезно мне — возможно будет полезно кому-нибудь еще. Добро пожаловать в комментарии.
-1. То, с чем имеем дело
Классическая ситуация — проект написан до такой стадии, когда его необходимо куда-то выгружать и начинать использовать. И проект сложней, чем простая html страница. Хотелось бы возможности горизонтального масштабирования, идентичности окружения на локальных, тестовых, прод стендах и более-менее нормальный процесс деплоя.
Речь пойдет о приложение на Laravel, чтобы показать от начала и до конца весь процесс. Но аналогичным образом можно деплоить россыпь сервисов на go, python-приложения, небольшие сайты на WP, html-страницы и много всего. До какого-то уровня этого достаточно, а затем уже и в команде появляется отдельный человек, который улучшит и дополнит.Последнее время я пришел к тому, что на локальных машинах устанавливаю GoLand, PhpStorm, Docker, Git и полностью готов к работе. Да и управлять с одной машины вы можете россыпью кластеров, поэтому весь процесс буду описывать без учета ОС, на которой работаете, упаковывая все вещи в докер контейнер.
0. Готовимся к работе.
Давайте представим, что мы уже зарегистрировали аккаунт на AWS, попросили через техподдержку увеличить лимиты аккаунта на количество одновременно запущенных серверов, создали IAM пользователя и теперь у нас есть Access Key + Secret Key. Зона — us-east-1.
Что нам понадобится на локальном компьютере? AWS CLI, Terraform для декларативного управления AWS, kubectl, kops для настройки кластера и Helm, для развертывания некоторых сервисов. Собираем Dockerfile (который я давно нашел где-то на просторах гитхаба, но не могу найти где). Пишем свой docker-compose.yml для маунта директорий и Makefile для алиасов.
Dockerfile
FROM ubuntu:16.04
ARG AWSCLI_VERSION=1.12.1
ARG HELM_VERSION=2.8.2
ARG ISTIO_VERSION=0.6.0
ARG KOPS_VERSION=1.9.0
ARG KUBECTL_VERSION=1.10.1
ARG TERRAFORM_VERSION=0.11.0
# Install generally useful things
RUN apt-get update && apt-get -y --force-yes install --no-install-recommends curl dnsutils git jq net-tools ssh telnet unzip vim wget && apt-get clean && apt-get autoclean && apt-get autoremove && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Install AWS CLI
RUN apt-get update && apt-get -y --force-yes install python-pip && pip install awscli==${AWSCLI_VERSION} && apt-get clean && apt-get autoclean && apt-get autoremove && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Install Terraform
RUN wget -O terraform.zip https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip && unzip terraform.zip && mv terraform /usr/local/bin/terraform && chmod +x /usr/local/bin/terraform && rm terraform.zip
# Install kubectl
ADD https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl /usr/local/bin/kubectl
RUN chmod +x /usr/local/bin/kubectl
# Install Kops
ADD https://github.com/kubernetes/kops/releases/download/${KOPS_VERSION}/kops-linux-amd64 /usr/local/bin/kops
RUN chmod +x /usr/local/bin/kops
# Install Helm
RUN wget -O helm.tar.gz https://storage.googleapis.com/kubernetes-helm/helm-v${HELM_VERSION}-linux-amd64.tar.gz && tar xfz helm.tar.gz && mv linux-amd64/helm /usr/local/bin/helm && chmod +x /usr/local/bin/helm && rm -Rf linux-amd64 && rm helm.tar.gz
# Create default user "kops"
RUN useradd -ms /bin/bash kops
WORKDIR /home/kops
USER kops
# Ensure the prompt doesn't break if we don't mount the ~/.kube directory
RUN mkdir /home/kops/.kube && touch /home/kops/.kube/config
docker-compose.yml
version: '2.1'
services:
cluster-main:
container_name: cluster.com
image: cluster.com
user: root
stdin_open: true
volumes:
- ./data:/data
- ./.ssh:/root/.ssh
- ./.kube:/root/.kube
- ./.aws:/root/.aws
cluster-proxy:
container_name: cluster.com-kubectl-proxy
image: cluster.com
user: root
entrypoint: kubectl proxy --address='0.0.0.0' --port=8001 --accept-hosts='.*'
ports:
- "8001:8001"
stdin_open: true
volumes:
- ./data:/data
- ./.ssh:/root/.ssh
- ./.kube:/root/.kube
- ./.aws:/root/.aws
Makefile
docker.build:
docker build -t cluster.com .
docker.run:
docker-compose up -d
docker.bash:
docker exec -it cluster.com bash
Dockerfile — берем базовый образ ubuntu и устанавливаем весь софт. Makefile — просто для удобства, можно использовать и обычный механизм алиасов. Docker-compose.yml — мы добавили дополнительный контейнер, который нам пробросит в браузер K8S Dashboard, если нужно визуально что-то посмотреть.
Создаем папки data, .ssh, .kube, .aws в корне и кладем туда наш конфиг для aws, ssh ключи и можем собирать и запускать наш контейнер через make docker.build & make docker.run.
Ну и в папке data создаем папку, в которую положим yaml файлы k8s, а рядом вторую, в которой будем хранить состояние terraform кластера. Примерный результат этого этапа положил на гитхаб.
1. Поднимаем наш кластер.
Дальше будет вольный перевод этой заметки. Я опущу много теоретических моментов, постараюсь описать краткую выжимку. Все таки формат моей заметки — tldr.
В нашу папку data/aws-cluster-init-kops-terraform клонируем то, что лежит в этом репозитории и заходим в консоль контейнера через make docker.bash. Начинается россыпь скучных команд.
AWS CLI
Создаем пользователя kops , добавляем права доступа и переконфигурируем AWS CLI на него, чтобы не запускать команды от суперюзера.
aws iam create-group --group-name kops
# Политики доступа
aws iam attach-group-policy --policy-arn arn:aws:iam::aws:policy/AmazonEC2FullAccess --group-name kops
aws iam attach-group-policy --policy-arn arn:aws:iam::aws:policy/AmazonRoute53FullAccess --group-name kops
aws iam attach-group-policy --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess --group-name kops
aws iam attach-group-policy --policy-arn arn:aws:iam::aws:policy/IAMFullAccess --group-name kops
aws iam attach-group-policy --policy-arn arn:aws:iam::aws:policy/AWSCertificateManagerFullAccess --group-name kops
aws iam attach-group-policy --policy-arn arn:aws:iam::aws:policy/AmazonVPCFullAccess --group-name kops
aws iam create-user --user-name kops
aws iam add-user-to-group --user-name kops --group-name kops
aws iam create-access-key --user-name kops
aws configure
Инициализируем Terraform
Изменяем в файле data/aws-cluster-init-kops-terraform/variables.tf имя кластера на нужное. Не забываем взять из файла update.json наши dns сервера и обновить их там, где покупали свой домен.
# Переходим в папку
cd /data/aws-cluster-init-kops-terraform
# Экспортируем переменные от AWS CLI
export AWS_ACCESS_KEY_ID=$(aws configure get aws_access_key_id)
export AWS_SECRET_ACCESS_KEY=$(aws configure get aws_secret_access_key)
# Инициализируем terraform
terraform init
terraform get
terraform apply
# Берем NS сервера
cat update-zone.json | jq ".Changes[].ResourceRecordSet.Name=\"$(terraform output name).\"" | jq ".Changes[].ResourceRecordSet.ResourceRecords=$(terraform output -json name_servers | jq '.value|[{"Value": .[]}]')" > update-zone.json
Kops
Создаем кластер через kops, экспортируя конфиг в .tf файл.
export NAME=$(terraform output cluster_name)
export KOPS_STATE_STORE=$(terraform output state_store)
export ZONES=$(terraform output -json availability_zones | jq -r '.value|join(",")')
kops create cluster --master-zones $ZONES --zones $ZONES --topology private --dns-zone $(terraform output public_zone_id) --networking calico --vpc $(terraform output vpc_id) --target=terraform --out=. ${NAME}
Здесь нужна небольшая ремарка. Terraform создаст VPC, и нам необходимо будет немного подправить конфиг, который нам отдаст kops. Это делается достаточно просто, через вспомогательный образ ryane/gensubnets:0.1
# Внутри конейнера
terraform output -json > subnets.json
# На вашей машине хоста
echo subnets.json | docker run --rm -i ryane/gensubnets:0.1
Вы можете добавить сразу политики для route53.
additionalPolicies:
master: |
[
{
"Effect": "Allow",
"Action": ["route53:ListHostedZonesByName"],
"Resource": ["*"]
},
{
"Effect": "Allow",
"Action": ["elasticloadbalancing:DescribeLoadBalancers"],
"Resource": ["*"]
},
{
"Effect": "Allow",
"Action": ["route53:ChangeResourceRecordSets"],
"Resource": ["*"]
}
]
node: |
[
{
"Effect": "Allow",
"Action": ["route53:ListHostedZonesByName"],
"Resource": ["*"]
},
{
"Effect": "Allow",
"Action": ["elasticloadbalancing:DescribeLoadBalancers"],
"Resource": ["*"]
},
{
"Effect": "Allow",
"Action": ["route53:ChangeResourceRecordSets"],
"Resource": ["*"]
}
]
Редактируем через kops edit cluster ${NAME}.
Теперь мы можем поднимать сам кластер.
kops update cluster --out=. --target=terraform ${NAME}
terraform apply
Все пройдет хорошо, контекст kubectl изменится. В папке data/aws-cluster-init-kops-terraform у нас будет хранится состояние кластера. Можно просто положить все в git и отправить в приватный репозиторий битбакета.
$ kubectl get nodes
NAME STATUS AGE
ip-10-20-101-252.ec2.internal Ready,master 7m
ip-10-20-103-232.ec2.internal Ready,master 7m
ip-10-20-103-75.ec2.internal Ready 5m
ip-10-20-104-127.ec2.internal Ready,master 6m
ip-10-20-104-6.ec2.internal Ready 5m
2. Поднимаем наше приложение
Теперь, когда у нас есть что-то, мы можем разворачивать в кластере наши сервисы. Примерные конфиги я положу в этот же репозиторий. Их можно пачкой положить в data/k8s.
Сервисные шутки
Начнем с сервисных вещей. Нам необходим helm, route53, storage-classes и доступ к нашему приватному registry на hub.docker.com. Ну или к любому другому, если есть такое желание.
# Init helm
kubectl create serviceaccount --namespace kube-system tiller
kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
kubectl patch deploy --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'
helm init
kubectl apply -f default-namespace.yaml
kubectl apply -f storage-classes.yaml
kubectl apply -f route53.yaml
kubectl apply -f docker-hub-secret.yml
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml
PostgreSQL + Redis
Я очень много раз обжигался, используя докер не для stateless контейнеров, но последняя конфигурация показала пока себя наиболее подходящей. Использую Stolon, чтобы обеспечить масштабируемость. Около года полет нормальный.
Развертываем helm-charts и пару быстреньких конфигов Redis.
# Разворачиваем etcd для stolon
cd etcd-chart
helm install --name global-etcd .
# Разворачиваем сам stolon
cd stolon-chart
helm dep build
helm install --name global-postgres .
# Разворачиваем redis
kubectl apply -f redis
Nginx + PHP
Обычная связка. Nginx и php-fpm. Конфиги я особо не вычищал, но каждый сможет под себя настроить. Перед применением необходимо указать образ, из которого мы будем брать код + добавить строчку сертификата из AWS Certificate Manager. Сам php — можно брать из докерхаба, но я собрал свой приватный, добавив немного библиотек.
kubectl apply -f nginx
kubectl apply -f php
В нашем образе с кодом мы храним его в папке /crm-code. Подменяем на свой образ и оно вполне корректно заработает. Файл — nginx/deployment.yml.
Выводим наружу домен. Route53 сервис его подхватит, изменит/добавит DNS записи, сертификат подгрузится на ELB из AWS Certificate Manager. Файл — nginx/service.yml.
Пробрасываем env переменные в php, чтобы иметь их внутри и подключаться к PostgreSQL/Redis. Файл — php/deployment.yml.
Как итог, мы имеем K8S кластер, который на базовом уровне мы можем масштабировать, добавлять новые сервисы, новые сервера (ноды), изменять количество PostgreSQL, PHP, Nginx инстансов и прожить до того, как в команде появится отдельный человек, который будет этим заниматься.
В рамках этой небольшой заметки я не буду касаться вопросов бэкапов/мониторинга этого всего добра. На начальном этапе будет достаточно localhost:8001/ui от K8S Dashboard сервиса. Позже можно будет прикрутить Prometheus, Grafana, Barman, либо любые другие схожие решения.
Используя терминал, либо Teamcity, Jenkins обновление кода будет делаться примерно так.
# Собираем образ с кодом где-то на локальной машине или в Teamcity
docker build -t GROUP/crm-code:latest .
docker push GROUP/crm-code:latest
# Обновляем код (здесь немного лайфхаков)
kubectl set image deployment/php-fpm php-fpm=GROUP/php-fpm
kubectl rollout status deployment/php-fpm
kubectl set image deployment/php-fpm php-fpm=GROUP/php-fpm:latest
kubectl set image deployment/nginx nginx=danday74/nginx-lua
kubectl rollout status deployment/nginx
kubectl set image deployment/nginx nginx=danday74/nginx-lua:latest
kubectl rollout status deployment/php-fpm
kubectl rollout status deployment/nginx
Буду рад, если это будет кому-нибудь интересно и вдвойне рад, если это кому-нибудь поможет. Спасибо за ваше внимание. Еще раз прикладываю ссылку на репозиторий один и два.
shandy
А не сложно для обычных разработчиков? Тут конфиги уже готовые, а как же понимание?
Как мне было бы проще взять Rancher 2.0 (установка в 1 клик докер команда) и зная AWS ключи можно поднять кластер в один клик без всяких kops и terraform...
Valeriy_tw3eX Автор
Да, я и такой вариант использовал. Просто в данном случае мы пласт инфраструктуры — сеть/volumes/elb делегируем на сторону AWS — и получаем минус головную боль об их работе.
Тк у меня довольно много возникало проблем с тем, что сеть между нодами просто разваливалась под небольшими нагрузками.
Akuma
Поддержу. Вообще не нравятся многие статьи про k8s именно таким подходом «как развернуть 10 сред и 50 серверов одной командой — а никак, вам придется установить 100500 утилит, создать 100501 конфиг, нужен макбук, станция на Ubuntu и калькулятор на Win10, еще нам понадобится яблоко, мышеловка и мясорубка Bosh».
Valeriy_tw3eX Автор
Верно. Но к сожалению эти утилиты необходимы просто потому, что без них сложней жить. И они не настолько сложны, как кажется.
Но вы правы — возможно мне необходимо описать как сделать это в ранчере тоже
Gorniv
напишите как в rancher2 cert-manager поднять правильно)
shandy
Он же в каталоге вроде есть. Но там есть нюансы в зависимости от ингресс контроллера…
З.Ы. Для меня неожиданность, что ранчер довольно популярное и/или узнаваемое решение в русскоязычном сегменте (да и вообще на этой стороне света)) Наверное стоит организовать какое-то комьюнити...
Valeriy_tw3eX Автор
Он хороший. Вроде бы самый простой способ оркестрации на своих серверах. Не нужно много знать) Только проблема была в том, что его поднимаешь — накатываешь немного вещей из каталога, используешь — и потом она начинает разваливаться 1-2 раза в неделю. Когда между нодами много траффика идет (в локальной сети DigitalOcean) — умирают ipsec сервисы и днс резолв.
shandy
Если речь про ранчер 1.6 — то да, тоже замечал что ipsec & dns помирают время от времени. Но в 2.0 уже ничего этого нет, пока не замечал таких странностей.
Gorniv
В каталоге то он есть, но я его не понимаю, ну то есть, если через yaml что-то поднимать — то в принципе я видел инструкции, а вот если через инфраструктуру rancher(2) — то не понимаю.
На 1.6 у меня все настроенно и работает, а вот kubernetes немного сложнее, сейчас планирую на 2-ой переходить, пока останавливают сертификаты.
У меня 3 сервера(хотя в целом и одного хватило)
контейнеры:
+ балансер и letsencrypt
Valeriy_tw3eX Автор
Чуть позже по ранчеру напишу что-нибудь. Но мне больше нравится route53 + letsencrypt. Из каталога все ок работает.
Там только надо пробросить запросы на acme внутрь контейнера (просто через haproxy дефолтный его, если cattle).
Gorniv
это 1.6 или 2.0? у меня dns в DO(Digital Ocean) — то же не совсем понимаю как теперь, так как cert-manager судя по доке dns для DO не поддерживает
Valeriy_tw3eX Автор
1.6
Я обычно route53 от AWS для этого использую. Тк там пару долларов.
Еще одно время использовал cloudflare — но было грустно. Не всегда челендж проходил корректно
Gorniv
на 1.6 и у меня все хорошо работает, ты на 2.0 настрой))