mirror of
https://github.com/status-im/consul.git
synced 2025-01-09 21:35:52 +00:00
e812f5516a
- A new endpoint `/v1/agent/service/:service_id` which is a generic way to look up the service for a single instance. The primary value here is that it: - **supports hash-based blocking** and so; - **replaces `/agent/connect/proxy/:proxy_id`** as the mechanism the built-in proxy uses to read its config. - It's not proxy specific and so works for any service. - It has a temporary shim to call through to the existing endpoint to preserve current managed proxy config defaulting behaviour until that is removed entirely (tested). - The built-in proxy now uses the new endpoint exclusively for it's config - The built-in proxy now has a `-sidecar-for` flag that allows the service ID of the _target_ service to be specified, on the condition that there is exactly one "sidecar" proxy (that is one that has `Proxy.DestinationServiceID` set) for the service registered. - Several fixes for edge cases for SidecarService - A fix for `Alias` checks - when running locally they didn't update their state until some external thing updated the target. If the target service has no checks registered as below, then the alias never made it past critical.
171 lines
6.2 KiB
Go
171 lines
6.2 KiB
Go
package agent
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
)
|
|
|
|
func (a *Agent) sidecarServiceID(serviceID string) string {
|
|
return serviceID + "-sidecar-proxy"
|
|
}
|
|
|
|
// 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
|
|
// SidecarService is also already validated.It also assumes that any check
|
|
// 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
|
|
// SidecarService definition contains a distint one.
|
|
func (a *Agent) sidecarServiceFromNodeService(ns *structs.NodeService, token string) (*structs.NodeService, []*structs.CheckType, string, error) {
|
|
if ns.Connect.SidecarService == nil {
|
|
return nil, nil, "", nil
|
|
}
|
|
|
|
// 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.
|
|
sidecar.ID = a.sidecarServiceID(ns.ID)
|
|
|
|
// 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.
|
|
if err := structs.ValidateMetadata(sidecar.Meta, false); err != nil {
|
|
return nil, nil, "", err
|
|
}
|
|
}
|
|
|
|
// Flag this as a sidecar - this is not persisted in catalog but only needed
|
|
// in local agent state to disambiguate lineage when deregistereing the parent
|
|
// 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
|
|
}
|
|
|
|
// Setup some sane connect proxy defaults.
|
|
if sidecar.Kind == "" {
|
|
sidecar.Kind = structs.ServiceKindConnectProxy
|
|
}
|
|
if sidecar.Service == "" {
|
|
sidecar.Service = ns.Service + "-sidecar-proxy"
|
|
}
|
|
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
|
|
}
|
|
if sidecar.Proxy.LocalServiceAddress == "" {
|
|
sidecar.Proxy.LocalServiceAddress = "127.0.0.1"
|
|
}
|
|
if sidecar.Proxy.LocalServicePort < 1 {
|
|
sidecar.Proxy.LocalServicePort = ns.Port
|
|
}
|
|
|
|
// Allocate port if needed (min and max inclusive).
|
|
rangeLen := a.config.ConnectSidecarMaxPort - a.config.ConnectSidecarMinPort + 1
|
|
if sidecar.Port < 1 && a.config.ConnectSidecarMinPort > 0 && rangeLen > 0 {
|
|
// This did pick at random which was simpler but consul reload would assign
|
|
// new ports to all the sidecar 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 permenantly in agent but it doesn't
|
|
// seem to be necessary - even with thousands of services this is not
|
|
// expensive to compute.
|
|
usedPorts := make(map[int]struct{})
|
|
for _, otherNS := range a.State.Services() {
|
|
// Check if other port is in auto-assign range
|
|
if otherNS.Port >= a.config.ConnectSidecarMinPort &&
|
|
otherNS.Port <= a.config.ConnectSidecarMaxPort {
|
|
if otherNS.ID == sidecar.ID {
|
|
// 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.
|
|
sidecar.Port = otherNS.Port
|
|
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.
|
|
if sidecar.Port < 1 {
|
|
// Iterate until we find lowest unused port
|
|
for p := a.config.ConnectSidecarMinPort; p <= a.config.ConnectSidecarMaxPort; p++ {
|
|
_, used := usedPorts[p]
|
|
if !used {
|
|
sidecar.Port = p
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// If no ports left (or auto ports disabled) fail
|
|
if sidecar.Port < 1 {
|
|
// 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 {
|
|
return nil, nil, "", fmt.Errorf("no port provided for sidecar_service " +
|
|
"and auto-assignement disabled in config")
|
|
}
|
|
return nil, nil, "", fmt.Errorf("no port provided for sidecar_service and none "+
|
|
"left in the configured range [%d, %d]", a.config.ConnectSidecarMinPort,
|
|
a.config.ConnectSidecarMaxPort)
|
|
}
|
|
|
|
// Setup checks
|
|
checks, err := ns.Connect.SidecarService.CheckTypes()
|
|
if err != nil {
|
|
return nil, nil, "", err
|
|
}
|
|
|
|
// Setup default check if none given
|
|
if len(checks) < 1 {
|
|
checks = []*structs.CheckType{
|
|
&structs.CheckType{
|
|
Name: "Connect Sidecar Listening",
|
|
// Default to localhost rather than agent/service public IP. The checks
|
|
// can always be overridden if a non-loopback IP is needed.
|
|
TCP: fmt.Sprintf("127.0.0.1:%d", sidecar.Port),
|
|
Interval: 10 * time.Second,
|
|
},
|
|
&structs.CheckType{
|
|
Name: "Connect Sidecar Aliasing " + ns.ID,
|
|
AliasService: ns.ID,
|
|
},
|
|
}
|
|
}
|
|
|
|
return sidecar, checks, token, nil
|
|
}
|