【Terraform】ECS on EC2でDBコンテナを立ち上げる方法

最終的なコードはこちらです。

EC2インスタンスの中にMySQLコンテナを立ち上げてローカルから接続します。

検証用として使用することを想定しています。

環境

  • Terraform 1.2.4
    Docker Imageを使用しました。
  • provider hashicorp/aws 4.21.0
  • docker 20.10.10
Terraform環境をDockerで構築する方法Terraform環境をDockerで構築する方法

手順

.gitignoreを作成

https://github.com/github/gitignore/blob/main/Terraform.gitignore

.gitignore
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc

DB環境変数ファイルの作成

任意のパスワードを設定します。

必要に応じてgitignoreに追記してください。

db.env
MYSQL_ROOT_PASSWORD=password

SSH秘密鍵、公開鍵の作成

こちらも必要に応じてgitignoreに追加します。

ターミナル
ssh-keygen -t rsa -f db-instance.pem

Terraformのバージョン、プロバイダーを指定

provider.tf
terraform { required_providers { aws = { source = "hashicorp/aws" version = "4.21.0" } } required_version = "1.2.4"
}
provider "aws" { region = "ap-northeast-1"
}

Terraform入力変数を設定

variables.tf
variable "db_env_path" { description = "データベースに設定する環境変数ファイルのパスです。" type = string
}
variable "public_key" { description = "SSHに使用する公開鍵です。" type = string
}
terraform.tfvars
db_env_path = "./db.env"
public_key = "ssh-rsa xxxxxxxxx"

VPC、セキュリティグループを作成

network.tf
module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "3.14.2" cidr = "10.0.0.0/16" azs = ["ap-northeast-1a"] public_subnets = ["10.0.0.0/24"]
}
resource "aws_security_group" "db" { name = "db" vpc_id = module.vpc.vpc_id
}
resource "aws_security_group_rule" "db_ingress" { type = "ingress" from_port = 3306 to_port = 3306 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] security_group_id = aws_security_group.db.id
}
resource "aws_security_group_rule" "db_egress" { type = "egress" to_port = 0 from_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] security_group_id = aws_security_group.db.id
}
resource "aws_security_group" "ssh" { name = "ssh" vpc_id = module.vpc.vpc_id
}
resource "aws_security_group_rule" "ssh_ingress" { type = "ingress" from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] security_group_id = aws_security_group.ssh.id
}
resource "aws_security_group_rule" "ssh_egress" { type = "egress" to_port = 0 from_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] security_group_id = aws_security_group.ssh.id
}

IAMロールを作成

