ハイパーマッスルエンジニア

Vim、ShellScriptについてよく書く

Udemy『AWS: ゼロから実践するAmazon Web Services。手を動かしながらインフラの基礎を習得』をterraformで構築する

f:id:rasukarusan:20210210221947p:plain

Udemyの講座楽しい

www.udemy.com

terraformで構築してみた。

個人用メモ。

死ぬほど参考にさせていただいたサイト

まずは公式のチュートリアルで概要を掴む
Introduction to Infrastructure as Code with Terraform | Terraform - HashiCorp Learn

どんなresourceを使うのかなど流れを把握
VPC - Terraformで構築するAWS
【Terraform 再入門】EC2 + RDS によるミニマム構成な AWS 環境をコマンドライン一発で構築してみよう – PSYENCE:MEDIA

公式ドキュメントでプロパティ等を確認しながら書いていく
Docs overview | hashicorp/aws | Terraform Registry

やらなかったこと

  • countやmoduleによる共通化
  • Wordpressのインストール

countは下記の通りやればいけそう。ただのリファクタになりそうだったのでモチベ上がらず。
Automate Terraform | Terraform - HashiCorp Learn

Wordpressのくだりは本来docker-compose.ymlやプロビジョンツールで行うことなので、terraformでやることではないと判断したため。

便利なshell関数を作った

terrと実行するとterraformコマンドをfzfで絞り込んで実行できる。

alias terr="_terraform_execute"
_terraform_execute() {
  local cmd=$(terraform -help | grep '^  \S' | sed 's/  //' | fzf --with-nth=1 --preview='echo {2..}' --preview-window=up:1  | awk '{print $1}')
  [ -z "$cmd" ] && return
  print -s "terraform $cmd $1"
  terraform $cmd $1
}

f:id:rasukarusan:20210210222521p:plain:w500

詰まった箇所

resourceの書き方。resource "aws_vpc" "main"のmainって何につかうの?

tfファイル内でのみ使用。aws上では使用されない。aws上で使用されるのはtags.Nameの部分。 tfファイル内ではsubnet作成などでvpc_id = aws_vpc.main.idのように使用される。

vimでterraform書く

Terraform職人入門: 日々の運用で学んだ知見を淡々とまとめる - Qiita ここ参考に。hashivim/vim-terraformをインストールする。
CocのLSPを使う方法は、見当違いなlinterエラーが出るようになってしまったのでアンインストールした。

ec2でsshするためのキーペア

あとからキーペアをアタッチできない。
terraformでキーペアを作成できない。
Macで予めキーを作成しておき、それをterraformで指定する形。

$ ssh-keygen -t rsa

destroyを個別に指定したい(Ctrl-Cで終了しちゃったときとか)

-targetをつければOK terraform destroyで特定のものだけ消したい場合 – ADACHIN SERVER LABO

$ terraform destroy -target=aws_instance.main

複数のingressを設定したい

aws_security_group_ruleで設定可能

ec2のuser_dataでhttpdをインストールできない

yumがうまく実行できていないっぽい。

[ec2-user@ip-10-0-10-53 ~]$ yum list
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
Could not retrieve mirrorlist http://amazonlinux.ap-northeast-1.amazonaws.com/2/core/latest/x86_64/mirror.list error was
12: Timeout on http://amazonlinux.ap-northeast-1.amazonaws.com/2/core/latest/x86_64/mirror.list: (28, 'Connection timed out after 5001 milliseconds')


 One of the configured repositories failed (不明),

egressを設定したらいけた。security_group作成時、terraformだとアウトバウンドがデフォルトで全拒否になってしまうため。GUIで設定すると全許可がデフォルトで設定される。

DBのセキュリティグループでソースってどうやって指定するの?

source_security_group_idで指定する
- 【Terraform 再入門】EC2 + RDS によるミニマム構成な AWS 環境をコマンドライン一発で構築してみよう – PSYENCE:MEDIA

