CircleCI
CircleCI2.0

公式サンプル集でCircleCI 2.0を試してみた&所感

テスト用GitHubレポジトリを作成

CircleCI 2.0の公式ドキュメントからもリンクされている

CircleCI Demos: Workflows: https://github.com/CircleCI-Public/circleci-demo-workflows

をforkしてCircleCI 2.0の機能を体験してみる。

今回のfork先は以下
https://github.com/mumoshu/circleci-demo-workflows

git clone git@github.com:mumoshu/circleci-demo-workflows.git

cd circleci-demo-workflows

masterブランチはREADMEしか置いてなくて、他のブランチにそれぞれ異なるテーマのサンプルが入っている模様。

ワークフローの仕組みをざっくりおさらい

2.0 Project Tutorial - CircleCI

  • Gitレポジトリ内の.circleci/config.ymlにワークフローの定義を書く
  • ワークフロー=Jobの集合

以下、その前提で読み進めます。

「Blogワークフロー」サンプルをビルドしてみる

git push origin remotes/origin/blog-workflows-1:master -f

image.png

rake_testタスクを覗いてみる。まず、rake db:create db:schema:loadを実行しているが、この対象のPostgresqlはどうやって起動するんだろう?と疑問に思った。

image.png

答えは、.circleci/config.ymlの以下の部分

  rake_test:
    docker:
      - image: circleci/ruby:2.4-node
      - image: circleci/postgres:9.4.12-alpine
    steps:
      - checkout
      - restore_cache:
          keys:
            - v1-bundle-{{ checksum "Gemfile.lock" }}
            - v1-bundle-
      - run: bundle --path vendor/bundle
      - run: bundle exec rake db:create db:schema:load
      - run:
          name: Run tests
          command: bundle exec rake

依存先サービスはどうやって定義する・どう実装されている?

CircleCI 2.0の公式ドキュメントを読むと、

Note that the PostgreSQL database is available at localhost.
2.0 Project Tutorial - CircleCI

  • rake_test.docker.images[]に依存先サービスのコンテナの定義(使用するコンテナイメージなど)を書く
  • PostgreSQLコンテナのPostgreSQLサーバはlocalhostで待ち受けしている

らしいので、おそらくK8SのPodのように、ビルド環境のコンテナ、依存先サービスのコンテナがnetnsを共有している?のかなと想像。詳しい方情報求む!

また、rake_test.steps[]にビルド環境のコンテナ内で実行するビルドステップ(任意のコマンド)を書く。

感想: Pipelineが第一級市民なCIとしてConcourseCIを長らく使っていましたが、その難点の

ビルドキャッシュはどうやって使う?どう実装されている?

image.png

save_cacheというステップを使うと、任意のキャッシュキーで任意のファイルパス(複数指定可)をキャッシュに保存できて、restore_cacheというステップでキャッシュキーを指定するとキャッシュを保存されたファイルパス以下に復元できる。

キャッシュはワークフロー内に閉じていて、ジョブ間で共有される。結果的に、save_cacherestore_cacheが異なるジョブにあってもOK。

実際、blog-workflowサンプルだとbundle_dependenciesという最初のジョブでsave_cacheしたもの(key: v1-bundle-{{ checksum "Gemfile.lock" }}でsave_cache)

  bundle_dependencies:
    docker:
      - image: circleci/ruby:2.4-node
      - image: circleci/postgres:9.4.12-alpine
    steps:
      - checkout
      - restore_cache:
          keys:
            - v1-bundle-{{ checksum "Gemfile.lock" }}
            - v1-bundle-
      - run: bundle install --path vendor/bundle
      - save_cache:
          key: v1-bundle-{{ checksum "Gemfile.lock" }}
          paths:
            - ~/circleci-demo-workflows/vendor/bundle

を後続のrake_testジョブでrestore_cacheしている(key: v1-bundle-{{ checksum "Gemfile.lock" }}でrestore_cache)

  rake_test:
    docker:
      - image: circleci/ruby:2.4-node
      - image: circleci/postgres:9.4.12-alpine
    steps:
      - checkout
      - restore_cache:
          keys:
            - v1-bundle-{{ checksum "Gemfile.lock" }}
            - v1-bundle-

Workspace Forwardingサンプルをビルドしてみる

workspace-forwardingブランチのビルドをトリガーするために、コミットを追加してpushする。

git checkout -b workspace-forwarding remotes/origin/workspace-forwardin
emacs README.md
git add
git commit -m 'First change'
git push origin workspace-forwarding

image.png

手動でデプロイメントを実行する

例えば、テストジョブが通った後にデプロイジョブが実行されるようになっているWorkflowで、デプロイジョブの実行を人が承認するまで待機させるという機能はある。

Web UIからボタンぽちでデプロイジョブを実行する、というような機能はない。

Manually triggered deployment stages - Feature Requests - CircleCI Community Discussion

例えば、workspace-forwardingサンプルの.circleci/config.ymlでworkflow定義を以下のように変更する。

BEFORE

workflows:
  version: 2
  build-and-deploy:
    jobs:
      - bundle_dependencies
      - rake_test:
          requires:
            - bundle_dependencies
      - precompile_assets:
          requires:
            - bundle_dependencies
      - deploy:
          requires:
            - rake_test
            - precompile_assets

