すべてのプロダクト
Search
ドキュメントセンター

Terraform:Terraform を活用した Pony.ai の Infrastructure as Code (IaC) 実践

最終更新日:Jun 04, 2026

Pony.ai の DevOps チームが、Terraform を採用してクラウドインフラストラクチャを完全に自動化した方法を紹介します。技術選定プロセス、アーキテクチャ上の意思決定、コードの再利用や複数環境へのデプロイに対応するために構築したソリューションを取り上げます。

背景情報

Pony.ai は 2016 年に設立された、グローバルな自動運転技術企業です。シリコンバレー、広州、北京、上海、深圳に R&D センターを有しています。同社は米国および中国の複数の地域で自動運転の試験・運用許可を取得しており、トヨタ、ヒョンデ、第一汽車グループ (FAW Group)、広汽グループ (GAC Group) などの自動車メーカーと協業しています。

Pony.ai の中国事業は Alibaba Cloud 上で稼働しており、データラベリング、ロボタクシー、ロボトラックのプラットフォームなどの重要サービスをホストしています。これらのサービスは Elastic Compute Service (ECS)、ApsaraDB RDS (RDS)、Server Load Balancer (SLB)、Bastionhost、Security Center に依存しており、DevOps チームにとってインフラストラクチャ管理はますます複雑になっていました。

この課題に対応するため、チームは 3 つの中核目標を定義しました。

  1. レビュー可能なデプロイ:要件収集、アーキテクチャ設計、コーディング、デプロイに至るまで、運用アクティビティの各段階で正確性を保ち、トレーサビリティを確保すること。

  2. バージョン管理されたデプロイ:すべてのインフラストラクチャ変更について、監査可能な完全な履歴を保持し、特定バージョンへのロールバックを可能にすること。

  3. 一貫したマルチ環境デプロイ:開発、ステージング、本番環境の間で差異を発生させず、環境固有の障害を防止すること。

技術選定

チームは、パブリッククラウドリソースを管理するための主要な 3 つのアプローチを評価しました。

  1. クラウドプロバイダーのコンソール:GUI を通じた直接管理。

  2. カスタム管理システム:クラウドプロバイダーの API を呼び出すシステムを構築または購入。

  3. Infrastructure as Code (IaC) フレームワーク:コードによるインフラストラクチャの定義と管理。

3steps

IaC はインフラストラクチャ自動化における確立された標準であり、マルチクラウド管理で最も広く利用されているフレームワークです。Git と組み合わせることで、IaC はトレーサビリティとバージョン管理の目標を直接達成できます。すべてのデプロイと変更はコードとして管理され、ロールバックは過去の Git ブランチに戻すだけで簡単に実行できます。

IaC エコシステムの中でも、Terraform はエンタープライズの本番環境で豊富な実績を持つ、先進的なオープンソースツールです。Terraform を採用することで、DevOps チームはゼロからカスタムのオーケストレーションシステムを構築するのではなく、インフラストラクチャのビジネスロジックの記述に集中できます。

Pony.ai のマルチクラウド戦略とハイブリッドクラウドアーキテクチャを踏まえると、Terraform の標準化、使いやすさ、活発なコミュニティは明確な選定理由となりました。

アーキテクチャと実装

チームは、以下に示す Git 中心のワークフローで、Terraform ベースの IaC ソリューションを実装しました。

arch

構成ファイルについては、既存の JSON ベースのアプリケーションとの整合性を保ち、コードレビューを簡素化するために、HashiCorp Configuration Language (HCL) ではなく JSON を選択しました。

コードはビジネスサービス単位で整理されています。あるサービスで SLB、証明書、ECS インスタンスが必要な場合、それらのリソースはそのサービス用の単一の Terraform ファイルに定義します。以下は簡略化した例です。

