Skip to content

Commit

Permalink
Adds dnsmasq-nanny, a wrapper for dnsmasq that configures dnsmasq bas…
Browse files Browse the repository at this point in the history
…ed on a ConfigMap
  • Loading branch information
bowei committed Feb 17, 2017
1 parent 63fa64a commit c1b986c
Show file tree
Hide file tree
Showing 8 changed files with 424 additions and 68 deletions.
14 changes: 10 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,17 @@ PKG := k8s.io/dns

# List of binaries to build. You must have a matching Dockerfile.BINARY
# for each BINARY.
BINARIES := e2e ginkgo sidecar-e2e
BINARIES := \
e2e \
ginkgo \
sidecar-e2e

# List of binaries to build that are containerized and pushed.
CONTAINER_BINARIES := kube-dns sidecar
CONTAINER_BINARIES := \
dnsmasq-nanny \
kube-dns \
sidecar

# List of images to build (contained in images/)
IMAGES := dnsmasq
# Registry to push to.
Expand All @@ -37,8 +45,6 @@ CONTAINER_PREFIX ?= k8s-dns

# This version-strategy uses git tags to set the version string
VERSION ?= $(shell git describe --tags --always --dirty)
# This version-strategy uses a manual value to set the version string
#VERSION := 1.2.3

# Set to 1 to print more verbose output from the build.
VERBOSE ?= 0
Expand Down
81 changes: 81 additions & 0 deletions cmd/dnsmasq-nanny/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
Copyright 2017 The Kubernetes Authors.
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 main

import (
"flag"
"fmt"
"os"
"time"

"github.com/golang/glog"

"k8s.io/dns/pkg/dns/config"
"k8s.io/dns/pkg/dnsmasq"
)

var (
opts = struct {
dnsmasq.RunNannyOpts
configDir string
syncInterval time.Duration
}{
RunNannyOpts: dnsmasq.RunNannyOpts{
DnsmasqExec: "/usr/sbin/dnsmasq",
RestartOnChange: false,
},
configDir: "/etc/k8s/dns/dnsmasq-nanny",
syncInterval: 10 * time.Second,
}

currentConfig *config.Config
)

func parseFlags() {
opts.DnsmasqArgs = dnsmasq.ExtractDnsmasqArgs(&os.Args)

flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
fmt.Fprintf(os.Stderr, `
Manages the dnsmasq daemon, handles configuration given by the ConfigMap.
Any arguments given after "--" will be passed directly to dnsmasq itself.
`)
flag.PrintDefaults()
}

flag.StringVar(&opts.DnsmasqExec, "dnsmasqExec", opts.DnsmasqExec,
"location of the dnsmasq executable")
flag.BoolVar(&opts.RestartOnChange, "restartDnsmasq",
opts.RestartOnChange,
"if true, restart dnsmasq when the configuration changes")
flag.StringVar(&opts.configDir, "configDir", opts.configDir,
"location of the configuration")
flag.DurationVar(&opts.syncInterval, "syncInterval",
opts.syncInterval,
"interval to check for configuration updates")
flag.Parse()
}

func main() {
parseFlags()
glog.V(0).Infof("opts: %v", opts)

sync := config.NewFileSync(opts.configDir, opts.syncInterval)

dnsmasq.RunNanny(sync, opts.RunNannyOpts)
}
79 changes: 62 additions & 17 deletions pkg/dnsmasq/nanny.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ func ExtractDnsmasqArgs(cmdlineArgs *[]string) []string {
}

