This fix tries to fix the issue raised in 24391 about allowing
custom DNS settings to service definition.
This fix adds `DNSConfig` (`Nameservers`, `Options`, `Search`) to
service definition, as well as `--dns`, `--dns-opt`, and `dns-search`
to `service create`.
An integration test has been added to cover the changes in this fix.
This fix fixes 24391.
A PR in swarmkit will be created separately.
Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
| ... | ... |
@@ -7,6 +7,20 @@ import ( |
| 7 | 7 |
"github.com/docker/docker/api/types/mount" |
| 8 | 8 |
) |
| 9 | 9 |
|
| 10 |
+// DNSConfig specifies DNS related configurations in resolver configuration file (resolv.conf) |
|
| 11 |
+// Detailed documentation is available in: |
|
| 12 |
+// http://man7.org/linux/man-pages/man5/resolv.conf.5.html |
|
| 13 |
+// `nameserver`, `search`, `options` have been supported. |
|
| 14 |
+// TODO: `domain` is not supported yet. |
|
| 15 |
+type DNSConfig struct {
|
|
| 16 |
+ // Nameservers specifies the IP addresses of the name servers |
|
| 17 |
+ Nameservers []string `json:",omitempty"` |
|
| 18 |
+ // Search specifies the search list for host-name lookup |
|
| 19 |
+ Search []string `json:",omitempty"` |
|
| 20 |
+ // Options allows certain internal resolver variables to be modified |
|
| 21 |
+ Options []string `json:",omitempty"` |
|
| 22 |
+} |
|
| 23 |
+ |
|
| 10 | 24 |
// ContainerSpec represents the spec of a container. |
| 11 | 25 |
type ContainerSpec struct {
|
| 12 | 26 |
Image string `json:",omitempty"` |
| ... | ... |
@@ -22,4 +36,5 @@ type ContainerSpec struct {
|
| 22 | 22 |
Mounts []mount.Mount `json:",omitempty"` |
| 23 | 23 |
StopGracePeriod *time.Duration `json:",omitempty"` |
| 24 | 24 |
Healthcheck *container.HealthConfig `json:",omitempty"` |
| 25 |
+ DNSConfig *DNSConfig `json:",omitempty"` |
|
| 25 | 26 |
} |
| ... | ... |
@@ -41,6 +41,9 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 41 | 41 |
flags.StringSliceVar(&opts.networks, flagNetwork, []string{}, "Network attachments")
|
| 42 | 42 |
flags.VarP(&opts.endpoint.ports, flagPublish, "p", "Publish a port as a node port") |
| 43 | 43 |
flags.StringSliceVar(&opts.groups, flagGroup, []string{}, "Set one or more supplementary user groups for the container")
|
| 44 |
+ flags.Var(&opts.dns, flagDNS, "Set custom DNS servers") |
|
| 45 |
+ flags.Var(&opts.dnsOptions, flagDNSOptions, "Set DNS options") |
|
| 46 |
+ flags.Var(&opts.dnsSearch, flagDNSSearch, "Set custom DNS search domains") |
|
| 44 | 47 |
|
| 45 | 48 |
flags.SetInterspersed(false) |
| 46 | 49 |
return cmd |
| ... | ... |
@@ -296,6 +296,9 @@ type serviceOptions struct {
|
| 296 | 296 |
groups []string |
| 297 | 297 |
tty bool |
| 298 | 298 |
mounts opts.MountOpt |
| 299 |
+ dns opts.ListOpts |
|
| 300 |
+ dnsSearch opts.ListOpts |
|
| 301 |
+ dnsOptions opts.ListOpts |
|
| 299 | 302 |
|
| 300 | 303 |
resources resourceOptions |
| 301 | 304 |
stopGrace DurationOpt |
| ... | ... |
@@ -325,7 +328,10 @@ func newServiceOptions() *serviceOptions {
|
| 325 | 325 |
endpoint: endpointOptions{
|
| 326 | 326 |
ports: opts.NewListOpts(ValidatePort), |
| 327 | 327 |
}, |
| 328 |
- logDriver: newLogDriverOptions(), |
|
| 328 |
+ logDriver: newLogDriverOptions(), |
|
| 329 |
+ dns: opts.NewListOpts(opts.ValidateIPAddress), |
|
| 330 |
+ dnsOptions: opts.NewListOpts(nil), |
|
| 331 |
+ dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch), |
|
| 329 | 332 |
} |
| 330 | 333 |
} |
| 331 | 334 |
|
| ... | ... |
@@ -358,16 +364,21 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
|
| 358 | 358 |
}, |
| 359 | 359 |
TaskTemplate: swarm.TaskSpec{
|
| 360 | 360 |
ContainerSpec: swarm.ContainerSpec{
|
| 361 |
- Image: opts.image, |
|
| 362 |
- Args: opts.args, |
|
| 363 |
- Env: currentEnv, |
|
| 364 |
- Hostname: opts.hostname, |
|
| 365 |
- Labels: runconfigopts.ConvertKVStringsToMap(opts.containerLabels.GetAll()), |
|
| 366 |
- Dir: opts.workdir, |
|
| 367 |
- User: opts.user, |
|
| 368 |
- Groups: opts.groups, |
|
| 369 |
- TTY: opts.tty, |
|
| 370 |
- Mounts: opts.mounts.Value(), |
|
| 361 |
+ Image: opts.image, |
|
| 362 |
+ Args: opts.args, |
|
| 363 |
+ Env: currentEnv, |
|
| 364 |
+ Hostname: opts.hostname, |
|
| 365 |
+ Labels: runconfigopts.ConvertKVStringsToMap(opts.containerLabels.GetAll()), |
|
| 366 |
+ Dir: opts.workdir, |
|
| 367 |
+ User: opts.user, |
|
| 368 |
+ Groups: opts.groups, |
|
| 369 |
+ TTY: opts.tty, |
|
| 370 |
+ Mounts: opts.mounts.Value(), |
|
| 371 |
+ DNSConfig: &swarm.DNSConfig{
|
|
| 372 |
+ Nameservers: opts.dns.GetAll(), |
|
| 373 |
+ Search: opts.dnsSearch.GetAll(), |
|
| 374 |
+ Options: opts.dnsOptions.GetAll(), |
|
| 375 |
+ }, |
|
| 371 | 376 |
StopGracePeriod: opts.stopGrace.Value(), |
| 372 | 377 |
}, |
| 373 | 378 |
Networks: convertNetworks(opts.networks), |
| ... | ... |
@@ -463,6 +474,9 @@ const ( |
| 463 | 463 |
flagContainerLabel = "container-label" |
| 464 | 464 |
flagContainerLabelRemove = "container-label-rm" |
| 465 | 465 |
flagContainerLabelAdd = "container-label-add" |
| 466 |
+ flagDNS = "dns" |
|
| 467 |
+ flagDNSOptions = "dns-opt" |
|
| 468 |
+ flagDNSSearch = "dns-search" |
|
| 466 | 469 |
flagEndpointMode = "endpoint-mode" |
| 467 | 470 |
flagHostname = "hostname" |
| 468 | 471 |
flagEnv = "env" |
| ... | ... |
@@ -25,6 +25,14 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) types.ContainerSpec {
|
| 25 | 25 |
TTY: c.TTY, |
| 26 | 26 |
} |
| 27 | 27 |
|
| 28 |
+ if c.DNSConfig != nil {
|
|
| 29 |
+ containerSpec.DNSConfig = &types.DNSConfig{
|
|
| 30 |
+ Nameservers: c.DNSConfig.Nameservers, |
|
| 31 |
+ Search: c.DNSConfig.Search, |
|
| 32 |
+ Options: c.DNSConfig.Options, |
|
| 33 |
+ } |
|
| 34 |
+ } |
|
| 35 |
+ |
|
| 28 | 36 |
// Mounts |
| 29 | 37 |
for _, m := range c.Mounts {
|
| 30 | 38 |
mount := mounttypes.Mount{
|
| ... | ... |
@@ -81,6 +89,14 @@ func containerToGRPC(c types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
|
| 81 | 81 |
TTY: c.TTY, |
| 82 | 82 |
} |
| 83 | 83 |
|
| 84 |
+ if c.DNSConfig != nil {
|
|
| 85 |
+ containerSpec.DNSConfig = &swarmapi.ContainerSpec_DNSConfig{
|
|
| 86 |
+ Nameservers: c.DNSConfig.Nameservers, |
|
| 87 |
+ Search: c.DNSConfig.Search, |
|
| 88 |
+ Options: c.DNSConfig.Options, |
|
| 89 |
+ } |
|
| 90 |
+ } |
|
| 91 |
+ |
|
| 84 | 92 |
if c.StopGracePeriod != nil {
|
| 85 | 93 |
containerSpec.StopGracePeriod = ptypes.DurationProto(*c.StopGracePeriod) |
| 86 | 94 |
} |
| ... | ... |
@@ -327,6 +327,12 @@ func (c *containerConfig) hostConfig() *enginecontainer.HostConfig {
|
| 327 | 327 |
GroupAdd: c.spec().Groups, |
| 328 | 328 |
} |
| 329 | 329 |
|
| 330 |
+ if c.spec().DNSConfig != nil {
|
|
| 331 |
+ hc.DNS = c.spec().DNSConfig.Nameservers |
|
| 332 |
+ hc.DNSSearch = c.spec().DNSConfig.Search |
|
| 333 |
+ hc.DNSOptions = c.spec().DNSConfig.Options |
|
| 334 |
+ } |
|
| 335 |
+ |
|
| 330 | 336 |
if c.task.LogDriver != nil {
|
| 331 | 337 |
hc.LogConfig = enginecontainer.LogConfig{
|
| 332 | 338 |
Type: c.task.LogDriver.Name, |
| ... | ... |
@@ -23,6 +23,9 @@ Create a new service |
| 23 | 23 |
Options: |
| 24 | 24 |
--constraint value Placement constraints (default []) |
| 25 | 25 |
--container-label value Service container labels (default []) |
| 26 |
+ --dns list Set custom DNS servers (default []) |
|
| 27 |
+ --dns-opt list Set DNS options (default []) |
|
| 28 |
+ --dns-search list Set custom DNS search domains (default []) |
|
| 26 | 29 |
--endpoint-mode string Endpoint mode (vip or dnsrr) |
| 27 | 30 |
-e, --env value Set environment variables (default []) |
| 28 | 31 |
--env-file value Read in a file of environment variables (default []) |
| ... | ... |
@@ -789,3 +789,30 @@ func (s *DockerSwarmSuite) TestSwarmServiceTTYUpdate(c *check.C) {
|
| 789 | 789 |
c.Assert(err, checker.IsNil) |
| 790 | 790 |
c.Assert(strings.TrimSpace(out), checker.Equals, "true") |
| 791 | 791 |
} |
| 792 |
+ |
|
| 793 |
+func (s *DockerSwarmSuite) TestDNSConfig(c *check.C) {
|
|
| 794 |
+ d := s.AddDaemon(c, true, true) |
|
| 795 |
+ |
|
| 796 |
+ // Create a service |
|
| 797 |
+ name := "top" |
|
| 798 |
+ _, err := d.Cmd("service", "create", "--name", name, "--dns=1.2.3.4", "--dns-search=example.com", "--dns-opt=timeout:3", "busybox", "top")
|
|
| 799 |
+ c.Assert(err, checker.IsNil) |
|
| 800 |
+ |
|
| 801 |
+ // Make sure task has been deployed. |
|
| 802 |
+ waitAndAssert(c, defaultReconciliationTimeout, d.checkActiveContainerCount, checker.Equals, 1) |
|
| 803 |
+ |
|
| 804 |
+ // We need to get the container id. |
|
| 805 |
+ out, err := d.Cmd("ps", "-a", "-q", "--no-trunc")
|
|
| 806 |
+ c.Assert(err, checker.IsNil) |
|
| 807 |
+ id := strings.TrimSpace(out) |
|
| 808 |
+ |
|
| 809 |
+ // Compare against expected output. |
|
| 810 |
+ expectedOutput1 := "nameserver 1.2.3.4" |
|
| 811 |
+ expectedOutput2 := "search example.com" |
|
| 812 |
+ expectedOutput3 := "options timeout:3" |
|
| 813 |
+ out, err = d.Cmd("exec", id, "cat", "/etc/resolv.conf")
|
|
| 814 |
+ c.Assert(err, checker.IsNil) |
|
| 815 |
+ c.Assert(out, checker.Contains, expectedOutput1, check.Commentf("Expected '%s', but got %q", expectedOutput1, out))
|
|
| 816 |
+ c.Assert(out, checker.Contains, expectedOutput2, check.Commentf("Expected '%s', but got %q", expectedOutput2, out))
|
|
| 817 |
+ c.Assert(out, checker.Contains, expectedOutput3, check.Commentf("Expected '%s', but got %q", expectedOutput3, out))
|
|
| 818 |
+} |