はじめに Link to heading

「Infrastructure as Code (IaC)」は理念としては理解していても、実際のプロジェクトでどう使い分けるかは悩ましいものです。

  • TerraformとAnsibleの役割分担は?
  • 変数はどこで管理する?
  • 環境ごとの差異はどう扱う?
  • 本当に再現可能な構成になっているか?

この記事では、GCP上でWordPress マルチテナント環境(10サイト)を構築する過程で確立した Terraform + Ansibleの実践的な運用パターン を共有します。

この記事で扱う内容:

  1. TerraformとAnsibleの責任分離
  2. モジュール設計とディレクトリ構造
  3. 変数管理とシークレット管理
  4. 冪等性の担保と検証
  5. 運用フェーズでの変更管理

1. TerraformとAnsibleの責任分離 Link to heading

「何をTerraformで、何をAnsibleで管理するか」 Link to heading

これはIaC運用の最重要設計判断です。

基本原則: インフラ vs 構成 Link to heading

┌─────────────────────────────────────────┐
│           Terraform                     │
│  「リソースの存在」を管理               │
│  - VPCネットワーク                      │
│  - Computeインスタンス                  │
│  - Cloud SQL                            │
│  - ロードバランサ                       │
│  - IAMロール                            │
└─────────────────────────────────────────┘
              ↓ 作成
┌─────────────────────────────────────────┐
│           Ansible                       │
│  「リソースの状態」を管理               │
│  - パッケージインストール               │
│  - 設定ファイル配置                     │
│  - サービス起動                         │
│  - アプリケーションデプロイ             │
└─────────────────────────────────────────┘

具体的な責任分離表 Link to heading

項目TerraformAnsible理由
VPCネットワーク作成GCPリソース
Computeインスタンス作成GCPリソース
Cloud SQL作成GCPリソース
Nginx インストールOS設定
PHP設定ミドルウェア設定
WordPressデプロイアプリケーション
SSL証明書取得アプリケーション層
IAMロール作成GCPリソース
Service Account作成GCPリソース
ファイアウォールルールGCPリソース
NFSマウントOS設定

判断基準 Link to heading

Terraformで管理すべきもの:

1✅ GCP APIで作成・管理されるリソース
2✅ 削除するとデータロスのリスクがあるもの
3✅ 複数環境で共通の定義
4✅ 依存関係が複雑なもの

Ansibleで管理すべきもの:

1✅ OSレベルの設定
2✅ パッケージのインストール
3✅ 設定ファイルの配置・テンプレート化
4✅ サービスの起動・停止
5✅ アプリケーションコードのデプロイ
6✅ 頻繁に変更されるもの

グレーゾーンの判断例 Link to heading

ケース1: Computeインスタンスのメタデータ Link to heading

選択肢:

  1. Terraformでメタデータとして設定
  2. Ansibleで動的に取得

今回の判断: Terraformで設定、Ansibleで読み取り

1# Terraform
2resource "google_compute_region_instance_template" "web" {
3  metadata = {
4    env      = var.env
5    db_host  = module.database.private_ip_address
6    nfs_ip   = var.nfs_ip
7    nfs_path = var.nfs_path
8  }
9}
1# Ansible
2- name: メタデータから環境情報取得
3  uri:
4    url: "http://metadata.google.internal/computeMetadata/v1/instance/attributes/db_host"
5    headers:
6      Metadata-Flavor: "Google"
7    return_content: yes
8  register: db_host_metadata

理由: インフラ情報はTerraformが単一真実の情報源(SSOT)

ケース2: データベースパスワード Link to heading

選択肢:

  1. Terraformで生成してSecret Managerに保存
  2. Ansibleで生成

今回の判断: Terraformで生成

 1# Terraform
 2resource "random_password" "db_passwords" {
 3  count   = 10
 4  length  = 20
 5  special = true
 6}
 7
 8resource "google_secret_manager_secret_version" "db_passwords" {
 9  count       = 10
10  secret      = google_secret_manager_secret.db_passwords[count.index].id
11  secret_data = random_password.db_passwords[count.index].result
12}

理由: インフラ層のリソース(Cloud SQL User)と密接に関連


2. モジュール設計とディレクトリ構造 Link to heading

プロジェクト全体のディレクトリ構造 Link to heading

