AWSを使わずMinIOでTerraformのstate共有基盤を用意する設計|互換オプションの指定と最小権限のアクセスキー発行

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Terraform > AWSを使わずMinIOでTerraformのstate共有基盤を用意する設計|互換オプションの指定と最小権限のアクセスキー発行
「Terraformのstateファイルをチームで安全に共有したい。でもそのためだけにAWSアカウントを用意するのは大げさすぎる。」
そういう場面で使えるのが、S3互換APIを実装したオブジェクトストレージ「MinIO」です。自分たちが管理するLinuxサーバーにMinIOを立ててS3互換バックエンドとして指定すれば、AWSなしでtfstateの共有基盤が成立します。

この記事では、MinIOをTerraform backendに接続するための設定に絞って解説します。「どのポートで立てるか」「force_path_styleとは何か」「アクセスキーの必要最小権限はどう書くか」という実装の観点で説明します。Rocky Linux / RHEL9での動作を前提にしています。

この記事のポイント

・MinIOのS3互換バックエンドにはforce_path_styleとskip系3オプションの設定が必須
・regionはMinIOが使わない値でも us-east-1 のようなダミー文字列の指定が必要
・最小権限は GetObject・PutObject・DeleteObject・ListBucket・GetBucketVersioning の5権限
・Terraform 1.10以降は use_lockfile=true でDynamoDB不要のstateロックが使える


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

MinIOがS3互換バックエンドとして使える理由

TerraformのS3バックエンドは、厳密には「AWSのS3」ではなく「S3互換API」に対応しています。エンドポイントURLを差し替えるだけで、S3互換のオブジェクトストレージなら何でも接続先として使えるよう設計されています。

MinIOはS3 APIをほぼ完全に実装しており、tfstateの読み書き(GetObject・PutObject)とstateロック(PutObject・DeleteObject)に必要なAPIもカバーしています。ただし、いくつかのAWS固有の検証処理が邪魔をするため、それをスキップする設定が必要です。この記事ではその設定を一つひとつ説明します。

MinIOをLinuxサーバーへインストールしてstateバケットを用意する

1. バイナリのダウンロードとsystemdサービス登録

MinIOはRPMパッケージとシングルバイナリの両方で配布されています。ここではバイナリを直接インストールする方法を使います。Rocky Linux 9 / RHEL9で確認済みの手順です。

# MinIOバイナリをダウンロードして実行権限を付与 wget https://dl.min.io/server/minio/release/linux-amd64/minio chmod +x minio mv minio /usr/local/bin/ # データ保管用ディレクトリとサービスユーザーを作成 useradd -r -s /sbin/nologin minio-user mkdir -p /data/minio chown minio-user:minio-user /data/minio

次に環境変数ファイルを用意します。MINIO_ROOT_PASSWORDは後でmcコマンドに使います。

# /etc/default/minio を作成 cat << 'EOF' > /etc/default/minio MINIO_VOLUMES="/data/minio" MINIO_ROOT_USER=minioadmin MINIO_ROOT_PASSWORD=ChangeMeToStrongPassword EOF

続いてsystemdのUnitファイルを作成し、サービスとして起動します。

# /etc/systemd/system/minio.service を作成 cat << 'EOF' > /etc/systemd/system/minio.service [Unit] Description=MinIO Object Storage After=network-online.target [Service] User=minio-user Group=minio-user EnvironmentFile=/etc/default/minio ExecStart=/usr/local/bin/minio server $MINIO_VOLUMES --address :9000 --console-address :9090 Restart=always LimitNOFILE=65536 [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable --now minio systemctl status minio

正常に起動すると次のような出力が確認できます。

* minio.service - MinIO Object Storage Loaded: loaded (/etc/systemd/system/minio.service; enabled; preset: disabled) Active: active (running) since Thu 2026-09-18 09:12:34 JST; 5s ago Main PID: 3841 (minio) Memory: 138.2M CPU: 1.224s CGroup: /system.slice/minio.service └─3841 /usr/local/bin/minio server /data/minio --address :9000 --console-address :9090

2. バケットの作成とポート疎通確認

MinIOが起動したら、Terraformがstateを格納するバケットを作ります。バケット作成にはMinIO Clientの「mc」を使います(次のセクションで詳しく説明します)。まずMinIOが9000番ポートで待ち受けているか確認してください。Linux ポート確認の全コマンドも参考にしてください。

# ssコマンドでMinIOが9000番ポートを待ち受けているか確認 ss -tlnp | grep 9000 # 出力例 LISTEN 0 4096 0.0.0.0:9000 0.0.0.0:* users:(("minio",pid=3841,fd=12))

backendブロックにS3互換オプションを設定する

1. backend設定の最小構成

