【Terraform】一気に環境構築を終わらせよう

【Terraform】一気に環境構築を終わらせよう

目次

    はじめに

    デジタルエンジニアリング部AI/DATA課1Gの辻口です。
    普段の業務では、インフラ基盤、設計、構築、運用、保守を幅広く担当させていただいております。
    最近流行りのIaCですが、みなさん使っておりますでしょうか?
    初学の一歩として私が得意としているTerraformというIaCツールがあります。

    AWS、Google Cloud(以下、GCP)、Microsoft Azure(以下、Azure)はもちろん監視のNew Relic、Zabbixなどの環境構築も可能なIaCツールとなっています。

    なぜIaCが必要なのか

    一番は『環境差分をなくすため!』です。
    どうしてもAWSなどのクラウドサービスで構築時に手作業で行っていると、環境差分が生まれやすく、それを防ぐために手順書の作成が必要になりがちです。
    こうなってくると時間もかかりますし、作業漏れで環境差分が出来上がってしまいます。
    できればインフラの構成は本番環境とテスト環境で差分がないことが一番です。

    Terraformを選ぶ理由

    • 入門のハードルが低い
    • 慣れれば誰でも見やすく書きやすい
    • 各所変更箇所も順番を決めることができる
    • 使い回しが可能

    さっそくTerraform入門(AWS編)

    前提条件

    • 事前に作成したVPCの中にEC2を1台とセキュリティグループを作成する

    仕様

    • EC2
      • スペック: t3.nano
      • ストレージ: 30GB, GP3
      • IOPS: 3000
    • SecurityGroup
      • アウトバウンドは制限なし
      • インバウンドはアタッチしているインスタンス同士であれば全ての通信を許可

    これからの流れ

    1. Terraformのインストール(Mac)
      • tfenvインストール
    2. ディレクトリ構成
    3. provider.tf作成
    4. versions.tf作成
    5. main.tf作成
    6. variable.tfを作成
    7. 各サービスごとに.tfを作成(例:ec2.tf)

    Terraformのインストール

    Terraformを使用するためにHomebrewを使用してtfenvをインストールします。 下記コマンドを実行してください。

    # tfenvをinstall
    brew install tfenv
    
    # tfenv install確認
    tfenv --version
    
    # Terraform version確認
    tfenv list-remote
    
    # 今回使用するのはTerraform version 1.2.0
    tfenv install 1.2.0
    
    # 使用するTerraform versionを指定
    tfenv use 1.2.0
    
    # Terraformがインストール済みか確認
    terraform version
    

    引用: M1 MACにTerraformをインストール


    ディレクトリ構成

    |--GCP
    |    |--- ...
    |    - ...
    |--Azure
    |    |--- ...
    |    - ...
    |--NewRelic
    |    |--- ...
    |    - ...
    |--AWS   #上の階層は使用するクラウドサービスごとでわけておくと管理が楽です。
    |   |
    |   |--ec2
    |   |   |-- modules                 # サービス設定項目用フォルダ
    |   |   |     |-- ec2.tf            # EC2設定項目
    |   |   |     |-- securitygroup.tf  # SecurityGroup設定
    |   |   |     |-- variable.tf       # 変数定義
    |   |   |
    |   |   |-- production              # production名(test,stage,prodなど)
    |   |   |     |-- main.tf           # EC2設定値
    |   |   |     |-- versions.tf        # Terraformが動作するAWSの設定
    |   |   |     |-- provider.tf       # Terraformが動作する環境の設定
    

    上記のような形でディレクトリを構成してください。
    こうすることでmodule設計が可能になります。

    moduleとは?

    インフラコードを再利用性や管理のしやすさを向上させるための方法論です。
    Terraformモジュールを使うと、同じインフラストラクチャコードを複数の環境やプロジェクトで再利用できます。

    作成シェルスクリプト

    適当な位置にterraform.shを作成
    下記スクリプトを貼り付けて 'sh terraform.sh' にて実行

    #!/bin/bash
    
    mkdir ~/Documents/terraform
    
    cd ~/Documents/terraform
    
    
    # 作成するディレクトリとファイルの構造
    ROOT_DIRECTORIES=("GCP" "Azure" "NewRelic" "AWS") # ここはAWSだけ欲しい場合はAWSだけ残してください。
    
    AWS_SUBDIRECTORIES=(
        "AWS/ec2/modules"
        "AWS/ec2/production"
    )
    
    AWS_FILES=(
        "AWS/ec2/modules/ec2.tf"
        "AWS/ec2/modules/securitygroup.tf"
        "AWS/ec2/modules/variable.tf"
        "AWS/ec2/production/main.tf"
        "AWS/ec2/production/versions.tf"
        "AWS/ec2/production/provider.tf"
    )
    
    # ディレクトリを作成
    for dir in "${ROOT_DIRECTORIES[@]}"; do
        if [ ! -d "$dir" ]; then
            mkdir -p "$dir"
            echo "Directory created: $dir"
        else
            echo "Directory already exists: $dir"
        fi
    done
    
    for dir in "${AWS_SUBDIRECTORIES[@]}"; do
        if [ ! -d "$dir" ]; then
            mkdir -p "$dir"
            echo "Directory created: $dir"
        else
            echo "Directory already exists: $dir"
        fi
    done
    
    # ファイルを作成
    for file in "${AWS_FILES[@]}"; do
        if [ ! -f "$file" ]; then
            touch "$file"
            echo "File created: $file"
        else
            echo "File already exists: $file"
        fi
    done
    
    # 完了メッセージ
    echo "All directories and files have been created."
    

    production環境の.tfファイル作成

    provider.tfの作成

    今回はAWSに環境を作るので下記コピペで大丈夫です。

    provider "aws" {             # デプロイ先はAWSです。
      region = "ap-northeast-1"  # 東京リージョンに作ります。
    
      default_tags {             # 作ったリソースに標準でつけるタグ
        tags = {                 # タグの設定
          Terraform = true
        }
      }
    }
    

    タグ設定をしておくと後々AWS GUI上でリソースを確認したときにタグでソートが可能になります。
    今回はterraformで作りましたという意味で[terraform: true]をつけておきます。


    versions.tfの作成

    下記もコピペで問題ないです。

    terraform {
      required_providers {            
        aws = {                         # AWSにデプロイします。
          source  = "hashicorp/aws"     # HashiCorpのAWSのリソースを使います。
          version = "~> 4.0.0"          # HashiCorpのversionは4.x.xです。
        }
      }
      required_version = "~> 1.2.0"     # 使用するTerraform versionは1.2.xです。
    }
    
    • hashicorp/aws versionについてはこちら

      • hashicorp/awsについては最新versionを使うことも可能。
      • AWS側の新規機能を使う場合にはversionをあげておくことが吉
    • required_version について

      • terraform 本体のversion
      • 実行の際のバージョンなので、先ほどtfenvで[tfenv use 1.2.0]に指定したバージョンと同様にします。
      • 別バージョンを使いたい場合はここを変更する必要があります。

    main.tf

    module "create-ec2" {
        source = "../modules"                   # modulesのフォルダを使用する
    
        env  = "test"                           # 環境ステージ名
        name = "asia-quest"                     # Project名など
    
        instance_type = "t3.nano"               # インスタンスタイプ
        key_name      = "asia-quest-key"        # キーペア(事前作成)
        iam_instance  = "ssm-role"              # SSM用のロールをアタッチ(事前作成)
        iops          = "3000"
        volume_size   = "30"
        volume_type   = "gp3"
    
        vpc_id      = "vpc-hogehoge"            # 作成したVPC IDを入力
        subnet_id   = "subnet-hogehoge"         # 作成したVPCの作成したいsubnet IDを入力
    }
    

    環境が変わってもenvのステータスだけ変えるなどで環境名の変更が可能です。
    スペックなども環境ごとで変えることができ、テスト環境であればスペックを落としてランニングコスト下げるなどの対策も可能です。

    modules .tfファイルの作成

    ec2.tf

    resource "aws_instance" "nat_ec2" { 
    
      tags = {
        Name = "${var.env}-ec2" 
        Dlm  = "${var.env}-ec2" 
      }
    // /production/main.tfの設定を継承する
      ami                         = "ami-012261b9035f8f938"     // Amazon Linux 2023
      instance_type               = var.instance_type           // インスタンスファミリーおよびタイプを指定
      key_name                    = var.key_name                //SSHキーを指定 ※要事前設定
      iam_instance_profile        = var.iam_instance            //セッションマネージャーをするためのiamロールを指定
    
      //キャパシティー予約をなしに設定
      capacity_reservation_specification {
        capacity_reservation_preference = "none"
      }
      
      vpc_security_group_ids = [aws_security_group.sg.id]
      monitoring             = true
    
      // EBSのルートボリューム設定
      root_block_device {
        // ボリュームサイズ(GiB)
        volume_size = var.volume_size
        // ボリュームタイプ
        volume_type = var.volume_type
        // GP3のIOPS
        iops = var.iops
        // GP3のスループット
        throughput = 125
        // EC2終了時に削除
        delete_on_termination = true
    
        // EBSのNameタグ
        tags = {
          Name = "${var.env}-${var.name}-ec2"
        }
      }
    }
    

    varについては後ほど解説しますが、ここで触れておくとproductionで作ったmain.tfの変数設定を引き継ぐことができます。 このあとvariable.tfで定義します。


    variables.tf

    variable "instance_type" {}
    
    variable "env" {}
    
    variable "name" {}
    
    variable "key_name" {}
    
    variable "iam_instance" {}
    
    variable "iops" {}
    
    variable "volume_size" {}
    
    variable "volume_type" {}
    
    variable "vpc_id" {}
    
    variable "subnet_id" {}
    

    このように書くとproductionで書いたmain.tfを元にして変数をmodule内に持ち込むことが可能です。


    securitygroup.tf

    resource "aws_security_group" "sg" {
      vpc_id = var.vpc_id                      // 作成先のVPCを指定
      name   = "${var.env}-${var.name}-private-all-SG" // セキュリティグループの名前を指定
      
      // アウトバンド側の設定
      egress {
        from_port        = 0            // 開始ポート
        to_port          = 0            // 終了ポート
        protocol         = "-1"         // すべてのプロトコルを許可
        cidr_blocks      = ["0.0.0.0/0"] // すべてのIPv4アドレス範囲からのトラフィックを許可
        ipv6_cidr_blocks = ["::/0"]     // すべてのIPv6アドレス範囲からのトラフィックを許可
      }
    }
    
    resource "aws_security_group_rule" "in"{
      type = "ingress"
      to_port = 0
      from_port = 0
      protocol = "-1"
      source_security_group_id = aws_security_group.sg.id //セキュリティグループをアタッチしているもの同士はすべての通信が可能。
      security_group_id = aws_security_group.sg.id
    }
    

    アウトバンドは制限なし、インバウンドに関しては、作成したセキュリティグループ同士であれば全ての通信を許可となってます。
    SSMでのアクセス前提の構築です。

    実際に実行してみよう!

    下記コマンドで移動してみましょう!

    cd ec2/production
    
    terraform init
    
    terraform plan
    

    planをすると今回作成するリソースが出てくると思います。
    作るものが間違いないかを確認していざ下記コマンドで実行しましょう!

    terraform apply
    

    作成されれば完了です。お疲れ様でした。

    各所解説

    module構造はいかがだったでしょうか?
    イメージは、変更する部分だけ変数化することで最小限の変更で構築をすることです。


    解説: variable定義について

    modulesのディレクトリ内にvariable.tfを作成してます。
    main.tf側でsourceの定義でmodulesのディレクトリ内を参照すると指定してると思います。
    これによって、main.tfに定義した値がmodulesディレクトリ内のvariable.tfに引き渡されます。 variable.tfに引き渡された値は各modules内のec2.tfなどのvar定義に引き渡され使用することが可能です。


    解説: resourceについて

    resourceはAWSなどのサービスごとにTerraform側で分かれています。
    ですが、今回みたいにセキュリティグループがないとEC2を作成できないといった依存関係が出てきます。 一緒に作れるものは一緒に作ってAWS上で設定されるidなどを引き渡してあげることがTerraform上で可能です。 今回のスクリプトで出てきてる部分でいうとEC2を作成する部分です。

      vpc_security_group_ids = [aws_security_group.sg.id]
      monitoring             = true
    

    aws_security_group.sg.idと定義していると

    resource "aws_security_group" "sg" {
      vpc_id = var.vpc_id               
      ...省略
    }
    

    この resource "aws_security_group" "sg" を使用しています。
    作成したあとにidを引っ張ってきてEC2側に自動で入力してくれるということです。

    resourceについては下記HashiCorp様のナレッジに載ってます。

    resource "aws_security_group" "sg"
    下線の部分はリソースに対しての名前なので自分でわかりやすい名前に変えましょう。 複数のセキュリティグループを作成するときは、名前が重複するとエラーになります。

    アジアクエスト株式会社では一緒に働いていただける方を募集しています。
    興味のある方は以下のURLを御覧ください。