2023-03-28 18:39:22 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2018-09-27 13:33:12 +00:00
|
|
|
package agent
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2022-09-07 00:35:31 +00:00
|
|
|
"strings"
|
2018-09-27 13:33:12 +00:00
|
|
|
"time"
|
|
|
|
|
2019-06-04 14:02:38 +00:00
|
|
|
"github.com/hashicorp/consul/ipaddr"
|
|
|
|
|
2018-09-27 13:33:12 +00:00
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
|
|
)
|
|
|
|
|
2023-05-12 20:49:42 +00:00
|
|
|
const sidecarIDSuffix = structs.SidecarProxySuffix
|
2022-09-09 14:47:10 +00:00
|
|
|
|
|
|
|
func sidecarIDFromServiceID(serviceID string) string {
|
|
|
|
return serviceID + sidecarIDSuffix
|
2018-09-27 13:33:12 +00:00
|
|
|
}
|
|
|
|
|
2022-09-09 14:47:10 +00:00
|
|
|
// reverses the sidecarIDFromServiceID operation
|
|
|
|
func serviceIDFromSidecarID(sidecarID string) string {
|
|
|
|
return strings.TrimSuffix(sidecarID, sidecarIDSuffix)
|
2022-09-07 00:35:31 +00:00
|
|
|
}
|
|
|
|
|
2018-09-27 13:33:12 +00:00
|
|
|
// sidecarServiceFromNodeService returns a *structs.NodeService representing a
|
|
|
|
// sidecar service with all defaults populated based on the current agent
|
|
|
|
// config.
|
|
|
|
//
|
|
|
|
// It assumes the ns has been validated already which means the nested
|
2021-07-26 21:12:29 +00:00
|
|
|
// SidecarService is also already validated. It also assumes that any check
|
2018-09-27 13:33:12 +00:00
|
|
|
// definitions within the sidecar service definition have been validated if
|
|
|
|
// necessary. If no sidecar service is defined in ns, then nil is returned with
|
|
|
|
// nil error.
|
|
|
|
//
|
|
|
|
// The second return argument is a list of CheckTypes to register along with the
|
|
|
|
// service.
|
|
|
|
//
|
|
|
|
// The third return argument is the effective Token to use for the sidecar
|
|
|
|
// registration. This will be the same as the token parameter passed unless the
|
2019-03-06 17:13:28 +00:00
|
|
|
// SidecarService definition contains a distinct one.
|
2020-11-30 18:26:58 +00:00
|
|
|
// TODO: return AddServiceRequest
|
2022-09-07 00:35:31 +00:00
|
|
|
func sidecarServiceFromNodeService(ns *structs.NodeService, token string) (*structs.NodeService, []*structs.CheckType, string, error) {
|
2018-09-27 13:33:12 +00:00
|
|
|
if ns.Connect.SidecarService == nil {
|
|
|
|
return nil, nil, "", nil
|
|
|
|
}
|
|
|
|
|
2021-07-26 21:12:29 +00:00
|
|
|
// for now at least these must be identical
|
|
|
|
ns.Connect.SidecarService.EnterpriseMeta = ns.EnterpriseMeta
|
|
|
|
|
2018-09-27 13:33:12 +00:00
|
|
|
// Start with normal conversion from service definition
|
|
|
|
sidecar := ns.Connect.SidecarService.NodeService()
|
|
|
|
|
|
|
|
// Override the ID which must always be consistent for a given outer service
|
|
|
|
// ID. We rely on this for lifecycle management of the nested definition.
|
2022-09-09 14:47:10 +00:00
|
|
|
sidecar.ID = sidecarIDFromServiceID(ns.ID)
|
2018-09-27 13:33:12 +00:00
|
|
|
|
|
|
|
// Set some meta we can use to disambiguate between service instances we added
|
|
|
|
// later and are responsible for deregistering.
|
|
|
|
if sidecar.Meta != nil {
|
|
|
|
// Meta is non-nil validate it before we add the special key so we can
|
|
|
|
// enforce that user cannot add a consul- prefix one.
|
2020-03-09 20:59:02 +00:00
|
|
|
if err := structs.ValidateServiceMetadata(sidecar.Kind, sidecar.Meta, false); err != nil {
|
2018-09-27 13:33:12 +00:00
|
|
|
return nil, nil, "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Improve Connect with Prepared Queries (#5291)
Given a query like:
```
{
"Name": "tagged-connect-query",
"Service": {
"Service": "foo",
"Tags": ["tag"],
"Connect": true
}
}
```
And a Consul configuration like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {} },
"tags": ["tag"]
]
}
```
If you executed the query it would always turn up with 0 results. This was because the sidecar service was being created without any tags. You could instead make your config look like:
```
{
"services": [
"name": "foo",
"port": 8080,
"connect": { "sidecar_service": {
"tags": ["tag"]
} },
"tags": ["tag"]
]
}
```
However that is a bit redundant for most cases. This PR ensures that the tags and service meta of the parent service get copied to the sidecar service. If there are any tags or service meta set in the sidecar service definition then this copying does not take place. After the changes, the query will now return the expected results.
A second change was made to prepared queries in this PR which is to allow filtering on ServiceMeta just like we allow for filtering on NodeMeta.
2019-02-04 14:36:51 +00:00
|
|
|
// Copy the service metadata from the original service if no other meta was provided
|
|
|
|
if len(sidecar.Meta) == 0 && len(ns.Meta) > 0 {
|
|
|
|
if sidecar.Meta == nil {
|
|
|
|
sidecar.Meta = make(map[string]string)
|
|
|
|
}
|
|
|
|
for k, v := range ns.Meta {
|
|
|
|
sidecar.Meta[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy the tags from the original service if no other tags were specified
|
|
|
|
if len(sidecar.Tags) == 0 && len(ns.Tags) > 0 {
|
|
|
|
sidecar.Tags = append(sidecar.Tags, ns.Tags...)
|
|
|
|
}
|
|
|
|
|
2023-08-10 18:00:44 +00:00
|
|
|
// Copy the locality from the original service if locality was not provided
|
|
|
|
if sidecar.Locality == nil && ns.Locality != nil {
|
|
|
|
tmp := *ns.Locality
|
|
|
|
sidecar.Locality = &tmp
|
|
|
|
}
|
|
|
|
|
2018-09-27 13:33:12 +00:00
|
|
|
// Flag this as a sidecar - this is not persisted in catalog but only needed
|
2018-10-09 16:57:26 +00:00
|
|
|
// in local agent state to disambiguate lineage when deregistering the parent
|
2018-09-27 13:33:12 +00:00
|
|
|
// service later.
|
|
|
|
sidecar.LocallyRegisteredAsSidecar = true
|
|
|
|
|
|
|
|
// See if there is a more specific token for the sidecar registration
|
|
|
|
if ns.Connect.SidecarService.Token != "" {
|
|
|
|
token = ns.Connect.SidecarService.Token
|
|
|
|
}
|
|
|
|
|
2021-07-02 16:18:46 +00:00
|
|
|
// Setup some reasonable connect proxy defaults.
|
2018-09-27 13:33:12 +00:00
|
|
|
if sidecar.Kind == "" {
|
|
|
|
sidecar.Kind = structs.ServiceKindConnectProxy
|
|
|
|
}
|
|
|
|
if sidecar.Service == "" {
|
2023-05-12 20:49:42 +00:00
|
|
|
sidecar.Service = ns.Service + structs.SidecarProxySuffix
|
2018-09-27 13:33:12 +00:00
|
|
|
}
|
|
|
|
if sidecar.Address == "" {
|
|
|
|
// Inherit address from the service if it's provided
|
|
|
|
sidecar.Address = ns.Address
|
|
|
|
}
|
|
|
|
// Proxy defaults
|
|
|
|
if sidecar.Proxy.DestinationServiceName == "" {
|
|
|
|
sidecar.Proxy.DestinationServiceName = ns.Service
|
|
|
|
}
|
|
|
|
if sidecar.Proxy.DestinationServiceID == "" {
|
|
|
|
sidecar.Proxy.DestinationServiceID = ns.ID
|
|
|
|
}
|
2021-05-04 04:43:55 +00:00
|
|
|
|
|
|
|
// Fill defaults from NodeService if none of the address components are present.
|
|
|
|
// This really argues for a refactoring to a more generalized 'address' concept.
|
|
|
|
if sidecar.Proxy.LocalServiceSocketPath == "" && (sidecar.Proxy.LocalServiceAddress == "" || sidecar.Proxy.LocalServicePort < 1) {
|
|
|
|
if ns.SocketPath != "" {
|
|
|
|
sidecar.Proxy.LocalServiceSocketPath = ns.SocketPath
|
|
|
|
} else {
|
|
|
|
if sidecar.Proxy.LocalServiceAddress == "" {
|
|
|
|
sidecar.Proxy.LocalServiceAddress = "127.0.0.1"
|
|
|
|
}
|
|
|
|
if sidecar.Proxy.LocalServicePort < 1 {
|
|
|
|
sidecar.Proxy.LocalServicePort = ns.Port
|
|
|
|
}
|
|
|
|
}
|
2018-09-27 13:33:12 +00:00
|
|
|
}
|
|
|
|
|
2022-07-27 18:19:17 +00:00
|
|
|
// Setup checks
|
|
|
|
checks, err := ns.Connect.SidecarService.CheckTypes()
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return sidecar, checks, token, nil
|
|
|
|
}
|
|
|
|
|
2022-09-07 00:35:31 +00:00
|
|
|
// sidecarPortFromServiceIDLocked is used to allocate a unique port for a sidecar proxy.
|
2022-07-27 18:19:17 +00:00
|
|
|
// This is called immediately before registration to avoid value collisions. This function assumes the state lock is already held.
|
2022-09-07 00:35:31 +00:00
|
|
|
func (a *Agent) sidecarPortFromServiceIDLocked(sidecarCompoundServiceID structs.ServiceID) (int, error) {
|
2022-08-03 01:50:40 +00:00
|
|
|
sidecarPort := 0
|
2022-07-27 18:19:17 +00:00
|
|
|
|
2018-09-27 13:33:12 +00:00
|
|
|
// Allocate port if needed (min and max inclusive).
|
|
|
|
rangeLen := a.config.ConnectSidecarMaxPort - a.config.ConnectSidecarMinPort + 1
|
2022-07-27 18:19:17 +00:00
|
|
|
if sidecarPort < 1 && a.config.ConnectSidecarMinPort > 0 && rangeLen > 0 {
|
2018-09-27 14:00:51 +00:00
|
|
|
// This did pick at random which was simpler but consul reload would assign
|
2018-10-09 16:57:26 +00:00
|
|
|
// new ports to all the sidecars since it unloads all state and
|
|
|
|
// re-populates. It also made this more difficult to test (have to pin the
|
|
|
|
// range to one etc.). Instead we assign sequentially, but rather than N^2
|
|
|
|
// lookups, just iterated services once and find the set of used ports in
|
|
|
|
// allocation range. We could maintain this state permanently in agent but
|
|
|
|
// it doesn't seem to be necessary - even with thousands of services this is
|
|
|
|
// not expensive to compute.
|
2018-09-27 14:00:51 +00:00
|
|
|
usedPorts := make(map[int]struct{})
|
2021-08-19 20:09:42 +00:00
|
|
|
for _, otherNS := range a.State.AllServices() {
|
2018-09-27 14:00:51 +00:00
|
|
|
// Check if other port is in auto-assign range
|
|
|
|
if otherNS.Port >= a.config.ConnectSidecarMinPort &&
|
|
|
|
otherNS.Port <= a.config.ConnectSidecarMaxPort {
|
2022-07-27 18:19:17 +00:00
|
|
|
if otherNS.CompoundServiceID() == sidecarCompoundServiceID {
|
2018-09-27 14:00:51 +00:00
|
|
|
// This sidecar is already registered with an auto-port and is just
|
|
|
|
// being updated so pick the same port as before rather than allocate
|
|
|
|
// a new one.
|
2022-07-27 18:19:17 +00:00
|
|
|
sidecarPort = otherNS.Port
|
2018-09-27 14:00:51 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
usedPorts[otherNS.Port] = struct{}{}
|
|
|
|
}
|
|
|
|
// Note that the proxy might already be registered with a port that was
|
|
|
|
// not in the auto range or the auto range has moved. In either case we
|
|
|
|
// want to allocate a new one so it's no different from ignoring that it
|
|
|
|
// already exists as we do now.
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check we still need to assign a port and didn't find we already had one
|
|
|
|
// allocated.
|
2022-07-27 18:19:17 +00:00
|
|
|
if sidecarPort < 1 {
|
2018-09-27 14:00:51 +00:00
|
|
|
// Iterate until we find lowest unused port
|
|
|
|
for p := a.config.ConnectSidecarMinPort; p <= a.config.ConnectSidecarMaxPort; p++ {
|
|
|
|
_, used := usedPorts[p]
|
|
|
|
if !used {
|
2022-07-27 18:19:17 +00:00
|
|
|
sidecarPort = p
|
2018-09-27 14:00:51 +00:00
|
|
|
break
|
2018-09-27 13:33:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If no ports left (or auto ports disabled) fail
|
2022-07-27 18:19:17 +00:00
|
|
|
if sidecarPort < 1 {
|
2018-09-27 13:33:12 +00:00
|
|
|
// If ports are set to zero explicitly, config builder switches them to
|
|
|
|
// `-1`. In this case don't show the actual values since we don't know what
|
|
|
|
// was actually in config (zero or negative) and it might be confusing, we
|
|
|
|
// just know they explicitly disabled auto assignment.
|
|
|
|
if a.config.ConnectSidecarMinPort < 1 || a.config.ConnectSidecarMaxPort < 1 {
|
2022-07-27 18:19:17 +00:00
|
|
|
return 0, fmt.Errorf("no port provided for sidecar_service " +
|
2018-10-09 16:57:26 +00:00
|
|
|
"and auto-assignment disabled in config")
|
2018-09-27 13:33:12 +00:00
|
|
|
}
|
2022-07-27 18:19:17 +00:00
|
|
|
return 0, fmt.Errorf("no port provided for sidecar_service and none "+
|
2018-09-27 13:33:12 +00:00
|
|
|
"left in the configured range [%d, %d]", a.config.ConnectSidecarMinPort,
|
|
|
|
a.config.ConnectSidecarMaxPort)
|
|
|
|
}
|
|
|
|
|
2022-07-27 18:19:17 +00:00
|
|
|
return sidecarPort, nil
|
|
|
|
}
|
2018-09-27 13:33:12 +00:00
|
|
|
|
2022-09-07 00:35:31 +00:00
|
|
|
func sidecarDefaultChecks(sidecarID string, sidecarAddress string, proxyServiceAddress string, port int) []*structs.CheckType {
|
|
|
|
// The check should use the sidecar's address because it makes a request to the sidecar.
|
|
|
|
// If the sidecar's address is empty, we fall back to the address of the local service, as set in
|
|
|
|
// sidecar.Proxy.LocalServiceAddress, in the hope that the proxy is also accessible on that address
|
|
|
|
// (which in most cases it is because it's running as a sidecar in the same network).
|
|
|
|
// We could instead fall back to the address of the service as set by (ns.Address), but I've kept it using
|
|
|
|
// sidecar.Proxy.LocalServiceAddress so as to not change things too much in the
|
|
|
|
// process of fixing #14433.
|
|
|
|
checkAddress := sidecarAddress
|
|
|
|
if checkAddress == "" {
|
|
|
|
checkAddress = proxyServiceAddress
|
|
|
|
}
|
|
|
|
serviceID := serviceIDFromSidecarID(sidecarID)
|
2022-07-27 18:19:17 +00:00
|
|
|
return []*structs.CheckType{
|
|
|
|
{
|
2022-09-01 21:03:35 +00:00
|
|
|
Name: "Connect Sidecar Listening",
|
2022-09-07 00:35:31 +00:00
|
|
|
TCP: ipaddr.FormatAddressPort(checkAddress, port),
|
2022-07-27 18:19:17 +00:00
|
|
|
Interval: 10 * time.Second,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "Connect Sidecar Aliasing " + serviceID,
|
|
|
|
AliasService: serviceID,
|
|
|
|
},
|
2018-09-27 13:33:12 +00:00
|
|
|
}
|
|
|
|
}
|