blob: a6cf9dd98feffc5b0db3193d1156ea15f17a2e8f [file] [log] [blame]
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +01001local kube = import "../../../kube/kube.libsonnet";
2
3{
radexc995c212023-11-24 12:01:49 +01004 local top = self,
5 local cfg = top.cfg,
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +01006 cfg:: {
7 image: error "cfg.image needs to be set",
8 storageClassName: error "cfg.storrageClassName needs to be set",
9
10 # webDomain is the domain name at which synapse instance will run
11 webDomain: error "cfg.webDomain must be set",
12 # serverName is the server part of the MXID this homeserver will cover
13 serverName: error "cfg.serverName must be set",
14
15 cas: { enable: false },
16 oidc: { enable: false },
17
Piotr Dobrowolski529e1812021-02-13 19:44:37 +010018 appserviceWorker: false,
19 federationWorker: false,
20
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +010021 macaroonSecretKey: error "cfg.macaroonSecretKey needs to be set",
22 registrationSharedSecret: error "cfg.registationSharedSecret needs to be set",
23 workerReplicationSecret: error "cfg.workerReplicationSecret needs to be set",
24 },
25
26 ns:: error "ns needs to be provided",
radexc995c212023-11-24 12:01:49 +010027 local ns = top.ns,
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +010028 postgres:: error "postgres needs to be provided",
29 redis:: error "redis needs to be provided",
30
31 // See matrix-ng.libsonnet for description
32 appservices:: error "appservices need to be provided",
33
radex99ed6a72023-11-24 11:42:55 +010034 dataVolume: ns.Contain(kube.PersistentVolumeClaim("synapse-data-waw3")) {
radex36964dc2023-11-24 11:19:46 +010035 storage:: "50Gi",
36 storageClass:: cfg.storageClassName,
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +010037 },
38
39 // homeserver.yaml that will be used to run synapse (in configMap).
40 // This is based off of //app/matrix/lib/synapse/homeserver.yaml with some fields overriden per
41 // deployment.
42 config:: (std.native("parseYaml"))(importstr "synapse/homeserver-ng.yaml")[0] {
43 server_name: cfg.serverName,
44 public_baseurl: "https://%s" % [cfg.webDomain],
45 signing_key_path: "/secrets/homeserver_signing_key",
46 app_service_config_files: [
47 "/appservices/%s/registration.yaml" % [k]
radexc995c212023-11-24 12:01:49 +010048 for k in std.objectFields(top.appservices)
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +010049 ],
Piotr Dobrowolski529e1812021-02-13 19:44:37 +010050
51 notify_appservices: cfg.appserviceWorker == false,
52
53 # FIXME(informatic) Rolling out with federationWorkers = true breaks
54 # *some* federation, needs investigation...
55 #send_federation: cfg.federationWorker == false,
56 #federation_sender_instances: if cfg.federationWorker then [
radexc995c212023-11-24 12:01:49 +010057 # "%s-%s" % [top.federationSenderWorker.deployment.metadata.name, idx]
58 # for idx in std.range(0, top.federationSenderWorker.deployment.spec.replicas)
Piotr Dobrowolski529e1812021-02-13 19:44:37 +010059 #] else [],
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +010060 } + (if cfg.cas.enable then {
61 cas_config: {
62 enabled: true,
63 server_url: "https://%s/_cas" % [cfg.webDomain],
64 service_url: "https://%s" % [cfg.webDomain],
65 },
Piotr Dobrowolski690ed452022-05-07 11:27:24 +020066 } else {}) + (if cfg.coturn.enable then {
67 turn_uris: [ "turn:%s?transport=udp" % cfg.coturn.config.domain, "turn:%s?transport=tcp" % cfg.coturn.config.domain ],
68
69 # Lifetime of single TURN user credentials - 1 day, recommended by TURN REST
70 # spec, see https://datatracker.ietf.org/doc/html/draft-uberti-behave-turn-rest-00#section-2.2
71 turn_user_lifetime: 24 * 60 * 60 * 1000,
72 turn_allow_guests: true,
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +010073 } else {}),
74
radex99ed6a72023-11-24 11:42:55 +010075 configMap: ns.Contain(kube.ConfigMap("synapse")) {
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +010076 data: {
radexc995c212023-11-24 12:01:49 +010077 "homeserver.yaml": std.manifestYamlDoc(top.config),
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +010078 "log.config": importstr "synapse/log.config",
79 },
80 },
81
82 // homeserver-secrets.yaml contains all the templated secret variables from
83 // base homeserver.yaml passed as yaml-encoded environment variable.
84 // $(ENVVAR)-encoded variables are resolved by Kubernetes on pod startup
85 secretsConfig:: (std.native("parseYaml"))(importstr "synapse/homeserver-secrets.yaml")[0] {
86 } + (if cfg.oidc.enable then {
87 oidc_config: cfg.oidc.config {
88 enabled: true,
89 client_secret: "$(OIDC_CLIENT_SECRET)",
90 },
Piotr Dobrowolski690ed452022-05-07 11:27:24 +020091 } else {}) + (if cfg.coturn.enable then {
92 turn_shared_secret: "$(TURN_SHARED_SECRET)",
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +010093 } else {}),
94
95 # Synapse process Deployment/StatefulSet base resource.
radex99ed6a72023-11-24 11:42:55 +010096 SynapseWorker(name, workerType, builder):: ns.Contain(builder(name)) {
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +010097 local worker = self,
98 cfg:: {
99 # Configuration customization. Can contain environment substitution
100 # syntax, as used in worker_name value.
101 localConfig: {
102 worker_app: workerType,
103 worker_name: "$(POD_NAME)",
104
105 # The replication listener on the main synapse process.
106 worker_replication_host: "synapse-replication-master",
107 worker_replication_http_port: 9093,
108 },
109
110 # Mount app.dataVolume in /data
111 mountData: false,
Piotr Dobrowolskia8bb6152022-09-13 20:37:33 +0200112
113 resources: {
Serge Bazanskif2628682023-03-26 21:56:09 +0200114 requests: { cpu: "300m", memory: "2Gi" },
Piotr Dobrowolskia8bb6152022-09-13 20:37:33 +0200115 limits: { cpu: "1500m", memory: "2Gi" },
116 },
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100117 },
118
119 spec+: {
120 replicas: 1,
121 template+: {
122 spec+: {
123 volumes_: {
radexc995c212023-11-24 12:01:49 +0100124 config: kube.ConfigMapVolume(top.configMap),
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100125 secrets: { secret: { secretName: "synapse" } },
126 } + {
127 [k]: { secret: { secretName: "appservice-%s-registration" % [k] } }
radexc995c212023-11-24 12:01:49 +0100128 for k in std.objectFields(top.appservices)
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100129 } + if worker.cfg.mountData then {
radexc995c212023-11-24 12:01:49 +0100130 data: kube.PersistentVolumeClaimVolume(top.dataVolume),
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100131 } else {},
132 containers_: {
133 web: kube.Container("synapse") {
134 image: cfg.image,
135 command: [
136 "/bin/sh", "-c", |||
137 set -e
138 echo "${X_SECRETS_CONFIG}" > /tmp/secrets.yaml
139 echo "${X_LOCAL_CONFIG}" > /tmp/local.yaml
140 exec python -m ${SYNAPSE_WORKER} --config-path /conf/homeserver.yaml --config-path /tmp/secrets.yaml --config-path /tmp/local.yaml
141 |||
142 ],
Piotr Dobrowolskia8bb6152022-09-13 20:37:33 +0200143 resources: worker.cfg.resources,
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100144 ports_: {
145 http: { containerPort: 8008 },
146 metrics: { containerPort: 9092 },
147 replication: { containerPort: 9093 },
148 },
149 env_: {
150 SYNAPSE_WORKER: workerType,
151
152 SYNAPSE_MACAROON_SECRET_KEY: cfg.macaroonSecretKey,
153 SYNAPSE_REGISTRATION_SHARED_SECRET: cfg.registrationSharedSecret,
154 WORKER_REPLICATION_SECRET: cfg.workerReplicationSecret,
Serge Bazanskif2628682023-03-26 21:56:09 +0200155
radexc995c212023-11-24 12:01:49 +0100156 POSTGRES_PASSWORD: top.postgres.cfg.password,
157 POSTGRES_USER: top.postgres.cfg.username,
158 POSTGRES_DB: top.postgres.cfg.database,
159 POSTGRES_HOST: top.postgres.cfg.host,
160 POSTGRES_PORT: top.postgres.cfg.port,
Serge Bazanskif2628682023-03-26 21:56:09 +0200161
radexc995c212023-11-24 12:01:49 +0100162 REDIS_PASSWORD: top.redis.cfg.password,
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100163 POD_NAME: { fieldRef: { fieldPath: "metadata.name" } },
164 OIDC_CLIENT_SECRET: if cfg.oidc.enable then cfg.oidc.config.client_secret else "",
Piotr Dobrowolski690ed452022-05-07 11:27:24 +0200165 TURN_SHARED_SECRET: if cfg.coturn.enable then cfg.coturn.config.authSecret else "",
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100166
radexc995c212023-11-24 12:01:49 +0100167 X_SECRETS_CONFIG: std.manifestYamlDoc(top.secretsConfig),
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100168 X_LOCAL_CONFIG: std.manifestYamlDoc(worker.cfg.localConfig),
169 },
170 volumeMounts_: {
171 config: { mountPath: "/conf", },
172 secrets: { mountPath: "/secrets" },
173 } + {
174 [k]: { mountPath: "/appservices/%s" % [k] }
radexc995c212023-11-24 12:01:49 +0100175 for k in std.objectFields(top.appservices)
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100176 } + if worker.cfg.mountData then {
177 data: { mountPath: "/data" },
178 } else {},
Piotr Dobrowolski77af94d2021-09-16 22:17:58 +0200179 readinessProbe: {
180 httpGet: {
181 path: "/health",
182 port: "http",
183 },
184 initialDelaySeconds: 5,
185 periodSeconds: 10,
186 },
187 livenessProbe: {
188 httpGet: {
189 path: "/health",
190 port: "http",
191 },
192 initialDelaySeconds: 60,
193 periodSeconds: 30,
194 },
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100195 },
196 },
197 securityContext: {
198 runAsUser: 991,
199 runAsGroup: 991,
200 fsGroup: 991,
201 },
202 },
203 },
204 },
205 },
206
207 # Synapse main process
208 main: {
radexc995c212023-11-24 12:01:49 +0100209 deployment: top.SynapseWorker("synapse", "synapse.app.homeserver", kube.Deployment) {
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100210 cfg+: {
Piotr Dobrowolski529e1812021-02-13 19:44:37 +0100211 localConfig: {
212 # Following configuration values need to cause master
213 # process restart.
radexc995c212023-11-24 12:01:49 +0100214 notify_appservices: top.config.notify_appservices,
215 # send_federation: top.config.send_federation,
216 # federation_sender_instances: top.config.federation_sender_instances,
Piotr Dobrowolskia8bb6152022-09-13 20:37:33 +0200217 },
218
219 resources+: {
220 limits+: { memory: "4Gi" },
221 requests+: { memory: "2Gi" },
222 },
Piotr Dobrowolski529e1812021-02-13 19:44:37 +0100223 },
224 spec+: {
225 strategy+: {
226 rollingUpdate: {
227 maxSurge: 0,
228 maxUnavailable: 1,
229 },
230 },
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100231 },
232 },
radex99ed6a72023-11-24 11:42:55 +0100233 svc: ns.Contain(kube.Service("synapse")) {
radexc995c212023-11-24 12:01:49 +0100234 target:: top.main.deployment,
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100235 },
radex99ed6a72023-11-24 11:42:55 +0100236 replicationSvc: ns.Contain(kube.Service("synapse-replication-master")) {
radexc995c212023-11-24 12:01:49 +0100237 target:: top.main.deployment,
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100238 spec+: {
239 ports: [
240 { port: 9093, name: 'replication', targetPort: 9093 },
241 ],
242 },
243 },
244 },
245
246 genericWorker: {
247 # Synapse generic worker deployment
radexc995c212023-11-24 12:01:49 +0100248 deployment: top.SynapseWorker("synapse-generic", "synapse.app.generic_worker", kube.StatefulSet) {
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100249 cfg+: {
250 localConfig+: {
251 worker_listeners: [{
252 type: "http",
253 port: 8008,
254 x_forwarded: true,
255 bind_addresses: ["::"],
256 resources: [{ names: ["client", "federation"]}],
Piotr Dobrowolski529e1812021-02-13 19:44:37 +0100257 }, {
258 port: 9092,
259 type: "metrics",
260 bind_address: "0.0.0.0",
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100261 }],
262 },
263 },
264 },
radex99ed6a72023-11-24 11:42:55 +0100265 svc: ns.Contain(kube.Service("synapse-generic")) {
radexc995c212023-11-24 12:01:49 +0100266 target:: top.genericWorker.deployment,
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100267 },
268
269 # Following paths can be handled by generic workers.
270 # See: https://github.com/matrix-org/synapse/blob/master/docs/workers.md
Piotr Dobrowolski6bd5d202023-03-28 23:44:14 +0200271 pathsList:: |||
272 # Sync requests
273 ^/_matrix/client/(r0|v3)/sync$
274 ^/_matrix/client/(api/v1|r0|v3)/events$
275 ^/_matrix/client/(api/v1|r0|v3)/initialSync$
276 ^/_matrix/client/(api/v1|r0|v3)/rooms/[^/]+/initialSync$
Piotr Dobrowolski7d0e56c2022-04-30 00:25:39 +0200277
Piotr Dobrowolski6bd5d202023-03-28 23:44:14 +0200278 # Federation requests
279 ^/_matrix/federation/v1/event/
280 ^/_matrix/federation/v1/state/
281 ^/_matrix/federation/v1/state_ids/
282 ^/_matrix/federation/v1/backfill/
283 ^/_matrix/federation/v1/get_missing_events/
284 ^/_matrix/federation/v1/publicRooms
285 ^/_matrix/federation/v1/query/
286 ^/_matrix/federation/v1/make_join/
287 ^/_matrix/federation/v1/make_leave/
288 ^/_matrix/federation/(v1|v2)/send_join/
289 ^/_matrix/federation/(v1|v2)/send_leave/
290 ^/_matrix/federation/(v1|v2)/invite/
291 ^/_matrix/federation/v1/event_auth/
Piotr Dobrowolski926252c2023-03-28 23:58:20 +0200292 ^/_matrix/federation/v1/timestamp_to_event/
Piotr Dobrowolski6bd5d202023-03-28 23:44:14 +0200293 ^/_matrix/federation/v1/exchange_third_party_invite/
294 ^/_matrix/federation/v1/user/devices/
Piotr Dobrowolski6bd5d202023-03-28 23:44:14 +0200295 ^/_matrix/key/v2/query
Piotr Dobrowolski926252c2023-03-28 23:58:20 +0200296 ^/_matrix/federation/v1/hierarchy/
Piotr Dobrowolski6bd5d202023-03-28 23:44:14 +0200297
298 # Inbound federation transaction request
299 ^/_matrix/federation/v1/send/
300
301 # Client API requests
302 ^/_matrix/client/(api/v1|r0|v3|unstable)/createRoom$
303 ^/_matrix/client/(api/v1|r0|v3|unstable)/publicRooms$
304 ^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/joined_members$
305 ^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/context/.*$
306 ^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/members$
307 ^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/state$
Piotr Dobrowolski926252c2023-03-28 23:58:20 +0200308 ^/_matrix/client/v1/rooms/.*/hierarchy$
309 ^/_matrix/client/(v1|unstable)/rooms/.*/relations/
310 ^/_matrix/client/v1/rooms/.*/threads$
311 ^/_matrix/client/unstable/org.matrix.msc2716/rooms/.*/batch_send$
Piotr Dobrowolski6bd5d202023-03-28 23:44:14 +0200312 ^/_matrix/client/unstable/im.nheko.summary/rooms/.*/summary$
313 ^/_matrix/client/(r0|v3|unstable)/account/3pid$
Piotr Dobrowolski926252c2023-03-28 23:58:20 +0200314 ^/_matrix/client/(r0|v3|unstable)/account/whoami$
Piotr Dobrowolski6bd5d202023-03-28 23:44:14 +0200315 ^/_matrix/client/(r0|v3|unstable)/devices$
316 ^/_matrix/client/versions$
317 ^/_matrix/client/(api/v1|r0|v3|unstable)/voip/turnServer$
Piotr Dobrowolski6bd5d202023-03-28 23:44:14 +0200318 ^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/event/
319 ^/_matrix/client/(api/v1|r0|v3|unstable)/joined_rooms$
Piotr Dobrowolski926252c2023-03-28 23:58:20 +0200320 ^/_matrix/client/v1/rooms/.*/timestamp_to_event$
Piotr Dobrowolski6bd5d202023-03-28 23:44:14 +0200321 ^/_matrix/client/(api/v1|r0|v3|unstable)/search$
Piotr Dobrowolski926252c2023-03-28 23:58:20 +0200322 ^/_matrix/client/(r0|v3|unstable)/user/.*/filter(/|$)
Piotr Dobrowolski6bd5d202023-03-28 23:44:14 +0200323
324 # Encryption requests
325 ^/_matrix/client/(r0|v3|unstable)/keys/query$
326 ^/_matrix/client/(r0|v3|unstable)/keys/changes$
327 ^/_matrix/client/(r0|v3|unstable)/keys/claim$
328 ^/_matrix/client/(r0|v3|unstable)/room_keys/
Piotr Dobrowolski926252c2023-03-28 23:58:20 +0200329 ^/_matrix/client/(r0|v3|unstable)/keys/upload/
Piotr Dobrowolski6bd5d202023-03-28 23:44:14 +0200330
331 # Registration/login requests
332 ^/_matrix/client/(api/v1|r0|v3|unstable)/login$
333 ^/_matrix/client/(r0|v3|unstable)/register$
334 ^/_matrix/client/v1/register/m.login.registration_token/validity$
335
336 # Event sending requests
337 ^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/redact
338 ^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/send
339 ^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/state/
340 ^/_matrix/client/(api/v1|r0|v3|unstable)/rooms/.*/(join|invite|leave|ban|unban|kick)$
341 ^/_matrix/client/(api/v1|r0|v3|unstable)/join/
Piotr Dobrowolski926252c2023-03-28 23:58:20 +0200342 ^/_matrix/client/(api/v1|r0|v3|unstable)/knock/
Piotr Dobrowolski6bd5d202023-03-28 23:44:14 +0200343 ^/_matrix/client/(api/v1|r0|v3|unstable)/profile/
344
Piotr Dobrowolski6bd5d202023-03-28 23:44:14 +0200345 # Account data requests
346 #^/_matrix/client/(r0|v3|unstable)/.*/tags
347 #^/_matrix/client/(r0|v3|unstable)/.*/account_data
348
349 # Receipts requests
350 #^/_matrix/client/(r0|v3|unstable)/rooms/.*/receipt
351 #^/_matrix/client/(r0|v3|unstable)/rooms/.*/read_markers
352
353 # Presence requests
354 #^/_matrix/client/(api/v1|r0|v3|unstable)/presence/
Piotr Dobrowolski926252c2023-03-28 23:58:20 +0200355
356 # User directory search requests
357 #^/_matrix/client/(r0|v3|unstable)/user_directory/search$
Piotr Dobrowolski6bd5d202023-03-28 23:44:14 +0200358 |||,
359
360 paths:: std.filterMap(
361 # Ignore comments and empty lines
362 function(v) !std.startsWith(v, '#') && std.length(v) > 1,
363 # Strip leading ^
364 function(v) std.substr(v, 1, std.length(v) - 1),
365 std.split(self.pathsList, "\n")
366 ),
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100367 },
368
369 # Synapse media worker. This handles access to uploads and media stored in app.dataVolume
370 mediaWorker: {
radexc995c212023-11-24 12:01:49 +0100371 deployment: top.SynapseWorker("synapse-media", "synapse.app.media_repository", kube.StatefulSet) {
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100372 cfg+: {
373 mountData: true,
374 localConfig+: {
375 worker_listeners: [{
376 type: "http",
377 port: 8008,
378 x_forwarded: true,
379 bind_addresses: ["::"],
380 resources: [{ names: ["media"]}],
Piotr Dobrowolski529e1812021-02-13 19:44:37 +0100381 }, {
382 port: 9092,
383 type: "metrics",
384 bind_address: "0.0.0.0",
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100385 }],
386 },
387 },
388 },
radex99ed6a72023-11-24 11:42:55 +0100389 svc: ns.Contain(kube.Service("synapse-media")) {
radexc995c212023-11-24 12:01:49 +0100390 target:: top.mediaWorker.deployment,
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100391 },
392 },
Piotr Dobrowolski529e1812021-02-13 19:44:37 +0100393
394 appserviceWorker: if cfg.appserviceWorker then {
395 # Worker responsible for sending traffic to registered appservices
radexc995c212023-11-24 12:01:49 +0100396 deployment: top.SynapseWorker("synapse-appservice", "synapse.app.appservice", kube.StatefulSet) {
Piotr Dobrowolski529e1812021-02-13 19:44:37 +0100397 cfg+: {
398 localConfig+: {
399 worker_listeners: [{
400 type: "http",
401 port: 8008,
402 x_forwarded: true,
403 bind_addresses: ["::"],
404 resources: [{ names: [] }],
405 }, {
406 port: 9092,
407 type: "metrics",
408 bind_address: "0.0.0.0",
409 }],
410 },
411 },
412 },
413 } else null,
414
415 federationSenderWorker: if cfg.federationWorker then {
radexc995c212023-11-24 12:01:49 +0100416 deployment: top.SynapseWorker("synapse-federation-sender", "synapse.app.federation_sender", kube.StatefulSet) {
Piotr Dobrowolski529e1812021-02-13 19:44:37 +0100417 cfg+: {
418 localConfig+: {
419 worker_listeners: [{
420 type: "http",
421 port: 8008,
422 x_forwarded: true,
423 bind_addresses: ["::"],
424 resources: [{ names: [] }],
425 }, {
426 port: 9092,
427 type: "metrics",
428 bind_address: "0.0.0.0",
429 }],
430 },
431 },
432 spec+: {
433 replicas: 2,
434 },
435 },
436 } else null,
Piotr Dobrowolskib67ae482021-01-31 10:35:38 +0100437}