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 +``` diff --git a/client/serviceregistration/address.go b/client/serviceregistration/address.go index b38653bf9c9..7d485ffb4e9 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.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,16 +147,17 @@ func GetAddress( // If no port label is specified just return the IP if portLabel == "" { - 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 { - return netStatus.Address, port.To, 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 @@ -168,10 +169,19 @@ func GetAddress( if port <= 0 { return "", 0, fmt.Errorf("invalid port: %q: port must be >0", portLabel) } - 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 +} 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 { 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..e9e513a649e 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, AddressModeAllocIPv6: // 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" + AddressModeAllocIPv6 = "alloc_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_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, 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 c527d3a8e06..adaee541da2 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 == 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 // 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 == 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() { // No need to continue validating check if it doesn't need a port continue diff --git a/website/content/docs/job-specification/service.mdx b/website/content/docs/job-specification/service.mdx index e7c69a420bb..5737d1da641 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,8 @@ 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` - 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 use a Docker network plugin such as weave, Docker will automatically use