AtlantisをFargateで導入してみた

AtlantisをFargateで導入してみた

2026.02.13

はじめに

こんにちは。コンサルティング部の津谷です。みなさんは、Terraform使っていますでしょうか。クラウドインフラのIaC化も昨今ではだいぶ標準的になっていますが、開発フローの改善活動は皆さんも課題感を持っているかと思います。

今日検証するのは、Atlantisというツールになります。こちらはTerraformでのワークフローをpull requests上で実行するOSSになります。Terraformでインフラ管理をする場合は、コードリポジトリを使いますがAtlantisはGithubともネイティブに統合しており、pull requestsの中でterraform planを自動実行して、コメントに出力することが可能です。またatlantisコマンドを使った、コメント内でのterraform applyを実行することが可能です。申し上げた通り、GithubとはWebhookを介して接続しており、ブランチ保護ルールによる承認ルールなども組み込めます。

詳しくは、公式ドキュメントにも記載あるので日本語にGoogle翻訳して読んでみてください。
https://www.runatlantis.io/

自分もAtlantisに入門して、少しずつ勉強をスタートしようと思います。まずは、Atlantisを導入してみて基本的な機能を確かめようと思います。ちなみに、Atlantisはインフラ環境を自分でホスティングする必要があります。今回は、ECS Fargateにデプロイしてみようと思います。さくっと試したい場合はローカルテストもできるのでお勧めです。
https://www.runatlantis.io/guide/testing-locally.html

基盤構成図

構成は以下のようになります。
スクリーンショット 2026-02-10 212959

デプロイするリソースの用途を以下に記載しました。

  • Route53
    • AtlantisはECS Fargateでホスティングします。Fargateの前段に置くALBへの名前解決に利用するドメインを払い出しております。
  • Certificate Manager
    • ALBに付与するSSL/TLS証明書になります。指定のドメインでアクセスさせるのでドメイン名に準拠した形でこちらも利用してみます。
  • Application Load Balancer
    • Fargateでは、複数のタスクをマルチAZに配置するので負荷分散に利用します。
  • ECS Fargate
    • Atlantisを実行する(Terraformの実行)本体になります。
  • Secrets Manager
    • 外部からGithubリポジトリへのアクセスに必要なGithubトークン情報や、Webhookに利用するシークレット情報を格納します。
  • Internet Gateway
    • AtlantisにもWebコンソールがあります。コンソールでは、terraform planの自動実行がどのリポジトリやブランチ上で行われたのかを一覧取得することができます。また、pull requestsを挙げてマージ(Atlantis deploy)するまでは実行ステータスをロックすることができるのですが、こちらもコンソールで状況確認できます。このコンソールへはブラウザ経由でアクセスします。また、Webhook経由でGithubからAtlantisへアクセスするのでこちらもGithubからのアクセス経路が必要になります。
  • Nat Gateway
    • AtlantisはTerraformを実行するので、Fargate起動時にTerraformをインストールしたり、Terraform実行時(デプロイ時)にAWS APIを叩く必要があります。また、PR情報の取得や、terraform planの結果出力でGithubへのアクセス経路が必要です。

構築

Atlantisの実装はモジュールが公開されているので、これを適用してみます。
https://github.com/terraform-aws-modules/terraform-aws-atlantis
モジュールでも手動で作成しておくリソースがあるので、準備しておきます。

  • VPC
  • サブネット
    • パブリック×2、プライベート×2
  • Route53ホストゾーン
    • レコード追加はモジュールを利用できますが、面倒なのでドメイン周りの設定は手動でやってしまいました。
  • Intenet Gateway
  • Nat Gateway
  • Route Table

以下はモジュールで作成できますが手動で作っちゃいました。

  • ALB
    • 本体、リスナー、ターゲットグループ
  • Secret Manager
    • Githubトークン、Webhookシークレットを格納します。
  • ACM
    • ドメイン周りは手動でやっていたので、証明書発行・ALBへの付与はそのまま手動でやりました。

それ以外のリソースはモジュールを使ってデプロイしています。

  • ECSクラスター/サービス/タスク定義
  • IAMロール(タスク実行ロール)
  • CloudWatchログ

