@@ -28,6 +28,12 @@ const (
28
28
ServiceNameSeparator = ":"
29
29
)
30
30
31
+ // EnvoyResources is a collection of Enovy API resource definitions
32
+ type EnvoyResources struct {
33
+ Clusters []cache.Resource
34
+ Listeners []cache.Resource
35
+ }
36
+
31
37
// SvcName formats an Envoy service name from our service name and port
32
38
func SvcName (name string , port int64 ) string {
33
39
return fmt .Sprintf ("%s%s%d" , name , ServiceNameSeparator , port )
@@ -61,57 +67,94 @@ func LookupHost(hostname string) (string, error) {
61
67
return addrs [0 ], nil
62
68
}
63
69
64
- // EnvoyListenersFromState creates a set of Enovy API listener
65
- // definitions from all the ServicePorts in the Sidecar state.
66
- func EnvoyListenersFromState (state * catalog.ServicesState , bindIP string ) ([]cache.Resource , error ) {
67
- var listeners []cache.Resource
68
-
69
- state .RLock ()
70
- defer state .RUnlock ()
71
-
72
- svcs := state .ByService ()
73
- // Loop over all the services by service name
74
- for _ , endpoints := range svcs {
75
- if len (endpoints ) < 1 {
76
- continue
77
- }
70
+ // EnvoyResourcesFromState creates a set of Enovy API resource definitions from all
71
+ // the ServicePorts in the Sidecar state. The Sidecar state needs to be locked by the
72
+ // caller before calling this function.
73
+ func EnvoyResourcesFromState (state * catalog.ServicesState , bindIP string ,
74
+ useHostnames bool ) EnvoyResources {
78
75
79
- var svc * service.Service
80
- // Find the first alive service and use that as the definition.
81
- // If none are alive, we won't open the port.
82
- for _ , endpoint := range endpoints {
83
- if endpoint .IsAlive () {
84
- svc = endpoint
85
- break
86
- }
87
- }
76
+ clusterMap := make (map [string ]* api.Cluster )
77
+ listenerMap := make (map [string ]cache.Resource )
88
78
89
- if svc == nil {
90
- continue
79
+ state .EachService (func (hostname * string , id * string , svc * service.Service ) {
80
+ if svc == nil || ! svc .IsAlive () {
81
+ return
91
82
}
92
83
93
- // Loop over the ports and generate a named listener for
94
- // each port.
84
+ // Loop over the ports and generate a named listener for each port
95
85
for _ , port := range svc .Ports {
96
86
// Only listen on ServicePorts
97
87
if port .ServicePort < 1 {
98
88
continue
99
89
}
100
90
101
- listener , err := EnvoyListenerFromService (svc , port .ServicePort , bindIP )
102
- if err != nil {
103
- return nil , fmt .Errorf ("failed to create listener from service: %s" , err )
91
+ envoyServiceName := SvcName (svc .Name , port .ServicePort )
92
+
93
+ if cluster , ok := clusterMap [envoyServiceName ]; ok {
94
+ cluster .LoadAssignment .Endpoints [0 ].LbEndpoints =
95
+ append (cluster .LoadAssignment .Endpoints [0 ].LbEndpoints ,
96
+ envoyServiceFromService (svc , port .ServicePort , useHostnames )... )
97
+ } else {
98
+ envoyCluster := & api.Cluster {
99
+ Name : envoyServiceName ,
100
+ ConnectTimeout : & duration.Duration {Nanos : 500000000 }, // 500ms
101
+ ClusterDiscoveryType : & api.Cluster_Type {Type : api .Cluster_STATIC }, // Use IPs only
102
+ ProtocolSelection : api .Cluster_USE_CONFIGURED_PROTOCOL ,
103
+ // Setting the endpoints here directly bypasses EDS, so we can
104
+ // avoid having to configure that as well
105
+ // Note that in `EnvoyClustersFromState()` for the REST API we only need
106
+ // the first non-nil alive endpoint instance to construct the cluster
107
+ // because, in that case, SDS (now EDS) fetches the actual endpoints in a
108
+ // separate call.
109
+ LoadAssignment : & api.ClusterLoadAssignment {
110
+ ClusterName : envoyServiceName ,
111
+ Endpoints : []* endpoint.LocalityLbEndpoints {{
112
+ LbEndpoints : envoyServiceFromService (svc , port .ServicePort , useHostnames ),
113
+ }},
114
+ },
115
+ // Contour believes the IdleTimeout should be set to 60s. Not sure if we also need to enable these.
116
+ // See here: https://github.com/projectcontour/contour/blob/2858fec20d26f56cc75a19d91b61d625a86f36de/internal/envoy/listener.go#L102-L106
117
+ // CommonHttpProtocolOptions: &core.HttpProtocolOptions{
118
+ // IdleTimeout: &duration.Duration{Seconds: 60},
119
+ // MaxConnectionDuration: &duration.Duration{Seconds: 60},
120
+ // },
121
+ // If this needs to be enabled, we might also need to set `ProtocolSelection: api.USE_DOWNSTREAM_PROTOCOL`.
122
+ // Http2ProtocolOptions: &core.Http2ProtocolOptions{},
123
+ }
124
+
125
+ clusterMap [envoyServiceName ] = envoyCluster
126
+ }
127
+
128
+ if _ , ok := listenerMap [envoyServiceName ]; ! ok {
129
+ listener , err := envoyListenerFromService (svc , envoyServiceName , port .ServicePort , bindIP )
130
+ if err != nil {
131
+ log .Errorf ("Failed to create Envoy listener for service %q and port %d: %s" , svc .Name , port .ServicePort , err )
132
+ continue
133
+ }
134
+ listenerMap [envoyServiceName ] = listener
104
135
}
105
- listeners = append (listeners , listener )
106
136
}
137
+ })
138
+
139
+ clusters := make ([]cache.Resource , 0 , len (clusterMap ))
140
+ for _ , cluster := range clusterMap {
141
+ clusters = append (clusters , cluster )
142
+ }
143
+
144
+ listeners := make ([]cache.Resource , 0 , len (listenerMap ))
145
+ for _ , listener := range listenerMap {
146
+ listeners = append (listeners , listener )
107
147
}
108
148
109
- return listeners , nil
149
+ return EnvoyResources {
150
+ Clusters : clusters ,
151
+ Listeners : listeners ,
152
+ }
110
153
}
111
154
112
- // EnvoyListenerFromService creates an Envoy listener from a service instance
113
- func EnvoyListenerFromService (svc * service.Service , port int64 , bindIP string ) (cache. Resource , error ) {
114
- apiName := SvcName ( svc . Name , port )
155
+ // envoyListenerFromService creates an Envoy listener from a service instance
156
+ func envoyListenerFromService (svc * service.Service , envoyServiceName string ,
157
+ servicePort int64 , bindIP string ) (cache. Resource , error ) {
115
158
116
159
var connectionManagerName string
117
160
var connectionManager proto.Message
@@ -127,7 +170,7 @@ func EnvoyListenerFromService(svc *service.Service, port int64, bindIP string) (
127
170
RouteSpecifier : & hcm.HttpConnectionManager_RouteConfig {
128
171
RouteConfig : & api.RouteConfiguration {
129
172
VirtualHosts : []* route.VirtualHost {{
130
- Name : apiName ,
173
+ Name : envoyServiceName ,
131
174
Domains : []string {"*" },
132
175
Routes : []* route.Route {{
133
176
Match : & route.RouteMatch {
@@ -138,7 +181,7 @@ func EnvoyListenerFromService(svc *service.Service, port int64, bindIP string) (
138
181
Action : & route.Route_Route {
139
182
Route : & route.RouteAction {
140
183
ClusterSpecifier : & route.RouteAction_Cluster {
141
- Cluster : apiName ,
184
+ Cluster : envoyServiceName ,
142
185
},
143
186
Timeout : & duration.Duration {},
144
187
},
@@ -154,7 +197,7 @@ func EnvoyListenerFromService(svc *service.Service, port int64, bindIP string) (
154
197
connectionManager = & tcpp.TcpProxy {
155
198
StatPrefix : "ingress_tcp" ,
156
199
ClusterSpecifier : & tcpp.TcpProxy_Cluster {
157
- Cluster : apiName ,
200
+ Cluster : envoyServiceName ,
158
201
},
159
202
}
160
203
default :
@@ -173,7 +216,7 @@ func EnvoyListenerFromService(svc *service.Service, port int64, bindIP string) (
173
216
SocketAddress : & core.SocketAddress {
174
217
Address : bindIP ,
175
218
PortSpecifier : & core.SocketAddress_PortValue {
176
- PortValue : uint32 (port ),
219
+ PortValue : uint32 (servicePort ),
177
220
},
178
221
},
179
222
},
@@ -189,80 +232,8 @@ func EnvoyListenerFromService(svc *service.Service, port int64, bindIP string) (
189
232
}, nil
190
233
}
191
234
192
- // EnvoyClustersFromState genenerates a list of Envoy clusters from the
193
- // current Sidecar state
194
- func EnvoyClustersFromState (state * catalog.ServicesState , useHostnames bool ) []cache.Resource {
195
- state .RLock ()
196
- defer state .RUnlock ()
197
-
198
- // `s.state.ByService()` returns the list of service endpoints for each service.
199
- // Since some services can expose multiple service ports, we need to create a
200
- // separate cluster for each (service, servicePort) pair. If a service doesn't
201
- // have any endpoints that are alive, we don't want to create a cluster for it.
202
- //
203
- // Note that in `EnvoyClustersFromState()` for the REST API we only need
204
- // the first non-nil alive endpoint instance to construct the cluster
205
- // because, in that case, SDS (now EDS) fetches the actual endpoints in a
206
- // separate call.
207
- var clusters []cache.Resource
208
- clustersMap := make (map [string ]* api.Cluster )
209
- for svcName , svcEndpoints := range state .ByService () {
210
- if len (svcEndpoints ) < 1 {
211
- continue
212
- }
213
-
214
- for _ , svcEndpoint := range svcEndpoints {
215
- if svcEndpoint == nil || ! svcEndpoint .IsAlive () {
216
- continue
217
- }
218
-
219
- for _ , port := range svcEndpoint .Ports {
220
- if port .ServicePort < 1 {
221
- continue
222
- }
223
-
224
- envoyServiceName := SvcName (svcName , port .ServicePort )
225
-
226
- if cluster , ok := clustersMap [envoyServiceName ]; ok {
227
- cluster .LoadAssignment .Endpoints [0 ].LbEndpoints =
228
- append (cluster .LoadAssignment .Endpoints [0 ].LbEndpoints ,
229
- envoyServiceFromService (svcEndpoint , port .ServicePort , useHostnames )... )
230
- } else {
231
- envoyCluster := & api.Cluster {
232
- Name : envoyServiceName ,
233
- ConnectTimeout : & duration.Duration {Nanos : 500000000 }, // 500ms
234
- ClusterDiscoveryType : & api.Cluster_Type {Type : api .Cluster_STATIC }, // Use IPs only
235
- ProtocolSelection : api .Cluster_USE_CONFIGURED_PROTOCOL ,
236
- // Setting the endpoints here directly bypasses EDS, so we can
237
- // avoid having to configure that as well
238
- LoadAssignment : & api.ClusterLoadAssignment {
239
- ClusterName : envoyServiceName ,
240
- Endpoints : []* endpoint.LocalityLbEndpoints {{
241
- LbEndpoints : envoyServiceFromService (svcEndpoint , port .ServicePort , useHostnames ),
242
- }},
243
- },
244
- // Contour believes the IdleTimeout should be set to 60s. Not sure if we also need to enable these.
245
- // See here: https://github.com/projectcontour/contour/blob/2858fec20d26f56cc75a19d91b61d625a86f36de/internal/envoy/listener.go#L102-L106
246
- // CommonHttpProtocolOptions: &core.HttpProtocolOptions{
247
- // IdleTimeout: &duration.Duration{Seconds: 60},
248
- // MaxConnectionDuration: &duration.Duration{Seconds: 60},
249
- // },
250
- // If this needs to be enabled, we might also need to set `ProtocolSelection: api.USE_DOWNSTREAM_PROTOCOL`.
251
- // Http2ProtocolOptions: &core.Http2ProtocolOptions{},
252
- }
253
-
254
- clustersMap [envoyServiceName ] = envoyCluster
255
- clusters = append (clusters , envoyCluster )
256
- }
257
- }
258
- }
259
- }
260
-
261
- return clusters
262
- }
263
-
264
- // envoyServiceFromService converts a Sidecar service to an Envoy
265
- // API service for reporting to the proxy
235
+ // envoyServiceFromService converts a Sidecar service to an Envoy API service for
236
+ // reporting to the proxy
266
237
func envoyServiceFromService (svc * service.Service , svcPort int64 , useHostnames bool ) []* endpoint.LbEndpoint {
267
238
var endpoints []* endpoint.LbEndpoint
268
239
for _ , port := range svc .Ports {
0 commit comments