feat(etcd): weekly defrag CronJob to stay under the etcd backend quota
etcd's 2 GiB quota was reached on 2026-10-07 (backend 2.1 GiB, ~73 days of churn). etcd raised NOSPACE and refused all writes, so kubelets could not post node status: three nodes went NotReady and the tunnel origin pods on them disappeared. Recovery was compact + defrag + alarm disarm per member. The apiserver compacts every 5m, but compaction never shrinks the backend file - only a defrag returns space, and nothing scheduled it. This adds: - CronJob etcd-defrag (kube-system, Sundays 03:00): compact to the current revision, defrag each member sequentially, disarm NOSPACE, print before/after. - Secret etcd-defrag-creds (SOPS/ksops, k8s/argocd/secrets): etcd CA plus a CN=etcd-defrag client cert signed by the cluster's etcd CA (expires 2036-10). Applied by the existing apps: cluster-maintenance (directory) and 00-secrets (ksops generator).
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: etcd-defrag-creds
|
||||||
|
namespace: kube-system
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: etcd-defrag
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
ca.crt: ENC[AES256_GCM,data:vso/Ouz5QRoRF2CFj9TsGYhoGkX8oGJIeiFCoS92AAQbyyGPTiZ/fPNFM03p+TpeKDb+Z+Fyo/HXuDeT5Fee5kjPnUHXf2hiUBmJLyHnPMp5i57dRzTC8X4VaNeV/1MmP2WpkWwieWOGBxJVIIrgeQhQexxmIzSlQ5Io9dVOH8F6iyakwdf7qfLFGdoer3rfrXadSOJtKRpLneFJDL2OmR1042LZ8KtNuRRXI/qrdpgr2bnyxFuCRc+tk5QY73mpwwWfKGn6FW/rVdjiEftkC+AvTUItx7RkYMrz0PlTYPUOLustEF2gIgWheYZhE5ebZDurVSG//sudjASliYJItwgSk3G5mZihOSDA7Jcus+U8p1iHQ9CQsey3CSAMmuSGRFmq42kI8135kjtBYrzVswHjX1/51qPMyA2TIeWlRJ7AJlHihG317gT1vPeM1ULl3PkuCxaBLCtjotZBzKLJ/6zXr1ig77Baz71YOVB0D0MgFvzIdao+mdxS8WLOZdiXOCwbI5Y2eo7wJHG4BnpKEdx1rjPkv7DI2NNEoXA7e5FvJWinyAxOy6YGH4Fv6dTlEVhI3Nl/1X+CeafL6Hk8YoqA04Hm+/dJCZi7SsF0r6zfPNNsZLivM8DN18h8KYM3eVY3CwtBldE7atz+oprQ6K/yGUbblHRQwuG3sH3AHrCKLPiUrMIi0jWc0IpdPZRZFL7SnMULfV05QgcvGySOJCuQU+/OYzrRs7umOjwpqvi9PyJYVXBliiYfJutvmfpFAhQj,iv:KnW9LYNzxZ6r3Q6LTeOrgSLLPgiJlibVp6YmTEKBkjE=,tag:KTL1nOWdNWDA4dLVaA4//g==,type:str]
|
||||||
|
client.crt: ENC[AES256_GCM,data:IEmZe505SwkW3U5vmRtFxR6PpuSFGWEHflUZwyhSODzYG1zLM8olVWDtH+yUVI54kLXxTtcpMkKrTBjpAxHG8zUTLPdTCVzPMq6U20jH4PYFMzoldnSgpRl/eiADUmE4I32CBbPdM5nkPvjVEYMmRafEGyJctjG6Shhh/sUO1yE9A+h1R7o3UCRPttWTozSKN4dPi75v1TSAOLyXqvQYl5Ea9iKW47ea7Q+XTcoKfMPXsTJp44kiZnVW0VT/7xNydR7AVWJJzYltY5380j0Ho+7PfNd+QqmdodkaZPS4b0PaoinMiZ40JnChGVhdS91j3IkUlU1g/+G2iVnPR9KB07xqRubqiASxIvpSaSCeYl3rR7+dXYX18VpWcWnJ6XoREoqxJiqmMQWYSROYDTd4G/zs4Usuh5r0wIodmCjud+g89mmXFtH0PbsMCVvZDUswjVfeT2PUBm036TXlxF/1aAy2GDqycbPgJgDLyw9tdiQUZe2iA+Z/Wx80qgQjmmqCaw77cDergLFj9KnByl3Tinwfm+IWFeQiDW+fwSzz+/vrpl55BkSVkVhw7r2Jd6zyg4VtLVXbxYhzdNz695N+fjkZHuPE5tCwr4WCzAg3vSS9hA7IHo8k93WO8s6+KlyjuwTdjPIYZghebO4ktwdqvhvuqxv8Nzwpc5J7Z2H2IChjXg==,iv:l8BwxxEfHfYrm+mM5UE6Yj42DeeU9y23jjIE1KjIiME=,tag:HwRT6/myIsxc84BYDj3K6w==,type:str]
|
||||||
|
client.key: ENC[AES256_GCM,data:QD/Glf3lFslBEdEoJ3CNrYzlw2HLCvGSeVuJLtxnFne2aJOuw0sk/e0h/kSAxvG4dZ+LIWJLLbqTjKHOAhkvS/ZOA7GSea4ubNkvTkw8+JBRuYwb2D2iB8kZhlER/CpiGkYJukjkgGP1CzHdvBY1M88pW9w6rH74MD3jY47qExIaNduQj5vkUQZ8ajqkd79l0nk98ZTawfPG4AFcEtAIKAPIuubT2GZ3qqNLjOI7agbtRv8xI44hhb2wj5J9xdl1GrbOWwG5EOo+hLNrcAAOCcWBpNTUCKBy7mrk2ID8jX3HsFWN76DDlQzkhcphMhlM7g==,iv:49HGJAEgllEkB3YSiZLmbWs/vlbFbY52kiKesdGVC/Y=,tag:g8YZ9at3V2l6RFRfvO2lAQ==,type:str]
|
||||||
|
sops:
|
||||||
|
age:
|
||||||
|
- enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4Z0h0R2dhbDdRcFJydE9o
|
||||||
|
R0hWeCtwVzluZWZlR0VtOXcxdW9FbklOQXlzCmo3eXNyNlFjejNETkJiVVRVWFhr
|
||||||
|
a2pORnhIQTI0UUk2N3dWMDZJcEhYU1UKLS0tIEduaHlmMHQza1A0TGVZb0JrbTYz
|
||||||
|
MWJwQjcrdkkzQXhOTmhycERsM0FtNVEKG+gBRcvUsyfRzuW999EQNzyDh2/5fY6p
|
||||||
|
8ab2xvTOJPiMUBTBuQZsA+iDImytUO/02ROQsbEZkALijp1nqrsV/g==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1e5fq3hwxy78psus2nfvmtmua36g0u3suk78ephw6246l974d2utsvn0hla
|
||||||
|
encrypted_regex: ^(data|stringData)$
|
||||||
|
lastmodified: "2026-10-07T05:37:26Z"
|
||||||
|
mac: ENC[AES256_GCM,data:vu6ltV0jYQNGV4eqIcqAp9p2TJ8ULBX5JBELUgm/nA9GX+I6Pv3fyjpcHRSWY0HbLvCwgXd22CG4IYcj4HJFtQGZR7OcQA5WM5plZH3RCPdvPNix/798vkETfpJxorQjlHoq4nlr3dEmUCbzdtE07qV2W1M6GJr4C9xKvB947bQ=,iv:aA9mjZCFAoLEhGqkl6NfyBZ3x3vWGfLYYAEe8jXuA4U=,tag:Drdiji33ckDd8ildpvSEXw==,type:str]
|
||||||
|
version: 3.13.2
|
||||||
@@ -34,3 +34,4 @@ files:
|
|||||||
- forgejo-smtp-secrets.enc.yaml
|
- forgejo-smtp-secrets.enc.yaml
|
||||||
- paperless-ai-secrets.enc.yaml
|
- paperless-ai-secrets.enc.yaml
|
||||||
- pi-sandbox-secrets.enc.yaml
|
- pi-sandbox-secrets.enc.yaml
|
||||||
|
- etcd-defrag-creds.enc.yaml
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# etcd defrag maintenance.
|
||||||
|
#
|
||||||
|
# Why this exists: the apiserver compacts etcd every 5m, but compaction only marks
|
||||||
|
# superseded revisions free INSIDE the backend file -- the file never shrinks.
|
||||||
|
# Only `etcdctl defrag` returns space to the filesystem, and nothing runs it
|
||||||
|
# automatically. On 2026-10-07 the 2 GiB quota was reached after ~73 days
|
||||||
|
# (backend 2.1 GiB) -> etcd raised the NOSPACE alarm -> etcd refused ALL writes
|
||||||
|
# -> kubelets could not post node status -> 3 nodes went NotReady and the tunnel
|
||||||
|
# origin pods on them vanished (502s). Recovery = compact + defrag + alarm disarm.
|
||||||
|
#
|
||||||
|
# This CronJob keeps the backend well under quota: compact to the current
|
||||||
|
# revision, defrag each member sequentially, then disarm any NOSPACE alarm.
|
||||||
|
#
|
||||||
|
# Companion alerts (monitoring): etcd_mvcc_db_total_size_in_bytes /
|
||||||
|
# etcd_server_quota_in_bytes > 0.8, and a NOSPACE alarm alert.
|
||||||
|
#
|
||||||
|
# Credentials: secret etcd-defrag-creds (kube-system), SOPS-managed in
|
||||||
|
# k8s/argocd/secrets/etcd-defrag-creds.enc.yaml. Its client cert is signed by the
|
||||||
|
# cluster's etcd CA and expires 2036-10 -- rotate it before then by re-running the
|
||||||
|
# mint + re-encrypt (see the repo's sops workflow).
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: etcd-defrag
|
||||||
|
namespace: kube-system
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: etcd-defrag
|
||||||
|
app.kubernetes.io/part-of: cluster-maintenance
|
||||||
|
spec:
|
||||||
|
schedule: "0 3 * * 0" # Sundays 03:00, low traffic
|
||||||
|
concurrencyPolicy: Forbid
|
||||||
|
startingDeadlineSeconds: 600
|
||||||
|
successfulJobsHistoryLimit: 3
|
||||||
|
failedJobsHistoryLimit: 3
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
backoffLimit: 2
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
tolerations:
|
||||||
|
- key: node-role.kubernetes.io/control-plane
|
||||||
|
operator: Exists
|
||||||
|
effect: NoSchedule
|
||||||
|
containers:
|
||||||
|
- name: defrag
|
||||||
|
image: quay.io/coreos/etcd:v3.5.17
|
||||||
|
env:
|
||||||
|
- name: ETCDCTL_API
|
||||||
|
value: "3"
|
||||||
|
- name: ETCDCTL_CACERT
|
||||||
|
value: /etc/etcd/ca.crt
|
||||||
|
- name: ETCDCTL_CERT
|
||||||
|
value: /etc/etcd/client.crt
|
||||||
|
- name: ETCDCTL_KEY
|
||||||
|
value: /etc/etcd/client.key
|
||||||
|
- name: EPS
|
||||||
|
value: "https://192.168.1.166:2379 https://192.168.1.214:2379 https://192.168.1.162:2379"
|
||||||
|
command: ["/bin/sh", "-c"]
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
set -u
|
||||||
|
PRIMARY=${EPS%% *}
|
||||||
|
echo "== before =="
|
||||||
|
etcdctl --endpoints=$EPS endpoint status -w table || true
|
||||||
|
|
||||||
|
REV=$(etcdctl --endpoints=$PRIMARY endpoint status -w json \
|
||||||
|
| sed -n 's/.*"revision":\([0-9]*\).*/\1/p' | head -1)
|
||||||
|
if [ -n "$REV" ]; then
|
||||||
|
echo "== compact to revision $REV =="
|
||||||
|
etcdctl --endpoints=$PRIMARY compact "$REV" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "== defrag each member (sequential, one at a time) =="
|
||||||
|
rc=0
|
||||||
|
for ep in $EPS; do
|
||||||
|
echo "-- $ep"
|
||||||
|
etcdctl --endpoints="$ep" defrag || { echo "DEFRAG-FAILED $ep"; rc=1; }
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "== disarm NOSPACE =="
|
||||||
|
for ep in $EPS; do
|
||||||
|
etcdctl --endpoints="$ep" alarm disarm 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "== after =="
|
||||||
|
etcdctl --endpoints=$EPS endpoint status -w table || true
|
||||||
|
etcdctl --endpoints=$PRIMARY alarm list || true
|
||||||
|
exit $rc
|
||||||
|
resources:
|
||||||
|
requests: { cpu: 10m, memory: 32Mi }
|
||||||
|
limits: { cpu: 200m, memory: 128Mi }
|
||||||
|
volumeMounts:
|
||||||
|
- name: creds
|
||||||
|
mountPath: /etc/etcd
|
||||||
|
readOnly: true
|
||||||
|
volumes:
|
||||||
|
- name: creds
|
||||||
|
secret:
|
||||||
|
secretName: etcd-defrag-creds
|
||||||
|
defaultMode: 0444
|
||||||
Reference in New Issue
Block a user