infra-ai-agent/
├── terraform/
│   ├── modules/           # 再利用可能なモジュール
│   │   ├── network/
│   │   ├── compute/
│   │   ├── database/
│   │   ├── filestore/
│   │   ├── loadbalancer/
│   │   ├── iam/
│   │   └── monitoring/
│   └── environments/      # 環境ごとの設定
│       ├── dev/
│       │   ├── main.tf
│       │   ├── variables.tf
│       │   ├── terraform.tfvars
│       │   └── outputs.tf
│       └── prod/
│           ├── main.tf
│           ├── variables.tf
│           ├── terraform.tfvars
│           └── outputs.tf
├── ansible/
│   ├── inventory/         # 動的インベントリ
│   │   └── gcp.yml
│   ├── playbooks/         # Playbook
│   │   └── deploy-wordpress.yml
│   └── roles/             # ロール
│       └── wordpress/
│           ├── tasks/
│           ├── templates/
│           ├── files/
│           └── defaults/
└── scripts/               # 運用スクリプト
    └── sync-db-passwords.sh

Terraformモジュール設計原則 Link to heading

原則1: 単一責任の原則 Link to heading

各モジュールは1つのGCPサービスに対応:

 1# ❌ 悪い例: すべてを1つのモジュールに
 2module "wordpress_infra" {
 3  source = "../../modules/all"
 4  # すべてのリソースをここで作成
 5}
 6
 7# ✅ 良い例: 責任を分離
 8module "network" {
 9  source = "../../modules/network"
10}
11
12module "database" {
13  source = "../../modules/database"
14  depends_on = [module.network]
15}
16
17module "compute" {
18  source = "../../modules/compute"
19  depends_on = [module.database]
20}

原則2: 依存関係の明示 Link to heading

1# terraform/environments/prod/main.tf
2module "compute" {
3  source = "../../modules/compute"
4
5  db_host = module.database.private_ip_address  # 明示的な依存
6  nfs_ip  = module.filestore.nfs_ip
7
8  depends_on = [module.database, module.filestore]
9}

原則3: 出力値の活用 Link to heading

 1# terraform/modules/database/outputs.tf
 2output "private_ip_address" {
 3  description = "Cloud SQL Private IP Address"
 4  value       = google_sql_database_instance.wordpress.private_ip_address
 5}
 6
 7output "instance_connection_name" {
 8  description = "Cloud SQL Instance Connection Name"
 9  value       = google_sql_database_instance.wordpress.connection_name
10}
11
12output "database_names" {
13  description = "List of database names"
14  value       = google_sql_database.wordpress_dbs[*].name
15}

モジュール構造の例: Database Link to heading

terraform/modules/database/
├── main.tf           # メインリソース定義
├── variables.tf      # 入力変数
├── outputs.tf        # 出力値
└── README.md         # モジュールのドキュメント

main.tf:

 1resource "google_sql_database_instance" "wordpress" {
 2  name             = "${var.env}-wordpress-db"
 3  database_version = "MYSQL_8_0"
 4  region           = var.region
 5
 6  settings {
 7    tier              = var.tier
 8    availability_type = var.availability_type
 9
10    ip_configuration {
11      ipv4_enabled    = false
12      private_network = var.network_id
13      require_ssl     = false
14    }
15
16    backup_configuration {
17      enabled            = true
18      start_time         = "03:00"
19      binary_log_enabled = true
20    }
21  }
22}
23
24resource "google_sql_database" "wordpress_dbs" {
25  count    = 10
26  name     = "wordpress_db_${count.index + 1}"
27  instance = google_sql_database_instance.wordpress.name
28}
29
30resource "google_sql_user" "wordpress_users" {
31  count    = 10
32  name     = "wp_user_${count.index + 1}"
33  instance = google_sql_database_instance.wordpress.name
34  password = random_password.db_passwords[count.index].result
35}

variables.tf:

 1variable "env" {
 2  description = "Environment name"
 3  type        = string
 4}
 5
 6variable "region" {
 7  description = "GCP region"
 8  type        = string
 9}
10
11variable "tier" {
12  description = "Cloud SQL machine tier"
13  type        = string
14  default     = "db-f1-micro"
15}
16
17variable "network_id" {
18  description = "VPC network ID for private IP"
19  type        = string
20}

Ansibleロール設計 Link to heading

ディレクトリ構造 Link to heading

ansible/roles/wordpress/
├── tasks/
│   ├── main.yml          # エントリーポイント
│   ├── packages.yml      # パッケージインストール
│   ├── nginx.yml         # Nginx設定
│   ├── php.yml           # PHP設定
│   ├── wpcli.yml         # WP-CLIインストール
│   ├── nfs.yml           # NFSマウント
│   └── scripts.yml       # スクリプト配置
├── templates/
│   ├── nginx-site.conf.j2
│   ├── php-fpm-pool.conf.j2
│   └── setup-wordpress-site.sh.j2
├── files/
│   └── cloudflare-origin-ca.pem
├── defaults/
│   └── main.yml          # デフォルト変数
└── handlers/
    └── main.yml          # サービス再起動ハンドラ

tasks/main.yml (エントリーポイント) Link to heading

 1---
 2- name: パッケージインストール
 3  import_tasks: packages.yml
 4  tags: packages
 5
 6- name: Nginx設定
 7  import_tasks: nginx.yml
 8  tags: nginx
 9
10- name: PHP設定
11  import_tasks: php.yml
12  tags: php
13
14- name: NFS設定
15  import_tasks: nfs.yml
16  tags: nfs
17
18- name: WP-CLI インストール
19  import_tasks: wpcli.yml
20  tags: wpcli
21
22- name: スクリプト配置
23  import_tasks: scripts.yml
24  tags: scripts

これにより、特定のタスクのみ実行可能:

1# Nginxだけ再設定
2ansible-playbook -i inventory/gcp.yml playbooks/deploy-wordpress.yml --tags nginx
3
4# WP-CLIだけ更新
5ansible-playbook -i inventory/gcp.yml playbooks/deploy-wordpress.yml --tags wpcli

3. 変数管理とシークレット管理 Link to heading

変数管理の階層 Link to heading

┌─────────────────────────────────────────┐
│  1. デフォルト値 (defaults/main.yml)    │  優先度: 低
└────────────┬────────────────────────────┘
             ↓
┌─────────────────────────────────────────┐
│  2. 環境固有値 (terraform.tfvars)       │  優先度: 中
└────────────┬────────────────────────────┘
             ↓
┌─────────────────────────────────────────┐
│  3. コマンドライン (-e "var=value")     │  優先度: 高
└─────────────────────────────────────────┘

Terraform変数の管理 Link to heading

terraform.tfvars (本番環境) Link to heading

 1# terraform/environments/prod/terraform.tfvars
 2env        = "prod"
 3project_id = "infra-ai-agent"
 4region     = "asia-northeast1"
 5zone       = "asia-northeast1-a"
 6
 7# Compute
 8machine_type = "e2-micro"
 9min_replicas = 3
10max_replicas = 10
11
12# Database
13db_tier              = "db-f1-micro"
14db_availability_type = "ZONAL"
15
16# Filestore
17filestore_tier        = "BASIC_HDD"
18filestore_capacity_gb = 1024
19
20# Domains
21domains = [
22  "ai-jisso.tech",
23  "dev-ops.tech",
24  "cloud-native.tech",
25  "kube-master.tech",
26  "infra-code.tech",
27  "serverless-app.tech",
28  "micro-service.tech",
29  "data-pipeline.tech",
30  "ml-platform.tech",
31  "edge-computing.tech"
32]

terraform.tfvars (開発環境) Link to heading

 1# terraform/environments/dev/terraform.tfvars
 2env        = "dev"
 3project_id = "infra-ai-agent-dev"
 4region     = "asia-northeast1"
 5zone       = "asia-northeast1-a"
 6
 7# Compute (開発環境は小さめ)
 8machine_type = "e2-micro"
 9min_replicas = 1
10max_replicas = 3
11
12# Database (開発環境は最小構成)
13db_tier              = "db-f1-micro"
14db_availability_type = "ZONAL"  # 高可用性は不要
15
16# Filestore (開発環境は最小容量)
17filestore_tier        = "BASIC_HDD"
18filestore_capacity_gb = 256
19
20# Domains (開発用ドメイン)
21domains = [
22  "dev.ai-jisso.tech",
23  "staging.ai-jisso.tech"
24]

Ansibleの変数管理 Link to heading

