こんにちは。Tomoyuki(@tomoyuki65)です。
直近で非同期処理のためにFloci(フローシー)を試しましたが、Terraformの練習にも使えそうだなと思い、試してみることにしました。
そこでこの記事では、DockerとFlociによるTerraformの実践入門についてご紹介します。
DockerとFloci(フローシー)でTerraform実践入門|S3でtfstate管理・既存インフラの取り込み・モジュール化
事前準備について
この記事ではDocker環境、AWS CLI、Terraform CLIを利用するため、後述の内容を試したい場合は、以下の関連記事を参考に各種コマンドを使えるように準備して下さい。
関連記事


ローカル開発環境の構築
次にローカル開発環境を構築するため、以下のコマンドを実行して各種ファイルを作成します。
$ mkdir floci-terraform && cd floci-terraform
$ touch compose.yml .gitignore
次に作成したファイルを以下のように記述します。
・「compose.yml」
services:
floci:
container_name: floci
image: floci/floci:latest
ports:
- "4566:4566"
volumes:
- floci_tf_data:/app/data
- /var/run/docker.sock:/var/run/docker.sock
environment:
TZ: Asia/Tokyo
FLOCI_STORAGE_MODE: hybrid
volumes:
floci_tf_data:
・「.gitignore」
.DS_Store
.env
# Terraform
.terraform/
# Terraform state
*.tfstate
*.tfstate.*
# Terraform crash logs
crash.log
crash.*.log
# Terraform plan files
*.tfplan
次に以下のコマンドを実行し、Dockerコンテナを起動します。
$ docker compose up -d
次に以下のコマンドを実行し、ステータスを確認します。
$ docker compose ps
実行後、以下のようにDockerコンテナが起動していればOKです。

Terraformのtfstate管理用のS3バケットを作成
次にflociコンテナに対して、Terraformのtfstate管理用のS3バケットを作成するため、以下のコマンドを実行します。
aws-local s3api create-bucket \
--bucket floci-terraform-state \
--create-bucket-configuration LocationConstraint=ap-northeast-1
※事前に作成したエイリアス「aws-local」を利用し、flociコンテナに向けてコマンドを実行しています。S3バケット名「floci-terraform-state」、リージョン「ap-northeast-1」としています。
次に以下のコマンドを実行し、S3バケットが作成されたことを確認します。
aws-local s3 ls
コマンド実行後、以下のようにバケットが作成されていればOKです。

次に作成したS3バケットに対して、バージョニング設定(変更履歴を残す設定)を追加するため、以下のコマンドを実行します。
aws-local s3api put-bucket-versioning \
--bucket floci-terraform-state \
--versioning-configuration Status=Enabled
次に以下のコマンドを実行し、対象のS3バケットのバージョニング設定の状態を確認します。
aws-local s3api get-bucket-versioning \
--bucket floci-terraform-state
コマンド実行後、以下のようにStatusが「Enabled」になっていればOKです。

次にS3バケットの暗号化について、実際のAWS環境でS3バケットを作成した場合、デフォルトでSSE-S3の自動暗号化がされますが、今回利用しているflociコンテナでは再現しないため、以下のコマンドを実行し、明示的に暗号化設定を追加します。
aws-local s3api put-bucket-encryption \
--bucket floci-terraform-state \
--server-side-encryption-configuration \
'{"Rules": [{"ApplyServerSideEncryptionByDefault": {"SSEAlgorithm": "AES256"}}]}'
※尚、暗号化の種類として「SSE-KMS」などを利用することも可能です。
次に以下のコマンドを実行し、対象のS3バケットの暗号化設定の状態を確認します。
aws-local s3api get-bucket-encryption \
--bucket floci-terraform-state
コマンド実行後、以下のように暗号化設定が追加されていればOKです。

