Skip to content
Published on

GitHub Actions Self-Hosted Runner 大規模運用とセキュリティハードニングガイド

Authors
  • Name
    Twitter
GitHub Actions Self-Hosted Runner 大規模運用とセキュリティハードニングガイド

なぜSelf-Hosted Runnerなのか

GitHub-hostedランナーは素早く始められるが、組織の規模が大きくなると限界にぶつかる。ビルド時間が30分を超え、GPUが必要になり、内部ネットワークリソースにアクセスする必要があり、コストが月数十万円を超え始めたら、self-hostedランナーの導入を検討すべきだ。

2026年3月からGitHubはself-hostedランナーに対しても分あたり$0.002のコントロールプレーン費用を課金し始めた(パブリックリポジトリとGitHub Enterprise Serverの顧客は除外)。しかし大規模組織では依然としてGitHub-hostedランナーと比較して60〜80%のコスト削減が可能であり、何よりインフラカスタマイズの自由度が圧倒的だ。

Self-hostedランナーを導入すると以下が可能になる:

  • 内部ネットワークのプライベートレジストリ、データベース、シークレットマネージャーへの直接アクセス
  • GPU、ARM、Apple Siliconなどの特殊ハードウェアでのビルドとテスト
  • ビルドキャッシュをローカルストレージに維持し、依存関係のインストール時間を90%以上短縮
  • 組織のセキュリティポリシーに準拠したネットワーク分離と監査ログの実装

GitHub-Hosted vs Self-Hosted vs ARC 比較

ランナーの選択はチーム規模、セキュリティ要件、運用能力によって異なる。以下の比較表を判断基準として活用する。

項目GitHub-HostedSelf-Hosted(VM)ARC(Kubernetes)
初期設定の難易度なし中程度高い
オートスケーリング自動自前で実装が必要ネイティブ対応
コスト(月1000時間基準)約$480(Linux 2コア)EC2費用+運用人件費K8sクラスター費用+運用
ビルドキャッシュ10GB制限、Azure Blobローカルディスク無制限PVCまたはS3
内部ネットワークアクセス不可可能可能
セキュリティ分離GitHub管理手動ハードニングPod単位の分離
GPUサポート限定的(ラージランナー)完全対応NVIDIA Device Plugin
最大同時ランナー数プランにより制限インフラの限界クラスターノードの限界
メンテナンス負荷なし高い(OSパッチ、バージョン管理)中程度(Helmアップグレード)
Ephemeralサポートデフォルト--ephemeralフラグデフォルト

判断基準:月間CI/CD時間が500時間以下で内部ネットワークアクセスが不要ならGitHub-hostedが合理的。500〜2000時間でK8s運用能力がなければVMベースのself-hostedを推奨。2000時間以上またはK8sクラスターがすでにあればARCが最善だ。

ARC(Actions Runner Controller)アーキテクチャ

Actions Runner ControllerはGitHubが公式に管理するKubernetesオペレーターだ。コミュニティプロジェクトとして始まったが、2023年からGitHubが直接開発し、Runner Scale Setsという新しいアーキテクチャに進化した。レガシーモードのwebhookベースのオートスケーリングとは異なり、Runner Scale SetsはGitHub APIと直接通信し、ジョブキューをリアルタイムで検知する。

ARCの動作原理

┌─────────────────┐     ┌──────────────────────┐
GitHub.com    │     │   Kubernetes Cluster│                 │     │                       │
Job Queue      │◄───►│  ARC Controller  (workflow_job)  │     │    │                  │
│                 │     │    ▼                  │
Scale Set API  │◄───►│  ScaleSet Listener│                 │     │    │                  │
└─────────────────┘     │    ▼                  │
EphemeralRunnerSet                        │    │                  │
                        │    ├─► Runner Pod 1                        │    ├─► Runner Pod 2                        │    └─► Runner Pod N                        └──────────────────────┘
  1. ScaleSet ListenerがGitHubのジョブキューをLong Polling方式で監視する
  2. Job Availableメッセージを受信すると、現在のランナー数とmaxRunners設定を比較する
  3. スケールアップが可能であればメッセージをACKし、Kubernetes APIを通じてEphemeralRunnerSetのreplica数をパッチする
  4. 新しいRunner Podが作成され、JIT(Just-In-Time)トークンでGitHubに登録される
  5. ジョブ実行が完了するとPodは即座に削除される(ephemeralのデフォルト動作)

