TerraformとCloudFormationで学ぶインフラ構築の自動化

この記事は[Nuco Advent Calendar 2022](https://qiita.com/advent-calendar/2022/nuco)の11日目の記事です。

インフラに関わるエンジニアにとって必須の知識であるIaCについて書こうと思います。
IaCは、Infrastructure as Codeの略であり、「インフラのコード化」と言われたりします。
ここ最近はクラウドを使った開発では、IaCが当たり前になっていると感じています。

### この記事で得られること
* IaCの基本知識（IaCとは？ なぜ使うのか？ どんなツールがあるか？）
* CloudForamtionの全体像・使い方・サンプルコード・実務での応用例
* Terraformの全体像・使い方・サンプルコード・実務での応用例
* IaC導入に関するtips



# 1. Infrastructure as Code (IaC)とは？
#### 1-1. 定義
WikipediaによるとIaCは下記のように定義されています。
> Infrastructure as Code（IaC） はコンピューティング・インフラ（プロセス、ベアメタルサーバー、仮想サーバーなど）の構成管理・機械処理可能な定義ファイルの設定・プロビジョニングを自動化するプロセスである。

クラウドを利用した開発が普及している現代においては、
IaC =「クラウド上のサーバーや設定をコードで管理すること」という理解で十分です。

上記の「コード」は、「テンプレート」や「設定ファイル」と呼ばれることもあります。

# 2. なぜIaCが必要なのか
#### 2-1. 環境構築における再利用性が上がる
ほとんどの開発現場では少なくとも2つは環境を用意しているのではないでしょうか？
* prod環境（本番環境）
* dev環境（開発環境）


サービスの性質や規模によっては、stg環境といった上記以外の環境を用意することもあります。
新しい環境が必要になった際に手作業で構築すると、ミスをする可能性が高くなりますが、IaC対応してあると、同一の環境を簡単に作成できるようになります。

#### 2-2. コミュニケーションコストが下がる

設定内容はファイルに書いてあるので、書き方さえ覚えれば（or 調べられるようになれば）自分が構築作業をしていなくても内容把握が容易になります。
誰かに聞かないと解決しない、といったことが減ります。




#### 2-3. 更新履歴を管理できる
テンプレートはアプリケーションのソースコードと同様にgitで管理することが多いです。
いつ・誰が・どんな変更をしたか、というのを簡単に遡ることができますし、レビューも実施しやすくなります。


# 3. 代表的なIaCのサービス
代表的なIaCのサービスを４つ紹介します。
|名前  |テンプレートの形式  |対応しているクラウド  |提供元  |
|---|---|---|---|
|CloudFormation  |JSON, YAML  |AWSのみ  |AWS  |
|Google Cloud Deployment Manager  |YAML, jinja  |GCPのみ  |GCP  |
|Azure Resource Manager|JSON|Azureのみ|Azure|
|Terraform|HCL|AWS, GCP, Azureなどの主要なパブリッククラウド|HashiCorp

1つのクラウドにのみ対応しているものと、マルチクラウド対応のものがあります。
今回はCloudFormationとTerraformについて見ていきます。

# 4. CloudFormation（CFn）
#### 4-1. 概要
[CloudFormation](https://aws.amazon.com/jp/cloudformation/)は、AWSリソース（ex. Amazon S3, Amazon EC2, AWS lambda）の構築を、テンプレートを元に自動化するサービスです。
#### 4-2. 全体像
CloudFormationの全体像は以下のようになっています。
![iac_cfn_1.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186503/d5cc85f0-5517-2432-74e3-1ed8e6dec7c4.png)






#### 4-3. 概念
CloudFormationを理解する上で重要な概念が3つあります。

① テンプレート
② スタック
③ 変更セット

それぞれ見ていきます。
##### ①テンプレート (Template)
* 設定ファイル（AWS リソースを作成する際の設計図というイメージでokです）
* 1つのテンプレートに複数のAWSリソースの設定を記述できる
* JSON, YAML形式のテキストファイル
* 拡張子は、.json、.yaml、.yml、.template、.txt

※コメントがかけるのでYAML形式での管理がおすすめです。
##### ②スタック (Stack)
* 1つのテンプレートから作成されたAWSリソースの集合体
* テンプレートをCloudFormationに読み込ませると作成される
##### ③変更セット（Change set）
* 変更差分データのこと
* 既存のスタックで管理されているAWSリソースの更新時に生成される
* 事前にどんな変更が反映されるかを確認することができる

#### 4-4. リソース作成までの流れ
実際にS3バケットを構築します。
![iac_cfn.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186503/f1e6b7df-7e52-5366-f7d2-56b5f051cd32.png)



##### ① テンプレートを作成
今回はサンプルとして**nuco-advent-sample-bucket**という名前のS3バケットを構築します。
```yaml: sample.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: template sample
Resources:
  SampleBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: nuco-advent-sample-bucket
```
##### ② S3にアップロード
AWSのマネジメントコンソールからCloudFormationを選択し、スタックの作成に進みます。
ここで①のテンプレートをアップロードします。
マネジメントコンソールからテンプレートをアップロードすると、テンプレート管理用のS3バケットに配置されます。
![スクリーンショット 2022-11-27 14.43.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186503/9b8e1a78-090e-c73d-f9ef-bd1c58021c53.png)
 
##### ③ 差分チェック（更新時）
スタックの新規作成時は変更セットは作成されません。
更新時の挙動については③'の項目で扱います。

##### ④, ⑤ スタックを作成・更新, AWSリソースを構築
スタックの作成・更新が完了するとテンプレートファイルに記述されたS3バケット（nuco-advent-sample-bucket）が構築されています。
![スクリーンショット 2022-11-27 16.23.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186503/0b793fbb-538f-b76e-6cc7-a5ff2c5f8f57.png)

##### ③' 更新時の挙動
スタックの更新時は、変更セットが作成されます。
以下のテンプレートを使い、作成済みのS3バケットを非公開にします。
```sample.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: template sample
Resources:
  SampleBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: nuco-advent-sample-bucket
        PublicAccessBlockConfiguration: # 追加
            BlockPublicAcls: true
            BlockPublicPolicy: true
            IgnorePublicAcls: true
            RestrictPublicBuckets: true

```

スタックの更新前に「変更セット」のプレビューが表示され、変更対象のAWSリソースが表示されています。
![スクリーンショット 2022-11-27 16.17.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186503/ba61625a-c6b3-ec60-1137-d07216128e39.png)

そのまま更新すると、テンプレートに追加した設定が反映され、バケットが非公開になります。
![スクリーンショット 2022-11-27 16.28.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186503/6b809e6c-eca5-b59c-5abb-aafec5c6af50.png)

#### 4-5. 実務での利用パターン
* 作成・更新したテンプレートの反映作業自体を自動化する場合もあります
    * → CI/CD (継続的インテグレーション/継続的デリバリー)を実現
* 具体的には、以下のサービスも使い、CFnの実行をパイプライン経由にします
    * AWS CodePipeline
    * AWS CodeBuild
![iac_cfn_pipeline.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186503/530845ba-a454-a204-175b-f90f0bf92e21.png)



このようにしておくと、以下のようなヒューマンエラーを防ぐことができます。
* 更新対象のスタックを間違える
* 使用するテンプレートファイルを間違える

# 5. Terraform
#### 5-1. 概要
[Terraform](https://www.terraform.io/)は、HashiCorp社が提供するマルチクラウド対応のIaCツールです。
#### 5-2. 全体像
Terrafromの全体像は以下のようになっています。
![iac_terraform_1.drawio (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186503/3aafa43c-dadf-fafa-f235-a1cbbd04c79e.png)




#### 5-3. 概念
Terraformを理解する上で重要な概念は以下の4つになります。
① tfファイル
② tfstateファイル (terraform.tfstate)
③ Terraform Core
④ Terraform Plugins

##### ①tfファイル
* 構築するリソースを定義する設定ファイルのこと
    * 開発者が作成するファイル
* HCL (HashiCorp Configuration Language)という言語で記述する
* 拡張子は.tf
##### ②tfstateファイル
* Terraformに認識されているリソースの状態が定義されたファイル
    * Terraformによって生成されるファイル
* 拡張子は.tfstate
* デフォルトのファイル名は、terraform.tfstate
##### ③Terraform Core
* Go言語で書かれたCLI
* tfファイルの作成後にterraformコマンドを使うことで実行される
* tfファイルとtfstateファイルを受け取り、差分を確認し、構築・変更するリソースの状態を決める
* Terraform Pluginsに対して、リソースに関する操作をリクエストする
    * Terraform v0.12からプロトコルv5になり、gRPCを採用

##### ④Terraform Plugins
* Terraform Coreから呼び出されるプログラム
* AWSやGCPといった個別のプロバイダに対してリソースの構築や破棄に関する操作を行う
* Pluginsを構成する要素として、ProviderとProvisinoerがある
    * Provider
        * AWSやGCPのリソースの作成, 更新, 削除に関する操作をするためのプラグイン
    * Provisinoer
        * 作成したリソースを操作(ex. サーバーの設定)するためのプラグイン


#### 5-4. リソース作成までの流れ
CFnのサンプル同様、AWS環境に**nuco-advent-sample-bucket**という名前のS3バケットを構築します。
![iac_terraform.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186503/2bacc79f-8214-b77d-b1e7-88e5d5315879.png)




##### 環境設定
Terraform Coreからローカル環境のPluginが実行されるので環境構築が必要になります。

##### terraformのインストール(Macユーザーを想定)
```
% brew install tfenv
% tfenv install 1.3.5
% tfenv use 1.3.5
```

##### ①tfファイルを作成
ディレクトリ構成は下記になります。
```
作業ディレクトリ/
　├ providers.tf    # providerの情報を設定
　├ sample.tf       # 後ほどS3のリソースを定義
```
今回はAWS環境にリソースを構築するので、「provider: "aws"」を設定します。

``` tf: providers.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "= 4.40.0"
    }
  }
  required_version = "1.3.5"
}


provider "aws" {
  region = "ap-northeast-1"
  profile = "xxxx" # awsの認証情報を設定
}
```

``` tf: sample.tf
resource "aws_s3_bucket" "nuco-advent-sample-bucket-tf" {
  bucket = "nuco-advent-sample-bucket-tf"
}

resource "aws_s3_bucket_public_access_block" "nuco-advent-sample-bucket-tf" {
  bucket = aws_s3_bucket.nuco-advent-sample-bucket-tf.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}
```
##### ②terraformコマンド実行
###### init
* terraformの設定を初期化するためのコマンド
* terraform init を実行すると、providers.tf で設定している plugin（aws provider）がダウンロードされる
* ダウンロードされたファイルは、コマンドを実行したディレクトリの .terraform内に配置される
```
AWS_PROFILE=xxxx terraform init
```

###### plan
* 作成予定のリソースを確認するためのコマンド
* planを実行すると、Terraform Core側でtfファイルとtfstateファイルが比較され、更新対象のリソースが決まる
```
AWS_PROFILE=xxxx terraform plan
```

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_s3_bucket.nuco-advent-sample-bucket-tf will be created
  + resource "aws_s3_bucket" "nuco-advent-sample-bucket-tf" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "nuco-advent-sample-bucket-tf"
      + bucket_domain_name          = (known after apply)
      
      (中略)

  # aws_s3_bucket_public_access_block.nuco-advent-sample-bucket-tf will be created
  + resource "aws_s3_bucket_public_access_block" "nuco-advent-sample-bucket-tf" {
      + block_public_acls       = true
      + block_public_policy     = true
      + bucket                  = (known after apply)
      + id                      = (known after apply)
      + ignore_public_acls      = true
      + restrict_public_buckets = true
    }

Plan: 2 to add, 0 to change, 0 to destroy.

```

実務だと、planが通った段階でレビューを実施することもあります。

###### apply
* リソースを作成するためのコマンド
* applyを実行するとTerrafomr CoreからProviderに対して、リソース操作のリクエストがされる
```
AWS_PROFILE=xxxx terraform apply
```

下記のように聞かれるので、yesを入力します。
```
Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: 
```

apply結果
```
aws_s3_bucket.nuco-advent-sample-bucket-tf: Creating...
aws_s3_bucket.nuco-advent-sample-bucket-tf: Creation complete after 3s [id=nuco-advent-sample-bucket-tf]
aws_s3_bucket_public_access_block.nuco-advent-sample-bucket-tf: Creating...
aws_s3_bucket_public_access_block.nuco-advent-sample-bucket-tf: Creation complete after 0s [id=nuco-advent-sample-bucket-tf]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
```
このタイミングでローカル環境にはterraform.tfstateが作成されます。
``` tfstate: terrafrom.tfstate
{
  "version": 4,
  "terraform_version": "1.3.5",
  "serial": 3,
  "lineage": "daa61021-d26d-a4ee-a299-9c6545780fff",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "aws_s3_bucket",
      "name": "nuco-advent-sample-bucket-tf",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "acceleration_status": "",
            "acl": null,
            "arn": "arn:aws:s3:::nuco-advent-sample-bucket-tf",
            "bucket": "nuco-advent-sample-bucket-tf",
```

#### 5-5. 実務での利用パターン
terraform.tfstateをリアルタイムで共有するため、backendという機能を使い、terraform.tfstateをS3バケットで管理します。
その場合、providers.tfは下記のようにします。
``` tf: providers.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "= 4.40.0"
    }
  }
  required_version = "1.3.5"
  backend "s3" { # 追加
    bucket  = "nuco-advent-terraform-state"
    region  = "ap-northeast-1"
    key     = "sample/terraform.tfstate"
    encrypt = true
  }
}
```
※上記の設定をしても手元からterraformコマンドを実行する必要があります。
※CI/CD化（コマンドの実行も自動化）する場合、以下のようなアーキテクチャになります。
※CodeBuild内でTerraformのSDKを実行します
![iac_terraform_pipeline.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186503/984afed8-949e-9d4a-331b-03523601fffa.png)



# 6. IaC導入に関する注意点
IaCには、もちろん**デメリット**もあります。
#### 6-1. 学習コストがかかる
サンプルからも分かる通り、同じ設定のS3バケットでも、テンプレートの書き方や構築手順はサービスによって異なります。
「今のプロジェクトがAWS環境だから、CFn一択！」とはならず、「インフラ担当全員がTerraformを扱える方が、今後のアサインがしやすくなる」という判断もアリだと思います。

#### 6-2. 既存のシステムをどうするか？
IaC対応されていないシステムは、いまからでもIaC対応した方が良いのでしょうか？
検証期間を十分に確保でき、長期的にIaC対応による恩恵を受けられる場合は対応しても良いかと思います。

#### 6-3. IaCは常に効率的ではない
例えば、下記のようなケースでは、意図的にIaC対応をしないで進めるケースもあります。
* 新しいAWSサービスをちょっと試してみたい
* PoC開発（コンセプト検証）のためのサンプルを作りたい

AWS環境であれば、検証時はマネジメントコンソールからリソースを作成し、その後CFnなどでIaC化することがあります。

# まとめ
CloudFormationとTerraformのサンプルを使いつつ、IaCについて解説してみました。
初めのうちは、面倒だなと感じるかもしれませんが、管理するリソース数が増えてくると「ああ、IaC化しておいて良かった」思えると思います。この記事が最初のハードルを乗り越える一助になれば幸いです。
 
# 最後に
弊社では、経験の有無を問わず、社員やインターン生の採用を行っています。

興味のある方は[こちら](https://www.recruit.nuco.co.jp/)をご覧ください。