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/BUILD b/core/BUILD
index ea4b70f..33ec07d 100644
--- a/core/BUILD
+++ b/core/BUILD
@@ -26,6 +26,9 @@
# Hyperkube
"//core/cmd/kube": "/kubernetes/bin/kube",
+ # CoreDNS
+ "@com_github_coredns_coredns//:coredns": "/kubernetes/bin/coredns",
+
# runsc/gVisor
"@com_github_google_gvisor//runsc": "/containerd/bin/runsc",
"@com_github_google_gvisor_containerd_shim//cmd/containerd-shim-runsc-v1": "/containerd/bin/containerd-shim-runsc-v1",
diff --git a/core/internal/kubernetes/BUILD.bazel b/core/internal/kubernetes/BUILD.bazel
index 6b5d652..e040a9d 100644
--- a/core/internal/kubernetes/BUILD.bazel
+++ b/core/internal/kubernetes/BUILD.bazel
@@ -17,6 +17,7 @@
"//core/internal/common:go_default_library",
"//core/internal/common/supervisor:go_default_library",
"//core/internal/kubernetes/clusternet:go_default_library",
+ "//core/internal/kubernetes/dns:go_default_library",
"//core/internal/kubernetes/nfproxy:go_default_library",
"//core/internal/kubernetes/pki:go_default_library",
"//core/internal/kubernetes/reconciler:go_default_library",
diff --git a/core/internal/kubernetes/dns/BUILD.bazel b/core/internal/kubernetes/dns/BUILD.bazel
new file mode 100644
index 0000000..173360d
--- /dev/null
+++ b/core/internal/kubernetes/dns/BUILD.bazel
@@ -0,0 +1,12 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+ name = "go_default_library",
+ srcs = ["coredns.go"],
+ importpath = "git.monogon.dev/source/nexantic.git/core/internal/kubernetes/dns",
+ visibility = ["//core:__subpackages__"],
+ deps = [
+ "//core/internal/common/supervisor:go_default_library",
+ "//core/pkg/fileargs:go_default_library",
+ ],
+)
diff --git a/core/internal/kubernetes/dns/coredns.go b/core/internal/kubernetes/dns/coredns.go
new file mode 100644
index 0000000..d020ec5
--- /dev/null
+++ b/core/internal/kubernetes/dns/coredns.go
@@ -0,0 +1,91 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package DNS provides a Kubernetes DNS server using CoreDNS.
+package dns
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "io"
+ "os/exec"
+ "text/template"
+
+ "git.monogon.dev/source/nexantic.git/core/internal/common/supervisor"
+ "git.monogon.dev/source/nexantic.git/core/pkg/fileargs"
+)
+
+type corefileSpec struct {
+ KubeconfigPath string
+ ClusterDomain string
+}
+
+var corefileTemplate = template.Must(template.New("corefile").Parse(`
+.:53 {
+ errors
+ health {
+ lameduck 5s
+ }
+ kubernetes {{.ClusterDomain}} in-addr.arpa ip6.arpa {
+ kubeconfig {{.KubeconfigPath}} default
+ pods insecure
+ fallthrough in-addr.arpa ip6.arpa
+ ttl 30
+ }
+ forward . /etc/resolv.conf
+ cache 30
+ loadbalance
+}
+`))
+
+type Service struct {
+ Output io.Writer
+ Kubeconfig []byte
+ ClusterDomain string
+}
+
+func (s *Service) Run(ctx context.Context) error {
+ args, err := fileargs.New()
+ if err != nil {
+ return fmt.Errorf("failed to create fileargs: %w", err)
+ }
+ defer args.Close()
+
+ var corefile bytes.Buffer
+ if err := corefileTemplate.Execute(&corefile, &corefileSpec{
+ KubeconfigPath: args.ArgPath("kubeconfig", s.Kubeconfig),
+ ClusterDomain: s.ClusterDomain,
+ }); err != nil {
+ return fmt.Errorf("failed to execute Corefile template: %w", err)
+ }
+
+ cmd := exec.CommandContext(ctx, "/kubernetes/bin/coredns",
+ args.FileOpt("-conf", "Corefile", corefile.Bytes()),
+ )
+
+ if args.Error() != nil {
+ return fmt.Errorf("failed to use fileargs: %w", err)
+ }
+
+ cmd.Stdout = s.Output
+ cmd.Stderr = s.Output
+
+ supervisor.Signal(ctx, supervisor.SignalHealthy)
+ err = cmd.Run()
+ fmt.Fprintf(s.Output, "coredns stopped: %v\n", err)
+ return err
+}
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")
}