AFTER

workflows:
  version: 2
  build-and-deploy:
    jobs:
      - bundle_dependencies
      - rake_test:
          requires:
            - bundle_dependencies
      - precompile_assets:
          requires:
            - bundle_dependencies
      - hold:
          type: approval
          requires:
            - rake_test
            - precompile_assets
      - deploy:
          requires:
            - hold

こうすると、deployジョブは、rake_testとprecompile_assetsのビルドが成功したあとで、さらに承認があって初めて実行されるようになる。

実際にこの.circleci/config.ymlの変更をするコミットをpushしてみる。

image.png

すると、holdの要求するrake_testとprecompile_assetsまで自動的にビルドが走った状態で停止する。

Workflowsをみると、workspace-forwardingブランチのワークフローが表示されているのでそれを見てみる。

image.png

ON HOLDと表示されているワークフロー実行をクリックすると・・・

image.png

パイプライン中でholdジョブの色が変わっている。そこをクリックするとダイアログがでる。

image.png

Approveをクリックすると、Workflowが再開する。

image.png

その他

Slack通知

Project > Chat Notificationsの設定でSlackのWebhook URLを設定すればOK

image.png

Test Hookをすると、以下のようにcircleci botがテストメッセージを送る

image.png

2.0 WorkflowsはデフォルトではFork PRsのビルドをしない

Travis等だとFork先からFork元にPRを投げた時にビルドを走らせてくれますが、2.0 Workflowsはデフォルトではそれをしません。

同レポジトリ内のブランチからPRを投げた場合

CircleCIによるビルドが動いて、結果がPRのステータスに反映されます。

image.png

CircleCI側ではBuilds > 以下に該当ブランチのビルド結果ページができています。

image.png

別レポジトリからPRを投げた場合

CircleCIによるビルドは動かず、PRのステータスにも登場しません。

image.png

Forked PRもビルドしたい場合

ドキュメントには未対応と書いてあった気がするのですが、Advanced Settings > Build forked pull requestsにチェックを入れるとできました。

image.png

この状態からPRを投げると、以下のようにCircleCIのビルドがトリガーされます。

image.png

Forked PRの注意点(?)

Forked PR元で.circleci/config.ymlを変更すると、変更後のワークフローとジョブがPR先のCircleCIプロジェクトで走ってしまいます。

以下はmumoshuがPR先、mumoshubotがPR元で、mumoshubot側のブランチで`.circleci/config.yamlにfooというjobしかないような状態に書き換えたPRを投げたときの様子です。

CircleCIが、mumoshubotが仕込んだfooというジョブをmumoshuプロジェクトで実行してしまっています。

image.png

これでクレデンシャルなどを抜けたりするとまずいので、もしそうならパブリックなCircleCIプロジェクトではForked PRのビルドは有効にしないほうがいいんでしょうか・・。

APIで任意のジョブを実行する

まずUser > User SErttings > Peronal API Tokensで適当なAPIトークンを作成する。

image.png

Running Jobs With the API - CircleCIを参考に以下のようなコマンドでジョブを実行する。

curl -u ${CIRCLE_API_TOKEN}: \
     -d build_parameters[CIRCLE_JOB]=deploy_production \
     https://circleci.com/api/v1.1/project/<vcs-type>/<org>/<repo>/tree/master
  • <vcs-type>はGitHubを使っている場合github
  • <org>はGitHubユーザまたはOrganizationアカウント名
  • <repo>はレポジトリ名
  • build_parameters[CIRCLE_JOB]=の右辺、上記の例でいうとdeploy_productionの部分が、.circleci/config.ymlのjobs直下に書いたjob名

例えば、blog-workflows-1サンプルのrake_testジョブを実行する場合は、以下のようなコマンドになる。

curl -u ${CIRCLE_API_TOKEN}: \
     -d build_parameters[CIRCLE_JOB]=rake_test \
     https://circleci.com/api/v1.1/project/github/mumoshu/circleci-demo-workflows/tree/master

実行すると、以下のようにWorkflowとは独立して、該当jobだけが実行される。

image.png

条件付きで後続ジョブを実行する

ワークフロー中などでdeployがビルドされるとして、deployのビルド後に特定の条件に合致したときだけ後続のdeploy_dockerをビルドする例。

前述のAPIを使って任意のジョブを実行する方法を応用して、同じプロジェクトのワークフロー内の他のジョブをAPIを使って呼ぶという荒技(?)。

- deploy:
          name: conditionally run a deploy job
          command: |
            # replace this with your build/deploy check (i.e. current branch is "release")
            if [[ true ]]; then
              curl --user ${CIRCLE_API_TOKEN}: \
                --data build_parameters[CIRCLE_JOB]=deploy_docker \
                --data revision=$CIRCLE_SHA1 \
                https://circleci.com/api/v1.1/project/github/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/tree/$CIRCLE_BRANCH
            fi

  deploy_docker:
    docker:
      - image: ruby:2.4.0
    working_directory: /
    steps:
      - ...

参考: Conditionally Running Jobs With the API

手動で特定のジョブを実行する

できないみたい?