AWS Terraform を書く上で基本的なコードをまとめてみました。
読者の皆様にお役立てれば幸いです。
環境は以下
| OS | Cataline 10.15.6 |
| Terraform | 0.14.4 |
terraformのインストール方法についてはこちらをご覧ください。
Terraform Macにインストールする方法
AWS Terraform 基本コードまとめ
AWS Terraform のドキュメント
https://registry.terraform.io/providers/hashicorp/aws/latest/docs
各AWSのリソースに対してサンプルなどあるので、見てみると良いかもです。
基本インフラ構成 (Block)
インフラのリソースはBlockと呼ばれる単位で作成していきます。
resource "aws_vpc" "vpc" {
cidr_block = "10.0.0.0/16"
}
この外輪枠の{}の中身が、リソースを構築するブロックになります。
上記はresouce ブロックと呼ばれ、
aws_vpcがAWSのリソース名、vpcがローカルでの変数名になります。
resource以外にも、variable, output, locals, module, data source などがあります。
順を追って説明したいと思います。
resource ブロック
実際にどのようなインフラのリソースを作成するか定義するブロックになります。
このブロックで、VPC や EC2インスタンスを定義してインフラを構築していきます。
例. VPC の定義は以下のようになります。
resource "aws_vpc" "vpc" {
cidr_block = "10.0.0.0/16"
}
コードを見るとなんとなくわかると思いますが、
CIDR が 10.0.0.0/16 の VPC を定義しています。
引数に cidr_block がありますが、これは定義するリソースによって変わるので注意してください。
rescource を流用する
基本、他で定義している resource 作成の返り値を使用して resource の定義をしているのでこちらの書き方も見ておきましょう。
例. 作成した vpc に public subnet を定義してみましょう。
resource "aws_vpc" "vpc" {
cidr_block = "10.0.0.0/16"
}
vpc を定義して、、
resource "aws_subnet" "subnet"{
vpc_id = aws_vpc.vpc.id # <リソース名>.<ローカル名>.id
availability_zone = "ap-northeast-1a"
cidr_block = "10.0.0.0/24"
map_public_ip_on_launch = true
}
subnet を作成するには vpc id が必要なので aws_vpc で定義した結果を使用します。
id を取得したい場合は、<リース名>.<ローカル名>.id になります。idの他にも arn などがあります。
variable ブロック
これは、インプット変数とも呼ばれるように変数を定義するブロックです。
リソースの変数をハードコードせず、定義できます。
例. 変数 vpc_cidr の定義は以下のようになります。
variable "vpc_cidr" {
type = string
description = "This cidr is vpc cidr"
default = "10.0.0.0/16"
}
- 引数
- type → ブロックの変数の型を指定する。他にnumber・bool・list・map がある。
省略化だがしないことをオススメします。 - description → 変数の説明
- default → 変数の値
- type → ブロックの変数の型を指定する。他にnumber・bool・list・map がある。
この変数は, var.vpc_cidr と指定することで使用可能です。
resource "aws_vpc" "vpc" {
cidr_block = var.vpc_cidr
}
variable で定義された変数は、plan, apply 時のオプションで上書きできます。
$ terraform apply -var="vpc_cidr=10.0.0.0/16"
variable ドキュメント
https://www.terraform.io/docs/language/values/variables.html
output ブロック
variable とは逆で、出力変数を定義するブロックです。
terraform apply 時にターミナルなど確認できます。
例. 作成した VPC の Id を表示する。
output "vpc_id" {
value = aws_vpc.vpc.id
description = "This id is vpc id"
}
- 引数
- value → 表示する値
- description → 変数の説明
$ terraform apply
...
Outputs:
vpc_id = "vpc-0ef22***"
output ドキュメント
https://www.terraform.io/docs/language/values/outputs.html
locals ブロック
variable 同様、変数を定義するブロックです。
ただ、locals ブロックで定義した変数は上書きすることができなくなります。
例. 変数 vpc_cidr を定義は以下のようになります。
locals {
vpc_cidr = "10.0.0.0/16"
}
この変数は, local.vpc_cidr と指定することで使用可能です。
resource "aws_vpc" "vpc" {
cidr_block = local.vpc_cidr
}
locals ドキュメント
https://www.terraform.io/docs/language/values/locals.html
module ブロック
Terraformの構成をModule化することで再利用が可能になるブロックです。
外部のModuleも使用可能になります。
例. EC2 インスタンスを定義している部分を module 化して、利用してみる。
フォルダ構成
$ tree . ├── main.tf ├── modules │ └── ec2 │ ├── main.tf
./main.tf
module "ec2" {
source = "./modules/ec2"
}
./modules/ec2/main.tf
data "aws_ami" "amazon_linux_2" {
most_recent = true
owners = ["amazon"]
filter {
name = "owner-alias"
values = ["amazon"]
}
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-ebs"]
}
}
variable "module_instance_type" {
default = "t2.micro"
}
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux_2.id
instance_type = var.module_instance_type
}
data source ブロック
自分のterraform module 外に定義されてあるリソース値を plan, apply 時にフェッチするブロックです。
AWS の amiやiam_policy などを取得できます。
例. 最新の ami を取得してみます。
data "aws_ami" "amazon_linux_2" {
most_recent = true
owners = ["amazon"]
filter {
name = "owner-alias"
values = ["amazon"]
}
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-ebs"]
}
}
- 引数
- most_recent → 検索結果が複数ある場合、最新AMIを返すかどうか。
- owners → AMIの所属の指定 (ex. amazon, aws-marketplace, …)
- filter → 検索条件
data socurce ドキュメント
https://www.terraform.io/docs/language/data-sources/index.html
aws_iam data source ドキュメント
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami
基本コード記述
ここで、よく見るインフラコードの記述方法をまとめてみます。
Expression Value
variable ブロックで少しちらっと出てきましたが、number・bool・list・mapの紹介です。
number
数値です。他の言語でいうところの、int, integerとかですね。
以下のように使われます。
variable "instance_count" {
type = number
default = 2
}
bool
ブール値。論理値です。他の言語でいうところの、booleanとかですね。
以下のように使われます。
variable "create_vpc" {
type = bool
default = true
}
Terraformでは三項演算子も使用可能です。
resouce "aws_vpc" "vpc" {
count = var.create_vpc == true ? 1: 0
cidr_block = "10.0.0.0/16"
}
変数create_vpc がtrueなら1個のVPCを、falseなら0個のVPCを作成する(作成しない)ように定義できます。※countはこちらで説明しています。
list(<TYPE>)
配列です。他の言語でいうところの、array, ArrayListですね。
以下のように使われます。
variable "public_subnet_cidrs" {
type = list(string)
default = ["10.0.0.0/24", "10.0.1.0/24", "10.0.2.0/24"]
}
type を見たらわかると思いますが、文字列の配列は list(string) になります。
数値だと、list(number) になりますね。
variable "ingress_ports" {
type = list(number)
default = [80, 443]
}
map
連想配列です。他の言語でいうところの、Hash, HashMap ですね。
variable "amis" {
type = map(string)
default = {
us-east-1 = "ami-0dc185deadd3ac449"
us-west-2 = "ami-014612c2d9afaf1ac"
ap-northeast-1 = "ami-01748a72bed07727c"
}
}
参照方法は、lookup(<map>, <key>) を使用します。
resource "aws_instance", "ec2" {
ami = lookup(var.amis, "ap-northeast-1")
instance_type = "t2.micro"
}
key: ap-northeast-1 の value: ami-01748a72bed07727c を取得できます。
count
リソースを任意の数分、動的に作成できるメタ引数(meta-argument)です。
どの resourceブロック にも使えて同じようなものを複数書かず一つのブロックで書けますので便利なものです。
例. cidrアドレスが[“10.0.0.0/24”, “10.0.1.0/24”, “10.0.2.0/24”]のパブリックサブネットを、count を用いた記述と用いない記述を紹介します。
インプット変数と vpc を定義しておきます。
variable "vpc_cidr" {
type = string
default = "10.0.0.0/16"
}
variable "public_subnet_cidrs" {
type = list(string)
default = ["10.0.0.0/24", "10.0.1.0/24", "10.0.2.0/24"]
}
variable "azs" {
type = list(string)
default = ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"]
}
# VPCの定義
resource "aws_vpc "vpc" {
cidr_block = var.vpc_cidr"
}
それでは各記述方法を見ていきましょう。
用いない
# subnetの定義
resource "aws_subnet" "public_1a" {
vpc_id = aws_vpc.vpc.id
cidr_block = "10.0.0.0/24"
availability_zone = "ap-northeast-1a"
map_public_ip_on_launch = true
}
resource "aws_subnet" "public_1b" {
vpc_id = aws_vpc.vpc.id
cidr_block = "10.0.1.0/24"
availability_zone = "ap-northeast-1b"
map_public_ip_on_launch = true
}
resource "aws_subnet" "public_1d" {
vpc_id = aws_vpc.example.id
cidr_block = "10.0.2.0/24"
availability_zone = "ap-northeast-1d"
map_public_ip_on_launch = true
}
上記のように、3つの resource ブロック を用いて記述しています。
これだと、可読性に欠けますし Human error も起こりやすくなります。
用いる
resource "aws_subnet" "public_subnets" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.vpc.id
cidr_block = var.public_subnet.cidrs[count.index]
availability_zone = var.azs[count.index]
map_public_ip_on_launch = true
}
上記のように1ブロックで定義できます。
length関数でpublic_subnet_cidrsの数を取得して、その数分繰り返します。
count.index で現在の実行回数を取得でき、それをインデックスとして配列から値を取得してリソースを定義します。
dynamic
こちらも count のように動的にリソースを作成できるものです。
これもどの resourceブロック に使えますがこれを多用しすぎると、構成の読み取りと保守が困難になる可能性があるため、詳細を非表示にする必要がある場合にのみブロックを使用することをお勧めします。と、ベストプラクティスにあります。

