CI/CDサーバを構築してみる

これまでAzureやらオンプレやらで色々なWebアプリケーションをデプロイしてきましたが、今回はその集大成と称してCI/CDサーバを構築してみようと思います。
はじめに
ソースコードをGitLabで管理し、既定のブランチにpushが来たらフックされてJenkinsに通知が入ります。Jenkins上でビルドなりテストなりServerspecなりあらかじめ定義しておいた一連の流れが動き出すというものです。またGitの変更通知とJenkinsの結果通知をSlackに連携することにします。

本来なら最終的なパイプライン処理として本番環境へのデプロイまでやるべきなのでしょうが、今のところこれといって立ち上げたいサービスもないのでとりあえず環境だけ作っておきます。実際、何らかのサービス開発のために活用するとしたら本番環境は別途クラウドに構築するのでしょうけどね。
そもそもCI/CDとは
スタートアップ系のブログ記事やIT業界のセミナーなどに参加すると最近とみに耳にすることの多くなったこの「CI/CD」ということばですが、そもそもどういう意味なのでしょうか。
CI/CDとは「Continous Integration/Contiious Delivery」の略で、日本語では継続的インティグレーション/継続的デリバリーといいます。CI/CDは1つの技術を指すものでなく、ソフトウェアの変更を常にテストして自動で本番環境にリリース可能な状態にしておく、ソフトウェア開発の手法を意味します。CI/CDを取り入れると、バグを素早く発見したり、変更を自動でリリースしたりできるようになります。
CI/CDのエキスパートが解説:CI/CDとは何か? なぜ今、必要とされるのか? (1/3):CodeZine(コードジン)
なるほど、「継続的インテグレーション/継続的デリバリー」ですか。分かったような分からないような横文字ですね。
その昔、IT企業はウォーターフォール型開発によってシステムを作り上げてきました。各フェーズにおいてレビューでの指摘事項をクリアしたら次のフェーズに進み、後ろを振り向かないのが基本的なスタンスでした。またかつては開発部門と運用・保守部門に線引きがあったりして既存システムの更改に対するスタンスに軋轢があることもしばしばでした。
そうして作られたシステムはアップグレードやリプレースにも多大のコストがかかりました。開発プロセスに人間系の作業が多すぎるあまり属人化が避けられず、たった1人エキスパートがプロジェクトを離れただけでもう手がつけられなくなる、なんてみっともない自体に容易になり得ました。
しかし時代が進むに連れてIT技術もめざましく進歩を遂げていきます。誰もがいつでも気軽にITサービスを使えるようになってから日々改善していく必要性が上がってきたのです。
旧態依然とした開発体制、開発手法ではどうしてもこのスピードについていくことができません。人間による作業が多すぎるからです。効率も悪ければミスも出ます。「人がやれば必ずミスが出る」がここ最近の基本的アイデアです。
そこで出てきた言葉がDevOpsです。DevOpsは開発チームと運用チームがお互いに協力し、そのソフトウェア/システムの価値をより高め、より確実かつ迅速にエンドユーザに届け続けるという概念です。
話が散らかってしまいましたが、今回紹介するCI/CDもDevOpsを実現するための方法論になります。DevOpsを実現するにはCI/CDだけあればいいというわけではありませんが、その第一歩には確実になるでしょう(関連書籍を読めばどこにでも書いてありますがDevOpsには元よりチームメンバーの理解が必要不可欠です)。
前提条件
冒頭お見せした通り今回はGitLabとJenkinsを物理的には一つのサーバ上にホスティングすることを想定しています。そのため以下のような条件を満たした構成であることが前提です。
- GitLabとJenkinsが互いに名前解決できること
- GitLabのOutboud requestsが許可されていること
- Jenkinsにプロジェクトが存在していること
- JenkinsにSlackの拡張機能がインストールされていること
- JenkinsにGitLabの認証情報が登録されていること
GitLab→Slack
まずソースコードへの変更通知をSlackで受け取れるようにします。
Slackの設定
Slack API から「Incoming webhooks」を選択します。
その後の手順は最近のGitHubのREADMEっぽくGIFでデモにしてみました。

最後の「Webhook URL」は後で使うのでコピーしておきます。
GitLabの設定
あらかじめ通知対象にしたいリポジトリを作って何か適当にpushしておいてください。
pushはしてなくてもよいのですがSlackへの通知テストの時に失敗したと怒られてちょっと焦るので精神衛生上のために(笑)

Slackを確かめるとテスト用に通知が上がっていることが分かります。

Jenkins→Slack
続いてJenkinsのビルド・テスト通知をSlackに飛ばすようにします。
Slackの設定
Jenkinsは公式プラグインがあるので割と簡単です。

Step3にある「基本URL」と「インテグレーションのトークン」は後で使うので控えておいてください。
Jenkinsの設定
まずSlackの設定を登録します。基本URLにはフルパスが記載されているようですが、ここはワークスペース名だけで大丈夫みたいです。

次にユーザのAPIトークンを発行します。
例によってトークンは後で使うのでメモっておいてください。

パイプライン処理の実装
ここからはGitLab→Jenkins→Slackまで一気通貫で処理を行うための設定を行います。
GitLabの設定
いったんGitLabに戻ってリポジトリの変更をJenkinsに通知するように設定します。

インテグレーションの「URL」には http://[Jenkinsユーザー名]:[APIトークン]@[Jenkinsホスト]:[Jenkinsポート]/project/[Jenkinsのプロジェクト名]
を設定します。
Jenkinsの設定
プロジェクトの設定を開き、「ソースコード管理」「ビルド・トリガ」「ビルド」「ビルド後の処理」と順に設定していきます。
ここまでもそうでしたが注意点は、今回GitLabもJenkinsも同一マシンにホスティングして内部ではHTTP通信しており、外部からアクセスされた時だけHTTPSでラップさせるコンテナを稼働させているだけなので、こうした各種設定項目にはHTTPを設定するということです。

変更を加えてみる
実際に変更を加えてみます。
README.mdに1行追加しpushします。
するとSlackに通知が上がると同時にJenkinsのビルドが走ります。今回はファイルの中身を表示するだけで終わらせていますが、ちゃんと成功してそれもSlackに通知されていますね。
