【AWS】Terraformを触って感じたことを述べる
今回は簡単なwebアプリケーション構成をTerraformで作成した感想を述べていきます。
メリット・デメリット両方ありましたが、クラウドという技術の真価を発揮させるために是非ともTerraformを習得したいなと感じました。
目次
はじめに
こんにちは。
クラウドソリューショングループのyoshikawa.kです。
今回のテーマは「インフラのコード化」(いわゆる IaC : Infrastructure as Code)についてです。
自分もAWSを触り始めて1年近く経過したのでそろそろTerraformを触る時期かなと思い、少し前から学習を始めました。
本記事はTerraformに興味はあるが中々手が出ない方向けに、「インフラをコード化するってどういうこと?」という疑問を少しでも解決する記事です。
Terraformとは
TerraformはAWSやGCP、Azureなどクラウドサービスのインフラリソースを、コードで作成出来るようにするツールです。
コードでインフラリソースを記述したものをファイル化し、TerraformがインストールされているCUI環境にてterraformコマンドを実行すると、高速でインフラリソースを作成することができます。
と言っても実感が湧かないと思いますので、画像で比較してみましょう。
下記の画像は皆さんお馴染みのAWSの管理画面からサブネットを作成する様子です。

AWSの管理画面からサブネットを作成する様子
一方で、下記の画像は作成予定のサブネットをコードにしたものです。
サブネットの設定をコードで記述し、CUI環境でterraformコマンドを実行することで、AWSの管理画面と同じように目的のサブネットを作成することができます。

