Terraform で ECS 環境を構築する② 〜ドメイン設定・ALB編〜

前回の記事から、Terraform で ECS 環境の構築に取り組んでいました。 今回は、ドメインの設定とロードバランサの設定を行い、HTTPS で外部からのリクエストを受けられるようにします。

blog.linkode.co.jp

はじめに:最終的なゴールと今回の目標の確認

  • 最終的な目標は、外部からのリクエストを受けられる ECS サービスの構築です。
    • 下図の青く囲ったところが前回構築した部分、赤く囲ったところが今回の目標です。

f:id:linkode-okazaki:20200918113004p:plain

無料で独自ドメインを取得し、Route 53 で利用する

無料ドメインの取得まで

  • freenom にアクセスしたら、取得したいドメイン名を入れて検索します。

f:id:linkode-okazaki:20200918113119p:plain

  • 取得可能な一覧が表示されますので、取得したい TLD の「今すぐ入手」を押します。

f:id:linkode-okazaki:20200918113115p:plain

  • 右上に「チェックアウト」のボタンが出るので押して次に進みます。

f:id:linkode-okazaki:20200918113111p:plain

  • 次の画面の「Period」で期間を選択します。12 ヶ月(12 Months)までは無料のようです。
    • 1 年(1 year)は有料です(なぜ・・・?)
    • 期間が終了して継続して利用する場合は、更新を忘れないようにします。
  • 選択したら、「Continue」で次に進みます。

f:id:linkode-okazaki:20200918113107p:plain

  • 次の画面でメールアドレスか Google アカウントによる認証を行います。

f:id:linkode-okazaki:20200918113103p:plain

  • 認証が終わったら、詳細情報を聞かれます。
    • この情報は公開されません。
    • whois には freenom の情報が記載されます。

f:id:linkode-okazaki:20200918113058p:plain

取得したドメインを Route 53 で利用できるようにする

  • 独自ドメインを Route 53 で利用できるようにします*1
    • AWS の各種サービスで利用しやすくなります。
  • Terraform は使わずにマネジメントコンソールで設定していきます。
  • Route 53の画面で左メニューから「ホストゾーン」を選択します。
  • 「ホストゾーンの作成」を押して次に進みます。
  • ドメイン名」に先程取得したドメイン名を記入して、「ホストゾーンの作成」を押して進みます。

f:id:linkode-okazaki:20200918113053p:plain

  • ホストゾーンを登録後、登録したホストゾーンの設定画面から、 NS レコードの4つの値を確認します。

f:id:linkode-okazaki:20200918113044p:plain

  • 再び、 freenom の画面に戻り、サインアップ後、上のメニューより「Services」->「My Domains」から取得したドメインの「Manage Domain」を押します。

f:id:linkode-okazaki:20200918113049p:plain

  • 「Management Tools」から「Nameservers」を選択します。
  • 「Use custom nameservers (enter below)」を選択し、Nameserver の入力欄に、先程 Route 53 で確認した NS レコードの入力して「Change Nameservers」を押します。
    • レコードの入力欄には、各設定値の最後のドット(.)は入れないことに注意します。
    • ネームサーバの設定変更の反映には時間がかかります。

f:id:linkode-okazaki:20200918113040p:plain

ALB の設定

  • 前段が長くなってしまいましたが、ここから Terraform を使って ALB を構築していきます。

ALB ログバケット

  • ALB のアクセスログを S3 に保存するためのバケットを作成します。Terraform で以下のように設定します。
    • force_destroy : true に設定しておかないと、バケット内にオブジェクトが残っていると、 terraform destroyバケットの削除ができません。
      • 逆に、削除操作から保護したいバケットはデフォルトのままにしておきます。
    • lifecycle_rule : ファイルのライフサイクルルールを設定します。
      • 下の設定では、 180 日経過したファイルを自動的に削除するようになります。
resource "aws_s3_bucket" "alb_log" {
  bucket        = "linkode-terraform-alb-log"
  force_destroy = true

  lifecycle_rule {
    enabled = true

    expiration {
      days = "180"
    }
  }
}
  • AWS の他のサービスから S3 バケットへの書き込みアクセス権を設定します。以下のようにバケットポリシーを設定します。
    • ALB の場合、AWS が管理しているアカウントから書き込むため、書き込みを行うアカウント ID を principals.identifiers に記載します。
    • アカウントの ID はリージョン毎に異なります。以下のページから確認できます。
