daemon: build the list of endpoint's DNS names
Instead of special-casing anonymous endpoints in libnetwork, let the daemon specify what (non fully qualified) DNS names should be associated to container's endpoints. Signed-off-by: Albin Kerouanton <albinker@gmail.com>
This commit is contained in:
parent
dc1e73cbbf
commit
ab8968437b
8 changed files with 134 additions and 1 deletions
|
@ -2530,6 +2530,21 @@ definitions:
|
|||
example:
|
||||
com.example.some-label: "some-value"
|
||||
com.example.some-other-label: "some-other-value"
|
||||
DNSNames:
|
||||
description: |
|
||||
List of all DNS names an endpoint has on a specific network. This
|
||||
list is based on the container name, network aliases, container short
|
||||
ID, and hostname.
|
||||
|
||||
These DNS names are non-fully qualified but can contain several dots.
|
||||
You can get fully qualified DNS names by appending `.<network-name>`.
|
||||
For instance, if container name is `my.ctr` and the network is named
|
||||
`testnet`, `DNSNames` will contain `my.ctr` and the FQDN will be
|
||||
`my.ctr.testnet`.
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: ["foobar", "server_x", "server_y", "my.ctr"]
|
||||
|
||||
EndpointIPAMConfig:
|
||||
description: |
|
||||
|
|
|
@ -13,7 +13,7 @@ type EndpointSettings struct {
|
|||
// Configurations
|
||||
IPAMConfig *EndpointIPAMConfig
|
||||
Links []string
|
||||
Aliases []string
|
||||
Aliases []string // Aliases holds the list of extra, user-specified DNS names for this endpoint.
|
||||
MacAddress string
|
||||
// Operational data
|
||||
NetworkID string
|
||||
|
@ -25,6 +25,9 @@ type EndpointSettings struct {
|
|||
GlobalIPv6Address string
|
||||
GlobalIPv6PrefixLen int
|
||||
DriverOpts map[string]string
|
||||
// DNSNames holds all the (non fully qualified) DNS names associated to this endpoint. First entry is used to
|
||||
// generate PTR records.
|
||||
DNSNames []string
|
||||
}
|
||||
|
||||
// Copy makes a deep copy of `EndpointSettings`
|
||||
|
@ -43,6 +46,12 @@ func (es *EndpointSettings) Copy() *EndpointSettings {
|
|||
aliases := make([]string, 0, len(es.Aliases))
|
||||
epCopy.Aliases = append(aliases, es.Aliases...)
|
||||
}
|
||||
|
||||
if len(es.DNSNames) > 0 {
|
||||
epCopy.DNSNames = make([]string, len(es.DNSNames))
|
||||
copy(epCopy.DNSNames, es.DNSNames)
|
||||
}
|
||||
|
||||
return &epCopy
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/docker/docker/daemon/network"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/internal/multierror"
|
||||
"github.com/docker/docker/internal/sliceutil"
|
||||
"github.com/docker/docker/libnetwork"
|
||||
"github.com/docker/docker/libnetwork/netlabel"
|
||||
"github.com/docker/docker/libnetwork/options"
|
||||
|
@ -650,6 +651,9 @@ func cleanOperationalData(es *network.EndpointSettings) {
|
|||
|
||||
func (daemon *Daemon) updateNetworkConfig(container *container.Container, n *libnetwork.Network, endpointConfig *networktypes.EndpointSettings, updateSettings bool) error {
|
||||
if containertypes.NetworkMode(n.Name()).IsUserDefined() {
|
||||
endpointConfig.DNSNames = buildEndpointDNSNames(container, endpointConfig.Aliases)
|
||||
|
||||
// TODO(aker): remove this code once endpoint's DNSNames is used for real.
|
||||
addShortID := true
|
||||
shortID := stringid.TruncateID(container.ID)
|
||||
for _, alias := range endpointConfig.Aliases {
|
||||
|
@ -687,6 +691,29 @@ func (daemon *Daemon) updateNetworkConfig(container *container.Container, n *lib
|
|||
return nil
|
||||
}
|
||||
|
||||
// buildEndpointDNSNames constructs the list of DNSNames that should be assigned to a given endpoint. The order within
|
||||
// the returned slice is important as the first entry will be used to generate the PTR records (for IPv4 and v6)
|
||||
// associated to this endpoint.
|
||||
func buildEndpointDNSNames(ctr *container.Container, aliases []string) []string {
|
||||
var dnsNames []string
|
||||
|
||||
if ctr.Name != "" {
|
||||
dnsNames = append(dnsNames, strings.TrimPrefix(ctr.Name, "/"))
|
||||
}
|
||||
|
||||
dnsNames = append(dnsNames, aliases...)
|
||||
|
||||
if ctr.ID != "" {
|
||||
dnsNames = append(dnsNames, stringid.TruncateID(ctr.ID))
|
||||
}
|
||||
|
||||
if ctr.Config.Hostname != "" {
|
||||
dnsNames = append(dnsNames, ctr.Config.Hostname)
|
||||
}
|
||||
|
||||
return sliceutil.Dedup(dnsNames)
|
||||
}
|
||||
|
||||
func (daemon *Daemon) connectToNetwork(cfg *config.Config, container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (err error) {
|
||||
start := time.Now()
|
||||
if container.HostConfig.NetworkMode.IsContainer() {
|
||||
|
|
56
daemon/container_operations_test.go
Normal file
56
daemon/container_operations_test.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package daemon
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
containertypes "github.com/docker/docker/api/types/container"
|
||||
networktypes "github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/container"
|
||||
"github.com/docker/docker/libnetwork"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestDNSNamesAreEquivalentToAliases(t *testing.T) {
|
||||
d := &Daemon{}
|
||||
ctr := &container.Container{
|
||||
ID: "35de8003b19e27f636fc6ecbf4d7072558b872a8544f287fd69ad8182ad59023",
|
||||
Name: "foobar",
|
||||
Config: &containertypes.Config{
|
||||
Hostname: "baz",
|
||||
},
|
||||
}
|
||||
nw := buildNetwork(t, map[string]any{
|
||||
"id": "1234567890",
|
||||
"name": "testnet",
|
||||
"networkType": "bridge",
|
||||
"enableIPv6": false,
|
||||
})
|
||||
epSettings := &networktypes.EndpointSettings{
|
||||
Aliases: []string{"myctr"},
|
||||
}
|
||||
|
||||
if err := d.updateNetworkConfig(ctr, nw, epSettings, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Check(t, is.DeepEqual(epSettings.Aliases, []string{"myctr", "35de8003b19e", "baz"}))
|
||||
assert.Check(t, is.DeepEqual(epSettings.DNSNames, []string{"foobar", "myctr", "35de8003b19e", "baz"}))
|
||||
}
|
||||
|
||||
func buildNetwork(t *testing.T, config map[string]any) *libnetwork.Network {
|
||||
t.Helper()
|
||||
|
||||
b, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
nw := &libnetwork.Network{}
|
||||
if err := nw.UnmarshalJSON(b); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return nw
|
||||
}
|
|
@ -821,9 +821,12 @@ func buildCreateEndpointOptions(c *container.Container, n *libnetwork.Network, e
|
|||
createOptions = append(createOptions, libnetwork.CreateOptionIpam(ip, ip6, ipList, nil))
|
||||
}
|
||||
|
||||
// TODO(aker): remove this loop once endpoint's DNSNames is used for real
|
||||
for _, alias := range epConfig.Aliases {
|
||||
createOptions = append(createOptions, libnetwork.CreateOptionMyAlias(alias))
|
||||
}
|
||||
createOptions = append(createOptions, libnetwork.CreateOptionDNSNames(epConfig.DNSNames))
|
||||
|
||||
for k, v := range epConfig.DriverOpts {
|
||||
createOptions = append(createOptions, libnetwork.EndpointOptionGeneric(options.Generic{k: v}))
|
||||
}
|
||||
|
|
|
@ -68,6 +68,8 @@ keywords: "API, Docker, rcli, REST, documentation"
|
|||
* The `Container` and `ContainerConfig` fields in the `GET /images/{name}/json`
|
||||
response are deprecated and will no longer be included in API v1.45.
|
||||
* `GET /info` now includes `status` properties in `Runtimes`.
|
||||
* A new field named `DNSNames` and containing all non-fully qualified DNS names
|
||||
a container takes on a specific network has been added to `GET /containers/{name:.*}/json`.
|
||||
|
||||
## v1.43 API changes
|
||||
|
||||
|
|
13
internal/sliceutil/sliceutil.go
Normal file
13
internal/sliceutil/sliceutil.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package sliceutil
|
||||
|
||||
func Dedup[T comparable](slice []T) []T {
|
||||
keys := make(map[T]struct{})
|
||||
out := make([]T, 0, len(slice))
|
||||
for _, s := range slice {
|
||||
if _, ok := keys[s]; !ok {
|
||||
out = append(out, s)
|
||||
keys[s] = struct{}{}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
|
@ -972,6 +972,14 @@ func CreateOptionAnonymous() EndpointOption {
|
|||
}
|
||||
}
|
||||
|
||||
// CreateOptionDNSNames specifies the list of (non fully qualified) DNS names associated to an endpoint. These will be
|
||||
// used to populate the embedded DNS server. Order matters: first name will be used to generate PTR records.
|
||||
func CreateOptionDNSNames(names []string) EndpointOption {
|
||||
return func(ep *Endpoint) {
|
||||
ep.dnsNames = names
|
||||
}
|
||||
}
|
||||
|
||||
// CreateOptionDisableResolution function returns an option setter to indicate
|
||||
// this endpoint doesn't want embedded DNS server functionality
|
||||
func CreateOptionDisableResolution() EndpointOption {
|
||||
|
|
Loading…
Reference in a new issue