From d6fc3b0d9c420fce99311d75e7e0656d4a8881d6 Mon Sep 17 00:00:00 2001 From: Wim Date: Wed, 9 Apr 2025 20:06:49 +0200 Subject: [PATCH 1/8] Add AllocAdvertiseIPv6 option --- client/serviceregistration/address.go | 14 +++++++++++++- client/serviceregistration/checks/result.go | 2 +- nomad/structs/services.go | 16 +++++++++------- nomad/structs/structs.go | 8 ++++++++ 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/client/serviceregistration/address.go b/client/serviceregistration/address.go index b38653bf9c9..3fbc1c2d7a0 100644 --- a/client/serviceregistration/address.go +++ b/client/serviceregistration/address.go @@ -134,7 +134,7 @@ func GetAddress( return driverNet.IP, port, nil - case structs.AddressModeAlloc: + case structs.AddressModeAlloc, structs.AddressModeAllocAdvertiseIPv6: // Cannot use address mode alloc with custom advertise address. if address != "" { return "", 0, fmt.Errorf("cannot use custom advertise address with %q address mode", structs.AddressModeAlloc) @@ -147,6 +147,9 @@ func GetAddress( // If no port label is specified just return the IP if portLabel == "" { + if addressMode == structs.AddressModeAllocAdvertiseIPv6 { + return netStatus.AddressIPv6, 0, nil + } return netStatus.Address, 0, nil } @@ -154,8 +157,14 @@ func GetAddress( if port, ok := ports.Get(portLabel); ok { // Use port.To value unless not set if port.To > 0 { + if addressMode == structs.AddressModeAllocAdvertiseIPv6 { + return netStatus.AddressIPv6, port.To, nil + } return netStatus.Address, port.To, nil } + if addressMode == structs.AddressModeAllocAdvertiseIPv6 { + return netStatus.AddressIPv6, port.Value, nil + } return netStatus.Address, port.Value, nil } @@ -168,6 +177,9 @@ func GetAddress( if port <= 0 { return "", 0, fmt.Errorf("invalid port: %q: port must be >0", portLabel) } + if addressMode == structs.AddressModeAllocAdvertiseIPv6 { + return netStatus.AddressIPv6, port, nil + } return netStatus.Address, port, nil default: diff --git a/client/serviceregistration/checks/result.go b/client/serviceregistration/checks/result.go index 1251c99aaf9..0094ea12b90 100644 --- a/client/serviceregistration/checks/result.go +++ b/client/serviceregistration/checks/result.go @@ -40,7 +40,7 @@ type Query struct { Timeout time.Duration // connection / request timeout - AddressMode string // host, driver, or alloc + AddressMode string // host, driver, alloc or alloc_advertise_ipv6 PortLabel string // label or value Protocol string // http checks only (http or https) diff --git a/nomad/structs/services.go b/nomad/structs/services.go index 2ab5d4561e8..8a766c1e040 100644 --- a/nomad/structs/services.go +++ b/nomad/structs/services.go @@ -305,7 +305,7 @@ func (sc *ServiceCheck) validateCommon(allowableTypes []string) error { // validate address_mode switch sc.AddressMode { - case "", AddressModeHost, AddressModeDriver, AddressModeAlloc: + case "", AddressModeHost, AddressModeDriver, AddressModeAlloc, AddressModeAllocAdvertiseIPv6: // Ok case AddressModeAuto: return fmt.Errorf("invalid address_mode %q - %s only valid for services", sc.AddressMode, AddressModeAuto) @@ -562,10 +562,11 @@ func hashHeader(h hash.Hash, m map[string][]string) { } const ( - AddressModeAuto = "auto" - AddressModeHost = "host" - AddressModeDriver = "driver" - AddressModeAlloc = "alloc" + AddressModeAuto = "auto" + AddressModeHost = "host" + AddressModeDriver = "driver" + AddressModeAlloc = "alloc" + AddressModeAllocAdvertiseIPv6 = "alloc_advertise_ipv6" // ServiceProviderConsul is the default service provider and the way Nomad // worked before native service discovery. @@ -597,7 +598,8 @@ type Service struct { PortLabel string // AddressMode specifies how the address in service registration is - // determined. Must be "auto" (default), "host", "driver", or "alloc". + // determined. Must be "auto" (default), "host", "driver", "alloc" or + // "alloc_advertise_ipv6". AddressMode string // Address enables explicitly setting a custom address to use in service @@ -768,7 +770,7 @@ func (s *Service) Validate() error { switch s.AddressMode { case "", AddressModeAuto: - case AddressModeHost, AddressModeDriver, AddressModeAlloc: + case AddressModeHost, AddressModeDriver, AddressModeAlloc, AddressModeAllocAdvertiseIPv6: if s.Address != "" { mErr.Errors = append(mErr.Errors, fmt.Errorf("Service address_mode must be %q if address is set", AddressModeAuto)) } diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index c527d3a8e06..b1e99126637 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -8313,6 +8313,10 @@ func validateServices(t *Task, tgNetworks Networks) error { mErr.Errors = append(mErr.Errors, fmt.Errorf("service %q cannot use address_mode=\"alloc\", only services defined in a \"group\" block can use this mode", service.Name)) } + if service.AddressMode == AddressModeAllocAdvertiseIPv6 { + mErr.Errors = append(mErr.Errors, fmt.Errorf("service %q cannot use address_mode=\"alloc_advertise_ipv6\", only services defined in a \"group\" block can use this mode", service.Name)) + } + // Ensure that services with the same name are not being registered for // the same port if _, ok := knownServices[service.Name+service.PortLabel]; ok { @@ -8350,6 +8354,10 @@ func validateServices(t *Task, tgNetworks Networks) error { mErr.Errors = append(mErr.Errors, fmt.Errorf("check %q cannot use address_mode=\"alloc\", only checks defined in a \"group\" service block can use this mode", service.Name)) } + if check.AddressMode == AddressModeAllocAdvertiseIPv6 { + mErr.Errors = append(mErr.Errors, fmt.Errorf("check %q cannot use address_mode=\"alloc_advertise_ipv6\", only checks defined in a \"group\" service block can use this mode", service.Name)) + } + if !check.RequiresPort() { // No need to continue validating check if it doesn't need a port continue From 09f6b91b9aa242acfabd0ea77b46e786d17cf19a Mon Sep 17 00:00:00 2001 From: Wim Date: Fri, 8 Aug 2025 11:40:31 +0200 Subject: [PATCH 2/8] Rename to AllocIPv6 --- client/serviceregistration/address.go | 10 +++++----- nomad/structs/services.go | 8 ++++---- nomad/structs/structs.go | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/client/serviceregistration/address.go b/client/serviceregistration/address.go index 3fbc1c2d7a0..96df1b2465e 100644 --- a/client/serviceregistration/address.go +++ b/client/serviceregistration/address.go @@ -134,7 +134,7 @@ func GetAddress( return driverNet.IP, port, nil - case structs.AddressModeAlloc, structs.AddressModeAllocAdvertiseIPv6: + case structs.AddressModeAlloc, structs.AddressModeAllocIPv6: // Cannot use address mode alloc with custom advertise address. if address != "" { return "", 0, fmt.Errorf("cannot use custom advertise address with %q address mode", structs.AddressModeAlloc) @@ -147,7 +147,7 @@ func GetAddress( // If no port label is specified just return the IP if portLabel == "" { - if addressMode == structs.AddressModeAllocAdvertiseIPv6 { + if addressMode == structs.AddressModeAllocIPv6 { return netStatus.AddressIPv6, 0, nil } return netStatus.Address, 0, nil @@ -157,12 +157,12 @@ func GetAddress( if port, ok := ports.Get(portLabel); ok { // Use port.To value unless not set if port.To > 0 { - if addressMode == structs.AddressModeAllocAdvertiseIPv6 { + if addressMode == structs.AddressModeAllocIPv6 { return netStatus.AddressIPv6, port.To, nil } return netStatus.Address, port.To, nil } - if addressMode == structs.AddressModeAllocAdvertiseIPv6 { + if addressMode == structs.AddressModeAllocIPv6 { return netStatus.AddressIPv6, port.Value, nil } return netStatus.Address, port.Value, nil @@ -177,7 +177,7 @@ func GetAddress( if port <= 0 { return "", 0, fmt.Errorf("invalid port: %q: port must be >0", portLabel) } - if addressMode == structs.AddressModeAllocAdvertiseIPv6 { + if addressMode == structs.AddressModeAllocIPv6 { return netStatus.AddressIPv6, port, nil } return netStatus.Address, port, nil diff --git a/nomad/structs/services.go b/nomad/structs/services.go index 8a766c1e040..1ce9fb9b520 100644 --- a/nomad/structs/services.go +++ b/nomad/structs/services.go @@ -305,7 +305,7 @@ func (sc *ServiceCheck) validateCommon(allowableTypes []string) error { // validate address_mode switch sc.AddressMode { - case "", AddressModeHost, AddressModeDriver, AddressModeAlloc, AddressModeAllocAdvertiseIPv6: + case "", AddressModeHost, AddressModeDriver, AddressModeAlloc, AddressModeAllocIPv6: // Ok case AddressModeAuto: return fmt.Errorf("invalid address_mode %q - %s only valid for services", sc.AddressMode, AddressModeAuto) @@ -566,7 +566,7 @@ const ( AddressModeHost = "host" AddressModeDriver = "driver" AddressModeAlloc = "alloc" - AddressModeAllocAdvertiseIPv6 = "alloc_advertise_ipv6" + AddressModeAllocIPv6 = "alloc_ipv6" // ServiceProviderConsul is the default service provider and the way Nomad // worked before native service discovery. @@ -599,7 +599,7 @@ type Service struct { // AddressMode specifies how the address in service registration is // determined. Must be "auto" (default), "host", "driver", "alloc" or - // "alloc_advertise_ipv6". + // "alloc_ipv6". AddressMode string // Address enables explicitly setting a custom address to use in service @@ -770,7 +770,7 @@ func (s *Service) Validate() error { switch s.AddressMode { case "", AddressModeAuto: - case AddressModeHost, AddressModeDriver, AddressModeAlloc, AddressModeAllocAdvertiseIPv6: + case AddressModeHost, AddressModeDriver, AddressModeAlloc, AddressModeAllocIPv6: if s.Address != "" { mErr.Errors = append(mErr.Errors, fmt.Errorf("Service address_mode must be %q if address is set", AddressModeAuto)) } diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index b1e99126637..adaee541da2 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -8313,8 +8313,8 @@ func validateServices(t *Task, tgNetworks Networks) error { mErr.Errors = append(mErr.Errors, fmt.Errorf("service %q cannot use address_mode=\"alloc\", only services defined in a \"group\" block can use this mode", service.Name)) } - if service.AddressMode == AddressModeAllocAdvertiseIPv6 { - mErr.Errors = append(mErr.Errors, fmt.Errorf("service %q cannot use address_mode=\"alloc_advertise_ipv6\", only services defined in a \"group\" block can use this mode", service.Name)) + if service.AddressMode == AddressModeAllocIPv6 { + mErr.Errors = append(mErr.Errors, fmt.Errorf("service %q cannot use address_mode=\"alloc_ipv6\", only services defined in a \"group\" block can use this mode", service.Name)) } // Ensure that services with the same name are not being registered for @@ -8354,8 +8354,8 @@ func validateServices(t *Task, tgNetworks Networks) error { mErr.Errors = append(mErr.Errors, fmt.Errorf("check %q cannot use address_mode=\"alloc\", only checks defined in a \"group\" service block can use this mode", service.Name)) } - if check.AddressMode == AddressModeAllocAdvertiseIPv6 { - mErr.Errors = append(mErr.Errors, fmt.Errorf("check %q cannot use address_mode=\"alloc_advertise_ipv6\", only checks defined in a \"group\" service block can use this mode", service.Name)) + if check.AddressMode == AddressModeAllocIPv6 { + mErr.Errors = append(mErr.Errors, fmt.Errorf("check %q cannot use address_mode=\"alloc_ipv6\", only checks defined in a \"group\" service block can use this mode", service.Name)) } if !check.RequiresPort() { From e3231d2e4374dfdb4a58a23f43a5442312554ab1 Mon Sep 17 00:00:00 2001 From: Wim Date: Fri, 8 Aug 2025 17:12:03 +0200 Subject: [PATCH 3/8] Add tests --- client/serviceregistration/address_test.go | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/client/serviceregistration/address_test.go b/client/serviceregistration/address_test.go index 77c657eddab..269b8c015d6 100644 --- a/client/serviceregistration/address_test.go +++ b/client/serviceregistration/address_test.go @@ -298,6 +298,26 @@ func Test_GetAddress(t *testing.T) { expIP: "172.26.0.1", expPort: 6379, }, + { + name: "Alloc", + mode: structs.AddressModeAllocIPv6, + portLabel: "db", + ports: []structs.AllocatedPortMapping{ + { + Label: "db", + Value: 12345, + To: 6379, + HostIP: HostIP, + }, + }, + status: &structs.AllocNetworkStatus{ + InterfaceName: "eth0", + Address: "172.26.0.1", + AddressIPv6: "2001:db8::8a2e:370:7334", + }, + expIP: "2001:db8::8a2e:370:7334", + expPort: 6379, + }, { name: "Alloc no to value", mode: structs.AddressModeAlloc, @@ -383,6 +403,12 @@ func Test_GetAddress(t *testing.T) { advertise: "example.com", expErr: `cannot use custom advertise address with "alloc" address mode`, }, + { + name: "Address with alloc IPv6 mode", + mode: structs.AddressModeAllocIPv6, + advertise: "example.com", + expErr: `cannot use custom advertise address with "alloc" address mode`, + }, } for _, tc := range testCases { From 2a8b37f0e16915cd630ef81d46617463e1633d51 Mon Sep 17 00:00:00 2001 From: Wim Date: Fri, 8 Aug 2025 17:17:47 +0200 Subject: [PATCH 4/8] Add changelog entry --- .changelog/25632.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/25632.txt diff --git a/.changelog/25632.txt b/.changelog/25632.txt new file mode 100644 index 00000000000..fe12780b22e --- /dev/null +++ b/.changelog/25632.txt @@ -0,0 +1,3 @@ +```release-note:bug +consul: Add AllocIPv6 option to allow IPv6 address being used for service registration +``` From 6979121e1a528185c0c0eee7638dfa454fc8124d Mon Sep 17 00:00:00 2001 From: Wim Date: Fri, 8 Aug 2025 17:26:23 +0200 Subject: [PATCH 5/8] Update documentation --- website/content/docs/job-specification/service.mdx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/website/content/docs/job-specification/service.mdx b/website/content/docs/job-specification/service.mdx index e7c69a420bb..14ced83f882 100644 --- a/website/content/docs/job-specification/service.mdx +++ b/website/content/docs/job-specification/service.mdx @@ -150,6 +150,8 @@ service mesh][connect] integration. If a `to` value is not set, the port falls back to using the allocated host port. The `port` field may be a numeric port or a port label specified in the same group's network block. + - `alloc_ipv6` - Same as `alloc` but use the IPv6 address in case of dual-stack or IPv6-only. + - `driver` - Advertise the port determined by the driver (e.g. Docker). The `port` may be a numeric port or a port label specified in the driver's `ports` field. @@ -190,7 +192,7 @@ service mesh][connect] integration. - `tagged_addresses` `(map` - Specifies custom [tagged addresses][tagged_addresses] to advertise in the Consul service registration. Only available where `provider = "consul"`. -- `address_mode` `(string: "auto")` - Specifies which address (host, alloc or +- `address_mode` `(string: "auto")` - Specifies which address (host, alloc, alloc_ipv6 or driver-specific) this service should advertise. See [below for examples.](#using-driver-address-mode) Valid options are: @@ -200,6 +202,9 @@ service mesh][connect] integration. where no port mapping is necessary. This mode can only be set for services which are defined in a "group" block. + - `alloc_ipv6` - Identical as `alloc` but will pick the IPv6 address in case of + dual-stack or IPv6 only. + - `auto` - Allows the driver to determine whether the host or driver address should be used. Defaults to `host` and only implemented by Docker. If you use a Docker network plugin such as weave, Docker will automatically use From ef4e9363aa929161fa1f0f389eab5c79012af73e Mon Sep 17 00:00:00 2001 From: Wim Date: Fri, 8 Aug 2025 17:44:16 +0200 Subject: [PATCH 6/8] Add getAddressPort helper function --- client/serviceregistration/address.go | 30 +++++++++++++-------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/client/serviceregistration/address.go b/client/serviceregistration/address.go index 96df1b2465e..7d485ffb4e9 100644 --- a/client/serviceregistration/address.go +++ b/client/serviceregistration/address.go @@ -147,25 +147,17 @@ func GetAddress( // If no port label is specified just return the IP if portLabel == "" { - if addressMode == structs.AddressModeAllocIPv6 { - return netStatus.AddressIPv6, 0, nil - } - return netStatus.Address, 0, nil + return getAddressPort(addressMode, netStatus, 0) } // If port is a label and is found then return it if port, ok := ports.Get(portLabel); ok { // Use port.To value unless not set if port.To > 0 { - if addressMode == structs.AddressModeAllocIPv6 { - return netStatus.AddressIPv6, port.To, nil - } - return netStatus.Address, port.To, nil - } - if addressMode == structs.AddressModeAllocIPv6 { - return netStatus.AddressIPv6, port.Value, nil + return getAddressPort(addressMode, netStatus, port.To) } - return netStatus.Address, port.Value, nil + + return getAddressPort(addressMode, netStatus, port.Value) } // Check if port is a literal number @@ -177,13 +169,19 @@ func GetAddress( if port <= 0 { return "", 0, fmt.Errorf("invalid port: %q: port must be >0", portLabel) } - if addressMode == structs.AddressModeAllocIPv6 { - return netStatus.AddressIPv6, port, nil - } - return netStatus.Address, port, nil + return getAddressPort(addressMode, netStatus, port) default: // Shouldn't happen due to validation, but enforce invariants return "", 0, fmt.Errorf("invalid address mode %q", addressMode) } } + +// getAddressPort is a helper function to return the IPv6 or IPv4 address based on the addressMode +func getAddressPort(addressMode string, netStatus *structs.AllocNetworkStatus, port int) (string, int, error) { + if addressMode == structs.AddressModeAllocIPv6 { + return netStatus.AddressIPv6, port, nil + } + + return netStatus.Address, port, nil +} From 62e3fbae95abc560281b873e21557c5094010785 Mon Sep 17 00:00:00 2001 From: Wim Date: Fri, 8 Aug 2025 17:47:02 +0200 Subject: [PATCH 7/8] Update website/content/docs/job-specification/service.mdx Co-authored-by: Aimee Ukasick --- website/content/docs/job-specification/service.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/website/content/docs/job-specification/service.mdx b/website/content/docs/job-specification/service.mdx index 14ced83f882..5737d1da641 100644 --- a/website/content/docs/job-specification/service.mdx +++ b/website/content/docs/job-specification/service.mdx @@ -202,8 +202,7 @@ service mesh][connect] integration. where no port mapping is necessary. This mode can only be set for services which are defined in a "group" block. - - `alloc_ipv6` - Identical as `alloc` but will pick the IPv6 address in case of - dual-stack or IPv6 only. + - `alloc_ipv6` - Same as `alloc` but use the IPv6 address in case of dual-stack or IPv6-only. - `auto` - Allows the driver to determine whether the host or driver address should be used. Defaults to `host` and only implemented by Docker. If you From 5e57eba7e506c1eee50931be424297ede80c4a78 Mon Sep 17 00:00:00 2001 From: Wim Date: Fri, 8 Aug 2025 18:03:35 +0200 Subject: [PATCH 8/8] Run gofmt -s --- nomad/structs/services.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nomad/structs/services.go b/nomad/structs/services.go index 1ce9fb9b520..e9e513a649e 100644 --- a/nomad/structs/services.go +++ b/nomad/structs/services.go @@ -562,10 +562,10 @@ func hashHeader(h hash.Hash, m map[string][]string) { } const ( - AddressModeAuto = "auto" - AddressModeHost = "host" - AddressModeDriver = "driver" - AddressModeAlloc = "alloc" + AddressModeAuto = "auto" + AddressModeHost = "host" + AddressModeDriver = "driver" + AddressModeAlloc = "alloc" AddressModeAllocIPv6 = "alloc_ipv6" // ServiceProviderConsul is the default service provider and the way Nomad