既存のAWS環境をterraformに落とし込むには?

terraform importを使って現在の設定を表示し、terraform planで変更がないことを確認しながらtfファイルを作っていく。
- 既存のAWS環境を後からTerraformでコード化する | DevelopersIO

例:既存のroute53をterraformでimportする

main.tfに定義を先に書いておく必要がある。

resource "aws_route53_zone" "main" {
  name = 'yourdomain.com'
}

定義したら下記を実行

terraform import aws_route53_zone.main Z076386518IPI8D2J7O7O

レコードのimport方法

terraform import aws_route53_record.www ZONEID_RECORDNAME_TYPE_SET-IDENTIFIER

これもすでにmain.tfに定義を先に書き込んでおく必要がある。

例:

terraform import aws_route53_record.www Z076386518IPI8D2J7O7O_rasukarusan.tk_A

amazon web services - Terraform + Route53 - manage existing record - Stack Overflow

RDSの作成が長い

8分ぐらいかかる。気長に待つ。

Error: DB Instance FinalSnapshotIdentifier is required when a final snapshot is requiredが出てRDSをdestroyできない

final_snapshot_identifierを設定していないと削除できなくなる。一旦手動でインスタンスを削除し、applyし直す。
が、これをするとオプショングループが削除できなくなるのでskip_final_snapshot = trueのみ設定する。

オプショングループの削除ができない

...
aws_db_option_group.terraform-mysql80: Still destroying... [id=terraform-mysql80, 19m50s elapsed]

Error: Error Deleting DB Option Group: InvalidOptionGroupStateFault: The option group 'terraform-mysql80' cannot be deleted because it is in use.
        status code: 400, request id: 297384d1-d362-4b33-9ee9-36d1dc0b80bf

スナップショットが作成されており、それに使用されてしまっているため。スナップショットを手動で削除し、destroyを実行し直すと削除できる。 final_snapshot_identifierをコメントアウトしてskip_final_snapshot = trueにすること。

terraform showは絞り込みができない?

destroyの-target=のようにしたができなかった。 jqで対応した。

terraform show -json | jq '.values.root_module.resources[] | select(.address=="aws_route53_zone.main")'

全体のソース

main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 2.70"
    }
  }
}

provider "aws" {
  profile = var.profile
  region  = var.region
}

########################################
# ネットワーク
# - VPC
# - サブネット
# - インターネットゲートウェイ
# - ルーティング
########################################
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "udemy-terraform"
  }
}

resource "aws_subnet" "public_1a" {
  vpc_id                  = aws_vpc.main.id
  availability_zone       = "ap-northeast-1a"
  cidr_block              = "10.0.10.0/24"
  map_public_ip_on_launch = true
  tags = {
    Name = "terraform-public-1a"
  }
}

resource "aws_subnet" "public_1c" {
  vpc_id                  = aws_vpc.main.id
  availability_zone       = "ap-northeast-1c"
  cidr_block              = "10.0.11.0/24"
  map_public_ip_on_launch = true
  tags = {
    Name = "terraform-public-1c"
  }
}

resource "aws_subnet" "private-1a" {
  vpc_id            = aws_vpc.main.id
  availability_zone = "ap-northeast-1a"
  cidr_block        = "10.0.20.0/24"
  tags = {
    Name = "terraform-private-1a"
  }
}

resource "aws_subnet" "private-1c" {
  vpc_id            = aws_vpc.main.id
  availability_zone = "ap-northeast-1c"
  cidr_block        = "10.0.21.0/24"
  tags = {
    Name = "terraform-private-1c"
  }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
  tags = {
    Name = "terraform-igw"
  }
}

# ルートテーブル作成
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id
  tags = {
    Name = "terrform-public-route"
  }
}

# ルート設定
resource "aws_route" "public" {
  destination_cidr_block = "0.0.0.0/0"
  route_table_id         = aws_route_table.public.id
  gateway_id             = aws_internet_gateway.main.id
}

