Cloud9 上で Terraform の実行環境を作る~Amazon Linux 2023 版~

2024.04.03

コーヒーが好きな emi です。

私は Windows ユーザーです。一般的に Web 開発の現場では Mac が使われることが多く、Terraform の利用方法を検索しても Mac 向けの情報しか見つからずやや苦労することがあります。
そこで今回は Cloud9 上で Terraform の実行環境を作ってみました。

Cloud9 環境の作成

以下のように、デフォルト VPC に Cloud9 のプラットフォームとなる Amazon Linux 2023 を作成していきます。

Cloud9 環境は以下のように作成します。まず名前を設定し、新しい EC2 インスタンスを作成するで進みます。

インスタンスタイプですが、最近メモリ不足で止まってしまうことがあったので一番小さいインスタンスではなく、t3.medium とやや大きめのものにしてみました。今回の検証では一番小さいインスタンスでも構いません。
ちなみに、EC2 の無料利用枠が終了している方は t2.micro よりも t3.micro の方が安くスペックが高いので t3.micro にされることをお勧めします。
プラットフォームはデフォルトで Amazon Linux 2023 が選択されているのでこのまま進みます。

後の設定はこのままで、「作成」をクリックします。

作成完了したら、「開く」から IDE の画面を開きます。

開きました。画面下部がターミナルになっているのでここからコマンドを実行します。

Terraform のインストール

Cloud9 にはデフォルトで aws-cli や git がインストールされています。

AWS CLI のバージョン確認

aws --version

▼実行結果例

user.emi:~/environment $ aws --version
aws-cli/2.15.32 Python/3.11.8 Linux/6.1.79-99.167.amzn2023.x86_64 exe/x86_64.amzn.2023 prompt/off
user.emi:~/environment $

Git のバージョン確認

git -v

▼実行結果例

user.emi:~/environment $ git -v
git version 2.40.1
user.emi:~/environment $

Terraform のバージョン管理に便利な tfenv をインストールします。
tfenv は Terraform のバージョン管理ツールです。tfenv を使うことで、プロジェクトごとに異なるバージョンの Terraform を簡単に切り替えて使うことができます。

git clone https://github.com/tfutils/tfenv.git ~/.tfenv

▼実行結果例

user.emi:~/environment $ git clone https://github.com/tfutils/tfenv.git ~/.tfenv
Cloning into '/home/ec2-user/.tfenv'...
remote: Enumerating objects: 2057, done.
remote: Counting objects: 100% (662/662), done.
remote: Compressing objects: 100% (207/207), done.
remote: Total 2057 (delta 517), reused 522 (delta 446), pack-reused 1395
Receiving objects: 100% (2057/2057), 437.28 KiB | 10.41 MiB/s, done.
Resolving deltas: 100% (1321/1321), done.
user.emi:~/environment $

上記コマンドでは、GitHub にある tfenv のリポジトリを、ローカルの ~/.tfenv ディレクトリにクローンしています。tfenv のソースコードが Cloud9 のローカル環境にダウンロードされました。

tfenv のパスを環境変数に追加します。

