ECS on Fargate × PipeCDで実現するGitOpsとカナリアリリース

ECS on Fargate × PipeCDで実現するGitOpsとカナリアリリース

はじめに

こんにちは、ZOZOMO部SREブロックの中村です。普段はZOZOMOのSREを担当しています。

本記事では、ECS on FargateにPipeCDを導入してGitOpsベースのデプロイ基盤を構築した取り組みをご紹介します。デプロイ経路の複数存在による管理の煩雑さと、段階的デプロイができない課題をPipeCDでどのように解決したかを解説します。

本記事がECS on Fargateを運用していてGitOps化に興味がある方や、PipeCDの導入を検討している方の参考になれば幸いです。

目次

導入前の課題

PipeCD導入前、私たちのチームでは以下の課題を抱えていました。

  • デプロイ経路の複数存在による管理の煩雑さ
  • 段階的デプロイができない

デプロイ経路の複数存在による管理の煩雑さ

アプリケーションのタスク定義をデプロイする経路が複数存在していました。アプリ、インフラそれぞれのデプロイの仕方によってデプロイ方法が異なり、どの経路でデプロイされたかの追跡が困難な状況でした。これにより以下の問題が発生していました。

  • デプロイ履歴の一元管理ができない
  • 障害発生時の原因特定に時間がかかる
  • 予期しないロールバックを引き起こす可能性

段階的デプロイができない

従来の環境では、新しいバージョンのアプリケーションを一度に全てのタスクにデプロイしていました。問題のあるリリースでは全ユーザーに影響するリスクがありました。

  • カナリアリリースの仕組みがない
  • 問題発生時の影響範囲が大きい
  • ロールバックに時間がかかる

解決アプローチ

これらの課題を解決するために、PipeCDの導入を決定しました。

CDツールの選定にあたり、Argo CD、CodePipeline(CodeBuild + CodeDeploy)、PipeCDを比較検討しました。

観点 Argo CD CodePipeline PipeCD
Kubernetes対応 サポート 非対応 サポート
ECS 対応 非対応 サポート サポート
GitOps 対応 非対応 対応
カナリアリリース 対応 対応 対応

Argo CDはEKSを含むKubernetes環境では広く採用されていますが、ECSへのネイティブ対応がありません。CodePipelineはECS on Fargateに対応していますが、GitOpsのようにGitの情報を正とは必ずしも言えないと思います。一方、PipeCDはECSを含むマルチプラットフォームに対応しており、単一のツールでGitOpsとカナリアリリースを実現できる点が決め手となりました。

PipeCDとは

PipeCDは、マルチクラウド・マルチプラットフォームに対応した継続的デリバリー(CD)ツールです。GitOpsの原則に基づき、Gitリポジトリをデプロイにおいて信頼できる唯一の情報源(Single Source of Truth)として扱います。

主な特徴としては以下の点が挙げられます。

  • GitOpsベース: プルリクエストによるデプロイ操作で、変更履歴が明確に管理される
  • マルチプラットフォーム対応: Kubernetes、ECS、Lambda、Cloud Run、Terraformなど幅広く対応
  • 段階的デプロイ: カナリアリリース、ブルーグリーンデプロイメントを標準サポート
  • セキュリティ: デプロイ用の認証情報がクラスター外に出ない設計
  • 可視化: デプロイ状況をWeb UIでリアルタイムに確認可能

またPipeCDは、大きく分けてControl PlaneとPipedの2つのコンポーネントで構成されます。

コンポーネント 役割
Control Plane Web UI、APIサーバー、デプロイ状況の管理
Piped 実際のデプロイを実行するエージェント。GitリポジトリとECSを監視し、差分を検知してデプロイを実行

Pipedはデプロイ対象の環境(今回はECS)と同じネットワーク内に配置します。

詳細は以下のPipeCD公式ドキュメントをご参照ください。

導入構成

今回構築した環境の全体像は以下のとおりです。

ECS on FargateへのPipeCD導入アーキテクチャ

  • Control Plane: 専用のECSで稼働
  • Piped: デプロイ対象と同じVPC内のECSクラスターで稼働
  • マニフェスト管理: 専用のGitHubリポジトリで管理

