github.imxd.top/hashicorp/consul@v1.4.5/agent/sidecar_service.go (about)

     1  package agent
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/hashicorp/consul/agent/structs"
     8  )
     9  
    10  func (a *Agent) sidecarServiceID(serviceID string) string {
    11  	return serviceID + "-sidecar-proxy"
    12  }
    13  
    14  // sidecarServiceFromNodeService returns a *structs.NodeService representing a
    15  // sidecar service with all defaults populated based on the current agent
    16  // config.
    17  //
    18  // It assumes the ns has been validated already which means the nested
    19  // SidecarService is also already validated.It also assumes that any check
    20  // definitions within the sidecar service definition have been validated if
    21  // necessary. If no sidecar service is defined in ns, then nil is returned with
    22  // nil error.
    23  //
    24  // The second return argument is a list of CheckTypes to register along with the
    25  // service.
    26  //
    27  // The third return argument is the effective Token to use for the sidecar
    28  // registration. This will be the same as the token parameter passed unless the
    29  // SidecarService definition contains a distinct one.
    30  func (a *Agent) sidecarServiceFromNodeService(ns *structs.NodeService, token string) (*structs.NodeService, []*structs.CheckType, string, error) {
    31  	if ns.Connect.SidecarService == nil {
    32  		return nil, nil, "", nil
    33  	}
    34  
    35  	// Start with normal conversion from service definition
    36  	sidecar := ns.Connect.SidecarService.NodeService()
    37  
    38  	// Override the ID which must always be consistent for a given outer service
    39  	// ID. We rely on this for lifecycle management of the nested definition.
    40  	sidecar.ID = a.sidecarServiceID(ns.ID)
    41  
    42  	// Set some meta we can use to disambiguate between service instances we added
    43  	// later and are responsible for deregistering.
    44  	if sidecar.Meta != nil {
    45  		// Meta is non-nil validate it before we add the special key so we can
    46  		// enforce that user cannot add a consul- prefix one.
    47  		if err := structs.ValidateMetadata(sidecar.Meta, false); err != nil {
    48  			return nil, nil, "", err
    49  		}
    50  	}
    51  
    52  	// Copy the service metadata from the original service if no other meta was provided
    53  	if len(sidecar.Meta) == 0 && len(ns.Meta) > 0 {
    54  		if sidecar.Meta == nil {
    55  			sidecar.Meta = make(map[string]string)
    56  		}
    57  		for k, v := range ns.Meta {
    58  			sidecar.Meta[k] = v
    59  		}
    60  	}
    61  
    62  	// Copy the tags from the original service if no other tags were specified
    63  	if len(sidecar.Tags) == 0 && len(ns.Tags) > 0 {
    64  		sidecar.Tags = append(sidecar.Tags, ns.Tags...)
    65  	}
    66  
    67  	// Flag this as a sidecar - this is not persisted in catalog but only needed
    68  	// in local agent state to disambiguate lineage when deregistering the parent
    69  	// service later.
    70  	sidecar.LocallyRegisteredAsSidecar = true
    71  
    72  	// See if there is a more specific token for the sidecar registration
    73  	if ns.Connect.SidecarService.Token != "" {
    74  		token = ns.Connect.SidecarService.Token
    75  	}
    76  
    77  	// Setup some sane connect proxy defaults.
    78  	if sidecar.Kind == "" {
    79  		sidecar.Kind = structs.ServiceKindConnectProxy
    80  	}
    81  	if sidecar.Service == "" {
    82  		sidecar.Service = ns.Service + "-sidecar-proxy"
    83  	}
    84  	if sidecar.Address == "" {
    85  		// Inherit address from the service if it's provided
    86  		sidecar.Address = ns.Address
    87  	}
    88  	// Proxy defaults
    89  	if sidecar.Proxy.DestinationServiceName == "" {
    90  		sidecar.Proxy.DestinationServiceName = ns.Service
    91  	}
    92  	if sidecar.Proxy.DestinationServiceID == "" {
    93  		sidecar.Proxy.DestinationServiceID = ns.ID
    94  	}
    95  	if sidecar.Proxy.LocalServiceAddress == "" {
    96  		sidecar.Proxy.LocalServiceAddress = "127.0.0.1"
    97  	}
    98  	if sidecar.Proxy.LocalServicePort < 1 {
    99  		sidecar.Proxy.LocalServicePort = ns.Port
   100  	}
   101  
   102  	// Allocate port if needed (min and max inclusive).
   103  	rangeLen := a.config.ConnectSidecarMaxPort - a.config.ConnectSidecarMinPort + 1
   104  	if sidecar.Port < 1 && a.config.ConnectSidecarMinPort > 0 && rangeLen > 0 {
   105  		// This did pick at random which was simpler but consul reload would assign
   106  		// new ports to all the sidecars since it unloads all state and
   107  		// re-populates. It also made this more difficult to test (have to pin the
   108  		// range to one etc.). Instead we assign sequentially, but rather than N^2
   109  		// lookups, just iterated services once and find the set of used ports in
   110  		// allocation range. We could maintain this state permanently in agent but
   111  		// it doesn't seem to be necessary - even with thousands of services this is
   112  		// not expensive to compute.
   113  		usedPorts := make(map[int]struct{})
   114  		for _, otherNS := range a.State.Services() {
   115  			// Check if other port is in auto-assign range
   116  			if otherNS.Port >= a.config.ConnectSidecarMinPort &&
   117  				otherNS.Port <= a.config.ConnectSidecarMaxPort {
   118  				if otherNS.ID == sidecar.ID {
   119  					// This sidecar is already registered with an auto-port and is just
   120  					// being updated so pick the same port as before rather than allocate
   121  					// a new one.
   122  					sidecar.Port = otherNS.Port
   123  					break
   124  				}
   125  				usedPorts[otherNS.Port] = struct{}{}
   126  			}
   127  			// Note that the proxy might already be registered with a port that was
   128  			// not in the auto range or the auto range has moved. In either case we
   129  			// want to allocate a new one so it's no different from ignoring that it
   130  			// already exists as we do now.
   131  		}
   132  
   133  		// Check we still need to assign a port and didn't find we already had one
   134  		// allocated.
   135  		if sidecar.Port < 1 {
   136  			// Iterate until we find lowest unused port
   137  			for p := a.config.ConnectSidecarMinPort; p <= a.config.ConnectSidecarMaxPort; p++ {
   138  				_, used := usedPorts[p]
   139  				if !used {
   140  					sidecar.Port = p
   141  					break
   142  				}
   143  			}
   144  		}
   145  	}
   146  	// If no ports left (or auto ports disabled) fail
   147  	if sidecar.Port < 1 {
   148  		// If ports are set to zero explicitly, config builder switches them to
   149  		// `-1`. In this case don't show the actual values since we don't know what
   150  		// was actually in config (zero or negative) and it might be confusing, we
   151  		// just know they explicitly disabled auto assignment.
   152  		if a.config.ConnectSidecarMinPort < 1 || a.config.ConnectSidecarMaxPort < 1 {
   153  			return nil, nil, "", fmt.Errorf("no port provided for sidecar_service " +
   154  				"and auto-assignment disabled in config")
   155  		}
   156  		return nil, nil, "", fmt.Errorf("no port provided for sidecar_service and none "+
   157  			"left in the configured range [%d, %d]", a.config.ConnectSidecarMinPort,
   158  			a.config.ConnectSidecarMaxPort)
   159  	}
   160  
   161  	// Setup checks
   162  	checks, err := ns.Connect.SidecarService.CheckTypes()
   163  	if err != nil {
   164  		return nil, nil, "", err
   165  	}
   166  
   167  	// Setup default check if none given
   168  	if len(checks) < 1 {
   169  		checks = []*structs.CheckType{
   170  			&structs.CheckType{
   171  				Name: "Connect Sidecar Listening",
   172  				// Default to localhost rather than agent/service public IP. The checks
   173  				// can always be overridden if a non-loopback IP is needed.
   174  				TCP:      fmt.Sprintf("127.0.0.1:%d", sidecar.Port),
   175  				Interval: 10 * time.Second,
   176  			},
   177  			&structs.CheckType{
   178  				Name:         "Connect Sidecar Aliasing " + ns.ID,
   179  				AliasService: ns.ID,
   180  			},
   181  		}
   182  	}
   183  
   184  	return sidecar, checks, token, nil
   185  }