上記の様にいくつか手動で作成したので、examples/github-separateリポジトリを参照しました。既存リソースがある場合はこちらが使い勝手いいですね。

ディレクトリ構造は以下です。

atlantis-infra/
├── terraform.tfvars              # 固有の変数値(秘密情報は含めない)  
├── provider.tf                   # プロバイダー設定(Terraform、AWS)
├── variables.tf                  # 入力変数の定義
├── main.tf                       # メインのリソース定義
└── outputs.tf                    # 出力変数の定義

各ファイルの詳細は以下に記載します。

  • provider.tf

    terraform {
      required_version = ">= 1.10"
    
      required_providers {
        aws = {
          source  = "hashicorp/aws"
          version = ">= 6.28"
        }
      }
    }
    
    provider "aws" {
      region = var.aws_region
    
      default_tags {
        tags = {
          Environment = var.environment
          Terraform   = "true"
          ManagedBy   = "Terraform"
        }
      }
    }
    
  • main.tf

    module "atlantis" {
      source  = "terraform-aws-modules/atlantis/aws"
    
      name   = "atlantis"
      vpc_id = var.vpc_id
    
    # Existing cluster
      create_cluster = false
      cluster_arn    = var.cluster_arn
    
    # Existing ALB
      create_alb            = false
      alb_target_group_arn  = var.alb_target_group_arn
      alb_security_group_id = var.alb_security_group_id
    
    # Existing ACM certificate
      create_certificate   = false
      certificate_arn      = var.certificate_arn
      validate_certificate = false
    
    # Route53 configuration
      route53_zone_id        = var.route53_zone_id
      create_route53_records = false
    
    # ECS Container Definition
      atlantis = {
        fqdn = var.atlantis_fqdn
        environment = [
          {
            name  = "ATLANTIS_GH_USER"
            value = var.github_user
          },
          {
            name  = "ATLANTIS_REPO_ALLOWLIST"
            value = var.github_repo_allowlist
          },
        ]
        secrets = [
          {
            name      = "ATLANTIS_GH_TOKEN"
            valueFrom = var.github_token_secret_arn
          },
          {
            name      = "ATLANTIS_GH_WEBHOOK_SECRET"
            valueFrom = var.github_webhook_secret_arn
          },
        ]
      }
    
    # ECS Service
      service = {
        subnet_ids = var.subnet_ids
    
        task_exec_secret_arns = [
          var.github_token_secret_arn,
          var.github_webhook_secret_arn,
        ]
    # Provide Atlantis permission necessary to create/destroy resources
        tasks_iam_role_policies = {
          AdministratorAccess = "arn:aws:iam::aws:policy/AdministratorAccess"
        }
      }
    
      tags = {
      Environment = var.environment
      Terraform   = "true"
      }
    
    }
    

