diff --git a/.conform.yaml b/.conform.yaml index 5ab969769f..2c9967b74b 100644 --- a/.conform.yaml +++ b/.conform.yaml @@ -8,7 +8,7 @@ policies: gitHubOrganization: siderolabs spellcheck: locale: US - maximumOfOneCommit: true + maximumOfOneCommit: false header: length: 89 imperative: true diff --git a/.drone.jsonnet b/.drone.jsonnet index f0de639e5c..d8f37d9893 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -341,7 +341,7 @@ local ExtensionsStep(with_e2e=true) = local extensions_build = TriggerDownstream( 'extensions-build', 'e2e-talos', - ['siderolabs/extensions@main'], + ['siderolabs/extensions@release-1.7'], params=[ std.format('REGISTRY=%s', local_registry), 'PLATFORM=linux/amd64', diff --git a/Makefile b/Makefile index 20141833b3..ff39e49738 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ ARTIFACTS := _out TOOLS ?= ghcr.io/siderolabs/tools:v1.7.0-1-g10b2a69 PKGS_PREFIX ?= ghcr.io/siderolabs -PKGS ?= v1.7.0-2-g6101299 +PKGS ?= v1.7.0-5-gb7f1920 EXTRAS ?= v1.7.0-1-gbb76755 PKG_FHS ?= $(PKGS_PREFIX)/fhs:$(PKGS) @@ -86,7 +86,7 @@ INTEGRATION_TEST_DEFAULT_TARGET := integration-test-$(OPERATING_SYSTEM) MODULE_SIG_VERIFY_DEFAULT_TARGET := module-sig-verify-$(OPERATING_SYSTEM) INTEGRATION_TEST_PROVISION_DEFAULT_TARGET := integration-test-provision-$(OPERATING_SYSTEM) # renovate: datasource=github-releases depName=kubernetes/kubernetes -KUBECTL_VERSION ?= v1.30.0-rc.1 +KUBECTL_VERSION ?= v1.30.0-rc.2 # renovate: datasource=github-releases depName=kastenhq/kubestr KUBESTR_VERSION ?= v0.4.44 # renovate: datasource=github-releases depName=helm/helm diff --git a/go.mod b/go.mod index adb72bb05b..5ec0ec183c 100644 --- a/go.mod +++ b/go.mod @@ -25,16 +25,16 @@ replace ( // Kubernetes dependencies sharing the same version. require ( - k8s.io/api v0.30.0-rc.1 - k8s.io/apimachinery v0.30.0-rc.1 - k8s.io/apiserver v0.30.0-rc.1 - k8s.io/client-go v0.30.0-rc.1 - k8s.io/component-base v0.30.0-rc.1 - k8s.io/cri-api v0.30.0-rc.1 - k8s.io/kube-scheduler v0.30.0-rc.1 - k8s.io/kubectl v0.30.0-rc.1 - k8s.io/kubelet v0.30.0-rc.1 - k8s.io/pod-security-admission v0.30.0-rc.1 + k8s.io/api v0.30.0-rc.2 + k8s.io/apimachinery v0.30.0-rc.2 + k8s.io/apiserver v0.30.0-rc.2 + k8s.io/client-go v0.30.0-rc.2 + k8s.io/component-base v0.30.0-rc.2 + k8s.io/cri-api v0.30.0-rc.2 + k8s.io/kube-scheduler v0.30.0-rc.2 + k8s.io/kubectl v0.30.0-rc.2 + k8s.io/kubelet v0.30.0-rc.2 + k8s.io/pod-security-admission v0.30.0-rc.2 ) require ( @@ -353,7 +353,7 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/cli-runtime v0.30.0-rc.1 // indirect + k8s.io/cli-runtime v0.30.0-rc.2 // indirect k8s.io/klog v1.0.0 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect diff --git a/go.sum b/go.sum index 28cb1eacf8..fe6eb14180 100644 --- a/go.sum +++ b/go.sum @@ -1261,34 +1261,34 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.30.0-rc.1 h1:0163kmXvT0JoER+nh9h1nSgX+sDwYYHPBgs+rWqjVIg= -k8s.io/api v0.30.0-rc.1/go.mod h1:mfiQxBiaioCBgc+jzmDpSXmSEQkqeHTh4FVOAh1iEqU= -k8s.io/apimachinery v0.30.0-rc.1 h1:Zi5mcxPCvhwJL8S8tNC5AakszlABd3UWr6OOXqPDToM= -k8s.io/apimachinery v0.30.0-rc.1/go.mod h1:wEJvNDlfxMRaMhyv38SIHIEC9hah/xuzqUUhxIyUv7Y= -k8s.io/apiserver v0.30.0-rc.1 h1:61klJwjoORznFtXWKdhD1hl2hDtZDAHs+iR4DcFfNkk= -k8s.io/apiserver v0.30.0-rc.1/go.mod h1:ceP6uSYuNHIx35dD74S5yb/v8HR9sZylInNkyg5uTLI= -k8s.io/cli-runtime v0.30.0-rc.1 h1:dyEoZPmO89jirDPm3dkkVe1/TWrzBlgW99hOhge/FWs= -k8s.io/cli-runtime v0.30.0-rc.1/go.mod h1:PPSMp1dE5CimHuJP0Eef6BAliWn7uZtQ6xmkO2aVVas= -k8s.io/client-go v0.30.0-rc.1 h1:vUhzEA59XUwGtFjea4UPLa9Tal3SskmNYSgR7lmjQNU= -k8s.io/client-go v0.30.0-rc.1/go.mod h1:LnVJuaom1T1YD5IN2KwCJN9WvWbEfUNTg1lsmErIW3g= -k8s.io/component-base v0.30.0-rc.1 h1:Rzj2ev1hG3bfvenMBdsm+M5aeARZ7MH+zUW/fYn1DJk= -k8s.io/component-base v0.30.0-rc.1/go.mod h1:bln4m7L7DC075qpAVDxLSbmQthruJPmDC5OgdywDdVE= -k8s.io/cri-api v0.30.0-rc.1 h1:74C6n5E7I3zoLlRxZUPhWwFjR5yOIFa42+wajnD9teg= -k8s.io/cri-api v0.30.0-rc.1/go.mod h1:4MvRsG7Jr/C0uyVjCforyO0BNJJlngqcMRsJvObl4q0= +k8s.io/api v0.30.0-rc.2 h1:wnrY4jFP4Kx7h/Ppg86D0dyctlKfiMSXHme004ptkCU= +k8s.io/api v0.30.0-rc.2/go.mod h1:AsZ3vl/SZOLpqzfIKhleVYl5R5ruyzhB3G08xpDEjPQ= +k8s.io/apimachinery v0.30.0-rc.2 h1:Q1JPqws5zCGjRwKtLW8ZKOY8lvl6aJejqIixJlHoAhc= +k8s.io/apimachinery v0.30.0-rc.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/apiserver v0.30.0-rc.2 h1:FGIjvgG6HrOjjeVQKSI2qItT6dXbmYKTD1KbBW8TsIo= +k8s.io/apiserver v0.30.0-rc.2/go.mod h1:Qs+prNQNN52O3tGv5Krq9r1Cm2rqz2+r+LCkM50dJNw= +k8s.io/cli-runtime v0.30.0-rc.2 h1:lY8Vs7jixol3rtbOCrIZxSvz86T+Q+OaCsCzERjq9jc= +k8s.io/cli-runtime v0.30.0-rc.2/go.mod h1:Xn4RL/ZV2nz2kRLBo43fbSPdmPtCWZZ+XEA8CegcStQ= +k8s.io/client-go v0.30.0-rc.2 h1:AqXSYq6s2BIr4WqK2dXGebxLPIsN48cMYjP71aXKspM= +k8s.io/client-go v0.30.0-rc.2/go.mod h1:vCtim9VeBumah2j1nZ/95O0V7F4Ad8N0wwCkSkgOE+Y= +k8s.io/component-base v0.30.0-rc.2 h1:0Qa6faUg01rBp9VxU76B8PmK58rBcAGB+7r4ckpLtgI= +k8s.io/component-base v0.30.0-rc.2/go.mod h1:rdQm+7+FBi+t74zJKiKBYVgQJEiNRMqvESRh8/f5z5k= +k8s.io/cri-api v0.30.0-rc.2 h1:7duYOq8BtLqDOE5zqDJvGix2WVUhPp6KbtH/1bITYwQ= +k8s.io/cri-api v0.30.0-rc.2/go.mod h1://4/umPJSW1ISNSNng4OwjpkvswJOQwU8rnkvO8P+xg= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= -k8s.io/kube-scheduler v0.30.0-rc.1 h1:OMZ2MALMRFe3DHOPqcB1lUxb6xbwKP7UeF+yNqexYW8= -k8s.io/kube-scheduler v0.30.0-rc.1/go.mod h1:/LW8Z2f6fqtn8LjRwOpkI5PMsCFsyw06kcGTjz4e/bY= -k8s.io/kubectl v0.30.0-rc.1 h1:MQ69d1SadPj9nL77XYSjEmylAKWYXM5aPweyYfrdQq4= -k8s.io/kubectl v0.30.0-rc.1/go.mod h1:vQSyksXUAoBXp9qpEr2y4yBLK2KONxiC1nwLXnaRMuE= -k8s.io/kubelet v0.30.0-rc.1 h1:HcjkEQgxpgAFlbhoCxhEjUlJmRZqIUiVRcAI0lRMD3o= -k8s.io/kubelet v0.30.0-rc.1/go.mod h1:T6p2fE038tUe//Zzw/K6IdeQbB8cFifiNZHbGjgWFU0= -k8s.io/pod-security-admission v0.30.0-rc.1 h1:r7iVpC+PrrOImQBf08O1pik+TnXGV7AjYTOpIUnFHHc= -k8s.io/pod-security-admission v0.30.0-rc.1/go.mod h1:ejiJNRssoIjTEWV90HZNjXBtZmpbry7LhvwpFcIp9+E= +k8s.io/kube-scheduler v0.30.0-rc.2 h1:ubgPuv1ECrXdQ711xRHBjl0K5orsrgy1d6rUp9Rc/gc= +k8s.io/kube-scheduler v0.30.0-rc.2/go.mod h1:FNgXnUZ56HIJMxVLwJnT3g1c0CHR8kfrYoxZpXL+cmc= +k8s.io/kubectl v0.30.0-rc.2 h1:zbJXzsl61XTs5kX5eV+14bcbPDH7f1BQ8htjHVi+aUU= +k8s.io/kubectl v0.30.0-rc.2/go.mod h1:A6CtbMlPch2+nMydUVImHA5RS7Ux1n9pX3wqS9u3ABE= +k8s.io/kubelet v0.30.0-rc.2 h1:JgHEHrTA55t0SO8EJ/BF5tfjcO07c+skVRB3p345/y8= +k8s.io/kubelet v0.30.0-rc.2/go.mod h1:GlLJUJ8rpptITej1l+jLUMZZwn1tUv2WatTSoNxzEP0= +k8s.io/pod-security-admission v0.30.0-rc.2 h1:Uszvw24nQVwT4FagnhCbsckS7sQ3oiGrXPIfl6hdjPs= +k8s.io/pod-security-admission v0.30.0-rc.2/go.mod h1:vNN82cNzahoJY5V1S4FZ0WNAcmR30h9Js8eZY9uQ2fg= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= kernel.org/pub/linux/libs/security/libcap/cap v1.2.69 h1:N0m3tKYbkRMmDobh/47ngz+AWeV7PcfXMDi8xu3Vrag= diff --git a/hack/release.toml b/hack/release.toml index 997f221837..a447782e16 100644 --- a/hack/release.toml +++ b/hack/release.toml @@ -17,10 +17,10 @@ preface = """\ [notes.updates] title = "Component Updates" description = """\ -Linux: 6.6.24 +Linux: 6.6.26 etcd: 3.5.11 -Kubernetes: 1.30.0-rc.1 -containerd: 1.7.14 +Kubernetes: 1.30.0-rc.2 +containerd: 1.7.15 runc: 1.1.12 Flannel: 0.24.4 @@ -40,9 +40,22 @@ Talos Linux now provides a caching DNS resolver for host workloads (including h ```yaml machine: - features: - localDNS: false + features: + hostDNS: + enabled: false ``` + +You can also enable dns caching for k8s pods with: + +```yaml +machine: + features: + hostDNS: + enabled: true + forwardKubeDNSToHost: true +``` + +Please note that on running cluster you will have to kill CoreDNS pods for this change to apply. """ [notes.secureboot-image] @@ -199,6 +212,16 @@ machine: title = "Platforms" description = """\ Talos Linux now supports [Akamai Connected Cloud](https://www.linode.com/) provider (platform `akamai`). +""" + + [notes.iptables] + title = "IPTables" + description = """\ +Talos Linux now forces `kubelet` and `kube-proxy` to use `iptables-nft` instead of `iptables-legacy` (`xtables`) which was the default +before Talos 1.7.0. + +Container images based on `iptables-wrapper` should work without changes, but if there was a direct call to `legacy` mode of `iptables`, make sure +to update to use `iptables-nft`. """ [make_deps] diff --git a/hack/test/e2e.sh b/hack/test/e2e.sh index 2b526f2a5d..8818f6aa66 100755 --- a/hack/test/e2e.sh +++ b/hack/test/e2e.sh @@ -41,7 +41,7 @@ export TALOS_VERSION # Kubernetes export KUBECONFIG="${TMP}/kubeconfig" -export KUBERNETES_VERSION=${KUBERNETES_VERSION:-1.30.0-rc.1} +export KUBERNETES_VERSION=${KUBERNETES_VERSION:-1.30.0-rc.2} export NAME_PREFIX="talos-e2e-${SHA}-${PLATFORM}" export TIMEOUT=1200 diff --git a/internal/app/apid/pkg/backend/apid.go b/internal/app/apid/pkg/backend/apid.go index 1c1eed7291..6c31acf5e6 100644 --- a/internal/app/apid/pkg/backend/apid.go +++ b/internal/app/apid/pkg/backend/apid.go @@ -15,6 +15,7 @@ import ( "github.com/siderolabs/net" "google.golang.org/grpc" "google.golang.org/grpc/backoff" + "google.golang.org/grpc/connectivity" "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" @@ -26,6 +27,11 @@ import ( "github.com/siderolabs/talos/pkg/machinery/proto" ) +// GracefulShutdownTimeout is the timeout for graceful shutdown of the backend connection. +// +// Talos has a few long-running API calls, so we need to give the backend some time to finish them. +const GracefulShutdownTimeout = 30 * time.Minute + var _ proxy.Backend = (*APID)(nil) // APID backend performs proxying to another apid instance. @@ -253,7 +259,36 @@ func (a *APID) Close() { defer a.mu.Unlock() if a.conn != nil { - a.conn.Close() //nolint:errcheck + gracefulGRPCClose(a.conn, GracefulShutdownTimeout) a.conn = nil } } + +func gracefulGRPCClose(conn *grpc.ClientConn, timeout time.Duration) { + // close the client connection in the background, tries to avoid closing the connection + // if the connection is in the middle of a call (e.g. streaming API) + // + // see https://github.com/grpc/grpc/blob/master/doc/connectivity-semantics-and-api.md for details on connection states + go func() { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + for ctx.Err() != nil { + switch state := conn.GetState(); state { //nolint:exhaustive + case connectivity.Idle, + connectivity.Shutdown, + connectivity.TransientFailure: + // close immediately, connection is not used + conn.Close() //nolint:errcheck + + return + default: + // wait for state change of the connection + conn.WaitForStateChange(ctx, state) + } + } + + // close anyways on timeout + conn.Close() //nolint:errcheck + }() +} diff --git a/internal/app/machined/pkg/controllers/k8s/render_secrets_static_pod.go b/internal/app/machined/pkg/controllers/k8s/render_secrets_static_pod.go index 16f3249338..61bbca7da8 100644 --- a/internal/app/machined/pkg/controllers/k8s/render_secrets_static_pod.go +++ b/internal/app/machined/pkg/controllers/k8s/render_secrets_static_pod.go @@ -18,6 +18,7 @@ import ( "github.com/cosi-project/runtime/pkg/state" "github.com/siderolabs/crypto/x509" "github.com/siderolabs/gen/optional" + "github.com/siderolabs/gen/xslices" "go.uber.org/zap" "github.com/siderolabs/talos/pkg/machinery/constants" @@ -184,7 +185,11 @@ func (ctrl *RenderSecretsStaticPodController) Run(ctx context.Context, r control keyFilename: "etcd-client.key", }, { - getter: func() *x509.PEMEncodedCertificateAndKey { return rootK8sSecrets.IssuingCA }, + getter: func() *x509.PEMEncodedCertificateAndKey { + return &x509.PEMEncodedCertificateAndKey{ + Crt: bytes.Join(xslices.Map(rootK8sSecrets.AcceptedCAs, func(ca *x509.PEMEncodedCertificate) []byte { return ca.Crt }), nil), + } + }, certFilename: "ca.crt", }, { diff --git a/internal/app/machined/pkg/controllers/network/dns_resolve_cache.go b/internal/app/machined/pkg/controllers/network/dns_resolve_cache.go index fef3a8a939..fb1c027d7e 100644 --- a/internal/app/machined/pkg/controllers/network/dns_resolve_cache.go +++ b/internal/app/machined/pkg/controllers/network/dns_resolve_cache.go @@ -8,7 +8,6 @@ import ( "context" "errors" "fmt" - "io" "net" "net/netip" "sync" @@ -19,9 +18,9 @@ import ( "github.com/cosi-project/runtime/pkg/safe" "github.com/cosi-project/runtime/pkg/state" "github.com/siderolabs/gen/optional" + "github.com/siderolabs/gen/pair" "go.uber.org/zap" - "github.com/siderolabs/talos/internal/pkg/ctxutil" "github.com/siderolabs/talos/internal/pkg/dns" "github.com/siderolabs/talos/pkg/machinery/resources/network" ) @@ -33,10 +32,9 @@ type DNSResolveCacheController struct { mx sync.Mutex handler *dns.Handler cache *dns.Cache - runners map[runnerConfig]*dnsRunner + runners map[runnerConfig]pair.Pair[func(), <-chan struct{}] reconcile chan struct{} originalCtx context.Context //nolint:containedctx - wg sync.WaitGroup } // Name implements controller.Controller interface. @@ -71,7 +69,7 @@ func (ctrl *DNSResolveCacheController) Outputs() []controller.Output { // //nolint:gocyclo,cyclop func (ctrl *DNSResolveCacheController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error { - ctrl.init(ctx, logger) + ctrl.init(ctx) ctrl.mx.Lock() defer ctrl.mx.Unlock() @@ -81,9 +79,19 @@ func (ctrl *DNSResolveCacheController) Run(ctx context.Context, r controller.Run for { select { case <-ctx.Done(): - return ctxutil.Cause(ctx) + return nil case <-r.EventCh(): case <-ctrl.reconcile: + for cfg, stop := range ctrl.runners { + select { + default: + continue + case <-stop.F2: + } + + stop.F1() + delete(ctrl.runners, cfg) + } } cfg, err := safe.ReaderGetByID[*network.HostDNSConfig](ctx, r, network.HostDNSConfigID) @@ -101,7 +109,7 @@ func (ctrl *DNSResolveCacheController) Run(ctx context.Context, r controller.Run ctrl.stopRunners(ctx, true) if err = safe.CleanupOutputs[*network.DNSResolveCache](ctx, r); err != nil { - return fmt.Errorf("error cleaning up dns status: %w", err) + return fmt.Errorf("error cleaning up dns status on disable: %w", err) } continue @@ -111,10 +119,10 @@ func (ctrl *DNSResolveCacheController) Run(ctx context.Context, r controller.Run for _, addr := range cfg.TypedSpec().ListenAddresses { for _, netwk := range []string{"udp", "tcp"} { - config := runnerConfig{net: netwk, addr: addr} + runnerCfg := runnerConfig{net: netwk, addr: addr} - if _, ok := ctrl.runners[config]; !ok { - runner, rErr := newDNSRunner(config, ctrl.cache, ctrl.Logger) + if _, ok := ctrl.runners[runnerCfg]; !ok { + runner, rErr := newDNSRunner(runnerCfg, ctrl.cache, ctrl.Logger) if rErr != nil { return fmt.Errorf("error creating dns runner: %w", rErr) } @@ -123,30 +131,23 @@ func (ctrl *DNSResolveCacheController) Run(ctx context.Context, r controller.Run continue } - ctrl.wg.Add(1) - - go func() { - defer ctrl.wg.Done() - - runner.Run(ctx, logger, ctrl.reconcile) - }() - - ctrl.runners[config] = runner + ctrl.runners[runnerCfg] = pair.MakePair(runner.Start(ctrl.handleDone(ctx, logger))) } - if err = ctrl.writeDNSStatus(ctx, r, config); err != nil { + if err = ctrl.writeDNSStatus(ctx, r, runnerCfg); err != nil { return fmt.Errorf("error writing dns status: %w", err) } - touchedRunners[config] = struct{}{} + touchedRunners[runnerCfg] = struct{}{} } } - for config := range ctrl.runners { - if _, ok := touchedRunners[config]; !ok { - ctrl.runners[config].Stop() + for runnerCfg, stop := range ctrl.runners { + if _, ok := touchedRunners[runnerCfg]; !ok { + stop.F1() + delete(ctrl.runners, runnerCfg) - delete(ctrl.runners, config) + continue } } @@ -182,7 +183,7 @@ func (ctrl *DNSResolveCacheController) writeDNSStatus(ctx context.Context, r con }) } -func (ctrl *DNSResolveCacheController) init(ctx context.Context, logger *zap.Logger) { +func (ctrl *DNSResolveCacheController) init(ctx context.Context) { if ctrl.runners != nil { if ctrl.originalCtx != ctx { // This should not happen, but if it does, it's a bug. @@ -195,7 +196,7 @@ func (ctrl *DNSResolveCacheController) init(ctx context.Context, logger *zap.Log ctrl.originalCtx = ctx ctrl.handler = dns.NewHandler(ctrl.Logger) ctrl.cache = dns.NewCache(ctrl.handler, ctrl.Logger) - ctrl.runners = map[runnerConfig]*dnsRunner{} + ctrl.runners = map[runnerConfig]pair.Pair[func(), <-chan struct{}]{} ctrl.reconcile = make(chan struct{}, 1) // Ensure we stop all runners when the context is canceled, no matter where we are currently. @@ -215,21 +216,34 @@ func (ctrl *DNSResolveCacheController) stopRunners(ctx context.Context, ignoreCt return } - for _, r := range ctrl.runners { - r.Stop() + for _, stop := range ctrl.runners { + stop.F1() } clear(ctrl.runners) ctrl.handler.Stop() - - ctrl.wg.Wait() } -type dnsRunner struct { - runner *dns.Runner - lis io.Closer - logger *zap.Logger +func (ctrl *DNSResolveCacheController) handleDone(ctx context.Context, logger *zap.Logger) func(err error) { + return func(err error) { + if ctx.Err() != nil { + if err != nil && !errors.Is(err, net.ErrClosed) { + logger.Error("controller is closing, but error running dns server", zap.Error(err)) + } + + return + } + + if err != nil { + logger.Error("error running dns server", zap.Error(err)) + } + + select { + case ctrl.reconcile <- struct{}{}: + default: + } + } } type runnerConfig struct { @@ -237,7 +251,7 @@ type runnerConfig struct { addr netip.AddrPort } -func newDNSRunner(cfg runnerConfig, cache *dns.Cache, logger *zap.Logger) (*dnsRunner, error) { +func newDNSRunner(cfg runnerConfig, cache *dns.Cache, logger *zap.Logger) (*dns.Server, error) { if cfg.addr.Addr().Is6() { cfg.net += "6" } @@ -246,8 +260,6 @@ func newDNSRunner(cfg runnerConfig, cache *dns.Cache, logger *zap.Logger) (*dnsR var serverOpts dns.ServerOptions - var lis io.Closer - switch cfg.net { case "udp", "udp6": packetConn, err := dns.NewUDPPacketConn(cfg.net, cfg.addr.String()) @@ -262,11 +274,10 @@ func newDNSRunner(cfg runnerConfig, cache *dns.Cache, logger *zap.Logger) (*dnsR return nil, fmt.Errorf("error creating udp packet conn: %w", err) } - lis = packetConn - serverOpts = dns.ServerOptions{ PacketConn: packetConn, Handler: cache, + Logger: logger, } case "tcp", "tcp6": @@ -282,8 +293,6 @@ func newDNSRunner(cfg runnerConfig, cache *dns.Cache, logger *zap.Logger) (*dnsR return nil, fmt.Errorf("error creating tcp listener: %w", err) } - lis = listener - serverOpts = dns.ServerOptions{ Listener: listener, Handler: cache, @@ -291,55 +300,9 @@ func newDNSRunner(cfg runnerConfig, cache *dns.Cache, logger *zap.Logger) (*dnsR WriteTimeout: 5 * time.Second, IdleTimeout: func() time.Duration { return 10 * time.Second }, MaxTCPQueries: -1, + Logger: logger, } } - runner := dns.NewRunner(dns.NewServer(serverOpts), logger) - - return &dnsRunner{ - runner: runner, - lis: lis, - logger: logger, - }, nil -} - -func (dnsRunner *dnsRunner) Run(ctx context.Context, logger *zap.Logger, reconcile chan<- struct{}) { - err := dnsRunner.runner.Run() - if err == nil { - if ctx.Err() == nil { - select { - case reconcile <- struct{}{}: - default: - } - } - - return - } - - if ctx.Err() == nil { - logger.Error("error running dns server, triggering reconcile", zap.Error(err)) - - select { - case reconcile <- struct{}{}: - default: - } - - return - } - - if !errors.Is(err, net.ErrClosed) { - logger.Error("controller is closing, but error running dns server", zap.Error(err)) - - return - } -} - -func (dnsRunner *dnsRunner) Stop() { - dnsRunner.runner.Stop() - - if err := dnsRunner.lis.Close(); err != nil && !errors.Is(err, net.ErrClosed) { - dnsRunner.logger.Error("error closing listener", zap.Error(err)) - } else { - dnsRunner.logger.Debug("dns listener closed") - } + return dns.NewServer(serverOpts), nil } diff --git a/internal/app/machined/pkg/controllers/network/dns_resolve_cache_test.go b/internal/app/machined/pkg/controllers/network/dns_resolve_cache_test.go index 49763da916..84d6cad4ad 100644 --- a/internal/app/machined/pkg/controllers/network/dns_resolve_cache_test.go +++ b/internal/app/machined/pkg/controllers/network/dns_resolve_cache_test.go @@ -78,12 +78,19 @@ func (suite *DNSServer) TestResolving() { var res *dns.Msg - err := retry.Constant(2*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(func() error { + err := retry.Constant(5*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(func() error { r, err := dns.Exchange(msg, "127.0.0.53:"+port) + if err != nil { + return retry.ExpectedError(err) + } + + if r.Rcode != dns.RcodeSuccess { + return retry.ExpectedErrorf("expected rcode %d, got %d", dns.RcodeSuccess, r.Rcode) + } res = r - return retry.ExpectedError(err) + return nil }) suite.Require().NoError(err) suite.Require().Equal(dns.RcodeSuccess, res.Rcode, res) @@ -137,7 +144,7 @@ func (suite *DNSServer) TestSetupStartStop() { func TestDNSServer(t *testing.T) { suite.Run(t, &DNSServer{ DefaultSuite: ctest.DefaultSuite{ - Timeout: 5 * time.Second, + Timeout: 10 * time.Second, AfterSetup: func(suite *ctest.DefaultSuite) { suite.Require().NoError(suite.Runtime().RegisterController(&netctrl.DNSUpstreamController{})) suite.Require().NoError(suite.Runtime().RegisterController(&netctrl.DNSResolveCacheController{ diff --git a/internal/app/machined/pkg/controllers/network/nftables_chain.go b/internal/app/machined/pkg/controllers/network/nftables_chain.go index 26aa0f3b08..9fbbc70a10 100644 --- a/internal/app/machined/pkg/controllers/network/nftables_chain.go +++ b/internal/app/machined/pkg/controllers/network/nftables_chain.go @@ -65,6 +65,10 @@ func (ctrl *NfTablesChainController) Run(ctx context.Context, r controller.Runti var conn nftables.Conn + if err := ctrl.preCreateIptablesNFTable(logger, &conn); err != nil { + return fmt.Errorf("error pre-creating iptables-nft table: %w", err) + } + list, err := safe.ReaderListAll[*network.NfTablesChain](ctx, r) if err != nil { return fmt.Errorf("error listing nftables chains: %w", err) @@ -176,3 +180,34 @@ func (ctrl *NfTablesChainController) Run(ctx context.Context, r controller.Runti r.ResetRestartBackoff() } } + +func (ctrl *NfTablesChainController) preCreateIptablesNFTable(logger *zap.Logger, conn *nftables.Conn) error { + // Pre-create the iptables-nft table, if it doesn't exist. + // This is required to ensure that the iptables universal binary prefers iptables-nft over + // iptables-legacy can be used to manage the nftables rules. + tables, err := conn.ListTablesOfFamily(nftables.TableFamilyIPv4) + if err != nil { + return fmt.Errorf("error listing existing nftables tables: %w", err) + } + + if slices.IndexFunc(tables, func(t *nftables.Table) bool { return t.Name == "mangle" }) != -1 { + return nil + } + + table := &nftables.Table{ + Family: nftables.TableFamilyIPv4, + Name: "mangle", + } + conn.AddTable(table) + + chain := &nftables.Chain{ + Name: "KUBE-IPTABLES-HINT", + Table: table, + Type: nftables.ChainTypeNAT, + } + conn.AddChain(chain) + + logger.Info("pre-created iptables-nft table 'mangle'/'KUBE-IPTABLES-HINT'") + + return nil +} diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/openstack/openstack.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/openstack/openstack.go index a9ff3370cd..d4b1d4111b 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/openstack/openstack.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/openstack/openstack.go @@ -275,6 +275,12 @@ func (o *Openstack) ParseMetadata( return nil, fmt.Errorf("failed to parse gateway ip: %w", err) } + priority := uint32(network.DefaultRouteMetric) + + if family == nethelpers.FamilyInet6 { + priority *= 2 + } + route := network.RouteSpecSpec{ ConfigLayer: network.ConfigPlatform, Gateway: gw, @@ -283,7 +289,7 @@ func (o *Openstack) ParseMetadata( Protocol: nethelpers.ProtocolStatic, Type: nethelpers.TypeUnicast, Family: family, - Priority: network.DefaultRouteMetric, + Priority: priority, } route.Normalize() diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/openstack/testdata/expected.yaml b/internal/app/machined/pkg/runtime/v1alpha1/platform/openstack/testdata/expected.yaml index 0b6a4cb717..01a1adc71f 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/openstack/testdata/expected.yaml +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/openstack/testdata/expected.yaml @@ -99,7 +99,7 @@ routes: gateway: 2000:0:100:2fff:ff:ff:ff:ff outLinkName: eth0 table: main - priority: 1024 + priority: 2048 scope: global type: unicast flags: "" diff --git a/internal/integration/api/extensions_qemu.go b/internal/integration/api/extensions_qemu.go index 20bca6ecff..37836aacac 100644 --- a/internal/integration/api/extensions_qemu.go +++ b/internal/integration/api/extensions_qemu.go @@ -788,6 +788,41 @@ func (suite *ExtensionsSuiteQEMU) TestExtensionsWasmEdge() { suite.Require().NoError(suite.WaitForPodToBeRunning(suite.ctx, 5*time.Minute, "default", "wasmedge-test")) } +// TestExtensionsSpin verifies spin runtime class is working. +func (suite *ExtensionsSuiteQEMU) TestExtensionsSpin() { + _, err := suite.Clientset.NodeV1().RuntimeClasses().Create(suite.ctx, &nodev1.RuntimeClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "wasmtime-spin-v2", + }, + Handler: "spin", + }, metav1.CreateOptions{}) + defer suite.Clientset.NodeV1().RuntimeClasses().Delete(suite.ctx, "wasmtime-spin-v2", metav1.DeleteOptions{}) //nolint:errcheck + + suite.Require().NoError(err) + + _, err = suite.Clientset.CoreV1().Pods("default").Create(suite.ctx, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "spin-test", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "spin-test", + Image: "ghcr.io/spinkube/containerd-shim-spin/examples/spin-rust-hello", + Command: []string{"/"}, + }, + }, + RuntimeClassName: pointer.To("wasmtime-spin-v2"), + }, + }, metav1.CreateOptions{}) + defer suite.Clientset.CoreV1().Pods("default").Delete(suite.ctx, "spin-test", metav1.DeleteOptions{}) //nolint:errcheck + + suite.Require().NoError(err) + + // wait for the pod to be ready + suite.Require().NoError(suite.WaitForPodToBeRunning(suite.ctx, 5*time.Minute, "default", "spin-test")) +} + func init() { allSuites = append(allSuites, &ExtensionsSuiteQEMU{}) } diff --git a/internal/integration/api/rotate.go b/internal/integration/api/rotate.go index 2c7e66aeee..baefc001c7 100644 --- a/internal/integration/api/rotate.go +++ b/internal/integration/api/rotate.go @@ -156,6 +156,8 @@ func (suite *RotateCASuite) TestKubernetes() { suite.Require().NoError(kubernetes.Rotate(suite.ctx, options)) + suite.AssertClusterHealthy(suite.ctx) + suite.T().Logf("rotating back new CA -> old CA") options = kubernetes.Options{ diff --git a/internal/pkg/ctxutil/ctxutil.go b/internal/pkg/ctxutil/ctxutil.go index 1fff6bad64..892992200f 100644 --- a/internal/pkg/ctxutil/ctxutil.go +++ b/internal/pkg/ctxutil/ctxutil.go @@ -7,16 +7,6 @@ package ctxutil import "context" -// MonitorFn starts a function in a new goroutine and cancels the context with error as cause when the function returns. -// It returns the new context. -func MonitorFn(ctx context.Context, fn func() error) context.Context { - ctx, cancel := context.WithCancelCause(ctx) - - go func() { cancel(fn()) }() - - return ctx -} - // Cause returns the cause of the context error, or nil if there is no error or the error is a usual context error. func Cause(ctx context.Context) error { if c := context.Cause(ctx); c != ctx.Err() { diff --git a/internal/pkg/ctxutil/ctxutil_test.go b/internal/pkg/ctxutil/ctxutil_test.go deleted file mode 100644 index 1c92879d28..0000000000 --- a/internal/pkg/ctxutil/ctxutil_test.go +++ /dev/null @@ -1,33 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -package ctxutil_test - -import ( - "context" - "errors" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/siderolabs/talos/internal/pkg/ctxutil" -) - -func TestStartFn(t *testing.T) { - ctx := ctxutil.MonitorFn(context.Background(), func() error { return nil }) - - <-ctx.Done() - - require.Equal(t, context.Canceled, ctx.Err()) - require.Nil(t, ctxutil.Cause(ctx)) - - myErr := errors.New("my error") - - ctx = ctxutil.MonitorFn(context.Background(), func() error { return myErr }) - - <-ctx.Done() - - require.Equal(t, context.Canceled, ctx.Err()) - require.Equal(t, myErr, ctxutil.Cause(ctx)) -} diff --git a/internal/pkg/dashboard/components/logviewer.go b/internal/pkg/dashboard/components/logviewer.go index 332952d381..44a918cb42 100644 --- a/internal/pkg/dashboard/components/logviewer.go +++ b/internal/pkg/dashboard/components/logviewer.go @@ -23,6 +23,7 @@ func NewLogViewer() *LogViewer { } widget.logs.ScrollToEnd(). + SetDynamicColors(true). SetMaxLines(maxLogLines). SetText(noData). SetBorderPadding(0, 0, 1, 1). @@ -54,7 +55,12 @@ func NewLogViewer() *LogViewer { } // WriteLog writes the log line to the widget. -func (widget *LogViewer) WriteLog(logLine string) { +func (widget *LogViewer) WriteLog(logLine, logError string) { + if logError != "" { + logLine = "[red]" + tview.Escape(logError) + "[-]\n" + } else { + logLine = tview.Escape(logLine) + "\n" + } + widget.logs.Write([]byte(logLine)) //nolint:errcheck - widget.logs.Write([]byte("\n")) //nolint:errcheck } diff --git a/internal/pkg/dashboard/dashboard.go b/internal/pkg/dashboard/dashboard.go index 13af827cf9..7d7775db76 100644 --- a/internal/pkg/dashboard/dashboard.go +++ b/internal/pkg/dashboard/dashboard.go @@ -70,7 +70,7 @@ type ResourceDataListener interface { // LogDataListener is a listener which is notified when a log line is received. type LogDataListener interface { - OnLogDataChange(node string, logLine string) + OnLogDataChange(node, logLine, logError string) } // NodeSelectListener is a listener which is notified when a node is selected. @@ -376,10 +376,7 @@ func (d *Dashboard) startDataHandler(ctx context.Context) func() error { defer d.resourceDataSource.Stop() //nolint:errcheck // start logs data source - if err := d.logDataSource.Start(ctx); err != nil { - return err - } - + d.logDataSource.Start(ctx) defer d.logDataSource.Stop() //nolint:errcheck lastLogTime := time.Now() @@ -393,11 +390,11 @@ func (d *Dashboard) startDataHandler(ctx context.Context) func() error { if time.Since(lastLogTime) < 50*time.Millisecond { d.app.QueueUpdate(func() { - d.processLog(nodeAlias, nodeLog.Log) + d.processLog(nodeAlias, nodeLog.Log, nodeLog.Error) }) } else { d.app.QueueUpdateDraw(func() { - d.processLog(nodeAlias, nodeLog.Log) + d.processLog(nodeAlias, nodeLog.Log, nodeLog.Error) }) } @@ -461,9 +458,9 @@ func (d *Dashboard) processNodeResource(nodeResource resourcedata.Data) { } // processLog re-renders the log components with new log data. -func (d *Dashboard) processLog(node, line string) { +func (d *Dashboard) processLog(node, logLine, logError string) { for _, component := range d.logDataListeners { - component.OnLogDataChange(node, line) + component.OnLogDataChange(node, logLine, logError) } } diff --git a/internal/pkg/dashboard/logdata/logdata.go b/internal/pkg/dashboard/logdata/logdata.go index 99b5cafccd..7bd20f817c 100644 --- a/internal/pkg/dashboard/logdata/logdata.go +++ b/internal/pkg/dashboard/logdata/logdata.go @@ -8,20 +8,26 @@ package logdata import ( "context" "errors" + "fmt" "strings" "sync" + "time" "golang.org/x/sync/errgroup" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "github.com/siderolabs/talos/cmd/talosctl/pkg/talos/helpers" + "github.com/siderolabs/talos/internal/pkg/dashboard/util" "github.com/siderolabs/talos/pkg/machinery/api/common" "github.com/siderolabs/talos/pkg/machinery/client" ) // Data is a log line from a node. type Data struct { - Node string - Log string + Node string + Log string + Error string } // Source is a data source for Kernel (dmesg) logs. @@ -45,14 +51,10 @@ func NewSource(client *client.Client) *Source { } // Start starts the data source. -func (source *Source) Start(ctx context.Context) error { - var err error - +func (source *Source) Start(ctx context.Context) { source.once.Do(func() { - err = source.start(ctx) + source.start(ctx) }) - - return err } // Stop stops the data source. @@ -62,38 +64,70 @@ func (source *Source) Stop() error { return source.eg.Wait() } -func (source *Source) start(ctx context.Context) error { +func (source *Source) start(ctx context.Context) { ctx, source.logCtxCancel = context.WithCancel(ctx) - dmesgStream, err := source.client.Dmesg(ctx, true, false) - if err != nil { - return err + for _, nodeContext := range util.NodeContexts(ctx) { + source.eg.Go(func() error { + return source.tailNodeWithRetries(nodeContext.Ctx, nodeContext.Node) + }) } +} - source.eg.Go(func() error { - return helpers.ReadGRPCStream(dmesgStream, func(data *common.Data, node string, multipleNodes bool) error { - if len(data.Bytes) == 0 { - return nil - } +func (source *Source) tailNodeWithRetries(ctx context.Context, node string) error { + for { + readErr := source.readDmesg(ctx, node) + if errors.Is(readErr, context.Canceled) || status.Code(readErr) == codes.Canceled { + return nil + } - line := strings.TrimSpace(string(data.Bytes)) - if line == "" { - return nil - } + if readErr != nil { + source.LogCh <- Data{Node: node, Error: readErr.Error()} + } - select { - case <-ctx.Done(): - if errors.Is(ctx.Err(), context.Canceled) { - return nil - } + // back off a bit before retrying + sleepWithContext(ctx, 30*time.Second) + } +} - return ctx.Err() - case source.LogCh <- Data{Node: node, Log: line}: - } +func (source *Source) readDmesg(ctx context.Context, node string) error { + dmesgStream, err := source.client.Dmesg(ctx, true, false) + if err != nil { + return fmt.Errorf("dashboard: error opening dmesg stream: %w", err) + } + readErr := helpers.ReadGRPCStream(dmesgStream, func(data *common.Data, _ string, _ bool) error { + if len(data.Bytes) == 0 { return nil - }) + } + + line := strings.TrimSpace(string(data.Bytes)) + if line == "" { + return nil + } + + select { + case <-ctx.Done(): + return ctx.Err() + case source.LogCh <- Data{Node: node, Log: line}: + } + + return nil }) + if readErr != nil { + return fmt.Errorf("error reading dmesg stream: %w", readErr) + } return nil } + +func sleepWithContext(ctx context.Context, d time.Duration) { + timer := time.NewTimer(d) + select { + case <-ctx.Done(): + if !timer.Stop() { + <-timer.C + } + case <-timer.C: + } +} diff --git a/internal/pkg/dashboard/resourcedata/resourcedata.go b/internal/pkg/dashboard/resourcedata/resourcedata.go index 6075b70489..9786790533 100644 --- a/internal/pkg/dashboard/resourcedata/resourcedata.go +++ b/internal/pkg/dashboard/resourcedata/resourcedata.go @@ -16,9 +16,8 @@ import ( "github.com/cosi-project/runtime/pkg/state" "github.com/siderolabs/gen/channel" "golang.org/x/sync/errgroup" - "google.golang.org/grpc/metadata" - "github.com/siderolabs/talos/pkg/machinery/client" + "github.com/siderolabs/talos/internal/pkg/dashboard/util" "github.com/siderolabs/talos/pkg/machinery/constants" "github.com/siderolabs/talos/pkg/machinery/resources/cluster" "github.com/siderolabs/talos/pkg/machinery/resources/config" @@ -70,10 +69,9 @@ func (source *Source) run(ctx context.Context) { source.NodeResourceCh = source.ch - nodes := source.nodes(ctx) - for _, node := range nodes { + for _, nodeContext := range util.NodeContexts(ctx) { source.eg.Go(func() error { - source.runResourceWatchWithRetries(ctx, node) + source.runResourceWatchWithRetries(nodeContext.Ctx, nodeContext.Node) return nil }) @@ -101,10 +99,6 @@ func (source *Source) runResourceWatchWithRetries(ctx context.Context, node stri //nolint:gocyclo,cyclop func (source *Source) runResourceWatch(ctx context.Context, node string) error { - if node != "" { - ctx = client.WithNode(ctx, node) - } - ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -211,22 +205,3 @@ func (source *Source) runResourceWatch(ctx context.Context, node string) error { } } } - -func (source *Source) nodes(ctx context.Context) []string { - md, mdOk := metadata.FromOutgoingContext(ctx) - if !mdOk { - return []string{""} // local node - } - - nodeVal := md.Get("node") - if len(nodeVal) > 0 { - return []string{nodeVal[0]} - } - - nodesVal := md.Get("nodes") - if len(nodesVal) == 0 { - return []string{""} // local node - } - - return nodesVal -} diff --git a/internal/pkg/dashboard/summary.go b/internal/pkg/dashboard/summary.go index 03ffe24011..b8482ea4b2 100644 --- a/internal/pkg/dashboard/summary.go +++ b/internal/pkg/dashboard/summary.go @@ -91,8 +91,8 @@ func (widget *SummaryGrid) OnResourceDataChange(nodeResource resourcedata.Data) } // OnLogDataChange implements the LogDataListener interface. -func (widget *SummaryGrid) OnLogDataChange(node string, logLine string) { - widget.logViewer(node).WriteLog(logLine) +func (widget *SummaryGrid) OnLogDataChange(node, logLine, logError string) { + widget.logViewer(node).WriteLog(logLine, logError) } func (widget *SummaryGrid) updateLogViewer() { diff --git a/internal/pkg/dashboard/util/util.go b/internal/pkg/dashboard/util/util.go new file mode 100644 index 0000000000..844b222739 --- /dev/null +++ b/internal/pkg/dashboard/util/util.go @@ -0,0 +1,49 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package util provides utility functions for the dashboard. +package util + +import ( + "context" + + "google.golang.org/grpc/metadata" + + "github.com/siderolabs/talos/pkg/machinery/client" +) + +// NodeContext contains the context.Context for a single node and the node name. +type NodeContext struct { + Ctx context.Context //nolint:containedctx + Node string +} + +// NodeContexts returns a list of NodeContexts from the given context. +// +// It extracts the node names from the outgoing GRPC context metadata. +// If the node name is not present in the metadata, context will be returned as-is with an empty node name. +func NodeContexts(ctx context.Context) []NodeContext { + md, mdOk := metadata.FromOutgoingContext(ctx) + if !mdOk { + return []NodeContext{{Ctx: ctx}} + } + + nodeVal := md.Get("node") + if len(nodeVal) > 0 { + return []NodeContext{{Ctx: ctx, Node: nodeVal[0]}} + } + + nodesVal := md.Get("nodes") + if len(nodesVal) == 0 { + return []NodeContext{{Ctx: ctx}} + } + + nodeContexts := make([]NodeContext, 0, len(nodesVal)) + + for _, node := range nodesVal { + nodeContexts = append(nodeContexts, NodeContext{Ctx: client.WithNode(ctx, node), Node: node}) + } + + return nodeContexts +} diff --git a/internal/pkg/dns/dns.go b/internal/pkg/dns/dns.go index 9bc7522a5b..838161c9ba 100644 --- a/internal/pkg/dns/dns.go +++ b/internal/pkg/dns/dns.go @@ -9,6 +9,7 @@ import ( "context" "errors" "fmt" + "io" "math/rand" "net" "slices" @@ -24,53 +25,8 @@ import ( "github.com/miekg/dns" "go.uber.org/zap" "golang.org/x/sys/unix" - - "github.com/siderolabs/talos/internal/pkg/utils" ) -// NewRunner creates a new Runner. -func NewRunner(srv Server, logger *zap.Logger) *Runner { - r := utils.NewRunner(srv.ActivateAndServe, srv.Shutdown, func(err error) bool { - // There a possible scenario where `Run` reached `ListenAndServe` and then yielded CPU time to another - // goroutine and then `Stop` reached `Shutdown`. In that case `ListenAndServe` will actually start after - // `Shutdown` and `Stop` method will forever block if we do not try again. - return strings.Contains(err.Error(), "server not started") - }) - - return &Runner{r: r, logger: logger} -} - -// Runner is a dns server handler. -type Runner struct { - r *utils.Runner - logger *zap.Logger -} - -// Server is a dns server. -type Server interface { - ActivateAndServe() error - Shutdown() error -} - -// Run runs dns server. -func (r *Runner) Run() error { - r.logger.Debug("starting dns server") - - err := r.r.Run() - - r.logger.Debug("dns server stopped", zap.Error(err)) - - return err -} - -// Stop stops dns server. It's safe to call even if server is already stopped. -func (r *Runner) Stop() { - err := r.r.Stop() - if err != nil { - r.logger.Warn("error shutting down dns server", zap.Error(err)) - } -} - // Cache is a [dns.Handler] to [plugin.Handler] adapter. type Cache struct { cache *cache.Cache @@ -211,22 +167,78 @@ type ServerOptions struct { WriteTimeout time.Duration IdleTimeout func() time.Duration MaxTCPQueries int + Logger *zap.Logger } // NewServer creates a new Server. -func NewServer(opts ServerOptions) Server { - return &server{&dns.Server{ - Listener: opts.Listener, - PacketConn: opts.PacketConn, - Handler: opts.Handler, - ReadTimeout: opts.ReadTimeout, - WriteTimeout: opts.WriteTimeout, - IdleTimeout: opts.IdleTimeout, - MaxTCPQueries: opts.MaxTCPQueries, - }} +func NewServer(opts ServerOptions) *Server { + return &Server{ + srv: &dns.Server{ + Listener: opts.Listener, + PacketConn: opts.PacketConn, + Handler: opts.Handler, + ReadTimeout: opts.ReadTimeout, + WriteTimeout: opts.WriteTimeout, + IdleTimeout: opts.IdleTimeout, + MaxTCPQueries: opts.MaxTCPQueries, + }, + logger: opts.Logger, + } } -type server struct{ *dns.Server } +// Server is a dns server. +type Server struct { + srv *dns.Server + logger *zap.Logger +} + +// Start starts the dns server. Returns a function to stop the server. +func (s *Server) Start(onDone func(err error)) (stop func(), stopped <-chan struct{}) { + done := make(chan struct{}) + + fn := sync.OnceFunc(func() { + for { + err := s.srv.Shutdown() + if err != nil { + if strings.Contains(err.Error(), "server not started") { + // There a possible scenario where `go func()` not yet reached `ActivateAndServe` and yielded CPU + // time to another goroutine and then this closure reached `Shutdown`. In that case + // `ActivateAndServe` will actually start after `Shutdown` and this closure will block forever + // because `go func()` will never exit and close `done` channel. + continue + } + + s.logger.Error("error shutting down dns server", zap.Error(err)) + } + + break + } + + closer := io.Closer(s.srv.Listener) + if closer == nil { + closer = s.srv.PacketConn + } + + if closer != nil { + err := closer.Close() + if err != nil && !errors.Is(err, net.ErrClosed) { + s.logger.Error("error closing dns server listener", zap.Error(err)) + } else { + s.logger.Debug("dns server listener closed") + } + } + + <-done + }) + + go func() { + defer close(done) + + onDone(s.srv.ActivateAndServe()) + }() + + return fn, done +} // NewTCPListener creates a new TCP listener. func NewTCPListener(network, addr string) (net.Listener, error) { diff --git a/internal/pkg/dns/dns_test.go b/internal/pkg/dns/dns_test.go index 702e2c6c0c..fb696a39ba 100644 --- a/internal/pkg/dns/dns_test.go +++ b/internal/pkg/dns/dns_test.go @@ -5,11 +5,7 @@ package dns_test import ( - "context" - "errors" "net" - "sync" - "sync/atomic" "testing" "time" @@ -18,10 +14,8 @@ import ( "github.com/siderolabs/gen/xslices" "github.com/siderolabs/gen/xtesting/check" "github.com/stretchr/testify/require" - "go.uber.org/zap" "go.uber.org/zap/zaptest" - "github.com/siderolabs/talos/internal/pkg/ctxutil" "github.com/siderolabs/talos/internal/pkg/dns" ) @@ -53,10 +47,8 @@ func TestDNS(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - ctx, stop := newServer(t, test.nameservers...) - - stopOnce := sync.OnceFunc(stop) - defer stopOnce() + stop := newServer(t, test.nameservers...) + defer stop() time.Sleep(10 * time.Millisecond) @@ -69,20 +61,14 @@ func TestDNS(t *testing.T) { t.Logf("r: %s", r) - stopOnce() - - <-ctx.Done() - - require.NoError(t, ctxutil.Cause(ctx)) + stop() }) } } func TestDNSEmptyDestinations(t *testing.T) { - ctx, stop := newServer(t) - - stopOnce := sync.OnceFunc(stop) - defer stopOnce() + stop := newServer(t) + defer stop() time.Sleep(10 * time.Millisecond) @@ -94,14 +80,10 @@ func TestDNSEmptyDestinations(t *testing.T) { require.NoError(t, err) require.Equal(t, dnssrv.RcodeServerFailure, r.Rcode, r) - stopOnce() - - <-ctx.Done() - - require.NoError(t, ctxutil.Cause(ctx)) + stop() } -func newServer(t *testing.T, nameservers ...string) (context.Context, func()) { +func newServer(t *testing.T, nameservers ...string) func() { l := zaptest.NewLogger(t) handler := dns.NewHandler(l) @@ -121,12 +103,21 @@ func newServer(t *testing.T, nameservers ...string) (context.Context, func()) { pc, err := dns.NewUDPPacketConn("udp", "127.0.0.53:10700") require.NoError(t, err) - runner := dns.NewRunner(dns.NewServer(dns.ServerOptions{ + srv := dns.NewServer(dns.ServerOptions{ PacketConn: pc, Handler: dns.NewCache(handler, l), - }), l) + Logger: l, + }) - return ctxutil.MonitorFn(context.Background(), runner.Run), runner.Stop + stop, _ := srv.Start(func(err error) { + if err != nil { + t.Errorf("error running dns server: %v", err) + } + + t.Logf("dns server stopped") + }) + + return stop } func createQuery() *dnssrv.Msg { @@ -144,86 +135,3 @@ func createQuery() *dnssrv.Msg { }, } } - -func TestActivateFailure(t *testing.T) { - // Ensure that we correctly handle an error inside [dns.Runner.Run]. - l := zaptest.NewLogger(t) - - runner := dns.NewRunner(&testServer{t: t}, l) - - ctx := ctxutil.MonitorFn(context.Background(), runner.Run) - defer runner.Stop() - - <-ctx.Done() - - require.Equal(t, errFailed, ctxutil.Cause(ctx)) -} - -func TestRunnerStopsBeforeRun(t *testing.T) { - // Ensure that we correctly handle an error inside [dns.Runner.Run]. - l := zap.NewNop() - - for range 1000 { - runner := dns.NewRunner(&runnerStopper{}, l) - - ctx := ctxutil.MonitorFn(context.Background(), runner.Run) - runner.Stop() - - <-ctx.Done() - } - - for range 1000 { - runner := dns.NewRunner(&runnerStopper{}, l) - - runner.Stop() - ctx := ctxutil.MonitorFn(context.Background(), runner.Run) - - <-ctx.Done() - } -} - -type testServer struct { - t *testing.T -} - -var errFailed = errors.New("listen failure") - -func (ts *testServer) ActivateAndServe() error { return errFailed } - -func (ts *testServer) Shutdown() error { - ts.t.Fatal("should not be called") - - return nil -} - -func (ts *testServer) Name() string { - return "test-server" -} - -type runnerStopper struct { - val atomic.Pointer[chan struct{}] -} - -func (rs *runnerStopper) ActivateAndServe() error { - ch := make(chan struct{}) - - if rs.val.Swap(&ch) != nil { - panic("chan should be empty") - } - - <-ch - - return nil -} - -func (rs *runnerStopper) Shutdown() error { - chPtr := rs.val.Load() - - if chPtr == nil { - return errors.New("server not started") - } - - close(*chPtr) - - return nil -} diff --git a/internal/pkg/utils/utils.go b/internal/pkg/utils/utils.go deleted file mode 100644 index 3334b89418..0000000000 --- a/internal/pkg/utils/utils.go +++ /dev/null @@ -1,74 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -// Package utils provides various utility functions. -package utils - -import ( - "errors" - "sync/atomic" -) - -const ( - notRunning = iota - running - closing - closed -) - -// Runner is a fn/stop runner. -type Runner struct { - fn func() error - stop func() error - retryStop func(error) bool - status atomic.Int64 - done chan struct{} -} - -// NewRunner creates a new runner. -func NewRunner(fn, stop func() error, retryStop func(error) bool) *Runner { - return &Runner{fn: fn, stop: stop, retryStop: retryStop, done: make(chan struct{})} -} - -// Run runs fn. -func (r *Runner) Run() error { - defer func() { - if r.status.Swap(closed) != closed { - close(r.done) - } - }() - - if !r.status.CompareAndSwap(notRunning, running) { - return ErrAlreadyRunning - } - - return r.fn() -} - -var ( - // ErrAlreadyRunning is the error that is returned when runner is already running/closing/closed. - ErrAlreadyRunning = errors.New("runner is already running/closing/closed") - // ErrNotRunning is the error that is returned when runner is not running/closing/closed. - ErrNotRunning = errors.New("runner is not running/closing/closed") -) - -// Stop stops runner. It's safe to call even if runner is already stopped or in process of being stopped. -func (r *Runner) Stop() error { - if r.status.CompareAndSwap(notRunning, closing) || !r.status.CompareAndSwap(running, closing) { - return ErrNotRunning - } - - for { - err := r.stop() - if err != nil { - if r.retryStop(err) && r.status.Load() == closing { - continue - } - } - - <-r.done - - return err - } -} diff --git a/pkg/cluster/check/kubernetes.go b/pkg/cluster/check/kubernetes.go index 90046490aa..8a0f86c74d 100644 --- a/pkg/cluster/check/kubernetes.go +++ b/pkg/cluster/check/kubernetes.go @@ -9,6 +9,7 @@ import ( "context" "fmt" "net/netip" + "slices" "strings" "github.com/cosi-project/runtime/pkg/safe" @@ -420,7 +421,10 @@ func K8sControlPlaneStaticPods(ctx context.Context, cl ClusterInfo) error { } if len(expectedStaticPods) > 0 { - return fmt.Errorf("missing static pods on node %s: %v", node.InternalIP, maps.Keys(expectedStaticPods)) + missingStaticPods := maps.Keys(expectedStaticPods) + slices.Sort(missingStaticPods) + + return fmt.Errorf("missing static pods on node %s: %v", node.InternalIP, missingStaticPods) } } diff --git a/pkg/imager/imager.go b/pkg/imager/imager.go index 2f834865d0..9180b5001f 100644 --- a/pkg/imager/imager.go +++ b/pkg/imager/imager.go @@ -39,6 +39,7 @@ type Imager struct { prof profile.Profile overlayInstaller overlay.Installer[overlay.ExtraOptions] + extraProfiles map[string]profile.Profile tempDir string @@ -192,6 +193,10 @@ func (i *Imager) handleOverlay(ctx context.Context, report *reporter.Reporter) e i.overlayInstaller = executor.New(filepath.Join(i.tempDir, constants.ImagerOverlayInstallersPath, i.prof.Overlay.Name)) + if i.extraProfiles == nil { + i.extraProfiles = make(map[string]profile.Profile) + } + for _, profilePath := range profileYAMLs { profileName := strings.TrimSuffix(filepath.Base(profilePath), ".yaml") @@ -206,7 +211,7 @@ func (i *Imager) handleOverlay(ctx context.Context, report *reporter.Reporter) e return fmt.Errorf("failed to unmarshal profile: %w", err) } - profile.Default[profileName] = overlayProfile + i.extraProfiles[profileName] = overlayProfile } return nil @@ -215,7 +220,12 @@ func (i *Imager) handleOverlay(ctx context.Context, report *reporter.Reporter) e func (i *Imager) handleProf() error { // resolve the profile if it contains a base name if i.prof.BaseProfileName != "" { - baseProfile, ok := profile.Default[i.prof.BaseProfileName] + baseProfile, ok := i.extraProfiles[i.prof.BaseProfileName] + + if !ok { + baseProfile, ok = profile.Default[i.prof.BaseProfileName] + } + if !ok { return fmt.Errorf("unknown base profile: %s", i.prof.BaseProfileName) } diff --git a/pkg/imager/out.go b/pkg/imager/out.go index 0b427f136e..7af7a11ea0 100644 --- a/pkg/imager/out.go +++ b/pkg/imager/out.go @@ -7,7 +7,6 @@ package imager import ( "context" "encoding/pem" - "errors" "fmt" "log" "os" @@ -91,23 +90,16 @@ func (i *Imager) outISO(ctx context.Context, path string, report *reporter.Repor if i.prof.SecureBootEnabled() { isoOptions := pointer.SafeDeref(i.prof.Output.ISOOptions) - crtData, readErr := os.ReadFile(i.prof.Input.SecureBoot.SecureBootSigner.CertPath) - if readErr != nil { - return fmt.Errorf("failed to read secureboot uki certificate: %w", readErr) - } - - block, rest := pem.Decode(crtData) - if block == nil { - return errors.New("failed to decode PEM data") - } + var signer pesign.CertificateSigner - if len(rest) > 0 { - return errors.New("more than one PEM block found in PEM data") + signer, err = i.prof.Input.SecureBoot.SecureBootSigner.GetSigner(ctx) + if err != nil { + return fmt.Errorf("failed to get SecureBoot signer: %w", err) } derCrtPath := filepath.Join(i.tempDir, "uki.der") - if err = os.WriteFile(derCrtPath, block.Bytes, 0o600); err != nil { + if err = os.WriteFile(derCrtPath, signer.Certificate().Raw, 0o600); err != nil { return fmt.Errorf("failed to write uki.der: %w", err) } @@ -134,13 +126,6 @@ func (i *Imager) outISO(ctx context.Context, path string, report *reporter.Repor report.Report(reporter.Update{Message: "generating SecureBoot database...", Status: reporter.StatusRunning}) // generate the database automatically from provided values - var signer pesign.CertificateSigner - - signer, err = i.prof.Input.SecureBoot.SecureBootSigner.GetSigner(ctx) - if err != nil { - return fmt.Errorf("failed to get SecureBoot signer: %w", err) - } - enrolledPEM := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: signer.Certificate().Raw, diff --git a/pkg/machinery/compatibility/kubernetes_version.go b/pkg/machinery/compatibility/kubernetes_version.go index b877c1cc92..f5ff140274 100644 --- a/pkg/machinery/compatibility/kubernetes_version.go +++ b/pkg/machinery/compatibility/kubernetes_version.go @@ -16,6 +16,7 @@ import ( "github.com/siderolabs/talos/pkg/machinery/compatibility/talos15" "github.com/siderolabs/talos/pkg/machinery/compatibility/talos16" "github.com/siderolabs/talos/pkg/machinery/compatibility/talos17" + "github.com/siderolabs/talos/pkg/machinery/compatibility/talos18" ) // KubernetesVersion embeds Kubernetes version. @@ -56,6 +57,8 @@ func (v *KubernetesVersion) SupportedWith(target *TalosVersion) error { minK8sVersion, maxK8sVersion = talos16.MinimumKubernetesVersion, talos16.MaximumKubernetesVersion case talos17.MajorMinor: // upgrades to 1.7.x minK8sVersion, maxK8sVersion = talos17.MinimumKubernetesVersion, talos17.MaximumKubernetesVersion + case talos18.MajorMinor: // upgrades to 1.8.x + minK8sVersion, maxK8sVersion = talos18.MinimumKubernetesVersion, talos18.MaximumKubernetesVersion default: return fmt.Errorf("compatibility with version %s is not supported", target.String()) } diff --git a/pkg/machinery/compatibility/kubernetes_version_test.go b/pkg/machinery/compatibility/kubernetes_version_test.go index 0ef202ff05..8aee46f26b 100644 --- a/pkg/machinery/compatibility/kubernetes_version_test.go +++ b/pkg/machinery/compatibility/kubernetes_version_test.go @@ -220,6 +220,39 @@ func TestKubernetesCompatibility17(t *testing.T) { } } +func TestKubernetesCompatibility18(t *testing.T) { + for _, tt := range []kubernetesVersionTest{ + { + kubernetesVersion: "1.27.1", + target: "1.8.0", + }, + { + kubernetesVersion: "1.26.1", + target: "1.8.0", + }, + { + kubernetesVersion: "1.30.3", + target: "1.8.0-beta.0", + }, + { + kubernetesVersion: "1.31.0-rc.0", + target: "1.8.7", + }, + { + kubernetesVersion: "1.32.0-alpha.0", + target: "1.8.0", + expectedError: "version of Kubernetes 1.32.0-alpha.0 is too new to be used with Talos 1.8.0", + }, + { + kubernetesVersion: "1.25.1", + target: "1.8.0", + expectedError: "version of Kubernetes 1.25.1 is too old to be used with Talos 1.8.0", + }, + } { + runKubernetesVersionTest(t, tt) + } +} + func TestKubernetesCompatibilityUnsupported(t *testing.T) { for _, tt := range []kubernetesVersionTest{ { diff --git a/pkg/machinery/compatibility/talos18/talos18.go b/pkg/machinery/compatibility/talos18/talos18.go new file mode 100644 index 0000000000..a7074ac0cc --- /dev/null +++ b/pkg/machinery/compatibility/talos18/talos18.go @@ -0,0 +1,28 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package talos18 provides compatibility constants for Talos 1.8. +package talos18 + +import ( + "github.com/blang/semver/v4" +) + +// MajorMinor is the major.minor version of Talos 1.8. +var MajorMinor = [2]uint64{1, 8} + +// MinimumHostUpgradeVersion is the minimum version of Talos that can be upgraded to 1.8. +var MinimumHostUpgradeVersion = semver.MustParse("1.5.0") + +// MaximumHostDowngradeVersion is the maximum (not inclusive) version of Talos that can be downgraded to 1.8. +var MaximumHostDowngradeVersion = semver.MustParse("1.10.0") + +// DeniedHostUpgradeVersions are the versions of Talos that cannot be upgraded to 1.8. +var DeniedHostUpgradeVersions = []semver.Version{} + +// MinimumKubernetesVersion is the minimum version of Kubernetes is supported with 1.8. +var MinimumKubernetesVersion = semver.MustParse("1.26.0") + +// MaximumKubernetesVersion is the maximum version of Kubernetes is supported with 1.8. +var MaximumKubernetesVersion = semver.MustParse("1.31.99") diff --git a/pkg/machinery/compatibility/talos_version.go b/pkg/machinery/compatibility/talos_version.go index 6b94d72fab..cfa80abab0 100644 --- a/pkg/machinery/compatibility/talos_version.go +++ b/pkg/machinery/compatibility/talos_version.go @@ -17,6 +17,7 @@ import ( "github.com/siderolabs/talos/pkg/machinery/compatibility/talos15" "github.com/siderolabs/talos/pkg/machinery/compatibility/talos16" "github.com/siderolabs/talos/pkg/machinery/compatibility/talos17" + "github.com/siderolabs/talos/pkg/machinery/compatibility/talos18" ) // TalosVersion embeds Talos version. @@ -79,6 +80,9 @@ func (v *TalosVersion) UpgradeableFrom(host *TalosVersion) error { case talos17.MajorMinor: // upgrades to 1.7.x minHostUpgradeVersion, maxHostDowngradeVersion = talos17.MinimumHostUpgradeVersion, talos17.MaximumHostDowngradeVersion deniedHostUpgradeVersions = talos17.DeniedHostUpgradeVersions + case talos18.MajorMinor: // upgrades to 1.8.x + minHostUpgradeVersion, maxHostDowngradeVersion = talos18.MinimumHostUpgradeVersion, talos18.MaximumHostDowngradeVersion + deniedHostUpgradeVersions = talos18.DeniedHostUpgradeVersions default: return fmt.Errorf("upgrades to version %s are not supported", v.version.String()) } diff --git a/pkg/machinery/compatibility/talos_version_test.go b/pkg/machinery/compatibility/talos_version_test.go index 3c3d322cf8..b9b9a0243d 100644 --- a/pkg/machinery/compatibility/talos_version_test.go +++ b/pkg/machinery/compatibility/talos_version_test.go @@ -245,18 +245,59 @@ func TestTalosUpgradeCompatibility17(t *testing.T) { } } -func TestTalosUpgradeCompatibilityUnsupported(t *testing.T) { +func TestTalosUpgradeCompatibility18(t *testing.T) { for _, tt := range []talosVersionTest{ { - host: "1.3.0", - target: "1.8.0-alpha.0", - expectedError: `upgrades to version 1.8.0-alpha.0 are not supported`, + host: "1.6.0", + target: "1.8.0", + }, + { + host: "1.5.0-alpha.0", + target: "1.8.0", + }, + { + host: "1.5.0", + target: "1.8.0-alpha.0", + }, + { + host: "1.7.0", + target: "1.8.1", + }, + { + host: "1.7.0-beta.0", + target: "1.8.0", + }, + { + host: "1.9.5", + target: "1.8.3", }, { host: "1.4.0", + target: "1.8.0", + expectedError: `host version 1.4.0 is too old to upgrade to Talos 1.8.0`, + }, + { + host: "1.10.0-alpha.0", + target: "1.8.0", + expectedError: `host version 1.10.0-alpha.0 is too new to downgrade to Talos 1.8.0`, + }, + } { + runTalosVersionTest(t, tt) + } +} + +func TestTalosUpgradeCompatibilityUnsupported(t *testing.T) { + for _, tt := range []talosVersionTest{ + { + host: "1.3.0", target: "1.9.0-alpha.0", expectedError: `upgrades to version 1.9.0-alpha.0 are not supported`, }, + { + host: "1.4.0", + target: "1.10.0-alpha.0", + expectedError: `upgrades to version 1.10.0-alpha.0 are not supported`, + }, } { runTalosVersionTest(t, tt) } diff --git a/pkg/machinery/config/types/v1alpha1/v1alpha1_validation.go b/pkg/machinery/config/types/v1alpha1/v1alpha1_validation.go index 76d440928c..5c57c8ce93 100644 --- a/pkg/machinery/config/types/v1alpha1/v1alpha1_validation.go +++ b/pkg/machinery/config/types/v1alpha1/v1alpha1_validation.go @@ -374,6 +374,10 @@ func (c *ClusterConfig) Validate(isControlPlane bool) error { } } + if c.ClusterCA != nil && !isControlPlane && len(c.ClusterCA.Key) > 0 { + result = multierror.Append(result, errors.New("cluster CA key is not allowed on non-controlplane nodes (.cluster.ca)")) + } + result = multierror.Append( result, c.ClusterInlineManifests.Validate(), diff --git a/pkg/machinery/constants/constants.go b/pkg/machinery/constants/constants.go index a33e3c728a..938d295cfe 100644 --- a/pkg/machinery/constants/constants.go +++ b/pkg/machinery/constants/constants.go @@ -14,7 +14,7 @@ import ( const ( // DefaultKernelVersion is the default Linux kernel version. - DefaultKernelVersion = "6.6.24-talos" + DefaultKernelVersion = "6.6.26-talos" // KernelModulesPath is the default path to the kernel modules without the kernel version. KernelModulesPath = "/lib/modules" @@ -342,7 +342,7 @@ const ( // DefaultKubernetesVersion is the default target version of the control plane. // renovate: datasource=github-releases depName=kubernetes/kubernetes - DefaultKubernetesVersion = "1.30.0-rc.1" + DefaultKubernetesVersion = "1.30.0-rc.2" // SupportedKubernetesVersions is the number of Kubernetes versions supported by Talos starting from DefaultKubernesVersion going backwards. SupportedKubernetesVersions = 6 @@ -477,7 +477,7 @@ const ( TrustdUserID = 51 // DefaultContainerdVersion is the default container runtime version. - DefaultContainerdVersion = "1.7.14" + DefaultContainerdVersion = "1.7.15" // SystemContainerdNamespace is the Containerd namespace for Talos services. SystemContainerdNamespace = "system" diff --git a/pkg/machinery/gendata/data/pkgs b/pkg/machinery/gendata/data/pkgs index dd12ac5ac8..b6af11d27f 100644 --- a/pkg/machinery/gendata/data/pkgs +++ b/pkg/machinery/gendata/data/pkgs @@ -1 +1 @@ -v1.7.0-2-g6101299 \ No newline at end of file +v1.7.0-5-gb7f1920 \ No newline at end of file diff --git a/website/content/v1.7/_index.md b/website/content/v1.7/_index.md index 1d9b07d0a1..2df3d0a2fe 100644 --- a/website/content/v1.7/_index.md +++ b/website/content/v1.7/_index.md @@ -4,8 +4,8 @@ no_list: true linkTitle: "Documentation" cascade: type: docs -lastRelease: v1.7.0-alpha.1 -kubernetesRelease: "1.30.0-rc.1" +lastRelease: v1.7.0-beta.0 +kubernetesRelease: "1.30.0-rc.2" prevKubernetesRelease: "1.28.3" nvidiaContainerToolkitRelease: "v1.14.5" nvidiaDriverRelease: "535.129.03" diff --git a/website/content/v1.7/reference/cli.md b/website/content/v1.7/reference/cli.md index 775884e2bb..6b18e33074 100644 --- a/website/content/v1.7/reference/cli.md +++ b/website/content/v1.7/reference/cli.md @@ -135,7 +135,7 @@ talosctl cluster create [flags] --ipxe-boot-script string iPXE boot script (URL) to use --iso-path string the ISO path to use for the initial boot (VM only) --kubeprism-port int KubePrism port (set to 0 to disable) (default 7445) - --kubernetes-version string desired kubernetes version to run (default "1.30.0-rc.1") + --kubernetes-version string desired kubernetes version to run (default "1.30.0-rc.2") --memory int the limit on memory usage in MB (each control plane/VM) (default 2048) --memory-workers int the limit on memory usage in MB (each worker/VM) (default 2048) --mtu int MTU of the cluster network (default 1500) @@ -1333,7 +1333,7 @@ talosctl gen config [flags] -h, --help help for config --install-disk string the disk to install to (default "/dev/sda") --install-image string the image used to perform an installation (default "ghcr.io/siderolabs/installer:latest") - --kubernetes-version string desired kubernetes version to run (default "1.30.0-rc.1") + --kubernetes-version string desired kubernetes version to run (default "1.30.0-rc.2") -o, --output string destination to output generated files. when multiple output types are specified, it must be a directory. for a single output type, it must either be a file path, or "-" for stdout -t, --output-types strings types of outputs to be generated. valid types are: ["controlplane" "worker" "talosconfig"] (default [controlplane,worker,talosconfig]) -p, --persist the desired persist value for configs (default true) @@ -2946,7 +2946,7 @@ talosctl upgrade-k8s [flags] --pre-pull-images pre-pull images before upgrade (default true) --proxy-image string kube-proxy image to use (default "registry.k8s.io/kube-proxy") --scheduler-image string kube-scheduler image to use (default "registry.k8s.io/kube-scheduler") - --to string the Kubernetes control plane version to upgrade to (default "1.30.0-rc.1") + --to string the Kubernetes control plane version to upgrade to (default "1.30.0-rc.2") --upgrade-kubelet upgrade kubelet service (default true) --with-docs patch all machine configs adding the documentation for each field (default true) --with-examples patch all machine configs with the commented examples (default true) diff --git a/website/content/v1.7/reference/configuration/v1alpha1/config.md b/website/content/v1.7/reference/configuration/v1alpha1/config.md index fd84b330c9..8531cd59f1 100644 --- a/website/content/v1.7/reference/configuration/v1alpha1/config.md +++ b/website/content/v1.7/reference/configuration/v1alpha1/config.md @@ -90,7 +90,7 @@ controlPlane: {{< /highlight >}} | | |`kubelet` |KubeletConfig |Used to provide additional options to the kubelet.
Show example(s){{< highlight yaml >}} kubelet: - image: ghcr.io/siderolabs/kubelet:v1.30.0-rc.1 # The `image` field is an optional reference to an alternative kubelet image. + image: ghcr.io/siderolabs/kubelet:v1.30.0-rc.2 # The `image` field is an optional reference to an alternative kubelet image. # The `extraArgs` field is used to provide additional flags to the kubelet. extraArgs: feature-gates: ServerSideApply=true @@ -499,7 +499,7 @@ KubeletConfig represents the kubelet config values. {{< highlight yaml >}} machine: kubelet: - image: ghcr.io/siderolabs/kubelet:v1.30.0-rc.1 # The `image` field is an optional reference to an alternative kubelet image. + image: ghcr.io/siderolabs/kubelet:v1.30.0-rc.2 # The `image` field is an optional reference to an alternative kubelet image. # The `extraArgs` field is used to provide additional flags to the kubelet. extraArgs: feature-gates: ServerSideApply=true @@ -552,7 +552,7 @@ machine: | Field | Type | Description | Value(s) | |-------|------|-------------|----------| |`image` |string |The `image` field is an optional reference to an alternative kubelet image.
Show example(s){{< highlight yaml >}} -image: ghcr.io/siderolabs/kubelet:v1.30.0-rc.1 +image: ghcr.io/siderolabs/kubelet:v1.30.0-rc.2 {{< /highlight >}}
| | |`clusterDNS` |[]string |The `ClusterDNS` field is an optional reference to an alternative kubelet clusterDNS ip list.
Show example(s){{< highlight yaml >}} clusterDNS: @@ -2883,7 +2883,7 @@ serviceAccount: {{< /highlight >}}
| | |`apiServer` |APIServerConfig |API server specific configuration options.
Show example(s){{< highlight yaml >}} apiServer: - image: registry.k8s.io/kube-apiserver:v1.30.0-rc.1 # The container image used in the API server manifest. + image: registry.k8s.io/kube-apiserver:v1.30.0-rc.2 # The container image used in the API server manifest. # Extra arguments to supply to the API server. extraArgs: feature-gates: ServerSideApply=true @@ -2922,14 +2922,14 @@ apiServer: {{< /highlight >}}
| | |`controllerManager` |ControllerManagerConfig |Controller manager server specific configuration options.
Show example(s){{< highlight yaml >}} controllerManager: - image: registry.k8s.io/kube-controller-manager:v1.30.0-rc.1 # The container image used in the controller manager manifest. + image: registry.k8s.io/kube-controller-manager:v1.30.0-rc.2 # The container image used in the controller manager manifest. # Extra arguments to supply to the controller manager. extraArgs: feature-gates: ServerSideApply=true {{< /highlight >}}
| | |`proxy` |ProxyConfig |Kube-proxy server-specific configuration options
Show example(s){{< highlight yaml >}} proxy: - image: registry.k8s.io/kube-proxy:v1.30.0-rc.1 # The container image used in the kube-proxy manifest. + image: registry.k8s.io/kube-proxy:v1.30.0-rc.2 # The container image used in the kube-proxy manifest. mode: ipvs # proxy mode of kube-proxy. # Extra arguments to supply to kube-proxy. extraArgs: @@ -2940,7 +2940,7 @@ proxy: {{< /highlight >}}
| | |`scheduler` |SchedulerConfig |Scheduler server specific configuration options.
Show example(s){{< highlight yaml >}} scheduler: - image: registry.k8s.io/kube-scheduler:v1.30.0-rc.1 # The container image used in the scheduler manifest. + image: registry.k8s.io/kube-scheduler:v1.30.0-rc.2 # The container image used in the scheduler manifest. # Extra arguments to supply to the scheduler. extraArgs: feature-gates: AllBeta=true @@ -3184,7 +3184,7 @@ APIServerConfig represents the kube apiserver configuration options. {{< highlight yaml >}} cluster: apiServer: - image: registry.k8s.io/kube-apiserver:v1.30.0-rc.1 # The container image used in the API server manifest. + image: registry.k8s.io/kube-apiserver:v1.30.0-rc.2 # The container image used in the API server manifest. # Extra arguments to supply to the API server. extraArgs: feature-gates: ServerSideApply=true @@ -3226,7 +3226,7 @@ cluster: | Field | Type | Description | Value(s) | |-------|------|-------------|----------| |`image` |string |The container image used in the API server manifest.
Show example(s){{< highlight yaml >}} -image: registry.k8s.io/kube-apiserver:v1.30.0-rc.1 +image: registry.k8s.io/kube-apiserver:v1.30.0-rc.2 {{< /highlight >}}
| | |`extraArgs` |map[string]string |Extra arguments to supply to the API server. | | |`extraVolumes` |[]VolumeMountConfig |Extra volumes to mount to the API server static pod. | | @@ -3365,7 +3365,7 @@ ControllerManagerConfig represents the kube controller manager configuration opt {{< highlight yaml >}} cluster: controllerManager: - image: registry.k8s.io/kube-controller-manager:v1.30.0-rc.1 # The container image used in the controller manager manifest. + image: registry.k8s.io/kube-controller-manager:v1.30.0-rc.2 # The container image used in the controller manager manifest. # Extra arguments to supply to the controller manager. extraArgs: feature-gates: ServerSideApply=true @@ -3375,7 +3375,7 @@ cluster: | Field | Type | Description | Value(s) | |-------|------|-------------|----------| |`image` |string |The container image used in the controller manager manifest.
Show example(s){{< highlight yaml >}} -image: registry.k8s.io/kube-controller-manager:v1.30.0-rc.1 +image: registry.k8s.io/kube-controller-manager:v1.30.0-rc.2 {{< /highlight >}}
| | |`extraArgs` |map[string]string |Extra arguments to supply to the controller manager. | | |`extraVolumes` |[]VolumeMountConfig |Extra volumes to mount to the controller manager static pod. | | @@ -3445,7 +3445,7 @@ ProxyConfig represents the kube proxy configuration options. {{< highlight yaml >}} cluster: proxy: - image: registry.k8s.io/kube-proxy:v1.30.0-rc.1 # The container image used in the kube-proxy manifest. + image: registry.k8s.io/kube-proxy:v1.30.0-rc.2 # The container image used in the kube-proxy manifest. mode: ipvs # proxy mode of kube-proxy. # Extra arguments to supply to kube-proxy. extraArgs: @@ -3462,7 +3462,7 @@ cluster: disabled: false {{< /highlight >}}
| | |`image` |string |The container image used in the kube-proxy manifest.
Show example(s){{< highlight yaml >}} -image: registry.k8s.io/kube-proxy:v1.30.0-rc.1 +image: registry.k8s.io/kube-proxy:v1.30.0-rc.2 {{< /highlight >}}
| | |`mode` |string |
proxy mode of kube-proxy.The default is 'iptables'.
| | |`extraArgs` |map[string]string |Extra arguments to supply to kube-proxy. | | @@ -3481,7 +3481,7 @@ SchedulerConfig represents the kube scheduler configuration options. {{< highlight yaml >}} cluster: scheduler: - image: registry.k8s.io/kube-scheduler:v1.30.0-rc.1 # The container image used in the scheduler manifest. + image: registry.k8s.io/kube-scheduler:v1.30.0-rc.2 # The container image used in the scheduler manifest. # Extra arguments to supply to the scheduler. extraArgs: feature-gates: AllBeta=true @@ -3491,7 +3491,7 @@ cluster: | Field | Type | Description | Value(s) | |-------|------|-------------|----------| |`image` |string |The container image used in the scheduler manifest.
Show example(s){{< highlight yaml >}} -image: registry.k8s.io/kube-scheduler:v1.30.0-rc.1 +image: registry.k8s.io/kube-scheduler:v1.30.0-rc.2 {{< /highlight >}}
| | |`extraArgs` |map[string]string |Extra arguments to supply to the scheduler. | | |`extraVolumes` |[]VolumeMountConfig |Extra volumes to mount to the scheduler static pod. | |