-
Notifications
You must be signed in to change notification settings - Fork 4.4k
/
sidecar_service.go
200 lines (180 loc) · 7.27 KB
/
sidecar_service.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
package agent
import (
"fmt"
"time"
"github.com/hashicorp/consul/ipaddr"
"github.com/hashicorp/consul/agent/structs"
)
func 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 distinct one.
// TODO: return AddServiceRequest
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 = sidecarServiceID(ns.ID)
// for now at least these must be identical
sidecar.EnterpriseMeta = ns.EnterpriseMeta
// 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.ValidateServiceMetadata(sidecar.Kind, sidecar.Meta, false); err != nil {
return nil, nil, "", err
}
}
// 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...)
}
// Flag this as a sidecar - this is not persisted in catalog but only needed
// in local agent state to disambiguate lineage when deregistering 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
}
// 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
}
}
}
// 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 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.
usedPorts := make(map[int]struct{})
for _, otherNS := range a.State.Services(structs.WildcardEnterpriseMeta()) {
// Check if other port is in auto-assign range
if otherNS.Port >= a.config.ConnectSidecarMinPort &&
otherNS.Port <= a.config.ConnectSidecarMaxPort {
if otherNS.CompoundServiceID() == sidecar.CompoundServiceID() {
// 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-assignment 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{
{
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: ipaddr.FormatAddressPort(sidecar.Proxy.LocalServiceAddress, sidecar.Port),
Interval: 10 * time.Second,
},
{
Name: "Connect Sidecar Aliasing " + ns.ID,
AliasService: ns.ID,
},
}
}
return sidecar, checks, token, nil
}