Всем привет.
Есть такие люди, которые работают с облачной инфраструктурой и не используют автоматизацию, потому что это долго, нужно вникать, а им надо фичи пилить. Накликали что-то там в 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.ymlstages: - build - plan - apply - destroyvariables: 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_REGIONbuild-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: trueplan-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: trueapply-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: trueapply-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: truedestroy-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: truedestroy-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/DockerfileFROM ubuntu:20.04USER rootARG DEBIAN_FRONTEND=noninteractiveARG TERRAFORM_VERSIONENV TERRAFORM_VERSION=$TERRAFORM_VERSIONARG TERRAGRUNT_VERSIONENV TERRAGRUNT_VERSION=$TERRAGRUNT_VERSIONRUN 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 shyamlRUN set -x && \ ln -sf /usr/bin/python3 /usr/bin/python && ln -sf /usr/bin/pip3 /usr/bin/pipRUN set -x && \ curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \ unzip awscliv2.zip && \ rm awscliv2.zip && \ ./aws/installRUN 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.zipRUN 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/terragruntRUN set -x && \ curl --version && \ envsubst --version && \ python --version && \ pip --version && \ shyaml --version && \ jq -V && \ yq -V && \ aws --version && \ terraform --version && \ terragrunt --versionENTRYPOINT ["/bin/bash", "-c"]
За код не ругайте, написал за несколько часов и решил поделиться.
Если есть конкретные замечания/предложения, то готов их
выслушать либо в комментариях, либо в личке, например, в телеграм:
@vainkop
Прошу также учитывать, что это моя первая публикация на Хабре. Приглашение приму с удовольствием.