{
    "output": {
        "ecs_instance_1-private-ip": {
            "value": "${alicloud_instance.ecs_instance_1.private_ip}"
        }, 
        "ecs_instance_2-private-ip": {
            "value": "${alicloud_instance.ecs_instance_2.private_ip}"
        }, 
        "ponyai_business_1-slb-address": {
            "value": "${alicloud_slb.ponyai_business_1-slb.address}"
        }
    }, 
    "provider": {
        "alicloud": {
            "region": "alicloud_region"
        }
    }, 
    "resource": {
        "alicloud_instance": {
            "ecs_instance_1": {
                "availability_zone": "availability_zone_1", 
                "data_disks": [
                    {
                        "category": "cloud_essd", 
                        "name": "data_volume", 
                        "size": "xx"
                    }
                ], 
                "host_name": "ecs_instance_1", 
                "image_id": "image_id_1", 
                "instance_name": "ecs_instance_1", 
                "instance_type": "ecs_instance_type", 
                "internet_charge_type": "PayByTraffic", 
                "internet_max_bandwidth_out": 10, 
                "key_name": "key_name_1", 
                "security_groups": [
                    "security_groups_1"
                ], 
                "system_disk_category": "cloud_essd", 
                "system_disk_size": "xx", 
                "tags": {
                    "host_name": "ecs_instance_1"
                }, 
                "vswitch_id": "vswitch_id_1"
            }, 
            "ecs_instance_2": {
                "availability_zone": "availability_zone_2", 
                "data_disks": [
                    {
                        "category": "cloud_essd", 
                        "name": "data_volume", 
                        "size": "xx"
                    }
                ], 
                "host_name": "ecs_instance_2", 
                "image_id": "image_id_1", 
                "instance_name": "ecs_instance_2", 
                "instance_type": "ecs_instance_type", 
                "internet_charge_type": "PayByTraffic", 
                "internet_max_bandwidth_out": 10, 
                "key_name": "key_name_1", 
                "security_groups": [
                    "security_groups_1"
                ], 
                "system_disk_category": "cloud_essd", 
                "system_disk_size": "xx", 
                "tags": {
                    "host_name": "ecs_instance_2"
                }, 
                "vswitch_id": "vswitch_id_2"
            }
        }, 
        "alicloud_slb": {
            "slb-1": {
                "address_type": "internet", 
                "internet_charge_type": "PayByTraffic", 
                "name": "slb_name", 
                "specification": "slb_specification"
            }
        }, 
        "alicloud_slb_listener": {
            "slb-listener-1": {
                "backend_port": "xx", 
                "bandwidth": -1, 
                "frontend_port": "xx", 
                "health_check": "on", 
                "health_check_connect_port": "xx", 
                "health_check_domain": "domain_name", 
                "health_check_type": "check_type", 
                "health_check_uri": "uri_1", 
                "load_balancer_id": "${alicloud_slb.slb-1.id}", 
                "protocol": "protocol_1", 
                "scheduler": "scheduler_1", 
                "server_certificate_id": "${alicloud_slb_server_certificate.slb-certificate-1.id}", 
                "server_group_id": "${alicloud_slb_server_group.slb-server-group-1.id}"
            }
        }, 
        "alicloud_slb_server_certificate": {
            "slb-certificate-1": {
                "alicloud_certificate_id": "xx", 
                "alicloud_certificate_name": "xx", 
                "name": "certificate_1"
            }
        }, 
        "alicloud_slb_server_group": {
            "slb-server-group-1": {
                "load_balancer_id": "${alicloud_slb.slb-1.id}", 
                "name": "slb-server-group", 
                "servers": {
                    "port": "xx", 
                    "server_ids": [
                        "${alicloud_instance.ecs_instance_1.id}", 
                        "${alicloud_instance.ecs_instance_2.id}"
                    ]
                }
            }
        }
    }, 
    "terraform": {
        "backend": {
            "oss": {
                "bucket": "bucket_name",
                "key": "key_1",
                "region": "region_1"
            }
        }, 
        "required_providers": {
            "alicloud": {
                "source": "aliyun/alicloud", 
                "version": "xx"
            }
        }
    }
}

ビジネス上の課題

Terraform のコードベースが拡大するにつれて、2 つの問題が顕在化しました。

  • ECS インスタンスのようなリソースでは、エンジニアが関心を持つパラメーターは instance_typeinstance_nameavailability_zone などの一部に限られますが、毎回すべての構成を定義する必要がありました。

  • 同一サービスを複数環境 (開発、ステージング、本番) にデプロイする場合、設定はほぼ同一であり、たとえば本番環境では slb.s2.medium の SLB を使用し、テストでは slb.s1.small を使用するといった、わずかな差分しかありません。環境ごとにコードをコピーして書き換える方法は、可読性と保守性を損なっていました。

ソリューション

これらの問題を解決するため、チームは Terraform の JSON ファイルを生成する手段として、オープンソースのデータテンプレート言語である Jsonnet を導入しました。Jsonnet により、繰り返し発生するボイラープレートコードを、再利用可能なユーティリティ関数のライブラリとして抽象化できます。たとえば、デフォルトの ECS パラメーターをすべてカプセル化する generateEcs 関数を構築しました。

