Terraformを使った環境構築入門（AWS）

# はじめに
こんにちは！！
CYBIRD Advent Calendar 2022 8日目担当の[もっふんにゃ](https://qiita.com/moffunnya)です。
7日目は@tomoko_ishizakaさんの[「ストーリー仕立てで理解する、AWSのIAM周りとアカウントの超入門知識」](https://qiita.com/tomoko_ishizaka/private/3a94a2764275a9a971a4)でした。
是非こちらも合わせてご覧ください！

私は今年の4月にエンジニアとして新卒入社しました。
これまで研修経て、現在ではインフラチームでOJTを受けているのですが、その中で革命的な概念・ツールに出会ってしまいました。
それが「Infrastructure as Code」という概念とそれを体現する「Terraform」というツールです。
今回は、私が衝撃を受けたこの「Terraform」について、インストールからインフラの構築・変更・破壊・変数定義・出力値設定まで伝えたいと思います！

# Terraformとは
Terraformは、HashiCorpが提供するInfrastructure as Code (IaC) ツールです。
IaCツールを使うと、コンソールなどのグラフィカルなユーザーインターフェイスではなく、設定ファイルを使ってインフラ構築、変更、管理を行うことができます。

ずっとコンソールでポチポチやっていた身としては、設定ファイル（ソースコード）を使ってポチっとするだけで簡単にインフラを構築できる革命的に便利なツールです。出会った時には感動しました。

# あらゆるインフラを管理

Terraformは、AWS, Azure,　GCP, Kubernetes, Helm, GitHub, Splunk, DataDogなど、1,000以上のプロバイダで、リソースを管理することができます。
その中で今回はAWSでのインフラ構築を行なっていきます！！

# Terraformのインストール

Terraformを使うには、インストールが必要です。

Terraformをインストールするには、[こちら](https://developer.hashicorp.com/terraform/downloads)からシステムに適したパッケージを見つけ、ZIPファイルをダウンロードするか、あるいはコマンドラインでバイナリを取得する方法があります。

今回はMacで進めているのでHomebrewを使ってコマンドライン（ターミナル）からインストールしていきます。

HomebrewはフリーでオープンソースのMacOSX用パッケージ管理システムです。
※Homebrewのインストールについては[こちら](https://brew.sh/index_ja)からお願いいたします。

まず、HashiCorp tapをインストールします。

```
$ brew tap hashicorp/tap
```

次に、`hashicorp/tap/terraform`でTerraformをインストールします。

```
$ brew install hashicorp/tap/terraform
```
Terraform の最新版に更新するには、Homebrewを更新します。

```
$ brew update
```

`upgrade`コマンドを実行して、最新のTerraformのバージョンをダウンロードします。

```
$ brew upgrade hashicorp/tap/terraform
```

## インストールを確認

新しいターミナルウィンドウを開いて、インストールがうまくいったことを確認します。
```
$ terraform -version
Terraform v1.3.5
on darwin_amd64
```
上記の様にバージョンが表示されれば問題なくインストールされています！
`terraform -help`コマンドを使ってTerraformで利用可能なサブコマンドをリストアップすることができます。
```
$ terraform -help 
Usage: terraform [global options] <subcommand> [args]

The available commands for execution are listed below.
The primary workflow commands are given first, followed by
less common or more advanced commands.

Main commands:
  init          Prepare your working directory for other commands
  validate      Check whether the configuration is valid
  plan          Show changes required by the current configuration
  apply         Create or update infrastructure
  destroy       Destroy previously-created infrastructure

##...省略
```

# インフラを構築
Terraformのインストールが完了したところで、いよいよ構築です！
今回はAWS(Amazon Web Services)のEC2インスタンスを起動してインフラを構築していきます。
※EC2インスタンス：AWS上で動作する仮想マシン
※無料枠の範囲内で構築していきます。

TerraformからAWS上にインフラを構築するには、以下の2点が必要です。
・AWS CLIのインストール
・AWSアカウントのアクセスキー

## AWS CLIのインストール
まずはAWS CLIのインストールです。
今回はMacOSのターミナルからインストールしていきます。
curlコマンドを使用してファイルをダウンロードします。

```
$ curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
```

ダウンロードしたファイルを指定して、MacOSのinstallerプログラムを実行します。
```
$ sudo installer -pkg AWSCLIV2.pkg -target /
```
インストールが完了しましたので、確認を行います。

```
$ which aws
/usr/local/bin/aws
$ aws --version
aws-cli/2.7.24 Python/3.8.8 Darwin/18.7.0 botocore/2.4.5
```
上記の様で出ればAWS CLIが問題なくインストールされています。

## アクセスキーを環境変数に設定
次にAWS CLIで使用するアクセスキーです。
TerraformからAWSへ認証する際に、IAMユーザーのアクセスキーが必要になります。
![スクリーンショット 2022-11-30 14.42.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2983941/82929245-f8fa-d808-b11e-8f91a6649c51.png)
今回はAWSコンソール上で「terraform-test」というIAMユーザーを作成したので、このユーザーを使ってTerraformからインフラを構築します。
作成したIAMユーザーのアクセスキー、シークレットアクセスキーを環境変数に指定していきます。
```
$ export AWS_ACCESS_KEY_ID=  ＜アクセスキー入力＞
$ export AWS_SECRET_ACCESS_KEY= ＜シークレットアクセスキー入力＞
```

## コンフィギュレーションを書く

Terraformでインフラを記述するためのファイル群を「Terraformコンフィギュレーション」と言います。

Terraformのファイル群は独自のディレクトリに置かれている必要があるので、ディレクトリを作成します。

```
$ mkdir learn-terraform-aws-instance
```

ディレクトリに変更します。

```
$ cd learn-terraform-aws-instance
```

インフラを定義するファイルを作成します。

```
$ touch main.tf
```
テキストエディタでmain.tfを開き、以下のコンフィギュレーションを貼り付けて、ファイルを保存します。
```diff_terraform
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.16"
    }
  }

  required_version = ">= 1.2.0"
}

provider "aws" {
  region  = "us-west-2"
}
#----------------------------------------
# EC2インスタンスの作成
#----------------------------------------
resource "aws_instance" "app_server" {
  ami           = "ami-830c94e3"
  instance_type = "t2.micro"

  tags = {
    Name = "ExampleAppServerInstance"
  }
}
```
## ディレクトリの初期化
作成したコンフィギュレーションを使って、EC2インスタンスを構築していきます。
Terraformの実行に必要なプラグインをインターネットから取得しディレクトリを初期化します。
```
$ terraform init
```
実行結果に以下が表示されていれば成功です。
```
Terraform has been successfully initialized!
```

## 設定のフォーマットとバリデーション
terraform fmtコマンドを実行すると、コードのインデントを自動で整形することができます。
```
$ terraform fmt
```
また、terraform validateコマンドを実行すると、問題があった場合、ファイル名と行数を表示してくれます。
問題がない場合は以下の様に表示されます。
```
$ terraform validate
Success! The configuration is valid.
```

## インフラストラクチャの作成
terraform applyコマンドで設定を適用します。
※TerraformがEC2インスタンスを利用できるようになるのを待つため、実行には数分かかります。
```
$ 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_instance.app_server will be created
  + resource "aws_instance" "app_server" {
      + ami                          = "ami-830c94e3"
      + arn                          = (known after apply)

##....省略

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:
```

構築内容に問題がなければ、`Enter a value:`にYesと入力します。
```
  Enter a value: yes

aws_instance.app_server: Creating...
aws_instance.app_server: Still creating... [10s elapsed]
aws_instance.app_server: Still creating... [20s elapsed]
aws_instance.app_server: Still creating... [30s elapsed]
aws_instance.app_server: Still creating... [40s elapsed]
aws_instance.app_server: Still creating... [50s elapsed]
aws_instance.app_server: Creation complete after 54s [id=i-*****************]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
```
これでTerraformを使ってインフラが構築されました！！
![スクリーンショット 2022-11-30 19.53.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2983941/a4f25637-3bf0-aae4-4a75-cfc648bd41d1.png)

AWSコンソールで確認してみるとしっかり構築されていました！

## 状態の確認
`terraform show`を使って現在のインフラの状態を確認できます。
```
$ terraform show
# aws_instance.app_server:
resource "aws_instance" "app_server" {
    ami                                  = "ami-830c94e3"
    arn                                  = "arn:aws:ec2:us-west-2:************:instance/i-*****************"
    associate_public_ip_address          = true
    availability_zone                    = "us-west-2c"
    cpu_core_count                       = 1
    cpu_threads_per_core                 = 1
    disable_api_stop                     = false
    disable_api_termination              = false
    ebs_optimized                        = false
    get_password_data                    = false
    hibernation                          = false
    id                                   = "i-*****************"
    instance_initiated_shutdown_behavior = "stop"
    instance_state                       = "running"
    instance_type                        = "t2.micro"
    ipv6_address_count                   = 0
##....省略
```


# インフラを変更
構築したリソースに対して変更を適用していきます。
ここではインスタンスのAMIを変更します。
詳細には、AWSでインスタンスを起動した後にAMIの変更はできないので、先ほど起動したインスタンスを破棄して新しくインスタンスを起動します！（これらはTerraformが自動でやってくれます！）
main.tfのresourceブロックにある、現在のAMI IDを別のものに置き換えます。

```diff_terraform
resource "aws_instance" "app_server" {
-   ami = "ami-830c94e3"  　　　　　　　　　     #削除
+   ami = "ami-08d70e59c07c61a3a"　　　　　#追加
    instance_type = "t2.micro"
}
```
この変更で、AMIがUbuntu 12.04からUbuntu 16.04に変更されます。

## 変更の適用
設定を変更したら、もう一度terraform applyを実行して、変更内容を確認します。
```
$ terraform apply
```
問題がなければ`Enter a value:`でyesと回答します。
```
Enter a value: yes
```
実行計画で示されているように、Terraformは先ほど作成した既存のインスタンスを破棄し、新しいインスタンスを起動しました。
これで変更が適応されています。


# インフラを破壊
インフラが不要になったら、セキュリティ面でのリスクカットやコストを削減するために破棄したいことがあります。
Terraformは、インフラの構築と変更に加えて、管理するインフラを破壊したり再作成したりすることができます。

## 破棄する
`terraform destroy`コマンドは、Terraformが管理するリソースを終了させます。
今回作成したTerraformプロジェクトの管理の外にあるリソースについては破壊しませんのでご安心ください。

作成したリソースを破棄します。
```
$ terraform destroy
```
問題がなければ`Enter a value:`でyesと回答します。
```
Enter a value: yes
```
これで今回作成されたリソースは破壊されました！
※複数のリソースがあるような場合は、Terraformが依存関係を考慮して適切な順序で破棄します。


# 入力変数の定義
ここまでのコンフィギュレーションではハードコードされた値を使っていました。
Terraformの設定には変数を含めることができ、より柔軟なものにすることができます。

## インスタンス名を変数で設定
インスタンス名を定義するための変数を追加します。
`learn-terraform-aws-instance`ディレクトリ内で`variables.tf`というファイルを新しく作成します。
```
$ touch variables.tf
```
新しい変数`instance_name`を定義するブロックを作成します。
`variables.tf`を開き、以下のコンフィギュレーションを貼り付けて、ファイルを保存します。
```diff_terraform
variable "instance_name" {
 description = "Value of the Name tag for the EC2 instance"
 type        = string
 default     = "Tamagotoji"
}
```
上記の内容は`instance_name`について、別の値を宣言しない限り、デフォルトで`Tamagotoji`になります。
`main.tf`を開き、新しい変数を使用するよう以下の変更点を修正し、ファイルを保存します。
```diff_terraform
resource "aws_instance" "app_server" {
 ami           = "ami-08d70e59c07c61a3a"
 instance_type = "t2.micro"
 tags = {
- Name = "ExampleAppServerInstance"  #削除
+ Name = var.instance_name           #追加
 }
}
```
## コンフィギュレーションを適用
コンフィギュレーションを適用します。
```
$ terraform apply
```
問題がなければ`Enter a value:`でyesと回答します。
```
Enter a value: yes
```
`-var`オプションを使って変数を渡し、デフォルトのインスタンス名に上書きするため、もう一回設定を適用します。
```
$ terraform apply -var "instance_name=Moffunnya"
```
問題がなければ`Enter a value:`でyesと回答します。
```
Enter a value: yes
```
これで`variables.tf`内の変数から値を入力できる様になりました！
※コマンドラインから変数を設定しても、`variables.tf`や`main.tf`などのコンフィギュレーションには保存されません。

# コマンドラインに欲しい情報を出力
構築されたインフラ環境の欲しい情報について、コマンドラインで出力するように設定することができます。

## 出力用EC2インスタンスの設定

`learn-terraform-aws-instance`ディレクトリに`outputs.tf`というファイルを作成します。
```
$ touch output.tf
```
以下の設定を`outputs.tf`に貼り付け、EC2インスタンスのID、IPアドレスの出力を定義します。
```diff_terraform
output "instance_id" {
 description = "ID of the EC2 instance"
 value       = aws_instance.app_server.id
}

output "instance_public_ip" {
 description = "Public IP address of the EC2 instance"
 value       = aws_instance.app_server.public_ip
}
```
## 出力値の適応、確認
`terraform apply`で設定を適用します。
```
$ terraform apply
```
問題がなければ`Enter a value:`でyesと回答します。
```
Enter a value: yes
```

Terraformは設定を適用すると、出力値を画面に表示します。
出力するように設定した値は`terraform output`コマンドで問い合わせることができます。
```
$ terraform output
instance_id = "i-*****************"
instance_public_ip = "**.**.**.**"
```
これで`output.tf`で設定した出力値をコマンドラインで確認することができます！

# 終わりに

おつかれさまでした！！いかがでしたでしょうか？！！
今回は、「Terraform」について、インストール、インフラの構築・変更・破壊・変数定義・出力値設定まで伝えさせていただきました。
これまでコンソールでインフラを構築されてきた方は、私と同じ感動をえられたのではないでしょうか！！！

同じような環境構築を行う場面は多くあると思うので、Terraformの決まった構成の設定ファイルをいくつか持っておくと、思いもよらないところで活かせるかもしれません！！是非いろいろな構成で試してみてください！！

CYBIRD Advent Calendar 2022 9日目は@ayany0_zzZさんの「AR.jsを使って恐竜を家に呼んでみた」です！！
こちら是非ご覧ください！！

# 参考サイト
https://developer.hashicorp.com/terraform/tutorials/aws-get-started