Skip to main content

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 /license endpoint 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_count and tenant_count unless an upgrade is in scope.
  • expires_at extended by one year (or the contracted renewal period).
  • The same tier and entitlements unless 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_at reflects the renewed expiry date.
  • tier and entitlements are correct.
  • current_seats and current_tenants are within the new max_seats / max_tenants quota.

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):

  1. The pods will log a startup error referencing the license blob. The API refuses to start with an invalid non-optional license.
  2. 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 -
  3. Rolling restart: kubectl rollout restart deployment/alexandria-ee -n <namespace>
  4. 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.