defaults/main.yml Link to heading

 1---
 2# PHP設定
 3php_version: "8.2"
 4php_memory_limit: "256M"
 5php_max_execution_time: "300"
 6php_upload_max_filesize: "64M"
 7
 8# Nginx設定
 9nginx_worker_connections: 1024
10nginx_client_max_body_size: "64M"
11
12# WordPress設定
13wordpress_version: "latest"
14wpcli_url: "https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar"
15wpcli_bin_path: "/usr/local/bin/wp"
16
17# NFS設定
18nfs_mount_point: "/var/www/wordpress"
19nfs_mount_options: "defaults,hard,intr"

Playbook内での変数上書き Link to heading

1# ansible/playbooks/deploy-wordpress.yml
2- name: WordPress デプロイ
3  hosts: label_service_wordpress
4  become: yes
5
6  vars:
7    env: "{{ lookup('env', 'ENV') | default('prod', true) }}"
8    gcp_project_id: "{{ lookup('env', 'GCP_PROJECT_ID') | default('', true) }}"

コマンドラインからの上書き Link to heading

1# 特定の変数を上書きして実行
2ansible-playbook -i inventory/gcp.yml playbooks/deploy-wordpress.yml \
3  -e "db_host=10.168.0.2" \
4  -e "nfs_ip=10.0.3.2" \
5  -e "nfs_path=/wordpress"

シークレット管理 Link to heading

NGパターン Link to heading

1# ❌ 絶対にやってはいけない
2vars:
3  db_password: "my_secret_password"  # 平文でコミット

正しいパターン Link to heading

Option 1: Secret Managerから動的取得

1# ✅ 推奨
2- name: Secret Managerからパスワード取得
3  command: >
4    gcloud secrets versions access latest
5    --secret={{ env }}-wordpress-db-password-{{ item }}
6    --project={{ gcp_project_id }}    
7  register: db_password
8  no_log: true  # ログに出力しない
9  loop: "{{ range(1, 11) | list }}"

Option 2: Ansible Vault

1# パスワードを暗号化
2ansible-vault encrypt_string 'my_secret_password' --name 'db_password'
3
4# 実行時に復号化
5ansible-playbook -i inventory/gcp.yml playbooks/deploy-wordpress.yml \
6  --ask-vault-pass

Option 3: 環境変数

1vars:
2  db_password: "{{ lookup('env', 'DB_PASSWORD') }}"
1# 実行時に環境変数を設定
2export DB_PASSWORD="my_secret_password"
3ansible-playbook -i inventory/gcp.yml playbooks/deploy-wordpress.yml

4. 冪等性の担保と検証 Link to heading

冪等性とは Link to heading

同じ操作を複数回実行しても、結果が変わらない性質

terraform apply  # 1回目: リソース作成
terraform apply  # 2回目: 何も変更なし ← 冪等性

ansible-playbook deploy.yml  # 1回目: 設定適用
ansible-playbook deploy.yml  # 2回目: 何も変更なし ← 冪等性

Terraformの冪等性 Link to heading

Terraformは宣言的なため、基本的に冪等性が保証されます:

1# 常に同じ状態を宣言
2resource "google_compute_instance" "web" {
3  name         = "prod-web-server"
4  machine_type = "e2-micro"
5}

冪等性を壊す例 Link to heading

1# ❌ 悪い例: タイムスタンプを含む
2resource "google_compute_instance" "web" {
3  name = "web-${timestamp()}"  # 実行のたびに異なる名前
4}
5
6# ✅ 良い例: 固定値または環境変数
7resource "google_compute_instance" "web" {
8  name = "${var.env}-web-server"
9}

冪等性の検証 Link to heading

1# 1回目の実行
2terraform apply
3
4# 2回目の実行(変更がないことを確認)
5terraform plan
6# 出力: No changes. Your infrastructure matches the configuration.

Ansibleの冪等性 Link to heading

Ansibleも基本的に冪等性を持つが、コマンド実行時は注意が必要

冪等的なタスク Link to heading

1# ✅ パッケージインストール(冪等的)
2- name: Nginxインストール
3  apt:
4    name: nginx
5    state: present  # すでにインストール済みなら何もしない

非冪等的なタスク Link to heading

1# ❌ コマンド実行(常に実行される)
2- name: ファイルに追記
3  command: echo "log entry" >> /var/log/myapp.log
4  # 実行のたびに追記される!

冪等性を担保する方法 Link to heading

