無職です。はたらいてます。 旧ブログ: 1 Entry per Day

真似しちゃいけないdeployステップの使い方

これはCircleCI Advent Calendar 2019の8日目の記事です。

qiita.com


こんにちは、CircleCI大プッシュしてたらCircleCIの回し者だと思われた@mstsskです。

今回はdeployステップの変な使い方を思いついたので紹介します。

ただし、真似しちゃいけません。

deployステップについて

CircleCIでは、CIジョブで実行するコマンドのステップをrunというキーワードで記述します。 そして実はdeployというキーワードでもコマンドを記述できるようになっています。

deployステップの書き方はrunと同じですが、文字通りデプロイのためのステップであり、動作が異なります。

parallelism を使ったジョブの場合、deploy ステップは他のすべてのノードが成功した場合にのみ、ノード番号 0 として実行されます。 ノード番号 0 以外はステップを実行しません。

引用元: https://circleci.com/docs/ja/2.0/configuration-reference/#deploy

deployステップを使ったconfig.ymlは次のような感じになります。

# .circleci/config.yml
version: 2.1
jobs:
  build:
    docker:
      - image: circleci/ruby
    parallelism: 4 # 4並列で実行
    steps:
      - checkout
      - run: bundle install
      - run: # circleciコマンドを使ってテストを4分割して実行
          name: Parallel Testing
          command: bundle exec rspec $(circleci tests glob spec/**/*.rb | circleci tests split)
      - deploy: # テストが通ったらデプロイ。このステップは4つのノードのうち0番目でだけ実行される
          name: Deploying to dev env
          command: bundle exec cap dev deploy

つまり、deployステップを使うとCircleCIのジョブの中で並列処理の待ち合わせができるんです!

待ち合わせ処理を書いてみる

並列実行している処理の待ち合わせが書けるなら、あとはデータの受け渡しができれば、もう並列処理のプログラム書けるようなもんだよね? 例えば、大きな処理の分割実行とその結果の統合が1つのジョブの中で済ませられるのでは?と考えてやってみました。

deployステップで並列実行の待ち合わせをして、各ノードの結果をCircleCIのキャッシュ経由で受け渡しするサンプルは次のとおりです。

# .circleci/config.yml
version: 2.1
jobs:
  build:
    docker:
      - image: circleci/node:12
    parallelism: 4
    steps:
      - checkout

      # 成果物を result-{ノード番号}.txt に保存する、ビルドとかテスト実行など並列実行したい遅い処理
      - run:
          name: Building Some Results
          command: |
            mkdir results/ &&
            ./dummy_slow.sh > results/result-$CIRCLE_NODE_INDEX.txt

      # 成果物をキャッシュに保存
      - save_cache:
          key: results-{{ .BuildNum }}-{{ .Environment.CIRCLE_NODE_INDEX }}
          paths:
            - results/

      # 各ノードのビルド実行・キャッシュ保存を待つ
      - deploy:
          name: Waiting Results
          command: ":"

      # 各ノードの成果物をキャッシュから取り出す
      - restore_cache: { keys: ["results-{{ .BuildNum }}-1"] }
      - restore_cache: { keys: ["results-{{ .BuildNum }}-2"] }
      - restore_cache: { keys: ["results-{{ .BuildNum }}-3"] }

      # 0番目のノードで他ノードの成果物を組み合わせる
      - deploy:
          name: Merging Results
          command: |
            ls results/
            echo
            cat results/result-*.txt

restore_cache のところがどうしても冗長な書き方になりますが、これで1つのジョブの中で並列実行した結果を統合できます。

もうちょっとconfig.ymlを整理したサンプルは↓。

https://circleci.com/gh/mstssk/circleci-deploy-step-play github.com

何に使うの?

もともとは、並列テストのカバレッジデータをサクッと統合したいと思ってなんとかジョブの中でミニマムに解決できないかと考えて思いついたものです。

実際にジョブの中でデータ集めができることを確認したあたりで正気に帰りました。 本来の使い方とは違うやり方だけど試行錯誤してなんとかなったぜやったぁ状態になる瞬間ってたまにありますよね。

よっぽど1つのジョブの中で完結させたい理由がもしあったなら使えるテクニックかもしれませんが、本当に必要かどうか3回考えてから諦めましょう。 普通に別ジョブにしてpersist_to_workspaceでデータ受け渡してやりましょう。

カバレッジデータの統合をしたい場合は、各カバレッジツールがCircleCIの場合の使い方サンプルを公開してたりしますし、Coverallsは結果をよしなにマージしてくれる機能があるらしいです1