社内向けのTerraformハンズオン勉強会用資料として作成
もくじ
Terraform導入メリット
技術負債を軽減するためのもの
- クラウドのデザインが変わる問題の解消
スクショ手順書が死ぬ脆弱性を回避できる - develop, staging productionと環境でのリソース作成漏れ防止 … 障害防止
- 再現、デプロイがスムーズ
- TerraformのコードとAWSの実態が=になる。実装がドキュメントになる
- プログラムと同じようにレビューができるようになる
- GitLabと組み合わせて履歴、バージョン管理ができる
- リソースの検索がVS Codeで簡単にできる
AWSみたいにページ遷移しての確認が不要。設定のプロパティの一覧性もファイル内で完結。
ハンズオンの目的
なんだか難しそう…。
→
結構簡単かも!?🐱 ✨
作れてからなんで動くのか考える
完成するもの
事前準備
MacにAWS Cliインストール
$ sudo easy_install pip $ sudo pip install awscli
aws cliのprofile 設定
事前に「hanson-terraform-deployer」なるIAMユーザを作っておきました。AdministratorAccess権限が付与されています。
こちらのユーザをMacに登録します
% aws configure --profile hanson-terraform-deployer AWS Access Key ID [None]: {シークレットキーID} AWS Secret Access Key [None]: {シークレットキー} Default region name [None]: ap-northeast-1 Default output format [None]: json
これで設定できました。
確認する場合
% cat ~/.aws/credentials [hanson-terraform-deployer] aws_access_key_id = {シークレットキーID} aws_secret_access_key = {シークレットキー}
MacにTerraformのインストール
Terraformのインストール
$ brew install tfenv
$ tfenv -v tfenv 2.2.2
安定している1.0.11を使います。
$ tfenv list-remote 1.1.0-beta1 1.1.0-alpha20211029 1.1.0-alpha20211020 1.1.0-alpha20211006 1.1.0-alpha20210922 1.1.0-alpha20210908 1.1.0-alpha20210811 1.1.0-alpha20210728 1.1.0-alpha20210714 1.1.0-alpha20210630 1.1.0-alpha20210616 1.0.11 1.0.10 1.0.9 1.0.8
1.0.11インストール $ tfenv install 1.0.11 1.0.11を使う $ tfenv use 1.0.11 Switching default version to v1.0.11 Switching completed
これでMacにTerraformが入りました。
VSCodeの拡張機能をインストール
AWSコンソールにログイン
コンソール画面にログインできることを確認してください。
Terraformで作ったリソースを確かめることができます!
ハンズオン開始!
こんなディレクトリ構造になります。
ハンズオン用リポジトリをgit cloneでダウンロードします。
$ git clone https://github.com/yuukanehiro/bs-terraform-hanson2022-02.git
tfstateを管理するS3バケットの作成
事前にtfstateを格納するS3バケット「tfstate-handson-484711902108」は手動で作っておきましたので、
ショートカットします。
ディレクトリを作成して、作成したディレクトリに移動します
$ mkdir -p /providers/aws/environments/test/{あなたの名前} $ cd /providers/aws/environments/test/{あなたの名前}/
そこがあなたの作業ディレクトリになります。
変数ファイル作成
terraform.tfvars
aws_region = "ap-northeast-1" aws_profile = "hanson-terraform-deployer" ENV_VALUE_ENVIRONMENT = "test" TAG_PROJECT = "terraform-handson" TAG_ROLE = "handon" TAG_AUTHOR = "yuu3" # 🔥 自分の名前にしてください 例) tanaka
main.tfファイル作成
terraform { required_version = "= 1.1.1" backend "s3" { bucket = "tfstate-handson-12345678" # S3のバケット。事前に作ってあります。 region = "ap-northeast-1" key = "{🔥ここに名前を英数入力お願いします}/terraform.tfstate" # 例) "yuu3/terraform.tfstate" profile = "hanson-terraform-deployer" # 🐱 AWS Cliで設定したIAMユーザのプロフィール名 } } variable aws_region {} variable aws_profile {} variable ENV_VALUE_ENVIRONMENT {} variable TAG_PROJECT {} variable TAG_ROLE {} variable TAG_AUTHOR {} provider "aws" { region = var.aws_region profile = var.aws_profile default_tags { tags = { env = var.ENV_VALUE_ENVIRONMENT project = var.TAG_PROJECT role = var.TAG_ROLE author = var.TAG_AUTHOR } } }
初期化します。
% terraform init Initializing the backend... Successfully configured the backend "s3"! Terraform will automatically use this backend unless the backend configuration changes. Initializing provider plugins... - Finding latest version of hashicorp/aws... - Installing hashicorp/aws v3.74.0... - Installed hashicorp/aws v3.74.0 (signed by HashiCorp) Terraform has created a lock file .terraform.lock.hcl to record the provider selections it made above. Include this file in your version control repository so that Terraform can guarantee to make the same selections by default when you run "terraform init" in the future. Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary.
VPCの作成
vpc.tf作成
resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" }
terraform planによる実行計画の確認
% terraform plan Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_vpc.main will be created + resource "aws_vpc" "main" { + arn = (known after apply) + cidr_block = "10.0.0.0/16" + default_network_acl_id = (known after apply) + default_route_table_id = (known after apply) + default_security_group_id = (known after apply) + dhcp_options_id = (known after apply) + enable_classiclink = (known after apply) + enable_classiclink_dns_support = (known after apply) + enable_dns_hostnames = (known after apply) + enable_dns_support = true + id = (known after apply) + instance_tenancy = "default" + ipv6_association_id = (known after apply) + ipv6_cidr_block = (known after apply) + ipv6_cidr_block_network_border_group = (known after apply) + main_route_table_id = (known after apply) + owner_id = (known after apply) + tags_all = { + "author" = "kanehiro" + "env" = "test" + "project" = "terraform-handson" + "role" = "handon" } } Plan: 1 to add, 0 to change, 0 to destroy. ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
terraform applyによる実行
% terraform apply Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_vpc.main will be created + resource "aws_vpc" "main" { + arn = (known after apply) + cidr_block = "10.0.0.0/16" + default_network_acl_id = (known after apply) + default_route_table_id = (known after apply) + default_security_group_id = (known after apply) + dhcp_options_id = (known after apply) + enable_classiclink = (known after apply) + enable_classiclink_dns_support = (known after apply) + enable_dns_hostnames = (known after apply) + enable_dns_support = true + id = (known after apply) + instance_tenancy = "default" + ipv6_association_id = (known after apply) + ipv6_cidr_block = (known after apply) + ipv6_cidr_block_network_border_group = (known after apply) + main_route_table_id = (known after apply) + owner_id = (known after apply) + tags_all = { + "author" = "kanehiro" + "env" = "test" + "project" = "terraform-handson" + "role" = "handon" } } Plan: 1 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes 🔥 yesと入力してEnterしてください aws_vpc.main: Creating... aws_vpc.main: Creation complete after 2s [id=vpc-08624fd5aa20406d0] Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
あなたのVPCが作られたことを確認してください。
authorタグから確認できます。
サブネットpublic_1aの作成
subnet.tf
# Subnet # https://www.terraform.io/docs/providers/aws/r/subnet.html resource "aws_subnet" "public_1a" { # 先程作成したVPCを参照し、そのVPC内にSubnetを立てる vpc_id = aws_vpc.main.id # 🐱 先ほど作ったVPCのIDを動的に指定しています。 # Subnetを作成するAZ availability_zone = "ap-northeast-1a" cidr_block = "10.0.1.0/24" tags = { Name = "handson-public-1a" } depends_on = [aws_vpc.main] # 🐱 依存関係を指定しています。vpcがある前提であることを明示しています。同時にリソースを作る際に重要 }
実行計画立てて確認しましょう。
% terraform plan aws_vpc.main: Refreshing state... [id=vpc-08624fd5aa20406d0] Note: Objects have changed outside of Terraform Terraform detected the following changes made outside of Terraform since the last "terraform apply": # aws_vpc.main has changed ~ resource "aws_vpc" "main" { id = "vpc-08624fd5aa20406d0" + tags = {} # (16 unchanged attributes hidden) } Unless you have made equivalent changes to your configuration, or ignored the relevant attributes using ignore_changes, the following plan may include actions to undo or respond to these changes. ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_subnet.public_1a will be created + resource "aws_subnet" "public_1a" { + arn = (known after apply) + assign_ipv6_address_on_creation = false + availability_zone = "ap-northeast-1a" + availability_zone_id = (known after apply) + cidr_block = "10.0.1.0/24" + enable_dns64 = false + enable_resource_name_dns_a_record_on_launch = false + enable_resource_name_dns_aaaa_record_on_launch = false + id = (known after apply) + ipv6_cidr_block_association_id = (known after apply) + ipv6_native = false + map_public_ip_on_launch = false + owner_id = (known after apply) + private_dns_hostname_type_on_launch = (known after apply) + tags = { + "Name" = "handson-public-1a" } + tags_all = { + "Name" = "handson-public-1a" + "author" = "kanehiro" + "env" = "test" + "project" = "terraform-handson" + "role" = "handon" } + vpc_id = "vpc-08624fd5aa20406d0" } Plan: 1 to add, 0 to change, 0 to destroy. ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
問題なければ実行しましょう。
% terraform apply aws_vpc.main: Refreshing state... [id=vpc-08624fd5aa20406d0] Note: Objects have changed outside of Terraform Terraform detected the following changes made outside of Terraform since the last "terraform apply": # aws_vpc.main has changed ~ resource "aws_vpc" "main" { id = "vpc-08624fd5aa20406d0" + tags = {} # (16 unchanged attributes hidden) } Unless you have made equivalent changes to your configuration, or ignored the relevant attributes using ignore_changes, the following plan may include actions to undo or respond to these changes. ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_subnet.public_1a will be created + resource "aws_subnet" "public_1a" { + arn = (known after apply) + assign_ipv6_address_on_creation = false + availability_zone = "ap-northeast-1a" + availability_zone_id = (known after apply) + cidr_block = "10.0.1.0/24" + enable_dns64 = false + enable_resource_name_dns_a_record_on_launch = false + enable_resource_name_dns_aaaa_record_on_launch = false + id = (known after apply) + ipv6_cidr_block_association_id = (known after apply) + ipv6_native = false + map_public_ip_on_launch = false + owner_id = (known after apply) + private_dns_hostname_type_on_launch = (known after apply) + tags = { + "Name" = "handson-public-1a" } + tags_all = { + "Name" = "handson-public-1a" + "author" = "kanehiro" + "env" = "test" + "project" = "terraform-handson" + "role" = "handon" } + vpc_id = "vpc-08624fd5aa20406d0" } Plan: 1 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes 🔥 yesと入力 aws_subnet.public_1a: Creating... aws_subnet.public_1a: Creation complete after 1s [id=subnet-04770c2b153ca7738] Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
subnet.tfにもう1つサブネットpublic_1cを作ろう
subnet.tf
# Subnet # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet resource "aws_subnet" "public_1a" { # 先程作成したVPCを参照し、そのVPC内にSubnetを立てる vpc_id = aws_vpc.main.id # 🐱 先ほど作ったVPCのIDを動的に指定しています # Subnetを作成するAZ availability_zone = "ap-northeast-1a" cidr_block = "10.0.1.0/24" tags = { Name = "handson-public-1a" } depends_on = [aws_vpc.main] # 🐱 依存関係を指定しています。vpcがある前提であることを明示しています。同時にリソースを作る際に重要 } # 🔥🔥🔥 下記を追加 resource "aws_subnet" "public_1c" { vpc_id = aws_vpc.main.id availability_zone = "ap-northeast-1c" cidr_block = "10.0.2.0/24" tags = { Name = "handson-public-1c" } }
$ terraform plan $ terraform apply
Internet Gatewayを作ろう
internet_gateway.tf
# Internet Gateway # https://www.terraform.io/docs/providers/aws/r/internet_gateway.html resource "aws_internet_gateway" "main" { vpc_id = aws_vpc.main.id tags = { Name = "handson-" } }
$ terraform plan $ terraform apply
Route Tableを作ろう
route_table.tf
# Route Table # https://www.terraform.io/docs/providers/aws/r/route_table.html resource "aws_route_table" "public" { vpc_id = aws_vpc.main.id tags = { Name = "handson-public" } } # Route # https://www.terraform.io/docs/providers/aws/r/route.html 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 } # Association # https://www.terraform.io/docs/providers/aws/r/route_table_association.html resource "aws_route_table_association" "public_1a" { subnet_id = aws_subnet.public_1a.id route_table_id = aws_route_table.public.id depends_on = [aws_route_table.public] } resource "aws_route_table_association" "public_1c" { subnet_id = aws_subnet.public_1c.id route_table_id = aws_route_table.public.id depends_on = [aws_route_table.public] }
$ terraform plan $ terraform apply
セキュリティグループを作ろう
sg.tf
# SecurityGroup # https://www.terraform.io/docs/providers/aws/r/security_group.html resource "aws_security_group" "http" { name = "handson-alb" description = "handson alb" vpc_id = aws_vpc.main.id # セキュリティグループ内のリソースからインターネットへのアクセスを許可する egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "handson-http" } } # SecurityGroup Rule # https://www.terraform.io/docs/providers/aws/r/security_group.html resource "aws_security_group_rule" "http" { security_group_id = aws_security_group.http.id # セキュリティグループ内のリソースへインターネットからのアクセスを許可する type = "ingress" from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] }
$ terraform plan $ terraform apply
EC2を作ろう
variable.tf
variable "ami_id_demo" { default = "ami-0acaef956254d73c1" # 事前にWebサーバ(Nginx)のAMI作っておきました type = string }
terraform.tfvars以外にもvariableディレクティブで定義できます。
ec2.tf
resource "aws_instance" "web1" { ami = var.ami_id_demo instance_type = "t2.nano" monitoring = true vpc_security_group_ids = [aws_security_group.http.id] subnet_id = aws_subnet.public_1a.id } resource "aws_instance" "web2" { ami = var.ami_id_demo instance_type = "t2.nano" monitoring = true vpc_security_group_ids = [aws_security_group.http.id] subnet_id = aws_subnet.public_1c.id }
$ terraform plan $ terraform apply
ALBを作ろう
alb.tf
# ALB # https://www.terraform.io/docs/providers/aws/d/lb.html resource "aws_lb" "main" { load_balancer_type = "application" name = "alb-${var.TAG_AUTHOR}" security_groups = [aws_security_group.http.id] subnets = [ aws_subnet.public_1a.id, aws_subnet.public_1c.id ] } # TargetGroup resource "aws_lb_target_group" "main" { name = "alb-${var.TAG_AUTHOR}-tg" port = 80 protocol = "HTTP" vpc_id = aws_vpc.main.id } resource "aws_lb_target_group_attachment" "web1" { target_group_arn = aws_lb_target_group.main.arn target_id = aws_instance.web1.id port = 80 depends_on = [aws_instance.web1] } resource "aws_lb_target_group_attachment" "web2" { target_group_arn = aws_lb_target_group.main.arn target_id = aws_instance.web2.id port = 80 depends_on = [aws_instance.web2] } # Listener # https://www.terraform.io/docs/providers/aws/r/lb_listener.html resource "aws_lb_listener" "main" { # HTTPでのアクセスを受け付ける port = "80" protocol = "HTTP" # ALBのarnを指定します。 #XXX: arnはAmazon Resource Names の略で、その名の通りリソースを特定するための一意な名前(id)です。 load_balancer_arn = aws_lb.main.arn default_action { type = "forward" target_group_arn = aws_lb_target_group.main.arn } depends_on = [ aws_lb.main, aws_lb_target_group.main ] }
$ terraform plan $ terraform apply
確認
AWSコンソールからロードバランサーのページに行きます。
説明からDNS名欄でオレンジのリンクをクリックするとコピーされます。
ブラウザのURL欄に貼り付けて、Test Pageが表示されたら成功です!
お疲れ様でした!
最後はお片づけ
えーい、バルス!
$ terraform destroy
// develop, staging, production環境では禁止です。。