SSL: автоматичне оновлення
Kubernetes — сертифікат у TLS-секреті оновлюється сам
CronJob раз на добу забирає свіжий сертифікат і оновлює TLS-секрет, з яким працює ingress; ключ лишається в тому самому секреті.
Оновлюється сам
Підходить, якщо сайт працює в Kubernetes за ingress (ingress-nginx, Traefik, HAProxy Ingress тощо) і сертифікат лежить у секреті типу kubernetes.io/tls. Ingress-контролери стежать за секретами самі: оновили секрет — за хвилину-дві сайт віддає новий сертифікат.
Як це працює: CronJob раз на добу забирає сертифікат за посиланням, порівнює з тим, що в секреті, перевіряє, що він підходить до ключа з того самого секрету, і замінює лише tls.crt. Ключ нікуди не копіюється. Права задачі — читати й змінювати один секрет в одному namespace.
Що знадобиться
- Посилання на сертифікат із кабінету:
https://api.zrazu.com/ssl-pull/zsp_ВАШ_ТОКЕН. kubectlз доступом до namespace сайту.- Вихід із кластера в інтернет (до
api.zrazu.comі до дзеркал Alpine Linux — образ ставитьcurl,opensslіjqпри кожному запуску).
Крок 1. TLS-секрет (якщо його ще немає)
Завантажте https://api.zrazu.com/ssl-pull/zsp_ВАШ_ТОКЕН/fullchain.pem у файл fullchain.pem і створіть секрет зі своїм ключем:
curl -fsS -o fullchain.pem "https://api.zrazu.com/ssl-pull/zsp_ВАШ_ТОКЕН/fullchain.pem"
kubectl -n НАЗВА_NAMESPACE create secret tls zrazu-tls --cert=fullchain.pem --key=key.pemВ Ingress сайту вкажіть цей секрет: spec.tls[].secretName: zrazu-tls.
Крок 2. CronJob
Збережіть текст нижче у файл zrazu-ssl.yaml. Якщо секрет у вас називається інакше, замініть zrazu-tls (він трапляється двічі).
# ZRAZU: оновлення сертифіката в TLS-секреті за особистим посиланням. Запускається раз на добу.
apiVersion: v1
kind: ServiceAccount
metadata:
name: zrazu-ssl
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: zrazu-ssl
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["zrazu-tls"]
verbs: ["get", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: zrazu-ssl
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: zrazu-ssl
subjects:
- kind: ServiceAccount
name: zrazu-ssl
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: zrazu-ssl
spec:
schedule: "17 4 * * *"
concurrencyPolicy: Forbid
jobTemplate:
spec:
backoffLimit: 2
template:
spec:
serviceAccountName: zrazu-ssl
restartPolicy: Never
containers:
- name: update
image: alpine:3.20
env:
- name: PULL
value: "https://api.zrazu.com/ssl-pull/zsp_ВАШ_ТОКЕН"
- name: SECRET
value: "zrazu-tls"
command: ["/bin/sh", "-c"]
args:
- |
set -eu
apk add --no-cache curl openssl jq >/dev/null
SA=/var/run/secrets/kubernetes.io/serviceaccount
API="https://kubernetes.default.svc/api/v1/namespaces/$(cat $SA/namespace)/secrets/$SECRET"
k() { curl -fsS --cacert "$SA/ca.crt" -H "Authorization: Bearer $(cat $SA/token)" "$@"; }
cd /tmp
curl -fsS --retry 3 --max-time 60 -o new.pem "$PULL/fullchain.pem"
grep -q "BEGIN CERTIFICATE" new.pem || { echo "ZRAZU: not a certificate"; exit 1; }
k "$API" > secret.json
jq -r '.data["tls.crt"]' secret.json | base64 -d > current.pem
if cmp -s new.pem current.pem; then echo "ZRAZU: unchanged"; exit 0; fi
jq -r '.data["tls.key"]' secret.json | base64 -d > key.pem
cert_pub=$(openssl x509 -in new.pem -noout -pubkey | openssl sha256)
key_pub=$(openssl pkey -in key.pem -pubout | openssl sha256)
rm -f key.pem
[ "$cert_pub" = "$key_pub" ] || { echo "ZRAZU: certificate is for another key"; exit 1; }
openssl x509 -in new.pem -noout -checkend 0 >/dev/null || { echo "ZRAZU: expired"; exit 1; }
crt=$(base64 < new.pem | tr -d '\n')
k -X PATCH -H "Content-Type: application/merge-patch+json" --data "{\"data\":{\"tls.crt\":\"$crt\"}}" "$API" >/dev/null
echo "ZRAZU: updated, valid until $(openssl x509 -in new.pem -noout -enddate | cut -d= -f2)"Застосуйте в namespace сайту:
kubectl -n НАЗВА_NAMESPACE apply -f zrazu-ssl.yamlПеревірка
- Запустіть задачу один раз руками:
kubectl -n НАЗВА_NAMESPACE create job --from=cronjob/zrazu-ssl zrazu-ssl-test. - Журнал:
kubectl -n НАЗВА_NAMESPACE logs job/zrazu-ssl-test— «updated» або «unchanged». - Сайт: https://zrazu.com/tools/ssl/example.com/.
- Приберіть пробну задачу:
kubectl -n НАЗВА_NAMESPACE delete job zrazu-ssl-test.
Якщо не вийшло
403у журналі — Role не на той секрет чи не в тому namespace: назва вresourceNamesмає збігатися з назвою секрету.apk: … network error— кластер не має виходу в інтернет до дзеркал Alpine. Зберіть свій образ ізcurl,opensslіjqі приберіть рядокapk add.- «certificate is for another key» — сертифікат перевипускали з новим ключем: перестворіть секрет (крок 1) з новим ключем.
- Секрет керує cert-manager — тоді ця інструкція не потрібна: cert-manager поновлює сертифікат сам через ACME-сервер ZRAZU (у кабінеті, «Встановлення» → ACME).