Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(kuma-dp) add coredns server to kuma-dp #1811

Merged
merged 5 commits into from
Apr 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 39 additions & 6 deletions app/kuma-dp/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"path/filepath"

kumadp_config "github.com/kumahq/kuma/app/kuma-dp/pkg/config"
"github.com/kumahq/kuma/app/kuma-dp/pkg/dataplane/dnsserver"
"github.com/kumahq/kuma/pkg/core/resources/model/rest"
"github.com/kumahq/kuma/pkg/core/runtime/component"

"github.com/pkg/errors"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -74,14 +76,22 @@ func newRunCmd(rootCtx *RootContext) *cobra.Command {
runLog.Info("picked a free port for Envoy Admin API to listen on", "port", cfg.Dataplane.AdminPort)
}

if cfg.DataplaneRuntime.ConfigDir == "" {
if cfg.DataplaneRuntime.ConfigDir == "" || cfg.DNS.ConfigDir == "" {
tmpDir, err = ioutil.TempDir("", "kuma-dp-")
if err != nil {
runLog.Error(err, "unable to create a temporary directory to store generated Envoy config at")
runLog.Error(err, "unable to create a temporary directory to store generated config sat")
return err
}
cfg.DataplaneRuntime.ConfigDir = tmpDir
runLog.Info("generated Envoy configuration will be stored in a temporary directory", "dir", tmpDir)

if cfg.DataplaneRuntime.ConfigDir == "" {
cfg.DataplaneRuntime.ConfigDir = tmpDir
}

if cfg.DNS.ConfigDir == "" {
cfg.DNS.ConfigDir = tmpDir
}

runLog.Info("generated configurations will be stored in a temporary directory", "dir", tmpDir)
}

if cfg.DataplaneRuntime.Token != "" {
Expand Down Expand Up @@ -116,7 +126,11 @@ func newRunCmd(rootCtx *RootContext) *cobra.Command {
}
}()
}

shouldQuit := setupQuitChannel()
components := []component.Component{
accesslogs.NewAccessLogServer(cfg.Dataplane),
}

