GitLab CIの基本チュートリアル(翻訳)
この記事は2016年6月29日に公開された、GitLab CI: Run jobs sequentially, in parallel or build a custom pipelineを著者のIvan Nemytchenkoさんの許可を得て、日本語に翻訳したものです。
Thank you Ivan Nemytchenko to permit to translate your article.
君が継続的インテグレーションについてどんなものか、なぜ必要かを知らないと仮定しよう。そうでなきゃ、忘れてくれ。何にせよ、最初から始めていこう。
君は全部で2つのテキストファイルでコードが構成されている、プロジェクトに取り組んでいると想像してほしい。その上、このプロジェクトはとても重要で、2つのファイルを結合すると"Hello world."というフレーズになるんだ。
こういうフレーズがないと、開発チーム全体が今月の給料をもらえない。そう、冗談じゃないんだ!
一番責任のある開発者が、コードを顧客へ送るために、毎回小さなスクリプトを書いていた。コードはとても洗練されている。
cat file1.txt file2.txt | grep -q "Hello world"
(訳注:catは指定されたファイルを結合するLinux/Unixコマンドです。grepは特定の文字列を検索するコマンドで、見つかると0(正常終了)、見つからない場合は1(異常終了)を返します。)
問題は10人の開発者がチームにいることで、君も知っている通り、人的要素は重大事を起こす。
一週間前、新人がこのスクリプトを実行し忘れて、3つのクライアンのビルドが壊れてしまった。それで君はこの問題を一度で解決して、二度と起きないようにすると決心した。幸運なことに、コードは既にGitLab上に乗っかっていて、GitLabには組み込みCIシステムがあることを覚えていた。その上に、あるカンファレンスで人々はテスト実行にCIを使っていると耳にしていた…
最初のテストをCIで実行する
二分かけてドキュメントを探し、読み終えたら、どうやら.gitlab-ci.ymlと言う名前のファイルに、2行のコードを書くだけで済むらしいとわかった。
test: script: cat file1.txt file2.txt | grep -q 'Hello world'
コミットしたら…やった! ビルドが成功した。

2つ目のテキストファイルの"world"を"Africa"へ変更して、何が起きるか調べてみよう。

期待通り、ビルドは失敗した!
よし、これで自動テストを体得した! GitLab CIは新しいコードをリポジトリへpushするたびに毎回、テストを実行してくれるだろう。
ビルド結果をダウンロード可能にする
次に要求されている仕事は、お客さんたちにコードを送る前にパッケージすることだ。
必要なのは、CIに別のジョブを定義することだけ。"package"と名付けるとしよう。
test: script: cat file1.txt file2.txt | grep -q 'Hello world' package: script: cat file1.txt file2.txt | gzip > package.gz
これでタブが2つになる。

ところが、この新しいファイルは、ダウンロード可能なビルトの生成物(artifact)であると指定し忘れた。artifactsセクションを付け加えて、修正しよう。
test:
script: cat file1.txt file2.txt | grep -q 'Hello world'
package:
script: cat file1.txt file2.txt | gzip > packaged.gz
artifacts:
paths:
- packaged.gz
見てみよう…、出来てるね。

完璧だ! けど、直さなきゃならない問題もある。ジョブが並列に実行されている。テストが失敗した場合は、アプリケーションのパッケージは欲しくないよね。
ジョブを順番に実行する
テストが成功した時だけ、packageジョブを実行したいよね。stagesを指定して、実行順を定義しよう。
stages:
- test
- package
test:
stage: test
script: cat file1.txt file2.txt | grep -q 'Hello world'
package:
stage: package
script: cat file1.txt file2.txt | gzip > packaged.gz
artifacts:
paths:
- packaged.gz
これでよし!
あと、いい忘れたけれど、(今回の例ではファイル結合で表現している)コンパイルは時間がかかるから、2回実行したくない。そのためにステップを分割しよう。
stages:
- compile
- test
- package
compile:
stage: compile
script: cat file1.txt file2.txt > compiled.txt
artifacts:
paths:
- compiled.txt
test:
stage: test
script: cat compiled.txt | grep -q 'Hello world'
package:
stage: package
script: cat compiled.txt | gzip > packaged.gz
artifacts:
paths:
- packaged.gz
生成物を見てみよう。

うーん。"compile"ファイルはダウンロードできる必要ないな。expire_inを"20 minutes"に指定して、一時的な生成物にしよう。
compile:
stage: compile
script: cat file1.txt file2.txt > compiled.txt
artifacts:
paths:
- compiled.txt
expire_in: 20 minutes
これで設定が、とても印象深いものになったね。
- アプリケーションをコンパイル、テスト、パッケージする3ステージを順番に実行する。
- コンパイルしたアプリを次のステージに渡しているので、コンパイルを2回実行する必要がない。(これにより、実行時間が早くなる)
- 将来使用するために、アプリのパッケージしたバージョンを生成物として保存した。
どのDockerイメージを使用するか学ぶ
ここまでは順調。けど、ビルドがまだまだ遅いようだ。ログを見てみよう。

待った。なんだこれ?Ruby 2.1??
Rubyなんて全然必要ないよな? ああ、GitLab.comがビルドを実行するために使っているDockerイメージは、デフォルトで、ruby:2.1イメージを使っているんだ。確かに、このイメージは必要としない多くのパッケージを含んでいる。一分ほどグーグルすると、alpineという、ほとんど空っぽのLinuxイメージが見つかった。
OK、このイメージを使用することを明確に指定するために、.gitlab-ci.ymlへimage:alpineを追加しよう。さあ、ご覧あれ。約三分節約できた。

