【初心者向け】細かいこと抜きでとりあえずTerraformを触ってみる② 

# はじめに
以下の記事の続きです。
いきなり技術書に入る前に、さっくりTerraformの挙動を確認しよう、がテーマです。

https://qiita.com/suiwave/items/aaeb74d0d44d1a7b94f5

前回記事で触れなかった「dataブロック」「moduleブロック」「outputブロック」の挙動を確認してみます。

記事の構成は以下の通りです。

1. dataブロックを使ってみる
2. module化を行い、moduleブロック、outputブロックを使ってみる

### 1. dataブロックを使ってみる
###### dataブロックとは
Terraformプロジェクトの外部で定義された情報を参照する場合に設定します。
Terraformプロジェクトの外部で定義された情報とは、AWSが定義しているマネージドなリソース（マネージドポリシーやami、マネージドルールグループ）や、別のTerraformプロジェクトなどで作成した既に存在するインフラリソースの情報を指します。

今回はdataブロックでAWSのマネージドなamiを取得して、EC2インスタンスを作成してみます。

###### 構成図
![cap0.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/12cda91d-174c-3e00-0cd9-753f833f574a.png)

パブリックサブネットにEC2インスタンスを1つ作成します。
セキュリティグループでSSH接続のみを許可しようと思います。

###### フォルダ構成
```
.
├── main.tf
├── ec2.tf
├── vpc.tf
```

###### tfファイル
```main.tf
# terraformブロック
# Terraformプロジェクト全体の定義を行います。
terraform {
    # Terraformのバージョン指定
    required_version = "~> 1.5.0"

    # Terraformのaws用ライブラリのバージョン指定
    required_providers {
        aws = {
            source  = "hashicorp/aws"
            version = "~> 5.31.0"
        }
    }
}

# providerブロック
# 各クラウドサービスの個別設定を行います。
provider "aws" {
    # リージョンを指定
    region = "ap-northeast-1"
}
```

```vpc.tf
# VPCを作成
resource "aws_vpc" "practice" {
    cidr_block = "20.0.0.0/16"
}

# publicサブネットを作成
resource "aws_subnet" "practice_public_1a" {
    vpc_id = aws_vpc.practice.id
    cidr_block = "20.0.1.0/24"
    availability_zone = "ap-northeast-1a"
}

# ルートテーブルを作成
resource "aws_route_table" "practice_public" {
    vpc_id = aws_vpc.practice.id
}

# インターネットゲートウェイを作成
resource "aws_internet_gateway" "practice" {
    vpc_id = aws_vpc.practice.id
}

# 作成したルートテーブルにすべてのトラフィックをインターネットゲートウェイに向ける設定を追加
resource "aws_route" "practice" {
    route_table_id = aws_route_table.practice_public.id
    gateway_id = aws_internet_gateway.practice.id
    destination_cidr_block = "0.0.0.0/0"
}

# パブリックサブネットにルートテーブルを関連付ける
resource "aws_route_table_association" "practice" {
    subnet_id = aws_subnet.practice_public_1a.id
    route_table_id = aws_route_table.practice_public.id
}

```

```ec2.tf
# dataブロック
# Terraformプロジェクトの外部で定義された情報を参照する場合に設定します。
# 外部リソースであるamiのデータをdataブロックで取得します。
data "aws_ami" "ubuntu" {
    most_recent = true

    filter {
        name   = "name"
        values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
    }

    filter {
        name   = "virtualization-type"
        values = ["hvm"]
    }

    owners = ["099720109477"] # Canonical
}

# EC2に設定するssh接続を許可するセキュリティグループを作成
resource "aws_security_group" "practice_ssh" {
    vpc_id = aws_vpc.practice.id
    name   = "practice-ssh-sg"
}

# インバウンドトラフィックを許可するセキュリティグループルールを作成
resource "aws_security_group_rule" "ingress" {
    type = "ingress"
    from_port = 22
    to_port = 22
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    security_group_id = aws_security_group.practice_ssh.id
}

# すべてのアウトバウンドトラフィックを許可するセキュリティグループルールを作成
resource "aws_security_group_rule" "egress" {
    type = "egress"
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
    security_group_id = aws_security_group.practice_ssh.id
}

# EC2インスタンスを作成
resource "aws_instance" "practice" {
    ami           = data.aws_ami.ubuntu.id
    instance_type = "t3.micro"
    subnet_id     = aws_subnet.practice_public_1a.id

    vpc_security_group_ids      = [aws_security_group.practice_ssh.id]
    associate_public_ip_address = true

    tags = {
        Name = "practice-instance"
    }
}

```

ec2.tfの4行目でdataブロックを設定しています。
Terraformのドキュメントをコピペしただけですが・・・。

###### 実行してみる
dataブロックの挙動を確認します。
```
terraform plan
```
![cap1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/cbb8d316-31be-94eb-2475-1ec69ccbd785.png)

plan結果を確認します。
dataブロックが読み込まれ、外部リソースであるamiのidを取得できています。
>data.aws_ami.ubuntu: Read complete after 0s [id=ami-07a48f4a2abe3a5fc]

実際にマネジメントコンソールから、dataブロックで指定したfilter通りのamiを取得できているかを確認します。

![cap2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/deffae6b-6da8-7b5b-b1a1-e9697a701237.png)

name、virtualization-type、owner、verがdataブロックで指定した通りのamiが取得できていることが分かります。

このように、dataブロックを使用することで、外部リソースを取得し、その値を参照できることが確認できました。

### 2. module化を行い、moduleブロック、outputブロックを使ってみる
###### module化とは
インフラ構成をテンプレート化することを指します。
インフラ構成すべてをテンプレート化することで、環境を簡単に複製することが可能になったり、設定量や記述量が多いリソースをテンプレート化することで、tfファイルの記載量を減らすことが可能になったり、と嬉しいことがたくさんあります。