iam.tf
data "aws_iam_policy" "ec2_container_service" { arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"
}
module "ec2_container_service_role" { source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role" version = "5.2.0" role_name = "ec2_container_service" create_role = true create_instance_profile = true role_requires_mfa = false trusted_role_services = [ "ec2.amazonaws.com" ] custom_role_policy_arns = [ data.aws_iam_policy.ec2_container_service.arn ]
}
data "aws_iam_policy" "ecs_task_execution_role" { arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
data "aws_iam_policy" "s3_readonly_access" { arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
}
module "ecs_task_execution_role" { source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role" version = "5.2.0" role_name = "ecs_task_execution" create_role = true role_requires_mfa = false trusted_role_services = [ "ecs-tasks.amazonaws.com" ] custom_role_policy_arns = [ data.aws_iam_policy.ecs_task_execution_role.arn, data.aws_iam_policy.s3_readonly_access.arn, ]
}

AWSにSSH公開鍵をインポート

key.tf
resource "aws_key_pair" "db_instance_key" { key_name_prefix = "db-instance-key" public_key = var.public_key
}

S3に環境変数設定ファイルをアップロード

SSMパラメータストアやSecretsManagerに環境変数を保存する方がセキュアですが今回は簡易的にS3にアップロードします。

s3.tf
resource "aws_s3_bucket" "db" { bucket_prefix = "db"
}
resource "aws_s3_object" "db_env" { bucket = aws_s3_bucket.db.id source = var.db_env_path key = "db.env"
}

ECSタスク定義、クラスター、サービスを作成

ecs.tf
resource "aws_ecs_cluster" "db" { name = "db"
}
resource "aws_ecs_task_definition" "db" { family = "db" requires_compatibilities = ["EC2"] network_mode = "bridge" execution_role_arn = module.ecs_task_execution_role.iam_role_arn container_definitions = jsonencode([ { "name" : "db", "image" : "mysql:8.0", "memory" : 512, "essential" : true, "portMappings" : [ { "hostPort" : 3306, "containerPort" : 3306, "protocol" : "tcp" } ], "environmentFiles" : [ { "value" : "${aws_s3_bucket.db.arn}/${aws_s3_object.db_env.id}", "type" : "s3" } ], "mountPoints" : [ { "sourceVolume" : "db-store", "containerPath" : "/var/lib/mysql", } ] } ]) volume { name = "db-store" docker_volume_configuration { scope = "shared" autoprovision = true } }
}
resource "aws_ecs_service" "db" { name = "db" cluster = aws_ecs_cluster.db.id task_definition = aws_ecs_task_definition.db.arn desired_count = 1 deployment_minimum_healthy_percent = 0 deployment_maximum_percent = 100
}

ECSクラスターにEC2インスタンスを追加

ec2.tf
data "aws_ssm_parameter" "ecs_ami" { name = "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended"
}
resource "aws_instance" "db_cluster_instance" { ami = jsondecode(data.aws_ssm_parameter.ecs_ami.value).image_id instance_type = "t2.micro" vpc_security_group_ids = [aws_security_group.db.id, aws_security_group.ssh.id] iam_instance_profile = module.ec2_container_service_role.iam_instance_profile_name subnet_id = module.vpc.public_subnets[0] key_name = aws_key_pair.db_instance_key.id root_block_device { volume_type = "gp3" volume_size = "30" delete_on_termination = true encrypted = false } user_data = <<EOF
#!/bin/bash
echo ECS_CLUSTER=${aws_ecs_cluster.db.name} >> /etc/ecs/ecs.config;
EOF
}
resource "aws_eip" "db_cluster_instance" { instance = aws_instance.db_cluster_instance.id vpc = true
}

データベースのドメインを出力するように設定

outputs.tf
output "db_domain" { description = "The domain of the database." value = aws_eip.db_cluster_instance.public_dns
}

Terraformを実行

ターミナル
terraform init
terraform apply

リソースが作成されDBのドメインが出力されます。

動作確認

DockerでMySQLとphpMyAdminを使用するDockerでMySQLとphpMyAdminを使用する

ローカルのphpMyAdminで接続してみる

PMA_HOSTには実行結果のdb_domain、PMA_PASSWORDには環境変数で定義したパスワードを設定します。

docker-compose.yml
version: '3'
services: phpmyadmin: image: phpmyadmin:5.2 ports: - target: 80 published: 80 protocol: tcp mode: host environment: - PMA_ARBITRARY=1 - PMA_HOST=ec2-54-64-155-180.ap-northeast-1.compute.amazonaws.com - PMA_USER=root - PMA_PASSWORD=password
ターミナル
docker-compose up -d

phpMyAdminにアクセス

データが永続化されているか確かめる

Dockerボリュームがホストインスタンスにマウントされていることをインスタンスに接続して確かめます。

ドメイン、秘密鍵を使用してSSHで接続します。

EC2インスタンスに接続するときのユーザー名は「ec2-user」です。

ターミナル
ssh -i db-instance.pem ec2-user@ec2-54-64-155-180.ap-northeast-1.compute.amazonaws.com

接続ができたらDockerボリュームが存在しているか確認します。

ターミナル
sudo ls /var/lib/docker/volumes

db-store volumeを確認できました。

参考文献

コメントを残す

メールアドレスが公開されることはありません。