mirror of
https://github.com/k3s-io/k3s.git
synced 2026-05-28 04:34:19 -04:00
Add support for the "nix" snapshotter, which enables running container images built with nix2container. Nix images reference store paths directly, avoiding layer tarballs and enabling deduplication through the nix store. Changes: - Register nix-snapshotter as a builtin containerd plugin - Add NixSupported() validation (checks nix-store is in PATH) - Configure nix-snapshotter image service proxy in V2/V3 templates with containerd_address for CRI image operations - Add Transfer service unpack_config with differ=walking for multi-arch support - Use containerd state dir for socket path (rootless compatible) - Disable NRI in rootless mode to prevent bind failures Usage: k3s server --snapshotter nix Signed-off-by: Ada <ada@6bit.com> Co-Authored-By: Joshua Perry <josh@6bit.com> Signed-off-by: Ada <ada@6bit.com>
411 lines
14 KiB
Go
411 lines
14 KiB
Go
package templates
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"io"
|
|
"net/url"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/rancher/wharfie/pkg/registries"
|
|
|
|
"github.com/k3s-io/k3s/pkg/daemons/config"
|
|
"github.com/k3s-io/k3s/pkg/version"
|
|
)
|
|
|
|
type ContainerdRuntimeConfig struct {
|
|
RuntimeType string
|
|
BinaryName string
|
|
}
|
|
|
|
type ContainerdConfig struct {
|
|
NodeConfig *config.Node
|
|
DisableCgroup bool
|
|
SystemdCgroup bool
|
|
IsRunningInUserNS bool
|
|
EnableUnprivileged bool
|
|
NoDefaultEndpoint bool
|
|
NonrootDevices bool
|
|
PrivateRegistryConfig *registries.Registry
|
|
ExtraRuntimes map[string]ContainerdRuntimeConfig
|
|
Program string
|
|
}
|
|
|
|
type RegistryEndpoint struct {
|
|
OverridePath bool
|
|
URL *url.URL
|
|
Rewrites map[string]string
|
|
Config registries.RegistryConfig
|
|
}
|
|
|
|
type HostConfig struct {
|
|
Default *RegistryEndpoint
|
|
Program string
|
|
Endpoints []RegistryEndpoint
|
|
}
|
|
|
|
// This version 2 config template is used by both Linux and Windows nodes
|
|
const ContainerdConfigTemplate = `
|
|
{{- /* */ -}}
|
|
# File generated by {{ .Program }}. DO NOT EDIT. Use config.toml.tmpl instead.
|
|
version = 2
|
|
imports = [{{ filepathjoin .NodeConfig.Containerd.Template "config.toml.d" "*.toml" | printf "%q" }}]
|
|
root = {{ printf "%q" .NodeConfig.Containerd.Root }}
|
|
state = {{ printf "%q" .NodeConfig.Containerd.State }}
|
|
|
|
[grpc]
|
|
address = {{ deschemify .NodeConfig.Containerd.Address | printf "%q" }}
|
|
|
|
[plugins."io.containerd.internal.v1.opt"]
|
|
path = {{ printf "%q" .NodeConfig.Containerd.Opt }}
|
|
|
|
[plugins."io.containerd.grpc.v1.cri"]
|
|
stream_server_address = "127.0.0.1"
|
|
stream_server_port = "10010"
|
|
enable_selinux = {{ .NodeConfig.SELinux }}
|
|
enable_unprivileged_ports = {{ .EnableUnprivileged }}
|
|
enable_unprivileged_icmp = {{ .EnableUnprivileged }}
|
|
device_ownership_from_security_context = {{ .NonrootDevices }}
|
|
|
|
{{- if .DisableCgroup}}
|
|
disable_cgroup = true
|
|
{{end}}
|
|
{{- if .IsRunningInUserNS }}
|
|
disable_apparmor = true
|
|
restrict_oom_score_adj = true
|
|
{{end}}
|
|
|
|
{{- if .NodeConfig.AgentConfig.PauseImage }}
|
|
sandbox_image = "{{ .NodeConfig.AgentConfig.PauseImage }}"
|
|
{{end}}
|
|
|
|
{{- if .NodeConfig.AgentConfig.Snapshotter }}
|
|
[plugins."io.containerd.grpc.v1.cri".containerd]
|
|
snapshotter = "{{ .NodeConfig.AgentConfig.Snapshotter }}"
|
|
disable_snapshot_annotations = {{ if or (eq .NodeConfig.AgentConfig.Snapshotter "stargz") (eq .NodeConfig.AgentConfig.Snapshotter "nix") }}false{{else}}true{{end}}
|
|
{{ if .NodeConfig.DefaultRuntime }}default_runtime_name = "{{ .NodeConfig.DefaultRuntime }}"{{end}}
|
|
{{ if eq .NodeConfig.AgentConfig.Snapshotter "stargz" }}
|
|
{{ if .NodeConfig.AgentConfig.ImageServiceSocket }}
|
|
[plugins."io.containerd.snapshotter.v1.stargz"]
|
|
cri_keychain_image_service_path = "{{ .NodeConfig.AgentConfig.ImageServiceSocket }}"
|
|
[plugins."io.containerd.snapshotter.v1.stargz".cri_keychain]
|
|
enable_keychain = true
|
|
{{end}}
|
|
|
|
[plugins."io.containerd.snapshotter.v1.stargz".registry]
|
|
config_path = {{ printf "%q" .NodeConfig.Containerd.Registry }}
|
|
|
|
{{ if .PrivateRegistryConfig }}
|
|
{{range $k, $v := .PrivateRegistryConfig.Configs }}
|
|
{{ if $v.Auth }}
|
|
[plugins."io.containerd.snapshotter.v1.stargz".registry.configs."{{$k}}".auth]
|
|
{{ if $v.Auth.Username }}username = {{ printf "%q" $v.Auth.Username }}{{end}}
|
|
{{ if $v.Auth.Password }}password = {{ printf "%q" $v.Auth.Password }}{{end}}
|
|
{{ if $v.Auth.Auth }}auth = {{ printf "%q" $v.Auth.Auth }}{{end}}
|
|
{{ if $v.Auth.IdentityToken }}identitytoken = {{ printf "%q" $v.Auth.IdentityToken }}{{end}}
|
|
{{end}}
|
|
{{end}}
|
|
{{end}}
|
|
{{end}}
|
|
{{end}}
|
|
|
|
{{ if eq .NodeConfig.AgentConfig.Snapshotter "nix" }}
|
|
{{ if .NodeConfig.AgentConfig.ImageServiceSocket }}
|
|
[plugins."io.containerd.snapshotter.v1.nix"]
|
|
address = "{{ .NodeConfig.AgentConfig.ImageServiceSocket }}"
|
|
|
|
[plugins."io.containerd.snapshotter.v1.nix".image_service]
|
|
enable = true
|
|
containerd_address = {{ deschemify .NodeConfig.Containerd.Address | printf "%q" }}
|
|
|
|
[[plugins."io.containerd.transfer.v1.local".unpack_config]]
|
|
platform = "linux/amd64"
|
|
snapshotter = "nix"
|
|
differ = "walking"
|
|
|
|
[[plugins."io.containerd.transfer.v1.local".unpack_config]]
|
|
platform = "linux/arm64"
|
|
snapshotter = "nix"
|
|
differ = "walking"
|
|
{{end}}
|
|
{{end}}
|
|
|
|
{{- if or .NodeConfig.AgentConfig.CNIBinDir .NodeConfig.AgentConfig.CNIConfDir }}
|
|
[plugins."io.containerd.grpc.v1.cri".cni]
|
|
{{ if .NodeConfig.AgentConfig.CNIBinDir }}bin_dir = {{ printf "%q" .NodeConfig.AgentConfig.CNIBinDir }}{{end}}
|
|
{{ if .NodeConfig.AgentConfig.CNIConfDir }}conf_dir = {{ printf "%q" .NodeConfig.AgentConfig.CNIConfDir }}{{end}}
|
|
{{end}}
|
|
|
|
{{- if or .NodeConfig.Containerd.BlockIOConfig .NodeConfig.Containerd.RDTConfig }}
|
|
[plugins."io.containerd.service.v1.tasks-service"]
|
|
{{ if .NodeConfig.Containerd.BlockIOConfig }}blockio_config_file = {{ printf "%q" .NodeConfig.Containerd.BlockIOConfig }}{{end}}
|
|
{{ if .NodeConfig.Containerd.RDTConfig }}rdt_config_file = {{ printf "%q" .NodeConfig.Containerd.RDTConfig }}{{end}}
|
|
{{end}}
|
|
|
|
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
|
|
runtime_type = "io.containerd.runc.v2"
|
|
|
|
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
|
|
SystemdCgroup = {{ .SystemdCgroup }}
|
|
|
|
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runhcs-wcow-process]
|
|
runtime_type = "io.containerd.runhcs.v1"
|
|
|
|
[plugins."io.containerd.grpc.v1.cri".registry]
|
|
config_path = {{ printf "%q" .NodeConfig.Containerd.Registry }}
|
|
|
|
{{ if .PrivateRegistryConfig }}
|
|
{{range $k, $v := .PrivateRegistryConfig.Configs }}
|
|
{{ if $v.Auth }}
|
|
[plugins."io.containerd.grpc.v1.cri".registry.configs."{{$k}}".auth]
|
|
{{ if $v.Auth.Username }}username = {{ printf "%q" $v.Auth.Username }}{{end}}
|
|
{{ if $v.Auth.Password }}password = {{ printf "%q" $v.Auth.Password }}{{end}}
|
|
{{ if $v.Auth.Auth }}auth = {{ printf "%q" $v.Auth.Auth }}{{end}}
|
|
{{ if $v.Auth.IdentityToken }}identitytoken = {{ printf "%q" $v.Auth.IdentityToken }}{{end}}
|
|
{{end}}
|
|
{{end}}
|
|
{{end}}
|
|
|
|
{{range $k, $v := .ExtraRuntimes}}
|
|
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes."{{$k}}"]
|
|
runtime_type = "{{$v.RuntimeType}}"
|
|
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes."{{$k}}".options]
|
|
BinaryName = "{{$v.BinaryName}}"
|
|
SystemdCgroup = {{ $.SystemdCgroup }}
|
|
{{end}}
|
|
`
|
|
|
|
// This version 3 config template is used by both Linux and Windows nodes
|
|
const ContainerdConfigTemplateV3 = `
|
|
{{- /* */ -}}
|
|
# File generated by {{ .Program }}. DO NOT EDIT. Use config-v3.toml.tmpl instead.
|
|
version = 3
|
|
imports = [{{ filepathjoin .NodeConfig.Containerd.Template "config-v3.toml.d" "*.toml" | printf "%q" }}]
|
|
root = {{ printf "%q" .NodeConfig.Containerd.Root }}
|
|
state = {{ printf "%q" .NodeConfig.Containerd.State }}
|
|
|
|
[grpc]
|
|
address = {{ deschemify .NodeConfig.Containerd.Address | printf "%q" }}
|
|
|
|
[plugins.'io.containerd.internal.v1.opt']
|
|
path = {{ printf "%q" .NodeConfig.Containerd.Opt }}
|
|
|
|
[plugins.'io.containerd.grpc.v1.cri']
|
|
stream_server_address = "127.0.0.1"
|
|
stream_server_port = "10010"
|
|
|
|
[plugins.'io.containerd.cri.v1.runtime']
|
|
enable_selinux = {{ .NodeConfig.SELinux }}
|
|
enable_unprivileged_ports = {{ .EnableUnprivileged }}
|
|
enable_unprivileged_icmp = {{ .EnableUnprivileged }}
|
|
device_ownership_from_security_context = {{ .NonrootDevices }}
|
|
|
|
{{ if .DisableCgroup}}
|
|
disable_cgroup = true
|
|
{{ end }}
|
|
|
|
{{ if .IsRunningInUserNS }}
|
|
disable_apparmor = true
|
|
restrict_oom_score_adj = true
|
|
{{ end }}
|
|
|
|
{{ with .NodeConfig.AgentConfig.Snapshotter }}
|
|
[plugins.'io.containerd.cri.v1.images']
|
|
snapshotter = "{{ . }}"
|
|
disable_snapshot_annotations = {{ if or (eq . "stargz") (eq . "nix") }}false{{else}}true{{end}}
|
|
use_local_image_pull = true
|
|
{{ end }}
|
|
|
|
{{ with .NodeConfig.AgentConfig.PauseImage }}
|
|
[plugins.'io.containerd.cri.v1.images'.pinned_images]
|
|
sandbox = "{{ . }}"
|
|
{{ end }}
|
|
|
|
{{- if or .NodeConfig.AgentConfig.CNIBinDir .NodeConfig.AgentConfig.CNIConfDir }}
|
|
[plugins.'io.containerd.cri.v1.runtime'.cni]
|
|
{{ with .NodeConfig.AgentConfig.CNIBinDir }}bin_dirs = [{{ printf "%q" . }}]{{ end }}
|
|
{{ with .NodeConfig.AgentConfig.CNIConfDir }}conf_dir = {{ printf "%q" . }}{{ end }}
|
|
{{ end }}
|
|
|
|
{{ if or .NodeConfig.Containerd.BlockIOConfig .NodeConfig.Containerd.RDTConfig }}
|
|
[plugins.'io.containerd.service.v1.tasks-service']
|
|
{{ with .NodeConfig.Containerd.BlockIOConfig }}blockio_config_file = {{ printf "%q" . }}{{ end }}
|
|
{{ with .NodeConfig.Containerd.RDTConfig }}rdt_config_file = {{ printf "%q" . }}{{ end }}
|
|
{{ end }}
|
|
|
|
{{ with .NodeConfig.DefaultRuntime }}
|
|
[plugins.'io.containerd.cri.v1.runtime'.containerd]
|
|
default_runtime_name = "{{ . }}"
|
|
{{ end }}
|
|
|
|
[plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc]
|
|
runtime_type = "io.containerd.runc.v2"
|
|
|
|
[plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc.options]
|
|
SystemdCgroup = {{ .SystemdCgroup }}
|
|
|
|
[plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runhcs-wcow-process]
|
|
runtime_type = "io.containerd.runhcs.v1"
|
|
|
|
{{ range $k, $v := .ExtraRuntimes }}
|
|
[plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.'{{ $k }}']
|
|
runtime_type = "{{$v.RuntimeType}}"
|
|
{{ with $v.BinaryName}}
|
|
[plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.'{{ $k }}'.options]
|
|
BinaryName = {{ printf "%q" . }}
|
|
SystemdCgroup = {{ $.SystemdCgroup }}
|
|
{{ end }}
|
|
{{ end }}
|
|
|
|
[plugins.'io.containerd.cri.v1.images'.registry]
|
|
config_path = {{ printf "%q" .NodeConfig.Containerd.Registry }}
|
|
|
|
{{ if .PrivateRegistryConfig }}
|
|
{{ range $k, $v := .PrivateRegistryConfig.Configs }}
|
|
{{ with $v.Auth }}
|
|
[plugins.'io.containerd.cri.v1.images'.registry.configs.'{{ $k }}'.auth]
|
|
{{ with .Username }}username = {{ printf "%q" . }}{{ end }}
|
|
{{ with .Password }}password = {{ printf "%q" . }}{{ end }}
|
|
{{ with .Auth }}auth = {{ printf "%q" . }}{{ end }}
|
|
{{ with .IdentityToken }}identitytoken = {{ printf "%q" . }}{{ end }}
|
|
{{ end }}
|
|
{{ end }}
|
|
{{ end }}
|
|
|
|
{{ if eq .NodeConfig.AgentConfig.Snapshotter "stargz" }}
|
|
{{ with .NodeConfig.AgentConfig.ImageServiceSocket }}
|
|
[plugins.'io.containerd.snapshotter.v1.stargz']
|
|
cri_keychain_image_service_path = {{ printf "%q" . }}
|
|
|
|
[plugins.'io.containerd.snapshotter.v1.stargz'.cri_keychain]
|
|
enable_keychain = true
|
|
{{ end }}
|
|
|
|
[plugins.'io.containerd.snapshotter.v1.stargz'.registry]
|
|
config_path = {{ printf "%q" .NodeConfig.Containerd.Registry }}
|
|
|
|
{{ if .PrivateRegistryConfig }}
|
|
{{ range $k, $v := .PrivateRegistryConfig.Configs }}
|
|
{{ with $v.Auth }}
|
|
[plugins.'io.containerd.snapshotter.v1.stargz'.registry.configs.'{{ $k }}'.auth]
|
|
{{ with .Username }}username = {{ printf "%q" . }}{{ end }}
|
|
{{ with .Password }}password = {{ printf "%q" . }}{{ end }}
|
|
{{ with .Auth }}auth = {{ printf "%q" . }}{{ end }}
|
|
{{ with .IdentityToken }}identitytoken = {{ printf "%q" . }}{{ end }}
|
|
{{ end }}
|
|
{{ end }}
|
|
{{ end }}
|
|
{{ end }}
|
|
|
|
{{ if eq .NodeConfig.AgentConfig.Snapshotter "nix" }}
|
|
{{ with .NodeConfig.AgentConfig.ImageServiceSocket }}
|
|
[plugins.'io.containerd.snapshotter.v1.nix']
|
|
address = {{ printf "%q" . }}
|
|
|
|
[plugins.'io.containerd.snapshotter.v1.nix'.image_service]
|
|
enable = true
|
|
containerd_address = {{ deschemify $.NodeConfig.Containerd.Address | printf "%q" }}
|
|
|
|
[[plugins.'io.containerd.transfer.v1.local'.unpack_config]]
|
|
platform = "linux/amd64"
|
|
snapshotter = "nix"
|
|
differ = "walking"
|
|
|
|
[[plugins.'io.containerd.transfer.v1.local'.unpack_config]]
|
|
platform = "linux/arm64"
|
|
snapshotter = "nix"
|
|
differ = "walking"
|
|
{{ end }}
|
|
{{ end }}
|
|
|
|
{{ if .IsRunningInUserNS }}
|
|
[plugins.'io.containerd.nri.v1.nri']
|
|
disable = true
|
|
{{ end }}
|
|
`
|
|
|
|
var HostsTomlHeader = "# File generated by " + version.Program + ". DO NOT EDIT.\n"
|
|
|
|
// This hosts.toml template is used by both Linux and Windows nodes
|
|
const HostsTomlTemplate = `
|
|
{{- /* */ -}}
|
|
# File generated by {{ .Program }}. DO NOT EDIT.
|
|
{{ with $e := .Default }}
|
|
{{- if $e.URL }}
|
|
server = "{{ $e.URL }}"
|
|
capabilities = ["pull", "resolve", "push"]
|
|
{{ end }}
|
|
{{- if $e.Config.TLS }}
|
|
{{- if $e.Config.TLS.CAFile }}
|
|
ca = [{{ printf "%q" $e.Config.TLS.CAFile }}]
|
|
{{- end }}
|
|
{{- if or $e.Config.TLS.CertFile $e.Config.TLS.KeyFile }}
|
|
client = [[{{ printf "%q" $e.Config.TLS.CertFile }}, {{ printf "%q" $e.Config.TLS.KeyFile }}]]
|
|
{{- end }}
|
|
{{- if $e.Config.TLS.InsecureSkipVerify }}
|
|
skip_verify = true
|
|
{{- end }}
|
|
{{ end }}
|
|
{{ end }}
|
|
[host]
|
|
{{ range $e := .Endpoints -}}
|
|
[host."{{ $e.URL }}"]
|
|
capabilities = ["pull", "resolve"]
|
|
{{- if $e.OverridePath }}
|
|
override_path = true
|
|
{{- end }}
|
|
{{- if $e.Config.TLS }}
|
|
{{- if $e.Config.TLS.CAFile }}
|
|
ca = [{{ printf "%q" $e.Config.TLS.CAFile }}]
|
|
{{- end }}
|
|
{{- if or $e.Config.TLS.CertFile $e.Config.TLS.KeyFile }}
|
|
client = [[{{ printf "%q" $e.Config.TLS.CertFile }}, {{ printf "%q" $e.Config.TLS.KeyFile }}]]
|
|
{{- end }}
|
|
{{- if $e.Config.TLS.InsecureSkipVerify }}
|
|
skip_verify = true
|
|
{{- end }}
|
|
{{ end }}
|
|
{{- if $e.Rewrites }}
|
|
[host."{{ $e.URL }}".rewrite]
|
|
{{- range $pattern, $replace := $e.Rewrites }}
|
|
"{{ $pattern }}" = "{{ $replace }}"
|
|
{{- end }}
|
|
{{ end }}
|
|
{{ end -}}
|
|
`
|
|
|
|
func ParseTemplateFromConfig(userTemplate, baseTemplate string, config any) (string, error) {
|
|
out := new(bytes.Buffer)
|
|
t := template.Must(template.New("compiled_template").Funcs(templateFuncs).Parse(userTemplate))
|
|
template.Must(t.New("base").Parse(baseTemplate))
|
|
if err := t.Execute(out, config); err != nil {
|
|
return "", err
|
|
}
|
|
return trimEmpty(out)
|
|
}
|
|
|
|
func ParseHostsTemplateFromConfig(userTemplate string, config any) (string, error) {
|
|
out := new(bytes.Buffer)
|
|
t := template.Must(template.New("compiled_template").Funcs(templateFuncs).Parse(userTemplate))
|
|
if err := t.Execute(out, config); err != nil {
|
|
return "", err
|
|
}
|
|
return trimEmpty(out)
|
|
}
|
|
|
|
// trimEmpty removes excess empty lines from the rendered template
|
|
func trimEmpty(r io.Reader) (string, error) {
|
|
builder := strings.Builder{}
|
|
scanner := bufio.NewScanner(r)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
if strings.TrimSpace(line) != "" {
|
|
if strings.HasPrefix(line, "[") {
|
|
builder.WriteString("\n")
|
|
}
|
|
builder.WriteString(line + "\n")
|
|
}
|
|
}
|
|
return builder.String(), scanner.Err()
|
|
}
|