Add Kubernetes DNS with CoreDNS

This adds Kubernetes DNS with a CoreDNS instance running on the host. This has some distinct advantages over
running it inside a container, like a simplified lifecycle (no state reconciliation) and the possibility of redirecting
all host DNS requests over this instance for observability or central DNSSEC enforcement.

Test Plan: Manually tested (`host kubernetes` in an Alpine container), will be covered by CTS.

X-Origin-Diff: phab/D616
GitOrigin-RevId: 281f5f384f4ef7eba2c3c3190be8e6a89772295c
diff --git a/core/internal/kubernetes/service.go b/core/internal/kubernetes/service.go
index a22b6b9..55c20bf 100644
--- a/core/internal/kubernetes/service.go
+++ b/core/internal/kubernetes/service.go
@@ -33,6 +33,7 @@
 
 	"git.monogon.dev/source/nexantic.git/core/internal/common/supervisor"
 	"git.monogon.dev/source/nexantic.git/core/internal/kubernetes/clusternet"
+	"git.monogon.dev/source/nexantic.git/core/internal/kubernetes/dns"
 	"git.monogon.dev/source/nexantic.git/core/internal/kubernetes/nfproxy"
 	"git.monogon.dev/source/nexantic.git/core/internal/kubernetes/pki"
 	"git.monogon.dev/source/nexantic.git/core/internal/kubernetes/reconciler"
@@ -55,6 +56,7 @@
 	controllerManagerLogs *logbuffer.LogBuffer
 	schedulerLogs         *logbuffer.LogBuffer
 	kubeletLogs           *logbuffer.LogBuffer
+	corednsLogs           *logbuffer.LogBuffer
 }
 
 type Service struct {
@@ -82,6 +84,7 @@
 		controllerManagerLogs: logbuffer.New(5000, 16384),
 		schedulerLogs:         logbuffer.New(5000, 16384),
 		kubeletLogs:           logbuffer.New(5000, 16384),
+		corednsLogs:           logbuffer.New(5000, 16384),
 	}
 	s.stateMu.Lock()
 	s.state = st
@@ -120,6 +123,8 @@
 		return fmt.Errorf("failed to get hostname: %w", err)
 	}
 
+	dnsHostIP := s.c.AdvertiseAddress // TODO: Which IP to use
+
 	apiserver := &apiserverService{
 		KPKI:                        s.c.KPKI,
 		AdvertiseAddress:            s.c.AdvertiseAddress,
@@ -130,7 +135,7 @@
 
 	kubelet := kubeletService{
 		NodeName:           hostname,
-		ClusterDNS:         nil,
+		ClusterDNS:         []net.IP{dnsHostIP},
 		KubeletDirectory:   &s.c.Root.Data.Kubernetes.Kubelet,
 		EphemeralDirectory: &s.c.Root.Ephemeral,
 		Output:             st.kubeletLogs,
@@ -162,6 +167,12 @@
 		ClientSet:   clientSet,
 	}
 
+	dns := dns.Service{
+		Kubeconfig:    masterKubeconfig,
+		Output:        s.state.corednsLogs,
+		ClusterDomain: "cluster.local", // Hardcode this here until we make this configurable
+	}
+
 	for _, sub := range []struct {
 		name     string
 		runnable supervisor.Runnable
@@ -175,6 +186,7 @@
 		{"csi-provisioner", csiProvisioner.Run},
 		{"clusternet", clusternet.Run},
 		{"nfproxy", nfproxy.Run},
+		{"dns", dns.Run},
 	} {
 		err := supervisor.Run(ctx, sub.name, sub.runnable)
 		if err != nil {
@@ -204,6 +216,8 @@
 		return s.state.schedulerLogs.ReadLinesTruncated(n, "..."), nil
 	case "kubelet":
 		return s.state.kubeletLogs.ReadLinesTruncated(n, "..."), nil
+	case "coredns":
+		return s.state.corednsLogs.ReadLinesTruncated(n, "..."), nil
 	default:
 		return nil, errors.New("component not available")
 	}