Развертывание приложения Go на AWS с помощью terraform


Обзор

В этой статье мы развернем простой сервис Golang на эластичном облачном вычислении (EC2) в AWS. Поскольку мы развертываем приложение в виде образа docker, мы можем аналогичным образом запустить практически любое приложение внутри контейнера docker.

Мы не будем развертывать высокодоступный и надежный проект. Цель статьи — познакомиться с terraform как инструментом инфраструктуры как кода (IaaC) и с тем, как развернуть простое приложение на AWS.

Весь код можно найти в репозитории GitHub. Мы будем использовать его для изучения кода terraform в других разделах статьи.

Простой сервер

Мы не будем глубоко погружаться в приложение Для создания мы будем использовать простейший сервер gin API.

router.GET("/", server.CounterHandler)
router.GET("/health", server.HealthHandler)

// HealthHandler returns a success message with code 200 when the server is running
func (s *Server) HealthHandler(ctx *gin.Context) {
    ctx.JSON(200, gin.H{"success": true})
}

// CounterHandler calculates number of requests and return a json with counter number
func (s *Server) CounterHandler(ctx *gin.Context) {
    counter := atomic.AddInt64(&s.counter, 1)

    ctx.JSON(200, gin.H{"counter": counter})
}The implementation of handlers
Вход в полноэкранный режим Выход из полноэкранного режима

Настройка окружения

Прежде чем приступить к созданию инфраструктуры, нам необходимо настроить все инструменты, которые мы будем использовать: AWS аккаунт, terraform и docker.

Создайте учетную запись AWS

Если у вас нет учетной записи на AWS, вам необходимо сначала создать ее. Инструкцию можно найти в официальном руководстве AWS.

Создайте роль для terraform с правами доступа

Использовать корневого пользователя AWS для чего-либо, кроме критически важных задач управления учетной записью, крайне нежелательно, поэтому мы создадим нового пользователя внутри учетной записи AWS, которую terraform будет использовать для создания инфраструктуры. Это можно сделать с помощью службы управления идентификацией и доступом (IAM).

После этого мы можем создать нового пользователя.

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

Добавьте пользователю группу admin с помощью поиска, чтобы terraform мог предоставлять все ресурсы.

Примечание: Одним из лучших методов обеспечения безопасности является принцип наименьших привилегий, когда каждому сервису и пользователю должны быть предоставлены только необходимые разрешения. В данном примере для простоты мы предоставляем пользователю полный контроль администратора.

Для шага 3, где мы устанавливаем теги, мы можем установить project: go-example или что-то подобное. Теги в AWS в основном используются для фильтрации и группировки ресурсов и сбора аналитики, например, затрат.

Шаг 4 — это валидация, где мы можем проверить все данные.

На шаге 5 нам нужно сохранить ID ключа доступа и секретный ключ доступа. Они предоставят нам программный доступ к учетной записи AWS.

Примечание: Секретный ключ доступа мы увидим только один раз, поэтому его следует хранить в надежном и безопасном месте.

Создайте ~/.aws/config и ~/.aws/credentials для использования профилей

Чтобы использовать профиль AWS, нам необходимо его настроить. Конфигурация AWS хранится в каталоге ~/.aws и состоит из двух важных файлов:

~/.aws/config — хранит конфигурацию AWS. Мы будем хранить профиль с именем go-example в файле config, и он должен выглядеть следующим образом:

[profile go-example]
region=eu-central-1
output=json
Войти в полноэкранный режим Выйти из полноэкранного режима

~/.aws/credentials — хранит учетные данные для профилей. Мы должны создать этот файл и скопировать туда ключи доступа и секретные ключи.

[go-example]
aws_access_key_id=<ACCESS_KEY>
aws_secret_access_key=<SECRET_KEY>
Вход в полноэкранный режим Выход из полноэкранного режима

Установите terraform

Для развертывания мы будем использовать Terraform. Для установки terraform следуйте официальной инструкции.

Совет: Для более реального использования проекта удобнее использовать https://github.com/tfutils/tfenv, так как он может управлять несколькими версиями Terraform для многих проектов.

Docker

Для создания образов docker нам необходимо установить docker, используя официальную инструкцию.

Настройка инфраструктуры

Существует несколько возможностей развернуть приложение на AWS: с помощью AWS Elastic Beanstalk, AWS Elastic container service (ECS) или непосредственно на экземпляре EC2. Мы будем использовать последний подход и развернем приложение на системе, а для простоты обернем его докером.