// Configure the nanny. This must be called before Start().
func (d *Nanny) Configure(args []string, config *config.Config) {
d.args = args
func (n *Nanny) Configure(args []string, config *config.Config) {
n.args = args

munge := func(s string) string {
return strings.Replace(s, ":", "#", -1)
Expand All @@ -63,34 +63,34 @@ func (d *Nanny) Configure(args []string, config *config.Config) {
for _, server := range serverList {
// dnsmasq port separator is '#' for some reason.
server = munge(server)
d.args = append(
d.args, "--server", fmt.Sprintf("/%v/%v", domain, server))
n.args = append(
n.args, "--server", fmt.Sprintf("/%v/%v", domain, server))
}
}

for _, server := range config.UpstreamNameservers {
// dnsmasq port separator is '#' for some reason.
server = munge(server)
d.args = append(d.args, "--server", server)
n.args = append(n.args, "--server", server)
}
}

// Start the nanny.
func (d *Nanny) Start() error {
glog.V(0).Infof("Starting dnsmasq %v", d.args)
func (n *Nanny) Start() error {
glog.V(0).Infof("Starting dnsmasq %v", n.args)

d.cmd = exec.Command(d.Exec, d.args...)
stderrReader, err := d.cmd.StderrPipe()
n.cmd = exec.Command(n.Exec, n.args...)
stderrReader, err := n.cmd.StderrPipe()
if err != nil {
return err
}

stdoutReader, err := d.cmd.StdoutPipe()
stdoutReader, err := n.cmd.StdoutPipe()
if err != nil {
return err
}

if err := d.cmd.Start(); err != nil {
if err := n.cmd.Start(); err != nil {
return err
}

Expand All @@ -116,27 +116,72 @@ func (d *Nanny) Start() error {
go logToGlog("stderr", stderrReader)
go logToGlog("stdout", stdoutReader)

d.ExitChannel = make(chan error)
n.ExitChannel = make(chan error)
go func() {
d.ExitChannel <- d.cmd.Wait()
n.ExitChannel <- n.cmd.Wait()
}()

return nil
}

// Kill the running Nanny.
func (d *Nanny) Kill() error {
func (n *Nanny) Kill() error {
glog.V(0).Infof("Killing dnsmasq")
if d.cmd == nil {
if n.cmd == nil {
return fmt.Errorf("Process is not running")
}

if err := d.cmd.Process.Kill(); err != nil {
if err := n.cmd.Process.Kill(); err != nil {
glog.Errorf("Error killing dnsmasq: %v", err)
return err
}

d.cmd = nil
n.cmd = nil

return nil
}

// RunNannyOpts for running the nanny.
type RunNannyOpts struct {
// Location of the dnsmasq executable.
DnsmasqExec string
// Extra arguments to dnsmasq.
DnsmasqArgs []string
// Restart the daemon on ConfigMap changes.
RestartOnChange bool
}

// RunNanny runs the nanny and handles configuration updates.
func RunNanny(sync config.Sync, opts RunNannyOpts) {
currentConfig, err := sync.Once()
if err != nil {
glog.Fatalf("Error getting initial config: %v", err)
}

nanny := &Nanny{Exec: opts.DnsmasqExec}
nanny.Configure(opts.DnsmasqArgs, currentConfig)
if err := nanny.Start(); err != nil {
glog.Fatalf("Could not start dnsmasq with initial configuration: %v", err)
}

configChan := sync.Periodic()

for {
select {
case status := <-nanny.ExitChannel:
glog.Fatalf("dnsmasq exited: %v", status)
break
case currentConfig = <-configChan:
if opts.RestartOnChange {
glog.V(0).Infof("Restarting dnsmasq with new configuration")
nanny.Kill()
nanny = &Nanny{Exec: opts.DnsmasqExec}
nanny.Configure(opts.DnsmasqArgs, currentConfig)
nanny.Start()
} else {
glog.V(2).Infof("Not restarting dnsmasq (--restartDnsmasq=false)")
}
break
}
}
}
84 changes: 41 additions & 43 deletions pkg/dnsmasq/nanny_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,50 +57,48 @@ func TestExtractDnsmasqArgs(t *testing.T) {
func TestNannyConfig(t *testing.T) {
gomega.RegisterTestingT(t)

nanny := &Nanny{Exec: "dnsmasq"}
nanny.Configure(
[]string{"--no-resolv"},
&config.Config{})

gomega.Expect(nanny.args).To(
gomega.Equal([]string{"--no-resolv"}))

nanny = &Nanny{Exec: "dnsmasq"}
nanny.Configure(
[]string{"--no-resolv"},
&config.Config{
StubDomains: map[string][]string{
"acme.local": []string{"1.1.1.1"},
"widget.local": []string{"2.2.2.2:10053", "3.3.3.3"},
for _, testCase := range []struct {
c *config.Config
e []string
sort bool
}{
{c: &config.Config{}, e: []string{"--no-resolv"}},
{
c: &config.Config{
StubDomains: map[string][]string{
"acme.local": []string{"1.1.1.1"},
"widget.local": []string{"2.2.2.2:10053", "3.3.3.3"},
}},
e: []string{
"--no-resolv",
"--server",
"--server",
"--server",
"/acme.local/1.1.1.1",
"/widget.local/2.2.2.2#10053",
"/widget.local/3.3.3.3",
},
})

sort.Sort(sort.StringSlice(nanny.args))
gomega.Expect(nanny.args).To(
gomega.Equal([]string{
"--no-resolv",
"--server",
"--server",
"--server",
"/acme.local/1.1.1.1",
"/widget.local/2.2.2.2#10053",
"/widget.local/3.3.3.3",
}))

nanny = &Nanny{Exec: "dnsmasq"}
nanny.Configure(
[]string{"--no-resolv"},
&config.Config{
UpstreamNameservers: []string{"2.2.2.2:10053", "3.3.3.3"},
})
gomega.Expect(nanny.args).To(
gomega.Equal([]string{
"--no-resolv",
"--server",
"2.2.2.2#10053",
"--server",
"3.3.3.3",
}))
sort: true,
},
{
c: &config.Config{
UpstreamNameservers: []string{"2.2.2.2:10053", "3.3.3.3"}},
e: []string{
"--no-resolv",
"--server",
"2.2.2.2#10053",
"--server",
"3.3.3.3",
},
},
} {
nanny := &Nanny{Exec: "dnsmasq"}
nanny.Configure([]string{"--no-resolv"}, testCase.c)
if testCase.sort {
sort.Sort(sort.StringSlice(nanny.args))
}
gomega.Expect(nanny.args).To(gomega.Equal(testCase.e))
}
}

func TestNannyLifecycle(t *testing.T) {
Expand Down
Loading

0 comments on commit c1b986c

Please sign in to comment.