ARCのインストールと構成

前提条件

  • Kubernetes 1.27以上
  • Helm 3.x
  • GitHub AppまたはPersonal Access Token(orgレベルadmin:org、repoレベルrepoスコープ)
  • cert-manager(TLS証明書の自動管理用、オプション)

Step 1:Controllerのインストール

# ARCコントローラー用の名前空間を作成
kubectl create namespace arc-systems

# Helmリポジトリの追加
helm install arc \
  --namespace arc-systems \
  oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller \
  --version 0.10.1

Step 2:GitHub App認証の設定

Personal Access TokenよりもGitHub App認証を強く推奨する。PATはユーザーに紐づくため退社時に問題が発生し、権限の範囲が広い。GitHub Appは組織レベルで管理され、必要最小限の権限のみ付与できる。

# GitHub Appシークレットの作成
kubectl create secret generic github-app-secret \
  --namespace arc-runners \
  --from-literal=github_app_id=12345 \
  --from-literal=github_app_installation_id=67890 \
  --from-file=github_app_private_key=./private-key.pem

Step 3:Runner Scale Setのデプロイ

# values.yaml - Runner Scale Set設定
githubConfigUrl: 'https://github.com/my-org'
githubConfigSecret: github-app-secret

# オートスケーリング設定
minRunners: 2 # 最小待機ランナー(コールドスタート防止)
maxRunners: 30 # 最大ランナー数(クラスターリソースを考慮)

# ランナーグループの指定(Enterprise/Orgレベル)
runnerGroup: 'production-runners'

# コンテナモード設定
containerMode:
  type: 'kubernetes'
  kubernetesModeWorkVolumeClaim:
    accessModes: ['ReadWriteOnce']
    storageClassName: 'gp3'
    resources:
      requests:
        storage: 50Gi

# Podテンプレートのカスタマイズ
template:
  spec:
    containers:
      - name: runner
        image: ghcr.io/actions/actions-runner:latest
        resources:
          requests:
            cpu: '2'
            memory: '4Gi'
          limits:
            cpu: '4'
            memory: '8Gi'
        env:
          - name: RUNNER_GRACEFUL_STOP_TIMEOUT
            value: '60'
    # ノード選択(ビルド専用ノードプール)
    nodeSelector:
      workload-type: ci-runner
    tolerations:
      - key: 'ci-runner'
        operator: 'Equal'
        value: 'true'
        effect: 'NoSchedule'
# Runner Scale Setのデプロイ
helm install arc-runner-set \
  --namespace arc-runners \
  --create-namespace \
  -f values.yaml \
  oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set \
  --version 0.10.1

カスタムRunnerイメージのビルド

デフォルトのRunnerイメージにはビルドツールが含まれていない。組織で使用するツールを事前にインストールしたカスタムイメージをビルドすることで、ワークフロー実行時間を大幅に短縮できる。

Dockerfile作成の原則

  • ベースイメージはできるだけslimバリアントを使用する
  • Runnerバイナリバージョンはv2.329.0以上を使用する(2026年3月16日から以前のバージョンは登録がブロックされる)
  • 不要なパッケージのインストールを避け、マルチステージビルドでイメージサイズを最小化する
  • rootではなく専用ユーザーでRunnerプロセスを実行する
# Dockerfile.runner - カスタムGitHub Actions Runnerイメージ
FROM ubuntu:22.04 AS base

# システムパッケージのインストール(最小限に維持)
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    ca-certificates \
    git \
    jq \
    unzip \
    zip \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

# Runner専用ユーザーの作成
RUN useradd -m -d /home/runner -s /bin/bash runner

