-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathspegel-kv.hcl
529 lines (474 loc) · 14.4 KB
/
spegel-kv.hcl
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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
variable "spegel" {
description = <<-EOF
Configuration related to the Spegel registry task.
--- Spegel registry configuration ---
name: Name of the Spegel registry job on Nomad
image:
name: Name of the Spegel registry image
tag: Tag of the Spegel registry image
sha256: SHA256 of the Spegel registry image (TODO)
resources:
cpu: CPU resources to allocate to the Spegel registry
memory: Memory resources to allocate to the Spegel registry
memory_max: Maximum memory resources to allocate to the Spegel registry
registries: List of registries for which mirror configuration will be created
mirrorResolveRetries: Number of retries for resolving a mirror
mirrorResolveTimeout: Maximum duration spent finding a mirror
containerdSock: Path to the Containerd socket
containerdNamespace: Containerd namespace where images are stored
containerdRegistryConfigPath: Path to the Containerd mirror configuration
containerdMirrorAdd: If true Spegel will add mirror configuration to the node
resolveTags: When true Spegel will resolve tags to digests
resolveLatestTag: When true latest tags will be resolved to digests
EOF
default = {
name = "spegel"
image = {
name = "ghcr.io/spegel-org/spegel"
tag = "v0.0.21"
}
resources = {
cpu = 1000
memory = 128
memory_max = 256
}
registries = [
"https://docker.io",
"https://ghcr.io",
"https://quay.io",
"https://mcr.microsoft.com",
"https://public.ecr.aws",
"https://gcr.io",
]
mirrorResolveRetries = 3
mirrorResolveTimeout = "5s"
containerdSock = "/run/containerd/containerd.sock"
containerdNamespace = "moby"
containerdRegistryConfigPath = "/etc/containerd/certs.d"
containerdMirrorAdd = true
resolveTags = true
resolveLatestTag = true
}
type = object({
name = string
image = object({
name = string
tag = string
})
resources = object({
cpu = number
memory = number
memory_max = number
})
registries = list(string)
mirrorResolveRetries = number
mirrorResolveTimeout = string
containerdSock = string
containerdNamespace = string
containerdRegistryConfigPath = string
containerdMirrorAdd = bool
resolveTags = bool
resolveLatestTag = bool
})
}
variable "service" {
description = <<-EOF
Service configuration for exposing the registry service port
--- Spegel service configuration ---
provider: Name of the service provider, either consul or nomad
registry:
port: Port on which the registry service will be exposed
EOF
default = {
provider = "consul"
registry = {
port = 30021
}
}
type = object({
provider = string
registry = object({
port = number
})
})
}
variable "bootstrap" {
description = <<-EOF
Leader election configuration, can use either consul or nomad as a kv backend
--- Spegel bootstrap configuration ---
key_prefix: Prefix for the leader election and bootstrap keys to be placed under.
provider: Configuration for the kv provider.
options:
name: Name of the kv provider, either consul or nomad.
bin: Path to the kv provider binary on the nomad client.
image:
name: Name of the leader election image
tag: Tag of the leader election image
sha256: SHA256 of the leader election image (TODO)
support bash, and should be compatible with the nomad/consul bin
EOF
default = {
key_prefix = "nomad/jobs"
provider = {
name = "consul"
bin = "/bin/consul"
}
image = {
name = "docker.io/debian"
tag = "bookworm-slim"
}
resources = {
cpu = 100
memory = 128
memory_max = 256
}
}
type = object({
key_prefix = string
provider = object({
name = string
bin = string
})
image = object({
name = string
tag = string
})
})
}
locals {
bootstrap = {
key_prefix = join(
"/",
[var.bootstrap.key_prefix, var.spegel.name, "bootstrap"]
)
}
}
job "spegel" {
priority = 100
type = "system"
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "15s"
}
group "spegel" {
network {
port "registryService" {
static = var.service.registry.port
to = var.service.registry.port
}
port "registryHost" {}
port "router" {}
port "metrics" {}
port "bootstrap" {}
}
service {
tags = ["router"]
name = var.spegel.name
port = "router"
provider = var.service.provider
check {
name = "router_up"
type = "tcp"
interval = "5s"
timeout = "2s"
check_restart {
limit = 5
grace = "30s"
ignore_warnings = false
}
}
}
service {
tags = ["metrics"]
name = var.spegel.name
port = "metrics"
provider = var.service.provider
check {
name = "metrics_healthy"
type = "http"
path = "/metrics"
interval = "5s"
timeout = "2s"
check_restart {
limit = 5
grace = "30s"
ignore_warnings = false
}
}
}
service {
tags = ["bootstrap"]
name = var.spegel.name
port = "bootstrap"
provider = var.service.provider
check {
name = "bootstrap_healthy"
type = "http"
path = "/id"
interval = "5s"
timeout = "2s"
check_restart {
limit = 5
grace = "30s"
ignore_warnings = false
}
}
}
service {
tags = ["registry"]
name = var.spegel.name
port = "registryService"
provider = var.service.provider
check {
name = "registry_healthy"
type = "http"
path = "/healthz"
interval = "5s"
timeout = "2s"
check_restart {
limit = 5
grace = "30s"
ignore_warnings = false
}
}
}
task "configuration" {
driver = "docker"
lifecycle {
hook = "prestart"
sidecar = false
}
config {
image = join(":", [var.spegel.image.name, var.spegel.image.tag])
volumes = [
join(":", [var.spegel.containerdRegistryConfigPath, var.spegel.containerdRegistryConfigPath])
]
args = concat([
"configuration",
"--containerd-registry-config-path=${var.spegel.containerdRegistryConfigPath}",
"--resolve-tags=${var.spegel.resolveTags}",
"--mirror-registries"
],
var.service.provider == "consul" ? [
"http://${NOMAD_ADDR_registryService}",
"http://${var.spegel.name}-registry.service.consul:${NOMAD_PORT_registryService}"
] : [
"http://${NOMAD_ADDR_registryService}"
],
[
"--registries",
],
var.spegel.registries,
)
}
}
task "registry" {
driver = "docker"
restart {
interval = "5m"
attempts = 5
delay = "15s"
mode = "delay"
}
resources {
cpu = try(var.spegel.resorces.cpu, 1000)
memory = try(var.spegel.resorces.memory, 128)
memory_max = try(var.spegel.resorces.memory_max, 256)
}
config {
network_mode = "host"
image = join(":", [var.spegel.image.name, var.spegel.image.tag])
ports = ["registryService", "registryHost", "router", "metrics", "bootstrap"]
volumes = [
join(":", [var.spegel.containerdRegistryConfigPath, var.spegel.containerdRegistryConfigPath]),
join(":", [var.spegel.containerdSock, var.spegel.containerdSock]),
]
args = concat([
"registry",
"--mirror-resolve-retries=${var.spegel.mirrorResolveRetries}",
"--mirror-resolve-timeout=${var.spegel.mirrorResolveTimeout}",
"--registry-addr=:${NOMAD_PORT_registryService}",
"--router-addr=:${NOMAD_PORT_router}",
"--metrics-addr=:${NOMAD_PORT_metrics}",
"--containerd-sock=${var.spegel.containerdSock}",
"--containerd-namespace=${var.spegel.containerdNamespace}",
"--containerd-registry-config-path=${var.spegel.containerdRegistryConfigPath}",
"--bootstrap-kind=http",
"--http-bootstrap-addr=:${NOMAD_PORT_bootstrap}",
"--resolve-latest-tag=${var.spegel.resolveLatestTag}",
"--local-addr=${NOMAD_ADDR_registryHost}",
"--registries",
],
var.spegel.registries,
)
}
template {
data = var.bootstrap.provider.name == "consul" ? (
<<-EOH
{{- with $k := key "${local.bootstrap.key_prefix}/bootstrap" | parseJSON -}}
HTTP_BOOTSTRAP_PEER="{{ $k.BOOTSTRAP_DATA }}"
{{- end -}}
EOH
) : (
<<-EOH
{{- with nomadVar "${local.bootstrap.key_prefix}/bootstrap" -}}
HTTP_BOOTSTRAP_PEER="{{ .BOOTSTRAP_DATA }}"
{{- end -}}
EOH
)
destination = "local/bootstrap.env"
env = true
change_mode = "restart"
}
}
task "bootstrap" {
driver = "docker"
lifecycle {
hook = "prestart"
sidecar = true
}
resources {
cpu = try(var.bootstrap.resorces.cpu, 100)
memory = try(var.bootstrap.resorces.memory, 32)
memory_max = try(var.bootstrap.resorces.memory_max, 128)
}
env {
KV_PROVIDER = var.bootstrap.provider.name
KV_BIN = var.bootstrap.provider.bin
KV_PREFIX = local.bootstrap.key_prefix
KV_BOOTSTRAP_DATA = "http://${NOMAD_ADDR_bootstrap}/id"
KV_HTTP_ADDR = format(
"http://${NOMAD_HOST_IP_bootstrap}:%s",
var.bootstrap.provider.name == "consul" ? "8500" : "4646"
)
}
config {
image = join(":", [var.bootstrap.image.name, var.bootstrap.image.tag])
args = [
"${NOMAD_TASK_DIR}/bin/bootstrap",
]
# mount the nomad or consul bin inside the container
volumes = [
join(":", [
var.bootstrap.provider.bin,
"/usr/local/bin/${basename(var.bootstrap.provider.name)}"
])
]
}
template {
destination = "local/bin/bootstrap"
perms = "755"
data = <<-EOF
#!/usr/bin/env bash
# basic leader election script using consul/nomad CLI
# set bash as the shell for consul lock
export SHELL=$(which bash)
export PATH=$PATH:$(dirname $KV_BIN)
required_vars=(
KV_PROVIDER
KV_PREFIX
KV_BIN
KV_BOOTSTRAP_DATA
KV_HTTP_ADDR
)
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $*" >&2
}
shutdown_message() {
log_message "Recieved SIGTERM, shutting down leader election process"
exit 0
}
check_env_var() {
local var_name="$1";
local var_value="$${!var_name}";
if [ -z "$var_value" ]; then
log_message "'$var_name' env var is not defined";
return 1;
else
return 0;
fi;
}
update_consul_key() {
consul kv put $1 "{\"BOOTSTRAP_DATA\":\"$2\"}" 2> /dev/null
}
update_nomad_key() {
nomad var put $1 "BOOTSTRAP_DATA=$2" 2> /dev/null
}
update_bootstrap_key() {
local provider=$1
local bootstrap_key=$2
local bootstrap_data=$3
log_message "Acquired lock on leader key"
update_$${provider}_key "$bootstrap_key" "$bootstrap_data" || {
log_message "Failed to update bootstrap key"
return 1
}
log_message "Updated bootstrap key with - '$bootstrap_data'"
log_message "Node is now the leader"
# sleep forever to hold the lock
sleep infinity
}
session_lock() {
local provider=$1
local leader_key="$2"
local bootstrap_key="$3"
local bootstrap_data="$4"
local lock_command=($${@:5})
log_message "Attempting to acquire lock on '$leader_key'"
# no timeout means we try until the lock becomes available
$${lock_command[@]} -shell $leader_key "\
update_bootstrap_key \
$provider \
$bootstrap_key \
$bootstrap_data \
" || {
log_message "Failed to acquire lock"
return 1
}
}
# ----- setup and initial checks ----
# make the functions available to lock subshells
export -f \
update_bootstrap_key \
log_message \
update_consul_key \
update_nomad_key
if [ "$KV_PROVIDER" == "consul" ]; then
export CONSUL_HTTP_ADDR="$KV_HTTP_ADDR"
lock_command=(consul lock)
log_message "Using consul for leader election"
elif [ "$KV_PROVIDER" == "nomad" ]; then
export NOMAD_ADDR="$KV_HTTP_ADDR"
lock_command=(nomad var lock)
log_message "Using nomad for leader election"
else
log_message "Unsupported kv provider"
exit 1
fi
for var in "$${required_vars[@]}"; do
check_env_var "$var" || undefined_vars=1
done
if [ -n "$undefined_vars" ]; then
log_message "Undefined env vars detected"
exit 1
fi
trap shutdown_message SIGTERM
# set kv paths
leader_key="$KV_PREFIX/leader"
bootstrap_key="$KV_PREFIX/bootstrap"
# ----- script starts here ----
log_message "Starting leader election process"
while true; do
session_lock \
"$KV_PROVIDER" \
"$leader_key" \
"$bootstrap_key" \
"$KV_BOOTSTRAP_DATA" \
$${lock_command[@]}
done
EOF
}
}
}
}