sudo ln -s ~/.tfenv/bin/* /usr/local/bin

▼実行結果例

user.emi:~/environment $ sudo ln -s ~/.tfenv/bin/* /usr/local/bin
user.emi:~/environment $

このコマンドは、tfenvのバイナリファイルへのシンボリックリンクを作成し、システムのパスに追加しています。
これで tfenv のバイナリファイル(tfenv コマンドなど)へのシンボリックリンクが /usr/local/bin に作成され、tfenv コマンドをシェルから直接実行できるようになります。

補足(クリックで展開)

1. ln -s でシンボリックリンク(ショートカット)を作成
2. ~/.tfenv/bin/\*~/.tfenv/bin/ ディレクトリ内の全てのファイルを指定
3. /usr/local/bin はシステムのパスに含まれるディレクトリで、このディレクトリ内のコマンドはシェルから直接実行できる

tfenv のバージョンを確認します。

tfenv -v

▼実行結果例

user.emi:~/environment $ tfenv -v
tfenv 3.0.0-49-g39d8c27
user.emi:~/environment $

インストール可能な Terraform のバージョンを確認します。

tfenv list-remote

▼実行結果例

user.emi:~/environment $ tfenv list-remote
1.8.0-rc1
1.8.0-beta1
1.8.0-alpha20240228
1.8.0-alpha20240216
1.8.0-alpha20240214
1.8.0-alpha20240131
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
:
:
:
0.1.1
0.1.0
user.emi:~/environment $

バージョン 1.7.5 が新しい GA 版であることが確認できますね。では、Terraform をインストールします。

tfenv install 1.7.5

▼実行結果例

user.emi:~/environment $ tfenv install 1.7.5
Installing Terraform v1.7.5
Downloading release tarball from https://releases.hashicorp.com/terraform/1.7.5/terraform_1.7.5_linux_amd64.zip
############################################################################################################################################ 100.0%
Downloading SHA hash file from https://releases.hashicorp.com/terraform/1.7.5/terraform_1.7.5_SHA256SUMS
Not instructed to use Local PGP (/home/ec2-user/.tfenv/use-{gpgv,gnupg}) & No keybase install found, skipping OpenPGP signature verification
Archive:  /tmp/tfenv_download.10sOWf/terraform_1.7.5_linux_amd64.zip
  inflating: /home/ec2-user/.tfenv/versions/1.7.5/terraform  
Installation of terraform v1.7.5 successful. To make this your default version, run 'tfenv use 1.7.5'
user.emi:~/environment $

インストールできました。では、インストールした Terraform のバージョンを確認します。

tfenv list

▼実行結果例

user.emi:~/environment $ tfenv list
  1.7.5
No default set. Set with 'tfenv use <version>'
user.emi:~/environment $

利用する Terraform のバージョンを指定しておきます。

tfenv use 1.7.5

▼実行結果例

user.emi:~/environment $ tfenv use 1.7.5
Switching default version to v1.7.5
Default version (when not overridden by .terraform-version or TFENV_TERRAFORM_VERSION) is now: 1.7.5
user.emi:~/environment $

利用中の Terraform バージョンは以下コマンドで確認します。

terraform -v

▼実行結果例

user.emi:~/environment $ terraform -v
Terraform v1.7.5
on linux_amd64
user.emi:~/environment $

Terraform を使って VPC を作成

実際に Cloud9 上で Terraform コードを作成しリソース作成します。今回は VPC とインターネットゲートウェイ(以降、IGW)を作成します。サブネットは作成しません。

まずテスト用にフォルダを作成し移動します。

mkdir test-terraform && cd test-terraform

▼実行結果例

user.emi:~/environment $ mkdir test-terraform && cd test-terraform
user.emi:~/environment/test-terraform $

main.tf の作成

main.tf を作成して、処理を記載します。 Terraform で実行・管理している状態を保存する .tfstateファイルは、保存先として S3 バケットを指定します。S3 バケットはあらかじめ作成しておいてください。

今回は検証のため簡易な 1 ファイルにまとめています。

user.emi:~/environment/test-terraform $ touch main.tf

main.tf

##################################################
# Terraform settings
##################################################
terraform { # https://developer.hashicorp.com/terraform/language/settings
  # Terraform バージョンの指定
  required_version = "~> 1.4"
  # AWS プロバイダーのバージョン指定 https://registry.terraform.io/providers/hashicorp/aws/latest
  required_providers {
      aws = {
          source  = "hashicorp/aws"
          version = "~> 5.01"
      }
  }
  # tfstate ファイルを S3 に配置する(配置先の S3 は事前に作成しておく)
  backend s3 {
      bucket = "tfstate-emikitani"
      region = "ap-northeast-1"
      key    = "tf-test-20240402.tfstate"
  }
}

##################################################
# Provider settings
##################################################
# AWS プロバイダーの定義
provider aws {
    region = "ap-northeast-1"
}

##################################################
# VPC
##################################################
# VPC  https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc
resource "aws_vpc" "vpc" {
  cidr_block = "10.0.0.0/16"
  enable_dns_support = true
  enable_dns_hostnames = true
  tags = {
    Name = "vpc"
  }
}

# Internet Gateway  https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/internet_gateway
resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.vpc.id
  tags = {
    Name = "igw"
  }
}

terraform init

terraform init コマンドで、ワークスペースの初期化や必要なプラグインのダウンロードをおこないます。

terraform init

▼実行結果例

user.emi:~/environment/test-terraform $ terraform init

Initializing the backend...

Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.1"...
- Installing hashicorp/aws v5.43.0...
- Installed hashicorp/aws v5.43.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
user.emi:~/environment/test-terraform $

terraform init は Terraform の動作に必要な初期設定を行います。

  • バックエンドの初期化
    • backend ブロックで指定されたバックエンド(今回は S3)の設定を読み込み、tfstate ファイル(状態ファイル)を保存するための準備を行います。
  • プロバイダープラグインのダウンロード
    • required_providers ブロックで指定されたプロバイダーのプラグインをダウンロードし、.terraform ディレクトリ内に保存します。
  • モジュールの取得
    • module ブロックで指定された外部の Terraform コード(モジュール)を、指定されたソースからダウンロードします。今回は使用していません。
  • 依存関係の解決
    • Terraform のコードで使用されているリソース間の依存関係を解決し、リソースが正しい順序で作成、更新、削除されるようになります。

terraform init は新しい Terraform プロジェクトを始める際や、プロバイダーやバックエンドの設定を変更した際に実行する必要があります。

terraform plan

terraform plan コマンドで、実行したときのリソースの差分を確認します。

terraform plan

▼実行結果例

user.emi:~/environment/test-terraform $ terraform 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_internet_gateway.igw will be created
  + resource "aws_internet_gateway" "igw" {
      + arn      = (known after apply)
      + id       = (known after apply)
      + owner_id = (known after apply)
      + tags     = {
          + "Name" = "igw"
        }
      + tags_all = {
          + "Name" = "igw"
        }
      + vpc_id   = (known after apply)
    }

  # aws_vpc.vpc will be created
  + resource "aws_vpc" "vpc" {
      + arn                                  = (known after apply)
      + cidr_block                           = "10.0.0.0/16"
      + default_network_acl_id               = (known after apply)
      + default_route_table_id               = (known after apply)
      + default_security_group_id            = (known after apply)
      + dhcp_options_id                      = (known after apply)
      + enable_dns_hostnames                 = true
      + enable_dns_support                   = true
      + enable_network_address_usage_metrics = (known after apply)
      + id                                   = (known after apply)
      + instance_tenancy                     = "default"
      + ipv6_association_id                  = (known after apply)
      + ipv6_cidr_block                      = (known after apply)
      + ipv6_cidr_block_network_border_group = (known after apply)
      + main_route_table_id                  = (known after apply)
      + owner_id                             = (known after apply)
      + tags                                 = {
          + "Name" = "vpc"
        }
      + tags_all                             = {
          + "Name" = "vpc"
        }
    }

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

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply"
now.
user.emi:~/environment/test-terraform $

# aws_internet_gateway.igw will be created で IGW が作成される予定であることと、# aws_vpc.vpc will be created で VPC が作成されることが分かります。main.tf で明示した cidr_blockenable_dns_hostnames などは値が入っていますが、vpc_id など作成されないと分からない値は (known after apply) となっているのが分かります。

terraform apply

terraform apply コマンドでリソースを作成します。途中で yes と入力してください。

terraform apply

▼実行結果例

user.emi:~/environment/test-terraform $ 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_internet_gateway.igw will be created
  + resource "aws_internet_gateway" "igw" {
      + arn      = (known after apply)
      + id       = (known after apply)
      + owner_id = (known after apply)
      + tags     = {
          + "Name" = "igw"
        }
      + tags_all = {
          + "Name" = "igw"
        }
      + vpc_id   = (known after apply)
    }

  # aws_vpc.vpc will be created
  + resource "aws_vpc" "vpc" {
      + arn                                  = (known after apply)
      + cidr_block                           = "10.0.0.0/16"
      + default_network_acl_id               = (known after apply)
      + default_route_table_id               = (known after apply)
      + default_security_group_id            = (known after apply)
      + dhcp_options_id                      = (known after apply)
      + enable_dns_hostnames                 = true
      + enable_dns_support                   = true
      + enable_network_address_usage_metrics = (known after apply)
      + id                                   = (known after apply)
      + instance_tenancy                     = "default"
      + ipv6_association_id                  = (known after apply)
      + ipv6_cidr_block                      = (known after apply)
      + ipv6_cidr_block_network_border_group = (known after apply)
      + main_route_table_id                  = (known after apply)
      + owner_id                             = (known after apply)
      + tags                                 = {
          + "Name" = "vpc"
        }
      + tags_all                             = {
          + "Name" = "vpc"
        }
    }

Plan: 2 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: yes

aws_vpc.vpc: Creating...
aws_vpc.vpc: Still creating... [10s elapsed]
aws_vpc.vpc: Creation complete after 11s [id=vpc-06bfd1f7689d7abbf]
aws_internet_gateway.igw: Creating...
aws_internet_gateway.igw: Creation complete after 1s [id=igw-031fce7a3bd8caf59]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
user.emi:~/environment/test-terraform $

48 行目の Plan: 2 to add, 0 to change, 0 to destroy. で、2 リソースの作成が計画されていることが分かります。Enter a value: でこのままリソースの操作を進めて良いか聞かれるので、今回は yes と入力してすすめました。無事 VPC と IGW の 2 リソースが作成されたことがわかります。

作成したリソースの確認

マネジメントコンソールから見ると、VPC と IGW が作成されているのが確認できます。

また、作成しておいた S3 バケット内には tfstate ファイルが作成され保存されていることが確認できます。

tfstate ファイルの中身の例をご参考にトグル内に貼っておきます。

tfstate ファイルの中身例(クリックで展開)

tf-test-20240402.json

{
  "version": 4,
  "terraform_version": "1.7.5",
  "serial": 5,
  "lineage": "93494a69-293e-0d58-0621-xxxxxxxxxx",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "aws_internet_gateway",
      "name": "igw",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "arn": "arn:aws:ec2:ap-northeast-1:123456789012:internet-gateway/igw-031fce7a3bd8caf59",
            "id": "igw-031fce7a3bd8caf59",
            "owner_id": "123456789012",
            "tags": {
              "Name": "igw"
            },
            "tags_all": {
              "Name": "igw"
            },
            "timeouts": null,
            "vpc_id": "vpc-06bfd1f7689d7abbf"
          },
          "sensitive_attributes": [],
          "private": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
          "dependencies": [
            "aws_vpc.vpc"
          ]
        }
      ]
    },
    {
      "mode": "managed",
      "type": "aws_vpc",
      "name": "vpc",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "schema_version": 1,
          "attributes": {
            "arn": "arn:aws:ec2:ap-northeast-1:123456789012:vpc/vpc-06bfd1f7689d7abbf",
            "assign_generated_ipv6_cidr_block": false,
            "cidr_block": "10.0.0.0/16",
            "default_network_acl_id": "acl-04018a1303263c4a4",
            "default_route_table_id": "rtb-01b53ee4baa50416b",
            "default_security_group_id": "sg-013140029634d4daf",
            "dhcp_options_id": "dopt-0d58ff88fe0ffca9d",
            "enable_dns_hostnames": true,
            "enable_dns_support": true,
            "enable_network_address_usage_metrics": false,
            "id": "vpc-06bfd1f7689d7abbf",
            "instance_tenancy": "default",
            "ipv4_ipam_pool_id": null,
            "ipv4_netmask_length": null,
            "ipv6_association_id": "",
            "ipv6_cidr_block": "",
            "ipv6_cidr_block_network_border_group": "",
            "ipv6_ipam_pool_id": "",
            "ipv6_netmask_length": 0,
            "main_route_table_id": "rtb-01b53ee4baa50416b",
            "owner_id": "123456789012",
            "tags": {
              "Name": "vpc"
            },
            "tags_all": {
              "Name": "vpc"
            }
          },
          "sensitive_attributes": [],
          "private": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
        }
      ]
    }
  ],
  "check_results": null
}

リソース作成済み状態で main.tf を修正した場合は、terraform apply を実行することでリソースの変更が行われます。

ちなみに Cloud9 のディレクトリ構成はこうなっています。

Terraform では CloudFormation スタックは生成されません。Terraform は裏で独自のプラグインを通じて AWS API を実行しリソースを作成します。

Terraform で構築したリソースを変更したい場合は、直接変更せずソースコードを修正して変更しましょう。

terraform destroy

作成したリソースを削除します。

terraform destroy

▼実行結果例

user.emi:~/environment/test-terraform $ terraform destroy
aws_vpc.vpc: Refreshing state... [id=vpc-0a699bce1795848e5]
aws_internet_gateway.igw: Refreshing state... [id=igw-0920414e3f5fc9bcb]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # aws_internet_gateway.igw will be destroyed
  - resource "aws_internet_gateway" "igw" {
      - arn      = "arn:aws:ec2:ap-northeast-1:123456789012:internet-gateway/igw-0920414e3f5fc9bcb" -> null
      - id       = "igw-0920414e3f5fc9bcb" -> null
      - owner_id = "123456789012" -> null
      - tags     = {
          - "Name" = "igw"
        } -> null
      - tags_all = {
          - "Name" = "igw"
        } -> null
      - vpc_id   = "vpc-0a699bce1795848e5" -> null
    }

  # aws_vpc.vpc will be destroyed
  - resource "aws_vpc" "vpc" {
      - arn                                  = "arn:aws:ec2:ap-northeast-1:123456789012:vpc/vpc-0a699bce1795848e5" -> null
      - assign_generated_ipv6_cidr_block     = false -> null
      - cidr_block                           = "10.0.0.0/16" -> null
      - default_network_acl_id               = "acl-027c4135ccfa90e82" -> null
      - default_route_table_id               = "rtb-0bd574ab733579e8b" -> null
      - default_security_group_id            = "sg-06cf8257475a7b970" -> null
      - dhcp_options_id                      = "dopt-0d58ff88fe0ffca9d" -> null
      - enable_dns_hostnames                 = true -> null
      - enable_dns_support                   = true -> null
      - enable_network_address_usage_metrics = false -> null
      - id                                   = "vpc-0a699bce1795848e5" -> null
      - instance_tenancy                     = "default" -> null
      - ipv6_netmask_length                  = 0 -> null
      - main_route_table_id                  = "rtb-0bd574ab733579e8b" -> null
      - owner_id                             = "123456789012" -> null
      - tags                                 = {
          - "Name" = "vpc"
        } -> null
      - tags_all                             = {
          - "Name" = "vpc"
        } -> null
    }

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

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

aws_internet_gateway.igw: Destroying... [id=igw-0920414e3f5fc9bcb]
aws_internet_gateway.igw: Destruction complete after 1s
aws_vpc.vpc: Destroying... [id=vpc-0a699bce1795848e5]
aws_vpc.vpc: Destruction complete after 0s

Destroy complete! Resources: 2 destroyed.
user.emi:~/environment/test-terraform $

これで、作成したリソースの削除が完了しました。

参考