実際に挙動を確認してみます。

###### フォルダ構成
```
.
├── main.tf
├── ec2.tf
├── vpc.tf
├── modules/
|   ├── security_group/
|       ├── main.tf 
```

dataブロックの挙動を確認するために使用したプロジェクトに変更を加えます。
セキュリティグループは意外と記載量が多いリソースなので、モジュール化し、ec2.tfのスリム化を目指します。

###### tfファイル
main.tfとvpc.tfは特に変更がないため、省略します。

```modules/security_group/main.tf
variable "name" {}
variable "vpc_id" {}

variable "port" {
    # ポート番号が設定されるようtypeを定義
    type = number
}

variable "cidr_blocks" {
    # string配列が設定されるようtypeを定義
    type = list(string)
}

# セキュリティグループを作成
resource "aws_security_group" "module" {
    # varからnameを指定
    name   = "practice-${var.name}-sg"
    # varから許可するVPCを指定
    vpc_id = var.vpc_id
}

# インバウンドトラフィックを許可するセキュリティグループルールを作成
resource "aws_security_group_rule" "ingress" {
    type = "ingress"
    # varからポートを指定
    from_port = var.port
    to_port = var.port
    protocol = "tcp"
    # varから許可するCIDRブロックを指定
    cidr_blocks = var.cidr_blocks
    security_group_id = aws_security_group.module.id
}

# すべてのアウトバウンドトラフィックを許可するセキュリティグループルールを作成
resource "aws_security_group_rule" "egress" {
    type = "egress"
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
    security_group_id = aws_security_group.module.id
}

# outputブロック
# モジュール化したtfファイル内で設定された値を、モジュール外のtfファイルで参照できるようにするために設定されます。
# module呼び出し側でセキュリティグループのidを取得するためにoutputする
output "security_group_id" {
    value = aws_security_group.module.id
}

```

1～19行目の部分で、「セキュリティグループ名」「どのVPCに作成するか？」「許可するポートは何番か？」「許可するCIDRブロックは何か？」をmodule呼び出し側に要求するよう設定しています。
また、ファイル末尾のoutputブロックで、module呼び出し側が参照できる値を設定しています。
**outputブロックで設定した値以外はmodule呼び出し側で参照できない**ため、注意が必要です。

```ec2.tf
# dataブロック
# Terraformプロジェクトの外部で定義された情報を参照する場合に設定します。
# 外部リソースであるamiのデータをdataブロックで取得します。
data "aws_ami" "ubuntu" {
    most_recent = true

    filter {
        name   = "name"
        values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
    }

    filter {
        name   = "virtualization-type"
        values = ["hvm"]
    }

    owners = ["099720109477"] # Canonical
}

# moduleブロック
# Terraformは「モジュール」という単位でインフラリソース設定をテンプレート化できます。
# モジュール化したtfファイルを参照する場合に設定します。
module "ssh_security_group" {
    source = "./modules/security_group"
    name = "ssh"
    vpc_id = aws_vpc.practice.id
    port = 22
    cidr_blocks = ["0.0.0.0/0"]
}

# 同様にhttpを許可するセキュリティグループをmoduleを利用して作成
module "http_security_group" {
    source = "./modules/security_group"
    name = "http"
    vpc_id = aws_vpc.practice.id
    port = 80
    cidr_blocks = ["0.0.0.0/0"]
}

# EC2インスタンスを作成
resource "aws_instance" "practice" {
    ami           = data.aws_ami.ubuntu.id
    instance_type = "t3.micro"
    subnet_id     = aws_subnet.practice_public_1a.id

    vpc_security_group_ids      = [
        # module側のoutputブロックの値を参照
        module.ssh_security_group.security_group_id,
        module.http_security_group.security_group_id
    ]
    associate_public_ip_address = true

    tags = {
        Name = "practice-instance"
    }
}

```

微妙に長かったセキュリティグループの設定部分がmoduleブロック1つに置き換わっています。
sshとhttpの複数のセキュリティグループを設定してみましたが、moduleブロックを1つ増やすだけで良いので、簡単でスリムです。

###### 実行してみる
moduleブロックの挙動を確認します。
moduleブロックを利用する際は「terraform init」コマンドの再実行が必要です。
```
terraform init
```

![cap3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/9e1a47f4-979a-0524-8e53-f7ba26f4c2e4.png)


>Initializing modules...
>-- http_security_group in modules/security_group
>-- ssh_security_group in modules/security_group

moduleの読み込みが行われていることが確認できます。

```terraform apply```し、マネジメントコンソールから作成されたEC2インスタンスを確認してみます。

![cap4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/502c37dd-0613-3283-d9da-911a1d40c8bc.png)


moduleブロックで指定したnameやポート、CIDRブロックでセキュリティグループが作成、インスタンスに設定されていることが確認できました。

以上で、「dataブロック」「moduleブロック」「outputブロック」の挙動確認は十分かと思います。

## おわりに
前回の続きで、サクッと「dataブロック」「moduleブロック」「outputブロック」の挙動をまとめてみました。
体系的な部分は参考書に任せるとして、その前段にあるTerraformの初歩理解はこれで十分かと思います。
リソースの消し忘れが無いよう、業務外でAWSのリソースを作成する際はIaCツールを使いたい、程度の要求は十分叶えることができるようになったのではないかと思います。

#### 感想
IaC楽しいですね。リソースの消し忘れがほぼ完全になくなるのは、業務外でビクビクAWSを触っている身としてはありがたいです。
for_eachなどの業務で頻出する記法も今後勉強できればと思います。