次にパブリックアクセスをブロックする設定を追加するため、以下のコマンドを実行します。
aws-local s3api put-public-access-block \
--bucket floci-terraform-state \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
・設定値一覧
| 設定 | 意味 | 具体的に防ぐもの |
|---|---|---|
BlockPublicAcls=true |
パブリックACLの設定を拒否 | public-read など、公開設定のACLを新しく設定することを防ぐ |
IgnorePublicAcls=true |
パブリックACLを無視 | 既に設定されているパブリックACLがあっても、それを公開設定として利用しない |
BlockPublicPolicy=true |
パブリックなバケットポリシーを拒否 | Principal: "*" など、不特定多数にアクセスを許可するバケットポリシーの設定を防ぐ |
RestrictPublicBuckets=true |
パブリックポリシーの影響を制限 | パブリックなバケットポリシーが存在する場合でも、AWSアカウント外などからのアクセスを制限する |
次に以下のコマンドを実行し、対象のS3バケットのパブリックアクセスのブロック設定の状態を確認します。
aws-local s3api get-public-access-block \
--bucket floci-terraform-state
コマンド実行後、以下のようにパブリックアクセスのブロック設定が追加されていればOKです。

Terraformの初期設定
次にTerraformの初期設定をするため、まずは以下のコマンドを実行して各種ファイルを作成します。
$ mkdir -p infra/terraform/environments/local
$ touch infra/terraform/environments/local/provider.tf infra/terraform/environments/local/backend.tf infra/terraform/environments/local/main.tf
※Terraformの設定ファイルについては、対象の環境に合わせて「environments」配下にディレクトリを切って各種ファイルを作成します。また、Terraformはファイルを分割しても.tfファイルをまとめて読み込む仕様になっています。
次に作成したファイルをそれぞれ以下のように記述します。
・「infra/terraform/environments/local/provider.tf」
provider "aws" {
region = "ap-northeast-1"
access_key = "test"
secret_key = "test"
endpoints {
s3 = "http://localhost:4566"
s3control = "http://localhost:4566"
sts = "http://localhost:4566"
}
}
※provider.tfは「Terraformがどこを操作するのか」に関する設定です。今回の例ではローカルのflociコンテナへ接続します。
・「infra/terraform/environments/local/backend.tf」
terraform {
backend "s3" {
bucket = "floci-terraform-state"
key = "terraform.tfstate"
region = "ap-northeast-1"
use_lockfile = true
endpoints = {
s3 = "http://localhost:4566"
sts = "http://localhost:4566"
}
}
}
※backend.tfは「Terraform Stateをどこに保存するか」に関する設定です。今回の例ではS3バケットに保存する設定です。尚、「use_lockfile = true」については、Terraform Stateのロック(state locking)を有効にする設定で、TerraformがS3上にロックファイルを作って、同じStateを複数のTerraformプロセスが同時に変更するのを防ぎます。
・「infra/terraform/environments/local/main.tf」
terraform {
required_version = ">= 1.15.8"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.58"
}
}
}
※main.tfは「何を作るか」を記述するファイルです。required_versionはTerraform CLIのバージョン、required_providers.aws.versionはAWS Providerのバージョンで、それぞれバージョン固定をするための記述です。特にAWS Providerのバージョンは固定推薦です。
次に以下のコマンドを実行し、Terraformの初期化をします。
$ AWS_PROFILE=local terraform \
-chdir=infra/terraform/environments/local init
※ローカル環境への接続では「AWS_PROFILE=local」を利用します。
実行後、以下のように正常終了すればOKです。

TerraformでS3バケットのリソース追加を試す
次にTerraformでS3バケットのリソース追加を試すため、以下のコマンドを実行してS3リソース用のファイルを作成します。
$ touch infra/terraform/environments/local/s3.tf
・「infra/terraform/environments/local/s3.tf」
resource "aws_s3_bucket" "example" {
bucket = "floci-example-bucket"
}
次にTerraformのplanコマンドを実行し、変更箇所を確認します。
$ AWS_PROFILE=local terraform \
-chdir=infra/terraform/environments/local plan
実行後、以下のようにリソースの追加の表示がされればOKです。

