はじめに
メグリでは、バックエンド、iOS、Androidともに、社内で開発している共通基盤となるコアライブラリを利用して複数のプロジェクトを運用しています。
当初はどのプロジェクトがどのバージョンのコアライブラリを利用しているかを手動でスプレッドシートに記録して管理していましたが、プロジェクトの数が増えるにつれて手動での管理では記載の更新が追いつかず、コアライブラリのバージョンを把握するのが難しくなりました。
そこで、これまで手作業で行っていたバージョン管理を自動化し、プロジェクトで利用しているコアライブラリを簡単かつ正確に把握できるようにしました。
自動化に際してGitHub Actionsを使ってバージョン情報の収集を行ったので、この記事ではそのときの内容を備忘録代わりにまとめておきたいと思います。
プロトタイプの作成
それぞれのプロジェクトはGitHubリポジトリで管理していて、特定のコンフィグファイルでコアライブラリのバージョンを記載しているので、それらの情報を収集すれば目的は達成できそうです。
弊社ではもともとBitriseを利用してアプリをビルドしていたので、Bitriseの環境を利用してGitHubの各リポジトリにアクセスしてバージョン情報を取得するBashスクリプトを作成しました。作成したものは以下のようなスクリプトです。
# server side
for repo in $(cat server.txt); do
./retrieve_version_server.sh $repo >> server_version.txt
done
# ios
for repo in $(cat ios.txt); do
./retrieve_version_ios.sh $repo >> ios_version.txt
done
# android
for repo in $(cat android.txt); do
./retrieve_version_android.sh $repo >> android_version.txt
done
# update spreadsheet
update_spreadsheet.sh server_version.txt ios_version.txt android_version.txt
一応、これで目的を達成できましたが、収集対象のリポジトリが数百あるため実行時間に10分程度かかり、それに相当するクレジットを消費してしまっているのが不満点です。
処理の高速化
上記処理の大枠はGitHubリポジトリをcloneして、その後で特定ファイルをgrepしてバージョン情報を取得するだけのものですが、シーケンシャルに実行しているため実行時間が長くかかっています。
各スクリプト自体は独立しているため、並列化は容易です。そこで並列化による高速化を試みました。
こちらの記事にもあるように、GitHub Actionsを利用し始めたタイミングだったので、GitHub Actions に移行しつつ、もともとのBashスクリプトを利用しながら以下のようにしてPythonで並列化して高速化を実施しました。
BitriseからGitHubのリポジトリにアクセスするより、GitHub Actionsを利用することでGitHub内でリポジトリにアクセスするほうが通信時間を短縮できるだろうというのも、目論見の一つです。
import subprocess
import concurrent.futures
max_procs = 2 # 並列度
def server_iter(repo):
subprocess.run(["./retrieve_version_server.sh", repo])
def ios_iter(repo):
subprocess.run(["./retrieve_version_ios.sh", repo])
def android_iter(repo):
subprocess.run(["./retrieve_version_android.sh", repo])
if __name__ == "__main__":
results = []
with concurrent.futures.ThreadPoolExecutor(max_workers=max_procs) as pool:
for repo in get_repositories():
results.append(pool.submit(server_iter, repo.server))
results.append(pool.submit(ios_iter, repo.ios))
results.append(pool.submit(android_iter, repo.android))
concurrent.futures.wait(results)
reduction_and_update_spreadsheet()
GitHub ActionsランナーのvCPU数は2でしたが、タスク自体はI/O BoundでCPU数以上の並列度でも効果が期待できます。
運用するときの並列度を確定するため、まずは max_procs を変更して実行時間を計測してみます。max_procsを1, 2, 4, 8, 16, 32, 64 の7パターンで、それぞれ10回計測したものが以下の通りでした。
図の棒グラフは実行時間の平均値を表し、エラーバーは標準偏差を示しています。
リポジトリによってclone処理に時間がかかることがあったり、GitHub Actionsランナーの負荷などによって処理時間に違いがでているようでした。そのため、特にmax_procsが1の場合は試行ごとで全体処理時間にばらつきがありましたが、並列化で処理を多重化することによって実行時間の長い処理を隠蔽でき、全体としての処理時間を短縮できました。
並列度は8以上でサチっていたので、並列度の標準偏差を考慮して最終的には max_procs を16として設定してあります。
以上の変更によって、実行時間はおおよそ6分から2分程度に短縮され、実行時間と消費クレジットを削減することができました。
参考: 測定したときのGitHub Actionsランナーのスペック
項目 | スペック |
---|---|
Machine type | Standard GitHub-hosted runner/Linux |
CPU | AMD EPYC 7763 64-Core Processor |
# CPU | 2 vCPU |
Memory | 8GB |
おわりに
この記事では、GitHub Actionsを活用してバージョン情報管理の自動化を行った事例を紹介しました。Python concurrent.futures による簡易な並列化により実行時間を短縮できました。
GitHub ActionsはCI/CDツールとしてだけでなくさまざまなタスクの自動化にも応用できるので、これからも活用方法を模索していこうと思います。