0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CircleCI でデプロイの競合を防ぐ — serial-group と job-groups によるデプロイ直列化

0
Posted at

AIコーディングやDevOpsの推進などにより、短時間に複数のデプロイを実行する開発組織が増えつつあります。本番であれステージング・検証環境であれ、デプロイ頻度が増加すると、「デプロイタスクの競合」による障害リスクが発生します。1つ目のデプロイタスクが完了する前に2つ目のデプロイが実行されることにより、「アプリケーションコードは2つ目のコードなのに、DBのスキーマは1つ目のコードベースのまま」といった不整合が発生し、そのままシステム障害に直結します。

アトミック性がない場合に複数パイプラインが共有環境で割り込み合う概念図

この記事では、CircleCIを利用してデプロイ作業を競合させず、直列実行させる方法について、job-groupsserial-groupの2機能を使った方法を紹介します。

デプロイジョブを直列化すべき理由

テストや静的解析などの検証タスクは、並列化させることで高速化できます。しかしデプロイについては、1つの環境に対して直列で実行することをお勧めします。それは冒頭に触れたように、「デプロイ作業同士の競合による障害」リスクを回避するためです。

たとえば「deploy ステップでコードをデプロイし、その後追加の設定変更作業等を実施する release ステップも実行する」デプロイフローを考えましょう。並列でデプロイを実行していると、release ステップが終わる前に後続のdeploy ステップが実行されてしまいます。

ステップ単位の順序設定であれば、requiresを利用して依存関係を設定します。今回のようなワークフロー単位で実行順序を制御する場合は、serial-groupを使いましょう。

serial-groupを使った直列化

serial-group をYAMLに設定しましょう。同じserial-groupキーを設定されたジョブは、CircleCIの組織単位で1つのキューを経由します。そのため、仮に複数のジョブがキューに入ったとしても、キューへ入った順番に直列処理されます。

version: 2.1

jobs:
  deploy:
    docker:
      - image: cimg/base:stable
    steps:
      - run: echo "Deploying..."

workflows:
  main:
    jobs:
      - deploy:
          serial-group: production-deploys

同じラベル(ここでは production-deploys)を付けたジョブは、複数のパイプラインから同時にトリガーされても同時には走りません。

CircleCIのUIを確認すると、実行したジョブに[serial-start]と[serial-end]ステップが追加されていることがわかります。この2つに挟まれたジョブは、CircleCIが内部に持つキューを経由して直列実行されます。

Pipeline #36 の verify-single-serial ワークフロー graph

CI/CD ジョブの直列制御3パターン

ここからはいくつかの組み合わせパターンを紹介します。

job-groupsを組み合わせた複数ステップの直列化

複数のステップを1まとめにして直列化させたい場合、job-groupsを組み合わせて利用します。例えばデプロイジョブとリリースジョブをまとめて直列化したいケースでは、次のようなYAMLを作ります。

version: 2.1

jobs:
  build:
    docker:
      - image: cimg/base:stable
    steps:
      - run: echo "Building..."
  deploy:
    docker:
      - image: cimg/base:stable
    steps:
      - run: echo "Deploying..."
  release:
    docker:
      - image: cimg/base:stable
    steps:
      - run: echo "Releasing..."
  notify:
    docker:
      - image: cimg/base:stable
    steps:
      - run: echo "Notifying..."

# deploy と release を 1 グループとして定義する
job-groups:
  deploy-and-release:
    jobs:
      - deploy
      - release:
          requires:
            - deploy

workflows:
  main:
    jobs:
      - build
      - deploy-and-release:
          requires:
            - build
          serial-group: production-critical
      - notify:
          requires:
            - deploy-and-release

workflowsjobsjobを直接登録する代わりに、job-groupsにジョブ内容をまとめています。job-groupsで定義した物をworkflowsへ追加し、そこにserial-groupを設定することにより、複数のジョブについても直列実行を指示できます。

こちらも実行結果を見てみましょう。[serial-start]と[serial-end]の間に[deploy]と[release]両方のジョブが挟まれています。

Pipeline #34 の verify-job-groups-serial ワークフロー graph

job-groupsserial-groupを組み合わせることで、deployおよびreleaseの2ジョブが完了するまで、後続のdeployジョブはスタートされずに待機します。

直列化する範囲をプロジェクト単位に限定する

serial-groupを含むジョブを、URL Orbsやgit submoduleで配布しているケースを考えましょう。このケースでは、複数のプロジェクトで同じ名前のserial-groupを使用することになります。そうなると「Aのプロジェクトのデプロイが終わるまで、Bのプロジェクトでもデプロイが行えなくなる」といった意図しない直列化が発生します。

そこで、serial-group の値にパイプラインコンテキストを埋め込むと、直列化のスコープをプロジェクト単位に絞れます。

workflows:
  main:
    jobs:
      - deploy-and-release:
-          serial-group: production-critical
+          serial-group: << pipeline.project.slug >>/production-critical

<< pipeline.project.slug >> のようなプロジェクト固有の値を組み合わせたグループ名にすることで、意図しない競合解決・直列化によるCDフローのつまりを解消できます。

2 プロジェクトの verify-project-scope が同時期に完走している比較
左が vitest-test-splitting の Pipeline #3、右が ci-sandboxes の Pipeline #42 です。同一サフィックスの serial-group でも、プロジェクト slug により別キューとなり、両ワークフローが同時期に完走しています。

デプロイが失敗した時のロールバック措置

serial-groupを指定したjog-groupsが途中でfailした場合、例えばコードのデプロイは成功したけども、DBの変更などが未完了という状況が発生します。不整合が起きた状況のまま、後続のワークフローが走り出すと、さらに問題が複雑になる可能性があります。

そのため、serial-groupjob-groupsを利用したフローを設計する際は、requiresを利用したロールバックジョブまで設定することをお勧めします。

job-groups:
  deploy-and-release:
    jobs:
      - deploy
      - release:
          requires:
            - deploy
      - rollback:
          requires:
            - deploy:
                - failed
                - canceled

例えばこのYAMLでは、deployジョブが失敗あるいはキャンセルされた時だけ、rollbackジョブが実行されます。

terraformやAWS CDK / Helmなどを利用したインフラのコード化(IaC)で、宣言的なデプロイを実現できます。これらに加えてrollbackジョブを整備することで、冪等化の難しい作業もロールバックできるようにしましょう。

serial-groupを使う際の注意点

serial-group含むワークフローを再実行すると、グループ内のジョブもまとめて再実行されます。そのため、「グループ内の特定ジョブだけ再実行する」のではなく、グループ内のジョブは全て再実行される想定で運用フローなどを準備しましょう。

Pipeline #41 verify-06-rerun の再実行 graph

まとめ

job-groupsserial-group を組み合わせると、「複数ジョブを 1 つの塊として直列化する」という要件を、設定ファイルだけで表現できます。また、途中で失敗した時のロールバック方法などについても、requiresなどを組み合わせることで、YAMLにて表現が可能です。

複雑なリリース作業はIaCによる宣言的デプロイの実践と、柔軟なCDパイプラインの制御で自動化・効率化を目指しましょう。

参考リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?