зеркало из https://github.com/microsoft/docker.git
Merge pull request #27567 from yongtang/24391-dns-setting
Add custom DNS settings to service definition
This commit is contained in:
Коммит
462e57f05e
|
@ -7,6 +7,20 @@ import (
|
|||
"github.com/docker/docker/api/types/mount"
|
||||
)
|
||||
|
||||
// DNSConfig specifies DNS related configurations in resolver configuration file (resolv.conf)
|
||||
// Detailed documentation is available in:
|
||||
// http://man7.org/linux/man-pages/man5/resolv.conf.5.html
|
||||
// `nameserver`, `search`, `options` have been supported.
|
||||
// TODO: `domain` is not supported yet.
|
||||
type DNSConfig struct {
|
||||
// Nameservers specifies the IP addresses of the name servers
|
||||
Nameservers []string `json:",omitempty"`
|
||||
// Search specifies the search list for host-name lookup
|
||||
Search []string `json:",omitempty"`
|
||||
// Options allows certain internal resolver variables to be modified
|
||||
Options []string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// ContainerSpec represents the spec of a container.
|
||||
type ContainerSpec struct {
|
||||
Image string `json:",omitempty"`
|
||||
|
@ -22,4 +36,5 @@ type ContainerSpec struct {
|
|||
Mounts []mount.Mount `json:",omitempty"`
|
||||
StopGracePeriod *time.Duration `json:",omitempty"`
|
||||
Healthcheck *container.HealthConfig `json:",omitempty"`
|
||||
DNSConfig *DNSConfig `json:",omitempty"`
|
||||
}
|
||||
|
|
|
@ -41,6 +41,9 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
flags.StringSliceVar(&opts.networks, flagNetwork, []string{}, "Network attachments")
|
||||
flags.VarP(&opts.endpoint.ports, flagPublish, "p", "Publish a port as a node port")
|
||||
flags.StringSliceVar(&opts.groups, flagGroup, []string{}, "Set one or more supplementary user groups for the container")
|
||||
flags.Var(&opts.dns, flagDNS, "Set custom DNS servers")
|
||||
flags.Var(&opts.dnsOptions, flagDNSOptions, "Set DNS options")
|
||||
flags.Var(&opts.dnsSearch, flagDNSSearch, "Set custom DNS search domains")
|
||||
|
||||
flags.SetInterspersed(false)
|
||||
return cmd
|
||||
|
|
|
@ -296,6 +296,9 @@ type serviceOptions struct {
|
|||
groups []string
|
||||
tty bool
|
||||
mounts opts.MountOpt
|
||||
dns opts.ListOpts
|
||||
dnsSearch opts.ListOpts
|
||||
dnsOptions opts.ListOpts
|
||||
|
||||
resources resourceOptions
|
||||
stopGrace DurationOpt
|
||||
|
@ -325,7 +328,10 @@ func newServiceOptions() *serviceOptions {
|
|||
endpoint: endpointOptions{
|
||||
ports: opts.NewListOpts(ValidatePort),
|
||||
},
|
||||
logDriver: newLogDriverOptions(),
|
||||
logDriver: newLogDriverOptions(),
|
||||
dns: opts.NewListOpts(opts.ValidateIPAddress),
|
||||
dnsOptions: opts.NewListOpts(nil),
|
||||
dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -358,16 +364,21 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
|
|||
},
|
||||
TaskTemplate: swarm.TaskSpec{
|
||||
ContainerSpec: swarm.ContainerSpec{
|
||||
Image: opts.image,
|
||||
Args: opts.args,
|
||||
Env: currentEnv,
|
||||
Hostname: opts.hostname,
|
||||
Labels: runconfigopts.ConvertKVStringsToMap(opts.containerLabels.GetAll()),
|
||||
Dir: opts.workdir,
|
||||
User: opts.user,
|
||||
Groups: opts.groups,
|
||||
TTY: opts.tty,
|
||||
Mounts: opts.mounts.Value(),
|
||||
Image: opts.image,
|
||||
Args: opts.args,
|
||||
Env: currentEnv,
|
||||
Hostname: opts.hostname,
|
||||
Labels: runconfigopts.ConvertKVStringsToMap(opts.containerLabels.GetAll()),
|
||||
Dir: opts.workdir,
|
||||
User: opts.user,
|
||||
Groups: opts.groups,
|
||||
TTY: opts.tty,
|
||||
Mounts: opts.mounts.Value(),
|
||||
DNSConfig: &swarm.DNSConfig{
|
||||
Nameservers: opts.dns.GetAll(),
|
||||
Search: opts.dnsSearch.GetAll(),
|
||||
Options: opts.dnsOptions.GetAll(),
|
||||
},
|
||||
StopGracePeriod: opts.stopGrace.Value(),
|
||||
},
|
||||
Networks: convertNetworks(opts.networks),
|
||||
|
@ -463,6 +474,15 @@ const (
|
|||
flagContainerLabel = "container-label"
|
||||
flagContainerLabelRemove = "container-label-rm"
|
||||
flagContainerLabelAdd = "container-label-add"
|
||||
flagDNS = "dns"
|
||||
flagDNSRemove = "dns-rm"
|
||||
flagDNSAdd = "dns-add"
|
||||
flagDNSOptions = "dns-options"
|
||||
flagDNSOptionsRemove = "dns-options-rm"
|
||||
flagDNSOptionsAdd = "dns-options-add"
|
||||
flagDNSSearch = "dns-search"
|
||||
flagDNSSearchRemove = "dns-search-rm"
|
||||
flagDNSSearchAdd = "dns-search-add"
|
||||
flagEndpointMode = "endpoint-mode"
|
||||
flagHostname = "hostname"
|
||||
flagEnv = "env"
|
||||
|
|
|
@ -48,6 +48,9 @@ func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
flags.Var(newListOptsVar(), flagMountRemove, "Remove a mount by its target path")
|
||||
flags.Var(newListOptsVar(), flagPublishRemove, "Remove a published port by its target port")
|
||||
flags.Var(newListOptsVar(), flagConstraintRemove, "Remove a constraint")
|
||||
flags.Var(newListOptsVar(), flagDNSRemove, "Remove custom DNS servers")
|
||||
flags.Var(newListOptsVar(), flagDNSOptionsRemove, "Remove DNS options")
|
||||
flags.Var(newListOptsVar(), flagDNSSearchRemove, "Remove DNS search domains")
|
||||
flags.Var(&opts.labels, flagLabelAdd, "Add or update a service label")
|
||||
flags.Var(&opts.containerLabels, flagContainerLabelAdd, "Add or update a container label")
|
||||
flags.Var(&opts.env, flagEnvAdd, "Add or update an environment variable")
|
||||
|
@ -55,6 +58,10 @@ func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
flags.StringSliceVar(&opts.constraints, flagConstraintAdd, []string{}, "Add or update a placement constraint")
|
||||
flags.Var(&opts.endpoint.ports, flagPublishAdd, "Add or update a published port")
|
||||
flags.StringSliceVar(&opts.groups, flagGroupAdd, []string{}, "Add an additional supplementary user group to the container")
|
||||
flags.Var(&opts.dns, flagDNSAdd, "Add or update custom DNS servers")
|
||||
flags.Var(&opts.dnsOptions, flagDNSOptionsAdd, "Add or update DNS options")
|
||||
flags.Var(&opts.dnsSearch, flagDNSSearchAdd, "Add or update custom DNS search domains")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
@ -257,6 +264,15 @@ func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
|
|||
}
|
||||
}
|
||||
|
||||
if anyChanged(flags, flagDNSAdd, flagDNSRemove, flagDNSOptionsAdd, flagDNSOptionsRemove, flagDNSSearchAdd, flagDNSSearchRemove) {
|
||||
if cspec.DNSConfig == nil {
|
||||
cspec.DNSConfig = &swarm.DNSConfig{}
|
||||
}
|
||||
if err := updateDNSConfig(flags, &cspec.DNSConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := updateLogDriver(flags, &spec.TaskTemplate); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -484,6 +500,71 @@ func updateGroups(flags *pflag.FlagSet, groups *[]string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func removeDuplicates(entries []string) []string {
|
||||
hit := map[string]bool{}
|
||||
newEntries := []string{}
|
||||
for _, v := range entries {
|
||||
if !hit[v] {
|
||||
newEntries = append(newEntries, v)
|
||||
hit[v] = true
|
||||
}
|
||||
}
|
||||
return newEntries
|
||||
}
|
||||
|
||||
func updateDNSConfig(flags *pflag.FlagSet, config **swarm.DNSConfig) error {
|
||||
newConfig := &swarm.DNSConfig{}
|
||||
|
||||
nameservers := (*config).Nameservers
|
||||
if flags.Changed(flagDNSAdd) {
|
||||
values := flags.Lookup(flagDNSAdd).Value.(*opts.ListOpts).GetAll()
|
||||
nameservers = append(nameservers, values...)
|
||||
}
|
||||
nameservers = removeDuplicates(nameservers)
|
||||
toRemove := buildToRemoveSet(flags, flagDNSRemove)
|
||||
for _, nameserver := range nameservers {
|
||||
if _, exists := toRemove[nameserver]; !exists {
|
||||
newConfig.Nameservers = append(newConfig.Nameservers, nameserver)
|
||||
|
||||
}
|
||||
}
|
||||
// Sort so that result is predictable.
|
||||
sort.Strings(newConfig.Nameservers)
|
||||
|
||||
search := (*config).Search
|
||||
if flags.Changed(flagDNSSearchAdd) {
|
||||
values := flags.Lookup(flagDNSSearchAdd).Value.(*opts.ListOpts).GetAll()
|
||||
search = append(search, values...)
|
||||
}
|
||||
search = removeDuplicates(search)
|
||||
toRemove = buildToRemoveSet(flags, flagDNSSearchRemove)
|
||||
for _, entry := range search {
|
||||
if _, exists := toRemove[entry]; !exists {
|
||||
newConfig.Search = append(newConfig.Search, entry)
|
||||
}
|
||||
}
|
||||
// Sort so that result is predictable.
|
||||
sort.Strings(newConfig.Search)
|
||||
|
||||
options := (*config).Options
|
||||
if flags.Changed(flagDNSOptionsAdd) {
|
||||
values := flags.Lookup(flagDNSOptionsAdd).Value.(*opts.ListOpts).GetAll()
|
||||
options = append(options, values...)
|
||||
}
|
||||
options = removeDuplicates(options)
|
||||
toRemove = buildToRemoveSet(flags, flagDNSOptionsRemove)
|
||||
for _, option := range options {
|
||||
if _, exists := toRemove[option]; !exists {
|
||||
newConfig.Options = append(newConfig.Options, option)
|
||||
}
|
||||
}
|
||||
// Sort so that result is predictable.
|
||||
sort.Strings(newConfig.Options)
|
||||
|
||||
*config = newConfig
|
||||
return nil
|
||||
}
|
||||
|
||||
type byPortConfig []swarm.PortConfig
|
||||
|
||||
func (r byPortConfig) Len() int { return len(r) }
|
||||
|
|
|
@ -120,6 +120,52 @@ func TestUpdateGroups(t *testing.T) {
|
|||
assert.Equal(t, groups[2], "wheel")
|
||||
}
|
||||
|
||||
func TestUpdateDNSConfig(t *testing.T) {
|
||||
flags := newUpdateCommand(nil).Flags()
|
||||
|
||||
// IPv4, with duplicates
|
||||
flags.Set("dns-add", "1.1.1.1")
|
||||
flags.Set("dns-add", "1.1.1.1")
|
||||
flags.Set("dns-add", "2.2.2.2")
|
||||
flags.Set("dns-rm", "3.3.3.3")
|
||||
flags.Set("dns-rm", "2.2.2.2")
|
||||
// IPv6
|
||||
flags.Set("dns-add", "2001:db8:abc8::1")
|
||||
// Invalid dns record
|
||||
assert.Error(t, flags.Set("dns-add", "x.y.z.w"), "x.y.z.w is not an ip address")
|
||||
|
||||
// domains with duplicates
|
||||
flags.Set("dns-search-add", "example.com")
|
||||
flags.Set("dns-search-add", "example.com")
|
||||
flags.Set("dns-search-add", "example.org")
|
||||
flags.Set("dns-search-rm", "example.org")
|
||||
// Invalid dns search domain
|
||||
assert.Error(t, flags.Set("dns-search-add", "example$com"), "example$com is not a valid domain")
|
||||
|
||||
flags.Set("dns-options-add", "ndots:9")
|
||||
flags.Set("dns-options-rm", "timeout:3")
|
||||
|
||||
config := &swarm.DNSConfig{
|
||||
Nameservers: []string{"3.3.3.3", "5.5.5.5"},
|
||||
Search: []string{"localdomain"},
|
||||
Options: []string{"timeout:3"},
|
||||
}
|
||||
|
||||
updateDNSConfig(flags, &config)
|
||||
|
||||
assert.Equal(t, len(config.Nameservers), 3)
|
||||
assert.Equal(t, config.Nameservers[0], "1.1.1.1")
|
||||
assert.Equal(t, config.Nameservers[1], "2001:db8:abc8::1")
|
||||
assert.Equal(t, config.Nameservers[2], "5.5.5.5")
|
||||
|
||||
assert.Equal(t, len(config.Search), 2)
|
||||
assert.Equal(t, config.Search[0], "example.com")
|
||||
assert.Equal(t, config.Search[1], "localdomain")
|
||||
|
||||
assert.Equal(t, len(config.Options), 1)
|
||||
assert.Equal(t, config.Options[0], "ndots:9")
|
||||
}
|
||||
|
||||
func TestUpdateMounts(t *testing.T) {
|
||||
flags := newUpdateCommand(nil).Flags()
|
||||
flags.Set("mount-add", "type=volume,source=vol2,target=/toadd")
|
||||
|
|
|
@ -25,6 +25,14 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) types.ContainerSpec {
|
|||
TTY: c.TTY,
|
||||
}
|
||||
|
||||
if c.DNSConfig != nil {
|
||||
containerSpec.DNSConfig = &types.DNSConfig{
|
||||
Nameservers: c.DNSConfig.Nameservers,
|
||||
Search: c.DNSConfig.Search,
|
||||
Options: c.DNSConfig.Options,
|
||||
}
|
||||
}
|
||||
|
||||
// Mounts
|
||||
for _, m := range c.Mounts {
|
||||
mount := mounttypes.Mount{
|
||||
|
@ -81,6 +89,14 @@ func containerToGRPC(c types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
|
|||
TTY: c.TTY,
|
||||
}
|
||||
|
||||
if c.DNSConfig != nil {
|
||||
containerSpec.DNSConfig = &swarmapi.ContainerSpec_DNSConfig{
|
||||
Nameservers: c.DNSConfig.Nameservers,
|
||||
Search: c.DNSConfig.Search,
|
||||
Options: c.DNSConfig.Options,
|
||||
}
|
||||
}
|
||||
|
||||
if c.StopGracePeriod != nil {
|
||||
containerSpec.StopGracePeriod = ptypes.DurationProto(*c.StopGracePeriod)
|
||||
}
|
||||
|
|
|
@ -327,6 +327,12 @@ func (c *containerConfig) hostConfig() *enginecontainer.HostConfig {
|
|||
GroupAdd: c.spec().Groups,
|
||||
}
|
||||
|
||||
if c.spec().DNSConfig != nil {
|
||||
hc.DNS = c.spec().DNSConfig.Nameservers
|
||||
hc.DNSSearch = c.spec().DNSConfig.Search
|
||||
hc.DNSOptions = c.spec().DNSConfig.Options
|
||||
}
|
||||
|
||||
if c.task.LogDriver != nil {
|
||||
hc.LogConfig = enginecontainer.LogConfig{
|
||||
Type: c.task.LogDriver.Name,
|
||||
|
|
|
@ -169,6 +169,7 @@ This section lists each version from latest to oldest. Each listing includes a
|
|||
* `GET /info` now returns more structured information about security options.
|
||||
* The `HostConfig` field now includes `CpuCount` that represents the number of CPUs available for execution by the container. Windows daemon only.
|
||||
* `POST /services/create` and `POST /services/(id or name)/update` now accept the `TTY` parameter, which allocate a pseudo-TTY in container.
|
||||
* `POST /services/create` and `POST /services/(id or name)/update` now accept the `DNSConfig` parameter, which specifies DNS related configurations in resolver configuration file (resolv.conf) through `Nameservers`, `Search`, and `Options`.
|
||||
|
||||
### v1.24 API changes
|
||||
|
||||
|
|
|
@ -5114,7 +5114,12 @@ image](#create-an-image) section for more details.
|
|||
}
|
||||
],
|
||||
"User": "33",
|
||||
"TTY": false
|
||||
"TTY": false,
|
||||
"DNSConfig": {
|
||||
"Nameservers": ["8.8.8.8"],
|
||||
"Search": ["example.org"],
|
||||
"Options": ["timeout:3"]
|
||||
}
|
||||
},
|
||||
"LogDriver": {
|
||||
"Name": "json-file",
|
||||
|
@ -5209,6 +5214,11 @@ image](#create-an-image) section for more details.
|
|||
- **Options** - key/value map of driver specific options.
|
||||
- **StopGracePeriod** – Amount of time to wait for the container to terminate before
|
||||
forcefully killing it.
|
||||
- **DNSConfig** – Specification for DNS related configurations in
|
||||
resolver configuration file (resolv.conf).
|
||||
- **Nameservers** – A list of the IP addresses of the name servers.
|
||||
- **Search** – A search list for host-name lookup.
|
||||
- **Options** – A list of internal resolver variables to be modified (e.g., `debug`, `ndots:3`, etc.).
|
||||
- **LogDriver** - Log configuration for containers created as part of the
|
||||
service.
|
||||
- **Name** - Name of the logging driver to use (`json-file`, `syslog`,
|
||||
|
@ -5394,7 +5404,12 @@ image](#create-an-image) section for more details.
|
|||
"Args": [
|
||||
"top"
|
||||
],
|
||||
"TTY": true
|
||||
"TTY": true,
|
||||
"DNSConfig": {
|
||||
"Nameservers": ["8.8.8.8"],
|
||||
"Search": ["example.org"],
|
||||
"Options": ["timeout:3"]
|
||||
}
|
||||
},
|
||||
"Resources": {
|
||||
"Limits": {},
|
||||
|
@ -5460,6 +5475,11 @@ image](#create-an-image) section for more details.
|
|||
- **Options** - key/value map of driver specific options
|
||||
- **StopGracePeriod** – Amount of time to wait for the container to terminate before
|
||||
forcefully killing it.
|
||||
- **DNSConfig** – Specification for DNS related configurations in
|
||||
resolver configuration file (resolv.conf).
|
||||
- **Nameservers** – A list of the IP addresses of the name servers.
|
||||
- **Search** – A search list for host-name lookup.
|
||||
- **Options** – A list of internal resolver variables to be modified (e.g., `debug`, `ndots:3`, etc.).
|
||||
- **Resources** – Resource requirements which apply to each individual container created as part
|
||||
of the service.
|
||||
- **Limits** – Define resources limits.
|
||||
|
|
|
@ -23,6 +23,9 @@ Create a new service
|
|||
Options:
|
||||
--constraint value Placement constraints (default [])
|
||||
--container-label value Service container labels (default [])
|
||||
--dns list Set custom DNS servers (default [])
|
||||
--dns-options list Set DNS options (default [])
|
||||
--dns-search list Set custom DNS search domains (default [])
|
||||
--endpoint-mode string Endpoint mode (vip or dnsrr)
|
||||
-e, --env value Set environment variables (default [])
|
||||
--env-file value Read in a file of environment variables (default [])
|
||||
|
|
|
@ -26,6 +26,12 @@ Options:
|
|||
--constraint-rm list Remove a constraint (default [])
|
||||
--container-label-add list Add or update a container label (default [])
|
||||
--container-label-rm list Remove a container label by its key (default [])
|
||||
--dns-add list Add or update custom DNS servers (default [])
|
||||
--dns-options-add list Add or update DNS options (default [])
|
||||
--dns-options-rm list Remove DNS options (default [])
|
||||
--dns-rm list Remove custom DNS servers (default [])
|
||||
--dns-search-add list Add or update custom DNS search domains (default [])
|
||||
--dns-search-rm list Remove DNS search domains (default [])
|
||||
--endpoint-mode string Endpoint mode (vip or dnsrr)
|
||||
--env-add list Add or update an environment variable (default [])
|
||||
--env-rm list Remove an environment variable (default [])
|
||||
|
|
|
@ -789,3 +789,49 @@ func (s *DockerSwarmSuite) TestSwarmServiceTTYUpdate(c *check.C) {
|
|||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(strings.TrimSpace(out), checker.Equals, "true")
|
||||
}
|
||||
|
||||
func (s *DockerSwarmSuite) TestDNSConfig(c *check.C) {
|
||||
d := s.AddDaemon(c, true, true)
|
||||
|
||||
// Create a service
|
||||
name := "top"
|
||||
_, err := d.Cmd("service", "create", "--name", name, "--dns=1.2.3.4", "--dns-search=example.com", "--dns-options=timeout:3", "busybox", "top")
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Make sure task has been deployed.
|
||||
waitAndAssert(c, defaultReconciliationTimeout, d.checkActiveContainerCount, checker.Equals, 1)
|
||||
|
||||
// We need to get the container id.
|
||||
out, err := d.Cmd("ps", "-a", "-q", "--no-trunc")
|
||||
c.Assert(err, checker.IsNil)
|
||||
id := strings.TrimSpace(out)
|
||||
|
||||
// Compare against expected output.
|
||||
expectedOutput1 := "nameserver 1.2.3.4"
|
||||
expectedOutput2 := "search example.com"
|
||||
expectedOutput3 := "options timeout:3"
|
||||
out, err = d.Cmd("exec", id, "cat", "/etc/resolv.conf")
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(out, checker.Contains, expectedOutput1, check.Commentf("Expected '%s', but got %q", expectedOutput1, out))
|
||||
c.Assert(out, checker.Contains, expectedOutput2, check.Commentf("Expected '%s', but got %q", expectedOutput2, out))
|
||||
c.Assert(out, checker.Contains, expectedOutput3, check.Commentf("Expected '%s', but got %q", expectedOutput3, out))
|
||||
}
|
||||
|
||||
func (s *DockerSwarmSuite) TestDNSConfigUpdate(c *check.C) {
|
||||
d := s.AddDaemon(c, true, true)
|
||||
|
||||
// Create a service
|
||||
name := "top"
|
||||
_, err := d.Cmd("service", "create", "--name", name, "busybox", "top")
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Make sure task has been deployed.
|
||||
waitAndAssert(c, defaultReconciliationTimeout, d.checkActiveContainerCount, checker.Equals, 1)
|
||||
|
||||
_, err = d.Cmd("service", "update", "--dns-add=1.2.3.4", "--dns-search-add=example.com", "--dns-options-add=timeout:3", name)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
out, err := d.Cmd("service", "inspect", "--format", "{{ .Spec.TaskTemplate.ContainerSpec.DNSConfig }}", name)
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(strings.TrimSpace(out), checker.Equals, "{[1.2.3.4] [example.com] [timeout:3]}")
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче