目次
前回の振り返り
EC2リソース定義の作成
– ec2.tf
– Elastic IPを追加する際のリソース定義
ELBリソース定義の作成
– elb.tf
変数定義の追加と.tfvarsの用意
– variables.tf
– vars/terraform.tfvars
Terraformの実行
終わりに
前回の振り返り
前回はTerraformで IAMロール、セキュリティグループ、Route53、Certificate Managerのリソースを作成しました。
今回はいよいよラストのEC2インスタンスとALBを用意してWebサイトを公開します。
terraform/ ├─vars/ │ └─terraform.tfvars ... ★今回★ ├─acm.tf ... 第2回で作成済 ├─ec2.tf ... ★今回★ ├─elb.tf ... ★今回★ ├─iam.tf ... 第2回で作成済 ├─provider.tf ... 第1回で作成済 ├─route53.tf ... 第2回で作成済 ├─securitygroup.tf ... 第2回で作成済 ├─terraform.tf ... 第1回で作成済 ├─variables.tf ... 第1回で作成済 └─vpc.tf ... 第1回で作成済
EC2リソース定義の作成
WebサーバーとしてEC2インスタンスを定義しますが、KMSで鍵を作成しEBSを暗号化するようにしています。
AMIはAmazon Linux2の5.10系の最新ビルドが選択されるようにData Sourcesを使ってAMI IDを取得します。
ec2.tf
# AWSアカウントIDを取得 data "aws_caller_identity" "current" {} locals { account_id = data.aws_caller_identity.current.account_id } # Amazon Linux 2の最新のAMI IDを取得 data "aws_ami" "amzn2" { most_recent = true owners = ["amazon"] filter { name = "state" values = ["available"] } filter { name = "name" values = ["amzn2-ami-kernel-5.10-hvm*"] } } # EBS用の暗号鍵の作成 # KMS Key resource "aws_kms_key" "ec2_kms_key" { description = "EC2 KMS Key" deletion_window_in_days = 10 policy = jsonencode({ Version = "2012-10-17" Statement = [ { "Sid" : "Enable IAM User Permissions", "Effect" : "Allow", "Principal" : { "AWS" : "arn:aws:iam::${local.account_id}:root" }, "Action" : "kms:*", "Resource" : "*" } ] }) } resource "aws_kms_alias" "ec2_kms_key" { name = "alias/ebs-kms-key" target_key_id = aws_kms_key.ec2_kms_key.key_id } # EC2 Web Instance resource "aws_instance" "web" { ami = data.aws_ami.amzn2.id instance_type = var.ec2_instance_type iam_instance_profile = aws_iam_instance_profile.ec2_profile.name key_name = var.ec2_keypair subnet_id = aws_subnet.private_a_1.id monitoring = false vpc_security_group_ids = ["${aws_security_group.web_sg.id}"] hibernation = false tenancy = "default" associate_public_ip_address = false user_data = <<EOF #!/bin/sh amazon-linux-extras install -y nginx1 systemctl start nginx systemctl enable nginx EOF credit_specification { cpu_credits = "standard" } root_block_device { delete_on_termination = true encrypted = true kms_key_id = aws_kms_key.ec2_kms_key.arn volume_size = var.ec2_volume_size volume_type = "gp3" tags = { Name = "${var.name_prefix}-web" } } lifecycle { ignore_changes = [ associate_public_ip_address ] } tags = { Name = "${var.name_prefix}-web" } }
※ インターネット経由でインスタンスにssh接続する場合は、インスタンスの配置サブネットをパブリックサブネットに変更し、Elastic IPを追加する必要があります。
Elastic IPを追加する際のリソース定義
# Elastic IP resource "aws_eip" "ec2" { instance = aws_instance.web.id vpc = true tags = { Name = "${var.name_prefix}-eip-ec2" } }
ELBリソース定義の作成
Application Load Balancer(ALB)、HTTPSとHTTPの各リスナー、ターゲットグループとターゲットグループをALBにアタッチするためのターゲットグループアタッチメントを記載します。
ALBにアタッチするセキュリティグループは第2回の記事で事前に用意しています。
elb.tf
# Application load balancer resource "aws_lb" "alb" { name = "${var.name_prefix}-alb" internal = false subnets = [aws_subnet.public_a_1.id, aws_subnet.public_c_1.id] security_groups = [aws_security_group.alb_sg.id] load_balancer_type = "application" idle_timeout = 60 tags = { Name = "${var.name_prefix}-alb" } } # HTTPS Listener resource "aws_lb_listener" "alb_https" { load_balancer_arn = aws_lb.alb.arn port = "443" protocol = "HTTPS" ssl_policy = "ELBSecurityPolicy-TLS-1-2-Ext-2018-06" certificate_arn = aws_acm_certificate.cert.id default_action { type = "forward" target_group_arn = aws_lb_target_group.alb_tg.arn } tags = { Name = "${var.name_prefix}-alb-https-listener" } } # HTTP Listener resource "aws_lb_listener" "alb_http" { load_balancer_arn = aws_lb.alb.arn port = "80" protocol = "HTTP" default_action { type = "forward" target_group_arn = aws_lb_target_group.alb_tg.arn } tags = { Name = "${var.name_prefix}-alb-http-listener" } } # Target Group resource "aws_lb_target_group" "alb_tg" { name = "${var.name_prefix}-alb-tg" port = 80 protocol = "HTTP" vpc_id = aws_vpc.vpc.id health_check { healthy_threshold = 5 unhealthy_threshold = 2 matcher = 200 timeout = 5 path = "/" interval = 30 } tags = { Name = "${var.name_prefix}-alb-tg" } } # Target Group Attachment resource "aws_lb_target_group_attachment" "alb_tg_attach" { target_group_arn = aws_lb_target_group.alb_tg.arn target_id = aws_instance.web.id port = 80 }
変数定義の追加と.tfvarsの用意
追加したec2とelbの定義を一部変数化したため variables.tf に変数を追加し terraform apply の際に手で入力せずに済むように terraform.tfvars で変数の値を定義しておきます。
variables.tf(第1回から追記)
# -------------------------------------- # # General # -------------------------------------- # # Prefix variable "name_prefix" { type = string description = "system code prefix" } # Environment variable "env" { type = string description = "tag" } # Tags variable "tag_terraform" { type = string default = "true" description = "tag" } variable "tag_system_name" { type = string description = "tag" } # Profile Name variable "profile" { type = string description = "aws cli profile name" } variable "region" { type = string default = "ap-northeast-1" description = "AWS region in which resources will get deployed. Defaults to Tokyo." } # -------------------------------------- # # VPC # -------------------------------------- # variable "vpc_cidr" { type = string description = "VPC CIDR" } variable "private_subnet_cidr_a_1" { type = string description = "Private Subnets CIDR" } variable "private_subnet_cidr_c_1" { type = string description = "Private Subnets CIDR" } variable "public_subnet_cidr_a_1" { type = string description = "Public Subnets CIDR" } variable "public_subnet_cidr_c_1" { type = string description = "Public Subnets CIDR" } # -------------------------------------- # # Route53 # -------------------------------------- # variable "dns_zone" { type = string description = "dns zone name" } # -------------------------------------- # # EC2 # -------------------------------------- # variable "ec2_instance_type" { type = string description = "EC2 Instance Type" } variable "ec2_volume_size" { type = string description = "EBS Volume Size" } variable "ec2_keypair" { type = string description = "EC2 Key pair name" } # -------------------------------------- # # ELB ヘルスチェックするパス # -------------------------------------- # variable "elb_healthcheck_path" { type = string default = "/" description = "health check path" }
変数に対して値を入れておきたいため .tfvars を用意し以下のような内容を入力します。
※ わざわざ変数化する理由としては開発、本番等のリソースを複数環境にデプロイをする際に一部設定を変えたい際などに変数化しておいたりしています。もしくは、複数のリソースに同じ値を入れておきたい場合などに使ったりします。(その場合は locals で定義しても良いかと思います)
vars/terraform.tfvars
# General name_prefix = "itport" # 各リソースのNameタグの接頭辞に使用する env = "dev" # リソースタグに記載する # Shared Resource Tags tag_terraform = "true" # Terraformを使用して構築されたリソースか分かるようにする tag_system_name = "itport" # SystemNameタグに使用する # Network region = "ap-northeast-1" profile = "itport" # VPC CIDR vpc_cidr = "10.0.0.0/22" # Subnets CIDR private_subnet_cidr_a_1 = "10.0.0.0/27" private_subnet_cidr_c_1 = "10.0.0.32/27" public_subnet_cidr_a_1 = "10.0.1.0/27" public_subnet_cidr_c_1 = "10.0.1.32/27" # EC2 ec2_instance_type = "t3.small" ec2_keypair = "itport" ec2_volume_size = 50 # Route53 dns_zone = "作成したいゾーン名"
Terraformの実行
それではTerraformを実行していきます。planを行い必ずどういうリソースが作られるのか確認をします。
# 初回はワークスペースを初期化する terraform init # 実行計画を確認 terraform plan -var-file .\vars\terraform.tfvars # 実行 terraform apply -var-file .\vars\terraform.tfvars
※ Windows で実行しています。MacやLinuxの場合は.tfvarsのパスをバックスラッシュではなくスラッシュにします。
※ ACM証明書発行時のDNS検証が成功しないと後続のALBのHTTPSリスナーの作成が失敗します。利用するドメインの権威DNSをRoute53に変えるようにする必要があります。
※ 初回のapply時はdestroyされるリソースはないと思いますが、2回目以降は再作成(destroy & add)されてしまう変更が含まれないか必ずチェックします。例えばEC2インスタンスのAMI IDを変えようとした場合など、マネジメントコンソールで変更出来ないような属性を変えようとすると削除して作成という挙動になることがあります。そのため最初に terraform plan で確認をしておくのがベストです。
terraform plan 時に表示される構築概要が表示されている内容の例
Plan: 34 to add, 0 to change, 0 to destroy.
終わりに
TerraformでAWS上にWebサーバーを構築する方法を最後まで見てくださりありがとうございます!
もちろんではございますが、私の書き方が全てではございません。
複数のEC2インスタンスを用意する場合はモジュール化をして簡略して書けるようにすることも可能ですし、今回全てのファイルを同階層に配置しましたが、影響範囲を少なくするためにコンポーネント単位でフォルダーを分けても良いかと思います。(Terraformの実行はカレントのフォルダー直下に配置された .tf を元に実行される)
そして、なるべくシンプルに記述することを心掛けていきましたが保守性等を考慮してどう書いていくかは、毎回割と悩むところです。例えばAMI IDに関しても Data Sources を使用したため、今回の書き方だと都度最新のIDを取得してしまうため、時間の経過と共に最新のIDが変わるためDestroyされる危険があります。
ですので、あえてAMI IDを直接書いてしまうのも手かと思います。
今後も、より良い書き方が出来るよう精進したいと思います。では!