PipeCDへのログインには、Entra IDを用いたSAML認証を採用しました。弊社ではSSO基盤としてEntra IDを広く利用しており、一元管理されたアクセス管理を実現しています。

実装

Control Planeの構築

Control Planeは以下のコンポーネントで構成され、今回はこのような構成で作成しました。

コンポーネント 役割 今回の構成
Server Web UI、APIサーバー ECS on Fargate
Ops 管理用サーバー ECS on Fargate
Gateway gRPC/HTTPルーティング Envoy(サイドカー)
Cache セッション・ログのキャッシュ Redis(サイドカー)
Datastore デプロイ情報の永続化 Aurora MySQL
Filestore ログ等のファイル保存 S3

PipeCDの利用を検証した際にそこまで膨大なセッションやログのキャッシュを持たないことから、ElastiCacheを利用するのではなく、サイドカーにRedisを立ててコスト面を抑える構成にしました。ECS上でのControl Plane構築例はPipeCD公式のデモリポジトリを参考に実装しました。

事前準備

Control Planeを構築する前に、以下のリソースを作成しました。

  • Aurora MySQL: Datastore用のデータベース
  • S3バケット: Filestore用のバケット
  • ALB: gRPC/HTTP通信用のロードバランサー(Target GroupはgRPC用とHTTP用の2つ)
  • Secrets Manager: 設定ファイルや暗号化キーの保存

ECSタスク構成

Control PlaneのECSタスクを、以下のコンテナで構成しました。

┌─────────────────────────────────────────────────────┐
│ ECS Task (Control Plane)                            │
│  ┌─────────────┐ ┌─────────────┐ ┌───────────────┐  │
│  │   Redis     │ │   Envoy     │ │ PipeCD Server │  │
│  │  (cache)    │ │  (gateway)  │ │               │  │
│  │  port:6379  │ │  port:9090  │ │ port:9080-9083│  │
│  └─────────────┘ └─────────────┘ └───────────────┘  │
└─────────────────────────────────────────────────────┘
  • Redis: ログやセッション情報のキャッシュ
  • Envoy: gRPC/HTTPリクエストのルーティング(ALBからのトラフィックを受信)
  • PipeCD Server: Web UIとAPIを提供

設定ファイル

Control Planeの設定はcontrol-plane-config.yamlで定義します。

apiVersion: "pipecd.dev/v1beta1"
kind: ControlPlane
spec:
  datastore:
    type: MYSQL
    config:
      url: ${DB_USER}:${DB_PASS}@tcp(pipecd-mysql:3306)
      database: controlplane
  filestore:
    type: S3
    config:
      bucket: ${ENV}-${RESOURCE_PREFIX}-pipecd-filestore
      region: ap-northeast-1
  stateKey: ${statekey}
  sharedSSOConfigs:
    - name: cognito-azure
      provider: OIDC
      oidc:
        clientId: ${clientId}
        clientSecret: ${clientSecret}
        issuer: https://cognito-idp.ap-northeast-1.amazonaws.com/${userPoolId}
        redirectUri: https://pipecd.${DOMAIN}/auth/callback
        scopes:
          - openid
          - profile

設定ファイルはBase64エンコードしてSecrets Managerに登録し、ECSタスク定義から参照します。詳細は公式ドキュメント(Configuration reference)をご参照ください。

また、Envoy(Gateway)の設定はenvoy-config.yamlで定義します。EnvoyはgRPC/HTTPリクエストをPipeCD Serverの各サービスにルーティングする役割を担います。

admin:
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 9095

