Runbook: Annual License Renewal
When to use this
Use this runbook to renew an Alexandria EE license blob that is approaching expiry. The API emits a structured license.expiring audit event at 30 days before expiry (month-11 in a 12-month term), which is the trigger to start this procedure.
Pre-checks
- Check the expiry warning:
kubectl logs -n <namespace> -l app.kubernetes.io/instance=alexandria-ee -c api | grep '"event_type":"license.expiring"' - Or query the
/licenseendpoint directly:curl -sf https://<host>/license | jq '{expires_at, current_seats, max_seats, current_tenants, max_tenants}' - Confirm the issuer portal / license server is reachable and the customer account is in good standing.
- Identify the current tier so the renewed blob carries the same entitlements (or an upgraded set).
Procedure
1. Obtain the renewed license blob from the issuer
Contact the Alexandria EE license issuer (internal licensing service or alex-license sign) and request a new blob for the customer. The new blob should have:
- The same
seat_countandtenant_countunless an upgrade is in scope. expires_atextended by one year (or the contracted renewal period).- The same
tierandentitlementsunless the customer is upgrading.
The issuer produces a signed binary blob (typically named alexandria-ee.lic or lic.txt).
2. Load the new blob into the cluster
Replace the existing license Secret:
# Overwrite the existing secret (preserves other keys in the secret if any)
kubectl create secret generic alexandria-license \
-n <namespace> \
--from-file=license.key=./lic.txt \
--dry-run=client -o yaml | kubectl apply -f -
The secret name and key must match license.fromSecret.name and license.fromSecret.key in your site values (defaults: "" and license.key — override in your site values file if you named the secret differently).
3. Trigger a license reload
The Go API currently loads the license blob at startup. If a future release adds SIGHUP-based hot-reload, send SIGHUP to the api process instead of restarting:
# Hot-reload (if SIGHUP license reload is implemented):
kubectl exec -n <namespace> deploy/alexandria-ee -c api -- kill -HUP 1
Until hot-reload is implemented, trigger a rolling restart:
kubectl rollout restart deployment/alexandria-ee -n <namespace>
kubectl rollout status deployment/alexandria-ee -n <namespace> --timeout=5m
4. Verify the new license is active
curl -sf https://<host>/license | jq '{tier, expires_at, current_seats, max_seats, current_tenants, max_tenants, entitlements}'
Confirm:
expires_atreflects the renewed expiry date.tierandentitlementsare correct.current_seatsandcurrent_tenantsare within the newmax_seats/max_tenantsquota.
5. Confirm no expiry warning in logs
After restart, the license.expiring audit event should not appear for at least 11 months. Monitor the /license endpoint in your observability stack — the alexandria-license.json Grafana dashboard (see k8s/o11y/dashboards/) will show days-to-expiry once the license_expires_at_seconds metric is exposed.
Rollback
If the new blob fails to load (invalid signature, wrong tier):
- The pods will log a startup error referencing the license blob. The API refuses to start with an invalid non-optional license.
- Restore the previous blob:
kubectl create secret generic alexandria-license -n <namespace> --from-file=license.key=./old_lic.txt --dry-run=client -o yaml | kubectl apply -f - - Rolling restart:
kubectl rollout restart deployment/alexandria-ee -n <namespace> - Contact the issuer to re-issue a corrected blob.
See also: key-rotation.md if the blob fails due to an untrusted signing key rather than bad content.