Мы собираемся

  1. Настроим виртуальное частное облако (VPC), интернет-шлюз (IG), список контроля доступа к сети (NACL) и группы безопасности для защиты сетевого доступа к нашему экземпляру.
  2. Создадим образ docker для нашего приложения и загрузим его в elastic container registry (ECR), который является реестром AWS для образов (аналогично хабу Docker).
  3. Создайте экземпляр EC2 и запустите в нем наше приложение как docker.

В этом случае мы можем управлять версиями приложения. С ECR проще использовать ECS, как систему управления контейнерами. Но мы собираемся настроить экземпляр EC2 как виртуальную машину и запустить на нем приложение как контейнер docker.

Примечание: ECR не входит в бесплатный уровень, поэтому вы будете платить несколько центов в месяц за использование.

Настройка сети

Мы создадим новый VPC с интернет-шлюзом и одной подсетью, базовой группой безопасности и NACL. Сеть будет простой, поскольку мы пытаемся избежать сложностей. Группа безопасности разрешит подключение только к порту приложения (80), а NACL разрешит весь трафик для подсети.

Примечание: Для повышения безопасности и управления доступом сетевой трафик лучше хранить внутри AWS. Это можно сделать с помощью конечной точки VPC — частного соединения, которое позволяет трафику между сервисами AWS не покидать сеть AWS. Мы не будем создавать конечную точку VPC, чтобы разрешить внутреннее подключение к ECR, поэтому мы будем загружать образ докера через интернет. Для производственной системы лучше сохранить соединение частным для повышения безопасности.

Все настройки сети можно найти в файле [network.tf](http://network.tf) в репозитории Github. Во-первых, нам нужен новый ресурс VPC с определенным блоком CIDR.

resource aws_vpc main {
  cidr_block = "10.0.0.0/20"
}
Вход в полноэкранный режим Выход из полноэкранного режима

VPC — это логически изолированная виртуальная сеть. Здесь мы можем создавать различные подсети, которые могут быть публичными или частными, и управлять трафиком так, как нам нужно. VPC не имеет доступа к Интернету. Поскольку мы ожидаем входящий трафик из Интернета, нам нужен интернет-шлюз (IG).

resource aws_internet_gateway gateway {
  vpc_id = aws_vpc.main.id
}
Вход в полноэкранный режим Выход из полноэкранного режима

Изначально в VPC есть только таблица маршрутов по умолчанию. Чтобы обеспечить доступ к интернету через IG, нам нужно добавить таблицу маршрутизации и связать ее с VPC. В этом случае весь трафик, который не является локальным (в нашем случае, не 10.0.0.0/20), мы должны направлять на IG.

resource aws_route_table public {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.gateway.id
  }
}

resource aws_main_route_table_association main {
  route_table_id = aws_route_table.public.id
  vpc_id         = aws_vpc.main.id
}
Вход в полноэкранный режим Выход из полноэкранного режима

В примере у нас будет только один экземпляр EC2, поэтому мы создадим одну подсеть в одной зоне доступности (AZ) — для AWS это похоже на один большой центр обработки данных в определенном регионе. Для обеспечения сетевой маршрутизации в подсети нам также необходимо создать ассоциацию с маршрутизатором main, который мы создали ранее.

resource aws_subnet main {
  vpc_id = aws_vpc.main.id
  cidr_block = "10.0.0.0/24"
  map_public_ip_on_launch = true
  availability_zone = "us-east-1a"
}

resource aws_route_table_association main {
  subnet_id      = aws_subnet.main.id
  route_table_id = aws_route_table.public.id
}
Вход в полноэкранный режим Выход из полноэкранного режима

По умолчанию весь трафик внутри созданной подсети будет запрещен (terraform обновляет VPC по умолчанию во время создания), и нам нужно обновить NACL по умолчанию, чтобы разрешить входящий и исходящий трафик и связать его с подсетью.

resource aws_default_network_acl main {
  default_network_acl_id = aws_vpc.main.default_network_acl_id

  ingress {
    protocol   = -1
    rule_no    = 100
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 0
    to_port    = 0
  }

  egress {
    protocol   = -1
    rule_no    = 100
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 0
    to_port    = 0
  }
}

resource aws_network_acl_association main {
  network_acl_id = aws_default_network_acl.main.id
  subnet_id = aws_subnet.main.id
}
Вход в полноэкранный режим Выход из полноэкранного режима

Группа безопасности разрешает государственное подключение к сервисам AWS, в нашем случае к будущему экземпляру. Правило ingress (для входящих соединений) должно разрешать HTTP (TCP) трафик на порт 80 и egress трафик (так как мы собираемся тянуть образ докера).

