@@ -2,168 +2,34 @@ local com = import 'lib/commodore.libjsonnet';
2
2
local kap = import 'lib/kapitan.libjsonnet' ;
3
3
local kube = import 'lib/kube.libjsonnet' ;
4
4
5
+ local egw = import 'espejote-templates/egress-gateway.libsonnet' ;
6
+
5
7
local inv = kap.inventory();
6
8
local params = inv.parameters.cilium;
7
9
8
- local CiliumEgressGatewayPolicy(name) =
9
- kube._Object('cilium.io/v2' , 'CiliumEgressGatewayPolicy' , name) {
10
- metadata+: {
11
- annotations+: {
12
- 'argocd.argoproj.io/sync-options' : 'SkipDryRunOnMissingResource=true,Prune=false' ,
13
- },
14
- },
15
- };
16
-
17
- local IsovalentEgressGatewayPolicy(name) =
18
- kube._Object('isovalent.com/v1' , 'IsovalentEgressGatewayPolicy' , name) {
19
- metadata+: {
20
- annotations+: {
21
- 'argocd.argoproj.io/sync-options' : 'SkipDryRunOnMissingResource=true' ,
22
- },
23
- },
24
- };
25
-
26
10
local EgressGatewayPolicy(name) =
27
11
if params.release == 'enterprise' then
28
- IsovalentEgressGatewayPolicy(name)
12
+ egw. IsovalentEgressGatewayPolicy(name)
29
13
else
30
- CiliumEgressGatewayPolicy(name);
14
+ egw. CiliumEgressGatewayPolicy(name);
31
15
32
16
local policies = com.generateResources(
33
17
params.egress_gateway.policies,
34
18
EgressGatewayPolicy
35
19
);
36
20
37
- // Convert an IPv4 address in A.B.C.D format that's already been split into an
38
- // array to decimal format according to the formula `A*256^3 + B*256^2 + C*256
39
- // + D`. The decimal format allows us to make range comparisons and compute
40
- // offsets into a range.
41
- // Parameter ip can either be the IP as a string, or already split into an
42
- // array holding each dotted part.
43
- local ipval(ip) =
44
- local iparr =
45
- if std.type (ip) == 'array' then
46
- ip
47
- else
48
- std.split (ip, '.' );
49
- std.foldl (
50
- function (v, p) v * 256 + p,
51
- std.map (std.parseInt , iparr),
52
- 0
53
- );
54
-
55
- // Extract start and end from the provided range, stripping any
56
- // whitespace. `prefix` is only used for the error message.
57
- local parse_ip_range(prefix, rangespec) =
58
- local range_parts = std.map (
59
- function (s) std.stripChars(s, ' ' ),
60
- std.split (rangespec, '-' )
61
- );
62
- if std.length (range_parts) != 2 then
63
- error 'Expected IP range for "%s" in format "192.0.2.32-192.0.2.63", got %s' % [
64
- prefix,
65
- rangespec,
66
- ]
67
- else
68
- {
69
- start: range_parts[0 ],
70
- end: range_parts[1 ],
71
- };
72
-
73
-
74
- // Per-namespace egress IPs according to the selected design choice in
75
- // https://kb.vshn.ch/oc4/explanations/decisions/cloudscale-cilium-egressip.html
76
- // Requires that the shadow IPs are assigned to suitable dummy interfaces on
77
- // the hosts matching the node selector and that SNAT rules are in place to
78
- // map the shadow ranges to the public range.
79
- local NamespaceEgressPolicy =
80
- function (interface_prefix, egress_range, node_selector, egress_ip, namespace)
81
- // Helper which computes the interface index of the egress IP.
82
- // Assumes that the IPs in egress_range are assigned to dummy interfaces
83
- // named
84
- //
85
- // "<interface_prefix>_<i>"
86
- //
87
- // where i = 0..length(egress_range) - 1.
88
- local ifindex =
89
- local range = parse_ip_range(interface_prefix, egress_range);
90
- local start = ipval(range.start);
91
- local end = ipval(range.end);
92
- local ip = ipval(egress_ip);
93
- if start > end then
94
- error 'Egress IP range for "%s" is empty: %s > %s' % [
95
- interface_prefix,
96
- range.start,
97
- range.end,
98
- ]
99
- else if start > ip || end < ip then
100
- error 'Egress IP for namespace "%s" (%s) outside of configured IP range (%s) for egress range "%s"' % [
101
- namespace,
102
- egress_ip,
103
- egress_range,
104
- interface_prefix,
105
- ]
106
- else
107
- local idx = ip - start;
108
- local name = '%s_%d' % [ interface_prefix, idx ];
109
- if std.length (name) > 15 then
110
- error 'Interface name is longer than 15 characters: %s' % [ name ]
111
- else
112
- {
113
- value: idx,
114
- ifname: '%s_%d' % [ interface_prefix, idx ],
115
- debug: 'start=%d, end=%d, ip=%d' % [ start, end, ip ],
116
- };
117
-
118
- EgressGatewayPolicy(namespace) {
119
- metadata+: {
120
- annotations+: {
121
- 'cilium.syn.tools/description' :
122
- 'Generated policy to assign egress IP %s in egress range "%s" (%s) to namespace %s.' % [
123
- egress_ip,
124
- interface_prefix,
125
- egress_range,
126
- namespace,
127
- ],
128
- 'cilium.syn.tools/egress-ip' : egress_ip,
129
- 'cilium.syn.tools/interface-prefix' : interface_prefix,
130
- 'cilium.syn.tools/egress-range' : egress_range,
131
- 'cilium.syn.tools/source-namespace' : namespace,
132
- 'cilium.syn.tools/debug-interface-index' : ifindex.debug,
133
- },
134
- },
135
- spec: {
136
- destinationCIDRs: [ '0.0.0.0/0' ],
137
- egressGroups: [
138
- {
139
- nodeSelector: {
140
- matchLabels: node_selector,
141
- },
142
- interface: ifindex.ifname,
143
- },
144
- ],
145
- selectors: [
146
- {
147
- podSelector: {
148
- matchLabels: {
149
- 'io.kubernetes.pod.namespace' : namespace,
150
- },
151
- },
152
- },
153
- ],
154
- },
155
- };
156
-
157
21
local egress_ip_policies = std.flattenArrays ([
158
22
local cfg = params.egress_gateway.egress_ip_ranges[interface_prefix];
159
23
local ns_egress_ips = std.get(cfg, 'namespace_egress_ips' , {});
160
24
[
161
- NamespaceEgressPolicy(
25
+ egw. NamespaceEgressPolicy(
162
26
interface_prefix,
163
27
cfg.egress_range,
28
+ std.objectValues(std.get(cfg, 'shadow_ranges' , [])),
164
29
cfg.node_selector,
165
30
ns_egress_ips[namespace],
166
31
namespace,
32
+ EgressGatewayPolicy,
167
33
)
168
34
for namespace in std.objectFields (ns_egress_ips)
169
35
if ns_egress_ips[namespace] != null
@@ -172,184 +38,6 @@ local egress_ip_policies = std.flattenArrays([
172
38
if params.egress_gateway.egress_ip_ranges[interface_prefix] != null
173
39
]);
174
40
175
- // NOTE(sg): This expects that each shadow range fully fits into a /24.
176
- local egress_ip_shadow_ranges =
177
- // Helper to extract the /24 prefix of the IP range passed as `range`. The
178
- // function raises an error if the provided range spans multiple /24.
179
- local extract_prefix(prefix, hostname, range) =
180
- // find <start_prefix>.0
181
- local start0 = ipval(std.mapWithIndex (
182
- function (idx, elem)
183
- if idx < 3 then elem else '0' ,
184
- std.split (range.start, '.' ),
185
- ));
186
- // find <end_prefix>.255
187
- local end255 = ipval(std.mapWithIndex (
188
- function (idx, elem)
189
- if idx < 3 then elem else '255' ,
190
- std.split (range.end, '.' ),
191
- ));
192
- if end255 - start0 + 1 > 256 then
193
- error "Shadow range %s-%s for '%s' in '%s' spans multiple /24. This isn't currently supported." % [
194
- range.start,
195
- range.end,
196
- hostname,
197
- prefix,
198
- ]
199
- else
200
- // extract the /24 prefix from `range.start` now that we know that the
201
- // range fits into a single /24.
202
- std.join ('.' , std.split (range.start, '.' )[0 :3 ]);
203
-
204
- local check_length(hostname, egress_range, range) =
205
- local public_range = parse_ip_range(
206
- egress_range.prefix,
207
- egress_range.config.egress_range
208
- );
209
- local public_len = ipval(public_range.end) - ipval(public_range.start);
210
- local shadow_len = ipval(range.end) - ipval(range.start);
211
-
212
- if public_len != shadow_len then
213
- error "Shadow IP range %s-%s for '%s' in '%s' doesn't match length of egress IP range %s" % [
214
- range.start,
215
- range.end,
216
- hostname,
217
- egress_range.prefix,
218
- egress_range.config.egress_range,
219
- ]
220
- else
221
- range;
222
-
223
-
224
- // Transform egress_ip_ranges.<range>.shadow_ranges into the format expected
225
- // by the systemd service (and script) managed in component
226
- // openshift4-nodes.
227
- local config = std.foldl (
228
- // Collect egress interface IP ranges by node. This object can be used to
229
- // generate the configmap that openshift4-nodes expects.
230
- function (data, egress_range)
231
- data {
232
- [hostname]+: {
233
- local range = check_length(
234
- hostname,
235
- egress_range,
236
- parse_ip_range(
237
- egress_range.prefix,
238
- egress_range.config.shadow_ranges[hostname]
239
- )
240
- ),
241
- [egress_range.prefix]:
242
- {
243
- base: extract_prefix(egress_range.prefix, hostname, range),
244
- from: std.split (range.start, '.' )[3 ],
245
- to: std.split (range.end, '.' )[3 ],
246
- },
247
- }
248
- for hostname in std.objectFields (egress_range.config.shadow_ranges)
249
- },
250
- // transform egress_ip_ranges object into a list of key-value pair
251
- // objects, so we can more easily implement the transformation.
252
- [
253
- local data = params.egress_gateway.egress_ip_ranges[interface_prefix];
254
- {
255
- prefix: interface_prefix,
256
- config: data,
257
- }
258
- for interface_prefix in std.objectFields (params.egress_gateway.egress_ip_ranges)
259
- if params.egress_gateway.egress_ip_ranges[interface_prefix] != null
260
- && std.objectHas (params.egress_gateway.egress_ip_ranges[interface_prefix], 'shadow_ranges' )
261
- && params.egress_gateway.egress_ip_ranges[interface_prefix].shadow_ranges != null
262
- ],
263
- {}
264
- );
265
-
266
- // generate 1 configmap for all egress ranges.
267
- local configmap =
268
- kube.ConfigMap('eip-shadow-ranges' ) {
269
- data: {
270
- [hostname]: std.manifestJsonMinified(config[hostname])
271
- for hostname in std.objectFields (config)
272
- },
273
- };
274
-
275
- // Generate 1 daemonset per unique node selector across all configured
276
- // egress ranges. The daemonset's purpose is to make the configmap available
277
- // to the kubelet on the node, so that we can use the Kubelet kubeconfig for
278
- // the script managed by openshift4-nodes.
279
- local daemonset_configs = std.foldl (
280
- function (dses, d) dses + d,
281
- [
282
- local sel = params.egress_gateway.egress_ip_ranges[interface_prefix].node_selector;
283
- local sel_hash = std.md5(std.manifestJsonMinified(sel));
284
- { [sel_hash]+: sel }
285
- for interface_prefix in std.objectFields (params.egress_gateway.egress_ip_ranges)
286
- if params.egress_gateway.egress_ip_ranges[interface_prefix] != null
287
- ],
288
- {}
289
- );
290
-
291
- local make_daemonset(ds_configs, sel_hash) =
292
- kube.DaemonSet(
293
- 'eip-shadow-ranges-%s' % std.substr (
294
- sel_hash, std.length (sel_hash) - 5 , 5
295
- )
296
- ) {
297
- metadata+: {
298
- annotations+: {
299
- 'cilium.syn.tools/description' :
300
- 'Daemonset which ensures that the Kubelet on the nodes where the'
301
- + ' pods are scheduled can access configmap %s in namespace %s.' %
302
- [
303
- configmap.metadata.name,
304
- params._namespace,
305
- ],
306
- },
307
- },
308
- spec+: {
309
- template+: {
310
- spec+: {
311
- containers_: {
312
- sleep: kube.Container('sleep' ) {
313
- image: '%(registry)s/%(image)s:%(tag)s' % params.images.kubectl,
314
- command: [ '/bin/sh' , '-c' , 'trap : TERM INT; sleep infinity & wait' ],
315
- volumeMounts_: {
316
- shadow_ranges: {
317
- mountPath: '/data/eip-shadow-ranges' ,
318
- },
319
- },
320
- },
321
- },
322
- nodeSelector: ds_configs[sel_hash],
323
- volumes_: {
324
- shadow_ranges: {
325
- configMap: {
326
- name: configmap.metadata.name,
327
- },
328
- },
329
- },
330
- },
331
- },
332
- },
333
- };
334
-
335
- local daemonsets =
336
- if std.length (params.egress_gateway.shadow_ranges_daemonset_node_selector) == 0 then [
337
- make_daemonset(daemonset_configs, sel_hash)
338
- for sel_hash in std.objectFields (daemonset_configs)
339
- ] else
340
- local sel_hash =
341
- std.md5(std.manifestJsonMinified(
342
- params.egress_gateway.shadow_ranges_daemonset_node_selector
343
- ));
344
- [
345
- make_daemonset({
346
- [sel_hash]:
347
- params.egress_gateway.shadow_ranges_daemonset_node_selector,
348
- }, sel_hash),
349
- ];
350
-
351
- [ configmap ] + daemonsets;
352
-
353
41
// Check for duplicated source namespaces in the provided list of policies
354
42
// Internal accumulator is an object which uses the source namespace as key
355
43
// and contains the full policies as values. The function returns the values
@@ -372,13 +60,19 @@ local validate(policies) = std.objectValues(std.foldl(
372
60
{}
373
61
));
374
62
63
+ local shadow_ranges = import 'egress-gateway-shadow-ranges.libsonnet' ;
64
+ local self_service = import 'egress-gateway-self-service.libsonnet' ;
65
+
375
66
{
376
67
[if params.egress_gateway.enabled && std.length (params.egress_gateway.policies) > 0 then
377
68
'20_egress_gateway_policies' ]: policies,
378
69
[if params.egress_gateway.enabled && std.length (egress_ip_policies) > 0 then
379
70
'20_namespace_egress_ip_policies' ]: validate(egress_ip_policies),
380
71
[if params.egress_gateway.enabled &&
381
72
params.egress_gateway.generate_shadow_ranges_configmap &&
382
- std.length (egress_ip_shadow_ranges) > 0 then
383
- '30_egress_ip_shadow_ranges' ]: egress_ip_shadow_ranges,
73
+ std.length (shadow_ranges.manifests) > 0 then
74
+ '30_egress_ip_shadow_ranges' ]: shadow_ranges.manifests,
75
+ [if params.egress_gateway.enabled &&
76
+ params.egress_gateway.self_service_namespace_ips then
77
+ '40_egress_ip_managed_resource' ]: self_service.manifests,
384
78
}
0 commit comments