こんにちは、末廣です。 みなさん、普段どのようにして Terraform 環境を構成し、デプロイしているでしょうか?
自分の PC、オフィスにある PC、開発用 EC2、tfaction、CodeBuild !?
いずれの環境でも基本的にはアウトバウンドでインターネットに接続できる状態であるはずです。 では、インターネットに接続できないような環境で可能でしょうか?
本ブログは terraform init ~ terraform apply をインターネットに接続できないオフラインの AWS 環境(EC2)でやってみたものをまとめたものになります。
※ AWS 内のパブリックネットワークは経由する想定です。
構成
以下図が Terraform を実行する AWS 環境の構成図になります。

VPC を作成した時に何も考えずに作成する Internet Gateway が存在しません。パブリックサブネットも存在しません。 代わりに、EC2 に人がログインするための経路を残すため、Systems Manager のエンドポイントを配置します。

EC2 は Amazon Linux 2023 を使います。当然この OS には Terraform のパッケージが入っていないため、どこかしらからパッケージをインストールする必要があります。 そのために S3 バケットを配置し、Gateway Endpoint 経由で EC2 が接続できるようにします。
※ オブジェクトを格納する作業自体はインターネット経由で実施します)

また、AWS サービスを作成するために、サービスごとのインターフェースエンドポイントも必要となります。
例えば VPC では com.amazonaws.region.ec2 が必要です。また、Terraform AWS プロバイダーは、認証情報の検証のために(terraform plan や terraform applyする時に)STS API(GetCallerIdentity など)を呼び出すため、com.amazonaws.ap-northeast-1.sts エンドポイントも必要となります。

では、実際に作業していきましょう。
セットアップ
Terraform パッケージのダウンロード
apt-get terraform yum install terraform brew install terraform dnf install terraform … もちろんできません。インターネットに接続できないのです。
binary の zip ファイルを S3 にいれます。
↓↓↓↓ こちらからダウンロード ↓↓↓↓
Amazon Linux の x86 で検証したので対象は画像の通りです。

