技術本部 サービスリライアビリティグループ(SRG)の松田です。
SRG(Service Reliability Group)は、主に弊社メディアサービスのインフラ周りを横断的にサポートしており、既存サービスの改善や新規立ち上げ、OSS貢献などを行っているグループです。
弊社では一部のプロダクトでGitHub Enterprise Server(以下GHES)を利用しています。GHESでGithub Actionsを使いたい場合、GitHub-hosted runnersは利用できないため、self-hosted runnersを選択することになります。私が関わっているプロダクトでは、既存のCIからGitHub Actionsに移行する予定があり、開発者がカジュアルに試せる環境を用意することにしました。
公式の導入方法のとおりにself-hosted runnersを導入すると以下の課題があります。
- 同一ホスト上で毎回実行されるため、ファイル操作などを伴う処理の場合に冪等性が無い
- 事前にホスト上に設定する必要があるため、ジョブキューに応じてオートスケールは出来ない
これらの課題を解決しつつself-hosted runnersを実行できるツールとしてactions-runner-controllerがあります。
今回はこちらの導入方法、メリット・デメリットを書いていきます。
導入方法
Kubernetes上で動作するため事前にClusterを用意します。
導入はactions-runner-controllerのREADMEの通りですが、GHES独自の部分がいくつかあります。
CRDの追加
actions-runner-controller内で利用するcert-managerとactions-runner-controllerを追加します。
$ kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v1.0.4/cert-manager.yaml
$ kubectl apply -f https://github.com/summerwind/actions-runner-controller/releases/latest/download/actions-runner-controller.yaml
GHESのURLを設定
利用しているGHESのURLをenvに設定します。
kubectl set env deploy controller-manager -c manager GITHUB_ENTERPRISE_URL="${GHES_URL}" -n actions-runner-system
Personal Access Tokenの登録
GHESとの認証方法はGitHub AppとPersonal Access Tokenの2通りあります。
今回はPersonal Access Tokenで認証します。Organization単位で利用できるようにするためadmin:org
scopeを持つPersonal Access Tokenを作成して登録します。
$ kubectl create secret generic controller-manager \
-n actions-runner-system \
--from-literal=github_token=${GITHUB_PERSONAL_ACCESS_TOKEN}
実行対象リポジトリの取得
今回はOrganization内の複数リポジトリで使用できるように設定します。
self-hosted runnersをジョブ数に応じてオートスケールさせるためには、HorizontalRunnerAutoscalerに対象リポジトリを登録する必要があります。
Organization内の全Repositoryは以下のコマンドで取得できます。
$ curl -v -H "Authorization: token ${TOKEN}" https://${GHES_URL}/api/v3/orgs/${ORG}/repos | jq -c -r '.[].name'
RunnerDeploymentとHorizontalRunnerAutoscalerの登録
self-hosted runnersを実行するRunnerDeploymentを登録します。
実行ログを保存するためにsidecarでaws-cliコンテナを立てて、Podが終了するときにS3へログを送信するようにしています。
aws_access_key_idとaws_secret_access_keyは事前にsecretに登録してください。
HorizontalRunnerAutoscalerを正しく動作させるために、先程取得したRepository名を設定してください。
これをapplyするとself-hosted runnersが利用できるようになります。
apiVersion: actions.summerwind.dev/v1alpha1
kind: RunnerDeployment
metadata:
name: ${ORG}-runnerdeploy
spec:
replicas: 1
template:
spec:
labels:
- test-runner
initContainers:
- name: chmod
image: alpine
command: ["chmod", "777", "/runner/_diag"]
volumeMounts:
- name: runner-log
mountPath: /runner/_diag
organization: ${ORG}
image: summerwind/actions-runner:v2.274.1
dockerdWithinRunnerContainer: false
env:
- name: GITHUB_ENTERPRISE_URL
value: "https://${GHES_URL}/"
volumeMounts:
- name: runner-log
mountPath: /runner/_diag
volumes:
- name: runner-log
emptyDir:
sidecarContainers:
- name: aws-cli
image: amazon/aws-cli:2.1.1
command: ["/bin/bash", "-c"]
args: ["trap \"aws s3 cp /var/log/runner-log/Worker* s3://BUCKET/self-hosted-runner/`date +%Y/%m/%d/` --region REGION\" TERM INT; sleep infinity & wait"]
env:
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: aws-access
key: aws_access_key_id
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: aws-access
key: aws_secret_access_key
volumeMounts:
- name: runner-log
mountPath: /var/log/runner-log
---
apiVersion: actions.summerwind.dev/v1alpha1
kind: HorizontalRunnerAutoscaler
metadata:
name: ${ORG}-runnerdeploy-autoscaler
spec:
scaleTargetRef:
name: ${ORG}-runnerdeploy
minReplicas: 1
maxReplicas: 5
scaleDownDelaySecondsAfterScaleOut: 300
metrics:
- type: TotalNumberOfQueuedAndInProgressWorkflowRuns
repositoryNames:
- ${REPOSITORY_A}
- ${REPOSITORY_B}
メリット
ジョブ実行ごとにrunnerが作成されるため冪等性がある
公式の導入方法のとおりにするとジョブ実行後もrunnerタスクは残り続け、VM内で変更したファイルはそのままです。
このツールでは、実行するジョブごとにrunner Podが作成されるため冪等性があります。
ジョブ数に応じてオートスケールする
ジョブ数に応じてrunnerを増やして並列に実行できます。
Organization単位でRunnerを指定できる
RunnerDeployment一つでOrganization一つを設定できるので、複数Organizationの設定も簡単にできます。
しかし、Repositoryごとに必要なPod性能が大きく異なる場合もあるため、ある程度似通った性能ごとに作成した方がリソース効率がよくなります。
デメリット
Kubernetes Clusterを管理する必要がある
すでにCI/CD用のClusterがある場合はそこにdeployするだけですが、ない場合はself-hosted runnersのためにClusterを管理する必要があります。
NodeのコストもClusterの運用コストも小さくは無いため、利用規模が小さい場合は他の手段を採用したほうが結果的に楽ができるかもしれません。
今回は利用するOrganizationや実行ジョブ数が多く、冪等性とオートスケールが必須要件であったため採用しました。
Docker in Dockerになる
Dockerコンテナのactionを実行する場合は、sidecarで立ち上がるDockerコンテナ内で実行されます。(runner Pod内で実行することも可能です)
Docker in Dockerになるためprivileged: true
で実行する必要があり、実行される処理によっては危険です。
Repositoryの追加作業が必要になる
Repositoryが新規作成された際に、HorizontalRunnerAutoscalerの対象Repositoryとして追加する機能は現在ありません。
そのため、新規作成されるごとに追加してHorizontalRunnerAutoscalerをDeployしなおす必要があります。
Tips
- ときどき、ジョブ実行後にrunner Podが削除されず手動で削除しないとキューが溜まり続けるときがあります。
- pushするごとにジョブを実行するようにしているとリソースを使い切ってキューが溜まり続けてしまうため、お試しで構築している場合はPull Requestで実行するくらいにしておくと良いと思います。
おわりに
self-hosted runnersをKubernetes上で実行できるツール actions-runner-controllerについて紹介しました。
大規模にself-hosted runnersを活用する場合は非常に便利なツールですのでぜひ!
弊社では他にもself-hosted runnersを実行するツールとして、Private Cloud Groupでmyshoesをクローズドβで提供しています。紹介記事はこちら。
利用者としてはGitHub-hosted runnersのような使用感になるため、運用コストを下げたいチームではこちらが採用されるでしょう。
私が担当しているプロダクトも今後myshoesに移行していく予定です。