resource aws_security_group public {
  name = "public-sg"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port = local.application_port
    to_port   = local.application_port
    protocol  = "tcp" // allows only tcp trafic for the provided port

    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port = 0
    to_port   = 0
    protocol  = "-1" // allows all outgoing traffic from the instance

    cidr_blocks = ["0.0.0.0/0"]
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Примечание: Группы безопасности могут разрешить только некоторый трафик, и они являются государственными (например, если запрос был сделан, то ответ пройдет без дополнительных правил). NACL может запретить трафик, и они не зависят от состояния (например, если запрос был сделан, нам нужны отдельные правила для входящего и исходящего трафика).

Создание образа docker

Мы собираемся обернуть наше приложение в docker и создать образ docker. Для хранения этого образа нам понадобится хранилище образов. В AWS для этого есть служба ECR, которую мы создадим и будем использовать для приложения. Нам нужно разрешить действия ECR, описанные в aws_iam_policy_document, для управления образами docker.

resource aws_ecr_repository go_server {
  name = local.service_name
  image_tag_mutability = "IMMUTABLE"

  image_scanning_configuration {
    scan_on_push = true
  }
}

data aws_iam_policy_document ecr_policy {
  statement {
    effect = "Allow"
    principals {
      identifiers = ["*"]
      type        = "*"
    }

    actions = [
      "ecr:GetDownloadUrlForLayer",
      "ecr:BatchGetImage",
      "ecr:BatchCheckLayerAvailability",
      "ecr:PutImage",
      "ecr:InitiateLayerUpload",
      "ecr:UploadLayerPart",
      "ecr:CompleteLayerUpload",
      "ecr:DescribeRepositories",
      "ecr:GetRepositoryPolicy",
      "ecr:ListImages",
      "ecr:DeleteRepository",
      "ecr:BatchDeleteImage",
      "ecr:SetRepositoryPolicy",
      "ecr:DeleteRepositoryPolicy"
    ]
  }
}

resource aws_ecr_repository_policy go_server {
  repository = aws_ecr_repository.go_server.name
  policy = data.aws_iam_policy_document.ecr_policy.json
}
Вход в полноэкранный режим Выход из полноэкранного режима

Для управления докерами мы будем использовать модуль terraform kreuzwerker/docker. Он предоставляет нам возможность создавать и загружать докер-образы в докер-репозиторий.

Мы можем создать провайдера terraform, который будет управлять аутентификацией в ECR

data aws_ecr_authorization_token go_server {
  registry_id = aws_ecr_repository.go_server.registry_id
}

provider docker {
  registry_auth {
    address  = split("/", local.ecr_url)[0]
    username = data.aws_ecr_authorization_token.go_server.user_name
    password = data.aws_ecr_authorization_token.go_server.password
  }
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Для создания образа из файла docker нам понадобится отдельный ресурс docker_registry_image, который будет создавать образ и загружать его в ECR.

resource docker_registry_image go_example {
  name = "${local.ecr_url}:v1"

  build {
    context    = "${path.module}/../app/."
    dockerfile = "app/Dockerfile"
    no_cache   = true
  }

  depends_on = [aws_ecr_repository.go_server]
}
Вход в полноэкранный режим Выход из полноэкранного режима

Настройка приложения в EC2

Прежде чем создавать экземпляр и запускать в нем контейнер docker, нам нужно разрешить экземпляру EC2 получать образ из репозитория. AWS имеет службу управления идентификацией и доступом (IAM) для контроля доступа между службами AWS. Чтобы позволить EC2 извлекать образы из ECR, нам нужно создать отдельную роль IAM — идентификатор, обладающий определенными правами.

data aws_iam_policy_document assume_role_ec2_policy_document {
  statement {
    effect = "Allow"
    principals {
      identifiers = ["ec2.amazonaws.com"]
      type        = "Service"
    }

    actions = [
      "sts:AssumeRole"
    ]
  }
}

resource aws_iam_role role {
  name               = "application-role"
  assume_role_policy = data.aws_iam_policy_document.assume_role_ec2_policy_document.json
}
Вход в полноэкранный режим Выход из полноэкранного режима

Чтобы предоставить разрешение на извлечение образа докера, нам нужно создать политику IAM и прикрепить ее к созданной нами роли.

// policy allows only ecr:ecr:BatchGetImage action for all resources that have such permission.
data aws_iam_policy_document ecr_access_policy_document {
  statement {
    effect = "Allow"
    actions = [
      "ecr:BatchGetImage",
    ]

    resources = ["*"]
  }
}

resource aws_iam_policy ecr_access_policy {
  name = "ecr-access-policy"
  policy = data.aws_iam_policy_document.ecr_access_policy_document.json
}

resource aws_iam_policy_attachment ecr_access {
  name       = "ecr-access"
  roles      = [aws_iam_role.role.name]
  policy_arn = aws_iam_policy.ecr_access_policy.arn
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Для экземпляра EC2 нам нужно создать профиль с этой ролью.

resource aws_iam_instance_profile profile {
  name = "application-ec2-profile"
  role = aws_iam_role.role.name
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Для создания экземпляра EC2 нам нужно знать идентификатор образа машины amazon (AMI), который представляет собой виртуальную машину, на которой будет запущено приложение. Для этой цели мы будем использовать образ ubuntu-20.04.

data aws_ami ubuntu {
  most_recent = true

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  owners = ["099720109477"] # Canonical
}
Вход в полноэкранный режим Выйти из полноэкранного режима

После этого мы готовы к созданию экземпляра EC2. Экземпляр определяет ресурсы для виртуальной машины. Тип t2.micro предоставляет 1 vCPU и 1 ГБ оперативной памяти, доступные на бесплатном уровне и достаточные для простого приложения.
Начальный скрипт предоставляется в user_data — скрипт, который запускается во время инициализации экземпляра EC2, где мы устанавливаем docker, извлекаем образ приложения и запускаем его.

resource aws_instance application {
  ami                         = data.aws_ami.ubuntu.id
  instance_type               = "t2.micro"
  vpc_security_group_ids      = [aws_security_group.public.id]
  subnet_id                   = aws_subnet.main.id
  associate_public_ip_address = true
  iam_instance_profile        = aws_iam_instance_profile.profile.name

  user_data = <<EOF
#!/bin/bash
sudo apt-get update
sudo apt-get install -y pt-transport-https ca-certificates curl gnupg lsb-release software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu `lsb_release -cs` test"
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
sudo gpasswd -a $USER docker
newgrp docker

echo ${data.aws_ecr_authorization_token.go_server.password} | docker login --username=${data.aws_ecr_authorization_token.go_server.user_name} --password-stdin ${aws_ecr_repository.go_server.repository_url}

docker run -p ${local.application_port}:${local.application_internal_port} -d --restart always ${docker_registry_image.go_example.name}
EOF

  depends_on = [aws_internet_gateway.gateway]
}
Вход в полноэкранный режим Выход из полноэкранного режима

Чтобы получить IP-адрес созданного сервера, мы можем создать выход terraform.

output application_ip {
  value       = aws_instance.application.public_ip
  description = "Application public IP"
}
Войти в полноэкранный режим Выход из полноэкранного режима

Предоставление ресурсов

Перед созданием ресурсов, которые мы описали в файле terraform, нам необходимо сначала инициализировать terraform, чтобы установить необходимые модули. Из корневого каталога репозитория запустите:

terraform -chdir=iaac init
Войти в полноэкранный режим Выйти из полноэкранного режима

Для создания ресурсов нам нужно запустить apply и terraform будет управлять всеми ресурсами.

terraform -chdir=iaac apply
Войти в полноэкранный режим Выход из полноэкранного режима

Перед применением terraform предоставляет план изменений (diff или ресурсы, которые он собирается сделать). Мы можем просмотреть план и увидеть все изменения, после чего набрать yes для их применения. В выводе мы увидим информацию о созданных ресурсах и вывод с IP приложения, например:

Apply complete! Resources: 17 added, 0 changed, 0 destroyed.

Outputs:

application_ip = "44.204.255.51"
Вход в полноэкранный режим Выход из полноэкранного режима

Через несколько минут приложение будет доступно по адресу application_ip.

Для уничтожения всех ресурсов необходимо выполнить команду destroy:

terraform -chdir=iaac destroy
Войти в полноэкранный режим Выйти из полноэкранного режима

Примечание: Terraform создаст все ресурсы и сохранит состояние terraform.tfstate и terraform.tfstate.backup. Для производственных систем файлы состояния обычно хранятся удаленно в долговечном месте, например, в S3. В этом случае несколько разработчиков могут обновлять окружение и поддерживать состояние terraform в неизменном виде.

Послесловие

Я написал эту статью, чтобы поделиться опытом управления инфраструктурой через IaaC и лучшими практиками создания и развертывания приложений. Чтобы помочь с выбором тем, о которых было бы ценнее написать, поделитесь тем, что было бы интересным улучшением текущего развертывания приложения:

  • Добавление постоянных уровней в приложение, таких как база данных и хранилище объектов.
  • Масштабирование сервера для обеспечения высокой доступности.
  • Добавление развертывания фронтенда.
  • Больше основных деталей об использованных технологиях.

Все советы по улучшению контента ценны и полезны 😇

Оцените статью
devanswers.ru
Добавить комментарий