Skip to main content

Install on Kubernetes (Kustomize)

The Kubernetes bundle is plain manifests with Kustomize, applied with kubectl apply -k. There is no Helm chart. The bundle lives in deploy/k8s of the repository. Use it when you already run a cluster; for one server without a cluster use the VM installer or the Docker Compose bundle.

:::info Status The manifests are validated statically and in a local kind cluster. The media path (SIP and RTP through a real cloud firewall and NAT) is not proven on the internet yet. Read the limits in deploy/k8s/README.md before you put live trunks on it. :::

Requirements​

  • Kubernetes 1.30 or newer, with a default StorageClass (three ReadWriteOnce volumes for the bundled databases).
  • A CNI that enforces NetworkPolicy (Calico, Cilium, GKE Dataplane V2). Without one, the policies are accepted and ignored.
  • At least one media node: a node with a public address (or 1:1 NAT) that you label mpbx.io/media=true. SIP and RTP run host-networked there.
  • kubectl and kustomize (or kubectl with its embedded Kustomize).
  • Your own container images of one release, pinned by digest for production.

Install​

Set the three values the overlay needs first. The overlay file has the same three places marked 1., 2. and 3..

export MEDIA_NODE="${MEDIA_NODE:?set to the name of the media node}"
export MEDIA_NODE_IP="${MEDIA_NODE_IP:?set to the internal IP of the media node}"
export MPBX_PUBLIC_URL="${MPBX_PUBLIC_URL:?set to the https URL users open, for example https://pbx.example.com}"

Then edit deploy/k8s/overlays/small/kustomization.yaml:

  1. images: names the mpbx, mpbx-media and mpbx-rtpengine images of your release. Use digest: sha256:... instead of newTag in production.
  2. MPBX_PUBLIC_URL= in configMapGenerator is the address of the web client. It is used for the first-admin setup link.
  3. db-from-media-nodes contains the internal address of the media node as a /32.

Apply the bundle:

set -eu
: "${MEDIA_NODE:?set MEDIA_NODE to the media node name}"
: "${MPBX_PUBLIC_URL:?set MPBX_PUBLIC_URL}"
kubectl label node "$MEDIA_NODE" mpbx.io/media=true --overwrite
kubectl apply -k deploy/k8s/overlays/small
kubectl -n mpbx wait --for=condition=complete job/mpbx-init --timeout=5m

Read the recovery key and the first-admin setup link. They are shown once:

set -eu
kubectl -n mpbx logs job/mpbx-init
kubectl -n mpbx delete job mpbx-init

Write the recovery key down in a password manager or on paper. It is not stored anywhere in the cluster. Without it, a lost Secret means lost trunk passwords, API keys and encrypted recordings. Back up the Secret mpbx/mpbx-secrets with your cluster backups, and take passphrase-encrypted PBX backups as described in Backup and restore. Those backups include the master key.

Open the setup link and continue with the first-run steps.

The API and SIP pods usually restart once or twice on the first install while Postgres starts. That is expected.

Check the install​

set -eu
kubectl -n mpbx get pods
kubectl -n mpbx rollout status deploy/mpbx-api --timeout=5m
kubectl -n mpbx port-forward svc/mpbx-api 8080:80 >/dev/null 2>&1 &
pf=$!
trap 'kill "$pf" 2>/dev/null || true' EXIT
sleep 3
curl -fsS http://127.0.0.1:8080/api/v1/version

The API answers only after its database migrations ran. The /healthz liveness endpoint is on the metrics port (9464), not on the API port.

Re-applying and upgrading​

kubectl apply -k can be run again at any time. The init Job creates the Secret only if it is absent, so re-applying never rotates a key. A Job's pod template is immutable: if mpbx-init still exists when you change the image, delete it first (kubectl -n mpbx delete job mpbx-init).

Upgrade and rollback are scripted in deploy/k8s/ops/upgrade and scripts/upgrade.sh. Run --dry-run first. See Upgrade and rollback.

Ports and firewall​

WherePortWhy
Ingress or load balancer443/tcpWeb client, API, WebSocket. The optional Ingress component (deploy/k8s/components/ingress) serves it.
Media node (cloud firewall)5060/udp and 5060/tcpSIP. Restrict to your trunks and sites where you can.
Media node (cloud firewall)40000-50000/udpRTP.

Never open 5432, 6379, 8123, 9000 or 22222 (rtpengine's control port has no authentication). The bundle creates no public Service for them.

Dry run without a cluster​

To check the manifests without a cluster, render the overlay:

set -eu
kustomize build deploy/k8s/overlays/small >/dev/null
echo "overlay renders"

deploy/k8s/scripts/validate.sh runs every overlay through kubeconform and the manifest invariants (non-root, read-only root file system, Pod Security exceptions).

Uninstall​

kubectl delete -k deploy/k8s/overlays/small

This removes the workloads. Persistent volumes and the Secret may remain; delete them only after your backup is verified.