次にTerraformのapplyコマンドを実行し、変更を実行します。
$ AWS_PROFILE=local terraform \
-chdir=infra/terraform/environments/local apply -auto-approve
※自動承認用のオプション「-auto-approve」を付けています。
実行後、以下のように正常終了すればOKです。

次に再度planコマンドを実行してみます。
$ AWS_PROFILE=local terraform \
-chdir=infra/terraform/environments/local plan
実行後、変更箇所がなければOKです。

次に以下のコマンドを実行し、S3バケット「floci-example-bucket」が作成されたことを確認します。
aws-local s3 ls
コマンド実行後、以下のようにバケットが作成されていればOKです。

次にTerraformのsteteのバージョン情報を確認するため、以下のコマンドを実行します。
aws-local s3api list-object-versions \
--bucket floci-terraform-state \
--prefix terraform.tfstate \
| jq '.Versions[] | select(.Key == "terraform.tfstate")'
※ロック用のファイル「terraform.tfstate.tflock」を除外して表示させています。
実行後、以下のように一つの履歴が表示されればOKです。

※IsLatestが「true」のものが現在のバージョン
既存のインフラ設定をtfstateに反映させる方法
次に既に作成済みのインフラ設定があり、それをTerraformのtfstateに反映させる方法について試します。
事前に新しいS3バケットを作成
まずは以下のコマンドを実行し、新しくS3バケットを作成します。
aws-local s3api create-bucket \
--bucket my-sample-1 \
--create-bucket-configuration LocationConstraint=ap-northeast-1
次に以下のコマンドを実行し、バージョニング設定を追加します。
aws-local s3api put-bucket-versioning \
--bucket my-sample-1 \
--versioning-configuration Status=Enabled
次に明示的に暗号化設定を追加します。
aws-local s3api put-bucket-encryption \
--bucket my-sample-1 \
--server-side-encryption-configuration \
'{"Rules": [{"ApplyServerSideEncryptionByDefault": {"SSEAlgorithm": "AES256"}}]}'
次に以下のコマンドを実行し、パブリックアクセスブロック設定を追加します。
aws-local s3api put-public-access-block \
--bucket my-sample-1 \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
Terraformのstateに反映したいリソース定義を追加
次にTerraformのstateに反映したいリソース定義を対象のファイルに追加します。
今回はS3のリソースを追加する例なので、ファイル「infra/terraform/environments/local/s3.tf」を以下のように修正し、リソース定義を追加します。
resource "aws_s3_bucket" "example" {
bucket = "floci-example-bucket"
}
# サンプルバケット1の設定
resource "aws_s3_bucket" "my_sample_1" {
bucket = "my-sample-1"
}
resource "aws_s3_bucket_versioning" "my_sample_1" {
bucket = aws_s3_bucket.my_sample_1.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "my_sample_1" {
bucket = aws_s3_bucket.my_sample_1.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_s3_bucket_public_access_block" "my_sample_1" {
bucket = aws_s3_bucket.my_sample_1.id
block_public_acls = true
ignore_public_acls = true
block_public_policy = true
restrict_public_buckets = true
}
※既存のS3バケットには、バージョニング設定、暗号化設定、パブリックアクセスブロック設定がされており、Terraformのリソース定義としては4つの定義を追加する必要がある場合の例としています。
次に以下のコマンドを実行し、差分確認をします。
$ AWS_PROFILE=local terraform \
-chdir=infra/terraform/environments/local plan
実行後、以下のように追加したリソース定義の差分が確認できればOKです。


次にimportコマンドを利用し、追加したリソース定義について、それぞれTerraformのtfstateに反映させます。
・S3バケットのリソースをインポート
AWS_PROFILE=local terraform \
-chdir=infra/terraform/environments/local \
import aws_s3_bucket.my_sample_1 my-sample-1
実行後、正常終了すればOKです。

・バージョニング設定のリソースをインポート
AWS_PROFILE=local terraform \
-chdir=infra/terraform/environments/local \
import aws_s3_bucket_versioning.my_sample_1 my-sample-1
実行後、正常終了すればOKです。

・暗号化設定のリソースをインポート
AWS_PROFILE=local terraform \
-chdir=infra/terraform/environments/local \
import aws_s3_bucket_server_side_encryption_configuration.my_sample_1 my-sample-1
実行後、正常終了すればOKです。

・パブリックアクセスブロック設定のリソースをインポート
AWS_PROFILE=local terraform \
-chdir=infra/terraform/environments/local \
import aws_s3_bucket_public_access_block.my_sample_1 my-sample-1
実行後、正常終了すればOKです。

次に再度以下のコマンドを実行し、差分確認をします。
$ AWS_PROFILE=local terraform \
-chdir=infra/terraform/environments/local plan
実行後、以下のように追加したリソース定義の差分がなくなればOKです。

このように、「.tfファイルにリソース定義追加 → planコマンドで差分確認 → importコマンドでtfstateに反映 → 再度planコマンドで差分確認」を繰り返し、既存のインフラ設定をtfstateに反映させることが可能です。
既存リソースの詳細設定がわからない場合について
上記の例では、S3バケットに対して「バージョニング設定」、「暗号化設定」、「パブリックアクセスブロック設定」の3つの設定を明示的に追加しているため、.tfファイルに追加すべきリソース定義の数も簡単に把握できました。
一方で、実務などで既存のインフラ設定をTerraformのtfstateに反映させる場合、既存のリソースに対してどのような設定が適用されているかがわからないことがあると思います。
その場合、まずは既存のリソースにどのような設定が適用されているかを調査・整理するところから始め、その結果をもとにTerraformのtfstateに反映させていくことになります。
具体的な作業手順としては、以下のように進めます。
↓
② Terraform Registryから適用できる項目に関するリソース名を確認し、調査用の一覧表に反映させる。(Terraformが対応していない設定もあるので、その場合はそれがわかるように色を付けること)
↓
③ 調査用の一覧表が完成後、インフラにあるCLIやAPIなどから対象のリソースに対して適用されている設定を確認し、適用されていれば調査用の一覧表にチェックを付ける。
↓
④ 調査用の一覧表でのチェックが完了後、.tfファイルに追加すべきリソース定義の数などが把握できるようになるため、それを確認する。
↓
⑤ ある程度範囲を絞りながら.tfファイルにリソース定義を追加し、importコマンドやplanコマンドを用いてTerraformのtfstateに反映させていく。
↓
⑥ 実際のインフラ設定とTerraformのtfstateの差分が無くなるまで繰り返し、planコマンドの結果が「No changes」になれば完了とする。
※このように既存のインフラ設定を後からTerraformで管理できるようにさせるのは難しいのがわかります。また、main.tfに記述したrequired_providersのバージョン設定などに応じて、Terraformのリソース定義名などが変わったりしている場合もあるので、その点はご注意下さい。
インフラの状態は変更せずに.tfファイルの修正をtfstateのみに反映させる方法
次にインフラの状態を変更せずに.tfファイルの修正をtfstateのみに反映させる方法について試します。
.tfファイルに定義したリソース名を修正する
まずは.tfファイルに定義したリソース名を修正してみます。
今回は上記のファイル「s3.tf」で追加したS3バケットのリソース名「example」を「example_bucket」に修正するため、まずはファイル「infra/terraform/environments/local/s3.tf」を以下のように修正します。
resource "aws_s3_bucket" "example_bucket" {
bucket = "floci-example-bucket"
}
# サンプルバケット1の設定
resource "aws_s3_bucket" "my_sample_1" {
bucket = "my-sample-1"
}
resource "aws_s3_bucket_versioning" "my_sample_1" {
bucket = aws_s3_bucket.my_sample_1.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "my_sample_1" {
bucket = aws_s3_bucket.my_sample_1.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_s3_bucket_public_access_block" "my_sample_1" {
bucket = aws_s3_bucket.my_sample_1.id
block_public_acls = true
ignore_public_acls = true
block_public_policy = true
restrict_public_buckets = true
}
次に以下のコマンドを実行し、差分確認をします。
$ AWS_PROFILE=local terraform \
-chdir=infra/terraform/environments/local plan
実行後、以下のように追加したリソース定義の差分が確認できればOKです。


次に以下のコマンドを実行し、リソース名の修正をtfstateに反映します。
$ AWS_PROFILE=local terraform \
-chdir=infra/terraform/environments/local \
state mv aws_s3_bucket.example aws_s3_bucket.example_bucket
※リソース名の修正はコマンド「terraform state mv 変更前のリソース名 変更後のリソース名」を利用します。
実行後、以下のように正常終了すればOKです。

次に再度以下のコマンドを実行し、差分確認をします。
$ AWS_PROFILE=local terraform \
-chdir=infra/terraform/environments/local plan
実行後、以下のように追加したリソース定義の差分がなくなればOKです。

尚、今回は.tfファイルの修正後、コマンド「state mv」を利用して修正を反映させましたが、.tfファイルにmovedブロックを記述し、その後にコマンド「apply」を実行して反映させる方法もあります。
・movedブロックを利用する場合のs3.tfの修正例
resource "aws_s3_bucket" "example_bucket" {
bucket = "floci-example-bucket"
}
moved {
from = aws_s3_bucket.example
to = aws_s3_bucket.example_bucket
}
# サンプルバケット1の設定
resource "aws_s3_bucket" "my_sample_1" {
bucket = "my-sample-1"
}
resource "aws_s3_bucket_versioning" "my_sample_1" {
bucket = aws_s3_bucket.my_sample_1.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "my_sample_1" {
bucket = aws_s3_bucket.my_sample_1.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_s3_bucket_public_access_block" "my_sample_1" {
bucket = aws_s3_bucket.my_sample_1.id
block_public_acls = true
ignore_public_acls = true
block_public_policy = true
restrict_public_buckets = true
}
※apply後はmovedブロックを削除しても影響ないです。movedブロックを利用した場合、変更履歴を残せるので、チーム開発でレビューする際には有効です。
・movedブロック追加後にplanを実行した例

リソース定義をモジュール化する方法
次にリソース定義をモジュール化し、.tfファイルの構成などを修正する方法を試します。
今回は上記のファイル「s3.tf」にあるサンプルバケット1(リソース名「my_sample_1」)に関するものをモジュール化してみます。
まずは以下のコマンドを実行し、モジュール用のファイルを作成します。
$ mkdir -p infra/terraform/modules/s3
$ touch infra/terraform/modules/s3/variables.tf infra/terraform/modules/s3/main.tf
次に作成したファイルをそれぞれ以下のように修正します。
・「infra/terraform/modules/s3/variables.tf」
variable "bucket_name" {
type = string
}
※これは変数定義です。
・「infra/terraform/modules/s3/main.tf」
resource "aws_s3_bucket" "this" {
bucket = var.bucket_name
}
resource "aws_s3_bucket_versioning" "this" {
bucket = aws_s3_bucket.this.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
bucket = aws_s3_bucket.this.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_s3_bucket_public_access_block" "this" {
bucket = aws_s3_bucket.this.id
block_public_acls = true
ignore_public_acls = true
block_public_policy = true
restrict_public_buckets = true
}
※これはS3バケット用のモジュール定義です。
次にファイル「infra/terraform/environments/local/s3.tf」を上記で作成したモジュール定義を使うように修正します。
resource "aws_s3_bucket" "example_bucket" {
bucket = "floci-example-bucket"
}
# サンプルバケット1の設定
module "my_sample_1" {
source = "../../modules/s3"
bucket_name = "my-sample-1"
}
moved {
from = aws_s3_bucket.my_sample_1
to = module.my_sample_1.aws_s3_bucket.this
}
moved {
from = aws_s3_bucket_versioning.my_sample_1
to = module.my_sample_1.aws_s3_bucket_versioning.this
}
moved {
from = aws_s3_bucket_server_side_encryption_configuration.my_sample_1
to = module.my_sample_1.aws_s3_bucket_server_side_encryption_configuration.this
}
moved {
from = aws_s3_bucket_public_access_block.my_sample_1
to = module.my_sample_1.aws_s3_bucket_public_access_block.this
}
次に以下のコマンドを実行し、モジュール定義を取り込みます。
$ AWS_PROFILE=local terraform \
-chdir=infra/terraform/environments/local init
実行後、以下のように正常終了すればOKです。

次に以下のコマンドを実行し、差分確認をします。
$ AWS_PROFILE=local terraform \
-chdir=infra/terraform/environments/local plan
実行後、以下のようにmovedに関する記述のみ表示されていればOKです。

次に以下のコマンドを実行し、変更を実行します。
$ AWS_PROFILE=local terraform \
-chdir=infra/terraform/environments/local apply -auto-approve
実行後、以下のように正常終了すればOKです。

次に以下のコマンドを実行し、差分確認をします。
$ AWS_PROFILE=local terraform \
-chdir=infra/terraform/environments/local plan
実行後、以下のように追加したリソース定義の差分がなくなればOKです。

Terraform CLIでよく使うコマンド一覧
| コマンド | 何をする? | よく使う場面 |
|---|---|---|
terraform init |
Terraformの作業ディレクトリを初期化する | 最初に実行。ProviderやModuleの取得など |
terraform plan |
これから行われる変更内容を確認する | applyする前の確認 |
terraform apply |
Terraformの設定を実際のインフラに反映する | リソースの作成・変更 |
terraform destroy |
Terraformで管理しているリソースを削除する | 検証環境などを全部消すとき |
terraform fmt |
.tfファイルをTerraform標準のフォーマットに整形する |
コードを書いたあと |
terraform validate |
Terraformの設定が正しいか検証する | Gitコミット前、CIなど |
terraform output |
outputの値を表示する |
IPアドレスやURLなどを確認 |
terraform show |
TerraformのStateやPlanの内容を表示する | 現在の状態・変更内容を詳しく見る |
terraform state list |
Stateに登録されているリソース一覧を表示する | Terraformの管理対象を確認 |
terraform state show |
特定リソースのState情報を表示する | リソースの詳細を確認 |
terraform workspace list |
Workspaceの一覧を表示する | Workspaceを使っている場合 |
terraform workspace select |
使用するWorkspaceを切り替える | dev / staging / prodなどの切り替え |
terraform workspace new |
新しいWorkspaceを作成する | 環境を追加するとき |
terraform console |
Terraformの式を対話的に評価する | 変数・関数・式の動作確認 |
Terraformを本番環境で使う場合のIAMについて
Terraformを本番環境で利用する場合は、Terraform専用のIAMロールを用意し、そのロールに必要な権限を付与したうえで、コマンド「terraform apply」を実行させるような構成にするのが一般的です。
例えばGitHub ActionsからTerraformを実行する場合は、GitHub Actionsから専用のIAMロールを利用してAWSへアクセスします。
その際にOIDC認証(パスワードや固定のアクセスキーを渡さず、信頼関係のあるサービス間でトークンを使って認証する仕組み)を利用すると、GitHub ActionsにAWSのアクセスキーを直接持たせずに認証することが可能です。
尚、今回利用しているflociコンテナについては、IAMロールなどのリソースを作成すること自体はできるものの、作成したIAMロールを使った実際の権限管理や認証までは本番環境と同じようには再現できないため、その点にはご注意下さい。
最後に
今回はDockerとFlociによるTerraformの実践入門についてご紹介しました。
Floci(フローシー)は、LocalStackの代替としてAWS環境をローカルで再現し、非同期処理を学ぶのに便利でしたが、それに加えてIaC(Infrastructure as Code)の代表的なツールであるTerraformの学習で利用するのにも最適です。
実際のAWS環境を用意せずにTerraformを試せるため、これからTerraformを学びたい方はぜひ参考にしてみて下さい。


コメント