Пришло время продолжить предыдущую статью о NAT Instance. В прошлый раз мы остановились на том, что создали объект VPC, настроили подсети и конечно же само окружение. Ну а теперь пора переходить к настройке Auto Scaling group, разрешений IAM и конечно же Security groups, чтобы разрешить EC2-инстансам выходить наружу. Обязательно загляните в первую статью в раздел подготовки окружения. Оно понадобится нам сейчас.
Напомню, что всю конфигурацию мы храним в Terraform, вопросы настройки через веб-интерфейс AWS затрагиваться не будут.
Содержание
NAT Instance. Часть 2 — Auto Scaling group
Из первой статьи напомню, что структура каталога выглядит следующим образом. Как и ранее, по мере повествования буду выкладывать и объяснять содержимое каждого файла.
Auto Scaling group
Все необходимые объекты мы создаем в файле vpc_nat_instance.tf, вот его содержимое:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# NAT data "template_file" "user_data_nat_instance" { template = file("${path.root}/files/user_data_nat_instance.sh") vars = { region = var.region } } resource "aws_network_interface" "nat_instance_interface" { subnet_id = aws_subnet.subnet_public[0].id security_groups = [aws_security_group.nat_instance_sg.id] source_dest_check = false } resource "aws_eip" "nat_instance_eip" { vpc = true network_interface = aws_network_interface.nat_instance_interface.id } resource "aws_key_pair" "admin_test_key" { key_name = "admin-test-key" # сгенерируйте свою пару ключей и замените значение ниже public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABABABgQDIFUVB7dqxhhBIiM5Q+hg5mMUD2uL2o0byKQrncdA0jaf+iTvZnqZki5w577MLx7iZ22HWi14FYCjDhNm7zAtXsxRh0EGEtKsxkb8trqZ4wvFgfteHtvqUR5KWjxQmCS97aX3ii7cW7FWCB9paW6Gv0HX5czo0Ny/ilyEsXYI4y4t6LcXL3oSM99iPtRgbBIfWDotGqKjRA6xLloUXh5AOFqh+eyUCofcyH/LSAhRswPxH+SabTfSkOn9oSyr2kVtPANkvgLKfnYdPPkoGfaPgDTKMc3hJiJ9e1H6Y+wkNGItJBg/lRmpzCSVtHzTFHblDuYoYxkqMQ6UCvqUcd1/KT42n/PY8jFoP3/knnG/83d4LlS3RE0mU+bZifVmB2aLkRoGLZsge2RN5eyzk9WIV3oVYgFRccxAkD0zdO3F7zEsnNZHIAvutNYF0iPJ3fyYY1NmDfvnkxjYQwZTIKckeM/tEjdgzlre+iQnK1isfpn4iXML6VCqINwVbwBLRG1s= admin_test_key" } resource "aws_launch_template" "launch_template" { name = var.nat_instance.name instance_type = var.nat_instance.instance_type image_id = var.nat_instance.image_id instance_initiated_shutdown_behavior = "terminate" key_name = aws_key_pair.admin_test_key.key_name user_data = base64encode(data.template_file.user_data_nat_instance.rendered) placement { availability_zone = var.vpc_azs[0] } network_interfaces { delete_on_termination = false network_interface_id = aws_network_interface.nat_instance_interface.id } iam_instance_profile { arn = aws_iam_instance_profile.nat_instance_profile.arn } } resource "aws_autoscaling_group" "autoscalling_group" { name = "nat-instance" max_size = 1 min_size = 1 availability_zones = [var.vpc_azs[0]] launch_template { id = aws_launch_template.launch_template.id version = aws_launch_template.launch_template.latest_version } lifecycle { create_before_destroy = true } } |
По очереди разберем основные моменты.
template_file
Это ни что иное, как sh-скрипт, который будет выполнен при запуске инстанса. Вот содержимое файла user_data_nat_instance.sh:
1 2 3 4 5 |
#!/bin/bash IMDS_TOKEN=$(curl -sX PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 600") export INSTANCE_ID=$(curl -H "X-aws-ec2-metadata-token: $IMDS_TOKEN" -s http://169.254.169.254/latest/meta-data/instance-id) aws ec2 modify-instance-attribute --region ${region} --instance-id $INSTANCE_ID --source-dest-check "{\"Value\": false}" |
Чтобы инстанс мог пересылать пакеты от машин в приватной сети наружу и обратно, нужно отключить у него атрибут SrcDestCheck 1. К сожалению, ресурс aws_launch_template 2 не позволяет 3 это сделать напрямую и пришлось искать обходные пути. Один из таких путей – смена нужного атрибута самим инстансом, что мы и реализуем в этом скрипте.
Идея скрипта в том, что вы сначала должны получить токен для авторизации, а потом сходить с этим токеном и поменять атрибут инстанса.
aws_network_interface
Ресурс создает тот самый интерфейс, о котором мы говорили в прошлой статье. Напомню, что трафик будет ходить как раз через этот интерфейс, который будет прикрепляться к EC2-инстанусу, созданному с помощью Auto Scaling group.
Стоит отметить, что навешивать Security Groups нужно именно на этот интерфейс, но не на Launch Template внутри секции network_interfaces, что могло бы показаться более логичным. В противном случае столкнетесь с подобными ошибками:
1 2 |
Error: Error updating Autoscaling group: InvalidQueryParameter: Invalid launch template: When a network interface is provided, the security groups must be a part of it. status code: 400, request id: 7b76a51e-e224-4db1-b17b-e1b892fa8ee2 |
У модуля aws_network_interface 4 есть атрибут source_dest_check, который по аналогии с EC2-инстансом надо выставить в значение False (Default true).
aws_launch_template
Ключевой ресурс во всей нашей схеме, именно в нем описывается вся конфигурация будущих инстансов. Несмотря на это, рассказать что-то особенного о нем не получится. В нем просто объединяются все другие объекты, которые мы так старательно описываем в других участках конфигурации.
В секции network_interfaces как раз подключается сетевой интерфейс, а в iam_instance_profile – IAM-профиль для предоставления разрешений, необходимых инстансу для самостоятельного изменения нужного атрибута.
IAM
Ну а теперь подробнее о разрешениях. Дело в том, что по умолчанию сам инстанс не имеет прав на изменение своих же атрибутов. Это было бы уязвимостью. Потому нам надо предоставить ему разрешения в явном виде. Именно это и описывается в файле iam.tf:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# allow nat instances nodes override source-dest-check data "aws_iam_policy_document" "nat_instance_role" { statement { principals { type = "Service" identifiers = ["ec2.amazonaws.com"] } actions = ["sts:AssumeRole"] } } resource "aws_iam_role" "nat_instance_role" { name = "NatInstanceServiceRole" assume_role_policy = data.aws_iam_policy_document.nat_instance_role.json } resource "aws_iam_instance_profile" "nat_instance_profile" { name = "NatInstanceProfile" role = aws_iam_role.nat_instance_role.id } data "aws_iam_policy_document" "nodes_change_attributes" { statement { actions = ["ec2:ModifyInstanceAttribute"] resources = ["*"] effect = "Allow" } } resource "aws_iam_policy" "nodes_change_attributes" { name = "nat_instance_nodes_change_attributes" policy = data.aws_iam_policy_document.nodes_change_attributes.json } resource "aws_iam_role_policy_attachment" "nodes_change_attributes" { policy_arn = aws_iam_policy.nodes_change_attributes.arn role = aws_iam_role.nat_instance_role.id } |
Специфика IAM в том, что сначала надо создать политику assume role, которая предоставляет объекту право на принятие роли.
Делается это с помощью обязательной опции assume_role_policy у ресурса aws_iam_role. Далее просто прикрепляем нужную политику, которая предоставляет право ec2:ModifyInstanceAttribute. Ну а в довесок создаем aws_iam_instance_profile, который уже прикрепляем к aws_launch_template.
Security groups
Нам понадобятся две группы безопасности. Первая нужна для самого NAT-инстанса, чтобы разрешить ему принимать трафик из локальной сети и отправлять данные. Вторая группа предназначена для EC2-инстансов, находящихся в приватных сетях.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
resource "aws_security_group" "nat_instance_sg" { name = "nat_instance_sg" vpc_id = aws_vpc.main.id egress { from_port = 0 protocol = "-1" to_port = 0 cidr_blocks = ["0.0.0.0/0"] } // ip addresses of your office/home // ingress { // from_port = 22 // protocol = "tcp" // to_port = 22 // cidr_blocks = ["1.2.3.4/32"] // } ingress { from_port = 0 protocol = "tcp" to_port = 65535 cidr_blocks = [var.vpc_cidr_block] } } resource "aws_security_group" "ec2_default" { name = "ec2_default" vpc_id = aws_vpc.main.id egress { cidr_blocks = ["0.0.0.0/0"] from_port = 0 protocol = "-1" to_port = 0 } ingress { cidr_blocks = [var.vpc_cidr_block] from_port = 0 protocol = "tcp" to_port = 65535 } } |
Бывает полезно разрешить ходить на публичные EC2 инстансы из офисных подсетей. Для этого раскомментируйте соответствующую секцию и укажите нужные адреса.
Заключение
В текущем состоянии все должно работать, остается только выполнить terrafom init && terraform apply. Тем не менее важно знать о недостатках этой схемы. Нюанс в том, что я размещаю целевые инстансы в разных зонах доступности, а NAT-инстанс у меня всего один и располагаться он может разумеется только в одной зоне доступности. Таким образом, трафик будет гоняться между зонами доступности одного региона, а это стоит дополнительных денег, что явно упоминается в документации 5:
Data transferred “in” to and “out” from Amazon EC2, Amazon RDS, Amazon Redshift, Amazon DynamoDB Accelerator (DAX), and Amazon ElastiCache instances, Elastic Network Interfaces or VPC Peering connections across Availability Zones in the same AWS Region is charged at $0.01/GB in each direction.
А вот что пишут на счет трафика внутри зоны доступности:
Data transferred between Amazon EC2, Amazon RDS, Amazon Redshift, Amazon ElastiCache instances, and Elastic Network Interfaces in the same Availability Zone is free.
Если хотите сделать схему дешевле, то разумно будет создать по одной ASG с NAT-инстансом в каждой зоне доступности. Другое дело, что сами EC2-инстансы тоже стоят денег и еще непонятно что в итоге будет дешевле – оплачивать cross-AZ трафик или дополнительный NAT-инстанс. Решать вам, а у меня на этом все. Успехов!