resource "aws_s3_bucket_policy" "alb_log" {
  bucket = aws_s3_bucket.alb_log.id
  policy = data.aws_iam_policy_document.alb_log.json
}

data "aws_iam_policy_document" "alb_log" {
  statement {
    effect    = "Allow"
    actions   = ["s3:PutObject"]
    resources = ["arn:aws:s3:::${aws_s3_bucket.alb_log.id}/*"]

    principals {
      type        = "AWS"
      identifiers = ["582318560864"]
    }
  }
}
  • ここまでの内容を s3.tf にまとめて記載しておきます。
// ここまでのファイル構成
.
|-- main.tf
|-- network.tf
|-- s3.tf          // 新規作成したファイル
`-- security_group
    |-- main.tf
    |-- output.tf
    `-- variable.tf

ALB の作成

  • ALB の構成要素は以下の図のようになっています。
    • リスナーに定義したポートでリクエストを受け付け、パスや IP アドレスなどのルールに基づいて、ターゲットにリクエストを転送します。
    • 下の図の例では、 HTTP のリスナーには、 EC2 と ECS をターゲットグループに持つため、HTTPS のリスナーは ECS と Lambda をターゲットグループに持っています。
    • ターゲットグループの設定については、次回の記事で行います。

f:id:linkode-okazaki:20200918113036p:plain

  • まず、ALB を以下のように定義します。
    • load_balancer_type : ロードバランサーの種別を設定します。application を指定すると ALB, network を指定すると NLB になります。
    • internal : インターネット向けなのか VPC 内部向けなのかを指定します。false にするとインターネット向けになります。
    • idle_timeout : タイムアウトの時間(秒単位)を指定します。デフォルトは 60 秒です。
    • enable_deletion_protection : 削除保護を有効にするか否かを指定します。本番環境では誤って削除しないよう true を指定します。
    • subnets : ALB が所属するサブネットを指定します。
    • access_logs : バケット名を指定して、アクセスログの保存を有効にします。
      • 先ほど作成した、S3 バケットを指定します。
resource "aws_lb" "example" {
  name                       = "example"
  load_balancer_type         = "application"
  internal                   = false
  idle_timeout               = 60
  enable_deletion_protection = false

  subnets = [
    aws_subnet.public_a.id,
    aws_subnet.public_b.id,
  ]

  access_logs {
    bucket  = aws_s3_bucket.alb_log.id
    prefix  = "example-alb"
    enabled = true
  }

  security_groups = [
    module.http_sg.security_group_id,
    module.https_sg.security_group_id,
  ]
}

output "alb_dns_name" {
  value = aws_lb.example.dns_name
}

セキュリティグループの作成

  • 前回の記事で作成したモジュールを用いて、セキュリティグループを作成します。
    • HTTP のポート 80 番と HTTPS の 443 番を許可します。
module "http_sg" {
  source      = "./security_group"
  name        = "http-sg"
  vpc_id      = aws_vpc.example.id
  port        = 80
  cidr_blocks = ["0.0.0.0/0"]
}

module "https_sg" {
  source      = "./security_group"
  name        = "https-sg"
  vpc_id      = aws_vpc.example.id
  port        = 443
  cidr_blocks = ["0.0.0.0/0"]
}

リスナーの作成

  • リスナーを作成し、どのポートでリクエストを受けるかを設定します。
  • 以下では、次のように設定しています:
  • certificate_arnSSL 証明書の ARN を指定していますが、これは後ほど設定します。
resource "aws_lb_listener" "http" {
  load_balancer_arn = aws_lb.example.arn

  port     = "80"
  protocol = "HTTP"

  default_action {
    type = "redirect"

    redirect {
      port        = "443"
      protocol    = "HTTPS"
      status_code = "HTTP_301"
    }
  }
}

resource "aws_lb_listener" "https" {
  load_balancer_arn = aws_lb.example.arn
  port              = "443"
  protocol          = "HTTPS"
  certificate_arn   = [後ほど設定]
  ssl_policy        = "ELBSecurityPolicy-2016-08"

  default_action {
    type = "fixed-response"
    fixed_response {
      content_type = "text/plain"
      message_body = "これは「HTTPS」です"
      status_code  = "200"
    }
  }
}
  • ここまでの内容を alb.tf に記述して保存しておきます。
// ここまでのファイル構成
.
|-- alb.tf         // 新規作成したファイル
|-- main.tf
|-- network.tf
|-- s3.tf
`-- security_group
    |-- main.tf
    |-- output.tf
    `-- variable.tf

