19
17

CICDチーム1年目が 荒れ果てた"旧バージョン" 開発環境に1年間でCIを導入した話

Last updated at Posted at 2024-02-05

軽く自己紹介

新卒でインフラの会社に入社。
転職しソフトウェアエンジニアに、JavaとJSで開発。
現在はCICD(Developer Productivity Engineeringに近い)のお仕事をしています。

つよつよメンバーに囲まれながらお仕事をしていました。
下の記事を書いた方とも半年間一緒にお仕事をしました。
そこで実施したことやそこでの学びを本記事に記します。

(ほんとはCICDきて8ヶ月目だけどオマージュもとにタイトルを合わせたかったので盛りました)

想定読者

CICDや開発環境整備にかかわる人間。
昔から続く開発環境をよりよい状態にしたことがある人、したい人。
開発者の役に立つ改善を志している方。などなど。

Before

・SVNでのソースコード管理
・プレマージでのソースコードレビューができない
・新人は使い慣れないSVN操作。加えて処理が重い
・Antでのビルド
・新旧バージョンで修正が使いまわせない
・CIがほとんどない
・ソースコードレビューの質がバラバラ

After

・GitHubでのソースコード管理
・プレマージでのソースコードレビューができる
・開発者は使い慣れたGit操作。快適
・Gradleでのビルド
・新旧バージョンで修正が使いまわせる
・CIが充実しつつある
・ソースコードレビューが一部標準化された

本題

荒れ果てた旧バージョンの開発環境の改善

前提:巨大かつ複雑なリポジトリ
image.png

Step 1. バージョン管理を何でしてる?

新バージョンはGitで管理されています。
旧バージョンはSVNで管理されています。

これはDX Criteriaの以下のアンチパターンに該当している由々しき事態。
バージョン管理システムが複数存在していたり、1つのツールからすべての履歴を閲覧できないなど、中途半端な状態のままになっていないか。

旧バージョンが抱える問題点

・管理している自前のサーバ(以降サーバA)が重すぎて、checkoutやコードのレビューを実施するのに時間がかかったりと開発をしているときのストレスが多い。
・修正をtrunkに直接コミットするため、コミット前にソースコードレビューができない(プレマージでのチェックができない状態)

ですが、すでにサーバAに多くのCICDの仕組みが紐づいている ため、すべて乗せ換えるのは工数がかかりすぎる。
加えて、完全に載せ替えをする場合開発者以外の多くの関係者を巻き込むことになる ため、残念ながら容易にGit/GitHubに移行できない状況。

抱える課題を解決するために

・GitHubからSVNリポジトリへ自動で同期する仕組みを作成。
これにより、開発者はSVNを使用することなく使い慣れているGitのみを使用できるようになりました。

サーバAに紐づくCICDの仕組みをそのまま使えるため、評価やデプロイは今まで通りの仕組みを利用できます。

さらに、新バージョンがGitで管理されているため旧バージョンに対する修正をGitでそのまま使いまわせるメリットも。
新・旧バージョンで使用しているサードパーティライブラリのバージョン違いにより修正を使いまわせない課題はStep 2にて解消

仕組み

image.png

簡単に同期方法を紹介します。
・プルリクエストをマージする際はスカッシュマージ
・masterブランチに入ったコミット単位でrsyncコマンドを使用してディレクトリの同期を実施
ただし、delete,rename の場合は特別な対応をする必要があります。機会があれば記事にします。

また、画像の「検証」にあるように、SVNのpre-commit で行われていた一部の処理をGitHubActionsで書きなおすことも実施しました。

Step 2. ビルドツール何を使っている?

新バージョンはGradleを使用しています。
旧バージョンはAntを使用しています。

今回は旧バージョンのAntで書かれたbuild.xmlを読み解きGradleに載せ替えました。
(1000行を超える超巨大なbuild.gradleを生み出してしまった。)

最近チームに加わった方が書いたGradleの記事

せっかくGitHubに移行したのに、「新・旧バージョンで使用しているライブラリのバージョンに差分があって修正を使いまわせない」ということもなくなりました。
開発者の生産性向上につながる いい取り組みだったと思います。

fatjarの作成

(細かな背景は割愛しますが)出荷作業の影響に大きな影響を与えたくないとの思いもあり、
サードパーティライブラリを一つのライブラリにまとめるためにfatjar を作成しました

参考:Creating Fat Jar In Gradle

task fatJar(type: Jar) {
  archiveBaseName = 'uber'
  duplicatesStrategy = DuplicatesStrategy.WARN

  def excludePatternsForFatJar = [
    // classファイルが重複する、再配布すべきでないなどいろいろな理由で除外したjarたちがあります
  ].join('|')

  from {
    configurations.runtimeClasspath
        .findAll { dep -> !dep.name.matches("^(${excludePatternsForFatJar})")}
        .collect { dep -> dep.isDirectory() ? dep : zipTree(dep)}
    }
  //署名付きJarは以下があれば動作しないので除外する
  exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA'
}
ぶちあたった問題点

fatjarにまとめる対象となるjarの間でclassファイルが重複していると、どれを利用するかはクラスローダーの読み込み順に依存してしまう。先に読み込まれたclassファイルが意図しないバージョンのものである場合、エラーが発生する

対応

DuplicatesStrategy.WARNを使用することで重複するclassファイルを特定し、どのjarが該当のclassファイルを含んでいるかを通知する

// fatjarタスクにて、uber.jarに含んだjarのclassファイルの出身地をテキストにまとめるgradleタスク
task outputClassesList {
    def classList = []
    doLast {
        configurations.runtimeClasspath.files.each { File file ->
            if (file.name.endsWith('.jar')) {
                zipTree(file).visit { FileVisitDetails details ->
                    if (details.file.name.endsWith('.class')) {
                        classList << (file.name + ' ' + details.relativePath.pathString)
                    }
                }
            }
        }
        file("fatjar_classes_location.txt").write(classList.join('\n'))
    }
}
DUPLICATED_PATH_FILE="fatjar_warning_path.txt"
SEARCH_TARGET_FILE="fatjar_classes_location.txt"

# 重複箇所を各行ごとに照合し、どのjarにて重複しているかを出力
while IFS= read -r line_a; do
  matched_lines=$(grep "$line_a" "$SEARCH_TARGET_FILE")

  if [[ ! -z "$matched_lines" ]]; then
    echo "クラス $line_a が、以下のサードパーティーライブラリで重複しています"
    echo "$matched_lines"
    echo
  fi
done < "$DUPLICATED_PATH_FILE" > fatjar_check_duplicate.txt
結果
クラス hoge/hoge/hogehoge.class が、以下のサードパーティーライブラリで重複しています
hogehoge.jar hoge/hoge/hogehoge.class
fugafuga.jar hoge/hoge/hogehoge.class

こんな感じでclassファイルの重複を検知する仕組みづくりができています。

Step 3. CIちゃんとしてる?

すこしタイトルとはずれてしまいますが、この一年で新旧バージョンに多くのCIを作成しました。
主に旧バージョンのCIを整備したのですが、最も伝えたいことは新バージョンで導入した修正差分に対してSonarQubeScan実施についてです。(そのうち旧バージョンにも導入予定)

以下作成したもの一例
プレマージ
・修正を紐づけるチケットの妥当性を確認
・プレマージでビルドが成功するかを確認
・修正差分に対してSonarQubeScanを実施し、ソースコードのレビューを簡単にする
・fatjarの妥当性を確認
定期ジョブ
・GitHub To SVNRepository の自動同期処理
・定期的にリポジトリの掃除
・バージョン情報を自動更新

最も開発者にインパクトのあったもの

ソースコードレビューを一定水準担保できる仕組みである、修正差分に対してSonarQubeScanを実施しソースコードのレビューを簡単にするについて解説します。

仕組み

修正したソースコードの差分のみを対象として、SonarQubeScanを実行し、プルリクエスト/マージリクエストにScan結果を通知する仕組みを作成しています。
これより「コード品質の継続的な改善」「セキュリティリスクの検出」をすべてのプルリクエスト/マージリクエストで担保できます。

参考:analyzing-source-code/pull-request-analysis

GitHubに来る通知
image.png

Gitlabに来る通知
image.png

結果

CICDチームで「プレマージにおけるSonarQubeScan結果の扱い方に関するルール」を作成し、開発全体に周知することでソースコードレビューが一部標準化されました。

小話

この辺りのCI整備は生成AIが大活躍。
生成AIなしでは生きていけない人間になってしまった。

Step 4. IDEの載せ替えでナウい開発環境へ

Eclipse → IntelliJへの載せ替えを検討/実施中。
まだ手付かず。あと4ヶ月ちょいで完了させたい。

Eclipseで何をやっているかは大体わかってきた。先人はすごいぞ。

あと、載せ替えることで開発者の皆がGitHub Copilotを使える環境を提供したい。

Appendix. スクラム開発を通して

スクラム開発で設計・実装・検証のうすべてを含めてSVNからGitHubへの引っ越しは1.5ヶ月で完了。Gradle化は3ヶ月くらい。

スクラムっていいね!

チーム体制
 PO, スクラムマスター, developer*4 (内1が私)
ステークホルダー
 開発の皆さま

難度の高い課題はモブプロを通して解決。チケットのタスクをリファインメントすることに力を入れて、チームの全員が現状抱えている課題を認識することができていた状態でした。それゆえ、自分が休んだ場合でもチケットが勝手に進んでいく状態でした。

管理している環境のトラブルなどに対応しつつ、多くの成果を出せたのではないかと思います。

スクラムで大事なことはチームで仕事するなら、リアクションし続けよということでしょうか。

感想

今のチームに移って、開発時代のストレスを原動力にいろいろと頑張ってきました。
巨大かつ複雑なリポジトリに対する課題に挑む中で成長できたこともあるでしょう。

自分が開発をしたいと思える環境ができるまでCICDにいようと思う反面、燃え尽き症候群気味で「そろそろ開発者に戻ろうかな」と考えたり。

一緒に働いてた「HT/YS/TH/HA/TT」さんに全力で感謝を。

25歳にして初Qiita投稿Done。

19
17
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
17