static_resources:
  listeners:
    - name: ingress
      address:
        socket_address:
          address: 0.0.0.0
          port_value: 9090
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                codec_type: AUTO
                stat_prefix: ingress_http
                http_filters:
                  - name: envoy.filters.http.grpc_web
                  - name: envoy.filters.http.router
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
                route_config:
                  name: local_route
                  virtual_hosts:
                    - name: envoy
                      domains:
                        - '*'
                      routes:
                        # Piped Service(Pipedとの通信)
                        - match:
                            prefix: /grpc.service.pipedservice.PipedService/
                            grpc: {}
                          route:
                            cluster: grpc-piped-service
                        # Web Service(Web UIからの通信)
                        - match:
                            prefix: /grpc.service.webservice.WebService/
                            grpc: {}
                          route:
                            cluster: grpc-web-service
                        # API Service(外部APIからの通信)
                        - match:
                            prefix: /grpc.service.apiservice.APIService/
                            grpc: {}
                          route:
                            cluster: grpc-api-service
                        # その他のHTTPリクエスト(静的ファイル等)
                        - match:
                            prefix: /
                          route:
                            cluster: server-http
  clusters:
    - name: grpc-piped-service
      http2_protocol_options: {}
      connect_timeout: 0.25s
      type: STRICT_DNS
      lb_policy: ROUND_ROBIN
      load_assignment:
        cluster_name: grpc-piped-service
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: localhost
                      port_value: 9080
    - name: grpc-web-service
      http2_protocol_options: {}
      connect_timeout: 0.25s
      type: STRICT_DNS
      lb_policy: ROUND_ROBIN
      load_assignment:
        cluster_name: grpc-web-service
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: localhost
                      port_value: 9081
    - name: grpc-api-service
      http2_protocol_options: {}
      connect_timeout: 0.25s
      type: STRICT_DNS
      lb_policy: ROUND_ROBIN
      load_assignment:
        cluster_name: grpc-api-service
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: localhost
                      port_value: 9083
    - name: server-http
      connect_timeout: 0.25s
      type: STRICT_DNS
      lb_policy: ROUND_ROBIN
      load_assignment:
        cluster_name: server-http
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: localhost
                      port_value: 9082

Envoyの設定では、リクエストのパスに応じてPipeCD Serverの各サービスにルーティングしています。

ECSタスク定義(CloudFormation)

Control PlaneのECSタスク定義は以下のように構成しました。Redis、Envoy(Gateway)、PipeCD Serverの3コンテナをサイドカー構成で動作させています。

Secretsで宣言しているCONTROL_PLANE_CONFIGとENVOY_CONFIGは、それぞれの設定ファイルをBase64エンコードした値です。これらはControl PlaneとEnvoyの起動時に利用されます。必要なリソースは別途宣言する必要があります。

ECSTaskDefinition:
  Type: 'AWS::ECS::TaskDefinition'
  Properties:
    Family: !Sub '${Env}-${ResourcePrefix}-pipecd-control-plane'
    NetworkMode: 'awsvpc'
    RequiresCompatibilities:
      - 'FARGATE'
    Cpu: !Ref ECSTaskCPU
    Memory: !Ref ECSTaskMemory
    ExecutionRoleArn: !GetAtt ECSTaskExecutionRole.Arn
    TaskRoleArn: !GetAtt ECSTaskRole.Arn
    ContainerDefinitions:
      # Redisコンテナ(キャッシュ用)
      - Name: 'redis'
        Image: 'redis:7.4-alpine'
        Essential: false
        PortMappings:
          - ContainerPort: 6379
            Protocol: tcp
        Command:
          - 'redis-server'
          - '--appendonly'
          - 'yes'
          - '--maxmemory'
          - '128mb'
          - '--maxmemory-policy'
          - 'allkeys-lru'
        LogConfiguration:
          LogDriver: 'awslogs'
          Options:
            awslogs-group: !Ref LogsLogGroup
            awslogs-region: !Ref 'AWS::Region'
            awslogs-stream-prefix: 'redis'
      # Envoyコンテナ(Gateway)
      - Name: 'pipecd-gateway'
        Image: !Ref PipeCDGatewayImageURL  # envoyproxy/envoy
        Essential: false
        PortMappings:
          - HostPort: 9090
            ContainerPort: 9090
            Protocol: tcp
        Command:
          - '/bin/sh -c ''echo $ENVOY_CONFIG | base64 -d >> envoy-config.yaml; envoy -c envoy-config.yaml;'''
        EntryPoint:
          - sh
          - '-c'
        Secrets:
          - Name: 'ENVOY_CONFIG'
            ValueFrom: !Sub '${SecretsManagerEnvoyConfigArn}:config::'
        LogConfiguration:
          LogDriver: 'awslogs'
          Options:
            awslogs-group: !Ref LogsLogGroup
            awslogs-region: !Ref 'AWS::Region'
            awslogs-stream-prefix: 'pipecd-gateway'
        DependsOn:
          - ContainerName: 'redis'
            Condition: 'START'
      # PipeCD Serverコンテナ
      - Name: 'pipecd-server'
        Image: !Ref PipeCDServerImageURL  # ghcr.io/pipe-cd/pipecd
        Essential: true
        Command:
          - !Sub '/bin/sh -c ''echo $CONTROL_PLANE_CONFIG | base64 -d >> control-plane-config.yaml; echo $ENCRYPTION_KEY >> encryption-key; pipecd server --insecure-cookie=true --cache-address=localhost:6379 --config-file=control-plane-config.yaml --encryption-key-file=encryption-key;'''
        EntryPoint:
          - sh
          - '-c'
        Secrets:
          - Name: 'CONTROL_PLANE_CONFIG'
            ValueFrom: !Sub '${SecretsManagerControlPlaneConfigArn}:config::'
          - Name: 'ENCRYPTION_KEY'
            ValueFrom: !Sub '${SecretsManagerEncryptionKeyArn}:key::'
        LogConfiguration:
          LogDriver: 'awslogs'
          Options:
            awslogs-group: !Ref LogsLogGroup
            awslogs-region: !Ref 'AWS::Region'
            awslogs-stream-prefix: 'pipecd-server'
        DependsOn:
          - ContainerName: 'redis'
            Condition: 'START'