Route 53 の設定

  • 最初に作成したホストゾーンに DNS レコードを定義します。
  • 予め作成したホストゾーンは以下のように参照します。
data "aws_route53_zone" "main" {
  name = "linkode-example.ml"
}
  • このホストゾーンに対して、DNS レコードを作成します。
    • AWS では ALIAS レコードという独自拡張のレコードを使用できます。
      • DNS から見ると単なる A レコードという扱いになります。
      • ALIAS レコードは AWS の各種サービスと統合されており、 ALB だけでなく、 S3 バケットや CloudFront も指定できます。
    • 今回は ALIAS レコードを使用するために、type で「A」を指定します。
resource "aws_route53_record" "example" {
  zone_id = data.aws_route53_zone.main.zone_id
  name    = data.aws_route53_zone.main.name
  type    = "A"

  alias {
    name                   = aws_lb.example.dns_name
    zone_id                = aws_lb.example.zone_id
    evaluate_target_health = true
  }
}
  • ここまでの内容を route53.tf に記載しておきます。
// ここまでのファイル構成
.
|-- alb.tf
|-- main.tf
|-- network.tf
|-- route53.tf      // 新規作成したファイル
|-- s3.tf
`-- security_group
    |-- main.tf
    |-- output.tf
    `-- variable.tf

ACMSSL 証明書の作成

  • ACM を利用すると、 SSL 証明書の自動更新を行なってくれます。
  • SSL 証明書はホストゾーンと同様に手動で設定します。
  • マネジメントコンソールより Certificate Manager を選択し、「証明書のリクエスト」を押します。
  • 「パブリック証明書のリクエスト」を選択し、「証明書のリクエスト」を押します。

f:id:linkode-okazaki:20200918113031p:plain

  • 次の画面で、取得したドメイン名を入力して次へ進みます。

f:id:linkode-okazaki:20200918113023p:plain

  • 検証方法は、DNS 検証を選択して次へ進む。

f:id:linkode-okazaki:20200918113027p:plain

  • タグについては特に入力せず、確認の画面に進み、次の画面で「確定とリクエスト」を押します。

f:id:linkode-okazaki:20200918113017p:plain

  • Route 53 でホストゾーンを管理していると、検証の画面で「Route 53 でのレコード作成」ボタンが現れるので押すと、検証用レコードが作成されます。
    • 検証には少々時間がかかりますが、完了したら「発行済み」の状況になります。

f:id:linkode-okazaki:20200918113013p:plain

f:id:linkode-okazaki:20200918113008p:plain

  • 証明書の発行が完了したら、HTTPSaws_lb_listener で空欄にしていた certificate_arnSSL 証明書の ARN を記載します。

適用と検証

  • ここまでの作業が完了したら、プロジェクトのルートディレクトリで、 terraform apply を実行します。
    • モジュールの適用のために一度 terraform get を実行します。
$ terraform fmt
$ terraform get
$ terraform validate
$ terraform plan
$ terraform apply
  • 完了した際にコンソールに表示される「alb_dns_name」の curl でアクセスしてみます。
    • 下のように、リダイレクトされた旨のレスポンスが返ってきたら成功です。
$ curl http://example-XXXXXX.ap-northeast-1.elb.amazonaws.com
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
</body>
</html>
  • 次に、独自ドメインの URL にアクセスしてみます。
    • http でアクセスすると、先ほどと同じくリダイレクトされた旨のレスポンスが返ってきます。
    • https でアクセスすると、 Terraform で設定した固定レスポンスが返ってきます。
$ curl http://linkode-example.ml
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
</body>
</html>
$ curl https://linkode-example.ml
これは「HTTPS」です

まとめ

  • Terraform を用いて、Route 53 や ACM, ALB を設定・構築し、独自ドメインHTTPS のリクエストを受けられるようにしました。
  • 無料で独自ドメインを取得する方法と AWS で利用するための方法も紹介しました。
    • freenom 以外のドメインサービスでも同様の設定で利用することが出来ます。
  • ここまでで、外部からのリクエストを受けられる状態になりました。次回はリクエストを ECS に振り分けるように設定します。

参考資料

*1:スクショの都合上、ドメインの取得の例は 「linkode-sample」 で手順を説明していましたが、以下の設定では独自ドメインとして 「linkode-example」 を使用します