こんにちは、いっしーです。
今回は、重くなっていた lint 実行環境を見直す中で、GitHub Actions と AWS CodeBuild を使う構成に切り替えたときの話を書きます。
この記事は構成の正解を紹介するというより、重い lint 実行環境をどう見直す判断に至ったか、という話です。
また、自分は普段 Rails アプリケーションの機能開発を主にしている Web エンジニアで、インフラ構築は今回が初めてでした。
それでも、小さい差分から進めることで形にできたので、その点も含めて残しておこうと思います。
背景
もともと、CI では Docker イメージを pull してジョブを実行する構成になっていました。
PR で変更されたファイルだけをチェックする lint も、その前提で動いていました。
そのため、lint を通すだけなのに、アプリ本体と同等の重い実行環境を使っていました。
以前から「もう少し軽くできそうだ」とは思っていたものの、機能開発や他の優先課題があり、すぐに手を付けられていたわけではありませんでした。
直近で、イメージの保管先を変更する出来事があり、請求の状況を見たときに、「これはジョブ単位の最適化ではなく、構成自体を見直したほうがよさそうだ」と感じるようになりました。
小さく切り出す方針にした
最初から GitHub Actions と AWS CodeBuild の構成を考えていたわけではありませんでした。
もともとは、実行基盤を見直せば費用を抑えられるのではないか、という仮説から調査を始めていました。
その中で、「lint 系の軽量 workflow だけを切り出す」というアイデアにたどり着きました。
この時点で、自分の中では課題が 2 つありました。
- 重い lint 実行環境をそのまま持ち続けるのはつらい
- 共有された CI 基盤の費用帰属が曖昧だった
この 2 つを同時に少し前進させるには、全部を一気に移すより、軽量 workflow だけを別基盤へ寄せるほうが現実的だと考えました。
なぜ GitHub Actions と AWS CodeBuild だったのか
この構成を選んだ理由は、単純な節約だけではありませんでした。
むしろ大きかったのは、費用負担の整理です。
利用している CI/CD サービスでは、実行にかかったコストを比較的そのまま追いやすい状況でした。 一方で、通常の GitHub Actions をそのまま使うと、費用は共有された GitHub Organization 側に乗ります。
組織としては同じでも、実際の負担単位は別で考えたいことがあります。
今回も、軽量な workflow を追加したいだけなのに、その実行コストが共有された GitHub Organization 側に乗り続ける構造は扱いづらいと感じていました。
つまり、通常の GitHub Actions に移すだけでは、実行環境を見直せても、費用負担の整理まではしにくかったということです。
そこで、GitHub Actions の self-hosted runner を AWS 側で動かすことで、
- lint 実行環境の重さを見直す
- 実行コストをサービス側の負担として扱いやすくする
の両方を狙うことにしました。
その結果、GitHub Actions の self-hosted runner を AWS CodeBuild で動かす構成を選びました。
実装の考え方はシンプルだった
やったこと自体は、意外とシンプルでした。
1. GitHub Actions 側に軽量 workflow を用意する
まず、PR で変更されたファイルを対象に lint を実行する workflow を用意しました。
ここで意識したのは、重い既存ジョブをそのまま持ってくるのではなく、
- PR で変更されたファイルに対象を絞る
- lint に必要なものだけを実行する
- アプリ本体の重い依存から切り離す
ということです。
たとえば、実際のイメージはかなりシンプルで、PR で変更された Ruby ファイルだけを対象に RuboCop を流すような形です。ポイントは、対象ファイルと実行内容を絞り、実行先も軽量な runner に寄せたことです。
name: lint
on:
pull_request:
types:
- opened
- synchronize
- reopened
jobs:
rubocop:
runs-on:
- self-hosted
- linux
- x64
- ci-light
steps:
- uses: actions/checkout@v5
- name: Get changed Ruby files
id: changed_files
uses: tj-actions/changed-files@v45
with:
files: |
**/*.rb
- name: Set up Ruby
if: steps.changed_files.outputs.any_changed == 'true'
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
bundler-config: gemfile=gemfiles/ci.gemfile
- name: Run RuboCop
if: steps.changed_files.outputs.any_changed == 'true'
run: bundle exec rubocop ${{ steps.changed_files.outputs.all_changed_files }}
2. lint 用の Gemfile を本体から分離する
lint のためだけにアプリ本体と同じ Gemfile を使うのは重いので、CI 用の Gemfile を分けました。
これによって、lint 実行に必要な依存だけを切り出しやすくなり、実行環境の責務も整理しやすくなりました。
たとえば、CI 用の Gemfile は次のようなイメージです。
source "https://rubygems.org"
ruby File.read(".ruby-version").strip
gem "rubocop"
gem "rubocop-performance"
gem "rubocop-rspec"
Rails アプリだと、なんとなく本体の Gemfile に寄せたまま進みがちですが、今回のように用途がはっきりしているなら、分けてしまったほうが考えやすかったです。
3. 対象 workflow を 3 つ想定し、実際には 2 つに絞った
最初に移設候補として考えたのは、軽量な workflow 3 つでした。
ただ、実際に今回移したのはそのうち 2 つです。
残り 1 つは、その時点では実行時間や役割の面でまだ切り出しどころを見極めきれなかったため、今回は移設せず、将来の候補としてコメントに残すにとどめました。
結果として、「軽量そうなものを全部移す」のではなく、まずは小さく確実に動かせる範囲に絞って進める形になりました。
この判断は、インフラ初挑戦の自分にとっても進めやすく、ちょうどよかったと思っています。
4. AWS 側に最小限の実行基盤を用意する
インフラ側では、GitHub Actions の self-hosted runner として動かすための実行基盤を用意しました。
- 実行する場所
- 権限
- 起動のための設定
- ログの出力先
を揃える、という話です。
最初から全部入りの構成を作るのではなく、まずは小さく動く最小構成を目指しました。
インフラ初挑戦でも進められた理由
今回、自分にとってインフラ構築は初めてでした。
それでも進められたのは、次の条件が揃っていたからだと思います。
インフラ構築がコード化されていたこと
変更をコードとして追えるのは大きかったです。
画面上で設定を手作業で積んでいくより、何をどう追加しているのか見通しが持ちやすかったです。
リソース名が追えたこと
正直、「名前は効いたことあるけどよく知らない」リソースが多かったです。
ただ、「これは何か」が分からなくても、ひとつずつ追える状態だったので、そこで完全に止まることはありませんでした。
分からないものは AI に聞きつつ、全体像を補いました。
インフラエンジニアにレビューしてもらえる環境があったこと
これが一番大きかったかもしれません。
最初から全部理解していたわけではありません。
インフラエンジニアに確認しながら、必要なポイントをレビューしてもらい、安心して進められる状態がありました。
また、開発のエンジニアにも使い勝手や動作確認をしてもらいながら進められました。
ChatGPT と GitHub 連携を、調査と理解の補助として使えたこと
今回は、社内で利用している ChatGPT を、壁打ち相手兼調査補助として使いました。
実装を丸ごと任せるというより、
- インフラ構成をコードで管理している既存の設定を調べる
- どのリソースが何のためにあるのか整理する
- 追加変更に必要そうな差分を見立てる
- 最小構成で始めるなら何が必要か考える
といった、理解の土台を作るところで特に助けられました。
自分は普段アプリケーション開発が中心なので、既存のインフラ構成を読むこと自体が最初のハードルでした。
GitHub 連携を通じて構成管理用のコードを調べながら、全体の見通しをつけられたのは大きかったです。最初の「何がどこにあるのか分からない」を越える助けになりました。
また、今回はまとまった時間を取って取り組むというより、業務の合間に少しずつ進める形でした。
そのため、開発用のAIツールを使うより、ブラウザでタブ開けっ放しで放置できる ChatGPT のほうが今回のケースには合っていました。
もちろん、最終的にはインフラエンジニアにレビューしてもらいながら進めました。
小さい差分に絞ったこと
これもかなり重要でした。
重い CI 全体を移すのではなく、軽量 workflow だけをまず移すと決めていたので、考えることが増えすぎませんでした。
結果として、業務の合間に進めながらでも、5 営業日以内には終えられました。
大きなハマりどころもなく進められたのは、この進め方のおかげだったと思います。
得られたものは、節約そのものより整理と経験だった
今回の成果をひとことで言うと、節約そのものより、整理だったと思っています。
- 重い lint 実行環境を見直せた
- 共有された CI 基盤で曖昧だった費用帰属を少し整理できた
- チームとして以前から感じていた lint 運用の課題に手を付けるきっかけになった
そして個人としては、これがかなり大きな経験になりました。
普段は Rails アプリケーションの機能開発が中心で、インフラ構築は今回が初めてでした。
その中で、必要な範囲を見極めて、小さく構成を変える経験ができたのは、自分にとってかなり意味がありました。
これからもメンテナンスしていく責任はある
ひとつ正直に書いておくと、運用について「もう完全に安心です」とはまだ言い切れません。
大きな問題は今のところ上がっていないのですが、だからといって十分な知見がもうあるとは思っていません。
ログの見方や継続的な保守も含めて、これから自分でも責任を持って見ていく必要があると感じています。
おわりに
今回やったのは、CI 全体を作り直すような大きな話ではありませんでした。
その中で、自分は小さく着手しやすい改善を 1 つ進めました。
全体を一気に変えず、軽いものだけに絞って、専門家のレビューを受けながら進める。 この進め方なら、普段アプリ開発をしているエンジニアでも、インフラ寄りの改善に踏み出せることがあると感じました。
また今回あらためて思ったのは、AI の助けによって、専門領域の垣根を越える動きは以前よりやりやすくなったということです。
もちろん、AI だけで何でもできるわけではありませんが、知らない領域に入るときに、調査や整理、壁打ちの相手がいることで、最初の一歩のハードルは確実に下がったと思います。
一方で、それが成り立ったのは、自分ひとりの力ではありませんでした。
インフラ構成がコード化されていて、既存の構成を追える状態だったこと。
分からないところを確認できるインフラエンジニアがいて、レビューを受けられる環境があったこと。
さらに、開発のエンジニアにも使い勝手や動作確認をしてもらえたこと。
そうしたチームと環境があったからこそ、小さくても前に進められたのだと思います。
同じような課題を抱えている方の参考になればうれしいです。