From 67104123a5092a302b3860a7d0da335814196ae7 Mon Sep 17 00:00:00 2001 From: Ultradesu Date: Mon, 29 Jun 2026 20:46:07 +0300 Subject: [PATCH] Added amnezia exporter --- k8s/apps/amnezia/configmap-scripts.yaml | 108 +++++++++++++++++- k8s/apps/amnezia/daemonset.yaml | 99 +++++++++++++++- .../amnezia/exporter-redis-configmap.yaml | 28 +++++ k8s/apps/amnezia/exporter-service.yaml | 17 +++ k8s/apps/amnezia/kustomization.yaml | 3 + k8s/apps/amnezia/rbac.yaml | 3 + k8s/apps/amnezia/servicemonitor.yaml | 23 ++++ 7 files changed, 273 insertions(+), 8 deletions(-) create mode 100644 k8s/apps/amnezia/exporter-redis-configmap.yaml create mode 100644 k8s/apps/amnezia/exporter-service.yaml create mode 100644 k8s/apps/amnezia/servicemonitor.yaml diff --git a/k8s/apps/amnezia/configmap-scripts.yaml b/k8s/apps/amnezia/configmap-scripts.yaml index 69d79dc..490175b 100644 --- a/k8s/apps/amnezia/configmap-scripts.yaml +++ b/k8s/apps/amnezia/configmap-scripts.yaml @@ -128,6 +128,9 @@ data: SERVER_CONFIG="/etc/amnezia/server/awg0.conf" CLIENTS_DIR="/etc/amnezia/clients" RUNTIME_CONFIG="/run/amnezia/awg0.conf" + SYNC_CONFIG="/run/amnezia/awg0.sync.conf" + STATUS_FILE="/run/amnezia/reload-status" + RELOAD_INTERVAL="${AMNEZIAWG_RELOAD_INTERVAL:-10}" cleanup() { if awg show awg0 >/dev/null 2>&1; then @@ -137,32 +140,125 @@ data: render_config() { mkdir -p "$(dirname "${RUNTIME_CONFIG}")" - cp "${SERVER_CONFIG}" "${RUNTIME_CONFIG}" - chmod 0600 "${RUNTIME_CONFIG}" + local tmp_config="${RUNTIME_CONFIG}.tmp" + cp "${SERVER_CONFIG}" "${tmp_config}" + chmod 0600 "${tmp_config}" local clients_found=0 for client_config in "${CLIENTS_DIR}"/*; do [ -f "${client_config}" ] || continue [ -s "${client_config}" ] || continue - printf '\n' >> "${RUNTIME_CONFIG}" - cat "${client_config}" >> "${RUNTIME_CONFIG}" + printf '\n' >> "${tmp_config}" + cat "${client_config}" >> "${tmp_config}" clients_found=1 done if [ "${clients_found}" = "0" ]; then echo "No client peer configs found in ${CLIENTS_DIR}; starting without peers" fi + + mv "${tmp_config}" "${RUNTIME_CONFIG}" + chmod 0600 "${RUNTIME_CONFIG}" + } + + client_config_hash() { + { + for client_config in "${CLIENTS_DIR}"/*; do + [ -f "${client_config}" ] || continue + sha256sum "${client_config}" + done + } | sha256sum | awk '{print $1}' + } + + write_reload_status() { + local state="${1}" + local hash="${2:-}" + local applied_at_ms="" + if [ "${state}" = "applied" ]; then + applied_at_ms="$(($(date +%s) * 1000))" + fi + + mkdir -p "$(dirname "${STATUS_FILE}")" + { + printf 'state=%s\n' "${state}" + printf 'hash=%s\n' "${hash}" + printf 'applied_at_ms=%s\n' "${applied_at_ms}" + } > "${STATUS_FILE}.tmp" + mv "${STATUS_FILE}.tmp" "${STATUS_FILE}" + } + + apply_live_config() { + render_config + awg-quick strip "${RUNTIME_CONFIG}" > "${SYNC_CONFIG}" + chmod 0600 "${SYNC_CONFIG}" + awg syncconf awg0 "${SYNC_CONFIG}" + } + + watch_client_config() { + local last_hash="${1}" + while true; do + sleep "${RELOAD_INTERVAL}" & + wait "$!" || return 0 + + local current_hash + current_hash="$(client_config_hash)" + if [ "${current_hash}" = "${last_hash}" ]; then + continue + fi + + echo "Detected AmneziaWG client peer config change; applying with awg syncconf" + if apply_live_config; then + last_hash="${current_hash}" + write_reload_status applied "${current_hash}" + awg show awg0 || true + else + echo "ERROR: failed to hot-reload AmneziaWG client peer config" >&2 + write_reload_status error "${current_hash}" + fi + done } trap cleanup EXIT trap 'exit 0' TERM INT + initial_hash="$(client_config_hash)" render_config cleanup awg-quick up "${RUNTIME_CONFIG}" awg show awg0 || true + write_reload_status applied "${initial_hash}" + watch_client_config "${initial_hash}" + + status-patch.sh: | + #!/usr/bin/env bash + set -euo pipefail + + STATUS_FILE="/run/amnezia/reload-status" + PATCH_INTERVAL="${AMNEZIAWG_STATUS_PATCH_INTERVAL:-5}" + NAMESPACE="${POD_NAMESPACE:-$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)}" + : "${POD_NAME:?POD_NAME is required}" + + last_file_hash="" + + patch_status() { + local state="unknown" + local hash="" + local applied_at_ms="" + + # The file is generated by run.sh and contains only shell assignments. + # shellcheck disable=SC1090 + source "${STATUS_FILE}" + + kubectl patch pod "${POD_NAME}" -n "${NAMESPACE}" --type merge -p "{\"metadata\":{\"annotations\":{\"amnezia-fellow.hexor.cy/client-secret-reload-status\":\"${state}\",\"amnezia-fellow.hexor.cy/client-secret-applied-at-ms\":\"${applied_at_ms}\",\"amnezia-fellow.hexor.cy/client-secret-applied-hash\":\"${hash}\"}}}" + } while true; do - sleep 3600 & - wait "$!" + if [ -f "${STATUS_FILE}" ]; then + file_hash="$(sha256sum "${STATUS_FILE}" | awk '{print $1}')" + if [ "${file_hash}" != "${last_file_hash}" ]; then + patch_status || true + last_file_hash="${file_hash}" + fi + fi + sleep "${PATCH_INTERVAL}" done diff --git a/k8s/apps/amnezia/daemonset.yaml b/k8s/apps/amnezia/daemonset.yaml index 640a561..da1f926 100644 --- a/k8s/apps/amnezia/daemonset.yaml +++ b/k8s/apps/amnezia/daemonset.yaml @@ -6,8 +6,8 @@ metadata: labels: app: amneziawg annotations: - reloader.stakater.com/auto: "true" - secret.reloader.stakater.com/reload: "amneziawg-server,amneziawg-clients" + secret.reloader.stakater.com/reload: "amneziawg-server" + configmap.reloader.stakater.com/reload: "amneziawg-scripts" spec: selector: matchLabels: @@ -27,6 +27,19 @@ spec: tolerations: - operator: Exists initContainers: + - name: install-awg + image: amneziavpn/amneziawg-go:latest + imagePullPolicy: IfNotPresent + command: + - /bin/bash + - -lc + - | + set -euo pipefail + cp /usr/bin/awg /shared-bin/awg + chmod 0755 /shared-bin/awg + volumeMounts: + - name: awg-bin + mountPath: /shared-bin - name: register-endpoint image: bitnami/kubectl:latest imagePullPolicy: IfNotPresent @@ -123,6 +136,81 @@ spec: mountPath: /run/amnezia - name: dev-net-tun mountPath: /dev/net/tun + - name: reload-status + image: bitnami/kubectl:latest + imagePullPolicy: IfNotPresent + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + command: + - /bin/bash + - /scripts/status-patch.sh + resources: + requests: + memory: "32Mi" + cpu: "10m" + limits: + memory: "128Mi" + cpu: "100m" + volumeMounts: + - name: scripts + mountPath: /scripts + readOnly: true + - name: runtime-config + mountPath: /run/amnezia + - name: amneziawg-exporter-redis + image: redis:alpine + imagePullPolicy: IfNotPresent + command: + - redis-server + - /etc/redis/redis.conf + ports: + - name: redis + containerPort: 6379 + protocol: TCP + resources: + requests: + memory: "32Mi" + cpu: "10m" + limits: + memory: "128Mi" + cpu: "100m" + volumeMounts: + - name: exporter-redis-config + mountPath: /etc/redis + readOnly: true + - name: exporter-redis-data + mountPath: /data + - name: amneziawg-exporter + image: amneziavpn/amneziawg-exporter:latest + imagePullPolicy: IfNotPresent + securityContext: + capabilities: + add: + - NET_ADMIN + env: + - name: AWG_EXPORTER_REDIS_HOST + value: "127.0.0.1" + - name: AWG_EXPORTER_REDIS_PORT + value: "6379" + ports: + - name: metrics + containerPort: 9351 + protocol: TCP + resources: + requests: + memory: "64Mi" + cpu: "25m" + limits: + memory: "256Mi" + cpu: "200m" + volumeMounts: + - name: awg-bin + mountPath: /usr/bin/awg + subPath: awg + readOnly: true volumes: - name: server-config secret: @@ -142,6 +230,13 @@ spec: defaultMode: 0755 - name: runtime-config emptyDir: {} + - name: awg-bin + emptyDir: {} + - name: exporter-redis-config + configMap: + name: amneziawg-exporter-redis + - name: exporter-redis-data + emptyDir: {} - name: dev-net-tun hostPath: path: /dev/net/tun diff --git a/k8s/apps/amnezia/exporter-redis-configmap.yaml b/k8s/apps/amnezia/exporter-redis-configmap.yaml new file mode 100644 index 0000000..5c025bd --- /dev/null +++ b/k8s/apps/amnezia/exporter-redis-configmap.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: amneziawg-exporter-redis + labels: + app: amneziawg + component: exporter +data: + redis.conf: | + bind 0.0.0.0 + protected-mode no + port 6379 + tcp-backlog 511 + timeout 0 + tcp-keepalive 300 + daemonize no + pidfile /run/redis.pid + loglevel warning + logfile "" + databases 16 + always-show-logo no + set-proc-title no + save 3600 1 + stop-writes-on-bgsave-error no + rdbcompression yes + rdbchecksum yes + dir /data diff --git a/k8s/apps/amnezia/exporter-service.yaml b/k8s/apps/amnezia/exporter-service.yaml new file mode 100644 index 0000000..b3b654d --- /dev/null +++ b/k8s/apps/amnezia/exporter-service.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: amneziawg-exporter + labels: + app: amneziawg + component: exporter +spec: + type: ClusterIP + selector: + app: amneziawg + ports: + - name: metrics + protocol: TCP + port: 9351 + targetPort: 9351 diff --git a/k8s/apps/amnezia/kustomization.yaml b/k8s/apps/amnezia/kustomization.yaml index 347f8dc..3ba3f72 100644 --- a/k8s/apps/amnezia/kustomization.yaml +++ b/k8s/apps/amnezia/kustomization.yaml @@ -12,4 +12,7 @@ resources: - fellow-service.yaml - fellow-ingress.yaml - fellow-deployment.yaml + - exporter-redis-configmap.yaml + - exporter-service.yaml + - servicemonitor.yaml - daemonset.yaml diff --git a/k8s/apps/amnezia/rbac.yaml b/k8s/apps/amnezia/rbac.yaml index c100746..f3f4870 100644 --- a/k8s/apps/amnezia/rbac.yaml +++ b/k8s/apps/amnezia/rbac.yaml @@ -42,6 +42,9 @@ rules: - apiGroups: [""] resources: ["secrets"] verbs: ["get", "create", "patch"] + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "patch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/k8s/apps/amnezia/servicemonitor.yaml b/k8s/apps/amnezia/servicemonitor.yaml new file mode 100644 index 0000000..6e857e2 --- /dev/null +++ b/k8s/apps/amnezia/servicemonitor.yaml @@ -0,0 +1,23 @@ +--- +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: amneziawg-exporter + labels: + app: amneziawg + component: exporter + release: prometheus +spec: + selector: + matchLabels: + app: amneziawg + component: exporter + endpoints: + - port: metrics + path: /metrics + interval: 30s + scrapeTimeout: 10s + honorLabels: true + namespaceSelector: + matchNames: + - amnezia