[FEAT] Initial version of automatic Actions Runner deployment

This commit addresses issue #90 by adding options to enable Forgejo
Actions and, if desired, automatically deploy actions runners with a
specified number of replicas.

Due to the complexity of building the app.ini, we copy most of setup
done for Forgejo itself, but we do not attempt any database migration
or run Forgejo itself. Instead we take the (possibly partial) built
app.ini file and use it in order to register Actions Runner
tokens. Once these tokens are generated, we pass them to the Actions
Runner images and create a runner file. Finally, we invoke the Actions
Runner. Note that this setup process only happens once -- we generate
new runner tokens for a replica if and only if a runner file does not
already exist in the persistent volume claimed by the replica.

Note that these changes do NOT add support for custom runner
labels. These can probably come later.
This commit is contained in:
rome-user 2023-07-14 02:11:41 -07:00
parent 5b7cf738f4
commit ac02458d9b
4 changed files with 380 additions and 0 deletions

View file

@ -46,6 +46,21 @@ Create image name and tag used by the deployment.
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{/*
Create image name and tag used by the actions runner.
*/}}
{{- define "gitea.actions-image" -}}
{{- $registry := .Values.gitea.actions.runner.image.registry | default (.Values.global.imageRegistry | default .Values.image.registry) -}}
{{- $name := .Values.gitea.actions.runner.image.repository -}}
{{- $tag := .Values.gitea.actions.runner.image.tag -}}
{{- $rootless := ternary "-rootless" "" (or .Values.image.rootless .Values.gitea.actions.runner.image.rootless) -}}
{{- if $registry -}}
{{- printf "%s/%s:%s%s" $registry $name $tag $rootless -}}
{{- else -}}
{{- printf "%s:%s%s" $name $tag $rootless -}}
{{- end -}}
{{- end -}}
{{/* {{/*
Docker Image Registry Secret Names evaluating values as templates Docker Image Registry Secret Names evaluating values as templates
*/}} */}}
@ -91,6 +106,26 @@ app.kubernetes.io/name: {{ include "gitea.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}} {{- end -}}
{{/*
Actions runner labels
*/}}
{{- define "gitea.actions.runner.labels" -}}
helm.sh/chart: {{ include "gitea.chart" . }}
app: actions-runner
{{ include "gitea.actions.runner.selectorLabels" . }}
app.kubernetes.io/version: {{ .Values.gitea.actions.runner.image.tag | quote }}
version: {{ .Values.gitea.actions.runner.image.tag | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
{{/*
Actions runner selector labels
*/}}
{{- define "gitea.actions.runner.selectorLabels" -}}
app.kubernetes.io/name: actions-runner
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}
{{- define "postgresql.dns" -}} {{- define "postgresql.dns" -}}
{{- printf "%s-postgresql.%s.svc.%s:%g" .Release.Name .Release.Namespace .Values.clusterDomain .Values.postgresql.global.postgresql.service.ports.postgresql -}} {{- printf "%s-postgresql.%s.svc.%s:%g" .Release.Name .Release.Namespace .Values.clusterDomain .Values.postgresql.global.postgresql.service.ports.postgresql -}}
{{- end -}} {{- end -}}
@ -199,6 +234,9 @@ https
{{- if not (hasKey .Values.gitea.config "metrics") -}} {{- if not (hasKey .Values.gitea.config "metrics") -}}
{{- $_ := set .Values.gitea.config "metrics" dict -}} {{- $_ := set .Values.gitea.config "metrics" dict -}}
{{- end -}} {{- end -}}
{{- if not (hasKey .Values.gitea.config "actions") -}}
{{- $_ := set .Values.gitea.config "actions" dict -}}
{{- end -}}
{{- if not (hasKey .Values.gitea.config "database") -}} {{- if not (hasKey .Values.gitea.config "database") -}}
{{- $_ := set .Values.gitea.config "database" dict -}} {{- $_ := set .Values.gitea.config "database" dict -}}
{{- end -}} {{- end -}}
@ -226,6 +264,9 @@ https
{{- if not (hasKey .Values.gitea.config.metrics "ENABLED") -}} {{- if not (hasKey .Values.gitea.config.metrics "ENABLED") -}}
{{- $_ := set .Values.gitea.config.metrics "ENABLED" .Values.gitea.metrics.enabled -}} {{- $_ := set .Values.gitea.config.metrics "ENABLED" .Values.gitea.metrics.enabled -}}
{{- end -}} {{- end -}}
{{- if not (hasKey .Values.gitea.config.actions "ENABLED") -}}
{{- $_ := set .Values.gitea.config.actions "ENABLED" .Values.gitea.actions.enabled -}}
{{- end -}}
{{- if .Values.memcached.enabled -}} {{- if .Values.memcached.enabled -}}
{{- $_ := set .Values.gitea.config.cache "ENABLED" "true" -}} {{- $_ := set .Values.gitea.config.cache "ENABLED" "true" -}}
{{- $_ := set .Values.gitea.config.cache "ADAPTER" "memcache" -}} {{- $_ := set .Values.gitea.config.cache "ADAPTER" "memcache" -}}

View file

@ -127,3 +127,62 @@ stringData:
configure_oauth configure_oauth
echo '==== END GITEA CONFIGURATION ====' echo '==== END GITEA CONFIGURATION ===='
generate_token.sh: |-
#!/usr/bin/env bash
set -euo pipefail
status=0
if [ -f /data/.runner ];
then
echo 'Runner seems to already be configured. We are not creating a new token.'
else
echo 'Generating runner secret...'
forgejo forgejo-cli actions generate-secret > /tmp/runner-secret || {
echo 'Failed to write secret to /tmp/runner-secret!'
exit 1
}
# try
{
echo 'Registering generated secret...'
forgejo forgejo-cli actions register \
--secret-file /tmp/runner-secret \
--config /data/gitea/conf/app.ini
} ||
# catch
{
echo 'Failed to register!'
status=1
}
fi
exit $status
configure_runner.sh: |-
#!/bin/ash
set -euo pipefail
echo '==== BEGIN RUNNER CONFIGURATION ===='
if [ -f /data/.runner ];
then
echo 'Runner seems to already be configured. We are not creating a new token.'
else
forgejo-runner create-runner-file \
--secret $(cat /tmp/runner-secret) \
--instance http://{{ .Release.Name }}-http.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.http.port }}
echo 'Moving runner file to /data directory'
mv /.runner /data/.runner
echo 'Creating symlink from /data/.runner to /.runner'
ln -sf /data/.runner /.runner
fi
echo '==== END RUNNER CONFIGURATION ===='

260
templates/gitea/runner.yaml Normal file
View file

@ -0,0 +1,260 @@
{{- if .Values.gitea.actions.runner.enabled -}}
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: actions-runner
labels:
{{- include "gitea.actions.runner.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.gitea.actions.runner.replicas }}
selector:
matchLabels:
{{- include "gitea.actions.runner.selectorLabels" . | nindent 6 }}
serviceName: actions-runner
template:
metadata:
labels:
{{- include "gitea.actions.runner.labels" . | nindent 8 }}
spec:
{{- if .Values.schedulerName }}
schedulerName: "{{ .Values.schedulerName }}"
{{- end }}
{{- if .Values.priorityClassName }}
priorityClassName: "{{ .Values.priorityClassName }}"
{{- end }}
{{- include "gitea.images.pullSecrets" . | nindent 6 }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
initContainers:
# First wait for a Forgejo instance to come online. This is to guard
# against race conditions between forgejo and the actions runner when
# both are being set up for the first time.
- name: await-forgejo
image: alpine/curl
command:
- sh
args:
- -c
- |
retries=0
max=3
echo 'Waiting for Forgejo to come online before proceeding'
until $(curl --output /dev/null --silent --head --fail http://{{ include "gitea.fullname" . }}-http.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.http.port }});
do
retries=$((retries+1))
if [ $retries -gt $max ]; then
echo 'Max retries reached, exiting'
exit 1
fi
echo "Checking again in 5 seconds. Retry count: ${retries} of ${max}";
sleep 5;
done;
# We are recreating just enough of the app.ini so that we can invoke
# the forgejo-cli to register secrets and create runner files out the
# newly registered secrets.
- name: init-directories
image: "{{ include "gitea.image" . }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command: ["/usr/sbin/init_directory_structure.sh"]
env:
- name: GITEA_APP_INI
value: /data/gitea/conf/app.ini
- name: GITEA_CUSTOM
value: /data/gitea
- name: GITEA_WORK_DIR
value: /data
- name: GITEA_TEMP
value: /tmp/gitea
{{- if .Values.statefulset.env }}
{{- toYaml .Values.statefulset.env | nindent 12 }}
{{- end }}
volumeMounts:
- name: init
mountPath: /usr/sbin
- name: temp
mountPath: /tmp
- name: data
mountPath: /data
{{- if .Values.persistence.subPath }}
subPath: {{ .Values.persistence.subPath }}
{{- end }}
securityContext:
{{- toYaml .Values.containerSecurityContext | nindent 12 }}
resources:
{{- toYaml .Values.initContainers.resources | nindent 12 }}
- name: init-app-ini
image: "{{ include "gitea.image" . }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command: ["/usr/sbin/config_environment.sh"]
env:
- name: GITEA_APP_INI
value: /data/gitea/conf/app.ini
- name: GITEA_CUSTOM
value: /data/gitea
- name: GITEA_WORK_DIR
value: /data
- name: GITEA_TEMP
value: /tmp/gitea
{{- if .Values.statefulset.env }}
{{- toYaml .Values.statefulset.env | nindent 12 }}
{{- end }}
{{- if .Values.gitea.additionalConfigFromEnvs }}
{{- toYaml .Values.gitea.additionalConfigFromEnvs | nindent 12 }}
{{- end }}
volumeMounts:
- name: config
mountPath: /usr/sbin
- name: temp
mountPath: /tmp
- name: data
mountPath: /data
{{- if .Values.persistence.subPath }}
subPath: {{ .Values.persistence.subPath }}
{{- end }}
- name: inline-config-sources
mountPath: /env-to-ini-mounts/inlines/
{{- range $idx, $value := .Values.gitea.additionalConfigSources }}
- name: additional-config-sources-{{ $idx }}
mountPath: "/env-to-ini-mounts/additionals/{{ $idx }}/"
{{- end }}
securityContext:
{{- toYaml .Values.containerSecurityContext | nindent 12 }}
resources:
{{- toYaml .Values.initContainers.resources | nindent 12 }}
- name: configure-gitea
image: "{{ include "gitea.image" . }}"
command:
- sh
args:
- -c
- /usr/sbin/generate_token.sh
imagePullPolicy: {{ .Values.image.pullPolicy }}
securityContext:
{{- /* By default this container runs as user 1000 unless otherwise stated */ -}}
{{- $csc := deepCopy .Values.containerSecurityContext -}}
{{- if not (hasKey $csc "runAsUser") -}}
{{- $_ := set $csc "runAsUser" 1000 -}}
{{- end -}}
{{- toYaml $csc | nindent 12 }}
env:
- name: GITEA_APP_INI
value: /data/gitea/conf/app.ini
- name: GITEA_CUSTOM
value: /data/gitea
- name: GITEA_WORK_DIR
value: /data
- name: GITEA_TEMP
value: /tmp/gitea
volumeMounts:
- name: init
mountPath: /usr/sbin
- name: temp
mountPath: /tmp
- name: data
mountPath: /data
{{- if .Values.persistence.subPath }}
subPath: {{ .Values.persistence.subPath }}
{{- end }}
resources:
{{- toYaml .Values.initContainers.resources | nindent 12 }}
- name: configure-runner
image: "{{ include "gitea.actions-image" . }}"
imagePullPolicy: {{ default .Values.image.pullPolicy .Values.gitea.actions.runner.image.pullPolicy }}
command: ["/usr/sbin/configure_runner.sh"]
volumeMounts:
- name: init
mountPath: /usr/sbin
- name: data
mountPath: /data
- name: temp
mountPath: /tmp
containers:
- name: {{ .Chart.Name }}
image: "{{ include "gitea.actions-image" . }}"
imagePullPolicy: {{ default .Values.image.pullPolicy .Values.gitea.actions.runner.image.pullPolicy }}
env:
- name: DOCKER_HOST
value: tcp://localhost:2376
- name: DOCKER_CERT_PATH
value: /certs/client
- name: DOCKER_TLS_VERIFY
value: "1"
command:
- sh
args:
- -c
- |
if [ ! -L /.runner ]; then
ln -s /data/.runner /.runner;
fi;
/bin/forgejo-runner daemon
volumeMounts:
- name: certs
mountPath: /certs
- name: data
mountPath: /data
{{- if .Values.persistence.subPath }}
subPath: {{ .Values.persistence.subPath }}
{{- end }}
- name: temp
mountPath: /tmp
- name: daemon
image: docker:23.0.6-dind-rootless
env:
- name: DOCKER_TLS_CERTDIR
value: /certs
# NOTE: the container needs to run as root to configure dockerd, but
# dockerd itself is NOT run as root
securityContext:
privileged: true
volumeMounts:
- name: certs
mountPath: /certs
volumes:
- name: init
secret:
secretName: {{ include "gitea.fullname" . }}-init
defaultMode: 110
- name: config
secret:
secretName: {{ include "gitea.fullname" . }}
defaultMode: 110
{{- if gt (len .Values.extraVolumes) 0 }}
{{- toYaml .Values.extraVolumes | nindent 8 }}
{{- end }}
- name: inline-config-sources
secret:
secretName: {{ include "gitea.fullname" . }}-inline-config
{{- range $idx, $value := .Values.gitea.additionalConfigSources }}
- name: additional-config-sources-{{ $idx }}
{{- toYaml $value | nindent 10 }}
{{- end }}
- name: certs
emptyDir: {}
- name: temp
emptyDir: {}
volumeClaimTemplates:
- metadata:
name: data
{{- with .Values.persistence.annotations }}
annotations:
{{- range $key, $value := . }}
{{ $key }}: {{ $value }}
{{- end }}
{{- end }}
{{- with .Values.persistence.labels }}
labels:
{{- range $key, $value := . }}
{{ $key }}: {{ $value }}
{{- end }}
{{- end }}
spec:
accessModes:
{{- range .Values.persistence.accessModes }}
- {{ . | quote }}
{{- end }}
{{- include "gitea.persistence.storageClass" . | indent 8 }}
resources:
requests:
storage: {{ .Values.persistence.size | quote }}
{{- end }}

View file

@ -299,6 +299,26 @@ gitea:
password: r8sA8CPHD9!bt6d password: r8sA8CPHD9!bt6d
email: "gitea@local.domain" email: "gitea@local.domain"
## @param gitea.actions.enabled Enable Forgejo Actions
## @param gitea.actions.runner.enabled Enable automatic deployment of a runner
## @param gitea.actions.runner.image.registry Image registry, e.g. gcr.io,docker.io
## @param gitea.actions.runner.image.repository Image to start for this pod
## @param gitea.actions.runner.image.tag Visit: [Image tag](https://code.forgejo.org/forgejo/-/packages/container/runner/versions).
## @param gitea.actions.runner.image.pullPolicy Overrides the pull policy set globally for actions runners
## @param gitea.actions.runner.image.rootless Determines whether we use a rootless container or not
## @param gitea.actions.runner.replicas Number of replicas to automatically deploy
actions:
enabled: false
runner:
enabled: false
image:
registry: "code.forgejo.org"
repository: forgejo/runner
tag: "2.3.0"
pullPolicy: Always
rootless: false
replicas: 1
## @param gitea.metrics.enabled Enable Forgejo metrics ## @param gitea.metrics.enabled Enable Forgejo metrics
## @param gitea.metrics.serviceMonitor.enabled Enable Forgejo metrics service monitor ## @param gitea.metrics.serviceMonitor.enabled Enable Forgejo metrics service monitor
metrics: metrics: