継続的インテグレーション・継続的デリバリー – 「若手エンジニアのためのDevOps入門」(4)

こんにちは、山本和道です。
本記事は連載「若手エンジニアのためのDevOps入門」の第4回です。

第1回 インフラエンジニアにとってのDevOps
第2回 Webアプリでの開発環境構築
第3回 バージョン管理システム
第4回 継続的インテグレーション/デリバリー
第5回 DevOpsのための道具箱: APIを使いこなす
第6回 リリース/構成管理: 概要

第3回はバージョン管理システムとバージョン管理システムを利用した開発フローについて扱いました。
第4回では継続的インテグレーション・継続的デリバリーについて扱います。

継続的インテグレーションとは

継続的インテグレーションとは、コードの変更を(中央の)リポジトリに頻繁にマージし、かつ「定期的・自動的」に「ビルド・テスト」を行うという手法です。リポジトリに頻繁にマージすることで複数人での作業の衝突や競合を早期に発見し、自動化しておくことでリリースまでの時間を短縮できるといった効果があります。CI(continuous integration)と略して呼ばれることも多いです。

対象はアプリケーションだけでなく、Infrastructure as Codeといった手法(以降の連載で扱う予定)でインフラの構成をコード化している場合などでは、リポジトリから構成情報を取得してマシンイメージをビルドし、インフラを構築するといったところまで対象とすることもあります。

以下では継続的インテグレーションの具体的な実践の前に、どのような問題の解決に有効なのかについて見ていきます。

複数人で開発する際に発生しうる作業の衝突・競合

開発においては作業を複数人で並行して行うことも多いでしょう。複数人で開発を行う場合、メンバー間で作業が重複したり、お互いの作業が競合してしまいビルド不能となったり、不正な処理となってしまうといったことがあります。

以下に、具体的にどのような問題が起こりえるのか例示します。

まず、以下のように同一箇所への修正が重複してしまう場合があります。

// Aさんが以下のようにソース修正(example.go)
func example1(msg string) {
    fmt.Printf("Message from A: %s", msg)
}

-----------------------------------------

// Bさんが同じ箇所を以下のようにソース修正
func example1(msg string) {
    fmt.Printf("Message from B: %s", msg)
}

このケースについては前回扱ったバージョン管理システム(git)を用いることで競合を検出できます。gitでは競合を検出すると競合を解決するまではマージできなくなっています。

次に、以下のような場合はどうでしょうか?

以下のような関数があったとします。

// 共通利用される関数
func example2(n int) {
    // do something
}

Aさんがこの関数に引数を追加しました。

// Aさんが共通利用される関数に引数追加
func example2(n int , i int) {
    // do something
}

同時に作業していたBさんはAさんの作業内容を知らず、以下のように共通関数を利用するようなコードを書きました。

// Bさんが共通利用される関数の呼び出しを実装(Aさんが変更を行っていると知らずに)
func main() {
    example2(10)
}

この場合、バージョン管理システム上はAさんの変更もBさんの変更も問題なくマージできてしまいます。
しかしビルドしようとすると以下のようなエラーとなるでしょう。

$ go build
./example2.go:4:10: not enough arguments in call to example2
   have (number)
   want (int, int)

このように、Aさん、Bさん共に自マシンではビルドやテストが通っても、それぞれの変更をマージすると不正となってしまうケースというのが存在しうるのです。

また、この例はビルドエラーとなるものでしたが、以下のようにビルドエラーとはならないものの意図した挙動にならないケースというのも存在します。

例えば以下のような関数があったとします。

// 引数として受け取った数値を2倍にして返す
func example3(n int) int {
    return n * 2
}

Aさんがこの関数の処理を変更しました。

// Aさんが、受け取った数値を3倍にして返すように関数の処理を変更
func example3(n int) int {
    return n * 3
}

同時に作業していたBさんはAさんの作業内容を知らず、以下のように共通関数を利用するようなコードを書きました。
Bさんは"example3"を呼び出して数値が2倍されると思ってますが、Aさんの変更をマージすると3倍にされてしまい意図と異なる動作となってしまいます。

// Bさんが関数の呼び出しを実装(Aさんが変更を行っていると知らず、数値は2倍にして返されると意図している)
func main() {
    example3(10)
}