何だか、いろいろたくさんのパブリックイメージがあるようだ。だから、自分たちの向きのテクノロジースタックとして、一つを選ぶだけでいいんだ。ダウンロード時間を短くするために、余計なソフトウェアを含んでいないイメージを指定するのは、納得できるでしょう。
複雑なシナリオを扱う
順調、順調。けど、あるクライアントがパッケージを.gzでなくて、.isoイメージでほしいと言い出したと想像してみよう。CIは幅広く役立ってくれる用になっているので、一つジョブを追加するだけだ。mkisofsコマンドを使えば、ISOイメージは作成できる。すると設定は、次のようになる。
image: alpine
stages:
- compile
- test
- package
# ... "compile"と"test"ジョブは、説明を短くするために飛ばすよ
pack-gz:
stage: package
script: cat compiled.txt | gzip > packaged.gz
artifacts:
paths:
- packaged.gz
pack-iso:
stage: package
script:
- mkisofs -o ./packaged.iso ./compiled.txt
artifacts:
paths:
- packaged.iso
ジョブ名はステージと同じでなくて良い点に注目。実際、同じでなくちゃならなきゃ、同じステージのジョブは並列に実行できなくなるからね。故に、ジョブとステージが同じ名前だったとしたら、たまたま一致したんだと思ってほしい。
でも、ビルドは失敗するよ。

問題はmkisofsはalpineイメージに含まれていないということなので、最初にインストールしておく必要があるんだ。
足りないソフトウェア/パッケージの取り扱い
Alpine LinuxのWebサイトによると、mkisofsはxorrisoとcdrkitパッケージの一部のようだ。以下がパッケージをインストールするために実行する必要のある、魔法のコマンドだ。
echo "ipv6" >> /etc/modules # ネットワークを有効にする apk update # パッケージリストを更新 apk add xorriso # パッケージをインストール
GitLab CIにとって、これらは他のコマンドと変わりない。scriptセクションに指定する必要のあるコマンドは、以下のようになる。
script: - echo "ipv6" >> /etc/modules - apk update - apk add xorriso - mkisofs -o ./packaged.iso ./compiled.txt
しかしながら、意味合い的に正しくするには、before_scriptでインストールパッケージに関連連するコマンドとして記述しよう。もし、before_scriptを設定のトップレベルで使うと、すべてのジョブの前にコマンドが実行されることに注意しよう。今回の場合、ある特定のジョブの前に実行したいわけだ。
最終バージョンの.gitlab-ci.ymlは次のようになる。
image: alpine
stages:
- compile
- test
- package
compile:
stage: compile
script: cat file1.txt file2.txt > compiled.txt
artifacts:
paths:
- compiled.txt
expire_in: 20 minutes
test:
stage: test
script: cat compiled.txt | grep -q 'Hello world'
pack-gz:
stage: package
script: cat compiled.txt | gzip > packaged.gz
artifacts:
paths:
- packaged.gz
pack-iso:
stage: package
before_script:
- echo "ipv6" >> /etc/modules
- apk update
- apk add xorriso
script:
- mkisofs -o ./packaged.iso ./compiled.txt
artifacts:
paths:
- packaged.iso
おー、何だかパイプラインでも作ったみたいだね!3つの連続するステージがあり、しかしpackageステージではpack-gzとpack-isoジョブが並列に実行される。

まとめ
まだまだあるんだけど、今はここまでで止めておきましょう。この短いストーリーを気に入ってくれるといいんだけど。馴染みがないテクノロジースタックに邪魔されることなく、GitLab CIのコンセプトを学んでもらえるように、すべての例は意図的に些細なものにしたんだ。学んだことをまとめておこう。
- 仕事をGitLab CIに行わせるには、
gitlab-ci.ymlの中のジョブを一つ以上定義する必要がある。 - ジョブには名前を付ける必要があり、良い名前を選ぶのは君の責任だ。
- GitLab CIのすべてのジョブは、特定のキーワードで定義する、一連のルールとインストラクションで構成される。
- ジョブは順番、もしくは並列に実行できる。もしくはカスタムパイプラインを定義することも可能。
- ジョブ間でファイルを渡すことも可能。ビルドの生成物として保存し、インターフェイスからダウンロードすることもできる。
以降の最終セクションは、より正式な言葉とキーワードで構成されているけど、GitLab CIが持つ機能のより細かい説明へリンクしてあるよ。
キーワードの説明とドキュメントへのリンク
| キーワード/用語 | 説明 |
|---|---|
| .gitlab-ci.yml | プロジェクトビルドする方法の定義がすべて含まれているファイル |
| script | 実行するシェルスクリプトを定義する |
| before_script | 全ジョブ、もしくは特定のジョブの前に実行すべきコマンドを定義するために使用する |
| image | どのDockerイメージを使用するか定義する |
| stage | パイプラインステージを定義する(デフォルトはtest) |
| artifacts | ビルドの生成物のリストを定義する |
| artifacts:expire_in | 指定時間後にアップロード可能な生成物を削除する |
| pipline | ステージ間で実行したビルドのグループ |
同様に、GitLab CIのストーリーも見逃すな: