enhance(kubernetes): fetch job pod copies from the runner over the pod network (#1354)

The Kubernetes backend copies actions and workspaces into job containers with `tar` over `pods/exec`, so every byte goes through the API server. On managed clusters that often sits in other zones than the nodes, so each byte can cross zones twice, and the copies load the API server.

With `kubernetes.transfer.mode: auto` or `direct`, an init container copies `gitea-runner` into the job pod, and the job container fetches directories and copies of 64 KiB or more gzipped from the runner over the pod network. `auto` falls back to the API server for a job pod that cannot reach the runner, `direct` fails the job.

Copies can hold secrets, so each is served once, over TLS 1.3 pinned to a certificate the runner generates, only to the job pod's IPs.

Copying setup-go@v5, cache@v4, setup-java@v4 and build-push-action@v6 sends 11.6 MB through the API server on main and 532 B with this PR.

Co-authored-by: silverwind <[email protected]>
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1354
Reviewed-by: silverwind <[email protected]>
Co-authored-by: Lunny Xiao <[email protected]>
This commit is contained in:
authored and silverwind committed 2026-10-31 08:00:36 +00:00
1 parent f4741df90d
commit 2e72cf902a
12 files changed
+667 -9

No files matched your search

+25
View File
@@ -210,6 +210,25 @@ type Kubernetes struct {
Namespace string `yaml:"namespace"` // Namespace of job pods, empty uses the service account's, else the kubeconfig context's, else default.
PodTemplate map[string]any `yaml:"pod_template"` // PodTemplate is a pod manifest the runner merges its own onto.
PodTemplates map[string]map[string]any `yaml:"pod_templates"` // PodTemplates are merged over PodTemplate, in runs-on order, for jobs whose runs-on has the label they are keyed by.
Transfer KubernetesTransfer `yaml:"transfer"` // Transfer has job pods fetch copied files from the runner over the pod network instead of through the API server.
}
// Kubernetes transfer modes: how files the runner copies reach job pods.
const (
KubernetesTransferModeAPI = "api" // through the API server
KubernetesTransferModeAuto = "auto" // over the pod network, through the API server for a job pod that cannot reach the runner
KubernetesTransferModeDirect = "direct" // over the pod network only, a copy that cannot reach the runner fails
)
// KubernetesTransferModes are the valid kubernetes.transfer.mode values.
var KubernetesTransferModes = []string{KubernetesTransferModeAPI, KubernetesTransferModeAuto, KubernetesTransferModeDirect}
// KubernetesTransfer configures copying files into job pods over the pod network.
type KubernetesTransfer struct {
Mode string `yaml:"mode"` // Mode is one of KubernetesTransferModes, api when empty.
Host string `yaml:"host"` // Host is the address the runner listens on and job pods reach it at, empty uses the outbound IP.
Port uint16 `yaml:"port"` // Port the runner listens on for job pods, 0 picks a free one.
Image string `yaml:"image"` // Image has gitea-runner on its PATH, empty uses the image of a single-container runner pod.
}
// Metrics represents the configuration for the Prometheus metrics endpoint.
@@ -346,6 +365,12 @@ func LoadDefault(file string) (*Config, error) {
if !slices.Contains(ToolCacheModes, cfg.Runner.ToolCacheMode) {
return nil, fmt.Errorf("invalid runner.tool_cache_mode %q: must be one of %q", cfg.Runner.ToolCacheMode, ToolCacheModes)
}
if cfg.Kubernetes.Transfer.Mode == "" {
cfg.Kubernetes.Transfer.Mode = KubernetesTransferModeAPI
}
if !slices.Contains(KubernetesTransferModes, cfg.Kubernetes.Transfer.Mode) {
return nil, fmt.Errorf("invalid kubernetes.transfer.mode %q: must be one of %q", cfg.Kubernetes.Transfer.Mode, KubernetesTransferModes)
}
if cfg.Runner.LogReportLevel != "" {
if _, err := logging.ParseLevel(cfg.Runner.LogReportLevel); err != nil {
return nil, fmt.Errorf("invalid runner.log_report_level: %w", err)