Control Planeの初期設定

Control PlaneのECSタスクがデプロイされた後、初期設定を実施します。この手順でPipedの認証情報を取得するため、Piped構築の前に実施します。

OPSサーバーへの接続

PipeCD OPSサーバーに接続し、プロジェクトの作成やユーザー管理を実施します。OPSサーバーは外部公開していないため、SSM Session Managerを使用してポートフォワードで接続します。

ポートフォワードが確立されたら、ブラウザでlocalhostを叩き、OPSサーバーにアクセスして管理画面を開きます。

ops

プロジェクトの作成

管理画面から「Add Project」でプロジェクトを作成します。

項目 設定値
ID プロジェクト識別子(例:your-project-dev)
Description 任意の説明
Shared SSO cognito-azure(control-plane-config.yamlで定義したSSO設定名)

createProject

プロジェクト作成後に表示される Static Admin UsernameStatic Admin Password は、初回ログインに必要なため必ずメモしておきます。

ユーザーグループの作成

  1. Control PlaneのURLにアクセス(例:https://pipecd.your-domain.com
  2. 作成したプロジェクト名を入力
  3. Static Adminの認証情報でログイン
  4. 「Settings」→「User Groups」からユーザーグループを作成

adminLogin

createUserGroup

グループ名 ロール 権限
Admin Admin プロジェクト全体の管理権限
Editor Editor デプロイ・設定変更(ユーザー管理除く)
Viewer Viewer 読み取り専用

権限の詳細は公式ドキュメント(Authentication and authorization)をご参照ください。

SSOログインの確認

  1. ログアウトし、「Login with OIDC」をクリック
  2. Entra IDの認証画面でログイン

Pipedの登録

Control PlaneにPipedを登録し、Piped構築に必要な認証情報を取得します。

  1. 「Settings」→「Piped」→「Add Piped」をクリック
  2. Piped情報を入力(Name、Description)
  3. 「Save」をクリック
  4. 表示される以下の情報をメモ
    • Piped Id: Piped設定ファイルのpipedIDに使用
    • Base64 Encoded Piped Key: Piped設定ファイルのpipedKeyDataに使用

createPiped

createdPiped

これらの値は次の「Pipedの構築」で使用します。

Pipedの構築

PipedはControl Planeと通信し、実際のデプロイを実行するエージェントです。デプロイ対象のECSクラスターと同じVPC内にECS on Fargateで構築しました。

Pipedの設定はpiped-config.yamlで定義します。

apiVersion: pipecd.dev/v1beta1
kind: Piped
spec:
  projectID: ${PROJECT_ID}
  pipedID: ${PIPED_ID}
  pipedKeyData: ${PIPED_KEY_DATA}
  apiAddress: pipecd.${DOMAIN}:443
  git:
    sshKeyData: ${SSH_KEY_DATA}
  repositories:
    - repoId: ${REPO}
      remote: git@github.com:hoge/${REPO}.git
      branch: main
  platformProviders:
    - name: ECS
      type: ECS
      config:
        region: ap-northeast-1
  notifications:
    routes:
      - name: xxxxx_ci_notice
        events:
          - DEPLOYMENT_TRIGGERED
          - DEPLOYMENT_SUCCEEDED
          - DEPLOYMENT_FAILED
          - DEPLOYMENT_ROLLING_BACK
        receiver: xxxxx_ci_notice
    receivers:
      - name: xxxxx_ci_notice
        slack:
          hookURL: ${SLACK_WEBHOOK_URL}

pipedIDpipedKeyDataは、Control PlaneのWeb UIからPipedを登録した際に発行されます。詳細は公式ドキュメント(Configuration reference)をご参照ください。

ECSタスク定義(CloudFormation抜粋)

PipedのECSタスク定義は以下のように構成しました。Secretsで宣言しているCONFIG_DATAは、piped-config.yamlをBase64エンコードした状態で取得し、Pipedの起動時に利用されます。

ECSTaskDefinitionPiped:
  Type: 'AWS::ECS::TaskDefinition'
  Properties:
    Family: !Sub '${Env}-${ResourcePrefix}-pipecd-piped'
    NetworkMode: 'awsvpc'
    RequiresCompatibilities:
      - 'FARGATE'
    Cpu: !Ref PipeCDPipedTaskCpuType
    Memory: !Ref PipeCDPipedTaskMemoryType
    ExecutionRoleArn: !GetAtt ECSTaskExecutionRole.Arn
    TaskRoleArn: !GetAtt ECSTaskRole.Arn
    ContainerDefinitions:
      - Name: 'piped'
        Image: !Sub '${ECRRepositoryUri}:${PipeCDPipedImageTag}'
        Essential: true
        EntryPoint:
          - sh
          - -c
        Command:
          - /bin/sh -c "piped piped --config-data=$(echo $CONFIG_DATA)"
        Secrets:
          - Name: 'CONFIG_DATA'
            ValueFrom: !Sub '${SecretsManagerPipedConfigArn}:config::'
        LogConfiguration:
          LogDriver: 'awslogs'
          Options:
            awslogs-group: !Ref LogsLogGroup
            awslogs-region: !Ref 'AWS::Region'
            awslogs-stream-prefix: 'piped'

IAMロール

PipedがECSサービスをデプロイするために、タスクロールに以下の権限を付与しています。

ECSTaskRole:
  Type: 'AWS::IAM::Role'
  Properties:
    RoleName: !Sub '${Env}-${ResourcePrefix}-pipecd-piped-ecs-task-role'
    AssumeRolePolicyDocument:
      Version: '2012-10-17'
      Statement:
        - Effect: 'Allow'
          Principal:
            Service: 'ecs-tasks.amazonaws.com'
          Action: 'sts:AssumeRole'
    Policies:
      - PolicyName: 'PipedECSServiceAccess'
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: 'Allow'
              Action:
                - ecs:CreateService
                - ecs:CreateTaskSet
                - ecs:DeleteTaskSet
                - ecs:DeregisterTaskDefinition
                - ecs:DescribeServices
                - ecs:DescribeTaskDefinition
                - ecs:DescribeTaskSets
                - ecs:DescribeTasks
                - ecs:ListClusters
                - ecs:ListServices
                - ecs:ListTaskDefinitions
                - ecs:ListTasks
                - ecs:RegisterTaskDefinition
                - ecs:RunTask
                - ecs:TagResource
                - ecs:UntagResource
                - ecs:ListTagsForResource
                - ecs:UpdateService
                - ecs:UpdateServicePrimaryTaskSet
                - elasticloadbalancing:DescribeListeners
                - elasticloadbalancing:DescribeRules
                - elasticloadbalancing:DescribeTargetGroups
                - elasticloadbalancing:ModifyListener
                - elasticloadbalancing:ModifyRule
              Resource: "*"
            - Effect: 'Allow'
              Action:
                - iam:PassRole
              Resource:
                - !Sub 'arn:aws:iam::${AWS::AccountId}:role/${Env}-${ResourcePrefix}-ecs-task-execution-role'
                - !Sub 'arn:aws:iam::${AWS::AccountId}:role/${Env}-${ResourcePrefix}-task-role'

PipedステータスのONLINE確認

PipedのECSタスクが起動したら、Control PlaneでPipedのステータスを確認します。

  1. Control PlaneのWeb UIにログイン
  2. 「Settings」→「Piped」へ移動
  3. 登録したPipedのステータスを確認

piped-status

ステータスが ONLINE(緑色のインジケーター)になっていれば、Control Planeとの接続が正常に確立されています。

ステータスがOFFLINEの場合は、以下を確認してください。

  • Piped設定ファイルのpipedIDpipedKeyDataが正しいか
  • apiAddressがControl PlaneのURLと一致しているか
  • ECSタスクのログでエラーが出ていないか

マニフェストファイルの構成

PipeCDでECSアプリケーションを管理するために、以下のファイルを用意しました。

ファイル 役割
app.pipecd.yaml PipeCDアプリケーション定義(デプロイ戦略、参照ファイルの指定等)
servicedef.yaml ECSサービス定義
taskdef.yaml ECSタスク定義

app.pipecd.yaml

PipeCDのアプリケーション設定ファイルです。デプロイ対象のサービス定義・タスク定義ファイルやターゲットグループを指定します。

apiVersion: pipecd.dev/v1beta1
kind: ECSApp
spec:
  name: hogehoge
  labels:
    env: dev
    team: zozo-xxxx
    app: hogehoge
  notification:
    mentions:
      - event: DEPLOYMENT_TRIGGERED
      - event: DEPLOYMENT_SUCCEEDED
      - event: DEPLOYMENT_FAILED
      - event: DEPLOYMENT_ROLLING_BACK
  input:
    serviceDefinitionFile: servicedef.yaml
    taskDefinitionFile: taskdef.yaml
    targetGroups:
      primary:
        targetGroupArn: arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxx:targetgroup/primary/xxxxxxxxxxxxx
        containerName: hogehoge
        containerPort: 8080
      canary:
        targetGroupArn: arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxx:targetgroup/canary/xxxxxxxxxxxxx
        containerName: hogehoge
        containerPort: 8080
  pipeline:
    stages:
      - name: ECS_CANARY_ROLLOUT
        with:
          scale: 20
      - name: ECS_TRAFFIC_ROUTING
        with:
          canary: 10
      - name: WAIT_APPROVAL
      - name: ECS_PRIMARY_ROLLOUT
      - name: ECS_TRAFFIC_ROUTING
        with:
          primary: 100
      - name: ECS_CANARY_CLEAN

設定項目の詳細は公式ドキュメント(Configuring ECS application)をご参照ください。

servicedef.yaml

ECSサービスの定義ファイルです。

version: v1
spec:
  cluster: arn:aws:ecs:ap-northeast-1:xxxx:cluster/sample-cluster
  serviceName: hogehoge
  desiredCount: 6
  launchType: FARGATE
  schedulingStrategy: REPLICA
  networkConfiguration:
    awsvpcConfiguration:
      subnets:
        - subnet-xxxx
        - subnet-yyyy
        - subnet-zzzz
      securityGroups:
        - sg-xxxx
      assignPublicIp: DISABLED
  propagateTags: SERVICE
  deploymentController:
    type: EXTERNAL

taskdef.yaml

ECSタスクの定義ファイルです。コンテナイメージやリソース設定を記述します。

family: hogehoge
networkMode: awsvpc
requiresCompatibilities:
  - FARGATE
cpu: "256"
memory: "512"
executionRoleArn: arn:aws:iam::xxxx:role/ecsTaskExecutionRole
taskRoleArn: arn:aws:iam::xxxx:role/hogehoge-task-role
containerDefinitions:
  - name: app
    image: xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/hogehoge:latest
    essential: true
    portMappings:
      - containerPort: 8080
        protocol: tcp
    logConfiguration:
      logDriver: awslogs
      options:
        awslogs-group: /ecs/hogehoge
        awslogs-region: ap-northeast-1
        awslogs-stream-prefix: ecs

アプリケーションの登録

マニフェストファイルをGitリポジトリにプッシュしたら、Control PlaneでアプリケーションをPipedに紐づけます。

  1. Control PlaneのWeb UIにログイン
  2. 「Applications」→「Add」をクリック
  3. アプリケーション情報を入力
項目 設定値
Name アプリケーション名(例:hogehoge)
Kind ECS
Piped 登録済みのPipedを選択
Repository piped-config.yamlで定義したリポジトリを選択
Path マニフェストファイルが配置されているディレクトリパス
Config Filename app.pipecd.yaml

pipecd-app

  1. 「Save」をクリック

登録が完了すると、PipedがGitリポジトリを監視し始めます。

デプロイの実行

アプリケーションの登録後、以下の流れでデプロイが実行されます。

初回デプロイ(Quick Sync)

アプリケーション登録直後は、現在のマニフェストの状態でデプロイが実行されます。「Applications」画面でデプロイの進行状況を確認できます。

通常のデプロイフロー

  1. マニフェストファイル(taskdef.yaml等)を変更
  2. GitリポジトリにPush
  3. Pipedが変更を検知し、自動でデプロイを開始
  4. app.pipecd.yamlで定義したパイプラインに従って段階的にデプロイ

補足: カナリアリリースの流れ

app.pipecd.yamlでカナリアリリースのパイプラインを定義していた場合、以下の流れでカナリアリリースが実行されます。

  1. ECS_CANARY_ROLLOUT: Canary用のTaskSetを作成
  2. ECS_TRAFFIC_ROUTING: Canaryにトラフィックを流す
  3. WAIT_APPROVAL: 承認待ち(Web UIで「Approve」をクリック)
  4. ECS_PRIMARY_ROLLOUT: Primary用のTaskSetを新バージョンに更新
  5. ECS_TRAFFIC_ROUTING: Primaryに100%のトラフィックを戻す
  6. ECS_CANARY_CLEAN: Canary用のTaskSetを削除

pipecd-app

承認待ちの間にCanary環境で問題が見つかった場合は、Web UIから「Cancel」でロールバックできます。

Webhook連携による自動テスト実行

PipeCDの通知機能を活用して、デプロイ成功後に自動でE2Eテストを実行する仕組みを構築しました。

Pipedの設定(piped-config.yaml)で、特定のアプリケーション・環境のデプロイ成功時にWebhookを送信できます。

# piped-config.yaml
notifications:
  routes:
    - name: acceptance_test_webhook
      events:
        - DEPLOYMENT_SUCCEEDED
      labels:
        app: test-app
        env: dev
      receiver: webhook_receiver
  receivers:
    - name: webhook_receiver
      webhook:
        url: ${WEBHOOK_URL}

私たちの環境では、WebhookをZapierに送信し、ZapierからGitHub Actionsのワークフローをトリガーしています。GitHub Actionsのワークフローでは、DevのAWS環境で稼働するtest-appへE2Eテストを実行しています。これにより、dev環境のtest-appデプロイ成功後にE2Eテストが自動実行される仕組みを実現しました。詳細は公式ドキュメント(Configuring Notifications)をご参照ください。

導入効果

PipeCDの導入により、以下の効果が得られました。

  • デプロイ経路の一本化
  • 段階的リリースによるリスク低減

デプロイ経路の一本化

GitOpsの原則に基づき、全てのデプロイがGitリポジトリを経由するようになりました。

  • デプロイ履歴がGitのコミット履歴として残る
  • 誰が・いつ・何をデプロイしたかが明確になった
  • PipeCDのWeb UIでデプロイ状況をリアルタイムに確認可能

段階的リリースによるリスク低減

カナリアリリースの導入により、リリースに伴うリスクを大幅に低減できました。

  • 新バージョンの問題を早期に検知可能
  • 問題発生時の影響範囲を限定できる
  • 自動ロールバックにより迅速な復旧が可能

まとめ

本記事では、ECS on FargateにPipeCDを導入してGitOpsベースの段階的リリースを実現した取り組みを紹介しました。ECS on FargateでGitOps化を検討している方や、カナリアリリースを導入したい方は、ぜひPipeCDを検討してみてください。

ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。

corp.zozo.com

カテゴリー