[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:
parent
5b7cf738f4
commit
ac02458d9b
4 changed files with 380 additions and 0 deletions
|
@ -46,6 +46,21 @@ Create image name and tag used by the deployment.
|
|||
{{- 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
|
||||
*/}}
|
||||
|
@ -91,6 +106,26 @@ app.kubernetes.io/name: {{ include "gitea.name" . }}
|
|||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- 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" -}}
|
||||
{{- printf "%s-postgresql.%s.svc.%s:%g" .Release.Name .Release.Namespace .Values.clusterDomain .Values.postgresql.global.postgresql.service.ports.postgresql -}}
|
||||
{{- end -}}
|
||||
|
@ -199,6 +234,9 @@ https
|
|||
{{- if not (hasKey .Values.gitea.config "metrics") -}}
|
||||
{{- $_ := set .Values.gitea.config "metrics" dict -}}
|
||||
{{- end -}}
|
||||
{{- if not (hasKey .Values.gitea.config "actions") -}}
|
||||
{{- $_ := set .Values.gitea.config "actions" dict -}}
|
||||
{{- end -}}
|
||||
{{- if not (hasKey .Values.gitea.config "database") -}}
|
||||
{{- $_ := set .Values.gitea.config "database" dict -}}
|
||||
{{- end -}}
|
||||
|
@ -226,6 +264,9 @@ https
|
|||
{{- if not (hasKey .Values.gitea.config.metrics "ENABLED") -}}
|
||||
{{- $_ := set .Values.gitea.config.metrics "ENABLED" .Values.gitea.metrics.enabled -}}
|
||||
{{- end -}}
|
||||
{{- if not (hasKey .Values.gitea.config.actions "ENABLED") -}}
|
||||
{{- $_ := set .Values.gitea.config.actions "ENABLED" .Values.gitea.actions.enabled -}}
|
||||
{{- end -}}
|
||||
{{- if .Values.memcached.enabled -}}
|
||||
{{- $_ := set .Values.gitea.config.cache "ENABLED" "true" -}}
|
||||
{{- $_ := set .Values.gitea.config.cache "ADAPTER" "memcache" -}}
|
||||
|
|
|
@ -127,3 +127,62 @@ stringData:
|
|||
configure_oauth
|
||||
|
||||
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
260
templates/gitea/runner.yaml
Normal 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 }}
|
20
values.yaml
20
values.yaml
|
@ -299,6 +299,26 @@ gitea:
|
|||
password: r8sA8CPHD9!bt6d
|
||||
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.serviceMonitor.enabled Enable Forgejo metrics service monitor
|
||||
metrics:
|
||||
|
|
Loading…
Reference in a new issue