Carpe Diem

備忘録

CircleCIのDynamic Configでconfig.ymlを分割管理する

背景

CircleCIを使っているのですが

  • 多数のリポジトリを管理している
  • config.ymlが肥大化している。けれど殆どは似たような記述

といった背景がある上で、新しいjobやworkflowを各リポジトリに適用していく際に

  • コピペ漏れが起きやすい
  • レビューがつらい

といった課題を持っていました。

そんな時にCircleCIがDynamic Configという新機能を出していたので、これを使って

を分割して管理することで、

  • 前者はファイル自体をコピペすれば良い
  • レビューは後者だけ注視すれば良い

という対応をします。

環境

  • CircleCI 2.1
  • circleci/continuation 0.2.0
  • yq 4.24.2

Dynamic Configとは

Dynamic Configを使うと動的にconfigファイルを用意できるので、以下のようなことができます。

  • Workflow毎にyamlを分割して管理できる
    • testやbuild、deployなどを分割して利用できる
  • yqコマンドなどで分割したyamlを結合して1つのconfig.yamlとして扱える
  • ビルド対象をpathベースで指定できる
    • 変更のあるサービスだけビルドする。モノリポでの差分ビルドを実現できる

Github Actionsなら元々できてましたが、CircleCIでも同様なことができるようになりました。

今回はyqを使ったやり方を紹介します。

フロー

Dynamic Configでは以下の図のようなフローを取ります。

f:id:quoll00:20220408055000p:plain

ref: api-preview-docs/setup-workflows.md at master · CircleCI-Public/api-preview-docs · GitHub

つまり最初に動的にconfigを設定するSetup処理があり、その後で設定したconfigのワークフローを実行する流れになります。

導入手順

CircleCI WebUI

CircleCIのWebコンソールで

Project SettingsAdvanced Settings

にてDynamic Configを有効化します。

f:id:quoll00:20220408055222p:plain

Setup workflowの用意

.circleci/config.ymlをSetup処理に書き換えます。

version: 2.1

# Dynamic Configuration
setup: true

orbs:
  continuation: circleci/continuation@0.2.0

jobs:
  some_job:
    docker:
      - image: cimg/base:stable
    steps:
      - checkout
      - run:
          name: "Generate yaml"
          command: |
            # 動的にconfigファイルを用意する処理
      - continuation/continue:
          configuration_path: # 用意したconfigファイル

workflows:
  setup-workflow:
    jobs:
      - some_job

ポイント

基本的にworkflowの書き方自体は変わりませんが、以下の点が違います。

動的に用意するconfig

例えばビルド処理は同じだけれど、リポジトリによってデプロイ先はGCPAWSと異なるみたいな場合に、ビルド処理とデプロイ処理を分割して管理します。

build.yml

version: 2.1

jobs:
  build:
    docker:
      - image: cimg/go:1.18
        auth:
          username: $DOCKERHUB_USER
          password: $DOCKERHUB_ACCESS_TOKEN
    steps:
      - checkout
      - setup_remote_docker:
          version: 20.10.11
          docker_layer_caching: true
      - run:
          name: Login
          command: docker login -u $DOCKERHUB_USER -p $DOCKERHUB_ACCESS_TOKEN
      - run:
          name: Push
          command: make push

deploy.yml

version: 2.1

jobs:
  deploy:
    docker:
      - image: cimg/go:1.18
        auth:
          username: $DOCKERHUB_USER
          password: $DOCKERHUB_ACCESS_TOKEN
    steps:
      - checkout
      - run:
          name: Deploy
          command: make deploy

workflows:
  build-and-deploy:
    jobs:
      - build:
          context:
            - DockerHub
      - deploy:
          context:
            - DockerHub
          requires:
            - build
          filters:
            branches:
              only:
                - /.*/

yqで結合

yqというコマンドで結合することができます。cimg/baseにはデフォルトでインストールされています。

$ yq eval-all '. as $item ireduce ({}; . * $item )' common.yml unique.yml

ref: Combine multiple sources (i.e. reduce) · Issue #674 · mikefarah/yq · GitHub

先程の2つのファイルを結合すると以下のようになります。

version: 2.1
jobs:
  build:
    docker:
      - image: cimg/go:1.18
        auth:
          username: $DOCKERHUB_USER
          password: $DOCKERHUB_ACCESS_TOKEN
    steps:
      - checkout
      - setup_remote_docker:
          version: 20.10.11
          docker_layer_caching: true
      - run:
          name: Login
          command: docker login -u $DOCKERHUB_USER -p $DOCKERHUB_ACCESS_TOKEN
      - run:
          name: Push
          command: make push
  deploy:
    docker:
      - image: cimg/go:1.18
        auth:
          username: $DOCKERHUB_USER
          password: $DOCKERHUB_ACCESS_TOKEN
    steps:
      - checkout
      - run:
          name: Deploy
          command: make deploy
workflows:
  build-and-deploy:
    jobs:
      - build:
          context:
            - DockerHub
      - deploy:
          context:
            - DockerHub
          requires:
            - build
          filters:
            branches:
              only:
                - /.*/

config.ymlは以下になります。

version: 2.1

# Dynamic Configuration
setup: true

orbs:
  continuation: circleci/continuation@0.2.0

jobs:
  yq:
    docker:
      - image: cimg/base:stable
    steps:
      - checkout
      - run:
          name: "Generate yaml"
          command: |
            yq eval-all '. as $item ireduce ({}; . * $item )' .circleci/build.yml .circleci/deploy.yml > .circleci/merged.yml
      - continuation/continue:
          configuration_path: .circleci/merged.yml

workflows:
  setup-workflow:
    jobs:
      - yq

動作確認

CircleCIで実行するとSetup処理の後で動的に用意したconfigが実行されます。

f:id:quoll00:20220412062556p:plain

https://app.circleci.com/pipelines/github/jun06t/circleci-dynamic-config?branch=main&filter=all

その他

tag filterはSetup処理でも必要

CircleCI 2.0 でworkflowを使ったtagからのデプロイ - Carpe Diemでも説明したようにtagフィルターは依存するjobにも同じtagフィルタを付ける必要があります。

なんとDynamic Configのjobにも付けないと発火しないことが分かりましたので付けて下さい。

workflows:
  setup-workflow:
    jobs:
      - yq:
          context:
            - DOCKER
          filters:
            tags:
              only:
                - /.*/

サンプルコード

今回のサンプルコードはこちら

github.com

まとめ

Dynamic Configを使ってCIのconfigファイルを分割して管理できるようになりました。

参考