# ルートテーブルとサブネットの紐付け
resource "aws_route_table_association" "public_1a" {
  subnet_id      = aws_subnet.public_1a.id
  route_table_id = aws_route_table.public.id
}

resource "aws_route_table_association" "public_1c" {
  subnet_id      = aws_subnet.public_1c.id
  route_table_id = aws_route_table.public.id
}

########################################
# Webサーバー
# - キーペア
# - セキュリティグループ
# - Elastic IP
# - EC2
########################################

resource "aws_key_pair" "main" {
  key_name   = "terraform-web-key"
  public_key = file(var.public_key_path)
}

resource "aws_security_group" "terraform-web-security" {
  vpc_id = aws_vpc.main.id
  name   = "terraform-web-security"
}

resource "aws_security_group_rule" "inbound_ssh" {
  type              = "ingress"
  from_port         = 22
  to_port           = 22
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.terraform-web-security.id
}

resource "aws_security_group_rule" "inbound_http" {
  type              = "ingress"
  from_port         = 80
  to_port           = 80
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.terraform-web-security.id
}

resource "aws_security_group_rule" "outbound_allow_all" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.terraform-web-security.id
}

resource "aws_instance" "ec2" {
  ami                    = "ami-0992fc94ca0f1415a"
  instance_type          = "t2.micro"
  subnet_id              = aws_subnet.public_1a.id
  key_name               = aws_key_pair.main.id
  vpc_security_group_ids = [aws_security_group.terraform-web-security.id]
  private_ip             = "10.0.10.10"
  user_data              = <<EOS
  #!/bin/bash
  yum update -y
  yum install -y httpd
  yum install -y mysql
  systemctl start httpd.service
  EOS
  tags = {
    Name = "terraform-ec2-web"
  }
}

resource "aws_eip" "terraform_web" {
  instance = aws_instance.ec2.id
  vpc      = true
}

########################################
# DBサーバー
# - セキュリティグループ
# - DBサブネットグループ
# - パラメータグループ
# - オプショングループ
# - RDS
########################################

resource "aws_security_group" "terraform-db-security" {
  vpc_id = aws_vpc.main.id
  name   = "terraform-db-security"
}

resource "aws_security_group_rule" "inbound_mysql" {
  type                     = "ingress"
  from_port                = 3306
  to_port                  = 3306
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.terraform-web-security.id
  security_group_id        = aws_security_group.terraform-db-security.id
}

resource "aws_db_subnet_group" "main" {
  name       = "terraform-db-subnet-group"
  subnet_ids = [aws_subnet.private-1a.id, aws_subnet.private-1c.id]
  tags = {
    Name = "terraform-db-subnet-group"
  }
}

resource "aws_db_parameter_group" "terraform-mysql80" {
  name   = "terraform-mysql80"
  family = "mysql8.0"
}

resource "aws_db_option_group" "terraform-mysql80" {
  name                 = "terraform-mysql80"
  engine_name          = "mysql"
  major_engine_version = "8.0"
}

resource "aws_db_instance" "main" {
  allocated_storage = 20
  storage_type      = "gp2"
  engine            = "mysql"
  engine_version    = "8.0.15"
  instance_class    = "db.t2.micro"
  name              = "aws_terraform_db"
  identifier        = "terraform-web"
  # snapshotを削除時に生成すると、destroyによるオプショングループの削除が不可能になる
  # final_snapshot_identifier = "final-snapshot-terraform-web"
  skip_final_snapshot     = true
  username                = var.db_username
  password                = var.db_password
  parameter_group_name    = aws_db_parameter_group.terraform-mysql80.name
  option_group_name       = aws_db_option_group.terraform-mysql80.name
  max_allocated_storage   = 0
  vpc_security_group_ids  = [aws_security_group.terraform-db-security.id]
  db_subnet_group_name    = aws_db_subnet_group.main.name
  availability_zone       = "ap-northeast-1a"
  port                    = 3306
  backup_retention_period = 30
  backup_window           = "19:00-19:30"
  copy_tags_to_snapshot   = true
  maintenance_window      = "Sun:20:00-Sun:20:30"
}

