github.com/outbrain/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 }