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>
| ... | ... |
@@ -2530,6 +2530,21 @@ definitions: |
| 2530 | 2530 |
example: |
| 2531 | 2531 |
com.example.some-label: "some-value" |
| 2532 | 2532 |
com.example.some-other-label: "some-other-value" |
| 2533 |
+ DNSNames: |
|
| 2534 |
+ description: | |
|
| 2535 |
+ List of all DNS names an endpoint has on a specific network. This |
|
| 2536 |
+ list is based on the container name, network aliases, container short |
|
| 2537 |
+ ID, and hostname. |
|
| 2538 |
+ |
|
| 2539 |
+ These DNS names are non-fully qualified but can contain several dots. |
|
| 2540 |
+ You can get fully qualified DNS names by appending `.<network-name>`. |
|
| 2541 |
+ For instance, if container name is `my.ctr` and the network is named |
|
| 2542 |
+ `testnet`, `DNSNames` will contain `my.ctr` and the FQDN will be |
|
| 2543 |
+ `my.ctr.testnet`. |
|
| 2544 |
+ type: array |
|
| 2545 |
+ items: |
|
| 2546 |
+ type: string |
|
| 2547 |
+ example: ["foobar", "server_x", "server_y", "my.ctr"] |
|
| 2533 | 2548 |
|
| 2534 | 2549 |
EndpointIPAMConfig: |
| 2535 | 2550 |
description: | |
| ... | ... |
@@ -13,7 +13,7 @@ type EndpointSettings struct {
|
| 13 | 13 |
// Configurations |
| 14 | 14 |
IPAMConfig *EndpointIPAMConfig |
| 15 | 15 |
Links []string |
| 16 |
- Aliases []string |
|
| 16 |
+ Aliases []string // Aliases holds the list of extra, user-specified DNS names for this endpoint. |
|
| 17 | 17 |
MacAddress string |
| 18 | 18 |
// Operational data |
| 19 | 19 |
NetworkID string |
| ... | ... |
@@ -25,6 +25,9 @@ type EndpointSettings struct {
|
| 25 | 25 |
GlobalIPv6Address string |
| 26 | 26 |
GlobalIPv6PrefixLen int |
| 27 | 27 |
DriverOpts map[string]string |
| 28 |
+ // DNSNames holds all the (non fully qualified) DNS names associated to this endpoint. First entry is used to |
|
| 29 |
+ // generate PTR records. |
|
| 30 |
+ DNSNames []string |
|
| 28 | 31 |
} |
| 29 | 32 |
|
| 30 | 33 |
// Copy makes a deep copy of `EndpointSettings` |
| ... | ... |
@@ -43,6 +46,12 @@ func (es *EndpointSettings) Copy() *EndpointSettings {
|
| 43 | 43 |
aliases := make([]string, 0, len(es.Aliases)) |
| 44 | 44 |
epCopy.Aliases = append(aliases, es.Aliases...) |
| 45 | 45 |
} |
| 46 |
+ |
|
| 47 |
+ if len(es.DNSNames) > 0 {
|
|
| 48 |
+ epCopy.DNSNames = make([]string, len(es.DNSNames)) |
|
| 49 |
+ copy(epCopy.DNSNames, es.DNSNames) |
|
| 50 |
+ } |
|
| 51 |
+ |
|
| 46 | 52 |
return &epCopy |
| 47 | 53 |
} |
| 48 | 54 |
|
| ... | ... |
@@ -19,6 +19,7 @@ import ( |
| 19 | 19 |
"github.com/docker/docker/daemon/network" |
| 20 | 20 |
"github.com/docker/docker/errdefs" |
| 21 | 21 |
"github.com/docker/docker/internal/multierror" |
| 22 |
+ "github.com/docker/docker/internal/sliceutil" |
|
| 22 | 23 |
"github.com/docker/docker/libnetwork" |
| 23 | 24 |
"github.com/docker/docker/libnetwork/netlabel" |
| 24 | 25 |
"github.com/docker/docker/libnetwork/options" |
| ... | ... |
@@ -650,6 +651,9 @@ func cleanOperationalData(es *network.EndpointSettings) {
|
| 650 | 650 |
|
| 651 | 651 |
func (daemon *Daemon) updateNetworkConfig(container *container.Container, n *libnetwork.Network, endpointConfig *networktypes.EndpointSettings, updateSettings bool) error {
|
| 652 | 652 |
if containertypes.NetworkMode(n.Name()).IsUserDefined() {
|
| 653 |
+ endpointConfig.DNSNames = buildEndpointDNSNames(container, endpointConfig.Aliases) |
|
| 654 |
+ |
|
| 655 |
+ // TODO(aker): remove this code once endpoint's DNSNames is used for real. |
|
| 653 | 656 |
addShortID := true |
| 654 | 657 |
shortID := stringid.TruncateID(container.ID) |
| 655 | 658 |
for _, alias := range endpointConfig.Aliases {
|
| ... | ... |
@@ -687,6 +691,29 @@ func (daemon *Daemon) updateNetworkConfig(container *container.Container, n *lib |
| 687 | 687 |
return nil |
| 688 | 688 |
} |
| 689 | 689 |
|
| 690 |
+// buildEndpointDNSNames constructs the list of DNSNames that should be assigned to a given endpoint. The order within |
|
| 691 |
+// the returned slice is important as the first entry will be used to generate the PTR records (for IPv4 and v6) |
|
| 692 |
+// associated to this endpoint. |
|
| 693 |
+func buildEndpointDNSNames(ctr *container.Container, aliases []string) []string {
|
|
| 694 |
+ var dnsNames []string |
|
| 695 |
+ |
|
| 696 |
+ if ctr.Name != "" {
|
|
| 697 |
+ dnsNames = append(dnsNames, strings.TrimPrefix(ctr.Name, "/")) |
|
| 698 |
+ } |
|
| 699 |
+ |
|
| 700 |
+ dnsNames = append(dnsNames, aliases...) |
|
| 701 |
+ |
|
| 702 |
+ if ctr.ID != "" {
|
|
| 703 |
+ dnsNames = append(dnsNames, stringid.TruncateID(ctr.ID)) |
|
| 704 |
+ } |
|
| 705 |
+ |
|
| 706 |
+ if ctr.Config.Hostname != "" {
|
|
| 707 |
+ dnsNames = append(dnsNames, ctr.Config.Hostname) |
|
| 708 |
+ } |
|
| 709 |
+ |
|
| 710 |
+ return sliceutil.Dedup(dnsNames) |
|
| 711 |
+} |
|
| 712 |
+ |
|
| 690 | 713 |
func (daemon *Daemon) connectToNetwork(cfg *config.Config, container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (err error) {
|
| 691 | 714 |
start := time.Now() |
| 692 | 715 |
if container.HostConfig.NetworkMode.IsContainer() {
|
| 693 | 716 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,56 @@ |
| 0 |
+package daemon |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/json" |
|
| 4 |
+ "testing" |
|
| 5 |
+ |
|
| 6 |
+ containertypes "github.com/docker/docker/api/types/container" |
|
| 7 |
+ networktypes "github.com/docker/docker/api/types/network" |
|
| 8 |
+ "github.com/docker/docker/container" |
|
| 9 |
+ "github.com/docker/docker/libnetwork" |
|
| 10 |
+ "gotest.tools/v3/assert" |
|
| 11 |
+ is "gotest.tools/v3/assert/cmp" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+func TestDNSNamesAreEquivalentToAliases(t *testing.T) {
|
|
| 15 |
+ d := &Daemon{}
|
|
| 16 |
+ ctr := &container.Container{
|
|
| 17 |
+ ID: "35de8003b19e27f636fc6ecbf4d7072558b872a8544f287fd69ad8182ad59023", |
|
| 18 |
+ Name: "foobar", |
|
| 19 |
+ Config: &containertypes.Config{
|
|
| 20 |
+ Hostname: "baz", |
|
| 21 |
+ }, |
|
| 22 |
+ } |
|
| 23 |
+ nw := buildNetwork(t, map[string]any{
|
|
| 24 |
+ "id": "1234567890", |
|
| 25 |
+ "name": "testnet", |
|
| 26 |
+ "networkType": "bridge", |
|
| 27 |
+ "enableIPv6": false, |
|
| 28 |
+ }) |
|
| 29 |
+ epSettings := &networktypes.EndpointSettings{
|
|
| 30 |
+ Aliases: []string{"myctr"},
|
|
| 31 |
+ } |
|
| 32 |
+ |
|
| 33 |
+ if err := d.updateNetworkConfig(ctr, nw, epSettings, false); err != nil {
|
|
| 34 |
+ t.Fatal(err) |
|
| 35 |
+ } |
|
| 36 |
+ |
|
| 37 |
+ assert.Check(t, is.DeepEqual(epSettings.Aliases, []string{"myctr", "35de8003b19e", "baz"}))
|
|
| 38 |
+ assert.Check(t, is.DeepEqual(epSettings.DNSNames, []string{"foobar", "myctr", "35de8003b19e", "baz"}))
|
|
| 39 |
+} |
|
| 40 |
+ |
|
| 41 |
+func buildNetwork(t *testing.T, config map[string]any) *libnetwork.Network {
|
|
| 42 |
+ t.Helper() |
|
| 43 |
+ |
|
| 44 |
+ b, err := json.Marshal(config) |
|
| 45 |
+ if err != nil {
|
|
| 46 |
+ t.Fatal(err) |
|
| 47 |
+ } |
|
| 48 |
+ |
|
| 49 |
+ nw := &libnetwork.Network{}
|
|
| 50 |
+ if err := nw.UnmarshalJSON(b); err != nil {
|
|
| 51 |
+ t.Fatal(err) |
|
| 52 |
+ } |
|
| 53 |
+ |
|
| 54 |
+ return nw |
|
| 55 |
+} |
| ... | ... |
@@ -821,9 +821,12 @@ func buildCreateEndpointOptions(c *container.Container, n *libnetwork.Network, e |
| 821 | 821 |
createOptions = append(createOptions, libnetwork.CreateOptionIpam(ip, ip6, ipList, nil)) |
| 822 | 822 |
} |
| 823 | 823 |
|
| 824 |
+ // TODO(aker): remove this loop once endpoint's DNSNames is used for real |
|
| 824 | 825 |
for _, alias := range epConfig.Aliases {
|
| 825 | 826 |
createOptions = append(createOptions, libnetwork.CreateOptionMyAlias(alias)) |
| 826 | 827 |
} |
| 828 |
+ createOptions = append(createOptions, libnetwork.CreateOptionDNSNames(epConfig.DNSNames)) |
|
| 829 |
+ |
|
| 827 | 830 |
for k, v := range epConfig.DriverOpts {
|
| 828 | 831 |
createOptions = append(createOptions, libnetwork.EndpointOptionGeneric(options.Generic{k: v}))
|
| 829 | 832 |
} |
| ... | ... |
@@ -68,6 +68,8 @@ keywords: "API, Docker, rcli, REST, documentation" |
| 68 | 68 |
* The `Container` and `ContainerConfig` fields in the `GET /images/{name}/json`
|
| 69 | 69 |
response are deprecated and will no longer be included in API v1.45. |
| 70 | 70 |
* `GET /info` now includes `status` properties in `Runtimes`. |
| 71 |
+* A new field named `DNSNames` and containing all non-fully qualified DNS names |
|
| 72 |
+ a container takes on a specific network has been added to `GET /containers/{name:.*}/json`.
|
|
| 71 | 73 |
|
| 72 | 74 |
## v1.43 API changes |
| 73 | 75 |
|
| 74 | 76 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,13 @@ |
| 0 |
+package sliceutil |
|
| 1 |
+ |
|
| 2 |
+func Dedup[T comparable](slice []T) []T {
|
|
| 3 |
+ keys := make(map[T]struct{})
|
|
| 4 |
+ out := make([]T, 0, len(slice)) |
|
| 5 |
+ for _, s := range slice {
|
|
| 6 |
+ if _, ok := keys[s]; !ok {
|
|
| 7 |
+ out = append(out, s) |
|
| 8 |
+ keys[s] = struct{}{}
|
|
| 9 |
+ } |
|
| 10 |
+ } |
|
| 11 |
+ return out |
|
| 12 |
+} |
| ... | ... |
@@ -972,6 +972,14 @@ func CreateOptionAnonymous() EndpointOption {
|
| 972 | 972 |
} |
| 973 | 973 |
} |
| 974 | 974 |
|
| 975 |
+// CreateOptionDNSNames specifies the list of (non fully qualified) DNS names associated to an endpoint. These will be |
|
| 976 |
+// used to populate the embedded DNS server. Order matters: first name will be used to generate PTR records. |
|
| 977 |
+func CreateOptionDNSNames(names []string) EndpointOption {
|
|
| 978 |
+ return func(ep *Endpoint) {
|
|
| 979 |
+ ep.dnsNames = names |
|
| 980 |
+ } |
|
| 981 |
+} |
|
| 982 |
+ |
|
| 975 | 983 |
// CreateOptionDisableResolution function returns an option setter to indicate |
| 976 | 984 |
// this endpoint doesn't want embedded DNS server functionality |
| 977 | 985 |
func CreateOptionDisableResolution() EndpointOption {
|