例. dynamic を使用した security group を定義してみましょう。
variable "ingress_ports" {
type = list(number)
description = "list of ingress ports"
default = [80, 443]
}
resource "aws_security_group" "security_group" {
vpc_id = var.vpc_id
# dynamic "ingress" {
# for_each = var.ingress_ports
# のSyntaxはJavaでいう下記のSyntaxに相当する
# Iterator<Map.Entry<Integer, Integer>> ingress = var.ingress_ports.entrySet().iterator();
# ingress.key()
# ingress.value()
dynamic "ingress" { # <---- localの名前を"ingress"と定義
# for_each argumentに、LoopするObjectをAssign
for_each = var.ingress_ports # <------ numberリストの変数をループする
# The iterator argument (任意)は現在ループしているオブジェクトの名前。デフォルトではdynamic blockの"ingress"になります。
iterator = port
# contentブロック内に、ループしながら作成するブロックのAttributesを定義する
content {
# iteratorオブジェクトであるsettingには2つのAttributeがあり、keyはmap keyかlistのindex.
# (例:JavaのMap iteratorがkeyとvalueの2つのAttributeを持つのと似ている)
from_port = port.value
to_port = port.value
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "sg"
}
}
説明しにくいのでコメントを見ていただければ、、、
- for_each で繰り返したい変数を指定し、dynamic で増やしたい変数を囲みます。
- iterator に現在ループしているオブジェクトの名前をセットします。
- content 内にresource の引数を定義します。
ちなみに、使用しないと以下になります。
resource "aws_security_group" "ecs_security_group" {
name = "sg"
description = "security group of rails-hello-nginx ecs"
vpc_id = var.vpc_id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group_rule" "http_ingress_rule" {
security_group_id = aws_security_group.ecs_security_group.id
type = "ingress"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
resource "aws_security_group_rule" "https_ingress_rule" {
security_group_id = aws_security_group.ecs_security_group.id
type = "ingress"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
おわりに
ここに記載している以外にも書き方もありますが、基本的にこれらに書かれているようにコードを書くと思います。
これだけでは、何だかわからないと思います。
今後、実際にインフラを構築する記事を書く予定なのでそちらも見ていただければと思います。
なにかわからない部分があれば、何でもおっしゃってください!
最後までご覧いただきありがとうございます!


コメント