Skip to main content

5 posts tagged with "circleci"

View All Tags

· 3 min read
Shunsuke Suzuki

In this post I introduce how to split a huge .circleci/config.yml.

CircleCI doesn't support to split .circleci/config.yml, so we manage all workflows and jobs configuration into one file .circleci/config.yml. If the repository is Monorepo, the more the number of services increases, the more the size of .circleci/config.yml becomes large and it's hard to maintain .circleci/config.yml. By splitting .circleci/config.yml per service, it makes easy to maintain .circleci/config.yml and we can configure split file's CODEOWNERS.

To split .circleci/config.yml, you have to generate .circleci/config.yml by merging split files and commit both split files and .circleci/config.yml.

circleci config pack

We can merge split files with the command circleci config pack, but I introduce the other tool circleci-config-merge.

CircleCI CLI is an official tool so it's reliable, but I feel the restriction of the file name and the directory structure is a little strict. We have to manage all files on the same directory, and the file path is reflected to generated YAML content.

For the detail of circleci config pack, please see the official document.

https://circleci.com/docs/2.0/local-cli/#packing-a-config

If you can accept the restriction of circleci config pack, I recommend to use it because it is an official tool. But if it is difficult to accept the restriction, maybe circleci-config-merge would help you.

circleci-config-merge

circleci-config-merge is a CLI tool to generate .circleci/config.yml by merging split files.

The usage of circleci-config-merge is like the following.

$ circleci-config-merge merge <file1> [<file2> ...]

There is no restriction of file paths, and the format of split file is same as .circleci/config.yml.

For example, you can manage files on the same directory.

.circleci/
config.yml # generated
src/
service1.yml # split config per service
service2.yml
...

Or you can also manage files on each service directory.

service1/
circleci/
workflow.yml # you can split file freely
jobs.yml
...
service2/
circleci/
config.yml
...
...

circleci-config-merge merges the list of workflow jobs.

For example,

workflows:
build:
jobs:
- foo
workflows:
build:
jobs:
- bar

The workflow build's jobs are merged as the following.

workflows:
build:
jobs: # sort by job name for comparison
- bar
- foo

Test .circleci/config.yml in CI

If you split .circleci/config.yml, you should test in CI whether .circleci/config.yml is generated by merging split files. circleci-config-merge doesn't provide such a feature, but you can implement the test with the other tool like dyff.

I have created an example repository suzuki-shunsuke/example-circleci-config-merge. You can use this example as a reference to split .circleci/config.yml and setup CI.

Use case

Lastly, I introduce a use case of circleci-config-merge. Recently, I split a huge .circleci/config.yml which is over 6,000 lines to about 60 files. It was hard to maintain the original .circleci/config.yml, but by splitting it became easy to maintain .circleci/config.yml. If you are suffer from a huge .circleci/config.yml, let's split it!

Conclusion

In this post I introduced how to split a huge .circleci/config.yml. We can generate .circleci/config.yml by merging split files with circleci-config-merge. Please see the example suzuki-shunsuke/example-circleci-config-merge as a reference to split .circleci/config.yml and setup CI.

· 2 min read
Shunsuke Suzuki

小ネタです。

dd-time を使って CircleCI の run を使ったコマンドの実行時間をどう計測したらいいのかちょっと考えました。

以前、コマンドの実行時間を DataDog に送るツール dd-time を作りました。

これは基本的に以下のように引数として -- 以降に実行するコマンドを指定します。

$ dd-time -m dd_time.execution_time -t command:docker-build -- docker build .

実行するスクリプトを標準入力で渡したい場合はこうします。

$ curl https://example.com/install.sh | dd-time -m dd_time.execution_time -- sh

もちろんシェルスクリプトである必要はなくて例えば Python だったらこうなります。

$ curl https://example.com/setup.py | dd-time -m dd_time.execution_time -- python

CircleCI の run では shell オプションで shell を指定できます。

https://circleci.com/docs/2.0/configuration-reference/#run