方法1: changed_when を使う

1- name: 設定ファイルの存在確認
2  command: test -f /etc/myapp/config.yml
3  register: config_check
4  changed_when: false  # 常に "changed" にならない
5  failed_when: config_check.rc not in [0, 1]

方法2: creates パラメータを使う

1- name: WP-CLI ダウンロード
2  get_url:
3    url: "{{ wpcli_url }}"
4    dest: /tmp/wp-cli.phar
5    creates: /tmp/wp-cli.phar  # ファイルが存在すればスキップ

方法3: stat モジュールで事前チェック

 1- name: 設定ファイルの存在確認
 2  stat:
 3    path: /etc/nginx/sites-available/wordpress.conf
 4  register: nginx_conf
 5
 6- name: Nginx設定ファイル配置
 7  template:
 8    src: nginx-site.conf.j2
 9    dest: /etc/nginx/sites-available/wordpress.conf
10  when: not nginx_conf.stat.exists

冪等性のテスト Link to heading

Ansible Dry Run:

1# チェックモード(実際には変更しない)
2ansible-playbook -i inventory/gcp.yml playbooks/deploy-wordpress.yml --check
3
4# 差分表示
5ansible-playbook -i inventory/gcp.yml playbooks/deploy-wordpress.yml --check --diff

2回実行して検証:

 1# 1回目
 2ansible-playbook -i inventory/gcp.yml playbooks/deploy-wordpress.yml
 3
 4# 2回目(変更がないことを確認)
 5ansible-playbook -i inventory/gcp.yml playbooks/deploy-wordpress.yml
 6
 7# 出力例
 8# PLAY RECAP *******************************************
 9# prod-web-l0br : ok=25 changed=0 unreachable=0 failed=0
10#                       ^^^^^^^^^ 変更なし!

5. 運用フェーズでの変更管理 Link to heading

変更の種類と対応方法 Link to heading

変更の種類使用ツール
インフラリソース追加TerraformComputeインスタンス追加
インフラ設定変更TerraformCloud SQL SSL無効化
ミドルウェア設定変更AnsibleNginx worker数変更
アプリケーションデプロイAnsibleWordPress更新
緊急対応手動 + 後でIaC化サービス再起動

変更フロー Link to heading

graph TD
    A[変更要求] --> B{変更の種類}
    B -->|インフラ| C[Terraform修正]
    B -->|構成| D[Ansible修正]

    C --> E[terraform plan]
    E --> F{影響範囲確認}
    F -->|OK| G[terraform apply]
    F -->|NG| H[設計見直し]

    D --> I[ansible-playbook --check]
    I --> J{影響範囲確認}
    J -->|OK| K[ansible-playbook]
    J -->|NG| H

    G --> L[検証]
    K --> L
    L --> M[完了]

ケーススタディ1: Cloud SQL SSL無効化 Link to heading

変更内容: require_ssl = truefalse

手順:

 1# 1. Terraform設定変更
 2cd terraform/environments/prod
 3vim ../../modules/database/main.tf
 4# require_ssl = false に変更
 5
 6# 2. 差分確認
 7terraform plan
 8# Cloud SQL設定が変更されることを確認
 9
10# 3. 適用
11terraform apply
12
13# 4. 影響確認
14gcloud sql instances describe prod-wordpress-db \
15  --format="value(settings.ipConfiguration.requireSsl)"
16# 出力: False
17
18# 5. WordPressから接続テスト
19ansible-playbook -i ../../ansible/inventory/gcp.yml \
20  ../../ansible/playbooks/deploy-wordpress.yml

結果: インスタンステンプレートが変更され、インスタンスが再作成された

学び: Instance Template変更はインスタンス再作成を引き起こす

ケーススタディ2: Nginx設定変更 Link to heading

変更内容: worker_connections を 1024 → 2048 に変更

手順:

 1# 1. Ansible変数変更
 2cd ansible/roles/wordpress
 3vim defaults/main.yml
 4# nginx_worker_connections: 2048 に変更
 5
 6# 2. ドライラン
 7cd ../../
 8ansible-playbook -i inventory/gcp.yml playbooks/deploy-wordpress.yml \
 9  --tags nginx --check --diff
