-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathall.yaml
More file actions
426 lines (398 loc) · 24.1 KB
/
Copy pathall.yaml
File metadata and controls
426 lines (398 loc) · 24.1 KB
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
# ░██████╗░██╗░░░░░░█████╗░██████╗░░█████╗░██╗░░░░░ ██╗░░░██╗░█████╗░██████╗░░██████╗
# ██╔════╝░██║░░░░░██╔══██╗██╔══██╗██╔══██╗██║░░░░░ ██║░░░██║██╔══██╗██╔══██╗██╔════╝
# ██║░░██╗░██║░░░░░██║░░██║██████╦╝███████║██║░░░░░ ╚██╗░██╔╝███████║██████╔╝╚█████╗░
# ██║░░╚██╗██║░░░░░██║░░██║██╔══██╗██╔══██║██║░░░░░ ░╚████╔╝░██╔══██║██╔══██╗░╚═══██╗
# ╚██████╔╝███████╗╚█████╔╝██████╦╝██║░░██║███████╗ ░░╚██╔╝░░██║░░██║██║░░██║██████╔╝
# ░╚═════╝░╚══════╝░╚════╝░╚═════╝░╚═╝░░╚═╝╚══════╝ ░░░╚═╝░░░╚═╝░░╚═╝╚═╝░░╚═╝╚═════╝░
domain: ethpandaops.io
network_subdomain: "{{ ethereum_network_name }}.{{ domain }}"
network_server_subdomain: "srv.{{ network_subdomain }}"
server_fqdn: "{{ inventory_hostname }}.{{ network_server_subdomain }}"
network_iteration: "{{ ethereum_network_name.split('-')[1:] | join('-') }}"
ethereum_network_id: >-
{{ (lookup('file', eth_testnet_config_local_dir_src + '/genesis.json') | from_json).config.chainId }}
ethereum_network_deposit_contract: >-
{{ lookup('file', eth_testnet_config_local_dir_src + '/deposit_contract.txt') }}
ethereum_network_deposit_contract_block: >-
{{ lookup('file', eth_testnet_config_local_dir_src + '/deposit_contract_block.txt') }}
ethereum_node_rpc_prefix: "rpc-" # prefix for rpc URLs
ethereum_node_beacon_prefix: "bn-" # prefix for beacon URLs
ethereum_node_rcp_hostname: "{{ ethereum_node_rpc_prefix }}{{ server_fqdn }}"
ethereum_node_beacon_hostname: "{{ ethereum_node_beacon_prefix }}{{ server_fqdn }}"
primary_bootnode: bootnode-1
### Checkpoint sync specific for Ansible & Kubernetes
checkpoint_sync_node: "{{ primary_bootnode }}"
ethereum_node_cl_checkpoint_sync_enabled: true
checkpoint_sync_url: https://checkpoint-sync.{{ ethereum_network_name }}.{{ domain }}
#checkpoint_sync_url: https://{{ secret_nginx_shared_basic_auth.name }}:{{ secret_nginx_shared_basic_auth.password }}@{{ ethereum_node_beacon_prefix }}{{ checkpoint_sync_node }}.{{ ethereum_network_name }}.{{ domain }}
checkpoint_sync_url_kube: https://<path:/secrets/services/services.enc.yaml#ethereum | jsonPath {.testnets.glamsterdam-devnets.node_ingress.combined}>@{{ ethereum_node_beacon_prefix }}{{ checkpoint_sync_node }}.{{ ethereum_network_name }}.{{ domain }}
# ██████╗░░█████╗░██╗░░░░░███████╗ ██╗░░░██╗░█████╗░██████╗░░██████╗
# ██╔══██╗██╔══██╗██║░░░░░██╔════╝ ██║░░░██║██╔══██╗██╔══██╗██╔════╝
# ██████╔╝██║░░██║██║░░░░░█████╗░░ ╚██╗░██╔╝███████║██████╔╝╚█████╗░
# ██╔══██╗██║░░██║██║░░░░░██╔══╝░░ ░╚████╔╝░██╔══██║██╔══██╗░╚═══██╗
# ██║░░██║╚█████╔╝███████╗███████╗ ░░╚██╔╝░░██║░░██║██║░░██║██████╔╝
# ╚═╝░░╚═╝░╚════╝░╚══════╝╚══════╝ ░░░╚═╝░░░╚═╝░░╚═╝╚═╝░░╚═╝╚═════╝░
# role: ethpandaops.general.ethereum_genesis
ethereum_genesis_generator_container_image: "{{ default_tooling_images.ethereum_genesis_generator }}"
ethereum_genesis_generator_version: "{{ ethereum_genesis_generator_container_image.split(':')[-1] }}"
ethereum_genesis_network_seed: "glamsterdam-devnet-0"
ethereum_genesis_chain_id: "70{{ 99999999 | random(start=10000000, seed=ethereum_genesis_network_seed) }}"
ethereum_genesis_fork_version_suffix: "{{ 999999 | random(start=100000, seed=ethereum_genesis_network_seed) }}"
ethereum_genesis_generator_output_dir: "../network-configs/{{ network_iteration }}"
ethereum_genesis_timestamp: "{{ lookup('ansible.builtin.pipe', '{{ ethereum_genesis_timestamp_relative_cmd[ansible_system] }}') }}"
ethereum_genesis_timedelay: 60
ethereum_genesis_timestamp_relative_cmd:
Linux: "date +%s -d '+45 minutes'"
Darwin: "date -v +15M +%s"
shadowfork_height: 0
ethereum_genesis_mnemonic: "{{ secret_genesis_mnemonic }}"
ethereum_genesis_generator_config_files:
cl/config.yaml: "{{ lookup('ansible.builtin.url', 'https://raw.githubusercontent.com/ethpandaops/ethereum-genesis-generator/v{{ethereum_genesis_generator_version}}/config-example/cl/config.yaml', split_lines=false) }}" # noqa yaml[line-length]
cl/mnemonics.yaml: "{{ lookup('ansible.builtin.url', 'https://raw.githubusercontent.com/ethpandaops/ethereum-genesis-generator/v{{ethereum_genesis_generator_version}}/config-example/cl/mnemonics.yaml', split_lines=false) }}" # noqa yaml[line-length]
el/genesis-config.yaml: "{{ lookup('ansible.builtin.url', 'https://raw.githubusercontent.com/ethpandaops/ethereum-genesis-generator/v{{ethereum_genesis_generator_version}}/config-example/el/genesis-config.yaml', split_lines=false) }}" # noqa yaml[line-length]
values.env: |-
export CHAIN_ID="{{ ethereum_genesis_chain_id }}"
export EL_AND_CL_MNEMONIC="{{ ethereum_genesis_mnemonic }}"
export NUMBER_OF_VALIDATORS=7500
export GENESIS_FORK_VERSION="0x10{{ ethereum_genesis_fork_version_suffix }}"
export ALTAIR_FORK_VERSION="0x20{{ ethereum_genesis_fork_version_suffix }}"
export BELLATRIX_FORK_VERSION="0x30{{ ethereum_genesis_fork_version_suffix }}"
export CAPELLA_FORK_VERSION="0x40{{ ethereum_genesis_fork_version_suffix }}"
export DENEB_FORK_VERSION="0x50{{ ethereum_genesis_fork_version_suffix }}"
export ELECTRA_FORK_VERSION="0x60{{ ethereum_genesis_fork_version_suffix }}"
export FULU_FORK_VERSION="0x70{{ ethereum_genesis_fork_version_suffix }}"
export GLOAS_FORK_VERSION="0x80{{ ethereum_genesis_fork_version_suffix }}"
export BPO_1_EPOCH="0"
export BPO_2_EPOCH="1"
export GLOAS_FORK_EPOCH="2"
export GENESIS_TIMESTAMP={{ ethereum_genesis_timestamp }}
export GENESIS_DELAY={{ ethereum_genesis_timedelay }}
export WITHDRAWAL_TYPE=0x02
export EL_PREMINE_ADDRS='{"0x9a97ee9d32a0d68406e32b34c92afb81ce2bc467": {"balance": "100000ETH"}, "0x107781Bc6FA8f66B843f4216fd6D5862D3aa4fcd": {"balance": "100000ETH"}, "0x118A1030baf8Fa56f9B8235C163d3112FA8c3b89": {"balance": "100000ETH"}}'
# 0x9a97ee9d32a0d68406e32b34c92afb81ce2bc467: 100000ETH - arbitrum
# 0x107781Bc6FA8f66B843f4216fd6D5862D3aa4fcd: 100000ETH - optimism
# 0x118A1030baf8Fa56f9B8235C163d3112FA8c3b89: 100000ETH - lido
ethereum_genesis_validator_keys_output_dir: "{{ ansible_inventory_sources[0] | dirname }}/files/validator_keys"
ethereum_genesis_validator_bls_change_execution_address: "{{ ethereum_node_cl_validator_fee_recipient }}"
ethereum_genesis_validator_keyranges: >-
{%- set ns = namespace() -%}
{%- set ns.ethereum_genesis_validator_keyranges = {} -%}
{%- for host in groups['all'] -%}
{%- if hostvars[host].validator_start is defined and hostvars[host].validator_end is defined -%}
{%- set v = {'start':hostvars[host].validator_start, 'end': hostvars[host].validator_end } -%}
{%- set _ = ns.ethereum_genesis_validator_keyranges.update({host: v}) -%}
{%- endif -%}
{%- endfor -%}
{{ ns.ethereum_genesis_validator_keyranges }}
# role: ethpandaops.general.bootstrap
bootstrap_default_user_authorized_keys_plain:
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDbboxOo0jyL3DNxqZ6UTEnZPEzPDPnujEYaClqNWSLWkphczHKAnJPkrwbAWB4JbJKjsAJ5kn53f10KPnUyZvJ5Jn8Rpf7RM7+56MYaBg84gVoA2KeIYxUa7h8neY7J61Galp0c6cOK+hp1lPsoiBSdCW/Rtbv6ALCcVe+4+uCW5FRoJcNRJfGRLRnjh1pw57HQw9O55mf319s4rVUq4umznQ0CciEx3rVMtXf4xjIZDZAhNpGaBh8AtHauaMZCOGociAIquYYqoSQnnmnOBiduRa5OkvGZomgybNQivlYboDeF6sQ71KVzRXSI+mxCYbSp246lqSdQtQsjFA54NYl/qWgAql0uqCqsZidW+XBjquyItRl2Rfzzy5Fk/gMOAJXHQYp4POfgFbqtxjWpfnuOKqW/1IGWcIt2g016effUqGgj/oePX0g+duFdszSKK773rJBySgafFF6XWNqagrLmE4LUGC+6P3oxzYTSFGeUVA21OayL+K40XPpJti5zns= # devops-eth2-shared"
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDWh9NW66VD4BPKETNyZeZrGN1f7G6dkihW3eAc7cbJPFQGIpnWc2tGq5o13vWW+SoCh16nkYM2oak+PJQxXYTiQnrMJSmSFd7E0DmdcoKadGJEnfosrH++aOZf/eVLe5q3E9NQFVSdOPo1MCRRTuZxPkuMxS6QikW3otWrA3F2vFgmYyki3Cy8huQzHKUZGicividYcUSFTydR2L0oWUNve3FyqMQQQPnfaJ1RvrkeGtdhRSAxa6L0jzgRK7fjpUyhKOofr7kCKARGELRRiB9QikRAoHU2/D/2jtJjKlTCJxArzXyDF2IcQCco+5Oe9x4c7Xch32dbscJSmjaAvsxRnu7GEFCS7b6kKGvwcoq5vJzvp3RBBR7Mosxv6pcM/q7Z4RhXOFVFFiPVl1dqkqSPkUrHwg8LtWOxC+GAl36vxhHLdDEV/RhbSAzO6SfYEWYGH1w7u4oiy2XAT2cNCO0j0tSHS5chX+d7TzwAbBE2HuPL84GVGHZG875hmiE+Dok= # github-actions-ci"
bootstrap_default_user_authorized_keys_github_all:
- barnabasbusa
- parithosh
- samcm
- savid
- skylenet
- pk910
- mattevans
- qu0b
bootstrap_default_user_authorized_keys_github: >
{{
(bootstrap_default_user_authorized_keys_github_all | default([])) +
(bootstrap_default_user_authorized_keys_github_team_el | default([])) +
(bootstrap_default_user_authorized_keys_github_team_cl | default([]))
}}
node_exporter_container_image: "{{ default_tooling_images.node_exporter }}"
prometheus_container_image: "{{ default_tooling_images.prometheus }}"
vector_container_image: "{{ default_tooling_images.vector }}"
json_rpc_snooper_container_image: "{{ default_tooling_images.json_rpc_snooper }}"
# role: ethpandaops.general.ethereum_node
ethereum_node_images_always_pull: true
ethereum_node_metrics_exporter_enabled: true
ethereum_node_xatu_sentry_enabled: true
ethereum_node_cl_validator_enabled: "{{ validator_start is defined and validator_end is defined }}"
ethereum_node_cl_validator_fee_recipient: "0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134"
ethereum_node_cl_ports_p2p_tcp: 9000
ethereum_node_cl_ports_p2p_udp: 9000
ethereum_node_cl_ports_http_beacon: 5052
ethereum_node_cl_ports_metrics: 5054
ethereum_node_el_ports_p2p_tcp: 30303
ethereum_node_el_ports_p2p_udp: 30303
ethereum_node_el_ports_http_rpc: 8545
ethereum_node_el_ports_ws_rpc: 8546
ethereum_node_el_ports_engine: 8551
ethereum_node_el_ports_metrics: 6060
ethereum_node_ipv6_enabled: true
ethereum_node_el_public_ipv6: "{{ host_ipv6_prefix | ansible.utils.ipaddr(service_ipv6_offsets.execution) | ansible.utils.ipaddr('address') if (docker_network_enable_ipv6 | bool) else '' }}"
ethereum_node_cl_public_ipv6: "{{ host_ipv6_prefix | ansible.utils.ipaddr(service_ipv6_offsets.beacon) | ansible.utils.ipaddr('address') if (docker_network_enable_ipv6 | bool) else '' }}"
ethereum_node_validator_public_ipv6: "{{ host_ipv6_prefix | ansible.utils.ipaddr(service_ipv6_offsets.validator) | ansible.utils.ipaddr('address') if (docker_network_enable_ipv6 | bool) else '' }}"
ethereum_node_snooper_rpc_public_ipv6: "{{ host_ipv6_prefix | ansible.utils.ipaddr(service_ipv6_offsets.snooper_rpc) | ansible.utils.ipaddr('address') if (docker_network_enable_ipv6 | bool) else '' }}"
ethereum_node_snooper_engine_public_ipv6: "{{ host_ipv6_prefix | ansible.utils.ipaddr(service_ipv6_offsets.snooper_engine) | ansible.utils.ipaddr('address') if (docker_network_enable_ipv6 | bool) else '' }}"
ethereum_node_skip_cleanup: true
ethereum_node_docker_watchtower_enabled: true
ethereum_node_docker_watchtower_containers_list:
- execution
- beacon
- validator
- xatu-sentry
- external-block-builder
docker_watchtower_container_additional_args:
- --interval=900
- --stop-timeout=300s
- --include-restarting
ethereum_node_json_rpc_snooper_engine_enabled: true
ethereum_node_json_rpc_snooper_engine_name: "snooper-engine"
ethereum_node_json_rpc_snooper_engine_port: 8561
ethereum_node_json_rpc_snooper_engine_public_port: 8961
ethereum_node_json_rpc_snooper_engine_container_env:
SNOOPER_API_PORT: "{{ ethereum_node_json_rpc_snooper_engine_public_port | quote }}"
SNOOPER_API_AUTH: "{{ secret_nginx_shared_basic_auth.name }}:{{ secret_nginx_shared_basic_auth.password }}"
# role: ethpandaops.general.generate_kubernetes_config
gen_kubernetes_config_dora_execution_snooper_port: 8961
gen_kubernetes_config_dora_frontend_rainbowkit_id: "15fe4ab4d5c0bcb6f0dc7c398301ff0e"
gen_kubernetes_config_dora_execution_endpoints_url: "https://raw.githubusercontent.com/ethpandaops/{{ devnet_name }}-devnets/refs/heads/master/kubernetes/{{ network_iteration }}/dora/endpoints-el.yaml"
gen_kubernetes_config_dora_consensus_endpoints_url: "https://raw.githubusercontent.com/ethpandaops/{{ devnet_name }}-devnets/refs/heads/master/kubernetes/{{ network_iteration }}/dora/endpoints-cl.yaml"
gen_kubernetes_config_dora_api_secret: "{{ secret_dora_api_key }}"
# role: ethpandaops.general.xatu_sentry
xatu_sentry_container_image: "{{ default_tooling_images.xatu_sentry }}"
xatu_sentry_config_name: "{{ ethereum_network_name }}-{{ inventory_hostname }}"
xatu_sentry_config_server_address: "{{ secret_xatu_sentry.server_address }}"
xatu_sentry_config_server_auth_user: "{{ secret_xatu_sentry.user }}"
xatu_sentry_config_server_auth_password: "{{ secret_xatu_sentry.password }}"
xatu_sentry_config_network_name_override: "{{ ethereum_network_name }}"
# role: ethpandaops.general.ethereum_metrics_exporter
ethereum_metrics_exporter_container_image: "{{ default_tooling_images.ethereum_metrics_exporter }}"
ethereum_metrics_exporter_container_user: root
ethereum_metrics_exporter_container_volumes:
- "{{ ethereum_metrics_exporter_dir_config }}:/config:ro"
- /var/run/docker.sock:/var/run/docker.sock:ro
- /data:/data:ro
ethereum_metrics_exporter_config: |
consensus:
enabled: true
url: "{{ ethereum_metrics_exporter_cl_endpoint }}"
name: "consensus-client"
execution:
enabled: true
url: "{{ ethereum_metrics_exporter_el_endpoint }}"
name: "execution-client"
modules:
- "eth"
- "net"
- "web3"
- "txpool"
docker:
enabled: true
endpoint: "unix:///var/run/docker.sock"
interval: "10s"
containers:
- name: "execution"
type: "execution"
filesystem:
enabled: true
volumes:
- name: "*"
monitor: true
- path: "/execution-auth.jwt"
monitor: false
- path: "/network-config"
monitor: false
- name: "beacon"
type: "consensus"
filesystem:
enabled: true
volumes:
- name: "*"
monitor: true
- path: "/execution-auth.jwt"
monitor: false
- path: "/network-config"
monitor: false
# role: eth_testnet_config
eth_testnet_config_dir: /data/ethereum-network-config/metadata
eth_testnet_config_local_dir_enabled: true
eth_testnet_config_local_dir_src: "{{ ethereum_genesis_generator_output_dir }}/metadata/"
# role: gen_basic_auth_nginx
gen_basic_auth_nginx_name: "{{ secret_nginx_shared_basic_auth.name }}"
gen_basic_auth_nginx_password: "{{ secret_nginx_shared_basic_auth.password }}"
docker_nginx_proxy_container_image: "{{ default_tooling_images.nginx_proxy }}"
docker_nginx_proxy_docker_gen_container_image: "{{ default_tooling_images.nginx_proxy_gen }}"
docker_nginx_proxy_acme_companion_container_image: "{{ default_tooling_images.nginx_proxy_acme }}"
# role: ethpandaops.general.docker_nginx_proxy
docker_nginx_proxy_public_ipv6: "{{ host_ipv6_prefix | ansible.utils.ipaddr(service_ipv6_offsets.nginx_proxy) | ansible.utils.ipaddr('address') if (docker_network_enable_ipv6 | bool) else '' }}"
docker_nginx_proxy_container_name: nginx-proxy
docker_nginx_proxy_default_email: "certs@{{ domain }}"
docker_nginx_proxy_docker_gen_container_name: nginx-proxy-gen
docker_nginx_proxy_docker_gen_container_env:
RESOLVERS: "1.1.1.1"
ENABLE_IPV6: "true"
docker_nginx_proxy_acme_companion_enabled: false
docker_nginx_proxy_acme_companion_container_name: nginx-proxy-acme
docker_nginx_proxy_acme_companion_container_env:
DEFAULT_EMAIL: "{{ docker_nginx_proxy_default_email }}"
NGINX_PROXY_CONTAINER: "{{ docker_nginx_proxy_container_name }}"
NGINX_DOCKER_GEN_CONTAINER: "{{ docker_nginx_proxy_docker_gen_container_name }}"
ACME_CA_URI: https://acme.zerossl.com/v2/DV90
ACME_EAB_KID: "{{ secret_zerossl.ACME_EAB_KID }}"
ACME_EAB_HMAC_KEY: "{{ secret_zerossl.ACME_EAB_HMAC_KEY }}"
docker_nginx_proxy_container_networks: >-
{{ [{'name': 'shared'} | combine({'ipv6_address': docker_nginx_proxy_public_ipv6} if (docker_nginx_proxy_public_ipv6 | default('') | length > 0) else {})] }}
docker_nginx_proxy_docker_gen_container_networks: []
docker_nginx_proxy_acme_companion_container_networks: "{{ docker_networks_shared }}"
docker_nginx_proxy_acme_monitor_container_networks: "{{ docker_networks_shared }}"
docker_nginx_proxy_acme_monitor_enabled: false
docker_nginx_proxy_datadir: /opt/nginx-proxy
docker_nginx_proxy_container_volumes:
- "{{ docker_nginx_proxy_datadir }}/conf:/etc/nginx/conf.d"
- "{{ docker_nginx_proxy_datadir }}/vhost:/etc/nginx/vhost.d"
- "{{ docker_nginx_proxy_datadir }}/certs:/etc/nginx/certs:ro"
- html:/usr/share/nginx/html
- "{{ docker_nginx_proxy_datadir }}/htpasswd:/etc/nginx/htpasswd:ro"
docker_nginx_proxy_cert_loader_container_image: "{{ default_tooling_images.nginx_proxy_cert_loader }}"
docker_nginx_proxy_cert_linker_container_image: "{{ default_tooling_images.nginx_proxy_cert_linker }}"
docker_nginx_proxy_cert_loader_container_networks: []
docker_nginx_proxy_cert_linker_container_networks: []
docker_nginx_proxy_wildcard_cert: "{{ network_server_subdomain }}"
docker_nginx_proxy_wildcard_cert_url: "http://cert.{{ network_server_subdomain }}/{{ network_server_subdomain }}-latest.tar.enc"
docker_nginx_proxy_wildcard_cert_psk: "{{ secret_cert_encryption_psk }}"
# OTLP egress to the prod analytics gateway (Vector ships container logs here).
# Ingress identity (auth username + ingress_user tag) is the current devnet name
# rather than secret_loki.username: the sops username drifts stale between devnet
# iterations, while the gateway only validates the (constant) password — so using
# the network name keeps log attribution correct no matter what sops holds.
otlp_endpoint: "https://otlp.analytics.production.platform.ethpandaops.io"
otlp_deployment_env: production
# role: ethpandaops.general.vector
vector_config: |
# Docker container logs (clean per-container metadata straight from the Docker API)
[sources.in]
type = "docker_logs"
exclude_containers = [
"{{ vector_container_name }}",
"ethereum-metrics-exporter",
"nginx-proxy",
"node_exporter",
"prometheus",
"snooper-",
]
# Shape docker_logs events into an OTLP resourceLogs envelope with full metadata.
[transforms.otel_shape]
type = "remap"
inputs = ["in"]
source = '''
ts_ns = to_string(to_unix_timestamp(now(), unit: "nanoseconds"))
if is_timestamp(.timestamp) {
ts_ns = to_string(to_unix_timestamp!(.timestamp, unit: "nanoseconds"))
}
msg = string(.message) ?? ""
ctr = string(.container_name) ?? "unknown"
img = string(.image) ?? ""
strm = string(.stream) ?? "stdout"
# Extract the source log level: JSON-structured lines (.level / .severity)
# first, then logfmt (level=xxx), then a level token near the start of a
# plain-text line. Client formats vary (lighthouse truncates to 4 chars:
# DEBG/ERRO/CRIT), so the text matcher allows a union of forms. SeverityText
# keeps the source's exact text; only SeverityNumber is normalised to the
# OTel scale (https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-severitynumber).
# Unrecognised lines are left unset rather than guessed.
sevtext = ""
if starts_with(msg, "{") {
j = parse_json(msg) ?? {}
if is_string(j.level) {
sevtext = string!(j.level)
} else if is_string(j.severity) {
sevtext = string!(j.severity)
}
}
if sevtext == "" {
lm = parse_regex(msg, r'(?i)\b(?:level|lvl|severity)="?(?P<l>[a-z]+)') ?? {}
if is_string(lm.l) {
sevtext = string!(lm.l)
} else {
hm = parse_regex(truncate(msg, 48), r'(?i)\b(?P<l>TRACE|DEBUG|DEBG|DBUG|NOTICE|INFO|WARNING|WARN|ERROR|ERRO|CRITICAL|CRIT|FATAL|PANIC)\b') ?? {}
if is_string(hm.l) {
sevtext = string!(hm.l)
}
}
}
lvl = upcase(sevtext)
sevnum = 0
if lvl == "TRACE" {
sevnum = 1
} else if lvl == "DEBUG" || lvl == "DEBG" || lvl == "DBUG" {
sevnum = 5
} else if lvl == "INFO" || lvl == "INFOR" || lvl == "NOTICE" {
sevnum = 9
} else if lvl == "WARN" || lvl == "WARNING" {
sevnum = 13
} else if lvl == "ERROR" || lvl == "ERRO" {
sevnum = 17
} else if lvl == "CRIT" || lvl == "CRITICAL" {
sevnum = 18
} else if lvl == "FATAL" || lvl == "PANIC" {
sevnum = 21
}
attrs = [
{"key": "service.name", "value": {"stringValue": ctr}},
{"key": "container.name", "value": {"stringValue": ctr}},
{"key": "container.image.name", "value": {"stringValue": img}},
{"key": "deployment.environment", "value": {"stringValue": "{{ otlp_deployment_env }}"}},
{"key": "forwarder", "value": {"stringValue": "vector"}},
{"key": "ingress_user", "value": {"stringValue": "{{ ethereum_network_name }}"}},
{"key": "network", "value": {"stringValue": "{{ ethereum_network_name }}"}},
{"key": "testnet", "value": {"stringValue": "{{ ethereum_network_name }}"}},
{"key": "instance", "value": {"stringValue": "{{ inventory_hostname }}"}},
{"key": "host.name", "value": {"stringValue": "{{ inventory_hostname }}"}}
]
{%- if ethereum_node_cl is defined +%}
attrs = push(attrs, {"key": "ethereum_cl", "value": {"stringValue": "{{ ethereum_node_cl }}"}})
{%- endif +%}
{%- if ethereum_node_el is defined +%}
attrs = push(attrs, {"key": "ethereum_el", "value": {"stringValue": "{{ ethereum_node_el }}"}})
{%- endif +%}
. = {
"resource_log": {
"resource": {"attributes": attrs},
"scopeLogs": [{
"scope": {"name": "{{ ethereum_network_name }}-vector"},
"logRecords": [{
"timeUnixNano": ts_ns,
"severityNumber": sevnum,
"severityText": sevtext,
"body": {"stringValue": msg},
"attributes": [{"key": "stream", "value": {"stringValue": strm}}]
}]
}]
}
}
'''
# Batch many shaped log events into one OTLP envelope (reduce works on logs).
[transforms.batch_envelope]
type = "reduce"
inputs = ["otel_shape"]
expire_after_ms = 30000
end_every_period_ms = 5000
max_events = 500
merge_strategies.resource_log = "array"
[transforms.finalize_envelope]
type = "remap"
inputs = ["batch_envelope"]
source = '''
. = {"resourceLogs": .resource_log}
'''
[sinks.otlp_logs]
type = "opentelemetry"
inputs = ["finalize_envelope"]
[sinks.otlp_logs.protocol]
type = "http"
uri = "{{ otlp_endpoint }}/v1/logs"
method = "post"
encoding.codec = "otlp"
auth.strategy = "basic"
auth.user = "{{ ethereum_network_name }}"
auth.password = "{{ secret_loki.password }}"
# One event here is already a full OTLP envelope (built by the reduce above).
# max_events MUST be 1 — OTLP/HTTP allows one envelope per request.
batch.max_events = 1
batch.timeout_secs = 5