これらの問題はバージョン管理システムだけでは防げません。これが開発期間中に起こったものであればすぐに気づけると思いますが、例えばリリースの直前の作業だった場合、リポジトリからチェックアウトしたソースをビルドしようとして初めて気づくことになったり、最悪の場合はリリース作業中の動作確認をすり抜けてリリース後に問題発覚してしまうといったことが発生する可能性もあります。

そこで継続的インテグレーションの出番となります。

継続的インテグレーションで行うこと

前述の通り、継続的インテグレーションとは、コードの変更を(中央の)リポジトリに頻繁にマージし、かつ「定期的・自動的」に「ビルド・テスト」を行うという手法です。

リポジトリの最新のソースコードを「定期的・自動的」にビルドする仕組みとしておくことで、ビルドエラーとなってもすぐに気づけます。加えて、ビルドと共にテストも実行しておくことで意図通りの挙動となっているか確認できるはずです。

ビルド・テストは日次などタイミングを決めて定期的に実行する方法や、GitHubなどと連携してプルリクエスト作成時やマージのタイミングで行う方法、両方を組み合わせる方法もあります。

開発者が作成・実行するテストについて

継続的インテグレーションを行う際、ビルドと共にテストを実行するためには、テストを手動ではなく自動で行えるようにしておかないと運用上厳しいと思います。当記事ではテストの記載方法について詳しくは扱いませんが、採用しているプログラミング言語・フレームワークなどでテストを記述するための仕組みが用意されていたり、UIのテストなども(ある程度)自動化できる仕組みもありますので、テストコードを記述しておき自動でテストできるようにしておきましょう。

継続的インテグレーションを実現するためのツール

継続的インテグレーションを行うには、ビルド・テストを実行するための環境や定期的な実行のためのスケジューラーなどが必要です。加えてGitHubなどと連携するには連携用のプログラム・ツールも必要となります。他にもビルド・テストの失敗時の通知や実行状況のレポートなどがあると運用上楽だと思います。

自前でサーバを建ててビルド・スクリプトを実行する仕組みを構築しても良いですが、継続的インテグレーションを支援してくれるツールを利用することで楽に導入することが可能です。

ここではよく使われる継続的インテグレーションのためのツールを2つご紹介します。

Jenkins(ジェンキンス)

Jenkinsはオープンソースで開発されているJava製の継続的インテグレーションツールです。サーバーにインストールして利用する形態のツールで、ブラウザから利用できるGUI(ダッシュボード)が提供されており、各種設定や操作はGUIで行えるのが特徴です。(Jenkins2からはJenkinsfileを利用してコードを用いて各種タスク・ジョブを定義することも可能になっています)

Gitをはじめとする数多くのバージョン管理システムに対応しており、様々な環境に対応させることも可能です。

また、プラグインという仕組みを利用することで拡張機能を利用することも可能です。導入事例や書籍なども充実しており広く利用されているCIツールとなっています。

TravisCI(トラヴィスシーアイ)

TravisCIはGitHubと連携して動作する継続的インテグレーションツールです。SaaS形態で提供されており、オープンソースのプロジェクトであれば無償で利用可能です。

ビルド・テストを実行する環境がサービスとして提供されているため、最低限の設定のみで導入が行えるようになっています。

GitHubとの連携

JenkinsもTravisCIもGitHubと連携できるようになっています。GitHubと連携することで、プルリクエスト作成のタイミングでビルド・テストを実行し、結果がNGであればマージできないといった仕組みも構築可能です。(以下はGitHubでのプルリクエスト作成時にTravisCIとAppVeyorでCIを行なっている様子です)

継続的デリバリー

継続的デリバリーとは継続的インテグレーションを拡張し、ビルド・テストが通ったらステージング環境や本番環境への反映(の準備)まで行う手法です。ビルド・テスト後に確認用のステージング環境を構築しデプロイする、といった作業を自動化します。

最近ではクラウドに代表される、使いたい時に使いたいだけ(インフラなどの)リソースを使うということも可能になっていますので、ステージング環境を動作確認の都度構築するといったことも低コストで行うことができます。

この環境構築作業についても手作業で行うと大変ですので様々なツールを用いてある程度自動化するのがオススメです。環境構築の自動化については次回扱います。

まとめ

今回は継続的インテグレーションが解決する問題や継続的インテグレーションのためのツールの紹介、継続的デリバリーについて扱いました。次回は継続的デリバリーを行う上で押さえておきたい、環境構築や構成管理の自動化について扱う予定です。

以上です。