(第2回)TerraformでAWS上にWebサーバーを構築する

(第2回)TerraformでAWS上にWebサーバーを構築する

目次

前回の振り返り
セキュリティグループのリソース定義を作成
 - securitygroup.tf
IAM関連のリソース定義を作成
 - iam.tf
Route 53のリソース定義を作成
 - route53.tf
Certificate Manager(ACM)の定義を作成
 - acm.tf
次回予告

前回の振り返り

前回はTerraformでVPCリソースの定義まで書きました。

今回はセキュリティグループ、IAM、Route 53、Certificate Managerリソースの定義を作成していきます。

terraform/
├─vars/
│  └─terraform.tfvars
├─acm.tf              ... ★今回★
├─ec2.tf
├─elb.tf
├─iam.tf              ... ★今回★
├─provider.tf         ... 第1回で作成済
├─route53.tf          ... ★今回★
├─securitygroup.tf    ... ★今回★
├─terraform.tf        ... 第1回で作成済
├─variables.tf        ... 第1回で作成済
└─vpc.tf              ... 第1回で作成済

セキュリティグループのリソース定義を作成

セキュリティグループは2つ作成します。

  • ALB用のセキュリティグループ
    • 今回は検証のためHTTPS、HTTPを自分のIPアドレスからのみアクセスできるように許可します。
  • EC2(Webサーバー)用のセキュリティグループ
    • ALBからの80番ポートへの受信を許可します。
securitygroup.tf
data http ifconfig {
  url = "https://ifconfig.co/ip"
}

locals {
  myip = chomp(data.http.ifconfig.body)
}

# Internal ALB Security Group
resource "aws_security_group" "alb_sg" {
  name   = "${var.name_prefix}-alb-sg"
  vpc_id = aws_vpc.vpc.id

  /* In-Bound */
  ingress = [
    {
      from_port        = 80
      to_port          = 80
      protocol         = "tcp"
      cidr_blocks      = ["${local.myip}/32"]
      description      = "http allow"
      ipv6_cidr_blocks = []
      prefix_list_ids  = []
      security_groups  = []
      self             = false
    },
    {
      from_port        = 443
      to_port          = 443
      protocol         = "tcp"
      cidr_blocks      = ["${local.myip}/32"]
      description      = "https allow"
      ipv6_cidr_blocks = []
      prefix_list_ids  = []
      security_groups  = []
      self             = false
    }
  ]

  /* Out-Bound */
  egress = [
    {
      from_port        = 0
      to_port          = 0
      protocol         = "-1"
      cidr_blocks      = ["0.0.0.0/0"]
      description      = "egress allow"
      ipv6_cidr_blocks = []
      prefix_list_ids  = []
      security_groups  = []
      self             = false
    }
  ]

  tags = {
    Name = "${var.name_prefix}-alb-sg"
  }
}

# EC2 Web Server Security Group
resource "aws_security_group" "web_sg" {
  name   = "${var.name_prefix}-web-server-sg"
  vpc_id = aws_vpc.vpc.id

  /* In-Bound */
  ingress = [
    {
      from_port        = 80
      to_port          = 80
      protocol         = "tcp"
      cidr_blocks      = []
      description      = "http allow from alb"
      ipv6_cidr_blocks = []
      prefix_list_ids  = []
      security_groups  = [aws_security_group.alb_sg.id]
      self             = false
    }
  ]

  /* Out-Bound */
  egress = [
    {
      from_port        = 0
      to_port          = 0
      protocol         = "-1"
      cidr_blocks      = ["0.0.0.0/0"]
      description      = "egress allow"
      ipv6_cidr_blocks = []
      prefix_list_ids  = []
      security_groups  = []
      self             = false
    }
  ]

  tags = {
    Name = "${var.name_prefix}-web-server-sg"
  }
}

※ 作成したリソースは後ほど、ALBとEC2のリソースから参照します。Webサイトを全公開する場合は[“${local.myip}/32”]の部分を[“0.0.0.0/0”]に変更します。

IAM関連のリソース定義を作成

EC2にIAMロールをアタッチするための定義を作成していきます。

  • EC2用のロール
    ⇒ SSMのセッションマネージャーが使用できるように「AmazonSSMManagedInstanceCore」AWS管理ポリシーをアタッチ
    ⇒ Cloud Watch Agentを導入する想定をして「CloudWatchAgentServerPolicy」をアタッチ
  • インスタンスプロファイル
  • ポリシー
    ⇒ ssm:StartSessionアクションを許可
  • ポリシーをロールにアタッチする定義
    ⇒ 作成したカスタマー管理ポリシーをEC2用のロールにアタッチする定義を書きます。
iam.tf
# ------------------------------------------------------ #
# IAM
# ------------------------------------------------------ #

# EC2 Instance Attach Role
resource "aws_iam_role" "ec2_role" {
  name = "${var.name_prefix}-ec2-role"
  assume_role_policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [
      {
        "Effect" : "Allow",
        "Principal" : {
          "Service" : "ec2.amazonaws.com"
        },
        "Action" : "sts:AssumeRole"
      }
    ]
  })

  managed_policy_arns = [
    "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
    "arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy"
  ]

  tags = {
    Name = "${var.name_prefix}-ec2-role"
  }
}

# Instance Profile
resource "aws_iam_instance_profile" "ec2_profile" {
  name = "${var.name_prefix}-ec2-profile"
  role = aws_iam_role.ec2_role.name
  tags = {
    Name = "${var.name_prefix}-ec2-profile"
  }
}

# IAM Policy
resource "aws_iam_policy" "start_session" {
  depends_on = [aws_instance.web]
  name        = "${var.name_prefix}-start-session-policy"
  description = "session manager start session"
  policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [
      {
        "Effect" : "Allow",
        "Action" : "ssm:StartSession",
        "Resource" : [
          "arn:aws:ec2:*:*:instance/${aws_instance.web.id}",
          "arn:aws:ssm:*:*:document/AWS-StartSSHSession"
        ]
      }
    ]
  })

  tags = {
    Name = "${var.name_prefix}-start-session-policy"
  }
}

resource "aws_iam_policy_attachment" "ec2_role" {
  depends_on = [aws_iam_policy.start_session]
  name       = "ec2-role-attachment"
  roles      = [aws_iam_role.ec2_role.name]
  policy_arn = aws_iam_policy.start_session.arn
}

※ SSMスタートセッション用のポリシーにインスタンスIDを変数として使用するため、「aws_iam_role」の「managed_policy_arns」には書かず、あえて「aws_iam_policy_attachment」リソースを別途定義しています。こうすることでapply実行時にEC2リソース作成後にこのポリシーを作成しアタッチしてくれるようになります。(下記参照)

  • IAMロールを作成
  • EC2を作成、作成したIAMロールをEC2にアタッチ
  • ポリシーを作成
    ⇒ EC2が作成されないと52行目のインスタンスIDが分からないため、このタイミングで作成されないといけない。「aws_iam_role」の「managed_policy_arns」でアタッチすることを書いてしまうとEC2が作成される前にこのポリシーを作成しようとしてエラーが発生する。
  • ポリシーをIAMロールにアタッチ

Route 53のリソース定義を作成

Webサーバーを公開するにはDNSの設定が必要です。Route53でゾーンを作成し、レコードを追加します。

route53.tf
# Public DNS Zone
resource "aws_route53_zone" "public" {
  name = var.dns_zone
}

# DNS Record
resource "aws_route53_record" "www" {
  zone_id = aws_route53_zone.public.zone_id
  name    = "www.${var.dns_zone}"
  type    = "CNAME"
  ttl     = "300"
  records = [
    "${aws_lb.alb.dns_name}"
  ]
}

Certificate Manager(ACM)の定義を作成

昨今では、SSL/TLSによる暗号化が必須ですので、ACMで証明書を発行します。DNS検証によるドメイン所有者の確認もここでレコード追加が可能です。公式のドキュメントを参考に記載をします。

acm.tf
resource "aws_acm_certificate" "cert" {
  domain_name = "www.${var.dns_zone}"
  validation_method = "DNS"

  lifecycle {
    create_before_destroy = true
  }

  tags = {
    Name = "${var.name_prefix}-acm"
  }
}

resource "aws_route53_record" "cert_validation" {
  for_each = {
    for x in aws_acm_certificate.cert.domain_validation_options : x.domain_name => {
      name   = x.resource_record_name
      record = x.resource_record_value
      type   = x.resource_record_type
    }
  }

  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  type            = each.value.type
  ttl             = "300"

  zone_id = aws_route53_zone.public.id
}

次回予告

今回はSSL/TLS証明書が実用的に利用できるよう考慮もしました。次回はいよいよ大詰めでELB、EC2を作成していきます。