新人が4時間かかるビルドとデリバリーを立て直した話

新人が配属されてから4時間かかっていたビルドとデリバリーを20分に変更した話を書いてみる。

# 配属
2年ほど前に新卒でSREに配属されました。
そこでとある上司に出会い今のお仕事の基礎を理解することができたのかもしれません。

とてもとてもありがたいですね。

配属された当初はビルドとデリバリー周りで使用されている技術に関して色々とわからないことが多かったです。

- jenkins
- jenkins slave
- [build flow plugin](https://wiki.jenkins.io/display/JENKINS/Build+Flow+Plugin)
- groovy
- ant
- maven
- nexus

他にも

- jenkins slaveのスペックがとても低い
- ビルドの成功率が悪い
    - 1ヶ月のほとんどjobが失敗しているときもありました
- ビルドがネットワークのエラーで落ちる
- jobのパラメーターをリリース後に手動で変更しないといけない
- ビルドのフローが複雑すぎる
    - 42個のjob
    - 上記のjob達が依存している
    - ビルドのフローの特定のjobから実行するためのロジックが組まれていてフローの理解が難しい
- ビルドに時間がかかる
    - 1回あたり4時間かかりました

配属されてなれるまではビルドを成功させるのがなかなか大変でした。
しかし、1ヶ月とある上司が異動後にビルドの改善が始まっていきます。

# チームの変更
とある上司が異動したのでチームが解散されたので新たなチームに配属されました。
新たなチームでも引き続きビルド周りを担当することになりました。

# ビルドとデリバリーの簡単なフロー
ビルドをしてデリバリーするまでのフローは下記になります

1. javaファイルをコンパイルしてjar,warを作成とnexusにアップロードする
1. コンパイルしたjar,war等とライブラリをオンラインストレージにアップロードするために.m2以下にファイルをインストールする
1. オンラインストレージにコンパイルしたjar,war等とライブラリをアップロードする
1. オンラインストレージにコンパイルしたjar,war等とライブラリがアップロードされたか確認する


# jobのコード化
今までjobがコードになっていないため手動でパラメーターの変更等を行わないといけませんでした。
そのためこの問題を解決するために[Seed Jenkins plug-in](https://github.com/jenkinsci/seed-plugin)を使用してjobをコード化しました。

Seed Jenkins plug-inを使用することでjenkinsのjobのすべてをコードにすることができ上記の手動の変更を無くすことができました。

他にも下記の変更を行いました。

- maven等のオプションを見直して並列でビルドするように変更
- git repositoryが1GB超でとても重いのでshallow cloneを使用してcloneの高速化
- デリバリーする際にファイル1つ1つをシーケンシャルにアップロードしていたので並列でアップロードするように変更

groovyを書いたことがなかったのでなれるまで大変だったのと、公式以外のドキュメントが少なかったためコードを理解するのが大変でした。

# jenkins slaveの変更によるビルドの高速化
元々jenkins slaveはopenstack上に構築されていました。このjenkins slaveがなかなかのポンコツでビルドにとても時間がかかっていました。(HDDなので遅かったんじゃないかなと思います)

そのためチーム変更の際に新しいPC(SSD,i5 6500)に変更してもらいました。

これだけでビルドとデリバリーが1時間程度早くなりました。

# オンラインストレージ上にファイルがアップロードされたかの確認の高速化
jar,warやライブラリをアップロードしたあとにすべてのファイルがアップロードされているか確認するフローがあったのですがこのフローを実現するためにファイル1つ1つをシーケンシャルに確認していました。

これを高速化するためにファイル1つ1つを確認するのではなく、オンラインストレージのファイルのリストとオンラインストレージにアップロードされてほしいファイルのリストを突合し差分のある無しを確認するようにしました。

# ビルドエラーを低減

### ネットワークエラー
ビルドがネットワーク関係のエラーで失敗してしまうことがたまにありました。
この問題は頻度がとても高い問題ではないためjobをretryすることで解決しました。


### コンパイルエラー
CIでビルドを行う際にビルドエラーが発生することがあります。
これはmaster branchにマージする前にビルドすることで防ぐことができます。

Merge Request Hookを使用しmaster branchにマージする前にビルドを実行することでビルドのエラーを低減してもらいました。

# ビルドフローの改善
現状のフローではjobがとても多く通常の人間が依存関係を理解することがとても難しいため、改善をおこなうことにしました。

### ビルドフローの解析
まず、ビルドフローを解析することにしました。
現状のフローを目で何度も確認や特定のjobを削除してビルドが完了するか確認を行いました。

ビルドをして数ヶ月以上立っていたのでなんとなく感で分かる部分もありましたが、マンパワーで解決する必要があったのでとても大変でした。(その代わりにビルドフローの詳細まですべてがわかるようになりましたが...)

### build flow pluginからdeclarative pipelineへ
build flow pluginはすでに非推奨になっており、build flow pluginのscriptが消失するバグを引き当てたためdeclarative pipelineに移行することにしました。

build flow pluginではフローの特定の場所からビルドすることができないので独自のロジックが書かれていましたがdeclarative pipelineではある程度特定の場所からビルドを開始することができるので不要なフロー制御ロジックを削除しシンプルにしました。

また、declarative pipelineではネストした並列を行うのは難しいため、stageを分割し並列にすることでなるべく並列化しながらフローをシンプルにすることができました。

### 不要なjobの削除
ビルドフローの解析の結果、現在は使用されていないjobがあることがわかったため不要なjobを削除しました。

しかし、.m2以下にinstallするためのjobは消すことができませんでした...

# オンラインストレージにファイルをアップロードするためのロジックの変更
オンラインストレージにファイルをアップロードするためにまず.m2以下にmavenでファイルをインストールし、.m2以下のパスとオンラインストレージ上のパスの組が書かれたテキストファイルを使用しファイルをアップロードしていました。

しかし、これを行うには.m2以下にファイルをインストールしないといけないのでいくつかの問題がありました。

- 特定のslaveですべてのビルドを行う必要がある
- .m2以下にインストールするためだけのjobが必要になる
- ファイルをアップロードする際に使用するテキストファイルに問題ないかテストができない
    - 間違った書き方をされてエラーになることがたびたびあった
- classifierのあるjarファイルは指定できない等の機能制限がある

上記を解決するために今回はgradleを用いてオンラインストレージにアップロードするようにしました。
アップロードまでのフローは下記です。

1. dependencyにかかれているファイルをnexusからダウンロード
1. ダウンロードした各ファイルをオンラインストレージ上にアップロード後と同様のディレクトリ構造にする
1. オンラインストレージとsyncする

これをgradleに変更したことにより

- nexusからファイルをダウンロードするので特定のslaveで実行する必要がない
- nexusからファイルをダウンロードするので.m2以下にインストールするjobが不要になる
- 今まではテストを行うのに.m2以下にファイルをインストールしないといけないのでテストが困難だったが、nexusに繋がればgitlab ciを使用してテストが可能になる
- jarのダウンロードはgradleの機能を使用するので独自のダウンロードロジックを組まなくて良い
- gradle側にjarのダウンロードロジックが書かれているのでプロダクト側で独自のダウンロードロジックが組める

### .m2以下にインストールするjobを削除
上記でアップロード方法を変更したので.m2以下にインストールするjobを削除することができました。

# 依存関係の削減
様々なjobを削除しましたがjob間には以前として依存関係が存在しているためシーケンシャルにしか実行できないjobが存在していました。

そのため可能な限り依存関係を無くすようにしました。

依存関係を無くすためにはステークホルダーがたくさんいたので確認が大変でした。

### 更新されていないモジュール
すでにリポジトリが更新されていないにもかかわらずビルドをしているリポジトリが複数あったため、ビルドを行わないようにしました。

参照しているプロジェクトに関しては特定のバージョンを参照するようにしました。

### 更新されているモジュール
更新されているが最新のモジュールを参照しなくてもよい依存関係がありました。

それらについては一つ前にデリバリーしたバージョンを参照するようにしました。

# antをgradleに変更
ビルドを行なっているいくつかのプロダクトではantを使用していました。

antを使用することでいくつかの問題がありました。

- 並列でビルドできないのでbuild時間が長い
- ビルドで使用するライブラリの取得がshell scriptで書かれておりライブラリの管理が煩雑になっている
- ビルドツールの支援が得にくい

### antからgradleに変更できるか検討
antからgradleに変更するためはjavaの循環依存関係を解消する必要があり、これを行うにはプロダクトのコードを大胆に変更する必要がありました。

プロダクトのコードを変更するためにはプロダクトの人を説得する必要があるのでまずは自身で1つのプロダクトをantからgradleに変更しantからの移行に説得力をもたせるようにしました。

antのxmlとshell scriptでできたビルドを人力とshell scriptで解析しgradleに移行しました。

### プロダクトの人にantからgradleに移行してもらう
antからgradleに変更したものをプロダクトの人に共有し、プロダクトの人にantからgradleに移行していただきました。


# おわりに

ここまでビルドとデリバリーを改善するのに2年かかりました。
改善するまでに人間の温かみをたくさん感じることができました。
ビルドとデリバリーの高速化だけではなくフローのシンプル化やビルドツールの支援を行えるようになり、弊社のDXが改善したのでとても良かったと思います。
これらの変更を行うに当たり手伝っていただきました皆様に、この場にて御礼を申し上げます。