はじめに
本記事では無料でCI/CD環境とその環境のモニタリング、アラート発砲をActions Runner Controller (ARC)とNew Relicを使って構築する方法を紹介します。
Actions Runner Controller (ARC)とは
ARCとはGitHub Actionsのランナーになります。GitHub Actionsで使用できるランナーには以下の2種類がありますが、後者に該当します
- GitHub-hosted runner
- GitHubが管理・保守されるランナー
- 無料枠やパブリックリポジトリでは無料などはあるが、基本的に有料
- Self-hosted runner
- 自身で管理・保守する必要のあるランナー
- 自身のマシンにインストールすれば追加料金は不要
Self-hosted runnerはLinux, Windows, Macなどにインストールすることが可能ですが、Kubernetes上で自動スケーリングなどが可能なため、今回選択しました。
New Relicとは
公式ホームページにいろいろと書いていますが、監視・モニタリングなどのオブザーバビリティを導入できるSaaSです。業務でDataDogなどを利用している方も多いと思いますが、今回は無料で使用できる機能が多かったので、New Relicを選択しました。今回構築したモニタリング、アラート発砲をDataDogで実現する場合には、Proプランの$18.75/month
は必要になります(2024/4/21時点)。
使用環境
今回は以下の環境に構築していきました。
- OS: Windows10 home
- WSL
- ubuntu 20.04:kubectl/helmをたたく環境
- Kubernetes環境
- 使用ツール:Rancher Desktop v1.12.3
$ kubectl version
Client Version: v1.28.2
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Server Version: v1.28.6+k3s2
$ helm version
version.BuildInfo{Version:"v3.13.3", GitCommit:"c8b948945e52abba22ff885446a1486cb5fd3474", GitTreeState:"clean", GoVersion:"go1.20.11"}
インストール
ARC
ARCを公式手順に従ってインストールしていきます。
コントローラーのインストールから実施していきます。
$ helm install arc --namespace "arc" --create-namespace oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller --version 0.9.1
docker-credential-secretservice: error while loading shared libraries: libsecret-1.so.0: cannot open shared object file: No such file or directory
Pulled: ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller:0.8.2
Digest: sha256:d4e2835bc2e96adb210efc10264c9864393aa3e24bdb8c5f8ccae059488765fc
NAME: arc
LAST DEPLOYED: Sun Feb 18 00:34:56 2024
NAMESPACE: arc
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Thank you for installing gha-runner-scale-set-controller.
Your release is named arc.
次にランナーセットのインストールをしていきます。ここで環境変数GITHUB_CONFIG_URL
とGITHUB_PAT
は各自の環境に合わせて事前に設定してください。
GITHUB_CONFIG_URL
はGitHubのURLです。ランナーはリポジトリ毎やorganization毎に作成が可能です。それぞれ以下のようなURLになります。
- リポジトリランナー:
https://github.com/<organization名>/<リポジトリ名>
- organizationランナー:
https://github.com/<organization名>
GITHUB_PAT
は必要な権限がランナーの単位によって違います。
- リポジトリランナー:
repo
- organizaitonランナー:
admin:org
helm install "arc-runner-set" --namespace "arc-runners" --create-namespace --set githubConfigUrl="${GITHUB_CONFIG_URL}" --set githubConfigSecret.github_token="${GITHUB_PAT}" oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set --version 0.9.1
docker-credential-secretservice: error while loading shared libraries: libsecret-1.so.0: cannot open shared object file: No such file or directory
Pulled: ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set:0.8.2
Digest: sha256:e715cbd53bd1d186dd7c1430ea105cb14f27f6dae185a5b5820c82c057555b71
NAME: arc-runner-set
LAST DEPLOYED: Sun Feb 18 00:43:11 2024
NAMESPACE: arc-runners
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Thank you for installing gha-runner-scale-set.
Your release is named arc-runner-set.
$ helm list -A
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
arc arc 1 2024-02-18 00:34:56.7723247 +0900 JST deployed gha-runner-scale-set-controller-0.8.2 0.8.2
arc-runner-set arc-runners 1 2024-02-18 00:43:11.8433244 +0900 JST deployed gha-runner-scale-set-0.8.2 0.8.2
$ kubectl get pods -n arc
NAME READY STATUS RESTARTS AGE
arc-gha-rs-controller-7dd75477f8-8h8gk 1/1 Running 2 (25m ago) 19d
arc-runner-set-754b578d-listener 1/1 Running 0 9m46s
ここまでで、ARCのインストールは完了しましたが、メトリクスはデフォルトで無効になっているので、公式手順に従って有効化していきます。
また、口述しますが、New Relicでpromethus形式のメトリクスをとるためにはprometheus.io/scrape: "true"
annotationが必要になります。上記の2つの設定をhelm valueファイルに記載すると以下のようになります。
podAnnotations:
prometheus.io/scrape: "true"
metrics:
controllerManagerAddr: ":8080"
listenerAddr: ":8080"
listenerEndpoint: "/metrics"
上記ファイルでコントローラーのhelm update
していきます。
$ helm upgrade -n arc arc -f values.yaml oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller --version 0.9.1
docker-credential-secretservice: error while loading shared libraries: libsecret-1.so.0: cannot open shared object file: No such file or directory
docker-credential-secretservice: error while loading shared libraries: libsecret-1.so.0: cannot open shared object file: No such file or directory
Pulled: ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller:0.9.0
Digest: sha256:06b038d924d82eb1f803fd7dbde7a6674e4aea83f681815cb3cffb610e424cff
Release "arc" has been upgraded. Happy Helming!
NAME: arc
LAST DEPLOYED: Thu Mar 28 19:37:54 2024
NAMESPACE: arc
STATUS: deployed
REVISION: 2
TEST SUITE: None
NOTES:
Thank you for installing gha-runner-scale-set-controller.
Your release is named arc.
WARNING: Older version of the listener (githubrunnerscalesetlistener) is deprecated and will be removed in the future gha-runner-scale-set-0.10.0 release. If you are using environment variable override to force the old listener, please remove the environment variable and use the new listener (ghalistener) instead.
listnerも同様にannotationを付与するためのhelm valueファイルは以下のようになります。
listenerTemplate:
metadata:
annotations:
prometheus.io/scrape: "true"
spec:
containers:
# Use this section to append additional configuration to the listener container.
# If you change the name of the container, the configuration will not be applied to the listener,
# and it will be treated as a side-car container.
- name: listener
上記ファイルでlistenrのhelm update
をしていきます。
helm upgrade -n arc-runners arc-runner-set -f listner-values.yaml oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set --set githubConfigSecret.github_token="<GitHub Token>" --set githubConfigUrl="<GITHUB URL>" --version 0.9.1
WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /home/uc/.kube/config
WARNING: Kubernetes configuration file is world-readable. This is insecure. Location: /home/uc/.kube/config
docker-credential-secretservice: error while loading shared libraries: libsecret-1.so.0: cannot open shared object file: No such file or directory
Pulled: ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set:0.9.0
Digest: sha256:f76dbf77a28206f030ab3165360cc6c028b13bcefde26284e6959323176e803e
Release "arc-runner-set" has been upgraded. Happy Helming!
NAME: arc-runner-set
LAST DEPLOYED: Thu Apr 18 17:48:41 2024
NAMESPACE: arc-runners
STATUS: deployed
REVISION: 5
TEST SUITE: None
NOTES:
Thank you for installing gha-runner-scale-set.
Your release is named arc-runner-set.
New Relic
続いてNew Relicをインストールしていきます。
上記画像のGuided installを選ぶと環境に合わせてインストールコマンドが表示されるので、それに従ってインストールできます。今回はKubernetes環境なので以下を選択してインストールしていきます。
- Kubernetes -> Helm
基本的には各自の取得したい項目を選んでいけばインストールコマンドが生成されます。ログなどはデータ量が多くなり、無料枠を超える可能性があるので今回はオフにしています。
ARCはpromethus形式でメトリクスを出力するため、newrelicプロメテウスエージェントによってメトリクスを収集します。今回は対象を絞るために、以下のように対象を絞ります。
- prometheus.io/scrape: "true"のannotationが付与されているpod
- podのラベルがいかに一致するpod
app.kubernetes.io/name=gha-rs-controller
app.kubernetes.io/component=runner-scale-set-listener
- デフォルト値
今回の選んだオプションをhelm valueファイルにあらわしたものは以下になります。
newrelic-infrastructure:
enabled: true
nri-prometheus:
enabled: false
nri-metadata-injection:
enabled: true
kube-state-metrics:
enabled: true
nri-kube-events:
enabled: true
newrelic-logging:
enabled: false
newrelic-pixie:
enabled: false
pixie-chart:
enabled: false
newrelic-infra-operator:
enabled: false
newrelic-prometheus-agent:
enabled: true
config:
kubernetes:
integrations_filter:
enabled: true
source_labels: ["app.kubernetes.io/name", "app.kubernetes.io/component"]
app_values: ["gha-rs-controller", "runner-scale-set-listener"]
newrelic-k8s-metrics-adapter:
enabled: false
上記ファイルを使ってインストール
$ KSM_IMAGE_VERSION="v2.10.0" && helm repo add newrelic https://helm-charts.newrelic.com && helm repo update && kubectl create namespace newrelic ; helm upgrade --install newrelic-bundle newrelic/nri-bundle --values nri-values.yaml --set global.licenseKey=<license key> --set global.cluster=rancher-desktop --namespace=newrelic
WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /home/uc/.kube/config
WARNING: Kubernetes configuration file is world-readable. This is insecure. Location: /home/uc/.kube/config
"newrelic" already exists with the same configuration, skipping
WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /home/uc/.kube/config
WARNING: Kubernetes configuration file is world-readable. This is insecure. Location: /home/uc/.kube/config
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "datadog" chart repository
...Successfully got an update from the "newrelic" chart repository
Update Complete. ⎈Happy Helming!⎈
Error from server (AlreadyExists): namespaces "newrelic" already exists
WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /home/uc/.kube/config
WARNING: Kubernetes configuration file is world-readable. This is insecure. Location: /home/uc/.kube/config
Release "newrelic-bundle" has been upgraded. Happy Helming!
NAME: newrelic-bundle
LAST DEPLOYED: Sun Apr 14 15:30:52 2024
NAMESPACE: newrelic
STATUS: deployed
REVISION: 2
TEST SUITE: None
$ kubectl get pod -n newrelic -w
NAME READY STATUS RESTARTS AGE
newrelic-bundle-nri-metadata-injection-56df859c76-jw27c 1/1 Running 0 33m
newrelic-bundle-newrelic-prometheus-agent-0 1/1 Running 0 13m
newrelic-bundle-kube-state-metrics-7d4f664976-5lv86 1/1 Running 0 9m44s
newrelic-bundle-nrk8s-kubelet-5wvjv 2/2 Running 0 62s
newrelic-bundle-nrk8s-controlplane-l9rw5 2/2 Running 0 61s
newrelic-bundle-nrk8s-ksm-64785f445f-z2hs4 2/2 Running 0 61s
newrelic-bundle-nri-kube-events-784688f776-x5p8q 2/2 Running 0 5s
DashBoard作成
ここまででARC, New Relicのインストールは終わり、メトリクスが取れているはずなので、ダッシュボードを作成していきます。
メトリクスからgha
で検索すると画像のようにARCに関するメトリクスが取れるので、Add to dashboard
からダッシュボードに追加していくのが楽だと思います。今回作ったダッシュボードのjsonを以下に貼っておくので参考にカスタマイズして使ってみてください。
dashboardのjsonファイル
{
"name": "GHA",
"description": null,
"permissions": "PRIVATE",
"pages": [
{
"name": "Controller",
"description": null,
"widgets": [
{
"title": "failed_ephemeral_runners",
"layout": {
"column": 1,
"row": 1,
"width": 4,
"height": 3
},
"linkedEntityGuids": null,
"visualization": {
"id": "viz.line"
},
"rawConfiguration": {
"nrqlQueries": [
{
"accountId": <account id>,
"query": "SELECT latest(gha_controller_failed_ephemeral_runners) FROM Metric FACET app_kubernetes_io_name SINCE 30 MINUTES AGO TIMESERIES"
}
]
}
},
{
"title": "pending_ephemeral_runners",
"layout": {
"column": 5,
"row": 1,
"width": 4,
"height": 3
},
"linkedEntityGuids": null,
"visualization": {
"id": "viz.line"
},
"rawConfiguration": {
"nrqlQueries": [
{
"accountId": <account id>,
"query": "SELECT latest(gha_controller_pending_ephemeral_runners) FROM Metric FACET app_kubernetes_io_name SINCE 30 MINUTES AGO TIMESERIES"
}
]
}
},
{
"title": "running_ephemeral_runners",
"layout": {
"column": 9,
"row": 1,
"width": 4,
"height": 3
},
"linkedEntityGuids": null,
"visualization": {
"id": "viz.line"
},
"rawConfiguration": {
"nrqlQueries": [
{
"accountId": <account id>,
"query": "SELECT latest(gha_controller_running_ephemeral_runners) FROM Metric FACET app_kubernetes_io_name SINCE 30 MINUTES AGO TIMESERIES"
}
]
}
}
]
},
{
"name": "Listner",
"description": null,
"widgets": [
{
"title": "Number of Runners Missing",
"layout": {
"column": 1,
"row": 1,
"width": 4,
"height": 3
},
"linkedEntityGuids": null,
"visualization": {
"id": "viz.billboard"
},
"rawConfiguration": {
"facet": {
"showOtherSeries": false
},
"nrqlQueries": [
{
"accountIds": [
<account id>
],
"query": "SELECT latest(gha_desired_runners) - latest(gha_busy_runners) FROM Metric FACET actions_github_com_scale_set_name "
}
],
"platformOptions": {
"ignoreTimeRange": false
}
}
},
{
"title": "assigned_jobs",
"layout": {
"column": 5,
"row": 1,
"width": 4,
"height": 3
},
"linkedEntityGuids": null,
"visualization": {
"id": "viz.line"
},
"rawConfiguration": {
"colors": {
"seriesOverrides": [
{
"color": "#3302f7",
"seriesName": "arc-runner-set-2"
}
]
},
"nrqlQueries": [
{
"accountId": <account id>,
"query": "SELECT latest(gha_assigned_jobs) FROM Metric FACET actions_github_com_scale_set_name SINCE 30 MINUTES AGO TIMESERIES"
}
]
}
},
{
"title": "running_jobs",
"layout": {
"column": 9,
"row": 1,
"width": 4,
"height": 3
},
"linkedEntityGuids": null,
"visualization": {
"id": "viz.line"
},
"rawConfiguration": {
"colors": {
"seriesOverrides": [
{
"color": "#3302f7",
"seriesName": "arc-runner-set-2"
}
]
},
"nrqlQueries": [
{
"accountId": <account id>,
"query": "SELECT latest(gha_running_jobs) FROM Metric FACET actions_github_com_scale_set_name SINCE 30 MINUTES AGO TIMESERIES"
}
]
}
},
{
"title": "registered_runners",
"layout": {
"column": 1,
"row": 4,
"width": 4,
"height": 3
},
"linkedEntityGuids": null,
"visualization": {
"id": "viz.line"
},
"rawConfiguration": {
"colors": {
"seriesOverrides": [
{
"color": "#3302f7",
"seriesName": "arc-runner-set-2"
}
]
},
"nrqlQueries": [
{
"accountId": <account id>,
"query": "SELECT latest(gha_registered_runners) FROM Metric FACET actions_github_com_scale_set_name SINCE 30 MINUTES AGO TIMESERIES"
}
]
}
},
{
"title": "busy_runners",
"layout": {
"column": 5,
"row": 4,
"width": 4,
"height": 3
},
"linkedEntityGuids": null,
"visualization": {
"id": "viz.line"
},
"rawConfiguration": {
"colors": {
"seriesOverrides": [
{
"color": "#3302f7",
"seriesName": "arc-runner-set-2"
}
]
},
"nrqlQueries": [
{
"accountId": <account id>,
"query": "SELECT latest(gha_busy_runners) FROM Metric FACET actions_github_com_scale_set_name SINCE 30 MINUTES AGO TIMESERIES"
}
]
}
},
{
"title": "desired_runners",
"layout": {
"column": 9,
"row": 4,
"width": 4,
"height": 3
},
"linkedEntityGuids": null,
"visualization": {
"id": "viz.line"
},
"rawConfiguration": {
"colors": {
"seriesOverrides": [
{
"color": "#3302f7",
"seriesName": "arc-runner-set-2"
}
]
},
"facet": {
"showOtherSeries": false
},
"legend": {
"enabled": true
},
"nrqlQueries": [
{
"accountIds": [
<account id>
],
"query": "SELECT latest(gha_desired_runners) FROM Metric FACET actions_github_com_scale_set_name AS actions_github_com_scale_set_name SINCE 30 MINUTES AGO TIMESERIES"
}
],
"platformOptions": {
"ignoreTimeRange": false
},
"thresholds": {
"isLabelVisible": true
},
"yAxisLeft": {
"zero": true
},
"yAxisRight": {
"zero": true
}
}
}
]
}
],
"variables": []
}
listnerの最初のウィジェットは以下のクエリで作成してます
SELECT latest(gha_desired_runners) - latest(gha_busy_runners) FROM Metric FACET actions_github_com_scale_set_name
これは必要なrunnnerの数(gha_desired_runners
)と実行中のrunnerの数(gha_busy_runners
)の数を比較しています。これが0より大きい状態が続いた場合は、何らかの理由でrunnerが起動しないという状態であることがわかります。このウィジェットを利用してアラームを設定していきます。
Alert作成
AlertはウィジェットのCreate alert condition
から作成が可能です。
先ほどのクエリを利用してGUIの画面に沿って作成していきます。
以下の設定以外はすべてデフォルト設定で作成しました。
- 閾値:1以上を5min以上
- ギャップ埋め:最後の値を利用
- 個人利用のため、マシンのシャットダウンをするため、設定しないとアラームが鳴り続ける
terraformで作成する場合は以下のコードで作成可能です。
Alertのterraformコード
resource "newrelic_nrql_alert_condition" "runner" {
account_id = 4419235
policy_id = 5243229
type = "static"
name = "Runnerが起動しない"
description = <<-EOT
必要とされているRunnerよりも実行中のRunnnerの数が少ないのが5min以上発生
EOT
enabled = true
violation_time_limit_seconds = 259200 # 3 days
nrql {
query = "SELECT latest(gha_desired_runners) - latest(gha_busy_runners) FROM Metric FACET actions_github_com_scale_set_name"
}
critical {
operator = "above_or_equals"
threshold = 1
threshold_duration = 300
threshold_occurrences = "all"
}
fill_option = "last_value"
aggregation_window = 60
aggregation_method = "event_flow"
aggregation_delay = 120
}
今回はalert発生した場合はslack通知を設定しました。テスト結果は以下です。
Alert通知確認
アラートの通知がうまく動作するかを試してみました。今回はrunnerが作成されるnamespace
に以下のリソースクオーターをつけてrunnerが作成されないようにしました。
apiVersion: v1
kind: ResourceQuota
metadata:
name: pods-medium
namespace: arc-runners
spec:
hard:
cpu: "10"
memory: 20Gi
$ kubectl apply -f resource-quota.yaml
resourcequota/pods-medium created
$ kubectl get resourcequotas -n arc-runners
NAME AGE REQUEST LIMIT
pods-medium 9s cpu: 0/10, memory: 0/20Gi
$ kubectl get ephemeralrunners.actions.github.com -n arc-runners
NAME GITHUB CONFIG URL RUNNERID STATUS JOBREPOSITORY JOBWORKFLOWREF WORKFLOWRUNID JOBDISPLAYNAME MESSAGE AGE
arc-runner-set-p6gnd-runner-2s2dn https://github.com/arc-poc-org 46 Failed Failed to create the pod: pods "arc-runner-set-p6gnd-runner-2s2dn" is forbidden: failed quota: pods-medium: must specify cpu for: runner; memory for: runner 5h9m
arc-runner-set-2-w7946-runner-74jq7 https://github.com/arc-poc-org 47 Failed Failed to create the pod: pods "arc-runner-set-2-w7946-runner-74jq7" is forbidden: failed quota: pods-medium: must specify cpu for: runner; memory for: runner 5h9m
runnerが待っても立ち上がらないので、5minまってるとアラートがslackに飛んできます
Acknowledged
を押すとnewrelicに飛びます
リソースクオーターを削除してクローズさせに行きます
$ kubectl delete -f ARC/manifest/resource-quota.yaml
resourcequota "pods-medium" deleted
$ kubectl get pod -n arc-runners -w
NAME READY STATUS RESTARTS AGE
arc-runner-set-2-99rxd-runner-mnp9v 0/1 Pending 0 0s
arc-runner-set-2-99rxd-runner-mnp9v 0/1 Pending 0 0s
arc-runner-set-2-99rxd-runner-mnp9v 0/1 ContainerCreating 0 0s
arc-runner-set-pxlw8-runner-q6f7s 0/1 Pending 0 0s
arc-runner-set-pxlw8-runner-q6f7s 0/1 Pending 0 0s
arc-runner-set-pxlw8-runner-q6f7s 0/1 ContainerCreating 0 0s
arc-runner-set-pxlw8-runner-q6f7s 1/1 Running 0 5s
arc-runner-set-2-99rxd-runner-mnp9v 1/1 Running 0 5s
エラーが解消したことが確認できたのでslackのClose
をおしてクローズします
まとめ
今回はARCとNew Relicを利用して無料でCI/CD環境とモニタリング、アラートを構築してみました。ARCはGitHub Actions チームが開発に参加する前のlegacy時代に苦労した苦い思い出がありますが、その時とは違ってインストールや設定がだいぶ楽になってましね!今回の記事を書くにあたって使ったNer Relicの使用量は一日1Gなので無料枠で収まると思います。(もちろん使い方によるので注意してください。)
快適なCI/CDライフを過ごしてください!