DevOps: автоматизация инфраструктуры на примере Terraform, docker, bash, prometheus exporters, Gitlab и WireGuard

—

от автора

Всем привет.

Есть такие люди, которые работают с облачной инфраструктурой и не используют автоматизацию, потому что это долго, нужно вникать, а им надо фичи пилить. Накликали что-то там в UI, подключились по ssh, поставили всякого с помощью apt и т.д. и конфигурационные файлы ещё вручную поменяли. Документации конечно же написать времени не хватило или в ней много разных хитрых шагов и повторить настройку этой инфраструктуры в точности уже нельзя или очень сложно, а сервисы крутятся в проде. А потом человек забыл что и как делал в точности или вообще уволился.

Хочу показать на небольшом примере, что автоматизировать инфраструктуру, например в AWS, может быть достаточно просто и приятно, а получившийся результат достаточно прозрачен и сам по себе является документацией, т.к. это инфраструктура как код. Если конечно есть знания Terraform или желание его немного изучить.

К слову, крайне рекомендую для автоматизации много чего, но в особенности облачных провайдеров вроде AWS / GCP / Azure и т.д. использовать именно Terraform, т.к. это достаточно зрелый инструмент, у него большое сообщество и кроме всего прочего он поддерживает автоматизацию далеко не только каких-то облачных провайдеров, но и практически всего у чего есть API. К тому же инструмент open source и при желании можно реализовать что угодно самостоятельно. Для таких облаков, как AWS не рекомендую пытаться реализовывать автоматизации с помощью чистого питона и запросов к AWS API с помощью cli или Cloudformation.

Также у Terraform есть удобная возможность организовывать код в блоки называемые модулями и передавая в них только параметры легко создавать необходимое с другими настройками.

Для тех, кто совсем не знаком с Terraform упомяну, что если в одну папку положить несколько файлов с расширением .tf и запустить Terraform в этой папке, то Terraform прочитает и использует код из всех файлов, а не только одного. Это в том числе позволяет разбивать один большой main.tf файл с кучей ресурсов на какие-то удобные вам логические блоки.

Итак, например, встала задача развернуть vpn серверы WireGuard на базе Ubuntu 20.04 в нескольких регионах + немного мониторинга. Поддержка WireGuard сейчас есть в ядре linux, но дополнительные инструменты, которые можно поставить отдельно облегчают жизнь, поэтому поставим и их.

Весь код модуля выложен здесь.

Также этот модуль опубликован в реестре модулей здесь.

Не буду разжёвывать код построчно, попробую описать архитектуру в целом, а код попробуйте разобрать самостоятельно. Если возникнут неразрешимые проблемы или вопросы попробую ответить.

Итак, создаются необходимые для работы iam политики, роль и т.п.

Используем elastic ip, отдельный для сервера в каждом регионе, которые нужно прописать в dns для того, чтобы пользователь мог использовать единое имя для подключения к vpn серверу. Планировал использовать geo dns route53, чтобы при местоположении пользователя в оперделённом регионе ему бы отдавался ip vpn сервера в его регионе, но т.к. на этом проекте route53 пока не используется, то создание записей в нём пока не автоматизировал.

Создаются security groups с правилами, которые позволяют подключиться к vpn серверу извне по udp (Wireguard работает только по udp) + ssh + несколько портов для prometheus exporter’ов.

Создаётся собственно сервер / ec2 машина, но не просто отдельно стоящая, а входящая в auto scaling group, в данном примере в единственном варианте. Это сделано для того, чтобы если с сервером что-то не так, то Амазон автоматом пересоздаст его. Self healing.

Позже немного допилив конфигурацию и добавив в неё load balancer можно добиться того, для чего auto scaling groups отлично подходят: при повышенной нагрузке на какой-то из ресурсов сервера, например на cpu, можно реализовать автоматическое создание дополнительных vpn серверов, а соответственно при падении нагрузки уменьшать их количество.

Этот модуль можно использовать просто с Terraform, но лучше использовать Terragrunt, который позволяет делать некоторые удобные вещи и местами реализовывать концепцию Keep your Terraform code DRY, например параметризуя некоторые вещи в backend блоке, чего сам Terraform пока не умеет. Terraform хранит состояние инфраструктуры в специальном файле и принято хранить его не локально, а, чаще всего, в S3 бакете. Также, если вы работаете с этим кодом не в одиночку, то принято хранить локи в Dynamodb, чтобы случайно не применить какое-то изменение инфраструктуры несогласованно и не поломать всё.

Именно пример такого использования я привожу в примере здесь.