opts := envoy.Opts{
Config: *cfg,
Expand All @@ -128,17 +142,34 @@ func newRunCmd(rootCtx *RootContext) *cobra.Command {
Quit: shouldQuit,
LogLevel: rootCtx.LogLevel,
}

if cfg.DNS.Enabled {
opts.DNSPort = cfg.DNS.EnvoyDNSPort
opts.EmptyDNSPort = cfg.DNS.CoreDNSEmptyPort

dnsOpts := &dnsserver.Opts{
Config: *cfg,
Stdout: cmd.OutOrStdout(),
Stderr: cmd.OutOrStderr(),
Quit: shouldQuit,
}

dnsServer, err := dnsserver.New(dnsOpts)
if err != nil {
return err
}

components = append(components, dnsServer)
}

dataplane, err := envoy.New(opts)
if err != nil {
return err
}
server := accesslogs.NewAccessLogServer(cfg.Dataplane)

if err := rootCtx.ComponentManager.Add(server, dataplane); err != nil {
components = append(components, dataplane)

if err := rootCtx.ComponentManager.Add(components...); err != nil {
return err
}

Expand Down Expand Up @@ -171,6 +202,8 @@ func newRunCmd(rootCtx *RootContext) *cobra.Command {
cmd.PersistentFlags().Uint32Var(&cfg.DNS.CoreDNSEmptyPort, "dns-coredns-empty-port", cfg.DNS.CoreDNSEmptyPort, "A port that always responds with empty NXDOMAIN respond. It is required to implement a fallback to a real DNS.")
cmd.PersistentFlags().StringVar(&cfg.DNS.CoreDNSBinaryPath, "dns-coredns-path", cfg.DNS.CoreDNSBinaryPath, "A path to CoreDNS binary.")
cmd.PersistentFlags().StringVar(&cfg.DNS.CoreDNSConfigTemplatePath, "dns-coredns-config-template-path", cfg.DNS.CoreDNSConfigTemplatePath, "A path to a CoreDNS config template.")
cmd.PersistentFlags().StringVar(&cfg.DNS.ConfigDir, "dns-server-config-dir", cfg.DNS.ConfigDir, "Directory in which DNS Server config will be generated")
cmd.PersistentFlags().Uint32Var(&cfg.DNS.PrometheusPort, "dns-prometheus-port", cfg.DNS.PrometheusPort, "A port for exposing Prometheus stats")
return cmd
}

Expand Down
26 changes: 26 additions & 0 deletions app/kuma-dp/pkg/dataplane/dnsserver/config_file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package dnsserver

import (
"io/ioutil"
"os"
"path/filepath"

"github.com/pkg/errors"

kuma_dp "github.com/kumahq/kuma/pkg/config/app/kuma-dp"
)

func GenerateConfigFile(cfg kuma_dp.DNS, config []byte) (string, error) {
configFile := filepath.Join(cfg.ConfigDir, "Corefile")
if err := writeFile(configFile, config, 0600); err != nil {
return "", errors.Wrap(err, "failed to persist Envoy bootstrap config on disk")
}
return configFile, nil
}

func writeFile(filename string, data []byte, perm os.FileMode) error {
if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil {
return err
}
return ioutil.WriteFile(filename, data, perm)
}
60 changes: 60 additions & 0 deletions app/kuma-dp/pkg/dataplane/dnsserver/config_file_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package dnsserver

import (
"io/ioutil"
"os"
"path/filepath"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

kuma_dp "github.com/kumahq/kuma/pkg/config/app/kuma-dp"
)

var _ = Describe("Config File", func() {
Describe("GenerateConfigFile(..)", func() {

var configDir string

BeforeEach(func() {
var err error
configDir, err = ioutil.TempDir("", "")
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
if configDir != "" {
// when
err := os.RemoveAll(configDir)
// then
Expect(err).ToNot(HaveOccurred())
}
})

It("should create DNS Server config file on disk", func() {
// given
config := `. {
errors
}`
// and
dnsConfig := kuma_dp.DNS{
ConfigDir: configDir,
}

// when
filename, err := GenerateConfigFile(dnsConfig, []byte(config))
// then
Expect(err).ToNot(HaveOccurred())
// and
Expect(filename).To(Equal(filepath.Join(configDir, "Corefile")))

// when
actual, err := ioutil.ReadFile(filename)
// then
Expect(err).ToNot(HaveOccurred())
// and
Expect(actual).To(Equal([]byte(`. {
errors
}`)))
})
})
})
184 changes: 184 additions & 0 deletions app/kuma-dp/pkg/dataplane/dnsserver/dnsserver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package dnsserver

import (
"bytes"
"context"
"io"
"os"
"os/exec"
"path/filepath"
"text/template"

"github.com/pkg/errors"

kuma_dp "github.com/kumahq/kuma/pkg/config/app/kuma-dp"
"github.com/kumahq/kuma/pkg/core"
)

var (
runLog = core.Log.WithName("kuma-dp").WithName("run").WithName("dns-server")
)

type DNSServer struct {
opts *Opts
}

type Opts struct {
Config kuma_dp.Config
Stdout io.Writer
Stderr io.Writer
Quit chan struct{}
}

const DefaultCoreFileTemplate = `.:{{ .CoreDNSPort }} {
forward . 127.0.0.1:{{ .EnvoyDNSPort }}
alternate NXDOMAIN,SERVFAIL,REFUSED . /etc/resolv.conf
prometheus localhost:{{ .PrometheusPort }}
errors
}

.:{{ .CoreDNSEmptyPort }} {
template ANY ANY . {
rcode NXDOMAIN
}
}`

func getSelfPath() (string, error) {
ex, err := os.Executable()
if err != nil {
return "", err
}

return filepath.Dir(ex), nil
}

func lookupBinaryPath(candidatePaths []string) (string, error) {
for _, candidatePath := range candidatePaths {
path, err := exec.LookPath(candidatePath)
if err == nil {
return path, nil
}
}

return "", errors.Errorf("could not find binary in any of the following paths: %v", candidatePaths)
}

func lookupDNSServerPath(configuredPath string) (string, error) {
selfPath, err := getSelfPath()
if err != nil {
return "", err
}

cwd, err := os.Getwd()
if err != nil {
return "", err
}

path, err := lookupBinaryPath([]string{
configuredPath,
selfPath + "/coredns",
cwd + "/coredns",
})
if err != nil {
return "", err
}

return path, nil
}

func New(opts *Opts) (*DNSServer, error) {
if _, err := lookupDNSServerPath(opts.Config.DNS.CoreDNSBinaryPath); err != nil {
runLog.Error(err, "could not find the DNS Server executable in your path")
return nil, err
}

return &DNSServer{opts: opts}, nil
}

func (s *DNSServer) NeedLeaderElection() bool {
return false
}

func (s *DNSServer) Start(stop <-chan struct{}) error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

dnsConfig := s.opts.Config.DNS

var tmpl *template.Template

if dnsConfig.CoreDNSConfigTemplatePath != "" {
t, err := template.ParseFiles(dnsConfig.CoreDNSConfigTemplatePath)
if err != nil {
return err
}

tmpl = t
} else {
t, err := template.New("Corefile").Parse(DefaultCoreFileTemplate)
if err != nil {
return err
}

tmpl = t
}

bs := bytes.NewBuffer([]byte{})

if err := tmpl.Execute(bs, dnsConfig); err != nil {
return err
}

configFile, err := GenerateConfigFile(dnsConfig, bs.Bytes())
if err != nil {
return err
}
runLog.Info("configuration saved to a file", "file", configFile)

binaryPathConfig := dnsConfig.CoreDNSBinaryPath
resolvedPath, err := lookupDNSServerPath(binaryPathConfig)
if err != nil {
return err
}

args := []string{
"-conf", configFile,
"-q",
}

command := exec.CommandContext(ctx, resolvedPath, args...)
command.Stdout = s.opts.Stdout
command.Stderr = s.opts.Stderr

runLog.Info("starting DNS Server (coredns)", "args", args)

if err := command.Start(); err != nil {
runLog.Error(err, "the DNS Server executable was found at "+resolvedPath+" but an error occurred when executing it")
return err
}

done := make(chan error, 1)

go func() {
done <- command.Wait()
}()

select {
case <-stop:
runLog.Info("stopping DNS Server")
cancel()
return nil
case err := <-done:
if err != nil {
runLog.Error(err, "DNS Server terminated with an error")
} else {
runLog.Info("DNS Server terminated successfully")
}

if s.opts.Quit != nil {
close(s.opts.Quit)
}

return err
}
}
13 changes: 13 additions & 0 deletions app/kuma-dp/pkg/dataplane/dnsserver/dnsserver_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package dnsserver

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestEnvoy(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "DNS Server Suite")
}
Loading