Files
act_runner/docs/kubernetes.md
silverwind 0cedd98051 feat: add Kubernetes backend (#1260)
Adds a Kubernetes backend. A `name:kubernetes://image` label runs the job as one pod through the Kubernetes API, with services as containers of the same pod. Step env is exported by a script sent over exec stdin, keeping it out of the pod spec and process arguments, and service env goes into a per-job Secret. Docker actions and container options are rejected. See `docs/kubernetes.md`.

Operators shape job pods with `kubernetes.pod_template`, with per-label `pod_templates` merged in `runs-on` order and a `$services` entry for service containers. The idle cleanup removes pods and Secrets a runner left behind.

The client calls the Kubernetes API directly and runs step commands over `coder/websocket` (same dependency used in gitea) instead of pulling in the heavyweight `kubernetes/client-go`. Includes an e2e test against k3s.

Replaces: https://gitea.com/gitea/runner/pulls/1000
Fixes: https://gitea.com/gitea/runner/issues/31

Assisted-by: Claude Code:claude-opus-5-5
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: bircni <bircni@icloud.com>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1260
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Zettat123 <39446+zettat123@noreply.gitea.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-01 06:59:15 +00:00

3.3 KiB

Jobs as Kubernetes pods

A kubernetes label runs each job as a pod, without a Docker daemon:

ubuntu-latest:kubernetes://docker.gitea.com/runner-images:ubuntu-latest

The pod has a job container from the label's image or the job's container.image, plus one container per service.

Setup

In a cluster, the runner uses its pod's service account and namespace. examples/kubernetes/kubernetes-pods.yaml deploys a runner with these permissions:

Resource Verbs
pods create, get, list, delete
pods/exec create, get
pods/log get
secrets create, delete

Outside a cluster, it uses the current context of the first file in $KUBECONFIG, or of ~/.kube/config, which must authenticate with a token, token file or client certificate. The cluster must run Kubernetes 1.30 or later, and job images need sh, tar, tail and env.

Configuration

kubernetes:
  kubeconfig: "" # instead of the service account
  namespace: "" # defaults to the service account's, the kubeconfig context's, or default
  pod_template: # merged under each job pod, a container named job merges into the job container, one named $services into each service container
    spec:
      containers:
        - name: job
          resources:
            limits:
              memory: 4Gi
        - name: $services
          resources:
            limits:
              memory: 1Gi
  pod_templates: # merged over pod_template for jobs whose runs-on has the label they are keyed by
    gpu:
      spec:
        nodeSelector:
          nvidia.com/gpu.present: "true"

A job with runs-on: [linux, gpu] gets the linux and then the gpu entry of pod_templates merged over pod_template, while its image comes from the first of its labels the runner has. A template volume named workspace or act replaces the emptyDir the runner mounts at the workspace or at /var/run/act, for example to limit its size. Images are pulled by the cluster's imagePullPolicy default, a template's, or always with container.force_pull.

Job pods mount no service account token unless the template sets automountServiceAccountToken: true. Job pods and their Secrets are labeled app.kubernetes.io/managed-by: gitea-runner and com.gitea.runner.uuid: <runner UUID>. The idle cleanup removes those the runner did not get to remove, once they are older than runner.workdir_cleanup_age.

Services

Services share the pod's network, so a step reaches one by its id or localhost, and two services cannot listen on the same port. An id is lowercased and has characters other than letters, digits and - replaced by -, so the service my_db is reached as my-db. Pass secrets to services through env, as command is visible in the pod spec. Of options, only --health-* is supported. They become a startup probe with Docker's defaults, which stops a service that fails its retries, so the job fails as on Docker.

Unsupported

Docker actions, docker:// steps, and the options, volumes and credentials of the job container fail the job, as do volumes and credentials of services. Run such workflows on a Docker in Docker runner. Pull private images with imagePullSecrets in pod_template.