Terraformの設定ファイルにbackendブロックを追加します。MinIOに接続するために必要な最小構成は次のとおりです。

# backend.tf(またはmain.tfのterraformブロック内) terraform { backend "s3" { bucket = "tfstate" key = "prod/terraform.tfstate" region = "us-east-1" endpoint = "http://192.168.0.10:9000" access_key = "tf-access-key" secret_key = "tf-secret-key" force_path_style = true skip_credentials_validation = true skip_metadata_api_check = true skip_region_validation = true } }

アクセスキーとシークレットキーはこの後説明するmcコマンドで払い出した値を入れます。本番環境では環境変数(AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY)か部分設定ファイルに移して、設定ファイル本体をリポジトリにコミットしないようにしてください。

2. force_path_styleが必要な理由

S3のURLには「バーチャルホスト型」と「パス型」の2種類があります。AWSのデフォルトはバーチャルホスト型(https://bucket-name.s3.amazonaws.com/)ですが、MinIOはパス型(http://host:9000/bucket-name/)でリクエストを受け付けます。

force_path_style = true を指定しないと、TerraformはMinIOに対してバーチャルホスト型のURLでリクエストを送ってしまい、名前解決に失敗します。Terraform 1.6以降では use_path_style = true という名前のオプションも追加されましたが、force_path_style は後方互換のため現在も機能します。どちらか一方を指定すれば問題ありません。

3. skip_*系3オプションの意味

skip_credentials_validation:AWSのSTS(Security Token Service)を呼んでアクセスキーが正規のものかチェックする処理をスキップします。MinIOにはSTSがないためtrueが必要です。
skip_metadata_api_check:EC2のインスタンスメタデータAPI(169.254.169.254)を呼んでIAMロールを取得しようとする処理をスキップします。MinIOサーバーはEC2ではないためtrueが必要です。
skip_region_validation:regionの値がAWSの実在するリージョン名かどうかの検証をスキップします。MinIOはregionを使いませんが、backendブロックの仕様上regionを省略できないため、ダミー値でよいようにするためtrueが必要です。

4. terraform initで初期化する

terraform init # 成功時の出力例 Initializing the backend... Successfully configured the backend "s3"! Terraform will automatically use this backend unless the backend configuration changes. Initializing provider plugins... Terraform has been successfully initialized!

「Successfully configured the backend "s3"」が表示されれば接続成功です。tfstateファイルの読み書きができる状態になりました。

TerraformのIaC設計をより体系的に学びたい方は Terraform実践コース(terraform.linuxmaster.jp) も参考にしてください。

最小権限のアクセスキーを発行する

rootアカウントのアクセスキーをTerraformに直接使いまわすのは避けてください。MinIO Clientの「mc」を使ってTerraform専用ユーザーを作り、必要な操作だけを許可したアクセスキーを払い出します。

1. mcクライアントのセットアップ

# mcバイナリをダウンロード wget https://dl.min.io/client/mc/release/linux-amd64/mc chmod +x mc mv mc /usr/local/bin/mc # MinIOサーバーのエイリアスを登録(rootアカウントで認証) mc alias set tfminio http://192.168.0.10:9000 minioadmin ChangeMeToStrongPassword # 接続確認 mc admin info tfminio # 出力例 * 192.168.0.10:9000 Uptime: 12 minutes Version: 2024-09-13T20:41:23Z Network: 1/1 OK Drives: 1/1 OK Pool: 1

2. stateバケットの作成とTerraform専用ユーザーの追加

# stateバケットを作成 mc mb tfminio/tfstate # Terraform専用ユーザーを作成 mc admin user add tfminio tfuser TfUserPassword123 # 最小権限ポリシーのJSONを作成 cat << 'EOF' > /tmp/tfstate-policy.json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject" ], "Resource": "arn:aws:s3:::tfstate/*" }, { "Effect": "Allow", "Action": [ "s3:ListBucket", "s3:GetBucketVersioning" ], "Resource": "arn:aws:s3:::tfstate" } ] } EOF # ポリシーを登録してユーザーに適用 mc admin policy create tfminio tfstate-rw /tmp/tfstate-policy.json mc admin policy attach tfminio tfstate-rw --user tfuser

ポリシーで許可した操作の意味は次のとおりです。
GetObject:tfstateファイルを読み取る(terraform plan / apply時の現在状態確認)
PutObject:tfstateファイルを書き込む(apply後の状態更新とstateロックファイル作成)
DeleteObject:stateロックファイルを削除する(ロック解放)
ListBucket:バケット内のオブジェクト一覧を取得する(terraform init時のstate確認)
GetBucketVersioning:バケットのバージョニング設定を確認する(backend初期化時に参照)

3. サービスアカウント(アクセスキーペア)を払い出す

MinIOではユーザーのログインパスワードとは別に、アクセスキーIDとシークレットキーのペアを「サービスアカウント」として発行できます。Terraformが使うのはこのサービスアカウントです。

# tfuserのサービスアカウントを作成(鍵ペアが自動生成される) mc admin user svcacct add tfminio tfuser --name terraform-ci # 出力例(この時点しか確認できないので必ず控えること) Access Key: AKIAIOSFODNN7TFUSER1 Secret Key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYTFUSERKEY1

表示されたAccess KeyとSecret Keyをbackend.tfの access_keysecret_key に設定します。Secret Keyはこのタイミングしか確認できないため、安全な場所に保存してください。

Terraform 1.10以降はuse_lockfileでDynamoDB不要のstateロックを使う

Terraform 1.10より前のバージョンでは、S3バックエンドのstateロックにDynamoDBが必要でした。MinIOはDynamoDBを実装していないため、古いバージョンでは dynamodb_table を指定せずstateロックなしで運用するしかありませんでした。

Terraform 1.10で追加された use_lockfile = true オプションを使うと、DynamoDBの代わりにS3バケット内に .tfstate.lock ファイルを置くことでstateロックを実現できます。MinIOは通常のPutObject・DeleteObjectで動くため、この方式はMinIOでも正常に動作します。

# Terraform >= 1.10 の場合:DynamoDB不要のstateロック設定 terraform { backend "s3" { bucket = "tfstate" key = "prod/terraform.tfstate" region = "us-east-1" endpoint = "http://192.168.0.10:9000" access_key = "AKIAIOSFODNN7TFUSER1" secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYTFUSERKEY1" force_path_style = true skip_credentials_validation = true skip_metadata_api_check = true skip_region_validation = true use_lockfile = true } }

use_lockfile を有効にする場合、ポリシーの s3:PutObjects3:DeleteObject の権限がロックファイルの作成・削除に使われます。前の手順で設定したポリシーのままで問題なく動作します。

トラブルシュート

「SignatureDoesNotMatch」が出る場合

access_keyまたはsecret_keyの値が間違っている可能性が高いです。コピー時に余分なスペースや改行が混入することがあります。mcコマンドで同じアクセスキーを使って接続確認すると切り分けやすくなります。

# サービスアカウントのエイリアスを別名で作って動作確認 mc alias set tftest http://192.168.0.10:9000 AKIAIOSFODNN7TFUSER1 wJalrXUtnFEMI/K7MDENG/bPxRfiCYTFUSERKEY1 mc ls tftest/tfstate # 正常なら tfstate バケットの中身が表示される(空バケットなら空行)

「NoSuchBucket」が出る場合

mc mb でバケットを作成する前にterraform initを実行した場合に発生します。backend.tfの bucket で指定した名前と mc mb で作成したバケット名が一致しているか確認してください。バケット名は英小文字・数字・ハイフンのみ使用できます(S3と同じルール)。

stateロックが残留した場合

terraform apply が途中で強制終了するなどしてstateロックが残ることがあります。terraform force-unlock でロックIDを指定して解除できます。

# ロックIDはエラーメッセージの中に含まれる # Error acquiring the state lock: # Lock ID: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" terraform force-unlock a1b2c3d4-e5f6-7890-abcd-ef1234567890

use_lockfile = true を使っている場合、ロックファイルはバケット内の <key>.lock オブジェクトとして存在します。terraform force-unlock で解除できない場合は mc rm で直接削除できますが、本当に誰もapplyを実行中でないことを確認してから行ってください。

本記事のまとめ

設定項目 値・指定内容 理由
endpoint http://MinIOサーバーIP:9000 MinIOのAPIエンドポイントを指定
region us-east-1(ダミー値でOK) MinIOは使わないが省略不可
force_path_style true MinIOはパス型URLのみ対応
skip_credentials_validation true AWS STS呼び出しをスキップ
skip_metadata_api_check true EC2メタデータAPIをスキップ
skip_region_validation true AWS実在リージョン検証をスキップ
use_lockfile(1.10以降) true DynamoDB不要のファイルベースstateロック
必要な権限(5つ) GetObject / PutObject / DeleteObject / ListBucket / GetBucketVersioning tfstateの読み書きとロック操作
MinIOをTerraform backendとして使うポイントは「S3互換なのにそのままでは繋がらない理由」を押さえることです。force_path_styleskip_* 系3オプションがセットになって初めて正しく接続できます。アクセスキーはmcコマンドで専用ユーザーにサービスアカウントとして払い出し、必要な5権限に絞るのがLinuxサーバー上での自前運用の基本です。

TerraformのIaC設計・運用をステップごとに学ぶなら Terraform実践コース(terraform.linuxmaster.jp) もご覧ください。

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。