なので command 全体の時間を計測したい場合は、 shell を次のようにします。

- run:
name: test dd-time
shell: /usr/local/bin/dd-time -m dd_time.test -- sh -eo pipefail
command: |
echo start
sleep 5
echo end

こうすると shell 以外を弄ることなく実行時間を計測して DataDog に送ることが出来ます。

この shell のカスタマイズは dd-time に限らず使えるかも知れないですね。

  • ログをどっかに送ったりとか
  • コマンドが失敗したらエラーを握りつぶしつつどっかに通知したりとか
  • etc

以上、小ネタでした。

· 6 min read
Shunsuke Suzuki

CircleCI の組み込みの command checkout の注意点について書きます。

なお、ここに書かれている内容は 2020/04/24 時点のものであり、予告なしに checkout の挙動が変わる可能性があります。

また、今回は話を簡略化するため、 checkout 実行時点で .git がない(つまりキャッシュしていない)ものとします。

最初に結論

先に結論を書くと

  • CircleCI ではローカルのデフォルトブランチを参照しないほうが良い($CIRCLE_BRANCH がデフォルトブランチである場合は除く)
    • 履歴が origin と異なり、 $CIRCLE_BRANCH と同様になっているため
  • 代わりに origin のデフォルトブランチを参照したほうが良い
  • git branch -f <デフォルトブランチ> origin/<デフォルトブランチ> を実行してデフォルトブランチの履歴を修正するのもあり

checkout がなにをやっているか

checkout でなにをやっているかは実際に使ってみて CircleCI の job の詳細画面(?) から確認できます。

サンプル: https://app.circleci.com/pipelines/github/suzuki-shunsuke/test-circleci/73/workflows/5611059c-d6b1-4a34-91b5-45d6f149d408/jobs/96

ここでは checkout の全てについては触れません。一部抜粋します。

elif [ -n "$CIRCLE_BRANCH" ]
then
git reset --hard "$CIRCLE_SHA1"
git checkout -q -B "$CIRCLE_BRANCH"
fi

git reset --hard などをしています。

なぜこんなことをしているのか?

なんでこんなことをしているのでしょうか?単に git checkout $CIRCLE_BRANCH とかじゃだめなんでしょうか?

本当のところは CircleCI の中の人に聞かないとわかりませんが、自分なりに考えてみました。

昔実行した job を rerun することを思い浮かべてみましょう。 rerun するタイミングではすでに remote branch が削除されているかもしれません。そうなれば単に git checkout では失敗します。 また、branch があったとしても $CIRCLE_SHA1 がその branch の HEAD とも限りません。 force push で履歴が変更され、 $CIRCLE_SHA1 が CIRCLE_BRANCH の履歴上にないかもしれません。

そういったケースを考慮し、このような実装になっているのだと思います。

注意点

しかし、 git reset --hard していることにより、一つ注意が必要です。 git reset --hard は branch を $CIRCLE_BRANCH に切り替える前に実行しているため、 実行しているブランチ(基本的にデフォルトブランチ)の履歴が変更されています。

ローカルで再現してみる

CircleCI の rerun with SSH でも確認できますが、 ローカルの適当なリポジトリで同様のコマンドを叩いてみることで簡単に再現できます。

$ mkdir sample
$ cd sample
$ git init
$ git commit --allow-empty -m "master first commit"
$ export CIRCLE_BRANCH=feature/hello
$ git checkout -b $CIRCLE_BRANCH
$ git commit --allow-empty -m "$CIRCLE_BRANCH first commit"
$ git log
$ git checkout master # clone 直後の状態
$ export CIRCLE_SHA1=$(git rev-parse $CIRCLE_BRANCH)

# ここから CircleCI 同様のコマンドを叩いてみる
$ git reset --hard "$CIRCLE_SHA1"
$ git checkout -q -B "$CIRCLE_BRANCH"

ここで git log してみると master branch の履歴が $CIRCLE_BRANCH と同様になっているのが分かると思います。

何が困るのか

