実際のアプリケーションの例
CircleCIの設定のイメージはつかんでもらえたでしょうか? Hello Worldではもの足りないので、今度はもっと実践的な例で詳しく説明していきます。
コードはhttps://github.com/kimh/codezine-circleci-demo-ruby-railsにあります。これはRuby On Railsで書かれた簡単なブログアプリです。このレポジトリを自分のGitHubアカウントにフォークして上記の手順に従いプロジェクトとして追加してください。
以下の設定はこのブログアプリをCircleCI上でビルドするための最低限の設定です。すこし項目が多いですが順番に説明していきます。
version: 2
jobs:
build:
working_directory: ~/circleci-demo-ruby-rails
docker:
- image: circleci/ruby:2.4.1-node
environment:
RAILS_ENV: test
- image: circleci/postgres:9.4.12-alpine
steps:
- checkout
- restore_cache:
keys:
- v1-rails-demo-{{ checksum "Gemfile.lock" }}
- v1-rails-demo
- run: bundle install --path vendor/bundle
- save_cache:
key: v1-rails-demo-{{ checksum "Gemfile.lock" }}
paths:
- vendor/bundle
- run:
command: |
bundle exec rake db:create
bundle exec rake db:schema:load
- run: bundle exec rspec
version: 2 jobs: build:
buildというジョブの定義しています。CircleCIではビルドの設定をジョブという単位に分けて管理します。ジョブに分けるメリットを体験するには次回解説するワークフローを理解しないといけないので、ここではbuildというジョブにビルドの設定を入れていくということだけ覚えておいてください。
working_directory: ~/circleci-demo-ruby-rails
working_directory:stepsに書かれているコマンドをどのディレクトリで実行するかを指定します。指定しない場合は~/projectが使われますが、明示的に~/<リポジトリ名>と指定するとよいでしょう。
docker:
- image: circleci/ruby:2.4.1-node
environment:
RAILS_ENV: test
- image: circleci/postgres:9.4.12-alpine
docker:このジョブがDocker Executorを使うということを定義しています。Executorとはビルドの実行環境のことです。
その他の実行環境
CircleCIではDockerの他にiOSやmacOSをビルドするために使われるmacosやVM環境でビルドするためのmachineを実行環境として指定できますが、有料プランのみの機能となるため今回は説明しません。詳しくは公式ドキュメントをご確認ください。
image:使用するDockerのイメージを指定します。ここで指定したイメージ上でコマンドが実行されます。CircleCIではDocker Hubのパブリック/プライベートだけではなく、その他のレジストリもサポートしています。例えば、DockerHubのプライベートなイメージを使う場合認証情報はauthを使って以下のように設定します。
docker:
- image: kimh/my-private-image
auth:
username: mydockerhub-user
password: $DOCKERHUB_PASSWORD
パスワードを直接書いてしまうのはまずいので、$DOCKERHUB_PASSWORDのように環境変数に設定しています。環境変数については後ほどrunステップのところで詳しく解説します。
environment: RAILS_ENV: test
このイメージに環境変数を設定しています。ここで指定した環境変数はこのイメージで実行されるコマンドのすべてで共有されます。今回の場合だとRAILS_ENV: testと指定しているのでRailsのテストを実行する際にRAILS_ENV=test <test-command>のように毎回環境変数を指定する必要がありません。
- image: circleci/postgres:9.4.12-alpine
2つ目のDockerイメージを指定しています。CircleCIではジョブで使うイメージを複数指定することができます。1つ目に指定したイメージ(ここではcircleci/ruby:2.4.1-node)はプライマリイメージと呼ばれ、この上で実際のテストやビルドのコマンドが実行されます。それ以降はサービスイメージと呼ばれ、アプリケーションが必要なデータベースなどのサービスを提供するために使われます。今回の例だと、ブログアプリを動かすためにPostgreSQLが必要なのでサービスイメージで指定しています。なお、サービスイメージは複数指定できるので、例えばビルドの中で複数のデーターベースを使ったりもできます。
steps:
steps:ビルドの中で実行される各処理をステップと呼びます。これ以降の階層にステップを定義していきます。
- checkout
checkout:ソースコードをVCS(GitHubやBitbucket)からダウンロードします。通常コードがないとビルドを始められないので、最初に書いておくとよいでしょう。
依存関係のキャッシュについて
以下の設定では依存関係をインストールしています。毎回ジョブを実行するたびに依存関係を最初からインストールするのは無駄なので、CircleCIでは依存関係のキャッシュをサポートしています。
- restore_cache:
keys:
- v1-rails-demo-{{ checksum "Gemfile.lock" }}
- v1-rails-demo
- run: bundle install --path vendor/bundle
- save_cache:
key: v1-rails-demo-{{ checksum "Gemfile.lock" }}
paths:
- vendor/bundle
save_cacheでキャッシュをアップロードしてrestore_cacheでダウンロードしてします。restore_cacheの直後にbundle installで依存関係をインストールしていることに注目してください。Ruby On Railの標準パッケージマネジャーであるBundlerはすでにダウンロードされている依存関係はスキップして、新しい依存関係だけをダウンロードしてくれます。つまり、restore_cacheでダウンロードしたキャッシュに含まれなかった依存関係だけをインストールします。
最後にsave_cacheでインストールした依存関係をアップロードします。こうすることで最新の依存関係をキャッシュできます。Bundlerに限らず最近のパッケージマネジャーであれば同じような挙動をするので、restore_cache⇒依存関係のインストールコマンド⇒save_cacheはCircleCIでもっともよく使われるパターンの一つです。
save_cacheとrestore_cacheについて詳しく見ていきましょう。どちらのステップにもv1-rails-demo-{{ checksum "Gemfile.lock" }}と書かれています。これはキャッシュする依存関係に対して一意なキーを設定しています。もう少し噛み砕いて言うと、CircleCIではキャッシュは単に依存関係をtarでまとめた1つのファイルで、restore_cacheでどのファイルをダウンロードするか指定するために、一意なキーで名前をつけています。
v1-rails-demo-は文字列リテラルです。{{ checksum "Gemfile.lock" }}はCircleCIが特別に用意してあるテンプレートで、指定したファイルのチェックサムに展開されます。具体的にはファイルのSHA256ハッシュを取って、それをBase64でエンコードした値になります。
以下は、v1-rails-demo-{{ checksum "Gemfile.lock" }}で指定したキャッシュが実際に使われているところです。bundle installでUsing...となっていることに注目してください。依存関係を新たにダウンロードせずにキャッシュからインストールしています。