generateEcs(instance_name,
            availability_zone,
            vswitch_id,
            security_groups,
            instance_type,
            host_name,
            data_volume_size=null,
            system_disk_size=null,
            internet_charge_type="PayByTraffic",
            image_id="ubuntu_18_04_x64_20G_alibase_20200914.vhd",
            key_name="bootstrap-bot",
            system_disk_category="cloud_essd",
            internet_max_bandwidth_out=10,
            data_disk_category="cloud_essd"): {
  instance_name: instance_name,
  availability_zone: availability_zone,
  vswitch_id: vswitch_id,
  security_groups: security_groups,
  instance_type: instance_type,
  internet_charge_type: internet_charge_type,
  image_id: image_id,
  system_disk_category: system_disk_category,
  [if system_disk_size != null then "system_disk_size"]:
    system_disk_size,
    key_name: key_name,
    internet_max_bandwidth_out: internet_max_bandwidth_out,
    host_name: host_name,
    data_disks: if data_volume_size != null then [
      {
        name: "data_volume",
        size: data_volume_size,
        category: data_disk_category,
      },
    ] else [],
      tags: {
        host_name: host_name,
      },
}

エンジニアはこの関数を呼び出して完全な構成を生成し、複数インスタンスをプロビジョニングするためのコードを簡潔なループに削減できます。

alicloud_instance: {
  [host_config.host_name]:
    ecsUtils.generateEcs(
      instance_name=host_config.host_name,
      availability_zone=host_config.az,
      security_groups=$.ecs_security_groups,
      host_name=host_config.host_name,
      instance_type=$.ecs_instance_type,
      vswitch_id=vpc_output["vswitch-public-" + host_config.az].value,
      data_volume_size=$.ecs_data_volume_size,
      system_disk_size=$.ecs_system_disk_size
    )
  for host_config in host_configs
},

パラメーターの変更は、個々のリソース定義ではなく、共有ユーティリティ関数に反映させます。

マルチ環境構成

環境ごとの差分となるパラメーターが少ないサービスについては、チームはベーステンプレートを定義します。各環境の構成はそのベーステンプレートをインポートし、変更が必要なフィールドのみを上書きします。

その結果、サービスごとに以下のディレクトリ構成になります。

説明

generated/main.tf.json は Terraform が実行する JSON ファイルです。main.tf.json.jsonnet から Jsonnet によって生成されます。main.tf.json.jsonnet.output には、Terraform が構成を適用した後に生成される output が保存されます。

├── alicloud-region
│   ├── dev
│   │   ├── generated
│   │   │   └── main.tf.json
│   │   ├── main.tf.json.jsonnet
│   │   └── main.tf.json.jsonnet.output
│   ├── prod
│   │   ├── generated
│   │   │   └── main.tf.json
│   │   ├── main.tf.json.jsonnet
│   │   └── main.tf.json.jsonnet.output
│   └── staging
│       ├── generated
│       │   └── main.tf.json
│       ├── main.tf.json.jsonnet
│       └── main.tf.json.jsonnet.output
└── ponyai_business_1_base.libsonnet

本番環境はベーステンプレートをインポートし、独自の SLB 仕様を設定します。

local base = import "../../ponyai_business_1_base.libsonnet";
base {
  name: "ponyai_business_1_prod",
  environment: "prod",
  region: "alicloud_region",
  slb_specification: "slb.s2.medium"
}

開発環境は同じベーステンプレートをインポートし、名前と仕様のみを上書きします。

local base = import "../../ponyai_business_1_base.libsonnet";
base {
  name: "ponyai_business_1_dev",
  environment: "dev",
  region: "alicloud_region",
  slb_specification: "slb.s1.small"
}

クロススタック参照

Jsonnet は、インフラストラクチャコンポーネント間の依存関係の問題も解決します。ECS インスタンスの作成には Virtual Private Cloud (VPC) ID が必要ですが、VPC は通常、別の Terraform ファイルで管理されます。生成された VPC ID をハードコーディングする代わりに、Pony.ai のワークフローでは VPC スタックの output ファイルから読み取ります。

main.tf.json ファイルは VPC を作成し、その ID を自身のディレクトリにある main.tf.json.jsonnet.output に書き込みます。

