最終的なコードはこちらです。
EC2インスタンスの中にMySQLコンテナを立ち上げてローカルから接続します。
検証用として使用することを想定しています。
環境
- Terraform 1.2.4
※Docker Imageを使用しました。 - provider hashicorp/aws 4.21.0
- docker 20.10.10

手順
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
任意のパスワードを設定します。
必要に応じてgitignoreに追記してください。
db.env
MYSQL_ROOT_PASSWORD=password
こちらも必要に応じてgitignoreに追加します。
ターミナル
ssh-keygen -t rsa -f db-instance.pem
provider.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "4.21.0"
}
}
required_version = "1.2.4"
}
provider "aws" {
region = "ap-northeast-1"
}
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"
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.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,
]
}
key.tf
resource "aws_key_pair" "db_instance_key" {
key_name_prefix = "db-instance-key"
public_key = var.public_key
}
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.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
}
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 init
terraform apply
リソースが作成されDBのドメインが出力されます。

動作確認

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を確認できました。