sourceでモジュールを参照しています。モジュールではboolean変数でリソースの作成可否を決められるので、既に作成済みのリソースはfalseにしています。

  • variables.tf

    variable "aws_region" {
    description = "AWS region for resources"
    type        = string
    default     = "ap-northeast-1"
    }
    
    variable "environment" {
    description = "Environment name"
    type        = string
    default     = "tsuya-test"
    }
    
    variable "vpc_id" {
    description = "VPC ID where Atlantis will be deployed"
    type        = string
    }
    
    variable "cluster_arn" {
    description = "Existing ECS cluster ARN"
    type        = string
    }
    
    variable "alb_target_group_arn" {
    description = "Existing ALB target group ARN"
    type        = string
    }
    
    variable "alb_security_group_id" {
    description = "Existing ALB security group ID"
    type        = string
    }
    
    variable "subnet_ids" {
    description = "List of subnet IDs for ECS service"
    type        = list(string)
    }
    
    variable "certificate_arn" {
    description = "Existing ACM certificate ARN for HTTPS"
    type        = string
    }
    
    variable "route53_zone_id" {
    description = "Route53 hosted zone ID"
    type        = string
    }
    
    variable "atlantis_fqdn" {
    description = "FQDN for Atlantis"
    type        = string
    }
    
    variable "github_user" {
    description = "GitHub username for Atlantis"
    type        = string
    }
    
    variable "github_repo_allowlist" {
    description = "GitHub repositories that Atlantis can manage"
    type        = string
    }
    
    variable "github_token_secret_arn" {
    description = "AWS Secrets Manager ARN for GitHub token"
    type        = string
    }
    
    variable "github_webhook_secret_arn" {
    description = "AWS Secrets Manager ARN for GitHub webhook secret"
    type        = string
    }
    
  • output.tf

    output "atlantis_url" {
    description = "URL to access Atlantis"
    value       = module.atlantis.url
    }
    
    output "ecs_service" {
    description = "ECS service details"
    value       = module.atlantis.service
    sensitive   = true
    }
    
  • terraform.tfvars

    # AWS Region
    aws_region  = "ap-northeast-1"
    environment = "tsuya-test"
    
    # Network Configuration
    vpc_id     = "vpc-xxx"
    subnet_ids = ["subnet-xxx", "subnet-xxx"]
    
    # Existing ECS Cluster
    cluster_arn = "arn:aws:ecs:ap-northeast-1:[アカウントID]:cluster/atlantis-cluster"
    
    # Existing ALB Configuration
    alb_target_group_arn  = "arn:aws:elasticloadbalancing:ap-northeast-1:[アカウントID]:targetgroup/atlantis-tg/xxx"
    alb_security_group_id = "sg-xxx"
    
    # ACM Certificate 
    certificate_arn = "arn:aws:acm:ap-northeast-1:xxx:certificate/xxx"
    
    # Route53 Configuration
    route53_zone_id     = "xxx"
    
    # Atlantis FQDN
    atrantis_fqdn = "xxx.com"
    
    # GitHub Configuration
    github_user            = "xxx"
    github_repo_allowlist  = "github.com/[gitユーザ]/[リポジトリ名]"
    
    # AWS Secrets Manager ARNs
    github_token_secret_arn   = "arn:aws:secretsmanager:ap-northeast-1:[アカウントID]:secret:xxx"
    github_webhook_secret_arn = "arn:aws:secretsmanager:ap-northeast-1:[アカウントID]:secret:xxx"
    

これでAtlantisの基盤自体はデプロイが成功します。

Github上での設定

実際にAtlantisを動かしてみましょう。今回は、同じアカウント上にVPCを1つTerraformで作成してみようと思います。コードを格納するテスト用のリポジトリを作成して、リポジトリ上の設定をいくつかやっていきます。

以下の個人アカウントでリポジトリを作成します。
スクリーンショット 2026-02-11 213354
プライベートリポジトリにしています。

次にAtlantisがpull requests上でterraform plan結果を出力するためのGithubユーザを指定する必要があります。今回は検証なので自分のGithubアカウントユーザを利用しますが、誰からコメントされているのか混在させないためにAtlantis用のGithubアカウント(bot)を作成しておくと良いかもしれません。Githubへのアクセストークンも作成しておきましょう。今回は、classicトークンを発行しましたが、個別リポジトリ単位、API単位で制限することがよりセキュアなのでFine-grainedトークンを使ったり、GitAppによる認証を使ったりすることが推奨されます。

Atlantisも公式でリポジトリ単位でのトークン作成を推奨しています。
https://www.runatlantis.io/docs/access-credentials.html

今回はリポジトリへの操作権限をフルで与えていますが、最小に絞る場合は以下の権限だけ付与すればよいです。

  • Commitのステータスの読み取り・編集権限
  • リポジトリのファイルに対する読み取り権限
    • リポジトリをクローンして、コードファイル等を読み取り差分を検出します。情報を基にAtlantisのローカル環境でterraform planを実行しています。
  • リポジトリのメタデータに対する読みとり権限(自動有効なので何もしなくていい)
    • AtlantisのWebUIにpull request中のリポジトリやブランチ情報等が出力されます。
  • プルリクエストへのコメント権限
    • terraform plan/applyの結果出力・エラーメッセージの出力、コメントコマンドの検出を行います。(atlantis plan等)

Fine-grainedトークンを発行する場合は以下のように権限をつけてあげると良いです。
スクリーンショット 2026-02-11 221100

