Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The Kubernetes package

FerroFED ships a kustomize package for Kubernetes, deploy/kubernetes/, and every release attaches it with each image pinned by digest. It has three parts:

PartWhat it holds
base/the gateway: its StatefulSet, ConfigMap, Service, the headless Service of its admin listeners, its PodDisruptionBudget and its NetworkPolicy
components/the parts you add: the operator console, an Ingress or a Gateway API route for each, a Prometheus Operator ServiceMonitor, and an egress NetworkPolicy
overlays/example/an overlay over the base with every component, the shape a production install takes

The base is the StatefulSet that The container image describes, with the probes, the audit spool claim per replica and the hardened security context. CI renders the base and the example overlay with kustomize and validates every object with kubeconform in strict mode, and runs ferrofed config check over the base’s configuration and the console’s configuration check over the console component’s, each with synthetic secrets.

Installing a release

Each release attaches ferrofed-vX.Y.Z-kubernetes.tar.gz and its SHA-256. The tarball is the tree of that tag, with the gateway’s image pinned to its digest in base/kustomization.yaml and the console’s in components/console/kustomization.yaml, so the pods run exactly the image the release scanned and attested:

gh release download vX.Y.Z --repo FerroHEALTH/FerroFED \
  --pattern 'ferrofed-vX.Y.Z-kubernetes.tar.gz*'
sha256sum -c ferrofed-vX.Y.Z-kubernetes.tar.gz.sha256sum
tar -xzf ferrofed-vX.Y.Z-kubernetes.tar.gz

Make an overlay of your own beside it, which names the base and the components you use, and keep your edits there:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ferrofed
resources:
  - ../ferrofed-vX.Y.Z-kubernetes/base
components:
  - ../ferrofed-vX.Y.Z-kubernetes/components/console
  - ../ferrofed-vX.Y.Z-kubernetes/components/ingress
  - ../ferrofed-vX.Y.Z-kubernetes/components/console-ingress
  - ../ferrofed-vX.Y.Z-kubernetes/components/service-monitor
  - ../ferrofed-vX.Y.Z-kubernetes/components/egress

Every value to replace is marked EDIT in the manifest that holds it: the issuer and audience of [auth], the members of registry.toml, the storage class of the audit spool, the namespaces of your reverse proxy and your Prometheus, the hosts and certificates of the routes, and the egress destinations. Create the Secrets the manifests name, then apply:

openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -out signing-key.pem
kubectl -n ferrofed create secret generic ferrofed-secrets \
  --from-file=signing-key.pem
kubectl -n ferrofed create secret generic ferrofed-console-secrets \
  --from-file=client-secret=./console-client-secret
kubectl apply -k my-overlay/

A deployment with a contact-point issuer, or with the stored-query registry on backend = "postgres", also sets [store] in configmap.yaml and adds its connection string to ferrofed-secrets as store-url (--from-file=store-url), with store-ca.pem when its CA is not one of the platform’s roots (FerroFED’s own store). Every replica shares that one database.

The tree in the repository names the images by tag alone; only the release tarball pins the digests. To pin them yourself, add an images entry with the digest of the image you verified (Verifying the image).

The components

ComponentWhat it adds
consolethe operator console: a Deployment of one replica (its sessions live in its memory), its ConfigMap, its Service and a NetworkPolicy that admits the reverse proxy alone. It reaches the gateway’s Service, and each replica’s admin listener at ferrofed-0.ferrofed-admin and so on, for the configuration page
ingress, console-ingressan Ingress for the gateway and for the console, over TLS
gateway-api, console-routea Gateway API HTTPRoute for each, behind a Gateway whose HTTPS listener terminates TLS; use these or the Ingresses
service-monitora Prometheus Operator ServiceMonitor that scrapes each replica’s admin listener through the headless Service, with a client-credentials token of the scrape client, its secret from the ferrofed-scrape Secret (Metrics)
egressan egress NetworkPolicy: DNS, and the destinations you list

Behind either proxy, list the proxy’s addresses in server.trusted_proxies so the access record names the client’s own address, and give the proxy a body limit and a timeout at least server.body_limit and server.request_timeout (A production deployment).

Network policies

The base’s NetworkPolicy admits two sources to the gateway pods, and nothing else:

  • the gateway port: your reverse proxy’s namespace, and the console pods;
  • the admin port: your Prometheus pods, and the console pods.

A client inside the cluster that calls the gateway directly, past the proxy, needs a peer of its own under the gateway port. The policies hold only where the cluster’s network plugin enforces NetworkPolicy, and they add to client authentication, never replace it (Hardening).

Egress stays open in the base, because the gateway reaches its members, its identity services and its issuers wherever they are. The egress component closes it to DNS and the destinations you list, each one the configuration names: every member endpoint of registry.toml, the issuers of [auth] and [[trust.issuer]], the Audit Record Repository, each identity service a binding names, the token endpoint of each onward grant, the OTLP collector, FerroFED’s own store ([store]) or a revision PostgreSQL, and the usage report’s collector unless the report is off. A NetworkPolicy matches addresses, so a destination behind changing addresses needs a CIDR wide enough to hold them. A destination you leave out is refused: a member shows offline and a service unavailable in GET {base}/operator/dependencies.

Spread, disruption and stopping

The StatefulSet spreads its replicas over zones and over nodes (topologySpreadConstraints with ScheduleAnyway), so losing one zone or one node leaves a replica serving, while a cluster with one zone or one node still schedules both. The PodDisruptionBudget keeps one replica serving through a node drain.

A pod’s stop has two holds before the drain. The preStop hook sleeps for 5 seconds, matching server.drain_delay, while the endpoint controllers and the proxies stop sending the pod new requests; the gateway then turns readiness to 503, keeps accepting for the drain delay, and drains (Stopping without dropping a request). terminationGracePeriodSeconds: 50 outlasts the sleep, the delay, the drain and the bindings’ drain with 5 seconds of room, which scripts/checks/kubernetes-example.sh holds in CI. Change the sleep with the delay, and the grace period with either.

Reloading a changed configuration

The ConfigMaps keep fixed names, so kubectl apply -k updates them in place and no pod restarts. The kubelet then writes the new files into each pod’s mounted volume, within its sync period, which is about a minute by default (ConfigMaps). The gateway never watches its files, so tell each replica to reload once the files have changed. The image has no shell to send SIGHUP from, so ask each replica’s admin listener, with an access token that carries auth.product_scope:

for pod in $(kubectl -n ferrofed get pods -l app.kubernetes.io/component=gateway -o name); do
  kubectl -n ferrofed port-forward "$pod" 9464:9464 &
  forward=$!
  sleep 2
  curl -fsS -X POST -H "Authorization: Bearer $OPERATOR_TOKEN" http://127.0.0.1:9464/admin/reload
  kill "$forward"
done

Each replica reads the configuration again, checks it whole and applies it or refuses it, and its answer names what changed and what waits for a restart (Reloading the registry, Reloading over the admin listener). A key that needs a restart takes effect on the next rollout, kubectl -n ferrofed rollout restart statefulset/ferrofed. Under config.ui = "managed" the operator console saves, applies and confirms a revision, and every replica follows it with no reload by hand (Configuration revisions).

The console reads its configuration at start alone: after a change to ferrofed-console-config, restart it with kubectl -n ferrofed rollout restart deployment/ferrofed-console.