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.