Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Actions Runner Controller Deep Dive

Junya Taniai
January 26, 2024
590

Actions Runner Controller Deep Dive

Junya Taniai

January 26, 2024
Tweet

Transcript

  1. ⾕合 純也(たにあい じゅんや) 福岡在住 • 所属:株式会社エーピーコミュニケーションズ Azure Container Solution事業部 Cloud

    Integrationチーム • 業務:GitHub Enterprise、 Microsoft Azureのコンテナ製品導入支援 • 趣味:Kubernetes Operator実装やコードリーディング、筋トレ jnytnai0613 Jnytnai0530 2
  2. はじめに Actions Runner Controller(以降ARC)の最新の機能であるAutoscaling Runner Scale Setsモードを、 紹介します。 なお、Autoscaling Runner

    Scale Sets登場以前のモードはレガシーモードとされていますので、 スコープ外とします。 actions/actions-runner-controller 今回は以下のブログを、登壇用としてまとめています。 Actions Runner Controller Deep Dive!- アーキテクチャ編 – Actions Runner Controller Deep Dive!- 動作解説編 - Actions Runner Controller Deep Dive!- コード解説 前編 - Actions Runner Controller Deep Dive!- コード解説 後編 - 4
  3. Agenda 1. アーキテクチャ紹介 2. 動作紹介 - 基本動作 - Runnerの最大/最小設定 -

    ARCでの、Workflow内Containerの使用 - Kubernetesモード 3. コードから見るARC - 事前知識 - GitHubとのロングポーリング - Runnerのスケール 5
  4. アーキテクチャ紹介 Controller • AutoScalingRunnerSet Controller EphemeralRunnerSetおよびAutoScalingListerner CustomResourceの作成および更新、削除 • EphemeralRunnerSet Controller

    EphemeralRunner CustomResourceの作成および更新、削除 • EphemeralRunner Controller EphemeralRunner Podの作成および削除 • AutoScalingListerner Controller AutoScalingListerner Podの作成および削除 Pod • Controller Pod 上記Controllerが動くmanager • EphemeralRunner Pod Self-hosted runnerとして動く • AutoScalingListerner Pod GitHubとのロングポーリングを確立し、GitHub Actions時にMessageを受信し、EphemeralRunnerSet の.Spec.Replicasを増減させる 6
  5. Agenda 1. アーキテクチャ紹介 2. 動作紹介 - 基本動作 - Runnerの最大/最小設定 -

    ARCでの、Workflow内Containerの使用 - Kubernetesモード 3. コードから見るARC - 事前知識 - GitHubとのロングポーリング - Runnerのスケール 9
  6. 動作紹介 - 基本動作 - $ INSTALLATION_NAME="arc-runner-set" $ NAMESPACE="arc-runners" $ GITHUB_CONFIG_URL="<リポジトリURL>"

    $ GITHUB_PAT="pre-defined-secret" $ helm install "${INSTALLATION_NAME}" ¥ --namespace "${NAMESPACE}" ¥ --set githubConfigUrl="${GITHUB_CONFIG_URL}" ¥ --set githubConfigSecret="${GITHUB_PAT}" ¥ oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set HelmでのAutoScalingRunnerSetリソースのデプロイコマンドは以下の通り # AutoScalingRunnerSet $ kubectl -n arc-runners get autoscalingrunnersets.actions.github.com NAME MINIMUM RUNNERS MAXIMUM RUNNERS CURRENT RUNNERS STATE PENDING RUNNERS RUNNING RUNNERS FINISHED RUNNERS DELETING RUNNERS arc-runner-set 0 0 0 # AutoScalingListner $ kubectl -n arc-systems get autoscalinglisteners.actions.github.com NAME GITHUB CONFIGURE URL AUTOSCALINGRUNNERSET NAMESPACE AUTOSCALINGRUNNERSET NAME arc-runner-set-754b578d-listener https://github.com/apc-jnytnai0613/arc-test arc-runners arc-runner-set # AutoScalingListener Pod $ kubectl -n arc-systems get po arc-runner-set-754b578d-listener NAME READY STATUS RESTARTS AGE arc-runner-set-754b578d-listener 1/1 Running 0 8m3s # EphemeralRunnerSet $ kubectl -n arc-runners get ephemeralrunnersets.actions.github.com NAME DESIREDREPLICAS CURRENTREPLICAS PENDING RUNNERS RUNNING RUNNERS FINISHED RUNNERS DELETING RUNNERS arc-runner-set-tcttw 0 0 0 各種リソースがデプロイされたことが確認できる 10
  7. 動作紹介 - 基本動作 - name: Test workflow on: workflow_dispatch: jobs:

    test1: runs-on: arc-runner-set steps: - name: Hello world run: echo "Hello" test2: runs-on: arc-runner-set steps: - name: Hello world run: echo "World" 以下のWorkflowを実行してみる 12
  8. 動作紹介 - 基本動作 - EphemeralRunnerSetを確認すると、Worklflowのジョブ数に応じて、EphemeralRunnerのスケールアウトからスケールインまで 実行されていることが確認できる $ kubectl -n arc-runners

    get ephemeralrunnersets.actions.github.com -w NAME DESIREDREPLICAS CURRENTREPLICAS PENDING RUNNERS RUNNING RUNNERS FINISHED RUNNERS DELETING RUNNERS arc-runner-set-tcttw 0 0 0 arc-runner-set-tcttw 2 0 0 0 arc-runner-set-tcttw 2 2 2 0 arc-runner-set-tcttw 2 2 1 1 arc-runner-set-tcttw 2 1 0 1 arc-runner-set-tcttw 2 1 1 1 arc-runner-set-tcttw 2 2 1 1 arc-runner-set-tcttw 2 2 1 1 arc-runner-set-tcttw 2 2 0 2 arc-runner-set-tcttw 2 0 2 arc-runner-set-tcttw 1 0 1 arc-runner-set-tcttw 0 0 0 スケールアウト スケールイン 13 スケールイン
  9. Agenda 1. アーキテクチャ紹介 2. 動作紹介 - 基本動作 - Runnerの最大/最小設定 -

    ARCでの、Workflow内Containerの使用 - Kubernetesモード 3. コードから見るARC - 事前知識 - GitHubとのロングポーリング - Runnerのスケール 15
  10. $ INSTALLATION_NAME="arc-runner-set" $ NAMESPACE="arc-runners" $ GITHUB_CONFIG_URL="https://github.com/apc-jnytnai0613/arc-test" $ GITHUB_PAT="pre-defined-secret" $ helm

    upgrade "${INSTALLATION_NAME}" ¥ --namespace "${NAMESPACE}" ¥ --set githubConfigUrl="${GITHUB_CONFIG_URL}" ¥ --set githubConfigSecret="${GITHUB_PAT}" ¥ --set maxRunners=5 ¥ --set minRunners=2 ¥ oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set HelmのオプションにmaxRunnersやminRunnersを設定することでRunnerに最大・最小設定を行うことが可能となる $ kubectl -n arc-runners get ephemeralrunnersets.actions.github.com -w NAME DESIREDREPLICAS CURRENTREPLICAS PENDING RUNNERS RUNNING RUNNERS FINISHED RUNNERS DELETING RUNNERS arc-runner-set-tcttw 2 2 0 2 EphemeralRunnerSetを確認すると、minRunnerで指定した数だけ、Runnerが起動しているのが見える。 動作紹介 - Runnerの最大/最小設定 - 16
  11. 動作紹介 - Runnerの最大/最小設定 - $ kubectl -n arc-runners get ephemeralrunnersets.actions.github.com

    -w NAME DESIREDREPLICAS CURRENTREPLICAS PENDING RUNNERS RUNNING RUNNERS FINISHED RUNNERS DELETING RUNNERS arc-runner-set-tcttw 2 2 0 2 arc-runner-set-tcttw 5 2 0 2 arc-runner-set-tcttw 5 5 3 2 arc-runner-set-tcttw 5 5 2 3 arc-runner-set-tcttw 5 5 1 4 arc-runner-set-tcttw 5 5 0 5 arc-runner-set-tcttw 5 4 0 4 arc-runner-set-tcttw 5 5 1 4 arc-runner-set-tcttw 5 4 1 3 arc-runner-set-tcttw 5 5 2 3 arc-runner-set-tcttw 5 5 1 4 arc-runner-set-tcttw 5 5 0 5 arc-runner-set-tcttw 4 5 0 5 arc-runner-set-tcttw 4 4 0 4 arc-runner-set-tcttw 4 3 0 3 arc-runner-set-tcttw 4 3 1 3 arc-runner-set-tcttw 4 4 1 3 : arc-runner-set-tcttw 2 1 1 0 arc-runner-set-tcttw 2 2 2 0 arc-runner-set-tcttw 2 2 1 1 arc-runner-set-tcttw 2 2 0 2 スケールアウト スケールイン Job数5のWorklflowを実行すると、EphemeralRunnerSetのreplicasが2→5に変化し、Workflow終了後は2に収束している ことが確認できます 17
  12. Agenda 1. アーキテクチャ紹介 2. 動作紹介 - 基本動作 - Runnerの最大/最小設定 -

    ARCでの、Workflow内Containerの使用 - Kubernetesモード 3. コードから見るARC - 事前知識 - GitHubとのロングポーリング - Runnerのスケール 18
  13. • Self-hosted runnerでは、runner内にdockerをあらかじめインストールしておく必要があります。 • Runnerコンテナの場合にWorkflowでコンテナを動かすには、docker.sockに接続してDocker Daemonと通信を行う必要があ りますが、ある工夫をしないと通信ができません。 ARCの場合は以下のエラーが発生しました。 ARCでの、Workflow内Containerの使用 Checking

    docker version /usr/bin/docker version --format '{{.Server.APIVersion}}’ Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? Error: Exit code 1 returned from process: file name '/usr/bin/docker', arguments 'version --format '{{.Server.APIVersion}}''. この問題を解決するに当たり、Workflowでコンテナを動かす場合は、以下の方法でdocker.sockと通信を行えます。 • ホスト上のdocker.sockをコンテナRunnerにマウントするDooD(Docker outside of Docker) • コンテナRunner内にさらにDockerを起動させるDinD(Docker in Docker) • Runner Container Hooks(actions/runner-container-hooks) の利用(ARCのRunner用コンテナイメージに組み込み 済) 参照:コンテナのセルフホストランナーの中でコンテナを使えるようにするrunner-container-hooks 19
  14. 動作紹介 - Kubernetesモード - Kubernetesモードは、helm用のvalues.yamlに以下の設定を記載します。 KubernetesモードではPVを/home/runner/_workディレクトリにマウントして、 Runner Podと、Workflowでコンテナを動かす用のJob Pod間で情報 を連携します。この時、ディレクトリに対して権限がない旨のエラーが発生するため、

    ドキュメントに従って、.spec.securityContext.fsGroupに コンテナのGIDを設定するか、以下のようにinitContainersを追加します。 $ cat values.yaml containerMode: type: "kubernetes" kubernetesModeWorkVolumeClaim: accessModes: ["ReadWriteOnce"] storageClassName: "managed" resources: requests: storage: 1Gi template: spec: initContainers: - name: kube-init image: ghcr.io/actions/actions-runner:latest command: ["sudo", "chown", "-R", "1001:1001", "/home/runner/_work"] volumeMounts: - name: work mountPath: /home/runner/_work containers: - name: runner image: ghcr.io/actions/actions-runner:latest command: ["/home/runner/run.sh"] env: - name: ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER value: "false" Kubernetesモード指定 PVC設定 Runner PodとJob Podの情報連携用ディレクトリ権限付与 20
  15. 動作紹介 - Kubernetesモード - $ INSTALLATION_NAME="arc-runner-set" $ NAMESPACE="arc-runners" $ GITHUB_CONFIG_URL="https://github.com/apc-jnytnai0613/arc-test"

    $ GITHUB_PAT="pre-defined-secret" $ helm upgrade "${INSTALLATION_NAME}" ¥ --namespace "${NAMESPACE}" ¥ --set githubConfigUrl="${GITHUB_CONFIG_URL}" ¥ --set githubConfigSecret="${GITHUB_PAT}" ¥ -f values.yaml ¥ oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set インストール後、Workflowを実行することで、以下のように、PVCとPVが自動で作成されます。 $ k -n arc-runners get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE arc-runner-set-8xxhb-runner-67dzd-work Bound pvc-4bb5fdd8-4137-4014-b42a-b55177a552bb 1Gi RWO managed 20m arc-runner-set-8xxhb-runner-h8fh7-work Bound pvc-6fe85296-323f-4441-b9c5-606454703619 1Gi RWO managed 44h $ k -n arc-runners get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS pvc-4bb5fdd8-4137-4014-b42a-b55177a552bb 1Gi RWO Delete Bound arc-runners/arc-runner-set-8xxhb-runner-67dzd-work managed pvc-6fe85296-323f-4441-b9c5-606454703619 1Gi RWO Delete Bound arc-runners/arc-runner-set-8xxhb-runner-h8fh7-work managed 21
  16. 動作紹介 - Kubernetesモード - function createPod(jobContainer, services, registry, extension) {

    var _a; return __awaiter(this, void 0, void 0, function () { var containers, appPod, instanceLabel, _b, claimName, secret, secretReference, body; var _c; return __generator(this, function (_d) { switch (_d.label) { : case 1: _b.nodeName = _d.sent(); claimName = (0, constants_1.getVolumeClaimName)(); appPod.spec.volumes = [ { name: 'work', persistentVolumeClaim: { claimName: claimName } } ]; if (!registry) return [3 /*break*/, 3]; return [4 /*yield*/, createDockerSecret(registry)]; : } }); }); } Workflow内でコンテナを実際に動かすのは、Runner Podではなく、Job Podです。 Job PodはARCが作成するのではなく、Runnerコンテナに組み込み済みのRunner Container Hooksが行います。 Runner Container HooksでJob Podを作成するコードは以下の通りで、values.yamlで指定したPVCを使用し、Volumeをマ ウントします。この時、workという名前のVolumeが使用され、ユーザは指定できませんでした。 22
  17. 動作紹介 - Kubernetesモード - ADR 0096: Hook extensions https://github.com/actions/runner-container-hooks/blob/main/docs/adrs/0096-hook-extensions.md 前述した通り、Runner

    Container Hookでは定義がガチガチに決まっており、ユーザが自由にVolumeの指定などができないよう になっていました。 そこで、最新のRunner Container Hook(0.4.0)から拡張が提供されました!! 拡張したいJob Pod定義をConfigMapリソース化して、Runner PodにACTIONS_RUNNER_CONTAINER_HOOK_TEMPLATE環 境変数として、読み込ませるだけで、拡張が可能になります。 これで、Volumeだけでなく、ServiceAccountなどのカスタムできるぞ! 23
  18. Agenda 1. アーキテクチャ紹介 2. 動作紹介 - 基本動作 - Runnerの最大/最小設定 -

    ARCでの、Workflow内Containerの使用 - Kubernetesモード 3. コードから見るARC - 事前知識 - GitHubとのロングポーリング - Runnerのスケール 24
  19. コードから見るARC - 事前知識 - 28 通常、なんらかの要因で本来あるべき振る舞いに乱れが生じた際、リトライ処理が必要となる。 ControllerはReconcile Loopという機能で、リトライ処理を自動化し、処理の冪等性を保証している。 つまり、あるべき姿が常に保持可能である。 あるべき姿

    Controller 管理対象Target (K8s 標準Resource, 外部Resource等) 比較 1. CRで管理対象Targetのあるべき姿を定義し、Deployする 2. Controllerが管理対象Targetあるべき姿を監視する 3. Controllerがあるべき姿と現在の姿を比較 4. 差異があれば、あるべき姿に戻す Runnerが削除されたら、自動デプロイ
  20. コードから見るARC - 事前知識 - 29 func (r * K8sNoviceOperatorReconciler) SetupWithManager(mgr

    ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). // 自分自身 For(&k8snoviceoperatorv1.K8sNoviceOperator{}). Complete(r) } • Reconcile Loopが呼び出されるタイミング • 10時間毎(デフォルト) • Manager.SyncPeriodで指定した時間毎 • 自分自身 or 子リソース or それ以外の監視対象のイベント発生時 自分自身以外の監視方法 Custom Controller外のリソースのイベントを監視する https://zenn.dev/ap_com/articles/5b64ab1ccd60a2
  21. コードから見るARC - 事前知識 - 1. 2個のジョブが定義されたGitHub Actions Workflowが実行される 2. ListenerはGitHubとロングポーリングを張っている。GitHubからジョブ実行のメッセージ受信

    3. ListenerはEphemeralRunnerSetの.Spec.ReplicasをWorkflowジョブの数だけスケールする 4. EphemeralRunnerSetの変更を検知したEphemeralRunnerSet Controllerは.Spec.Replicasの数だけ、 EphemeralRunnerを作成 5. EphemeralRunnerの作成を検知したEphemeralRunner Controllerは、 EphemeralRunnerの数だけRunner Podを作成 6. Runner Podは自分自身をGitHub Actionsに登録 Listenerの動作 31
  22. コードから見るARC - 事前知識 - Controller • AutoScalingRunnerSet Controller EphemeralRunnerSetおよびAutoScalingListerner CustomResourceの作成および更新、削除

    • EphemeralRunnerSet Controller EphemeralRunner CustomResourceの作成および更新、削除 • EphemeralRunner Controller EphemeralRunner Podの作成および削除 • AutoScalingListerner Controller AutoScalingListerner Podの作成および削除 Pod • Controller Pod 上記Controllerが動くmanager • EphemeralRunner Pod Self-hosted runnerとして動く • AutoScalingListerner Pod GitHubとのロングポーリングを確立し、GitHub Actions時にMessageを受信し、EphemeralRunnerSet の.Spec.Replicasを増減させる 今回の登場人物 今回の登場人物 32
  23. Agenda 1. アーキテクチャ紹介 2. 動作紹介 - 基本動作 - Runnerの最大/最小設定 -

    ARCでの、Workflow内Containerの使用 - Dindモード - Kubernetesモード 3. コードから見るARC - 事前知識 - GitHubとのロングポーリング - Runnerのスケール 33
  24. コードから見るARC - GitHubとのロングポーリング - 1. 2個のジョブが定義されたGitHub Actions Workflowが実行される 2. ListenerはGitHubとロングポーリングを張っている。GitHubからジョブ実行のメッセージ受信

    3. ListenerはEphemeralRunnerSetの.Spec.ReplicasをWorkflowジョブの数だけスケールする 4. EphemeralRunnerSetの変更を検知したEphemeralRunnerSet Controllerは.Spec.Replicasの数だけ、 EphemeralRunnerを作成 5. EphemeralRunnerの作成を検知したEphemeralRunner Controllerは、 EphemeralRunnerの数だけRunner Podを作成 6. Runner Podは自分自身をGitHub Actionsに登録 34 Listenerの動作
  25. コードから見るARC - GitHubとのロングポーリング - func (s *Service) Start() error {

    for { s.logger.Info("waiting for message...") select { case <-s.ctx.Done(): s.logger.Info("service is stopped.") return nil default: err := s.rsClient.GetRunnerScaleSetMessage(s.ctx, s.processMessage) if err != nil { return fmt.Errorf("could not get and process message. %w", err) } } } } <<ロングポーリングの実体>> 無限ループでGitHubからメッセージを待っている。 context.Contextがキャンセルされたら、ループを抜ける。そうでなければ、メッセージ受信を待ち続ける。 メッセージ受信は、 s.rsClient.GetRunnerScaleSetMessage関数で、メッセージの処理は、 s.processMessage関数で行われる。 https://github.com/actions/actions-runner-controller/blob/f1d7c52253b89f0beae60141f8465d9495cdc2cf/cmd/githubrunnerscalesetlistener/autoScalerService.go#L83-L97 35
  26. コードから見るARC - GitHubとのロングポーリング - func (m *AutoScalerClient) GetRunnerScaleSetMessage(ctx context.Context, handler

    func(msg *actions.RunnerScaleSetMessage) error) error { for { message, err := m.client.GetMessage(ctx, m.lastMessageId) if err != nil { return fmt.Errorf("get message failed from refreshing client. %w", err) } if message == nil { continue } err = handler(message) if err != nil { return fmt.Errorf("handle message failed. %w", err) } m.lastMessageId = message.MessageId return m.deleteMessage(ctx, message.MessageId) } } メッセージ受信は、 GetRunnerScaleSetMessage関数で行われる。この時、 受信したメッセージは、引数にあるhandler関数( s.processMessage関数)で処理され、EphemeralRunnerSetのスケールに 利用される。 https://github.com/actions/actions-runner- controller/blob/f1d7c52253b89f0beae60141f8465d9495cdc2cf/cmd/githubrunnerscalesetlistener/autoScalerMessageListener.go#L132-L162 36
  27. コードから見るARC - GitHubとのロングポーリング - func (c *Client) GetMessage(ctx context.Context, messageQueueUrl,

    messageQueueAccessToken string, lastMessageId int64) (*RunnerScaleSetMessage, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) if err != nil { return nil, err } resp, err := c.Do(req) if err != nil { return nil, err } if resp.StatusCode == http.StatusAccepted { defer resp.Body.Close() return nil, nil } var message *RunnerScaleSetMessage err = json.NewDecoder(resp.Body).Decode(&message) if err != nil { return nil, err } return message, nil } メッセージ受信は、GetMessage関数で行われる。 受信は、GETメソッドでRequestを作成して、Do関数でResponseを得る。得たjsonのResponseをGoの構造体にDecodeして メッセージに変換するといった流れとなる。 https://github.com/actions/actions-runner-controller/blob/f1d7c52253b89f0beae60141f8465d9495cdc2cf/github/actions/client.go#L457-L510 37
  28. Agenda 1. アーキテクチャ紹介 2. 動作紹介 - 基本動作 - Runnerの最大/最小設定 -

    ARCでの、Workflow内Containerの使用 - Dindモード - Kubernetesモード 3. コードから見るARC - 事前知識 - GitHubとのロングポーリング - Runnerのスケール 38
  29. コードから見るARC - Runnerのスケール - 1. 2個のジョブが定義されたGitHub Actions Workflowが実行される 2. ListenerはGitHubとロングポーリングを張っている。GitHubからジョブ実行のメッセージ受信

    3. ListenerはEphemeralRunnerSetの.Spec.ReplicasをWorkflowジョブの数だけスケールする 4. EphemeralRunnerSetの変更を検知したEphemeralRunnerSet Controllerは.Spec.Replicasの数だけ、 EphemeralRunnerを作成 5. EphemeralRunnerの作成を検知したEphemeralRunner Controllerは、 EphemeralRunnerの数だけRunner Podを作成 6. Runner Podは自分自身をGitHub Actionsに登録 39 Listenerの動作
  30. コードから見るARC - Runnerのスケール - func (s *Service) processMessage(message *actions.RunnerScaleSetMessage) error

    { var batchedMessages []json.RawMessage if err := json.NewDecoder(strings.NewReader(message.Body)).Decode(&batchedMessages); err != nil { return fmt.Errorf("could not decode job messages. %w", err) } err := s.rsClient.AcquireJobsForRunnerScaleSet(s.ctx, availableJobs) if err != nil { return fmt.Errorf("could not acquire jobs. %w", err) } return s.scaleForAssignedJobCount(message.Statistics.TotalAssignedJobs) } ロングポーリングで受信したメッセージには、GitHubでAssignされたジョブの合計数が統計として、包含されている。 このジョブの合計数に応じて、 s.scaleForAssignedJobCount関数で、EphemeralRunnerSetをスケールさせる。 https://github.com/actions/actions-runner-controller/blob/f1d7c52253b89f0beae60141f8465d9495cdc2cf/cmd/githubrunnerscalesetlistener/autoScalerService.go#L99-L204 40
  31. コードから見るARC - Runnerのスケール - func (s *Service) scaleForAssignedJobCount(count int) error

    { targetRunnerCount := int(math.Max(math.Min(float64(s.settings.MaxRunners), float64(count)), float64(s.settings.MinRunners))) if targetRunnerCount != s.currentRunnerCount { err := s.kubeManager.ScaleEphemeralRunnerSet(s.ctx, s.settings.Namespace, s.settings.ResourceName, targetRunnerCount) if err != nil { return fmt.Errorf("could not scale ephemeral runner set (%s/%s). %w", s.settings.Namespace, s.settings.ResourceName, err) } s.currentRunnerCount = targetRunnerCount } return nil } scaleForAssignedJobCount関数では、まずスケールすべき数を求めます。 1. math.Min(float64(s.settings.MaxRunners), float64(count)) ActionsRunnerScaleSetデプロイ時に指定したMaxRunnersと、GitHub ActionsでAssignされた数の内、小さい方を選択する 2. math.Max(math.Min(float64(s.settings.MaxRunners), float64(count)), float64(s.settings.MinRunners)) 1で求めた値と、 ActionsRunnerScaleSetデプロイ時に指定したMinRunnersの内、大きい方を選択する この計算により、MinRunners以下にはスケールされないようになっている。 上記で計算した数を引数にs.kubeManager.ScaleEphemeralRunnerSet関数を呼び出して、EphemeralRunnerSetのスケールを 行う。 https://github.com/actions/actions-runner-controller/blob/f1d7c52253b89f0beae60141f8465d9495cdc2cf/cmd/githubrunnerscalesetlistener/autoScalerService.go#L206-L226 41
  32. コードから見るARC - Runnerのスケール - func (k *AutoScalerKubernetesManager) ScaleEphemeralRunnerSet(ctx context.Context, namespace,

    resourceName string, runnerCount int) error { original := &v1alpha1.EphemeralRunnerSet{Spec: v1alpha1.EphemeralRunnerSetSpec{Replicas: -1}} originalJson, err := json.Marshal(original) if err != nil { k.logger.Error(err, "could not marshal empty ephemeral runner set") } patch := &v1alpha1.EphemeralRunnerSet{Spec: v1alpha1.EphemeralRunnerSetSpec{Replicas: runnerCount}} patchJson, err := json.Marshal(patch) if err != nil { k.logger.Error(err, "could not marshal patch ephemeral runner set") } mergePatch, err := jsonpatch.CreateMergePatch(originalJson, patchJson) if err != nil { k.logger.Error(err, "could not create merge patch json for ephemeral runner set") } patchedEphemeralRunnerSet := &v1alpha1.EphemeralRunnerSet{} err = k.RESTClient().Patch(types.MergePatchType).Prefix("apis", "actions.github.com", "v1alpha1").Namespace(namespace).Resource("EphemeralRunnerSets"). Name(resourceName).Body([]byte(mergePatch)).Do(ctx).Into(patchedEphemeralRunnerSet) if err != nil { return fmt.Errorf("could not patch ephemeral runner set , patch JSON: %s, error: %w", string(mergePatch), err) } return nil } スケールさせるべき数runnerCountを使ってパッチを作成して、RestClientでPatchを当て、EphemeralRunnerSetをスケール させる https://github.com/actions/actions-runner- controller/blob/f1d7c52253b89f0beae60141f8465d9495cdc2cf/cmd/githubrunnerscalesetlistener/autoScalerKubernetesManager.go#L40-L83 42
  33. コードから見るARC - Runnerのスケール - 1. 2個のジョブが定義されたGitHub Actions Workflowが実行される 2. ListenerはGitHubとロングポーリングを張っている。GitHubからジョブ実行のメッセージ受信

    3. ListenerはEphemeralRunnerSetの.Spec.ReplicasをWorkflowジョブの数だけスケールする 4. EphemeralRunnerSetの変更を検知したEphemeralRunnerSet Controllerは.Spec.Replicasの数だけ、 EphemeralRunnerを作成 5. EphemeralRunnerの作成を検知したEphemeralRunner Controllerは、 EphemeralRunnerの数だけRunner Podを作成 6. Runner Podは自分自身をGitHub Actionsに登録 Listenerの動作 43
  34. コードから見るARC - Runnerのスケール - func (r *EphemeralRunnerSetReconciler) SetupWithManager(mgr ctrl.Manager) error

    { : return ctrl.NewControllerManagedBy(mgr). For(&v1alpha1.EphemeralRunnerSet{}). Owns(&v1alpha1.EphemeralRunner{}). WithEventFilter(predicate.ResourceVersionChangedPredicate{}). Complete(r) } EphemeralRunnerSet ControllerはForの部分で、自身のCustomResourceが変更されたのを検知する仕組みになっている。 https://github.com/actions/actions-runner-controller/blob/f1d7c52253b89f0beae60141f8465d9495cdc2cf/controllers/actions.github.com/ephemeralrunnerset_controller.go#L547- L576 44
  35. コードから見るARC - Runnerのスケール - func (r *EphemeralRunnerSetReconciler) createEphemeralRunners(ctx context.Context, runnerSet

    *v1alpha1.EphemeralRunnerSet, count int, log logr.Logger) error { // Track multiple errors at once and return the bundle. errs := make([]error, 0) for i := 0; i < count; i++ { ephemeralRunner := r.resourceBuilder.newEphemeralRunner(runnerSet) if err := ctrl.SetControllerReference(runnerSet, ephemeralRunner, r.Scheme); err != nil { log.Error(err, "failed to set controller reference on ephemeral runner") errs = append(errs, err) continue } log.Info("Creating new ephemeral runner", "progress", i+1, "total", count) if err := r.Create(ctx, ephemeralRunner); err != nil { log.Error(err, "failed to make ephemeral runner") errs = append(errs, err) continue } } return multierr.Combine(errs...) } Listenerにより、EphemeralRunnerSetがスケールされたことを検知したEphemeralRunnerSet Controllerは、 createEphemeralRunners関数を呼び出し、スケールさせるべき数分、EphemeralRunnerを作成する。 この時、 r.resourceBuilder.newEphemeralRunner関数で、 EphemeralRunner定義を作成する。 https://github.com/actions/actions-runner-controller/blob/f1d7c52253b89f0beae60141f8465d9495cdc2cf/controllers/actions.github.com/ephemeralrunnerset_controller.go#L342- L369 45
  36. コードから見るARC - Runnerのスケール - func (b *resourceBuilder) newEphemeralRunner(ephemeralRunnerSet *v1alpha1.EphemeralRunnerSet) *v1alpha1.EphemeralRunner

    { return &v1alpha1.EphemeralRunner{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ GenerateName: ephemeralRunnerSet.Name + "-runner-", Namespace: ephemeralRunnerSet.Namespace, Labels: labels, Annotations: annotations, }, Spec: ephemeralRunnerSet.Spec.EphemeralRunnerSpec, } } EphemeralRunnerは、 SpecにEphemeralRunnerSet.Spec.EphemeralRunnerSpecを設定することで、 EphemeralRunnerSetの 定義を引き継ぐ。これらを更にRunner Podに引き継ぐことでPodがRunnerとして機能することになる。 https://github.com/actions/actions-runner-controller/blob/f1d7c52253b89f0beae60141f8465d9495cdc2cf/controllers/actions.github.com/resourcebuilder.go#L438-L466 46
  37. コードから見るARC - Runnerのスケール - { "githubConfigSecret": "pre-defined-secret", "githubConfigUrl": "https://github.com/apc-jnytnai0613/arc-test", "metadata":

    {}, "runnerScaleSetId": 17, "spec": { "containers": [ { "command": [ "/home/runner/run.sh" ], "image": "ghcr.io/actions/actions-runner:latest", "name": "runner", "resources": {} } ], "restartPolicy": "Never", "serviceAccountName": "arc-runner-set-gha-rs-no-permission" } } EphemeralRunnerSet.Spec.EphemeralRunnerSpecは以下のような定義となっている。 • GitHubの認証に使用するPATやGitHub App • リポジトリURL • Runner Pod用コンテナイメージ などが定義されている。 47
  38. コードから見るARC - Runnerのスケール - 1. 2個のジョブが定義されたGitHub Actions Workflowが実行される 2. ListenerはGitHubとロングポーリングを張っている。GitHubからジョブ実行のメッセージ受信

    3. ListenerはEphemeralRunnerSetの.Spec.ReplicasをWorkflowジョブの数だけスケールする 4. EphemeralRunnerSetの変更を検知したEphemeralRunnerSet Controllerは.Spec.Replicasの数だけ、 EphemeralRunnerを作成 5. EphemeralRunnerの作成を検知したEphemeralRunner Controllerは、 EphemeralRunnerの数だけRunner Podを作成 6. Runner Podは自分自身をGitHub Actionsに登録 Listenerの動作 48
  39. コードから見るARC - Runnerのスケール - func (r *EphemeralRunnerReconciler) SetupWithManager(mgr ctrl.Manager) error

    { return ctrl.NewControllerManagedBy(mgr). For(&v1alpha1.EphemeralRunner{}). Owns(&corev1.Pod{}). WithEventFilter(predicate.ResourceVersionChangedPredicate{}). Named("ephemeral-runner-controller"). Complete(r) } EphemeralRunner ControllerはForの部分で、自身のCustomResourceが変更されたのを検知する仕組みになっている。 https://github.com/actions/actions-runner-controller/blob/f1d7c52253b89f0beae60141f8465d9495cdc2cf/controllers/actions.github.com/ephemeralrunner_controller.go#L789- L798 49
  40. コードから見るARC - Runnerのスケール - Listenerの動作 50 EphemeralRunnerSet Controllerにより、EphemeralRunnerが作成されたことを検知したEphemeralRunner Controller は、Runner

    Podを作成する。Pod作成前に、GitHub上にJobごとにRunnerを登録する時用の、Just-In-Time(以降JIT) Configを作 成する。JIT ConfigはGenerateJitRunnerConfig関数により、scaleSetEndpointにPOSTメソッドを投げ、作成される。 https://github.com/actions/actions-runner-controller/blob/f1d7c52253b89f0beae60141f8465d9495cdc2cf/github/actions/client.go#L691-L719 func (c *Client) GenerateJitRunnerConfig(ctx context.Context, jitRunnerSetting *RunnerScaleSetJitRunnerSetting, scaleSetId int) (*RunnerScaleSetJitRunnerConfig, error) { path := fmt.Sprintf("/%s/%d/generatejitconfig", scaleSetEndpoint, scaleSetId) body, err := json.Marshal(jitRunnerSetting) if err != nil { return nil, err } req, err := c.NewActionsServiceRequest(ctx, http.MethodPost, path, bytes.NewBuffer(body)) if err != nil { return nil, err } resp, err := c.Do(req) if err != nil { return nil, err } var runnerJitConfig *RunnerScaleSetJitRunnerConfig err = json.NewDecoder(resp.Body).Decode(&runnerJitConfig) if err != nil { return nil, err } return runnerJitConfig, nil }
  41. コードから見るARC - Runnerのスケール - Listenerの動作 51 JIT Configは以下のような形式となっており、RunnerScaleSetIdやRunnerScaleSetNameなどScaleSetに関する情報も含まれる。 JIT ConfigはK8sのSecretリソースとして作成され、2重にエンコードされています。

    { ".runner": { "AgentId": "322", "AgentName": "arc-runner-set-v7nzm-runner-fmfcz", "DisableUpdate": "True", "Ephemeral": "True", "PoolId": "1", "PoolName": null, "ServerUrl": "https://pipelines.actions.githubusercontent.com/abKlCigKduPqALsnnIvHC16pYwPgN97mNIjD0UdG00l4ne31uP/", "RunnerScaleSetId": "12", "RunnerScaleSetName": "arc-runner-set", "WorkFolder": "_work" }, ".credentials": {xxxxxxxxxxxxxxxxxxxxxxx}, ".credentials_rsaparams": {xxxxxxxxxxxxxxxxxxxxxxx} } 参考:新しいjust-in-time runnerでセルフホストランナーのオートスケールが劇的に楽になりそう
  42. コードから見るARC - Runnerのスケール - Listenerの動作 53 Runner Podは、createPod関数で作成される。 r.resourceBuilder.newEphemeralRunnerPod関数で定義を作成し、EphemeralRunnerリソースを親としてOwnerReferenceを設 定後、r.Create関数でPodを作成します。

    https://github.com/actions/actions-runner-controller/blob/f1d7c52253b89f0beae60141f8465d9495cdc2cf/controllers/actions.github.com/ephemeralrunner_controller.go#L559- L630 func (r *EphemeralRunnerReconciler) createPod(ctx context.Context, runner *v1alpha1.EphemeralRunner, secret *corev1.Secret, log logr.Logger) (ctrl.Result, error) { newPod := r.resourceBuilder.newEphemeralRunnerPod(ctx, runner, secret, envs...) if err := ctrl.SetControllerReference(runner, newPod, r.Scheme); err != nil { log.Error(err, "Failed to set controller reference to a new pod") return ctrl.Result{}, err } log.Info("Created new pod spec for ephemeral runner") if err := r.Create(ctx, newPod); err != nil { log.Error(err, "Failed to create pod resource for ephemeral runner.") return ctrl.Result{}, err } return ctrl.Result{}, nil }
  43. コードから見るARC - Runnerのスケール - Listenerの動作 54 Runner Pod定義は、newEphemeralRunnerPod関数で作成される。 定義内には、JIT Configが環境変数として設定される。

    https://github.com/actions/actions-runner-controller/blob/f1d7c52253b89f0beae60141f8465d9495cdc2cf/controllers/actions.github.com/resourcebuilder.go#L468-L534 func (b *resourceBuilder) newEphemeralRunnerPod(ctx context.Context, runner *v1alpha1.EphemeralRunner, secret *corev1.Secret, envs ...corev1.EnvVar) *corev1.Pod { var newPod corev1.Pod newPod.Spec = runner.Spec.PodTemplateSpec.Spec newPod.Spec.Containers = make([]corev1.Container, 0, len(runner.Spec.PodTemplateSpec.Spec.Containers)) for _, c := range runner.Spec.PodTemplateSpec.Spec.Containers { if c.Name == EphemeralRunnerContainerName { c.Env = append( c.Env, corev1.EnvVar{ Name: EnvVarRunnerJITConfig, ValueFrom: &corev1.EnvVarSource{ SecretKeyRef: &corev1.SecretKeySelector{ LocalObjectReference: corev1.LocalObjectReference{ Name: secret.Name, }, Key: jitTokenKey, }, }, },) c.Env = append(c.Env, envs...) } newPod.Spec.Containers = append(newPod.Spec.Containers, c) } return &newPod }
  44. コードから見るARC - Runnerのスケール - 1. 2個のジョブが定義されたGitHub Actions Workflowが実行される 2. ListenerはGitHubとロングポーリングを張っている。GitHubからジョブ実行のメッセージ受信

    3. ListenerはEphemeralRunnerSetの.Spec.ReplicasをWorkflowジョブの数だけスケールする 4. EphemeralRunnerSetの変更を検知したEphemeralRunnerSet Controllerは.Spec.Replicasの数だけ、 EphemeralRunnerを作成 5. EphemeralRunnerの作成を検知したEphemeralRunner Controllerは、 EphemeralRunnerの数だけRunner Podを作成 6. Runner Podは自分自身をGitHub Actionsに登録 Listenerの動作 55
  45. コードから見るARC - Runnerのスケール - Listenerの動作 56 GitHubへのRunnerの登録は、ARCではなく、ghcr.io/actions/actions-runnerイメージから行う。 Pod起動後に、JIT Configを使用し、Runnerの登録を行う流れとなる。 "spec":

    { "containers": [ { "command": ["/home/runner/run.sh"] "env": [ { "name": "ACTIONS_RUNNER_INPUT_JITCONFIG", "valueFrom": { "secretKeyRef": { "key": "jitToken", "name": "arc-runner-set-qwchf-runner-kqdv9" } } }, { "name": "GITHUB_ACTIONS_RUNNER_EXTRA_USER_AGENT", "value": "actions-runner-controller/0.6.1" } ], "image": "ghcr.io/actions/actions-runner:latest", "imagePullPolicy": "Always", "name": "runner", :