├── alicloud-region
│   ├── dev
│   │   ├── generated
│   │   │   └── main.tf.json
│   │   ├── main.tf.json.jsonnet
│   │   └── main.tf.json.jsonnet.output
│   └── prod
│       ├── generated
│       │   └── main.tf.json
│       ├── main.tf.json.jsonnet
│       └── main.tf.json.jsonnet.output

生成される main.tf.json.jsonnet.output ファイルは次のようになります。

{
  "vpc_id": {
    "sensitive": false,
    "type": "string",
    "value": "vpc_id_for_ponyai"
  },
  "vswitch-public-availability_zone_1": {
    "sensitive": false,
    "type": "string",
    "value": "vswitch_id_for_zone_1"
  },
  "vswitch-public-availability_zone_2": {
    "sensitive": false,
    "type": "string",
    "value": "vswitch_id_for_zone_2"
  }
}

これらの値が必要なサービスは、output ファイルを直接インポートします。

{
"alicloud-region": {
  prod: import "./alicloud-region/prod/main.tf.json.jsonnet.output",
  }
}

この階層化された抽象化により、Pony.ai の DevOps チームは Terraform エコシステムを最大限に活用しながら、サービス間の依存関係をクリーンかつ明示的に保つことができます。

ビジネス成果

Terraform と Git による IaC の導入は、インフラストラクチャのライフサイクル全体にわたって具体的な改善をもたらしました。すべてのインフラストラクチャパラメーターはコードで明示的に定義されます。たとえば、あるサービスで特定のサイズとディスク構成の ECS インスタンスが 2 台必要な場合でも、すべての詳細は 1 つのファイルに集約されます。

各デプロイの前に、チームは Git でプルリクエスト (PR) 全体をレビューし、変更点を議論します。このレビューステップにより、すべてのインフラストラクチャ変更の履歴が記録され、完全なトレーサビリティが確保されます。

変更点をパラメーター単位でレビューできるため、最終的なデプロイが当初の設計と一致していることを確認できます。Terraform のコーディングフェーズで検知された不整合は、本番環境に到達する前に修正できます。さらに、開発者は PR を作成する前にセルフテストの実行が求められるため、壊れたコードによる複数回のレビューサイクルを回避できます。

この変革により、次の 4 つの主要な成果が得られました。

  • 高速化:インフラストラクチャのプロビジョニングは、反復的な手作業によるコンソール操作ではなくなりました。プロダクションサイクルの短縮により、同社はビジネス判断や市場機会に対してより迅速に対応できます。

  • 制御性の向上:すべての変更はバージョン管理され、監査可能です。組織は手作業のコンソール操作から、追跡可能で信頼性の高い管理システムへと移行しました。

  • 効率化:国際的に分散したチームも含め、チーム間のコラボレーションが大幅に改善し、タイムゾーンの違いや多様な働き方に起因する遅延が軽減されました。

  • セキュリティの向上:本番環境におけるヒューマンエラーの発生可能性が大幅に低下しました。自動化されたコード駆動のプロセスと、環境別の承認ワークフローを組み合わせることで、Pony.ai は事業運営に対する堅牢な安全策を確立しました。

結論

  • 管理モデルのアップグレード

IaC の思想は現在、Pony.ai におけるインフラストラクチャ開発全体の基盤となっています。チームは ECS、VPC、Object Storage Service (OSS)、Public DNS、Resource Access Management (RAM)、Simple Log Service、ユーザー管理など幅広い Alibaba Cloud コンポーネントを、社内で再利用可能な関数として抽象化しました。

  • ビジネスモデルのアップグレード

チームがクラウドリソースを必要とする場合、これらの事前構築・検証済みの関数のいずれかを呼び出します。DevOps チームは社内ライブラリの保守と改善に注力し、組織全体の運用効率の継続的な向上を実現しています。

  • 運用モデルのアップグレード

現在、Pony.ai では 20 を超える事業部門が、この IaC ワークフローを通じて 100% デプロイ・管理されています。すべてのコンポーネントには明確でバージョン管理された履歴があり、変更は厳格なレビューを経ます。また、チームは非準拠のデプロイを本番環境に到達する前に却下できます。その結果、オンライン環境はクリーンで信頼性が高く、スケーラブルになりました。

著者について

Pony.ai DevOps チーム

本記事は外部の著者から寄稿されたものであり、著作権はすべて著者に帰属します。Alibaba Cloud は内容について一切の責任を負いません。