デフォルトブランチ を参照しなければ特に困ることはないでしょう。 一方でデフォルトブランチとの差分を検知して変更があったものだけビルドするとかそういうことをやっている場合には注意が必要です。

$ git diff --name-only master $CIRCLE_BRANCH

こうすると差分がないことになってしまいます。

回避方法

master の代わりに origin/master を参照すれば良いでしょう。

$ git diff --name-only origin/master $CIRCLE_BRANCH

ただ、うっかりローカルのデフォルトブランチを参照してしまうことは十分考えられる上に別にエラーは起こらないので間違いに気づきにくいです。

そこで checkout 直後にローカルのデフォルトブランチの履歴を origin と強制的に同じにしてしまうというテクニック(?)が考えられます。

DEFAULT_BRANCH=master
if [ "${CIRCLE_BRANCH:-}" != "$DEFAULT_BRANCH" ]; then
git branch -f "$DEFAULT_BRANCH" "origin/$DEFAULT_BRANCH"
fi

こうすれば間違えてローカルのデフォルトブランチを参照してしまっても安心です。 とはいえ、コードを部分的に移植したりする際にも危険(間違いに気づきにくい)なので、 origin を参照することを推奨します。

· 2 min read
Shunsuke Suzuki

travis ci と circle ci の無償SaaS 版を比較しています。

OSS の CI では travis ci がよく使われる印象がありますが、 場合によっては circle CI に移行するとCIの時間が大幅に短くなったりして良いと思います。 ただし、複数バージョンで並列にテストしたい場合、circle ci の無償planだと並列に実行できないため、 travis でやったほうが速いかもしれません。

Circle CI の良いところ

  • 好きな Docker Image が使える
  • ローカルでテストが出来る
  • Pending 時間が travis ci に比べて短い気がする(主観)
  • private repository の CI も出来る

好きな Docker Image が使えるのが大きいですね。 予め CI に必要なツールをインストールした Image を用意しておくことで大幅に高速化出来ますし、 ツールがインストールできなかったりバージョンが変わってしまったりするトラブルも避けられます。 同じImageを使ってローカルでテストできるのでローカルでの検証もしやすいです。

自分の場合 Golang のツールの CI用に Docker Image を用意しています。

https://hub.docker.com/r/suzukishunsuke/go-ci/

· 3 min read
Shunsuke Suzuki

drone は同じ pipeline の step 間で同じ workspace を docker の volume としてマウントすることで workspace を共有します。

http://docs.drone.io/workspace/

circle ci はデフォルトで job 間で workspace を共有しません。 persist_to_workspace を指定することで共有する事ができます。

https://circleci.com/docs/2.0/workflows/#using-workspaces-to-share-data-among-jobs

circle ci の場合は volume を共有するのではなく、指定したディレクトリを archive し、次の job で展開することでファイルを共有するようです。

この違いには一長一短があります。

circle ci の場合は archive, unarchive する分、volume 共有に比べて時間がかかります。

そのため、下手に job を分けるより一つの job で処理したほうが処理時間が短くなる場合がありますが、 build や test といった処理は出来れば別の job として実行したいでしょうし、それでは workflow が使えません。

ただし、共有するパスは自由に選べるので必要最小限に抑えることで時間を短縮できます。

また、circle ci の場合は archive するパス及び展開先のパスを自由に選べるので自由度が高いです。 drone の場合、 workspace 以外のファイルを共有できません。

また、drone の場合 volume を共有するので同じ pipeline の step は同じノードで実行されるという制約がありますが、 circle ci の場合、別のノードでの実行が可能です。 drone の group を使って並列に実行する場合、複数のノードに分散できませんが、 circle ci の場合分散できるのでよりスケールしやすいと言えるでしょう。

結局どっちのほうがいいのか

一長一短があると言ったとおり、一概にどっちが良いとは言えませんが、個人的には drone のやり方のほうが直感的だし、 何より速いので好きです。同じ pipeline の処理を複数のノードに分散させたいことって個人的にはあまりありません。