########################################
# ドメイン
# - Route53
# - Aレコード登録
########################################
resource "aws_route53_zone" "main" {
  name = var.domain
}

resource "aws_route53_record" "A-record" {
  zone_id = aws_route53_zone.main.zone_id
  name    = aws_route53_zone.main.name
  type    = "A"
  alias {
    name                   = aws_lb.main.dns_name
    zone_id                = aws_lb.main.zone_id
    evaluate_target_health = true
  }
}

########################################
# ALB
# - EC2冗長化
# - セキュリティグループ
# - ターゲットグループ
# - ALB
########################################
resource "aws_instance" "ec2-2" {
  ami                    = "ami-0992fc94ca0f1415a"
  instance_type          = "t2.micro"
  subnet_id              = aws_subnet.public_1c.id
  key_name               = aws_key_pair.main.id
  vpc_security_group_ids = [aws_security_group.terraform-web-security.id]
  private_ip             = "10.0.11.10"
  user_data              = <<EOS
  #!/bin/bash
  yum update -y
  yum install -y httpd
  yum install -y mysql
  systemctl start httpd.service
  EOS
  tags = {
    Name = "terraform-ec2-web"
  }
}

resource "aws_security_group" "terraform-alb-security" {
  vpc_id = aws_vpc.main.id
  name   = "terraform-alb-security"
}

resource "aws_security_group_rule" "alb-inbound-http" {
  type              = "ingress"
  from_port         = 80
  to_port           = 80
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.terraform-alb-security.id
}
resource "aws_security_group_rule" "alb-allow-all" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.terraform-alb-security.id
}


resource "aws_lb_target_group" "main" {
  name        = "terraform-alb-tg"
  port        = 80
  protocol    = "HTTP"
  target_type = "instance"
  vpc_id      = aws_vpc.main.id
  health_check {
    path              = "/index.html"
    healthy_threshold = 2
    interval          = 10
  }
}

resource "aws_lb_target_group_attachment" "main" {
  target_group_arn = aws_lb_target_group.main.arn
  target_id        = aws_instance.ec2.id
  port             = 80
}

resource "aws_lb_target_group_attachment" "main2" {
  target_group_arn = aws_lb_target_group.main.arn
  target_id        = aws_instance.ec2-2.id
  port             = 80
}

resource "aws_lb_listener" "main" {
  load_balancer_arn = aws_lb.main.arn
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.main.arn
  }
}

resource "aws_lb" "main" {
  name               = "terraform-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.terraform-alb-security.id]
  subnets            = [aws_subnet.public_1a.id, aws_subnet.public_1c.id]
  tags = {
    Name = "terraform-alb"
  }
}

variables.tf

variable "profile" {}
variable "db_username" {}
variable "db_password" {}
variable "domain" {}

variable "region" {
  default = "ap-northeast-1"
}

variable "public_key_path" {
  default     = "~/.ssh/terraform.pub"
  description = <<EOS
Command: ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/you/.ssh/id_rsa): /Users/you/.ssh/terraform
Enter passphrase (empty for no passphrase): `empty`
Enter same passphrase again: `empty`
EOS
}

outputs.tf

output "elastic_ip" {
  value = aws_eip.terraform_web.public_ip
}

output "db_endpoint" {
  value = aws_db_instance.main.endpoint
}

output "name_servers" {
  value = aws_route53_zone.main.name_servers
}

output "public_ip" {
  value = aws_instance.ec2-2.public_ip
}

terraform.tfvars

profile     = "YOUR_AWS_PROFILE"
db_username = "YOUR_DB_USERNAMEJ"
db_password = "YOUR_DB_PASSWORD"
domain      = "YOUR_DOMAIN"