10
11# 3. 適用
12ansible-playbook -i inventory/gcp.yml playbooks/deploy-wordpress.yml \
13  --tags nginx
14
15# 4. 確認
16ansible -i inventory/gcp.yml label_service_wordpress \
17  -m shell -a "nginx -T | grep worker_connections" --become

結果: Nginxがリロードされ、新しい設定が反映された

学び: タグを使って部分的な変更が可能

ケーススタディ3: 緊急対応(サービス再起動) Link to heading

状況: WordPressサイトが応答しなくなった

緊急対応:

1# 手動でサービス再起動
2gcloud compute ssh prod-web-l0br \
3  --zone=asia-northeast1-a \
4  --tunnel-through-iap \
5  --command="sudo systemctl restart php8.2-fpm nginx"

後処理:

1# 原因調査
2ansible-playbook -i inventory/gcp.yml playbooks/deploy-wordpress.yml \
3  --tags nginx,php
4
5# 設定が正しいことを確認
6# 必要に応じてAnsible Playbookを修正

学び: 緊急時は手動対応OK、ただし後で必ずIaC化

Gitワークフロー Link to heading

 1# 1. ブランチ作成
 2git checkout -b feature/disable-cloud-sql-ssl
 3
 4# 2. 変更実施
 5vim terraform/modules/database/main.tf
 6
 7# 3. コミット
 8git add terraform/modules/database/main.tf
 9git commit -m "feat: disable Cloud SQL SSL requirement for private IP connection"
10
11# 4. テスト環境で検証
12cd terraform/environments/dev
13terraform plan
14terraform apply
15
16# 5. プッシュ
17git push origin feature/disable-cloud-sql-ssl
18
19# 6. プルリクエスト作成
20# GitHub上でレビュー
21
22# 7. マージ後、本番適用
23git checkout main
24git pull
25cd terraform/environments/prod
26terraform apply

6. ベストプラクティス Link to heading

Terraform Link to heading

1□ モジュール化で再利用性を高める
2□ terraform.tfvarsで環境ごとの差異を管理
3□ terraform planで必ず変更内容を確認
4□ ステートファイルをリモートバックエンドで管理(GCS等)
5□ ステートファイルのロック機能を有効化
6□ 破壊的変更は lifecycle.prevent_destroy で保護
7□ 出力値を活用してモジュール間連携

Ansible Link to heading

1□ ロールで機能を分割
2□ タグで部分実行を可能に
3□ テンプレートで設定ファイルを動的生成
4□ ハンドラでサービス再起動を自動化
5□ no_log でシークレット情報の漏洩を防止
6□ changed_when で冪等性を担保
7□ --check --diff でドライラン

変数管理 Link to heading

1□ シークレットは平文で保存しない
2□ Secret Manager / Ansible Vaultを活用
3□ 環境ごとの差異はtfvarsで管理
4□ デフォルト値はdefaults/main.ymlに
5□ コマンドラインからの上書きを許可

運用 Link to heading

1□ 変更前に必ずplanまたは--check
2□ Gitでバージョン管理
3□ プルリクエストでレビュー
4□ テスト環境で先に検証
5□ ドキュメントを更新
6□ 変更履歴をコミットメッセージに記録

まとめ Link to heading

TerraformとAnsibleの使い分け Link to heading

ツール役割再実行の影響
Terraformインフラリソースの作成・管理べき等(差分のみ適用)
AnsibleOS・ミドルウェア・アプリの設定べき等(変更検知)

成功のポイント Link to heading

  1. 明確な責任分離

    • Terraformはインフラ層
    • Ansibleは構成管理層
  2. 変数管理の一元化

    • 環境ごとの差異をtfvarsで管理
    • シークレットはSecret Managerで管理
  3. 冪等性の担保

    • 何度実行しても安全
    • ドライランで事前確認
  4. Gitによるバージョン管理

    • すべての変更を記録
    • レビュープロセスの導入
  5. ドキュメント化

    • モジュールの使い方を記録
    • 運用手順を文書化

次のステップ Link to heading

  • CI/CDパイプラインの構築
  • Terraformステートのリモート管理
  • Ansibleの実行をCI/CDに統合
  • 自動テストの追加

参考リンク Link to heading


この記事のコード Link to heading

GitHub: infra-ai-agent

Terraformモジュール:

Ansible Playbook:


この記事が役に立ったら: GitHub Starをいただけると嬉しいです! infra-ai-agent