# Runnerバイナリのインストール
ARG RUNNER_VERSION=2.321.0
RUN curl -fsSL -o runner.tar.gz \
    "https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz" \
    && mkdir -p /home/runner/actions-runner \
    && tar xzf runner.tar.gz -C /home/runner/actions-runner \
    && rm runner.tar.gz \
    && /home/runner/actions-runner/bin/installdependencies.sh

# Node.js 22 LTSのインストール
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
    && apt-get install -y nodejs \
    && rm -rf /var/lib/apt/lists/*

# Docker CLIのインストール(DinDではなくCLIのみ)
RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker.gpg \
    && echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu jammy stable" \
    > /etc/apt/sources.list.d/docker.list \
    && apt-get update && apt-get install -y docker-ce-cli \
    && rm -rf /var/lib/apt/lists/*

# 自動更新の無効化(イメージビルドでバージョン管理)
ENV RUNNER_MANUALLY_TRAP_SIG=1
ENV ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT=1

# 権限設定とユーザー切替
RUN chown -R runner:runner /home/runner
USER runner
WORKDIR /home/runner/actions-runner

ENTRYPOINT ["./run.sh"]
# イメージのビルドとプッシュ
docker build -t ghcr.io/my-org/actions-runner:v2.321.0-custom -f Dockerfile.runner .
docker push ghcr.io/my-org/actions-runner:v2.321.0-custom

注意:Runnerの自動更新を無効化しているため、新しいRunnerバージョンがリリースされたらイメージを再ビルドし、ARCのRunner Scale Setイメージタグを更新する必要がある。GitHubは最小バージョン要件を定期的に引き上げるため、リリースノートを監視すること。

セキュリティハードニング

Self-hostedランナーは組織のインフラ上で外部コードを実行する。ハードニングなしで運用すると、サプライチェーン攻撃、シークレット漏洩、ネットワーク侵入の経路となる。

Ephemeral Runnerの必須化

Persistentランナーでは、以前のジョブのファイル、環境変数、プロセスが次のジョブに影響を与える可能性がある。攻撃者が悪意あるワークフローを通じてランナーにバックドアをインストールすると、以降のすべてのジョブが汚染される。Ephemeralランナーはジョブ完了後に即座に破棄されるため、このリスクを根本的に遮断する。

# ワークフローでephemeralランナーの使用を確認
runs-on: arc-runner-set # ARCはデフォルトでephemeral

# VMベースのself-hostedランナーの場合
# ./config.sh --ephemeralフラグで登録

ネットワーク分離

Runner Podはインターネットと内部ネットワークの両方にアクセスできるため、NetworkPolicyで必要なトラフィックのみを許可する必要がある。

# network-policy.yaml - Runner Podネットワーク制限
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: runner-network-policy
  namespace: arc-runners
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/component: runner
  policyTypes:
    - Egress
    - Ingress
  ingress: [] # 外部からRunnerへのインバウンドを遮断
  egress:
    # GitHub APIとActionsサービス
    - to:
        - ipBlock:
            cidr: 140.82.112.0/20 # github.com
        - ipBlock:
            cidr: 185.199.108.0/22 # GitHub Pages/CDN
      ports:
        - protocol: TCP
          port: 443
    # 内部コンテナレジストリ
    - to:
        - namespaceSelector:
            matchLabels:
              name: registry
      ports:
        - protocol: TCP
          port: 5000
    # DNS
    - to: []
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

RBAC最小権限の原則

Runner PodのServiceAccountには最小限の権限のみを付与する。特にKubernetes APIへのアクセスを制限する必要がある。

# rbac.yaml - Runner ServiceAccount最小権限
apiVersion: v1
kind: ServiceAccount
metadata:
  name: runner-sa
  namespace: arc-runners
automountServiceAccountToken: false # K8s APIトークンの自動マウントを無効化
---
# 必要な場合にのみ最小権限のRoleをバインド
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: runner-minimal
  namespace: arc-runners
rules: [] # デフォルトで権限なし

ワークフローレベルのセキュリティ

# 安全なワークフロー作成パターン
name: Secure CI Pipeline
on:
  pull_request:
    branches: [main]

# 最小権限トークン
permissions:
  contents: read
  packages: read

jobs:
  build:
    runs-on: arc-runner-set
    steps:
      # SHA固定でActionを使用(タグではない)
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      # 環境変数にシークレットを直接公開しない
      - name: Build
        run: |
          echo "Building..."
        env:
          # シークレットは必要なステップでのみ注入
          REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}

Harden-Runnerを活用したランタイムセキュリティ

StepSecurityのHarden-RunnerはGitHub Actions専用のEDR(Endpoint Detection and Response)で、ネットワークエグレスモニタリング、ファイル整合性チェック、プロセスアクティビティ追跡を提供する。

jobs:
  build:
    runs-on: arc-runner-set
    steps:
      - uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
        with:
          egress-policy: audit # まずauditでトラフィックパターンを把握
          # パターン把握後にblockに切替
          # egress-policy: block
          # allowed-endpoints: >
          #   github.com:443
          #   registry.npmjs.org:443
          #   ghcr.io:443

パブリックリポジトリでの注意事項

パブリックリポジトリでは絶対にself-hostedランナーを使用してはならない。外部の攻撃者がFork PRを通じて任意のコードをランナー上で実行できる。pull_request_targetイベントとself-hostedランナーの組み合わせは特に危険だ。必ずプライベートリポジトリまたは組織内部のリポジトリでのみ使用すること。

キャッシュ戦略

Ephemeralランナーの最大の欠点は、ジョブごとにキャッシュが消えることだ。効率的なキャッシュ戦略がなければ、毎回のビルドで依存関係のダウンロードから始めなければならない。

戦略1:PersistentVolumeClaim(PVC)ベースのキャッシュ

# ARC Runner Scale Set values.yamlにPVCを追加
template:
  spec:
    containers:
      - name: runner
        image: ghcr.io/my-org/actions-runner:latest
        volumeMounts:
          - name: cache-volume
            mountPath: /opt/cache
        env:
          - name: RUNNER_TOOL_CACHE
            value: /opt/cache/tool-cache
          - name: npm_config_cache
            value: /opt/cache/npm
          - name: GOPATH
            value: /opt/cache/go
    volumes:
      - name: cache-volume
        persistentVolumeClaim:
          claimName: runner-cache-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: runner-cache-pvc
  namespace: arc-runners
spec:
  accessModes:
    - ReadWriteMany # 複数のRunner Podが同時アクセス
  storageClassName: efs # AWS EFSまたはNFS
  resources:
    requests:
      storage: 100Gi

注意ReadWriteManyモードはEFS、NFS、GlusterFSなどのネットワークファイルシステムが必要だ。EBSのようなブロックストレージはReadWriteOnceのみサポートし、一度に1つのPodのみアクセス可能だ。

戦略2:S3互換キャッシュサーバー

GitHubのデフォルトキャッシュ(actions/cache)はAzure Blob Storageを使用する。AWS環境ではリージョン間の遅延が発生する。S3互換のキャッシュサーバーをself-hostedで運用すればネットワーク遅延を最小化できる。

# MinIOベースのキャッシュサーバーデプロイ(同じVPC/リージョン内)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: actions-cache-server
  namespace: arc-systems
spec:
  replicas: 1
  selector:
    matchLabels:
      app: actions-cache
  template:
    spec:
      containers:
        - name: minio
          image: minio/minio:latest
          args: ['server', '/data', '--console-address', ':9001']
          env:
            - name: MINIO_ROOT_USER
              valueFrom:
                secretKeyRef:
                  name: minio-credentials
                  key: user
            - name: MINIO_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: minio-credentials
                  key: password
          volumeMounts:
            - name: data
              mountPath: /data
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: minio-data-pvc

戦略3:Dockerレイヤーキャッシュ(BuildKit)

コンテナイメージのビルドがCIの主要ワークロードなら、BuildKitのキャッシュバックエンドをレジストリに設定してレイヤーキャッシュを共有する。

# ワークフローでBuildKitレジストリキャッシュを活用
- name: Build and Push
  uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6
  with:
    push: true
    tags: ghcr.io/my-org/my-app:${{ github.sha }}
    cache-from: type=registry,ref=ghcr.io/my-org/my-app:buildcache
    cache-to: type=registry,ref=ghcr.io/my-org/my-app:buildcache,mode=max

モニタリングと可観測性

Self-hostedランナーを運用していて最も多い質問は「ランナーがなぜ起動しないのか」だ。モニタリングなしでは答えを出せない。

Prometheus + Grafanaメトリクス

ARCはデフォルトでPrometheusメトリクスを公開する。重要なメトリクスは以下の通り:

  • gha_runner_scale_set_desired_replicas:現在要求されているランナー数
  • gha_runner_scale_set_running_replicas:実行中のランナー数
  • gha_runner_scale_set_registered_replicas:GitHubに登録完了したランナー数
  • gha_runner_scale_set_idle_replicas:アイドルランナー数
# Prometheus ServiceMonitor設定
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: arc-controller-monitor
  namespace: arc-systems
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: gha-runner-scale-set-controller
  endpoints:
    - port: metrics
      interval: 30s
      path: /metrics

重要なアラートルール

# Alertmanagerルール
groups:
  - name: arc-runner-alerts
    rules:
      # ランナープール枯渇の警告
      - alert: RunnerPoolExhausted
        expr: |
          gha_runner_scale_set_desired_replicas
          >= gha_runner_scale_set_max_replicas * 0.9
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: 'ランナープールが90%以上使用中'
          description: 'maxRunnersの増設またはワークフローの最適化が必要'

      # ランナー登録失敗の検知
      - alert: RunnerRegistrationFailed
        expr: |
          rate(gha_runner_scale_set_registration_failures_total[5m]) > 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: 'ランナー登録失敗が発生'
          description: 'GitHub App認証またはネットワークの確認が必要'

      # Pending Pod長時間待機
      - alert: RunnerPodPending
        expr: |
          kube_pod_status_phase{namespace="arc-runners", phase="Pending"} > 0
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: 'Runner Podが10分以上Pending状態'
          description: 'ノードリソース不足またはPVCバインディング失敗の可能性'

障害ケースとリカバリ手順

障害1:ScaleSet Listener CrashLoopBackOff

症状:Listener Podが繰り返し再起動し、ランナーが全くスケールアップしない。

原因分析の順序

# 1. Listener Podのログ確認
kubectl logs -n arc-systems -l app.kubernetes.io/component=runner-scale-set-listener --tail=100

# 2. よくある原因:GitHub App認証の期限切れ
# - 秘密鍵ファイルの確認
# - Appインストレーションの状態確認(org settings > GitHub Apps)

# 3. ネットワーク問題:GitHub APIにアクセスできない
kubectl exec -n arc-systems deploy/arc-gha-runner-scale-set-controller -- \
  curl -s https://api.github.com/meta | jq '.actions[]'

復旧:GitHub Appの秘密鍵を更新し、シークレットを更新する。

kubectl create secret generic github-app-secret \
  --namespace arc-runners \
  --from-literal=github_app_id=12345 \
  --from-literal=github_app_installation_id=67890 \
  --from-file=github_app_private_key=./new-private-key.pem \
  --dry-run=client -o yaml | kubectl apply -f -

# Controllerの再起動
kubectl rollout restart deployment -n arc-systems arc-gha-runner-scale-set-controller

障害2:Runner PodがPending状態で停止

症状:ジョブはキューに溜まるが、Runner Podが作成されないかPending状態のままになる。

# Podイベントの確認
kubectl describe pod -n arc-runners -l actions.github.com/scale-set-name=arc-runner-set

# よくある原因別の対応
# 1. ノードリソース不足
kubectl top nodes
# -> Cluster Autoscalerが動作しているか確認、またはmaxRunnersを下方修正

# 2. PVCバインディング待ち
kubectl get pvc -n arc-runners
# -> StorageClass設定、アベイラビリティゾーンの不一致を確認

# 3. イメージPull失敗
kubectl get events -n arc-runners --sort-by='.lastTimestamp' | grep -i pull
# -> イメージタグ、レジストリ認証を確認

障害3:ジョブがRunnerに割り当てられない

症状:GitHub UIでジョブが「Queued」状態で無期限に待機する。

# Runner登録状態の確認
kubectl get ephemeralrunner -n arc-runners

# Runnerラベルの確認(runs-onと一致する必要がある)
kubectl get autoscalingrunnersets -n arc-runners -o yaml | grep -A5 labels

# GitHubでRunnerグループ設定の確認
# Settings > Actions > Runner groups > 該当グループにリポジトリが含まれているか確認

復旧:ワークフローのruns-onラベルがARC Runner Scale Setの名前と正確に一致しているか確認する。Runnerグループが設定されている場合、該当リポジトリがグループに含まれているかも検証する。

障害4:Runnerバージョン互換性の問題

2026年3月16日からv2.329.0未満のRunnerの登録がブロックされる。カスタムイメージを使用中なら必ずRunnerバージョンを確認すること。

# 現在のRunnerバージョンの確認
kubectl exec -n arc-runners -it <runner-pod> -- ./config.sh --version

# イメージの更新(values.yaml修正後)
helm upgrade arc-runner-set \
  --namespace arc-runners \
  -f values.yaml \
  oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set

大規模運用の最適化

ランナーグループ分離戦略

ワークロード特性に応じてRunner Scale Setを分離運用する。単一のScale Setですべてのワークロードを処理すると、リソース競合とノイジーネイバー問題が発生する。

# 用途別Runner Scale Setの分離
# 1. 一般CI(軽量テスト、リント)
# values-ci-light.yaml
minRunners: 2
maxRunners: 20
template:
  spec:
    containers:
      - name: runner
        resources:
          requests:
            cpu: "1"
            memory: "2Gi"

# 2. ビルド専用(コンパイル、Dockerビルド)
# values-ci-build.yaml
minRunners: 1
maxRunners: 10
template:
  spec:
    containers:
      - name: runner
        resources:
          requests:
            cpu: "4"
            memory: "8Gi"

# 3. GPUワークロード(MLモデルテスト)
# values-gpu.yaml
minRunners: 0
maxRunners: 4
template:
  spec:
    containers:
      - name: runner
        resources:
          limits:
            nvidia.com/gpu: 1
    nodeSelector:
      accelerator: nvidia-a10g

Graceful Shutdownの処理

ランナーがジョブを実行中にノードドレインやスケールダウンが発生するとジョブが失敗する。RUNNER_GRACEFUL_STOP_TIMEOUTを設定して、進行中のジョブが完了するまで待機するようにする。

template:
  spec:
    terminationGracePeriodSeconds: 3600 # 最大1時間待機
    containers:
      - name: runner
        env:
          - name: RUNNER_GRACEFUL_STOP_TIMEOUT
            value: '3500' # terminationGracePeriodSecondsより少し短く

ノードオートスケーラーとの連携

ARCがRunner Podを作成してもノードが不足するとPodはPending状態のままになる。Cluster AutoscalerまたはKarpenterを併せて構成する。

# Karpenter NodePool例(CIランナー専用)
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: ci-runners
spec:
  template:
    metadata:
      labels:
        workload-type: ci-runner
    spec:
      taints:
        - key: ci-runner
          value: 'true'
          effect: NoSchedule
      requirements:
        - key: kubernetes.io/arch
          operator: In
          values: ['amd64']
        - key: karpenter.sh/capacity-type
          operator: In
          values: ['on-demand', 'spot']
        - key: node.kubernetes.io/instance-type
          operator: In
          values: ['m7i.xlarge', 'm7i.2xlarge', 'm6i.xlarge', 'm6i.2xlarge']
  limits:
    cpu: 200
    memory: 400Gi
  disruption:
    consolidationPolicy: WhenEmpty
    consolidateAfter: 60s

Spotインスタンスを活用すれば、さらに50〜70%のコスト削減が可能だ。ただし、Spotインタラプション時にジョブが失敗する可能性があるため、重要度の低いCIワークロードにのみ適用すること。本番デプロイパイプラインにはOn-Demandインスタンスを使用する。

運用チェックリスト

Self-hostedランナーの導入前後で以下のチェックリストを点検する。

初期構築チェックリスト

  • GitHub App認証の構成完了(PATではなくGitHub Appを使用)
  • Runnerイメージに必要なツールの事前インストール完了
  • Runnerバージョンv2.329.0以上の確認
  • Ephemeralモードの有効化確認
  • NetworkPolicyの適用(最小限のエグレスのみ許可)
  • ServiceAccountにautomountServiceAccountToken: falseを設定
  • Runner Podにリソースrequests/limitsを設定
  • ノードセレクター(nodeSelector)またはTaint/Tolerationでビルドノードを分離
  • キャッシュ戦略の決定と実装(PVC、S3、レジストリキャッシュ)

セキュリティハードニングチェックリスト

  • パブリックリポジトリでのself-hostedランナー使用をブロック
  • ランナーグループでリポジトリアクセス範囲を制限
  • ワークフローでpermissions最小権限を宣言
  • Action参照時にcommit SHAを固定(タグではなく)
  • Dockerソケットマウントの禁止(コンテナモードを使用)
  • シークレットスキャニングおよび漏洩防止ツールの適用
  • RunnerホストOSのハードニング(不要なサービスの削除、ファイアウォール設定)
  • OIDCを活用した短期トークンベースのクラウド認証

運用モニタリングチェックリスト

  • Prometheusメトリクス収集とGrafanaダッシュボードの構成
  • ランナープール枯渇アラートの設定(maxRunnersの90%閾値)
  • ランナー登録失敗アラートの設定
  • Pod Pending長時間待機アラートの設定
  • Runnerバージョン更新アラート(GitHub Changelogを購読)
  • 月次セキュリティ監査スケジュールの確立(ネットワークポリシーレビュー、シークレットローテーション)

まとめ

Self-hostedランナーの運用は、単にVMやPodを立ち上げることではない。セキュリティ、スケーリング、キャッシュ、モニタリング、障害対応までを包括するプラットフォームエンジニアリング領域だ。ARCとRunner Scale Setsの登場によりKubernetes上での運用は大幅に安定化したが、結局は組織のワークロードに合わせたチューニングと継続的なモニタリングが必要だ。

ポイントを改めて整理すると:

  1. Ephemeralは選択ではなく必須だ。 セキュリティと再現性を同時に保証する。
  2. ARC Runner Scale Setsが現時点で最善のオートスケーリング方式だ。 レガシーのwebhookベースモードは使用しないこと。
  3. セキュリティハードニングをDay 0で適用せよ。 NetworkPolicy、RBAC、SHA固定、パブリックリポジトリのブロックは基本だ。
  4. キャッシュ戦略のないephemeralランナーは遅いランナーに過ぎない。 PVC、S3、レジストリキャッシュを必ず構成すること。
  5. モニタリングとアラートは運用の生命線だ。 ランナープールの枯渇と登録失敗を即座に検知できなければならない。

References

  1. GitHub Docs - Actions Runner Controller - ARC公式ドキュメントとアーキテクチャ説明
  2. GitHub Docs - Deploying Runner Scale Sets with ARC - Runner Scale Setデプロイチュートリアル
  3. GitHub Docs - Self-Hosted Runners - Self-hostedランナー公式ガイド
  4. GitHub Docs - Secure Use Reference - GitHub Actionsセキュリティ参照ドキュメント
  5. GitHub Actions Runner Controller Repository - ARCソースコードとHelm chart values参照
  6. AWS Blog - Best Practices for Self-Hosted Runners at Scale - AWS環境での大規模ランナー運用
  7. StepSecurity Harden-Runner - GitHub Actionsランタイムセキュリティモニタリングツール
  8. GitHub Blog - Self-Hosted Runner Minimum Version Enforcement - 2026年ランナー最小バージョン要件の変更