Skip to content

Commit

Permalink
configurable dns search domains
Browse files Browse the repository at this point in the history
Add a --dns-search parameter and a DnsSearch
configuration field for specifying dns search
domains.

Docker-DCO-1.1-Signed-off-by: Daniel Norberg <[email protected]> (github: danielnorberg)
  • Loading branch information
danielnorberg authored and Daniel Norberg committed Mar 19, 2014
1 parent d3c5891 commit fbfac21
Show file tree
Hide file tree
Showing 13 changed files with 229 additions and 29 deletions.
4 changes: 4 additions & 0 deletions daemonconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type Config struct {
Root string
AutoRestart bool
Dns []string
DnsSearch []string
EnableIptables bool
EnableIpForward bool
DefaultIp net.IP
Expand Down Expand Up @@ -49,6 +50,9 @@ func ConfigFromJob(job *engine.Job) *Config {
if dns := job.GetenvList("Dns"); dns != nil {
config.Dns = dns
}
if dnsSearch := job.GetenvList("DnsSearch"); dnsSearch != nil {
config.DnsSearch = dnsSearch
}
if mtu := job.GetenvInt("Mtu"); mtu != 0 {
config.Mtu = mtu
} else {
Expand Down
3 changes: 3 additions & 0 deletions docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func main() {
flSocketGroup = flag.String([]string{"G", "-group"}, "docker", "Group to assign the unix socket specified by -H when running in daemon mode; use '' (the empty string) to disable setting of a group")
flEnableCors = flag.Bool([]string{"#api-enable-cors", "-api-enable-cors"}, false, "Enable CORS headers in the remote API")
flDns = opts.NewListOpts(opts.ValidateIp4Address)
flDnsSearch = opts.NewListOpts(opts.ValidateDomain)
flEnableIptables = flag.Bool([]string{"#iptables", "-iptables"}, true, "Enable Docker's addition of iptables rules")
flEnableIpForward = flag.Bool([]string{"#ip-forward", "-ip-forward"}, true, "Enable net.ipv4.ip_forward")
flDefaultIp = flag.String([]string{"#ip", "-ip"}, "0.0.0.0", "Default IP address to use when binding container ports")
Expand All @@ -45,6 +46,7 @@ func main() {
flMtu = flag.Int([]string{"#mtu", "-mtu"}, 0, "Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if no default route is available")
)
flag.Var(&flDns, []string{"#dns", "-dns"}, "Force docker to use specific DNS servers")
flag.Var(&flDnsSearch, []string{"-dns-search"}, "Force Docker to use specific DNS search domains")
flag.Var(&flHosts, []string{"H", "-host"}, "tcp://host:port, unix://path/to/socket, fd://* or fd://socketfd to use in daemon mode. Multiple sockets can be specified")

flag.Parse()
Expand Down Expand Up @@ -115,6 +117,7 @@ func main() {
job.Setenv("Root", realRoot)
job.SetenvBool("AutoRestart", *flAutoRestart)
job.SetenvList("Dns", flDns.GetAll())
job.SetenvList("DnsSearch", flDnsSearch.GetAll())
job.SetenvBool("EnableIptables", *flEnableIptables)
job.SetenvBool("EnableIpForward", *flEnableIpForward)
job.Setenv("BridgeIface", *bridgeName)
Expand Down
9 changes: 7 additions & 2 deletions docs/sources/reference/commandline/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Commands
--bip="": Use this CIDR notation address for the network bridge's IP, not compatible with -b
-d, --daemon=false: Enable daemon mode
--dns=[]: Force docker to use specific DNS servers
--dns-search=[]: Force Docker to use specific DNS search domains
-g, --graph="/var/lib/docker": Path to use as the root of the docker runtime
--icc=true: Enable inter-container communication
--ip="0.0.0.0": Default IP address to use when binding container ports
Expand All @@ -96,6 +97,8 @@ To force Docker to use devicemapper as the storage driver, use ``docker -d -s de

To set the DNS server for all Docker containers, use ``docker -d --dns 8.8.8.8``.

To set the a DNS search domain for all Docker containers, use ``docker -d --dns-search example.com``.

To run the daemon with debug output, use ``docker -d -D``.

To use lxc as the execution driver, use ``docker -d -e lxc``.
Expand Down Expand Up @@ -396,6 +399,7 @@ not overridden in the JSON hash will be merged in.
"VolumesFrom" : "",
"Cmd" : ["cat", "-e", "/etc/resolv.conf"],
"Dns" : ["8.8.8.8", "8.8.4.4"],
"DnsSearch" : ["example.com"],
"MemorySwap" : 0,
"AttachStdin" : false,
"AttachStderr" : false,
Expand Down Expand Up @@ -1131,6 +1135,7 @@ image is removed.
-t, --tty=false: Allocate a pseudo-tty
-u, --user="": Username or UID
--dns=[]: Set custom dns servers for the container
--dns-search=[]: Set custom DNS search domains for the container
-v, --volume=[]: Create a bind mount to a directory or file with: [host-path]:[container-path]:[rw|ro]. If a directory "container-path" is missing, then docker creates a new volume.
--volumes-from="": Mount all volumes from the given container(s)
--entrypoint="": Overwrite the default entrypoint set by the image
Expand Down Expand Up @@ -1288,15 +1293,15 @@ A complete example
$ sudo docker run -d --name static static-web-files sh
$ sudo docker run -d --expose=8098 --name riak riakserver
$ sudo docker run -d -m 100m -e DEVELOPMENT=1 -e BRANCH=example-code -v $(pwd):/app/bin:ro --name app appserver
$ sudo docker run -d -p 1443:443 --dns=dns.dev.org -v /var/log/httpd --volumes-from static --link riak --link app -h www.sven.dev.org --name web webserver
$ sudo docker run -d -p 1443:443 --dns=dns.dev.org --dns-search=dev.org -v /var/log/httpd --volumes-from static --link riak --link app -h www.sven.dev.org --name web webserver
$ sudo docker run -t -i --rm --volumes-from web -w /var/log/httpd busybox tail -f access.log
This example shows 5 containers that might be set up to test a web application change:

1. Start a pre-prepared volume image ``static-web-files`` (in the background) that has CSS, image and static HTML in it, (with a ``VOLUME`` instruction in the ``Dockerfile`` to allow the web server to use those files);
2. Start a pre-prepared ``riakserver`` image, give the container name ``riak`` and expose port ``8098`` to any containers that link to it;
3. Start the ``appserver`` image, restricting its memory usage to 100MB, setting two environment variables ``DEVELOPMENT`` and ``BRANCH`` and bind-mounting the current directory (``$(pwd)``) in the container in read-only mode as ``/app/bin``;
4. Start the ``webserver``, mapping port ``443`` in the container to port ``1443`` on the Docker server, setting the DNS server to ``dns.dev.org``, creating a volume to put the log files into (so we can access it from another container), then importing the files from the volume exposed by the ``static`` container, and linking to all exposed ports from ``riak`` and ``app``. Lastly, we set the hostname to ``web.sven.dev.org`` so its consistent with the pre-generated SSL certificate;
4. Start the ``webserver``, mapping port ``443`` in the container to port ``1443`` on the Docker server, setting the DNS server to ``dns.dev.org`` and DNS search domain to ``dev.org``, creating a volume to put the log files into (so we can access it from another container), then importing the files from the volume exposed by the ``static`` container, and linking to all exposed ports from ``riak`` and ``app``. Lastly, we set the hostname to ``web.sven.dev.org`` so its consistent with the pre-generated SSL certificate;
5. Finally, we create a container that runs ``tail -f access.log`` using the logs volume from the ``web`` container, setting the workdir to ``/var/log/httpd``. The ``--rm`` option means that when the container exits, the container's layer is removed.


Expand Down
13 changes: 13 additions & 0 deletions opts/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,16 @@ func ValidateIp4Address(val string) (string, error) {
}
return "", fmt.Errorf("%s is not an ip4 address", val)
}

func ValidateDomain(val string) (string, error) {
alpha := regexp.MustCompile(`[a-zA-Z]`)
if alpha.FindString(val) == "" {
return "", fmt.Errorf("%s is not a valid domain", val)
}
re := regexp.MustCompile(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`)
var ns = re.FindSubmatch([]byte(val))
if len(ns) > 0 {
return string(ns[1]), nil
}
return "", fmt.Errorf("%s is not a valid domain", val)
}
54 changes: 54 additions & 0 deletions opts/opts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,57 @@ func TestValidateIP4(t *testing.T) {
}

}

func TestValidateDomain(t *testing.T) {
valid := []string{
`a`,
`a.`,
`1.foo`,
`17.foo`,
`foo.bar`,
`foo.bar.baz`,
`foo.bar.`,
`foo.bar.baz`,
`foo1.bar2`,
`foo1.bar2.baz`,
`1foo.2bar.`,
`1foo.2bar.baz`,
`foo-1.bar-2`,
`foo-1.bar-2.baz`,
`foo-1.bar-2.`,
`foo-1.bar-2.baz`,
`1-foo.2-bar`,
`1-foo.2-bar.baz`,
`1-foo.2-bar.`,
`1-foo.2-bar.baz`,
}

invalid := []string{
``,
`.`,
`17`,
`17.`,
`.17`,
`17-.`,
`17-.foo`,
`.foo`,
`foo-.bar`,
`-foo.bar`,
`foo.bar-`,
`foo.bar-.baz`,
`foo.-bar`,
`foo.-bar.baz`,
}

for _, domain := range valid {
if ret, err := ValidateDomain(domain); err != nil || ret == "" {
t.Fatalf("ValidateDomain(`"+domain+"`) got %s %s", ret, err)
}
}

for _, domain := range invalid {
if ret, err := ValidateDomain(domain); err == nil || ret != "" {
t.Fatalf("ValidateDomain(`"+domain+"`) got %s %s", ret, err)
}
}
}
6 changes: 6 additions & 0 deletions runconfig/compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func Compare(a, b *Config) bool {
}
if len(a.Cmd) != len(b.Cmd) ||
len(a.Dns) != len(b.Dns) ||
len(a.DnsSearch) != len(b.DnsSearch) ||
len(a.Env) != len(b.Env) ||
len(a.PortSpecs) != len(b.PortSpecs) ||
len(a.ExposedPorts) != len(b.ExposedPorts) ||
Expand All @@ -38,6 +39,11 @@ func Compare(a, b *Config) bool {
return false
}
}
for i := 0; i < len(a.DnsSearch); i++ {
if a.DnsSearch[i] != b.DnsSearch[i] {
return false
}
}
for i := 0; i < len(a.Env); i++ {
if a.Env[i] != b.Env[i] {
return false
Expand Down
4 changes: 4 additions & 0 deletions runconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type Config struct {
Env []string
Cmd []string
Dns []string
DnsSearch []string
Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
Volumes map[string]struct{}
VolumesFrom string
Expand Down Expand Up @@ -68,6 +69,9 @@ func ContainerConfigFromJob(job *engine.Job) *Config {
if Dns := job.GetenvList("Dns"); Dns != nil {
config.Dns = Dns
}
if DnsSearch := job.GetenvList("DnsSearch"); DnsSearch != nil {
config.DnsSearch = DnsSearch
}
if Entrypoint := job.GetenvList("Entrypoint"); Entrypoint != nil {
config.Entrypoint = Entrypoint
}
Expand Down
16 changes: 16 additions & 0 deletions runconfig/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,27 +164,31 @@ func TestCompare(t *testing.T) {
volumes1["/test1"] = struct{}{}
config1 := Config{
Dns: []string{"1.1.1.1", "2.2.2.2"},
DnsSearch: []string{"foo", "bar"},
PortSpecs: []string{"1111:1111", "2222:2222"},
Env: []string{"VAR1=1", "VAR2=2"},
VolumesFrom: "11111111",
Volumes: volumes1,
}
config2 := Config{
Dns: []string{"0.0.0.0", "2.2.2.2"},
DnsSearch: []string{"foo", "bar"},
PortSpecs: []string{"1111:1111", "2222:2222"},
Env: []string{"VAR1=1", "VAR2=2"},
VolumesFrom: "11111111",
Volumes: volumes1,
}
config3 := Config{
Dns: []string{"1.1.1.1", "2.2.2.2"},
DnsSearch: []string{"foo", "bar"},
PortSpecs: []string{"0000:0000", "2222:2222"},
Env: []string{"VAR1=1", "VAR2=2"},
VolumesFrom: "11111111",
Volumes: volumes1,
}
config4 := Config{
Dns: []string{"1.1.1.1", "2.2.2.2"},
DnsSearch: []string{"foo", "bar"},
PortSpecs: []string{"0000:0000", "2222:2222"},
Env: []string{"VAR1=1", "VAR2=2"},
VolumesFrom: "22222222",
Expand All @@ -194,11 +198,20 @@ func TestCompare(t *testing.T) {
volumes2["/test2"] = struct{}{}
config5 := Config{
Dns: []string{"1.1.1.1", "2.2.2.2"},
DnsSearch: []string{"foo", "bar"},
PortSpecs: []string{"0000:0000", "2222:2222"},
Env: []string{"VAR1=1", "VAR2=2"},
VolumesFrom: "11111111",
Volumes: volumes2,
}
config6 := Config{
Dns: []string{"1.1.1.1", "2.2.2.2"},
DnsSearch: []string{"foos", "bars"},
PortSpecs: []string{"1111:1111", "2222:2222"},
Env: []string{"VAR1=1", "VAR2=2"},
VolumesFrom: "11111111",
Volumes: volumes1,
}
if Compare(&config1, &config2) {
t.Fatalf("Compare should return false, Dns are different")
}
Expand All @@ -211,6 +224,9 @@ func TestCompare(t *testing.T) {
if Compare(&config1, &config5) {
t.Fatalf("Compare should return false, Volumes are different")
}
if Compare(&config1, &config6) {
t.Fatalf("Compare should return false, DnsSearch are different")
}
if !Compare(&config1, &config1) {
t.Fatalf("Compare should return true")
}
Expand Down
6 changes: 6 additions & 0 deletions runconfig/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ func Merge(userConf, imageConf *Config) error {
//duplicates aren't an issue here
userConf.Dns = append(userConf.Dns, imageConf.Dns...)
}
if userConf.DnsSearch == nil || len(userConf.DnsSearch) == 0 {
userConf.DnsSearch = imageConf.DnsSearch
} else {
//duplicates aren't an issue here
userConf.DnsSearch = append(userConf.DnsSearch, imageConf.DnsSearch...)
}
if userConf.Entrypoint == nil || len(userConf.Entrypoint) == 0 {
userConf.Entrypoint = imageConf.Entrypoint
}
Expand Down
3 changes: 3 additions & 0 deletions runconfig/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
flPublish opts.ListOpts
flExpose opts.ListOpts
flDns opts.ListOpts
flDnsSearch = opts.NewListOpts(opts.ValidateDomain)
flVolumesFrom opts.ListOpts
flLxcOpts opts.ListOpts

Expand Down Expand Up @@ -73,6 +74,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
cmd.Var(&flPublish, []string{"p", "-publish"}, fmt.Sprintf("Publish a container's port to the host (format: %s) (use 'docker port' to see the actual mapping)", nat.PortSpecTemplateFormat))
cmd.Var(&flExpose, []string{"#expose", "-expose"}, "Expose a port from the container without publishing it to your host")
cmd.Var(&flDns, []string{"#dns", "-dns"}, "Set custom dns servers")
cmd.Var(&flDnsSearch, []string{"-dns-search"}, "Set custom dns search domains")
cmd.Var(&flVolumesFrom, []string{"#volumes-from", "-volumes-from"}, "Mount volumes from the specified container(s)")
cmd.Var(&flLxcOpts, []string{"#lxc-conf", "-lxc-conf"}, "Add custom lxc options --lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")

Expand Down Expand Up @@ -196,6 +198,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
Env: flEnv.GetAll(),
Cmd: runCmd,
Dns: flDns.GetAll(),
DnsSearch: flDnsSearch.GetAll(),
Image: image,
Volumes: flVolumes.GetMap(),
VolumesFrom: strings.Join(flVolumesFrom.GetAll(), ","),
Expand Down
17 changes: 14 additions & 3 deletions runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,13 +493,19 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe
}

// If custom dns exists, then create a resolv.conf for the container
if len(config.Dns) > 0 || len(runtime.config.Dns) > 0 {
var dns []string
if len(config.Dns) > 0 || len(runtime.config.Dns) > 0 || len(config.DnsSearch) > 0 || len(runtime.config.DnsSearch) > 0 {
dns := utils.GetNameservers(resolvConf)
dnsSearch := utils.GetSearchDomains(resolvConf)
if len(config.Dns) > 0 {
dns = config.Dns
} else {
} else if len(runtime.config.Dns) > 0 {
dns = runtime.config.Dns
}
if len(config.DnsSearch) > 0 {
dnsSearch = config.DnsSearch
} else if len(runtime.config.DnsSearch) > 0 {
dnsSearch = runtime.config.DnsSearch
}
container.ResolvConfPath = path.Join(container.root, "resolv.conf")
f, err := os.Create(container.ResolvConfPath)
if err != nil {
Expand All @@ -511,6 +517,11 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe
return nil, nil, err
}
}
if len(dnsSearch) > 0 {
if _, err := f.Write([]byte("search " + strings.Join(dnsSearch, " ") + "\n")); err != nil {
return nil, nil, err
}
}
} else {
container.ResolvConfPath = "/etc/resolv.conf"
}
Expand Down
Loading

0 comments on commit fbfac21

Please sign in to comment.