Terraform Provider パッケージのダウンロード
AWS にデプロイするためには provider の plugin も必要となります。
↓↓↓↓ こちらからダウンロード ↓↓↓↓
上記と合わせて x86 用の terraform-provider-aws_6.0.0_linux_386.zip をダウンロードします。
マネジメントコンソール等から(ここは御愛嬌)これらの zip ファイルを S3 に格納しましょう。
EC2 での作業
セッションマネージャー経由でログインし、任意のユーザにスイッチします。
zip ファイルのダウンロードと解凍
適当なディレクトリにそれぞれ zip ファイルを S3 からダウンロードします。
$ aws s3 cp s3://terraform-plugins-mirror-xxxxxxxxxxxx/terraform_1.12.2_linux_386.zip ./ # 結果 download: s3://terraform-plugins-mirror-xxxxxxxxxxxx/terraform_1.12.2_linux_386.zip to ./terraform_1.12.2_linux_386.zip
aws s3 cp s3://terraform-plugins-mirror-xxxxxxxxxxxx/terraform-provider-aws_6.0.0_linux_386.zip ./ # 結果 download: s3://terraform-plugins-mirror-xxxxxxxxxxxx/terraform-provider-aws_6.0.0_linux_386.zip to ./terraform-provider-aws_6.0.0_linux_386.zip
unzip します
$ unzip terraform-provider-aws_6.0.0_linux_386.zip -bash: unzip: command not found
!!!
Amazon Linux 2023 には unzip はいないようです。 でも大丈夫です。Amazon Linux リポジトリは、S3 バケットでホストされているので、ここからインストールできます。
$ sudo dnf install zip
terraform command を打てるまで
terraform_1.12.2_linux_386.zip を解凍すると出現する terraform (binary ファイル) を /usr/local/bin/ 配下に移動します。
ls -la total 111208 drwxr-xr-x. 3 ec2-user ec2-user 96 Jun 30 13:20 . drwx------. 5 ec2-user ec2-user 103 Jun 30 13:06 .. -rw-r--r--. 1 ec2-user ec2-user 4922 Jun 11 10:22 LICENSE.txt -rwxr-xr-x. 1 ec2-user ec2-user 87740600 Jun 11 10:22 terraform -rw-r--r--. 1 ec2-user ec2-user 26122131 Jun 25 20:28 terraform_1.12.2_linux_386.zip
$ sudo mv terraform /usr/local/bin/
これで terraform コマンドが実行できるようになったはずです!
$ terraform version # 結果 Terraform v1.12.2 on linux_386
やりました!
とりあえずこの状態で、いつものごとく試しに providers.tf を作って terraform init してみます。
provider "aws" { region = "ap-northeast-1" }
$ terraform init Initializing the backend... Initializing provider plugins... - Finding latest version of hashicorp/aws... - Installing hashicorp/aws v6.0.0... ╷ │ Error: Failed to install provider │ │ Error while installing hashicorp/aws v6.0.0: provider binary not found: │ could not find executable file starting with terraform-provider-aws ╵
aws provider がないと怒られました。まあ当然です。続けて作業進めましょう。
terraform init が成功するまで
上記エラーの通り provider の binary ファイルがないので既にダウンロード、解凍した provider の binary ファイルを該当ディレクトリを作成し、移動します。
$ ls LICENSE.txt terraform-provider-aws_6.0.0_linux_386.zip terraform-provider-aws_v6.0.0_x5
$ mkdir -p ~/.terraform.d/plugins/registry.terraform.io/hashicorp/aws/6.0.0./linux_386 $ mv terraform-provider-aws_v6.0.0_x5 ~/.terraform.d/plugins/registry.terraform.io/hashicorp/aws/6.0.0./linux_386/
プロバイダーの取得方法を .terraformrc に記載します
provider_installation { filesystem_mirror { path = "/home/ec2-user/.terraform.d/plugins" } direct { exclude = ["registry.terraform.io/hashicorp/*"] } }
これで準備は完了です。
terraform 実行
providers.tf に以下を記載します。
terraform { required_providers { aws = "6.0.0" } }
init します。
$ terraform init Initializing the backend... Initializing provider plugins... - Finding hashicorp/aws versions matching "6.0.0"... - Installing hashicorp/aws v6.0.0... - Installed hashicorp/aws v6.0.0 (unauthenticated) 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. ╷ │ Warning: Incomplete lock file information for providers │ │ Due to your customized provider installation methods, │ Terraform was forced to calculate lock file checksums │ locally for the following providers: │ - hashicorp/aws │ │ The current .terraform.lock.hcl file only includes │ checksums for linux_386, so Terraform running on another │ platform will fail to install these providers. │ │ To calculate additional checksums for another platform, │ run: │ terraform providers lock -platform=linux_amd64 │ (where linux_amd64 is the platform to generate) ╵ 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.
成功!?
何やら警告が出てきていますが、オフラインで実行している現環境では気にしなくてよいでしょう。ほかプラットフォーム(ARM など)でチェックサムに失敗するとの内容ですが、今回はあえて Linux x86 用のものだけをインストールしているため、関係のない内容となっています。
つまり、成功です!
構築してみる
簡単な VPC を作成してみます。vpc.tf を作り…
resource "aws_vpc" "this" { cidr_block = "10.0.0.0/16" enable_dns_support = true enable_dns_hostnames = true tags = { Name = "offline-kara-tsukutta" } }
terraform plan し…
$ 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_vpc.this will be created + resource "aws_vpc" "this" { + 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) + region = "ap-northeast-1" + tags = { + "Name" = "offline-kara-tsukutta" } + tags_all = { + "Name" = "offline-kara-tsukutta" } } Plan: 1 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.
terraform apply !
$ 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_vpc.this will be created + resource "aws_vpc" "this" { + 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) + region = "ap-northeast-1" + tags = { + "Name" = "offline-kara-tsukutta" } + tags_all = { + "Name" = "offline-kara-tsukutta" } } 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: yes aws_vpc.this: Creating... aws_vpc.this: Still creating... [00m10s elapsed] aws_vpc.this: Creation complete after 11s [id=vpc-0ac3f9edaadcd2422] Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
できました!
最後に注意点
サービスを作成する時に、必要なインターフェースエンドポイントが必要と記載しましたが、かなり重要な注意すべき点があります。
↓ 再掲

com.amazonaws.iam と名のついてるエンドポイントをよく見てください。region がついていません。グローバルサービスであることが理由ですが、これは東京リージョン等で作成することが不可となっています。バージニア北部では問題なく作成できるため、IAM リソースを作成する場合は本環境をバージニア北部で作成する必要があります。

また、同様に CloudFront 等のリソースはインターフェースエンドポイントが存在しないため、本環境からの構築は難しいと考えられます。
まとめ
本ブログでは、インターネットに接続できない EC2 環境から Terraform のセットアップ、実行をしてみました。
最後の記載した点の制約がある部分や、Terraform アップデート運用が大変であるという点から、あまり実用的ではない構成ではあるとは思います。 要件としては少ないかもしれませんが、最低限の構成をを最大のセキュリティで実施することはできるでしょう。
末廣 満希(執筆記事の一覧)
2022年新卒入社です。ここに何かかっこいい一言を書くことができるエンジニアになれるように頑張ります。