Skip to content

Commit 246cb7f

Browse files
authored
[eks/actions-runner-controller] Add ability to dynamically annotate pods once they start a job (cloudposse/terraform-aws-components#1055)
1 parent 806c5c6 commit 246cb7f

File tree

7 files changed

+238
-171
lines changed

7 files changed

+238
-171
lines changed

src/README.md

Lines changed: 37 additions & 27 deletions
Large diffs are not rendered by default.

src/charts/actions-runner/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type: application
1515
# This is the chart version. This version number should be incremented each time you make changes
1616
# to the chart and its templates, including the app version.
1717
# Versions are expected to follow Semantic Versioning (https://semver.org/)
18-
version: 0.1.2
18+
version: 0.2.0
1919

2020
# This chart only deploys Resources for actions-runner-controller, so app version does not really apply.
2121
# We use Resource API version instead.

src/charts/actions-runner/templates/runnerdeployment.yaml

Lines changed: 125 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,3 @@
1-
{{- if .Values.pvc_enabled }}
2-
---
3-
# Persistent Volumes can be used for image caching
4-
apiVersion: v1
5-
kind: PersistentVolumeClaim
6-
metadata:
7-
name: {{ .Values.release_name }}
8-
spec:
9-
accessModes:
10-
- ReadWriteMany
11-
# StorageClassName comes from efs-controller and must be deployed first.
12-
storageClassName: efs-sc
13-
resources:
14-
requests:
15-
# EFS is not actually storage constrained, but this storage request is
16-
# required. 100Gi is a ballpark for how much we initially request, but this
17-
# may grow. We are responsible for docker pruning this periodically to
18-
# save space.
19-
storage: 100Gi
20-
{{- end }}
21-
{{- if .Values.docker_config_json_enabled }}
22-
---
23-
apiVersion: v1
24-
kind: Secret
25-
metadata:
26-
name: {{ .Values.release_name }}-regcred
27-
type: kubernetes.io/dockerconfigjson
28-
data:
29-
.dockerconfigjson: {{ .Values.docker_config_json }}
30-
{{- end }}
31-
---
321
apiVersion: actions.summerwind.dev/v1alpha1
332
kind: RunnerDeployment
343
metadata:
@@ -38,13 +7,13 @@ spec:
387
# See https://github.com/actions-runner-controller/actions-runner-controller/issues/206#issuecomment-748601907
398
# replicas: 1
409
template:
41-
{{- with index .Values "pod_annotations" }}
10+
{{- with .Values.pod_annotations }}
4211
metadata:
4312
annotations:
4413
{{- toYaml . | nindent 8 }}
4514
{{- end }}
4615
spec:
47-
{{- if .Values.docker_config_json_enabled }}
16+
{{- if .Values.docker_config_json_enabled }}
4817
# secrets volumeMount are always mounted readOnly so config.json has to be copied to the correct directory
4918
# https://github.com/kubernetes/kubernetes/issues/62099
5019
# https://github.com/actions/actions-runner-controller/issues/2123#issuecomment-1527077517
@@ -82,14 +51,41 @@ spec:
8251
# - effect: NoSchedule
8352
# key: node-role.kubernetes.io/actions-runner
8453
# operator: Exists
54+
{{- with .Values.node_selector }}
55+
nodeSelector:
56+
{{- toYaml . | nindent 8 }}
57+
{{- end }}
58+
{{- with .Values.tolerations }}
59+
tolerations:
60+
{{- toYaml . | nindent 8 }}
61+
{{- end }}
62+
{{- with .Values.affinity }}
63+
affinity:
64+
{{- toYaml . | nindent 8 }}
65+
{{- end }}
66+
67+
{{- with .Values.running_pod_annotations }}
68+
# Run a pre-run hook to set pod annotations
69+
# See https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/running-scripts-before-or-after-a-job#triggering-the-scripts
70+
containers:
71+
- name: runner
72+
# ARC (Summerwind) has its own pre-run hook, so we do not want to set
73+
# env:
74+
# - name: ACTIONS_RUNNER_HOOK_JOB_STARTED
75+
# value: /hooks/pre-run.sh # triggers when a job is started, and sets the pod to NOT safe-to-evict
76+
# Instead, its pre-run hook runs scripts in /etc/arc/hooks/job-started.d/
77+
volumeMounts:
78+
- name: hooks
79+
mountPath: /etc/arc/hooks/job-started.d/
80+
{{- end }}
8581

86-
{{ if eq .Values.type "organization" }}
82+
{{- if eq .Values.type "organization" }}
8783
organization: {{ .Values.scope }}
8884
{{- end }}
89-
{{ if eq .Values.type "repository" }}
85+
{{- if eq .Values.type "repository" }}
9086
repository: {{ .Values.scope }}
9187
{{- end }}
92-
{{ if index .Values "group" }}
88+
{{- if index .Values "group" }}
9389
group: {{ .Values.group }}
9490
{{- end }}
9591
# You can use labels to create subsets of runners.
@@ -103,14 +99,6 @@ spec:
10399
{{- range .Values.labels }}
104100
- {{ . | quote }}
105101
{{- end }}
106-
{{- if gt ( len (index .Values "node_selector") ) 0 }}
107-
nodeSelector:
108-
{{- toYaml .Values.node_selector | nindent 8 }}
109-
{{- end }}
110-
{{- if gt ( len (index .Values "tolerations") ) 0 }}
111-
tolerations:
112-
{{- toYaml .Values.tolerations | nindent 8 }}
113-
{{- end }}
114102
# dockerdWithinRunnerContainer = false means access to a Docker daemon is provided by a sidecar container.
115103
dockerdWithinRunnerContainer: {{ .Values.dind_enabled }}
116104
image: {{ .Values.image | quote }}
@@ -133,7 +121,7 @@ spec:
133121
{{- if index .Values.resources.requests "ephemeral_storage" }}
134122
ephemeral-storage: {{ .Values.resources.requests.ephemeral_storage }}
135123
{{- end }}
136-
{{- if and .Values.dind_enabled .Values.storage }}
124+
{{- if and .Values.dind_enabled .Values.docker_storage }}
137125
dockerVolumeMounts:
138126
- mountPath: /var/lib/docker
139127
name: docker-volume
@@ -150,24 +138,24 @@ spec:
150138
- mountPath: /home/runner/.docker
151139
name: docker-config-volume
152140
{{- end }}
153-
{{- end }}
154-
{{- if or (and .Values.dind_enabled .Values.storage) (.Values.pvc_enabled) (.Values.docker_config_json_enabled) }}
141+
{{- end }}{{/* End of volumeMounts */}}
142+
{{- if or (and .Values.dind_enabled .Values.docker_storage) (.Values.pvc_enabled) (.Values.docker_config_json_enabled) (not (empty .Values.running_pod_annotations)) }}
155143
volumes:
156-
{{- if and .Values.dind_enabled .Values.storage }}
144+
{{- if and .Values.dind_enabled .Values.docker_storage }}
157145
- name: docker-volume
158146
ephemeral:
159147
volumeClaimTemplate:
160148
spec:
161149
accessModes: [ "ReadWriteOnce" ] # Only 1 pod can connect at a time
162150
resources:
163151
requests:
164-
storage: {{ .Values.storage }}
165-
{{- end }}
166-
{{- if .Values.pvc_enabled }}
152+
storage: {{ .Values.docker_storage }}
153+
{{- end }}
154+
{{- if .Values.pvc_enabled }}
167155
- name: shared-volume
168156
persistentVolumeClaim:
169157
claimName: {{ .Values.release_name }}
170-
{{- end }}
158+
{{- end }}
171159
{{- if .Values.docker_config_json_enabled }}
172160
- name: docker-secret
173161
secret:
@@ -178,4 +166,88 @@ spec:
178166
- name: docker-config-volume
179167
emptyDir:
180168
{{- end }}
181-
{{- end }}
169+
{{- with .Values.running_pod_annotations }}
170+
- name: hooks
171+
configMap:
172+
name: runner-hooks
173+
defaultMode: 0755 # Set execute permissions for all files
174+
{{- end }}
175+
{{- end }}{{/* End of volumes */}}
176+
{{- if .Values.pvc_enabled }}
177+
---
178+
# Persistent Volumes can be used for image caching
179+
apiVersion: v1
180+
kind: PersistentVolumeClaim
181+
metadata:
182+
name: {{ .Values.release_name }}
183+
spec:
184+
accessModes:
185+
- ReadWriteMany
186+
# StorageClassName comes from efs-controller and must be deployed first.
187+
storageClassName: efs-sc
188+
resources:
189+
requests:
190+
# EFS is not actually storage constrained, but this storage request is
191+
# required. 100Gi is a ballpark for how much we initially request, but this
192+
# may grow. We are responsible for docker pruning this periodically to
193+
# save space.
194+
storage: 100Gi
195+
{{- end }}
196+
{{- if .Values.docker_config_json_enabled }}
197+
---
198+
apiVersion: v1
199+
kind: Secret
200+
metadata:
201+
name: {{ .Values.release_name }}-regcred
202+
type: kubernetes.io/dockerconfigjson
203+
data:
204+
.dockerconfigjson: {{ .Values.docker_config_json }}
205+
{{- end }}
206+
{{- with .Values.running_pod_annotations }}
207+
---
208+
apiVersion: v1
209+
kind: ConfigMap
210+
metadata:
211+
name: runner-hooks
212+
data:
213+
annotate.sh: |
214+
#!/bin/bash
215+
216+
# If we had kubectl and a KUBECONFIG, we could do this:
217+
# kubectl annotate pod $HOSTNAME 'karpenter.sh/do-not-evict="true"' --overwrite
218+
# kubectl annotate pod $HOSTNAME 'karpenter.sh/do-not-disrupt="true"' --overwrite
219+
220+
# This is the same thing, the hard way
221+
222+
# Metadata about the pod
223+
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
224+
POD_NAME=$(hostname)
225+
226+
# Kubernetes API URL
227+
API_URL="https://kubernetes.default.svc"
228+
229+
# Read the service account token
230+
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
231+
232+
# Content type
233+
CONTENT_TYPE="application/merge-patch+json"
234+
235+
PATCH_JSON=$(cat <<EOF
236+
{
237+
"metadata": {
238+
"annotations":
239+
{{- . | toJson | nindent 10 }}
240+
}
241+
}
242+
EOF
243+
)
244+
245+
# Use curl to patch the pod
246+
curl -sSk -X PATCH \
247+
-H "Authorization: Bearer $TOKEN" \
248+
-H "Content-Type: $CONTENT_TYPE" \
249+
-H "Accept: application/json" \
250+
-d "$PATCH_JSON" \
251+
"$API_URL/api/v1/namespaces/$NAMESPACE/pods/$POD_NAME" | jq .metadata.annotations
252+
253+
{{ end }}

src/charts/actions-runner/values.yaml

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,29 @@ type: "repository" # can be either 'organization' or 'repository'
22
dind_enabled: true # If `true`, a Docker sidecar container will be deployed
33
# To run Docker in Docker (dind), change image from summerwind/actions-runner to summerwind/actions-runner-dind
44
image: summerwind/actions-runner-dind
5-
node_selector:
6-
kubernetes.io/os: "linux"
7-
kubernetes.io/arch: "amd64"
5+
86
#scope: "example/app"
9-
scale_down_delay_seconds: 300
10-
min_replicas: 1
11-
max_replicas: 2
7+
#scale_down_delay_seconds: 300
8+
#min_replicas: 1
9+
#max_replicas: 2
1210
#busy_metrics:
1311
# scale_up_threshold: 0.75
1412
# scale_down_threshold: 0.25
1513
# scale_up_factor: 2
1614
# scale_down_factor: 0.5
17-
resources:
18-
limits:
19-
cpu: 1.5
20-
memory: 4Gi
21-
# ephemeral_storage: "10Gi"
22-
requests:
23-
cpu: 0.5
24-
memory: 1Gi
25-
# ephemeral_storage: "10Gi"
15+
#resources:
16+
# limits:
17+
# cpu: 1
18+
# memory: 1Gi
19+
# ephemeral_storage: "10Gi"
20+
# requests:
21+
# cpu: 500m
22+
# memory: 512Mi
23+
# ephemeral_storage: "1Gi"
2624

27-
storage: "10Gi"
2825
pvc_enabled: false
2926
webhook_driven_scaling_enabled: true
30-
webhook_startup_timeout: "30m"
27+
webhook_startup_timeout: "90m"
3128
pull_driven_scaling_enabled: false
3229
#labels:
3330
# - "Ubuntu"

src/main.tf

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ data "aws_ssm_parameter" "docker_config_json" {
111111

112112
module "actions_runner_controller" {
113113
source = "cloudposse/helm-release/aws"
114-
version = "0.10.0"
114+
version = "0.10.1"
115115

116116
name = "" # avoids hitting length restrictions on IAM Role names
117117
chart = var.chart
@@ -140,14 +140,15 @@ module "actions_runner_controller" {
140140
file("${path.module}/resources/values.yaml"),
141141
# standard k8s object settings
142142
yamlencode({
143-
fullnameOverride = module.this.name,
143+
fullnameOverride = module.this.name
144144
serviceAccount = {
145145
name = module.this.name
146-
},
146+
}
147147
resources = var.resources
148148
rbac = {
149149
create = var.rbac_enabled
150150
}
151+
replicaCount = var.controller_replica_count
151152
githubWebhookServer = {
152153
enabled = var.webhook.enabled
153154
queueLimit = var.webhook.queue_limit
@@ -166,7 +167,7 @@ module "actions_runner_controller" {
166167
}
167168
]
168169
}
169-
},
170+
}
170171
authSecret = {
171172
enabled = true
172173
create = local.create_secret
@@ -201,7 +202,7 @@ module "actions_runner" {
201202
for_each = local.enabled ? var.runners : {}
202203

203204
source = "cloudposse/helm-release/aws"
204-
version = "0.10.0"
205+
version = "0.10.1"
205206

206207
name = each.key
207208
chart = "${path.module}/charts/actions-runner"
@@ -215,15 +216,16 @@ module "actions_runner" {
215216
values = compact([
216217
yamlencode({
217218
release_name = each.key
218-
pod_annotations = lookup(each.value, "pod_annotations", "")
219+
pod_annotations = each.value.pod_annotations
220+
running_pod_annotations = each.value.running_pod_annotations
219221
service_account_name = module.actions_runner_controller.service_account_name
220222
type = each.value.type
221223
scope = each.value.scope
222224
image = each.value.image
223225
dind_enabled = each.value.dind_enabled
224226
service_account_role_arn = module.actions_runner_controller.service_account_role_arn
225227
resources = each.value.resources
226-
storage = each.value.storage
228+
docker_storage = each.value.docker_storage != null ? each.value.docker_storage : each.value.storage
227229
labels = concat(each.value.labels, local.context_labels)
228230
scale_down_delay_seconds = each.value.scale_down_delay_seconds
229231
min_replicas = each.value.min_replicas
@@ -233,6 +235,7 @@ module "actions_runner" {
233235
pull_driven_scaling_enabled = each.value.pull_driven_scaling_enabled
234236
pvc_enabled = each.value.pvc_enabled
235237
node_selector = each.value.node_selector
238+
affinity = each.value.affinity
236239
tolerations = each.value.tolerations
237240
docker_config_json_enabled = local.docker_config_json_enabled
238241
docker_config_json = local.docker_config_json

0 commit comments

Comments
 (0)