Доброго времени суток. Очередная заметка из моего опыта. В этот раз поверхностно о базовой инфраструктуре, которую использую, если надо что-то выгрузить, а рядом нет 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

Буду рад, если это будет кому-нибудь интересно и вдвойне рад, если это кому-нибудь поможет. Спасибо за ваше внимание. Еще раз прикладываю ссылку на репозиторий один и два.

Комментарии (13)


  1. shandy
    17.09.2018 09:51

    А не сложно для обычных разработчиков? Тут конфиги уже готовые, а как же понимание?
    Как мне было бы проще взять Rancher 2.0 (установка в 1 клик докер команда) и зная AWS ключи можно поднять кластер в один клик без всяких kops и terraform...


    1. Valeriy_tw3eX Автор
      17.09.2018 11:35

      Да, я и такой вариант использовал. Просто в данном случае мы пласт инфраструктуры — сеть/volumes/elb делегируем на сторону AWS — и получаем минус головную боль об их работе.

      Тк у меня довольно много возникало проблем с тем, что сеть между нодами просто разваливалась под небольшими нагрузками.


    1. Akuma
      17.09.2018 11:35

      Поддержу. Вообще не нравятся многие статьи про k8s именно таким подходом «как развернуть 10 сред и 50 серверов одной командой — а никак, вам придется установить 100500 утилит, создать 100501 конфиг, нужен макбук, станция на Ubuntu и калькулятор на Win10, еще нам понадобится яблоко, мышеловка и мясорубка Bosh».


      1. Valeriy_tw3eX Автор
        17.09.2018 11:42

        Верно. Но к сожалению эти утилиты необходимы просто потому, что без них сложней жить. И они не настолько сложны, как кажется.

        Но вы правы — возможно мне необходимо описать как сделать это в ранчере тоже


        1. Gorniv
          17.09.2018 11:55

          напишите как в rancher2 cert-manager поднять правильно)


          1. shandy
            17.09.2018 12:20

            Он же в каталоге вроде есть. Но там есть нюансы в зависимости от ингресс контроллера…
            З.Ы. Для меня неожиданность, что ранчер довольно популярное и/или узнаваемое решение в русскоязычном сегменте (да и вообще на этой стороне света)) Наверное стоит организовать какое-то комьюнити...


            1. Valeriy_tw3eX Автор
              17.09.2018 12:57

              Он хороший. Вроде бы самый простой способ оркестрации на своих серверах. Не нужно много знать) Только проблема была в том, что его поднимаешь — накатываешь немного вещей из каталога, используешь — и потом она начинает разваливаться 1-2 раза в неделю. Когда между нодами много траффика идет (в локальной сети DigitalOcean) — умирают ipsec сервисы и днс резолв.


              1. shandy
                17.09.2018 14:36

                Если речь про ранчер 1.6 — то да, тоже замечал что ipsec & dns помирают время от времени. Но в 2.0 уже ничего этого нет, пока не замечал таких странностей.


            1. Gorniv
              17.09.2018 13:08

              В каталоге то он есть, но я его не понимаю, ну то есть, если через yaml что-то поднимать — то в принципе я видел инструкции, а вот если через инфраструктуру rancher(2) — то не понимаю.
              На 1.6 у меня все настроенно и работает, а вот kubernetes немного сложнее, сейчас планирую на 2-ой переходить, пока останавливают сертификаты.
              У меня 3 сервера(хотя в целом и одного хватило)
              контейнеры:
              image
              + балансер и letsencrypt


          1. Valeriy_tw3eX Автор
            17.09.2018 13:00

            Чуть позже по ранчеру напишу что-нибудь. Но мне больше нравится route53 + letsencrypt. Из каталога все ок работает.
            Там только надо пробросить запросы на acme внутрь контейнера (просто через haproxy дефолтный его, если cattle).

            image


            1. Gorniv
              17.09.2018 13:14

              это 1.6 или 2.0? у меня dns в DO(Digital Ocean) — то же не совсем понимаю как теперь, так как cert-manager судя по доке dns для DO не поддерживает


              1. Valeriy_tw3eX Автор
                18.09.2018 07:20

                1.6
                Я обычно route53 от AWS для этого использую. Тк там пару долларов.
                Еще одно время использовал cloudflare — но было грустно. Не всегда челендж проходил корректно


                1. Gorniv
                  18.09.2018 07:49

                  на 1.6 и у меня все хорошо работает, ты на 2.0 настрой))