作成予定のサブネットをコードに記述した様子
AWSで早速やってみた
今回は以下の構成図で作成してみました。
シンプルなWebアプリケーション構成で、これくらいであれば慣れていると30分もあればAWSの管理画面で作成できます。
実際のコード
作成はTerrafromの公式ドキュメントなどを参照しました。
カーソルをコード上にかざすとファイル名が表示され、スクロースするとコードが最後まで確認できます。
#========================================================= #リージョンの指定 provider "aws" { region = "ap-northeast-1" }
#========================================================= #VPCの作成 resource "aws_vpc" "dev-vpc-main" { cidr_block = "10.0.0.0/16" enable_dns_hostnames = true } #========================================================= #サブネットの作成 resource "aws_subnet" "dev-subnet-public-1" { vpc_id = aws_vpc.dev-vpc-main.id cidr_block = "10.0.1.0/24" availability_zone = "ap-northeast-1a" map_public_ip_on_launch = true } resource "aws_subnet" "dev-subnet-public-2" { vpc_id = aws_vpc.dev-vpc-main.id cidr_block = "10.0.2.0/24" availability_zone = "ap-northeast-1c" map_public_ip_on_launch = true<br>} resource "aws_subnet" "dev-subnet-private-1" { vpc_id = aws_vpc.dev-vpc-main.id cidr_block = "10.0.11.0/24" availability_zone = "ap-northeast-1a" } resource "aws_subnet" "dev-subnet-private-2" { vpc_id = aws_vpc.dev-vpc-main.id cidr_block = "10.0.12.0/24" availability_zone = "ap-northeast-1c" } #========================================================= #インターネットゲートウェイの作成 resource "aws_internet_gateway" "dev-igw-main" { vpc_id = aws_vpc.dev-vpc-main.id } #========================================================= #ルートテーブルの作成 resource "aws_route_table" "dev-rtb-public" { vpc_id = aws_vpc.dev-vpc-main.id route { cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.dev-igw-main.id } } resource "aws_route_table" "dev-rtb-private" { vpc_id = aws_vpc.dev-vpc-main.id } #========================================================= #ルートテーブルをサブネットに設定 resource "aws_route_table_association" "public-1" { subnet_id = aws_subnet.dev-subnet-public-1.id route_table_id = aws_route_table.dev-rtb-public.id } resource "aws_route_table_association" "public-2" { subnet_id = aws_subnet.dev-subnet-public-2.id route_table_id = aws_route_table.dev-rtb-public.id } resource "aws_route_table_association" "private-1" { subnet_id = aws_subnet.dev-subnet-private-1.id route_table_id = aws_route_table.dev-rtb-private.id } resource "aws_route_table_association" "private-2" { subnet_id = aws_subnet.dev-subnet-private-2.id route_table_id = aws_route_table.dev-rtb-private.id } #========================================================= #SGの作成 resource "aws_security_group" "dev-sg-elb" { name = "dev-sg-elb" vpc_id = aws_vpc.dev-vpc-main.id ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["xxx.xxx.xxx.xxx/32"] } egress { from_port = 80 to_port = 80 protocol = "tcp" security_groups = [aws_security_group.dev-sg-ec2.id] } } resource "aws_security_group" "dev-sg-ec2" { name = "dev-sg-ec2" vpc_id = aws_vpc.dev-vpc-main.id } resource "aws_security_group_rule" "dev-sg-ec2-rule-1" { type = "ingress" from_port = 80 to_port = 80 protocol = "tcp" source_security_group_id = aws_security_group.dev-sg-elb.id security_group_id = aws_security_group.dev-sg-ec2.id } resource "aws_security_group_rule" "dev-sg-ec2-rule-2" { type = "egress" from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] security_group_id = aws_security_group.dev-sg-ec2.id } resource "aws_security_group" "dev-sg-rds" { name = "dev-sg-rds" vpc_id = aws_vpc.dev-vpc-main.id ingress { from_port = 3306 to_port = 3306 protocol = "tcp" security_groups = [aws_security_group.dev-sg-ec2.id] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } }
#========================================================= #ALBの作成 resource "aws_lb" "dev-elb-alb" { name = "dev-elb-alb" internal = false load_balancer_type = "application" security_groups = [ aws_security_group.dev-sg-elb.id ] subnets = [ aws_subnet.dev-subnet-public-1.id, aws_subnet.dev-subnet-public-2.id, ] } #========================================================= #ALBリスナーの作成 resource "aws_lb_listener" "dev-elb-alb" { load_balancer_arn = aws_lb.dev-elb-alb.arn port = "80" protocol = "HTTP" default_action { type = "forward" target_group_arn = aws_lb_target_group.dev-tg-elb.arn } } #========================================================= #リスナールールの作成 resource "aws_lb_listener_rule" "forward" { listener_arn = aws_lb_listener.dev-elb-alb.arn priority = 99 action { type = "forward" target_group_arn = aws_lb_target_group.dev-tg-elb.arn } condition { path_pattern { values = ["/*"] } } } #========================================================= #TGの作成 resource "aws_lb_target_group" "dev-tg-elb" { name = "dev-tg-elb" port = 80 protocol = "HTTP" vpc_id = aws_vpc.dev-vpc-main.id health_check { path = "/" } } #========================================================= #インスタンスにTGを設定 resource "aws_lb_target_group_attachment" "dev-ec2-ap-1" { target_group_arn = aws_lb_target_group.dev-tg-elb.arn target_id = aws_instance.dev-ec2-ap-1.id port = 80 } resource "aws_lb_target_group_attachment" "dev-ec2-ap-2" { target_group_arn = aws_lb_target_group.dev-tg-elb.arn target_id = aws_instance.dev-ec2-ap-2.id port = 80 }
#========================================================= #ec2の作成 resource "aws_instance" "dev-ec2-ap-1" { ami = "ami-09d28faae2e9e7138" instance_type = "t2.micro" subnet_id = aws_subnet.dev-subnet-public-1.id vpc_security_group_ids = [aws_security_group.dev-sg-ec2.id] user_data = <<EOF #! /bin/bash sudo yum install -y httpd sudo systemctl start httpd sudo systemctl enable httpd EOF } resource "aws_instance" "dev-ec2-ap-2" { ami = "ami-09d28faae2e9e7138" instance_type = "t2.micro" subnet_id = aws_subnet.dev-subnet-public-2.id vpc_security_group_ids = [aws_security_group.dev-sg-ec2.id] user_data = <<EOF #! /bin/bash sudo yum install -y httpd sudo systemctl start httpd sudo systemctl enable httpd EOF }
#========================================================= #RDSの作成 resource "aws_db_instance" "dev-rds-db" { identifier = "dev-rds-db" db_name = "test" allocated_storage = 20 storage_type = "gp2" engine = "mysql" engine_version = "5.7" instance_class = "db.t3.medium" username = "hogehoge" password = "examplepw" db_subnet_group_name = aws_db_subnet_group.dev-subnet-group.name skip_final_snapshot = true vpc_security_group_ids = [aws_security_group.dev-sg-rds.id] } #========================================================= #DBサブネットグループの作成 resource "aws_db_subnet_group" "dev-subnet-group" { name = "dev-subnet-group" subnet_ids = [aws_subnet.dev-subnet-private-1.id, aws_subnet.dev-subnet-private-2.id] }
AWSでの利用手順
ポイントとなる部分のみを記載します。
- 上記のコードを「*.tf」ファイルとして各種作成する
- AWSの管理画面からCloud9を立ち上げ、「~/environment」に上記ファイルを全てアップロード・配置する
- コマンドを「terraform init」「terraform validate」「terraform apply」の順に実行し、「terraform apply」の途中で「yes」を入力する。しばらく待つとリソースの作成が完了する
- リソースを削除するときは「terraform destroy」を実行し、「yes」を入力し削除する
以上です。今回はデフォルトでterraformがインストールされているCloud9を利用しましたが、自分で環境を用意できるならローカルでもEC2インスタンスでも大丈夫です。
Terraformに触って感じたメリット
■同じ構成の環境をいくつも短時間で複製できる
インフラをコード化する一番のメリットはこれかなと思います。
実際の利用時には開発環境や本番環境など目的によってリソースに付与する名称等を変更することが多いです。
しかし大部分の設定は使いまわすことが可能で、労力自体は大幅に削減出来ます。
また手順書等を用意する必要がなく、設定も全てコードで管理されているので属人性を排除出来ます。
■AWSやGCP、Azureをより深く理解できる
管理画面ではデフォルトで設定されていたものを、コードで記述する際はわざわざ明示的に設定しなければならないことも多いです。
その際に意識していなかった設定を改めて調べ直す機会にもなり、各種サービスのより深い理解に繋げられます。
■スキルの客観的証明
AWSやGCP、Azureの管理画面では中々クラウド技術の証明は難しかったですが、コードで記述することで誰でも簡単にクラウドスキルを証明することが可能になりました。
また一度書いたコードでもアップデートして、随時自分のスキルセットを追加で証明することができます。
Terraformに触って感じたデメリット
■慣れるまで難しい
AWSの管理画面だと、今回作成した構成図も30分程で作成できます。
ですが今回はTerraformに慣れていないこともあり、上記のコードを記述し、エラー無くAWS上で起動するまでに4~5時間程かかりました。
コードをどうやって記述すればいいのか調べるのはもちろん、実際にコマンドで実行すると何度もエラーを吐かれたので修正するのに結構苦労しました。
業務で活用するレベルに持っていくには力を入れて学習する必要があります。
■いきなりTerraformから学習するのは現実的でない
いきなりAWSやGCP、Azureの学習を飛ばして、Terraformから学習を始めるのはかなり厳しいです。
最初はGUIでAWSやGCP、Azureのことを理解し、それらの知識をベースに徐々にインフラをコード化するのが王道です。
Terraformが流行っているからと、いきなり手を出すのは挫折するきっかけになるかもしれません。
■小規模で構築する環境の数が少ないなら管理画面から作成する方が早い(かも)
これはコードの書き方を調べたり、エラー対処の労力がそれなりに掛かるからです。
大規模で同じ環境を複数作成するのであればコード化のメリットは大きくなりますが、そうでなければ普通に管理画面から作成した方が早いと思われます。
■解説記事が少ない(日本語の記事がほとんどない)
AWSのVPCやEC2などよく使われるサービスならそこそこ解説記事がありますが、少しマイナーなサービスになるとTerraformの解説記事はほとんどありません。
現状では公式ドキュメントや海外のサイトを上手く使う必要があるでしょう。
まとめ
まだまだ自分もTerraform初心者ですが、簡単な構成をコード化し実行するだけでも色々な発見がありました。
デメリットもいくつか書きましたが、要は「最初の学習が難しい」ということであり、参入障壁が高くまだまだ使えるエンジニアが少ないです。
だからこそ使いこなせるようになれば、インフラをコード化するメリットと相まって非常に需要が高いスキルだと思います。
Terraformの本も数は少ないですが、何冊か評判のいい良書があるみたいですので、体系的に知識を獲得すると効率よく勉強できるのではないでしょうか。