はじめまして! 2026年の2月後半に10日間、エムスリーのAI・機械学習チームでインターンをしていた竜です。
この記事では、セルフホストされた社内のCI/CD基盤を新しく構築し、その上で弊チームにおいてデプロイ時間を約半分短縮した事例について紹介します。
合わせて、1人の学生の視点から見たエムスリーの雰囲気であったり良さを紹介できればと思います!

背景
エムスリーでは、github-aws-runners/terraform-aws-github-runnerを用いたSelf-hosted Runnerを用意しGitHub Actionsにおいて全社的に利用しています。Self-hosted Runnerを採用している背景については以下のブログを参照していただければと思います。
このSelf-hosted RunnerはLambdaやSQS, EC2といったAWS上のコンポーネントで構成されています。
一方で、エムスリーのAIチームで動かしているサービスは基本的にGCP上にあります。その上、AIチームが使用しているDockerイメージにはPyTorchといった機械学習系のライブラリをDockerイメージに含んでいることがあり数GBのイメージも存在します。
このような重いイメージをAWSとGCPの間で転送する場合、DockerイメージのPull, Push時間が支配的になっているJobについてはクラウド間のネットワーク転送時間がボトルネックとなっていました。このような背景から、AIチーム内においてはGCP上でRunnerを動かすことで、よりDockerイメージのPullやPushを行う際に従来よりも広い帯域幅を使うことができCI及びCDの時間を短縮できると考えました。
Kubernetesの利用
従来のエムスリーでのRunner基盤はEC2を用いたVMベースの構成になっていました。KubernetesでRunner基盤を構築する場合、AIチームに限らず全社的にメリットがあると考えSREチームに対して提案を行いました。例えば、以下のようなメリットが考えられます:
- GitHub OrgごとのRunner使用数といったカスタムの指標を見ることができるようになる
- Runnerと同じ環境をローカルで作ることができる
- 1Nodeの中に複数のPodを同居させればコストパフォーマンスが良い
- Nodeに空きがあれば、スケールアウトが早い
AIチーム内におけるCI/CDの実行時間改善に限らず全社的なメリットが見込まれるため、最終的には横展開も見据えて本タスクに取り組むことにしました。
GKE+ARCによるRunner基盤構築
ARCとは、GitHub Actionsで利用するRunnerに関する調整やスケーリングを行うカスタムコントローラーです。この仕組みを使うことでKubernetes上でGitHub Actions用のRunnerを構築することができます。
なお、CI実行時にコンテナを起動したい場合があると思います。この時、Kubernetes+ARCの構成ではRunnerはPodとして動作しますが、Pod内のRunnerコンテナにおいてDockerコンテナを起動できるようにする必要があります。これをDinD(Docker in Docker)と言います。
ARCではDinDのサポートがされていますが、注意点の一つとして特権コンテナが必要になるという点があります。また、GKEで動かす場合Autopilotクラスタは特権コンテナが使えないため、Standardクラスタ等を使う必要がある点に注意が必要です。
GKE Runnerでの工夫
カスタムイメージの利用
EC2を用いて全社的にRunner基盤を提供する場合、CI時に最低限どのOrgでも必要なコマンドのみ事前にAMIに入れるようになっています。そのため、gcloudといった基本的にAIチームのみ使うようなコマンドはGitHub Actions上でセットアップする必要がありました。
そこで、Runner用のコンテナをOrgごとに分け、OrgごとのRunnerイメージに予め必要なコマンドを入れておけばCI時にセットアップする必要がなくなり、時間短縮に繋がります。
Node Poolの自動作成
AIチームでは、GKE Standardクラスタを構築する際に多くのNode Poolを作成し管理していました。この管理工数を削減するため、ノードプールの自動作成をGKE Runnerで導入することにしました。ここでの構成について簡単に紹介します。
GKEクラスタをTerraformで構築する際に、クラスタの自動スケーリングを有効化しNode Poolの自動作成が行われるようにします。ここで、Nodeをスケールアウトする必要がある場合にGCPが自動でNode Poolを作成するようになるため我々がNode Poolを明示的に作成する必要はありません。
しかし、ここで注意したい点の一つとして、GCPが新たに作成するNode Poolは基本的に最もコストの安いインスタンス(e2など)を起動するということです。ただ、我々はGKE Runnerにおいてなるべく早くCIを終わらせたいという思いがあります。そのため、よりパフォーマンスが良いインスタンスでNodeを起動しその分早くCIを終わらせたいと考えています。
私の調べた範囲だと、Compute Classを設定することでNode Poolを自動で作成しつつ我々の使用したいインスタンスを優先順位付けした形で定義できるということがわかりました。Spotインスタンスを利用するか否かという点も定義することができ、祝日等にRunnerが稼働しないような日には自動でNodeがスケールインするため、GKE Runnerとは相性が良いと考えました。
例えば、以下のようにしてn2dのSpotインスタンスを最優先で起動するようにし、Spotが枯渇している場合にはn2のSpotインスタンスの起動にフォールバックする、というルールを記述することができます。
apiVersion: cloud.google.com/v1 kind: ComputeClass metadata: name: computeclass-large spec: priorities: - machineType: n2d-standard-16 spot: true - machineType: n2-standard-16 spot: true whenUnsatisfiable: ScaleUpAnyway nodePoolAutoCreation: enabled: true
ここまでの設定で、Standardクラスタを使用しながらもGKE RunnerのNode Poolの管理をGCP側に任せ、かつ我々の使いたいインスタンスの希望が反映された状態を実現することができました。
リポジトリに応じたRunnerスペックの選定
元々、特定のRunnerインスタンスを任意のリポジトリのCI/CD実行時に起動していました。
しかし、CIでRunnerが消費するリソース使用量を計測したところ、リポジトリごとに大きくが異なることがわかりました。そのため、一律のRunnerのスペックを使用するのではなくリポジトリごとにカスタマイズできた方がより効率的にリソースを利用することができると考えました。
ARCでは、Runner Groupsという単位の中で固有のrunnerScaleSetNameを指定することができます。GitHub Actions上でRunnerを呼び出す際には、ここで指定したrunnerScaleSetNameの名前を指定することになります。
そこで、Runner Groupをリソース使用量大・中・小といった形で用意し、その上でGitHub Actions上でrunner-largeであったりrunner-smallといった形でRunnerを呼び出すと良いのではないかといったことを提案しました。その上で、上述したCompute ClassをNode Selectorを指定することでActions上で指定したRunner名に応じて起動したいインスタンスを変えることができ、より柔軟な運用ができるようになると考えています。
runner-large用のhelm valuesファイル例
githubConfigUrl: "https://github.com/m3dev" runnerScaleSetName: "runner-large" runnerGroup: "large-resource-group" template: spec: nodeSelector: cloud.google.com/compute-class: computeclass-large containers: - name: runner image: ghcr.io/actions/actions-runner:latest command: ["/home/runner/run.sh"] resources: limits: cpu: "8" memory: "16Gi" requests: cpu: "4" memory: "8Gi"
runner-small用のhelm valuesファイル例
githubConfigUrl: "https://github.com/m3dev" runnerScaleSetName: "runner-small" runnerGroup: "small-resource-group" template: spec: nodeSelector: cloud.google.com/compute-class: computeclass-small containers: - name: runner image: ghcr.io/actions/actions-runner:latest command: ["/home/runner/run.sh"] resources: limits: cpu: "1" memory: "2Gi" requests: cpu: "500m" memory: "1Gi"
今後の展望
Runner起動時間の短縮化
Runnerが起動するまでの時間を短くするために以下のような仕組みが必要だと考えています。
Runner Podが既存のNodeにスケジューリングできる場合、Runnerはすぐに起動する一方でNodeをスケールアウトする必要がある場合、Nodeの起動を待つ必要があるため1~2分待機してしまいます。そのため、Runner用Node数の最適化が出来ればと考えています。例えば、時間ベースのNodeのスケールイン・アウトの仕組みを導入するといったことです。
Runnerリソース自動割り当て
また、Runnerのリソース割り当てについて、リポジトリごとに過去のCI/CDで使ったリソース量をもとに自動でRunnerのリソース割り当てを行う仕組みがあるとリソース効率を最大限活かすことができ理想的だと考えています。
全社展開の検討(私の反省点も含めて)
長期的な視点で見たときには、AIチームで安定した運用実績を作った上でk8s Runnerを全社展開するといった流れができれば理想的であると考えています。しかし、それを行うためには全社的にk8s Runnerに移行するためのROIを考える必要があり、インターン期間中に考慮しておくべきだったと考えています。
例えば、従来は一つのVMインスタンス上でRunnerを動かしていたもののk8sを利用する場合は一つのNode上で複数Podを動かすことができるためその分コストパフォーマンスが良く、その効果を試算するといったことが挙げられます。
今回のインターン期間中に全社展開も視野に入れていましたが、その際の説得材料の一つとして定量的なメリットを示すことが出来ればより納得感のある提案ができたはずです。
このように、技術刷新をする際において新たに発生するメリット及びデメリットを開発者・基盤運用者・経営陣それぞれに対して説明できるようにするための努力はもっと出来たと思っておりその点を反省点としつつ、個人的にインターン中に得られた大きな学びの一つとなりました。
インターンの感想
期間中の流れ
初日と最終日はオフラインで他の日程についてはオンラインで業務を行いました。エムスリーではSlack等でのコミュニケーションが非常に活発でオンラインであってもコミュニケーションの機会が多かった点が非常に印象的でした。様々な方が質問や相談に対して親身になって一緒に考えてくださり、オンラインでの業務に不安は全くありませんでした。
中間発表まで
既存のRunnerの構成や起動時間を整理し、Kubernetesを利用するメリット/デメリットをSREの方とも擦り合わせた上で、Runner用のGKEクラスタの構築を行いました。
最終発表まで
インターン後半では実際にARCを導入しパフォーマンスが改善されたかについて計測を行いました。
最終日前日にTech Talkが開催されていたため、ギリギリのタイミングで無理を言って発表の機会を頂き、GKE Runnerの話について共有することができました。Tech TalkでもZoomのコメント欄が大盛り上がりしていたことが印象的でした。
なお、Tech Talkについては例えば以下の記事を参照して頂くと、雰囲気が分かるかと思います!
チームの雰囲気
論文読み会であったり技術共有会、Tech Talkなど、技術に関するインプット及びアウトプットの機会が非常に多くびっくりしたことを覚えています。ギークな方が揃っており日々のチーム内の勉強会だけでも多くの知見が得られる環境だと感じました。 また、ギークなだけではなく、どのように技術を事業に活かすかといったことを皆さんが考えていることが同時に印象的でした。
業務でのClaude Code使い放題
地味に嬉しかったことです。特に申請等をする必要がなく、インターン生であっても簡単に使うことができたため驚きました。
コーディングに限らず調査等にもかなり使うことができ、間違いなく生産性を上げてくれたツールだったと感じています。
最後に
Self-hosted Runner自体私が初めて触るものであったということもあり、初日はやや不安なところもありました。 しかし、メンターの北川さん、横本さんを始めAIチーム・SREチームの多くの方たちのサポートもあり最終的には成果を出すことができ良かったです。 エムスリーでの2週間は非常に濃密でかつあっという間な時間でした。間違いなく自分を大きく成長させてくれたインターンであり、参加して良かったと感じています。