もう一つ重要なポイントはCircleCIは依存関係をダウンロードする時に最もマッチするキーを優先的に使おうとします。分かりやすいように具体例で説明します。
-
Gemfile.lockのチェックサムが
abc123だったとします(実際はBase64なのでもっと長いです)。 -
CircleCIは
v1-rails-demo-abc123というキーのキャッシュがすでに保存されているかチェックします。 - もしあればその依存関係をビルドにダウンロードします。
-
なければ次のキーである
v1-rails-demoに前方マッチするキャッシュがあればダウンロードします。
ここまで理解できれば、save_cacheの動きは簡単に理解できるのではないでしょうか? keyでアップロードするキャッシュに対してキーを決めます。pathsはキャッシュする依存関係があるディレクトリを指定しています。
以上がキャッシュに関する基本的な説明です。実はCircleCIにはchecksum以外にも使えるテンプレートが用意されています。紙面の関係ですべてを紹介することはできませんが、コミットのSHAに展開される{{ .Revision }}や任意の環境変数に展開される{{ .Environment.variableName }}などもあります。詳しくは公式ドキュメントをご覧ください。
Runステップでコマンドを実行する
テストの実行などのコマンドはrunステップで実行します。
- run:
command: |
bundle exec rake db:create
bundle exec rake db:schema:load
- run: bundle exec rspec
YAMLでの改行
command:の直後の|は複数行を書く時に便利です。こうすると、改行されていても、1つの文字列として扱われます。YAMLの標準機能の一つです。
上の例ではテストデータベースをセットアップするコマンドとテストコマンドをrunで実行しています。runステップで指定したコマンドは新しいシェルを介して実行されます。使うイメージにインストールされていれば、/bin/bashが、なければ/bin/shが使われます。
ここで注意したい点は、各runステップは新しいシェル上で実行されるということです。つまり、前とその後のrunステップでは環境が引き継がれません。例えば以下のようにしてもうまく動きません。
- run: export Foo=foo - run: echo $FOO #$FOOには何も入っていない
このようなことを実現するためにCircleCIでは環境変数がサポートされています。使い方は2通りあって、.circleci/config.ymlに書く方法とプロジェクトの設定画面を使う方法があります。
.circleci/config.yml内で設定する場合はenvironmentを使います。3つの設定方法があります。
1:ジョブのトップレベルキーとして指定する
version: 2
jobs:
build:
environment:
FOO: bar
この方法だとbuildジョブの中のすべてのステップからFOOが参照できるようになります。
2:イメージの中で指定する
- image: postgres:9.4.1
environment:
POSTGRES_USER: root
ある特定のイメージだけに環境変数を指定したい場合にはimageで指定することもできます。
3:runで指定する
- run:
environment:
RAILS_ENV: "test"
command: |
bundle exec rspec # RAILS_ENV=test bundle exec rspecと同じ
runの中だけで有効な環境変数も設定できます。
環境変数の展開は行えない
CircleCIではenvironment内での変数展開はサポートされていません。
environment: PATH: $PATH:/sbin
つまり、上記のようにすると値は文字列として解釈され、結果、PATHに$PATH:/sbinという文字列がセットされてしまいます。CircleCIで最もよく陥る間違いの一つなので気をつけてください。
.circleci/config.ymlはソースコードと一緒にレポジトリで管理されるので、パスワードのような内容を隠したい環境変数は書くことができません。そのような場合はプロジェクト設定画面から追加しましょう。
右上にあるギアのアイコンをクリックするとプロジェクトの設定ページへ行くことができます。そこから、Environment Variablesをクリックすると、ここで追加した環境変数を使うことができます。

例えばパスワードで認証が必要なサイトをcurlで取得していた場合、$SITE_PASSWORDをプロジェクト設定から追加すれば以下のようにすることができます。
- run: curl -u my-user:$SITE_PASSWORD http://hoge.com
本格的にCI/CDの設定をする場合、環境変数の利用は避けれないのでぜひマスターしてください。もっと詳しくしりたい場合は公式ドキュメント読むことをおすすめします。
