Migrate Forgejo runner to Kubernetes with DinD (#60)
## Summary - Deploy Forgejo runner to k8s with Docker-in-Docker sidecar - Add job execution image with Node.js and Docker CLI - Retire host-mode runner on indri - All CI jobs now run containerized in k8s ## Components Added - `containers/forgejo-runner/Dockerfile` - Job execution image - `argocd/apps/forgejo-runner.yaml` - ArgoCD Application - `argocd/manifests/forgejo-runner/` - Kubernetes manifests ## Components Removed - `ansible/roles/forgejo_runner/` - No longer needed ## Changes to Existing Files - `.forgejo/workflows/build-container.yaml` - Use `k8s` runner with `DOCKER_HOST` env - `.github/actionlint.yaml` - Only `k8s` label now valid ## Deployment 1. Apply secret: `op inject -i argocd/manifests/forgejo-runner/secret.yaml.tpl | kubectl --context=minikube-indri apply -f -` 2. Sync ArgoCD: `argocd app sync forgejo-runner` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/60
This commit is contained in:
parent
66badfafd1
commit
ea42362b6f
14 changed files with 181 additions and 156 deletions
|
|
@ -15,7 +15,7 @@ on:
|
|||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: k8s
|
||||
steps:
|
||||
- name: Parse tag
|
||||
id: parse
|
||||
|
|
|
|||
4
.github/actionlint.yaml
vendored
4
.github/actionlint.yaml
vendored
|
|
@ -1,5 +1,3 @@
|
|||
self-hosted-runner:
|
||||
labels:
|
||||
- docker-builder
|
||||
- ubuntu-latest
|
||||
- ubuntu-22.04
|
||||
- k8s
|
||||
|
|
|
|||
|
|
@ -61,23 +61,6 @@
|
|||
no_log: true
|
||||
tags: [forgejo]
|
||||
|
||||
# Forgejo runner token (for indri-based runner)
|
||||
- name: Fetch forgejo runner token
|
||||
ansible.builtin.command:
|
||||
cmd: op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get w3663ffnvkewbftncqxtcpeavy --fields runner_reg --reveal
|
||||
delegate_to: localhost
|
||||
register: _forgejo_runner_token
|
||||
changed_when: false
|
||||
no_log: true
|
||||
check_mode: false
|
||||
tags: [forgejo_runner]
|
||||
|
||||
- name: Set forgejo runner token fact
|
||||
ansible.builtin.set_fact:
|
||||
forgejo_runner_token: "{{ _forgejo_runner_token.stdout }}"
|
||||
no_log: true
|
||||
tags: [forgejo_runner]
|
||||
|
||||
# Caddy Gandi token for ACME DNS-01 challenges
|
||||
- name: Fetch Gandi PAT for Caddy
|
||||
ansible.builtin.command:
|
||||
|
|
@ -114,7 +97,5 @@
|
|||
tags: minikube_metrics
|
||||
- role: plex_metrics
|
||||
tags: plex_metrics
|
||||
- role: forgejo_runner
|
||||
tags: forgejo_runner
|
||||
- role: caddy
|
||||
tags: caddy
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
---
|
||||
# Forgejo Runner - host execution mode
|
||||
#
|
||||
# The runner daemon runs directly on indri and executes jobs on the host.
|
||||
# This avoids container networking complexity since it can reach Forgejo
|
||||
# at localhost:3001 directly.
|
||||
|
||||
forgejo_runner_binary: /Users/erichblume/code/3rd/forgejo-runner/forgejo-runner
|
||||
forgejo_runner_data_dir: /Users/erichblume/.forgejo-runner
|
||||
forgejo_runner_config_dir: /Users/erichblume/.config/forgejo-runner
|
||||
forgejo_runner_log_dir: /Users/erichblume/Library/Logs
|
||||
|
||||
# Runner registration - use localhost since we're running on indri
|
||||
forgejo_runner_instance_url: "http://localhost:3001"
|
||||
forgejo_runner_name: "indri-host-runner"
|
||||
|
||||
# Labels format for host execution: label:host
|
||||
# Jobs run directly on the host, not in containers
|
||||
forgejo_runner_labels: "ubuntu-latest:host,ubuntu-22.04:host"
|
||||
|
||||
# Runner config
|
||||
forgejo_runner_capacity: 2
|
||||
forgejo_runner_timeout: 3h
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
- name: Restart forgejo-runner
|
||||
listen: Restart forgejo-runner
|
||||
ansible.builtin.shell: |
|
||||
launchctl unload ~/Library/LaunchAgents/mcquack.forgejo-runner.plist 2>/dev/null || true
|
||||
launchctl load ~/Library/LaunchAgents/mcquack.forgejo-runner.plist
|
||||
changed_when: true
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
---
|
||||
# Forgejo Runner - host execution mode
|
||||
#
|
||||
# The runner daemon runs directly on indri using a locally compiled binary.
|
||||
# Jobs execute on the host, reaching Forgejo at localhost:3001.
|
||||
|
||||
- name: Ensure forgejo-runner directories exist
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
loop:
|
||||
- "{{ forgejo_runner_data_dir }}"
|
||||
- "{{ forgejo_runner_config_dir }}"
|
||||
|
||||
- name: Deploy forgejo-runner config
|
||||
ansible.builtin.template:
|
||||
src: config.yaml.j2
|
||||
dest: "{{ forgejo_runner_config_dir }}/config.yaml"
|
||||
mode: '0644'
|
||||
notify: Restart forgejo-runner
|
||||
|
||||
- name: Check if runner is registered
|
||||
ansible.builtin.stat:
|
||||
path: "{{ forgejo_runner_data_dir }}/.runner"
|
||||
register: forgejo_runner_registered
|
||||
|
||||
- name: Register runner with Forgejo
|
||||
ansible.builtin.command:
|
||||
cmd: >
|
||||
{{ forgejo_runner_binary }} register
|
||||
--instance "{{ forgejo_runner_instance_url }}"
|
||||
--token "{{ forgejo_runner_token }}"
|
||||
--name "{{ forgejo_runner_name }}"
|
||||
--labels "{{ forgejo_runner_labels }}"
|
||||
--no-interactive
|
||||
chdir: "{{ forgejo_runner_data_dir }}"
|
||||
when: not forgejo_runner_registered.stat.exists
|
||||
changed_when: true
|
||||
|
||||
- name: Deploy forgejo-runner launchd plist
|
||||
ansible.builtin.template:
|
||||
src: forgejo-runner.plist.j2
|
||||
dest: ~/Library/LaunchAgents/mcquack.forgejo-runner.plist
|
||||
mode: '0644'
|
||||
notify: Restart forgejo-runner
|
||||
|
||||
- name: Check if forgejo-runner is loaded
|
||||
ansible.builtin.command: launchctl list mcquack.forgejo-runner
|
||||
register: forgejo_runner_launchctl_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Load forgejo-runner if not loaded
|
||||
ansible.builtin.command: launchctl load ~/Library/LaunchAgents/mcquack.forgejo-runner.plist
|
||||
when: forgejo_runner_launchctl_check.rc != 0
|
||||
changed_when: true
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
# {{ ansible_managed }}
|
||||
log:
|
||||
level: info
|
||||
|
||||
runner:
|
||||
file: {{ forgejo_runner_data_dir }}/.runner
|
||||
capacity: {{ forgejo_runner_capacity }}
|
||||
timeout: {{ forgejo_runner_timeout }}
|
||||
|
||||
# Even in host execution mode, some actions run in containers.
|
||||
# Use host networking so containers can access localhost services.
|
||||
container:
|
||||
network: "host"
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- {{ ansible_managed }} -->
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>mcquack.forgejo-runner</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>{{ forgejo_runner_binary }}</string>
|
||||
<string>daemon</string>
|
||||
<string>--config</string>
|
||||
<string>{{ forgejo_runner_config_dir }}/config.yaml</string>
|
||||
</array>
|
||||
<key>WorkingDirectory</key>
|
||||
<string>{{ forgejo_runner_data_dir }}</string>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>PATH</key>
|
||||
<string>/Users/erichblume/.local/share/mise/shims:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
||||
<key>HOME</key>
|
||||
<string>/Users/erichblume</string>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>StandardOutPath</key>
|
||||
<string>{{ forgejo_runner_log_dir }}/mcquack.forgejo-runner.out.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>{{ forgejo_runner_log_dir }}/mcquack.forgejo-runner.err.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
17
argocd/apps/forgejo-runner.yaml
Normal file
17
argocd/apps/forgejo-runner.yaml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: forgejo-runner
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://forge.ops.eblu.me/eblume/blumeops.git
|
||||
targetRevision: main
|
||||
path: argocd/manifests/forgejo-runner
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: forgejo-runner
|
||||
syncPolicy:
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
23
argocd/manifests/forgejo-runner/configmap.yaml
Normal file
23
argocd/manifests/forgejo-runner/configmap.yaml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: forgejo-runner-config
|
||||
namespace: forgejo-runner
|
||||
data:
|
||||
config.yaml: |
|
||||
log:
|
||||
level: info
|
||||
|
||||
runner:
|
||||
file: /data/.runner
|
||||
capacity: 2
|
||||
timeout: 3h
|
||||
# Set DOCKER_HOST in job containers so they can run docker commands
|
||||
envs:
|
||||
DOCKER_HOST: tcp://127.0.0.1:2375
|
||||
|
||||
container:
|
||||
# Use our custom job execution image with Node.js + Docker CLI
|
||||
network: "host"
|
||||
# Connect to DinD sidecar via TCP (not socket)
|
||||
docker_host: tcp://127.0.0.1:2375
|
||||
77
argocd/manifests/forgejo-runner/deployment.yaml
Normal file
77
argocd/manifests/forgejo-runner/deployment.yaml
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: forgejo-runner
|
||||
namespace: forgejo-runner
|
||||
labels:
|
||||
app: forgejo-runner
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: forgejo-runner
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: forgejo-runner
|
||||
spec:
|
||||
containers:
|
||||
# Forgejo runner daemon
|
||||
- name: runner
|
||||
image: code.forgejo.org/forgejo/runner:6.3.1
|
||||
env:
|
||||
- name: DOCKER_HOST
|
||||
value: tcp://localhost:2375
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
# Wait for DinD to be ready
|
||||
echo "Waiting for Docker daemon..."
|
||||
while ! wget -q -O /dev/null http://localhost:2375/_ping 2>/dev/null; do
|
||||
sleep 1
|
||||
done
|
||||
echo "Docker daemon ready"
|
||||
|
||||
# Register if not already registered
|
||||
if [ ! -f /data/.runner ]; then
|
||||
echo "Registering runner..."
|
||||
forgejo-runner register \
|
||||
--instance "$FORGEJO_URL" \
|
||||
--token "$RUNNER_TOKEN" \
|
||||
--name "$RUNNER_NAME" \
|
||||
--labels "$RUNNER_LABELS" \
|
||||
--no-interactive
|
||||
fi
|
||||
|
||||
# Start daemon
|
||||
exec forgejo-runner daemon --config /config/config.yaml
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: forgejo-runner-env
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
- name: config
|
||||
mountPath: /config
|
||||
|
||||
# Docker-in-Docker sidecar
|
||||
- name: dind
|
||||
image: docker:27-dind
|
||||
securityContext:
|
||||
privileged: true
|
||||
env:
|
||||
- name: DOCKER_TLS_CERTDIR
|
||||
value: ""
|
||||
volumeMounts:
|
||||
- name: dind-storage
|
||||
mountPath: /var/lib/docker
|
||||
|
||||
volumes:
|
||||
- name: data
|
||||
emptyDir: {}
|
||||
- name: dind-storage
|
||||
emptyDir: {}
|
||||
- name: config
|
||||
configMap:
|
||||
name: forgejo-runner-config
|
||||
4
argocd/manifests/forgejo-runner/namespace.yaml
Normal file
4
argocd/manifests/forgejo-runner/namespace.yaml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: forgejo-runner
|
||||
17
argocd/manifests/forgejo-runner/secret.yaml.tpl
Normal file
17
argocd/manifests/forgejo-runner/secret.yaml.tpl
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Forgejo Runner Environment Secret
|
||||
# This template is processed by `op inject` to resolve 1Password references.
|
||||
#
|
||||
# Usage:
|
||||
# op inject -i secret.yaml.tpl | kubectl --context=minikube-indri apply -f -
|
||||
#
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: forgejo-runner-env
|
||||
namespace: forgejo-runner
|
||||
type: Opaque
|
||||
stringData:
|
||||
FORGEJO_URL: "https://forge.ops.eblu.me"
|
||||
RUNNER_NAME: "k8s-runner"
|
||||
RUNNER_LABELS: "k8s:docker://registry.ops.eblu.me/blumeops/forgejo-runner:v2.1.7"
|
||||
RUNNER_TOKEN: "{{ op://vg6xf6vvfmoh5hqjjhlhbeoaie/w3663ffnvkewbftncqxtcpeavy/runner_reg }}"
|
||||
41
containers/forgejo-runner/Dockerfile
Normal file
41
containers/forgejo-runner/Dockerfile
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# Forgejo Actions Job Execution Image
|
||||
#
|
||||
# This image is used as the job execution environment for Forgejo Actions.
|
||||
# The host runner daemon creates containers from this image to run workflow steps.
|
||||
#
|
||||
# Includes: Node.js (for GitHub Actions), Docker CLI, git, and common CI tools.
|
||||
#
|
||||
# Usage: Configure runner with label like:
|
||||
# docker:docker://registry.ops.eblu.me/blumeops/forgejo-runner:latest
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
ARG TARGETARCH
|
||||
|
||||
# Install base dependencies
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
git \
|
||||
jq \
|
||||
gnupg \
|
||||
lsb-release \
|
||||
xz-utils \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Node.js 20.x (required for actions/checkout@v4 and other GitHub Actions)
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
||||
&& apt-get install -y --no-install-recommends nodejs \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Docker CLI (for container builds - daemon accessed via socket mount)
|
||||
RUN install -m 0755 -d /etc/apt/keyrings \
|
||||
&& curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc \
|
||||
&& chmod a+r /etc/apt/keyrings/docker.asc \
|
||||
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends docker-ce-cli \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Default to bash
|
||||
CMD ["/bin/bash"]
|
||||
Loading…
Add table
Add a link
Reference in a new issue