GitLab CIの基本チュートリアル(翻訳)

タグ: 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."というフレーズになるんだ。

こういうフレーズがないと、開発チーム全体が今月の給料をもらえない。そう、冗談じゃないんだ!

一番責任のある開発者が、コードを顧客へ送るために、毎回小さなスクリプトを書いていた。コードはとても洗練されている。

  1. 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行のコードを書くだけで済むらしいとわかった。

  1. test:
  2. script: cat file1.txt file2.txt | grep -q 'Hello world'

コミットしたら…やった! ビルドが成功した。

ビルド成功

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

ビルド失敗

期待通り、ビルドは失敗した!

よし、これで自動テストを体得した! GitLab CIは新しいコードをリポジトリへpushするたびに毎回、テストを実行してくれるだろう。

ビルド結果をダウンロード可能にする

次に要求されている仕事は、お客さんたちにコードを送る前にパッケージすることだ。

必要なのは、CIに別のジョブを定義することだけ。"package"と名付けるとしよう。

  1. test:
  2. script: cat file1.txt file2.txt | grep -q 'Hello world'
  3.  
  4. package:
  5. script: cat file1.txt file2.txt | gzip > package.gz

これでタブが2つになる。

2つのタブ

ところが、この新しいファイルは、ダウンロード可能なビルトの生成物(artifact)であると指定し忘れた。artifactsセクションを付け加えて、修正しよう。

  1. test:
  2. script: cat file1.txt file2.txt | grep -q 'Hello world'
  3.  
  4. package:
  5. script: cat file1.txt file2.txt | gzip > packaged.gz
  6. artifacts:
  7. paths:
  8. - packaged.gz

見てみよう…、出来てるね。

生成物

完璧だ! けど、直さなきゃならない問題もある。ジョブが並列に実行されている。テストが失敗した場合は、アプリケーションのパッケージは欲しくないよね。

ジョブを順番に実行する

テストが成功した時だけ、packageジョブを実行したいよね。stagesを指定して、実行順を定義しよう。

  1. stages:
  2. - test
  3. - package
  4.  
  5. test:
  6. stage: test
  7. script: cat file1.txt file2.txt | grep -q 'Hello world'
  8.  
  9. package:
  10. stage: package
  11. script: cat file1.txt file2.txt | gzip > packaged.gz
  12. artifacts:
  13. paths:
  14. - packaged.gz

これでよし!

あと、いい忘れたけれど、(今回の例ではファイル結合で表現している)コンパイルは時間がかかるから、2回実行したくない。そのためにステップを分割しよう。

  1. stages:
  2. - compile
  3. - test
  4. - package
  5.  
  6. compile:
  7. stage: compile
  8. script: cat file1.txt file2.txt > compiled.txt
  9. artifacts:
  10. paths:
  11. - compiled.txt
  12.  
  13. test:
  14. stage: test
  15. script: cat compiled.txt | grep -q 'Hello world'
  16.  
  17. package:
  18. stage: package
  19. script: cat compiled.txt | gzip > packaged.gz
  20. artifacts:
  21. paths:
  22. - packaged.gz

生成物を見てみよう。

2つの生成物

うーん。"compile"ファイルはダウンロードできる必要ないな。expire_inを"20 minutes"に指定して、一時的な生成物にしよう。

  1. compile:
  2. stage: compile
  3. script: cat file1.txt file2.txt > compiled.txt
  4. artifacts:
  5. paths:
  6. - compiled.txt
  7. expire_in: 20 minutes

これで設定が、とても印象深いものになったね。

  • アプリケーションをコンパイル、テスト、パッケージする3ステージを順番に実行する。
  • コンパイルしたアプリを次のステージに渡しているので、コンパイルを2回実行する必要がない。(これにより、実行時間が早くなる)
  • 将来使用するために、アプリのパッケージしたバージョンを生成物として保存した。

どのDockerイメージを使用するか学ぶ

ここまでは順調。けど、ビルドがまだまだ遅いようだ。ログを見てみよう。

実行ログ

待った。なんだこれ?Ruby 2.1??

Rubyなんて全然必要ないよな? ああ、GitLab.comがビルドを実行するために使っているDockerイメージは、デフォルトで、ruby:2.1イメージを使っているんだ。確かに、このイメージは必要としない多くのパッケージを含んでいる。一分ほどグーグルすると、alpineという、ほとんど空っぽのLinuxイメージが見つかった。

OK、このイメージを使用することを明確に指定するために、.gitlab-ci.ymlimage:alpineを追加しよう。さあ、ご覧あれ。約三分節約できた。

高速化

何だか、いろいろたくさんのパブリックイメージがあるようだ。だから、自分たちの向きのテクノロジースタックとして、一つを選ぶだけでいいんだ。ダウンロード時間を短くするために、余計なソフトウェアを含んでいないイメージを指定するのは、納得できるでしょう。

複雑なシナリオを扱う

順調、順調。けど、あるクライアントがパッケージを.gzでなくて、.isoイメージでほしいと言い出したと想像してみよう。CIは幅広く役立ってくれる用になっているので、一つジョブを追加するだけだ。mkisofsコマンドを使えば、ISOイメージは作成できる。すると設定は、次のようになる。

  1. image: alpine
  2.  
  3. stages:
  4. - compile
  5. - test
  6. - package
  7.  
  8. # ... "compile"と"test"ジョブは、説明を短くするために飛ばすよ
  9.  
  10. pack-gz:
  11. stage: package
  12. script: cat compiled.txt | gzip > packaged.gz
  13. artifacts:
  14. paths:
  15. - packaged.gz
  16.  
  17. pack-iso:
  18. stage: package
  19. script:
  20. - mkisofs -o ./packaged.iso ./compiled.txt
  21. artifacts:
  22. paths:
  23. - packaged.iso

ジョブ名はステージと同じでなくて良い点に注目。実際、同じでなくちゃならなきゃ、同じステージのジョブは並列に実行できなくなるからね。故に、ジョブとステージが同じ名前だったとしたら、たまたま一致したんだと思ってほしい。

でも、ビルドは失敗するよ。

mkisofs実行失敗

問題はmkisofsalpineイメージに含まれていないということなので、最初にインストールしておく必要があるんだ。

足りないソフトウェア/パッケージの取り扱い

Alpine LinuxのWebサイトによると、mkisofsxorrisocdrkitパッケージの一部のようだ。以下がパッケージをインストールするために実行する必要のある、魔法のコマンドだ。

  1. echo "ipv6" >> /etc/modules # ネットワークを有効にする
  2. apk update # パッケージリストを更新
  3. apk add xorriso # パッケージをインストール

GitLab CIにとって、これらは他のコマンドと変わりない。scriptセクションに指定する必要のあるコマンドは、以下のようになる。

  1. script:
  2. - echo "ipv6" >> /etc/modules
  3. - apk update
  4. - apk add xorriso
  5. - mkisofs -o ./packaged.iso ./compiled.txt

しかしながら、意味合い的に正しくするには、before_scriptでインストールパッケージに関連連するコマンドとして記述しよう。もし、before_scriptを設定のトップレベルで使うと、すべてのジョブの前にコマンドが実行されることに注意しよう。今回の場合、ある特定のジョブの前に実行したいわけだ。

最終バージョンの.gitlab-ci.ymlは次のようになる。

  1. image: alpine
  2.  
  3. stages:
  4. - compile
  5. - test
  6. - package
  7.  
  8. compile:
  9. stage: compile
  10. script: cat file1.txt file2.txt > compiled.txt
  11. artifacts:
  12. paths:
  13. - compiled.txt
  14. expire_in: 20 minutes
  15.  
  16. test:
  17. stage: test
  18. script: cat compiled.txt | grep -q 'Hello world'
  19.  
  20. pack-gz:
  21. stage: package
  22. script: cat compiled.txt | gzip > packaged.gz
  23. artifacts:
  24. paths:
  25. - packaged.gz
  26.  
  27. pack-iso:
  28. stage: package
  29. before_script:
  30. - echo "ipv6" >> /etc/modules
  31. - apk update
  32. - apk add xorriso
  33. script:
  34. - mkisofs -o ./packaged.iso ./compiled.txt
  35. artifacts:
  36. paths:
  37. - packaged.iso

おー、何だかパイプラインでも作ったみたいだね!3つの連続するステージがあり、しかしpackageステージではpack-gzpack-isoジョブが並列に実行される。

パイプライン

まとめ

まだまだあるんだけど、今はここまでで止めておきましょう。この短いストーリーを気に入ってくれるといいんだけど。馴染みがないテクノロジースタックに邪魔されることなく、GitLab CIのコンセプトを学んでもらえるように、すべての例は意図的に些細なものにしたんだ。学んだことをまとめておこう。

  1. 仕事をGitLab CIに行わせるには、gitlab-ci.ymlの中のジョブを一つ以上定義する必要がある。
  2. ジョブには名前を付ける必要があり、良い名前を選ぶのは君の責任だ。
  3. GitLab CIのすべてのジョブは、特定のキーワードで定義する、一連のルールとインストラクションで構成される。
  4. ジョブは順番、もしくは並列に実行できる。もしくはカスタムパイプラインを定義することも可能。
  5. ジョブ間でファイルを渡すことも可能。ビルドの生成物として保存し、インターフェイスからダウンロードすることもできる。

以降の最終セクションは、より正式な言葉とキーワードで構成されているけど、GitLab CIが持つ機能のより細かい説明へリンクしてあるよ。

キーワードの説明とドキュメントへのリンク

キーワード/用語 説明
.gitlab-ci.yml プロジェクトビルドする方法の定義がすべて含まれているファイル
script 実行するシェルスクリプトを定義する
before_script 全ジョブ、もしくは特定のジョブの前に実行すべきコマンドを定義するために使用する
image どのDockerイメージを使用するか定義する
stage パイプラインステージを定義する(デフォルトはtest
artifacts ビルドの生成物のリストを定義する
artifacts:expire_in 指定時間後にアップロード可能な生成物を削除する
pipline ステージ間で実行したビルドのグループ

同様に、GitLab CIのストーリーも見逃すな: