こんにちは、サーバーサイドエンジニアの@YusukeIwaki です。 もう半年くらい前になりますが、2020年4月15日に突如としてGitHub Teamプランの値下げが発表されました。
弊社ではBitbucketを使ってソースコードの管理をしていましたが、前々から「GitHubを使いたい」という声も社内でちらほらあったことから、値下げを受けてGitHub移行を決めました。そして3ヶ月間の移行期間を経て2020年の7月28日にGitHubに移行を果たしました。
移行からしばらく経ってしまいましたが、この記事ではGitHub移行を意思決定するまでの課題や、GitHub移行後の状況などを紹介していきたいと思います。
Bitbucket利用時に起きていた問題
そもそも、組織にとってBitbucketがGitHubに比べて使用するメリットが大きいならば、わざわざ乗り換える必要はありません。
「Bitbucketを利用していて、何が問題か?」をなるべく解釈をいれずに事実ベースで整理するところから始めました。
静的解析のCIのメンテナンスコスト
ハッピーな開発ライフを過ごすために静的解析ツールを本格導入した - i3Systems Engineering blogで紹介がありましたが、サーバーサイドの静的解析ツールRuboCopを導入したときのことです。
Pull Requestで修正した範囲に限ってRuboCop指摘をおこなうために、 pronto というライブラリを導入しましたが、先の記事でも書いていたように、このGemはBitbucketでレビューコメントを付ける機能が動きませんでした。
かろうじてmasterブランチをGemfileで指定すれば動作する状態でしたが、RuboCopのバージョンアップを行うとprontoの依存関係が解決できなくなったり、ある日突然使えなくなったりと、かなり不安定でした。
Bitbucket利用時は、このように静的解析ツールのCIの面倒をみるためだけに、1ヶ月あたり4時間程度を要していました。
Pull Requestレビュー時のストレス
エンジニアとして、コードレビューの無い日はありません。毎日のことなので、コードレビューのしやすさは非常に重要です。
Bitbucket利用時には、
- 変更量が多いPull Requestを表示しようとすると白画面になる/スクロールがガタガタする
- ブラウザのページ内検索で、メソッドの利用箇所などを調べようとしても正しくひっかからない
- コメント欄がWYSIWYGエディタで、ソースコードを引用/コピペすると、意図しないスタイルが引き継がれてしまう
など、1つ1つは軽微ながらも、不便を感じることが多くありました。
毎日のことなので、小さなストレスも積み重なると爆発してしまいます。
外部連携サービスがGitHubしかサポートしていないものが多い
開発では手作業を減らすために、コードビルド/デプロイ系のツール(Azure DevOpsなど)や、自動レビューのツール(Amazon CodeGuru, Siderなど)を必要に応じて導入します。
その導入検討時に「GitHubには対応しているのにBitbucketには対応していない」というツールが数多くありました。作業効率化をしたいのに、Bitbucketを使っているがゆえに選択肢が減ってしまうのは困ります。
そもそもなぜBitbucketが使われていたのか
GitHubはOSS開発のソースコード管理のデファクトスタンダードなので、多くのエンジニアはGitHubの使用感は知っていますし、便利な連携ツールがたくさん存在することも知っていました。
それなのに、社内では長らくBitbucketが使われていました。
- 前章で整理したように問題があってもBitbucketを使う理由があるとすると、それは何なのか?
- Bitbucketを使ってきた理由は今でも当てはまる理由か?
を改めて整理しました。
歴史的経緯① 昔はPull Requestを利用しておらず、Gitさえ使えたらよかった
Bitbucket導入時のメンバーが居たのでヒアリングしたところ、Bitbucketを導入した頃はPull Requestを使っていなかった、という事実が浮上しました。Gitログを見てみると、確かに昔のコミットには Pull Requestの形跡がありません。
とはいえ、GitHub移行検討を始めた2020年4月時点でこれが当てはまるかというと、明らかにNOでした。
歴史的経緯② GitHubはBitbucketにくらべて利用料が高く、その価格差に見合う価値が見いだせなかった
ヒアリングで、もう一つの大きな理由が聞けました。サービスの利用料です。
4/15にGitHubが値下げされるまでは、GitHubチームプランで9ドル/人 Bitbucketは3ドル/人、と、3倍の価格差がありました。
Bitbucket | GitHub |
---|---|
pricing |
pricing |
Bitbucketに不満を持つエンジニアでも、「GitHubに移行して、この差額6ドル以上の価値を出せる」ということを定量的に説明することがなかなか難しかったのです。
ただ、冒頭で書いたように2020年4月からGitHubは4ドル/人になったので、この理由も過去の話になりました。
歴史的経緯③ Bitbucket, Confluence, JIRAの相互連携が便利だった
社内では、ConfluenceやJIRAなど、アトラシアンの製品がいくつか利用されています。Bitbucketは同じくアトラシアンの製品のため、相互連携機能が豊富です。(アトラシアンも公式に GitHub から Bitbucket に移行しよう でこの点を推しています)
たとえば、
- Bitbucket側でPull Requestにチケット番号を書いただけで、そのPull RequestのステータスやCI状況がJIRA側で見えるようになる
- JIRAコメントにBitbucketのURLを張るだけで、タイトルやステータスなどをマークアップしてくれる
などです。
GitHub移行をしたら、これらの連携機能が同様に使えるのか?という懸念がありました。
ただ、これも実際に予備調査をしたところ、ほとんどの連携機能はGitHubでも利用することができるので、移行を妨げる理由にはならないことがわかりました。
すでにあるリポジトリをGitHubに移行するのが面倒
Bitbucketで管理されているリポジトリは、正直あまり整理されていなくて非常に数が多く、200以上のリポジトリが存在していました。
BitbucketからGitHubへ、なるべく情報を欠損させることなく移行ができるのか?というところが大きな問題でした。
移行手順の確立
ここまでの整理で以下の2点は明らかになりました。
- Bitbucketを使い続けると、静的解析ツールのメンテナンスで継続的に4時間/月がかかる
- GitHubとBitbucketの価格差が少なくなった今となっては、明らかにGitHubに+1ドル/人・月を投資する価値はある!
- 外部連携ツールが豊富なのは明らかにGitHubで、今後もその流れはきっと変わらない
残る課題は「200以上のリポジトリを、なるべく情報の欠損なく移行できるか?」の1点のみです。
こればっかりは、実際にやってみないとわからないので、10年以上の歴史があるCLOMOのGitリポジトリと、その他いくつかのアプリのリポジトリで、「この手順でやれば移行できる」という手順の確立をすることにしました。
git mirrorを使う
大抵のリポジトリは、git mirrorを使うことで、BitbucketからGitHub移行ができます。
たとえばi3-systems/clomoという名前のリポジトリをBitbucketからGitHubに移行したいときは、
git clone --bare git@bitbucket.org:i3-systems/clomo.git git push --mirror git@github.com:i3-systems/clomo.git
以上のように2つのコマンドを叩くだけで、全てのコミット・リモートブランチ・タグの情報をそのまま移行することができます。
10年以上の歴史があるCLOMOリポジトリであっても、上記の手順では10分足らずで移行ができました。
git filter-branch で巨大ファイルを消してから移行する
"大抵の" リポジトリはgit mirrorで簡単に移行ができましたが、いくつかのリポジトリはそうは行きませんでした。その原因の1つが「100MB以上のファイルがある」リポジトリです。
Bitbucketではファイルサイズ上限が1GBとかなり大きく、200MBくらいのファイルを雑にGitリポジトリに置くことができます。しかしGitHubは100MB以上のファイルをgit pushするとエラーになります。
https://docs.github.com/ja/github/managing-large-files/conditions-for-large-files
巨大ファイルは間違ってGit管理してしまっていたものがほとんどでしたが、自動試験のリポジトリで、ファイルアップロードの上限を確認するための301MBのファイルが管理されていたりもしました。
Git LFSを使用して100MB以上のファイルを管理する方法も検討はしましたが、現状だとそもそも大容量のデータをGit管理する必要がない(自動テスト実行時に動的に301MBのファイルを生成するなど、代替手段がある)ため、100MB以上のファイルをGit管理下から外す方向で調整をしました。
GitHubにpushするには単純に巨大ファイルをgit rm して pushするだけではダメで、巨大ファイルが追加されてからの全コミットログを書き換える必要があります。手順はGitHubの公式ヘルプに詳細に書かれています。
https://docs.github.com/ja/github/authenticating-to-github/removing-sensitive-data-from-a-repository
たとえば、 i3-systems/autotest というBitbucketリポジトリから files/300mb.txt
files/301mb.txt
という2ファイルを消してGitHub移行するときには
git clone git@bitbucket.org:i3-systems/autotest.git git filter-branch --force --index-filter \ "git rm --cached --ignore-unmatch files/300mb.txt files/301mb.txt" \ --prune-empty --tag-name-filter cat -- --all # https://stackoverflow.com/a/379842 for i in `git branch -a | grep remote | grep -v HEAD | grep -v master`; do git branch --track ${i#remotes/origin/} $i; done git remote add github git@github.com:i3-systems/autotest.git git push github --force --all git push github --force --tags
以上のコマンドを順に叩くと、全てのコミット・リモートブランチ・タグの情報を(巨大ファイルを除いて)そのまま移行することができます。
途中に謎のforループが存在しているのは、すべてのリモートブランチ/タグ情報をローカルに取得するための呪文です。StackOverflowに情報があったものを、ありがたく拝借させていただきました。
破損したコミットツリーをどうにかしてから移行する
git mirrorで簡単に移行ができなかったもう一つの理由がこれです。200以上あるリポジトリのうち2リポジトリだけ、コミットツリーが破損しているものがありました。
Bitbucketでは特に問題なくpushできていたものが、GitHubでは git fsck が有効でpushできないようです。
remote: error: object 4aa22715b6dff4e986b96f451b9fa8af7a80: duplicateEntries: contains duplicate file entries remote: fatal: fsck error in packed object error: unpack failed: index-pack abnormal exit
git素人の私では修復は困難だったため、関係者と調整の上で、破損したコミット以前の歴史を保持することを諦めました。
git replace --graft <破損コミットの次のコミットハッシュ> git filter-branch -f --tag-name-filter cat -- --all
上記のコマンドにより、破損したコミット以前を全て1コミットに丸めることができるため、これでGitHub移行することができます。
準備はできた!さあ移行だ!
以上のように、下準備を重ねた結果、BitbucketからGitHubへの移行はメリットが大きく、実現可能性も十分にあることから、移行プロジェクトが2020/5/18から開始となりました。
なんとなく感じられたかもしれませんが、弊社では「GitHubのほうが使いやすいので…」という"解釈"をベースにした提案は割と通りづらくて、事実を整理して提案をすることが強く求められる傾向にあります。
そのため、このような下準備をいろいろと重ねた上でプロジェクトの開始となったのです。
すべての移行作業を自動化して、夜間実行できるようにする
前述の通りGitリポジトリは全部で200以上あるので、手作業だとどうしてもミスが発生してしまいます。また、そもそもこんな単純作業で業務時間を使うこと自体がイヤです。
そのため、移行作業はスクリプトを叩くだけで実行できるよう、徹底的に自動化しました。
自動化を徹底することにより、単純に手作業のストレスが減るのはもちろんのこと、
- 移行作業を夜間実行できる: ほとんどのケースでは「明日からGitHub使ってくださいね」って関係者に声をかけておけばよくなる
- 何度でも同じ状態でリポジトリを作ることができる: まずGitHub移行して予行練習→問題なければ改めてリポジトリを本移行、のように運用課題の事前調査がしやすい
など多くのメリットが得られます。
git-mirrorを使うパターン
#!/bin/bash set -x export GITHUB_TOKEN=ひみつ for arg in "$@" do rm -rf $arg.git curl -u i3-systems-admim:$GITHUB_TOKEN -X DELETE https://api.github.com/repos/i3-systems/$arg curl -u i3-systems-admim:$GITHUB_TOKEN -X POST https://api.github.com/orgs/i3-systems/repos -d '{"name": "'$arg'", "private": true, "has_wiki": false, "has_projects": false, "has_issues": false, "allow_squash_merge": false, "allow_rebase_merge": false, "delete_branch_on_merge": true}' git clone --bare git@bitbucket.org:i3-systems/$arg.git pushd $arg.git/ git push --mirror git@github.com:i3-systems/$arg.git popd done
git-mirrorでコケて、大容量ファイルの削除が必要なパターン
#!/bin/bash set -x repo=$1 shift files="$@" echo $repo echo $files # https://help.github.com/en/github/authenticating-to-github/removing-sensitive-data-from-a-repository rm -rf $repo git clone git@bitbucket.org:i3-systems/$repo.git pushd $repo git filter-branch --force --index-filter \ "git rm --cached --ignore-unmatch $files" \ --prune-empty --tag-name-filter cat -- --all # https://stackoverflow.com/a/379842 for i in `git branch -a | grep remote | grep -v HEAD | grep -v master`; do git branch --track ${i#remotes/origin/} $i; done git remote add github git@github.com:i3-systems/$repo.git git push github --force --all git push github --force --tags popd
以上のような移行スクリプトをつくり、コマンド1つでリポジトリがササッとBitbucket→GitHub移行できるようにしました。
./create-mirror.sh \ clomo \ clomo-android \ clomo-ios \ clomo-windows \ autotest \ autotest-android ./remove-large-file.sh autotest files/300mb.txt files/301mb.txt ./remove-large-file.sh autotest-android output/clomo.apk
移行スケジュールの調整
Pull Requestの情報がBitbucketからGitHubへ移行できないという制約があるので、アクティブな(絶賛開発中/レビュー中の)リポジトリの移行を避ける必要があります。
アクティブなリポジトリについては、それぞれいつ移行するかを決めて、段階的に移行を進めました。これは愚直にスプレッドシートでの手動管理でした。(苦笑)
ちなみに、このスプレッドシートはBitbucket APIと jq を駆使してベースを作りました。手作業は多ければ多いほどミスが多くなるので、こういう細かいところもなるべく自動で収集するようにしました。
curl https://api.bitbucket.org/2.0/repositories/i3-systems | jq -r '.values[] | [.full_name, .updated_on, .links.html.href] | @csv'
メンバーの追加のTerraform化
せっかく移行スクリプトを自動化したのに、いちいち「○○さんのアカウントを追加お願いします!」と依頼を受けて手作業でGitHubのWeb UIをポチポチやるのはイケてないです。
BitbucketではTerraformでユーザの管理ができないので、アカウント追加/削除依頼のようなフローを作らざるをえませんでしたが、GitHubならTerraformでメンバー管理ができます。
今回はメンバー管理用のGitHubリポジトリを真っ先に作成し、アカウント追加作業は部内の誰でも対応できる状態にしました。
terraform validate, plan, apply するためのGitHub ActionsがHachiCorpから公開されているため、ほんの少しCI設定を書くだけで
- Pull Requestが出されると、terraform validate, terraform plan (=メンバー追加内容のレビュー)
- masterブランチにマージされると、terraform apply (=メンバー追加)
というフローが簡単にできます。
アカウント追加作業をPull Requestベースで行うことで、アカウント追加作業の属人化がなくなるだけでなく、誰が誰をいつinviteしたのかという情報も残るので、監査的な観点でもメリットがあります。
移行が完了!
そんなこんなで、いろいろ工夫をしながら移行作業を2ヶ月ほどで終えました。
以下が実績工数の推移です。前半はスクリプト作成などでガッと上がっていますが、移行作業を進めていく段階ではほとんど工数を使わず、最終的には予定よりも早く&省コストで移行が完了しました。
数の暴力と戦うときには、作業の自動化って本当に大事ですね。
移行して本当によかったこと
GitHub移行してから、開発メンバーからは様々なフィードバックはありましたが、特筆すべき定量的な改善が2点ありました。
静的解析ツールの管理コストがほぼゼロになった
こちらですでに紹介していた内容そのものですが、Bitbucketのときは静的解析ツールの管理で毎月4時間かけていたものが、GitHubではreviewdogとGitHub Actionsを使って、ほぼ手放しで運用できるようになりました。
工数削減ももちろんのことながら、「なんでこんなことやらなあかんねん・・・」っていうモチベーション低下がなくなり、本来の作業により集中できるようになりました。
利用料がBitbucketのときよりも安くなった
これは完全に偶然の産物で、移行検討時には想定していなかったものです。
Bitbucketの頃は1つのOrganizationでメンバーとリポジトリを管理していました。しかし、情シスのメンバーが他の部のリポジトリを参照したりすることは運用上ほとんどなかったので、権限管理・職務分散の観点からGitHub移行に伴いOrganization自体を分けることにしました。
Bitbucketは5人以上のOrganizationが課金対象であるのに対し、GitHubは人数制限なく(特定の機能を有効化しない限り)無料で使えます。そのため、Organizationを分離した結果、(特定の機能を使用しない)情シスとPF運用部が課金対象から外れました。
1人あたりの課金額はGitHubのほうが1ドル高いにもかかわらず、課金対象人数が減ったことで、GitHub移行後のほうが利用料が安くなりました。
まとめ
「GitHub移行しました」という軽いタイトルに反して、いろいろと説明をしました。
BitbucketからGitHubへの移行をみんなに推奨したいわけではなく、あくまで「我々の組織にはBitbucketよりもGitHubのほうが合っていたので移行をした」という旨の説明をするため、経緯の部分が特に長い記事になってしまいました。
私たちは今後も着実に少しずつ改善を重ねていきたいと思います。そして、一緒に製品や開発環境を改善していける仲間を募集しています!