次に、Webhookシークレット作成しましょう。Webhookはpull requestsをトリガーにAtlantisのURLを呼び出して自動planを実行してもらうために必要になります。GithubのGIPで正当性を確認することも可能ですが、可変だと追従が難しいのでシークレットを付与することでGitからのWebhookが正当なものだと確かめることが可能です。Webhookの設定で必要になるので先に設定しておきます。

次に、Webhookの設定をしていきます。
スクリーンショット 2026-02-12 121133

PayloadURLは、AtlantisのWebUIにアクセスするためのドメインを指定してあげます。ドメインはRoute53で払い出したドメインを利用します。「このリポジトリ内でpull requestが発生したらこのドメイン宛てに送ってね」の情報です。公式ドキュメントにも記載ありますが、/eventsパスを追加しておきましょう。Content Typeはjsonを指定します。Atlantisは、Webhookで受け取るイベント(リクエストボディ)をJsonでパースするためです。

次に先ほどジェネレータで作成した文字列をシークレットとして登録します。
スクリーンショット 2026-02-12 121901

次にWebhookのトリガーイベントを設定します。ここは「Let me select individual events」を選択し、個別にイベントを設定しておきます。
スクリーンショット 2026-02-12 120339
イベントは以下を指定しています。

  • Issue comments
    • atlantis plan/applyといったTerraformコマンド操作を実行する際に、pull requests上のコメントで記載するので、commentをトリガーにAtlantisをhookする必要があります。
  • Pull request reviews
    • Atlantis側で、レビュー状況の変更をトリガーにhookします。「terraform実行までのワークフローで例えば、plan実行後にレビュアーに承認されないといけない」といったケースでポリシーを作りこんだ場合に、レビュー状況を知らせてもらう必要があります。
  • Pull requests
    • Pull requestsが作成・更新・クローズされたことをAtlantisに知らせるために必要です。PRが開かれたときに自動でplan を実行したり、マージ・クローズ時にロックを解放したりします。
  • Pushes
    • PRのブランチに追加のコミットがプッシュされたことを検知するためにhookします。

ここまで出来たら、Atlnatisを実行する準備は完了です。

動作確認

VPCを作成してみようと思います。
ローカルで開発を始める前に、先ほど作ったリポジトリはクローンしておきます。ディレクトリ構造は以下です。

atlantis-test/
├── atlantis.yaml              # 固有の変数値(秘密情報は含めない) 
├── provider.tf                # プロバイダー設定(Terraform、AWS)
├── main.tf                    # メインのリソース定義
├── variables.tf               # 入力変数の定義
└── outputs.tf                 # 出力変数の定義
  • atlantis.yaml

    version: 3
    
    projects:
      - name: vpc-project
        dir: .
        workspace: default
        terraform_version: v1.6.0
        autoplan:
          when_modified:
            - "*.tf"
            - "*.tfvars"
          enabled: true
    

ちなみにですが、Atlantis.ymlはなくても動作します。その場合は、リポジトリのルート直下にtfファイル等が必要です。
各変数について整理します。

項目 説明
version atlantis.ymlの書式のバージョンです。最新は3です。
projects リポジトリで管理するTerraformを環境別に分ける場合などに便利です。atlantisコマンドもプロジェクトごとに実行できます。
dir tfファイルがあるディレクトリを指しています。今回はルート直下におくので「.」を指定します。
workspace Terraformのワークスペースです。環境はディレクトリ分離するパターンが多いように感じますが、こちらも1つのコードでワークスペースを切り替えて流用できます。
terraform_version Terraformのバージョンを明示的に指定することが可能です。
autoplan pull requestsが作成された際に自動でteraform plan結果を発動するか決められます。どのファイルの変更をトリガーにするかも指定できます。今回は.tfと.tfvarsのみです。

あとは、省略します。

リポジトリにpushするだけです。pull requestsを作成してみると、自動でterraform planが実行されているのがわかりますね。

スクリーンショット 2026-02-12 153438

さいごに

今回はAtlantisのセットアップをしてみました。セットアップがメインだったのでAtlantisの機能については次回試してみようと思います。今回もお読みいただきありがとうございました。

この記事をシェアする

FacebookHatena blogX

関連記事