С помощью файла terragrunt.hcl в корне репозитория (https://github.com/vainkop/terraform-aws-wireguard/blob/master/example/terragrunt.hcl) я могу, например, задать место для хранения state для всех поддиректорий, а потом ссылаться на этот файл в других terragrunt.hcl с помощью функции find_in_parent_folders() https://github.com/vainkop/terraform-aws-wireguard/blob/master/example/us-east-1/terragrunt.hcl#L2

При этом key, т.е. файл, где будет храниться состояние инфраструктуры в конкретном регионе будет храниться отдельно, что достигается с помощью функции path_relative_to_include() https://github.com/vainkop/terraform-aws-wireguard/blob/master/example/terragrunt.hcl#L11

Также я реализовал хранение/чтение переменных в yaml формате, что мне кажется более удобочитаемым с помощью функции yamldecode(file(...)) https://github.com/vainkop/terraform-aws-wireguard/blob/master/example/eu-central-1/terragrunt.hcl#L9

Вот так выглядит пример передаваемых в модуль уникальных параметров (конечно YOUR_... нужно заменить на реальные значения): https://github.com/vainkop/terraform-aws-wireguard/blob/master/example/us-east-1/values.yaml

Иногда удобно реализовать использование имени папки в качестве параметра, например в приведённом примере это мог бы быть параметр region и реализуется это с помощью, например, функций basename(get_terragrunt_dir()) и задавать его в values.yaml не пришлось бы, но по определённым причинам решил этого не делать.

В итоге в вашем приватном репозитории код из которого применяете либо вы, либо какой-то ci cd runner может лежать только содержимое похожее на мою папку example, т.е. только terragrunt.hcl и yaml файлы с параметрами, а модуль можно использовать как публичный и хорошо поддерживаемый, так и написать свой. Это позволяет отдать «пользователям» только задание параметров в yaml и в принципе ничего не знать про Terraform код.

Отдельно советую: по-возможности, не нужно изобретать велосипед и на каждое действие пытаться писать свой Terraform код или тем более модуль. Для большинства вещей модули уже написаны и в их разработке и поддержке участвует множество грамотных людей, поэтому берите и пользуйтесь.

Для того, чтобы изменения в коде open source модулей, как впрочем и в частных, не повлияли на работу вашей автоматизации принято фиксировать версии используемых модулей, например в моём коде это сделано с помощью source = "github.com/vainkop/terraform-aws-wireguard?ref=v1.2.0" здесь https://github.com/vainkop/terraform-aws-wireguard/blob/master/example/eu-central-1/terragrunt.hcl#L6

Ну а если вы всё же не хотите зависеть от open source и чувствуете в себе силы поддерживать и развивать свой модуль самостоятельно, всегда можно форкнуть общественный и сделать всё, что хочется.

Например я реализовал cloud-init скрипт, который осуществляет предварительную установку и настройку софта на свежеразвёрнутый сервер и делает это каждый раз, когда сервер пересоздаётся в auto scaling group, что очень удобно: https://github.com/vainkop/terraform-aws-wireguard/blob/master/templates/user-data.txt

Ближе к концу скрипта устанавливается 2 prometheus exporter’а, которые позволяют как мониторить метрики самой ec2 машины, так и базовые метрики самого WireGuard, на основании которых можно построить удобные Dashboards и соответственно определённые alerts и т.п.

В частности я реализовал это для того, чтобы видеть к какому из vpn серверов подключён клиент, чтобы, например, была возможность подключиться к нему именно из его региона, т.к. связности между этими vpn серверами нет. Т.к. клиентские публичные ключи зашиты в каждый из серверов и серверные ключи одинаковые, то клиент будет автоматически переключаться между ними путешествуя между регионами на основании geo ip route53.

Также привожу пример кода из .gitlab-ci.yml и Dockerfile где можно увидеть какие команды используются для применения всего этого хозяйства с помощью Gitlab runner’а и какой docker контейнер можно использовать для этого runner’а.

$ cat .gitlab-ci.yml  stages:   - build   - plan   - apply   - destroy  variables:   GIT_DEPTH: 1  .aws_configure: &aws_configure   before_script:     - aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID     - aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY     - aws configure set default.region $AWS_DEFAULT_REGION   build-terraform:   image: docker:19.03.15   services:     - docker:19.03.15-dind   stage: build   variables:     DOCKER_TLS_CERTDIR: ""     DOCKER_HOST: tcp://docker:2375     DOCKER_DRIVER: overlay2     TERRAFORM_VERSION: "0.13.6"     TERRAGRUNT_VERSION: "v0.28.9"   before_script:     - printenv     - docker info     - echo $CI_REGISTRY_PASSWORD | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin   script:     - cd docker     - docker build --build-arg TERRAFORM_VERSION=$TERRAFORM_VERSION --build-arg TERRAGRUNT_VERSION=$TERRAGRUNT_VERSION -t $CI_REGISTRY_IMAGE:$TERRAFORM_VERSION .     - docker push $CI_REGISTRY_IMAGE:$TERRAFORM_VERSION   rules:     - changes:         - docker/*   plan-us-east-1:   image:     name: registry.gitlab.com/vainkop/terraform:0.13.6     entrypoint: [""]   stage: plan   <<: *aws_configure   script:     - cd wireguard/us-east-1     - terragrunt run-all plan --terragrunt-non-interactive -out $CI_PROJECT_DIR/wireguard/us-east-1/tfplan-$CI_COMMIT_SHA   artifacts:     paths:     - $CI_PROJECT_DIR/wireguard/us-east-1/tfplan-$CI_COMMIT_SHA     expire_in: 1 month   rules:     - changes:         - wireguard/us-east-1/*       allow_failure: true  plan-eu-central-1:   image:     name: registry.gitlab.com/vainkop/terraform:0.13.6     entrypoint: [""]   stage: plan   <<: *aws_configure   script:     - cd wireguard/eu-central-1     - terragrunt run-all plan --terragrunt-non-interactive -out $CI_PROJECT_DIR/wireguard/eu-central-1/tfplan-$CI_COMMIT_SHA   artifacts:     paths:     - $CI_PROJECT_DIR/wireguard/eu-central-1/tfplan-$CI_COMMIT_SHA     expire_in: 1 month   rules:     - changes:         - wireguard/eu-central-1/*       allow_failure: true  apply-us-east-1:   image:     name: registry.gitlab.com/vainkop/terraform:0.13.6     entrypoint: [""]   stage: apply   <<: *aws_configure   script:     - cd wireguard/us-east-1     - terragrunt run-all apply --terragrunt-non-interactive -auto-approve $CI_PROJECT_DIR/wireguard/us-east-1/tfplan-$CI_COMMIT_SHA   rules:     - changes:         - wireguard/us-east-1/*       when: manual       allow_failure: true  apply-eu-central-1:   image:     name: registry.gitlab.com/vainkop/terraform:0.13.6     entrypoint: [""]   stage: apply   <<: *aws_configure   script:     - cd wireguard/eu-central-1     - terragrunt run-all apply --terragrunt-non-interactive -auto-approve $CI_PROJECT_DIR/wireguard/eu-central-1/tfplan-$CI_COMMIT_SHA   rules:     - changes:         - wireguard/eu-central-1/*       when: manual       allow_failure: true  destroy-us-east-1:   image:     name: registry.gitlab.com/vainkop/terraform:0.13.6     entrypoint: [""]   stage: destroy   <<: *aws_configure   script:     - cd wireguard/us-east-1     - terragrunt run-all destroy --terragrunt-non-interactive -auto-approve   rules:     - changes:         - wireguard/us-east-1/*       when: manual       allow_failure: true  destroy-eu-central-1:   image:     name: registry.gitlab.com/vainkop/terraform:0.13.6     entrypoint: [""]   stage: destroy   <<: *aws_configure   script:     - cd wireguard/eu-central-1     - terragrunt run-all destroy --terragrunt-non-interactive -auto-approve   rules:     - changes:         - wireguard/eu-central-1/*       when: manual       allow_failure: true
$ cat docker/Dockerfile  FROM ubuntu:20.04  USER root  ARG DEBIAN_FRONTEND=noninteractive  ARG TERRAFORM_VERSION ENV TERRAFORM_VERSION=$TERRAFORM_VERSION  ARG TERRAGRUNT_VERSION ENV TERRAGRUNT_VERSION=$TERRAGRUNT_VERSION  RUN set -x && \     apt-get update && \     apt-get install -y \     apt-transport-https \     ca-certificates \     build-essential \     software-properties-common \     unzip \     net-tools \     wget \     curl \     python3 \     python3-dev \     python3-pip \     jq \     gettext-base \     git && \     rm -rf /var/lib/apt/lists/*  RUN set -x && \     apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CC86BB64 && \     add-apt-repository ppa:rmescandon/yq && \     apt update && \     apt install -y yq && \     rm -rf /var/lib/apt/lists/*  RUN set -x && \     pip3 install -U --no-cache-dir setuptools shyaml  RUN set -x && \     ln -sf /usr/bin/python3 /usr/bin/python && ln -sf /usr/bin/pip3 /usr/bin/pip  RUN set -x && \     curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \     unzip awscliv2.zip && \     rm awscliv2.zip && \     ./aws/install  RUN set -x && \     cd /tmp && \     curl -O https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip && \     unzip terraform_${TERRAFORM_VERSION}_linux_amd64.zip -d /usr/local/bin && \     chmod +x /usr/local/bin/terraform && \     rm /tmp/terraform_${TERRAFORM_VERSION}_linux_amd64.zip  RUN set -x && \     wget "https://github.com/gruntwork-io/terragrunt/releases/download/${TERRAGRUNT_VERSION}/terragrunt_linux_amd64" && \     mv terragrunt_linux_amd64 /usr/local/bin/terragrunt && \     chmod +x /usr/local/bin/terragrunt  RUN set -x && \     curl --version && \     envsubst --version && \     python --version && \     pip --version && \     shyaml --version && \     jq -V && \     yq -V && \     aws --version && \     terraform --version && \     terragrunt --version  ENTRYPOINT ["/bin/bash", "-c"]

За код не ругайте, написал за несколько часов и решил поделиться.

Если есть конкретные замечания/предложения, то готов их выслушать либо в комментариях, либо в личке, например, в телеграм: @vainkop

Прошу также учитывать, что это моя первая публикация на Хабре. Приглашение приму с удовольствием 🙂

#devops #infrastructureascode #iac #terraform #terragrunt #docker #bash #prometheus #gitlab #wireguard

ссылка на оригинал статьи https://habr.com/ru/post/547298/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *