Getting Started
PodMotion migrates live Kubernetes pods between nodes within a single cluster
using CRIU checkpoint/restore. This guide takes you from a fresh cluster to a
verified migration driven entirely by the PodMigration custom resource and
kubectl apply.
PodMotion is alpha software (v0.1.0-alpha). It is not production-ready.
One published end-to-end proof runs on amd64 / Linux 6.17 /
containerd 1.7.24 / Kubernetes v1.32.0 / kindnet (host-routed, no VXLAN overlay) in
BestEffort transfer mode. It proves a complete live cold-path pod migration with the pod
restored on another node and reachable at its original pod IP (L3/L4 IP reachability) — it
does not itself prove live TCP-connection survival. Separately, seq_delta=0 was first proven
at Sprint 46 for a single held connection in Strict mode on a different, KVM/Cilium cluster —
see How Migration Works for that proof scope. An earlier
arm64/kind PoC (Ubuntu 24.04 / kernel 6.8 / Flannel VXLAN) exists for history only and is not the
current proven substrate.
The only shipped user interface is the PodMigration CRD applied with
kubectl apply. There is no kubectl podmotion plugin — a CLI is described
in docs/kubectl-migrate-proposal.md (Status: Draft) but is not shipped. See
the CRD / Interface Reference for the proposal status.
Prerequisites
Before you begin, confirm every node and the cluster meet the baselines from the project README:
- Kubernetes 1.27+ and a matching
kubectl(Supported floor is 1.27+, per ADR-0104. Kubernetes 1.25 and 1.26 are not supported.) - Go 1.24.6+ and Docker 17.03+ (only to build images)
- Linux kernel 5.15+ on every node (Ubuntu 22.04 HWE floor); the authoritative current end-to-end proof (Sprint 33/34) ran on kernel 6.17 (amd64). The earlier arm64/kind PoC ran on kernel 6.8.
PostCopymode additionally requiresCONFIG_USERFAULTFD=yon the destination kernel- CRIU 4.x installed on every node (4.2 used for iterative pre-copy)
- bpfman DaemonSet running on every node before the PodMotion agent starts (see bpfman below)
kindnet (host-routed, no VXLAN overlay) is the CNI of the current authoritative end-to-end
proof (Sprint 33/34). The earlier arm64/kind Flannel VXLAN PoC is historical only — not the
current proven substrate. TCP source-IP preservation (spec.sourcePodIP) is Calico/Cilium-only
and is a roadmap item.
bpfman
bpfman is an eBPF program manager required by the PodMotion node agent to load TC egress programs. The node agent uses bpfman to attach its TC egress hook to each pod's network interface — without bpfman, the eBPF relay that enables TCP sequence translation cannot load.
bpfman must be running on every node as a DaemonSet before the PodMotion agent starts. Install it from the upstream project: bpfman.io — Installation.
The authoritative current end-to-end proof (Sprint 33/34) ran on kernel 6.17 (amd64, kindnet). An earlier arm64/kind PoC ran on kernel 6.8 (Flannel VXLAN). Kernel 5.15 is the functional floor for the bpfman TC egress attach point. Kernels at the floor that are neither 6.17 nor the earlier 6.8 PoC kernel have not been validated against any PodMotion proof run.
Install
Install the CRDs and deploy the manager and node-agent DaemonSet:
# Install the 5 CRDs (podmigrations, migrationpolicies,
# nodemigrationcapacities, migrationcheckpoints, migrationwebhookconfigs)
make install
# Deploy the controller-manager
make deploy IMG=<your-registry>/podmotion:<tag>
There is no single-file install.yaml. PodMotion is pre-release, alpha software:
there are no published container images, no Helm repository, and no GitHub remote yet.
The deploy path is Helm against the in-tree chart, not a flat manifest, and CRDs must be
applied separately (the chart's crds/ directory is empty). The intended
build-and-deploy-from-source flow, once the repository is published:
git clone https://github.com/podmotion-io/podmotion.git
cd podmotion
# Build the operator + agent binaries (linux/amd64) and the kind node image
make build-linux
make kind-image KIND_NODE_IMG=podmotion-node:dev
# edit images/kind/kind-config.yaml to reference podmotion-node:dev, then:
make kind-create KIND_CLUSTER=podmotion-dev
# Build and load the operator + agent images
docker build -f images/operator/Dockerfile -t podmotion-operator:dev .
docker build -f images/agent/Dockerfile -t podmotion-agent:dev .
make kind-load IMG_OPERATOR=podmotion-operator:dev IMG_AGENT=podmotion-agent:dev \
KIND_CLUSTER=podmotion-dev
# CRDs must be applied separately (the Helm chart's crds/ directory is empty)
KUBECONFIG=/tmp/podmotion-dev.kubeconfig kubectl apply -f config/crd/bases/
# Install via Helm (the authoritative deploy path — there is no flat install manifest)
KUBECONFIG=/tmp/podmotion-dev.kubeconfig \
helm upgrade --install podmotion charts/podmotion/ \
--namespace podmotion-system --create-namespace \
--set operator.image.repository=podmotion-operator --set operator.image.tag=dev \
--set operator.image.pullPolicy=Never \
--set agent.image.repository=podmotion-agent --set agent.image.tag=dev \
--set agent.image.pullPolicy=Never \
--set operator.replicas=1 --set webhook.certManager.enabled=false \
--set scheduler.enabled=false --set installCRDs=false \
--set checkpoint.transferMode="" --set checkpoint.registry.url=""The clone URL matches the Go module path, but no public remote exists yet — treat this as
the intended post-publication path. The checkpoint.transferMode="" /
checkpoint.registry.url="" overrides suppress two chart-emitted operator flags
(--transfer-mode, --checkpoint-registry) that the current operator binary does not
recognize.
The Helm chart is available in the source repository at charts/podmotion and has not been
published to a public chart repository. Install from the local chart path:
helm install podmotion ./charts/podmotion -n podmotion-system --create-namespaceA published chart repository is planned for M26.
SECURITY/INSTALL TRAP — cert-manager webhook: webhook.certManager.enabled defaults to
true but the chart ships no Certificate or Issuer resource — a vanilla helm install
will crashloop the webhook pod immediately. For kind/dev clusters, disable it explicitly:
helm install podmotion ./charts/podmotion -n podmotion-system --create-namespace \
--set webhook.certManager.enabled=falsecert-manager ACME issuance is planned under ADR-0159 (Draft — plan, not a commitment) and is not implemented in v0.1.0-alpha. See Security §4 for the full webhook TLS posture.
SECURITY — mTLS on operator↔agent gRPC (:9090) fails open in v0.1.0-alpha: enabling mTLS on the operator-to-agent gRPC channel does not guarantee enforcement — the channel defaults to plaintext and the mTLS code path is opt-in but does not enforce mutual authentication in this release (ADR-0161 C4, Draft — plan, not a commitment). Enabling the flag does not make the channel ciphertext-only. See Security §4 for the full transport security posture.
A successful install gives you the controller-manager Deployment, the node-agent
DaemonSet, the admission webhooks, and the five migration.podmotion.io CRDs.
Node agent configuration is environment-variable only — the agent binary parses no
command-line flags. The only required variable is NODE_NAME, supplied via the Kubernetes
downward API in the DaemonSet template (the agent exits non-zero if it is unset). KUBECONFIG
is read only as an out-of-cluster fallback. The agent's gRPC MigrationAgentService listens
on a fixed port, :9090 (mTLS in production). Any --grpc-port / --node-name /
--metrics-port container args present in older chart templates are inert and ignored by
the binary.
SECURITY WARNING: The inter-component gRPC endpoint at :9090 defaults to plaintext when
TLS certificates are not configured. Enable mTLS before deploying to any non-isolated environment.
See Security Architecture for configuration steps.
Your First Migration
A minimal PodMigration requires podName and podNamespace:
apiVersion: migration.podmotion.io/v1alpha1
kind: PodMigration
metadata:
name: migrate-my-app
namespace: default
spec:
podName: my-app-pod # required
podNamespace: default # required
targetNodeName: worker-2 # optional; scheduler picks if empty
mode: PreCopy # PreCopy (default) | PostCopy | Cold
# tcpPreservationMode defaults to None (no TCP continuity).
# Opt in with Strict or BestEffort — see below.
Apply it:
kubectl apply -f migrate-my-app.yaml
The CRD has shortName pm and category podmotion. List migrations with
kubectl get podmigration or kubectl get pm. Note: podmotion is the
category label — kubectl get podmotion resolves by category, not by resource
name; use podmigration or pm for reliable listing.
TCP connection continuity is opt-in
spec.tcpPreservationMode defaults to None, which skips TCP
verification entirely — in-flight TCP connections are not preserved. This
is the conservative default (ADR-0041). Zero-connection-loss behavior is
opt-in.
To preserve TCP connections, set the mode explicitly:
| Mode | Behavior |
|---|---|
None (default) | No TCP continuity; TCPVerifying phase skipped. Stateless workloads. |
Strict | Hard rollback if TCPSequenceContinuityVerified=False (ADR-0019 Amendment A). |
BestEffort | Warning condition only; no rollback. |
The full TCP-first pipeline runs only for Strict and BestEffort.
Verify It Worked
Watch the migration status:
kubectl get podmigration migrate-my-app -o yaml
Track status.phase. The full MigrationPhase enum has 22 values — the
list below is a partial selection of the most observable phases. See
api/v1alpha1/podmigration_types.go for the complete enum.
Common phases: Pending, Validating, Checkpointing, PreCopyMemory,
SocketInventory, ZeroWindowArm, OverlayHandoff, Transferring,
Restoring, RestoreSocketReattach, DisengageHold, TCPVerifying,
ServiceVerifying, CutoverComplete, Complete, DryRunComplete,
Failed, RollingBack.
Inspect status.conditions. The canonical condition types are:
TCPVerified— aggregate TCP gate (only populated forStrict/BestEffort)TCPSequenceContinuityVerified— the condition that reportsseq_delta=0(zero TCP sequence delta),TruewhenStrictmode holds a connection byte-exact across a migration. First proven at Sprint 46 for a single held connection; the broader multi-flow guarantee is still ahead. (Separately, the BestEffort/kindnet end-to-end proof above verifies IP reachability after restore, not TCP-connection survival.)TrafficVerified— destination is serving traffic; this is the gate that authorizes source-pod deletion (ADR-0012). Source pod is never deleted before TrafficVerified=True.
See the CRD Reference for the authoritative condition list.
For a dry run that produces a DryRunEstimate without moving the pod, set
spec.dryRun: true; the migration ends in phase DryRunComplete.
Security
PodMotion is a privileged operator. Read this before installing on any cluster you care about.
- Privileged node-agent DaemonSet (
charts/podmotion/templates/daemonset.yaml): a node-root surface area required for live CRIU checkpoint-restore and eBPF relay. The mainagentcontainer itself runsprivileged: falsewithdrop: [ALL], adding back onlySYS_PTRACE,SYS_ADMIN,CHECKPOINT_RESTORE,DAC_READ_SEARCH,SYS_RESOURCE,NET_ADMIN,NET_RAW,KILL(plusBPF/PERFMONon kernels ≥5.8), withallowPrivilegeEscalation: trueand a read-write mount of the containerd Unix socket. The netns-broker sidecar is the one genuinelyprivileged: true, long-lived container; two short-lived init containers (install-apparmor-profile,init-checkpoint-tmpfs) also runprivileged: true. A CRIU-scoped seccomp profile ships in the chart but is opt-in (disabled by default). The DaemonSet tolerates all taints (operator: Exists), so it runs on every node including control-plane nodes. ADR-0148 (Accepted, unimplemented) replaces the catch-all tolerations with a dedicated migration-node pool (nodeSelectorpodmotion.io/migration-node=true+ matching NoSchedule taint/toleration) to bound the blast radius off control-plane nodes. It hostPath-mounts the host CRIU binary (/usr/sbin/criu, read-only). See the Node Agent Architecture page for the full privilege model. ADR-0148 (Accepted, unimplemented) defines the post-1.0 hardening roadmap: further capability reduction, read-only containerd socket, seccomp on by default, and user namespace isolation. - Checkpoints are plaintext. Checkpoints are written to tmpfs at
/run/podmotion/checkpoints/<ref>/(mode 0700, wiped on node reboot). Checkpoint encryption is NOT implemented. ADR-0029 (which recorded encryption as on-by-default) was never wired into the CRIU writer and is Superseded by ADR-0160 (Draft, the authoritative forward plan: none/aes-gcm/aws-kms/gcp-kms/azure-kms/vault providers). See Security Architecture. CRIU checkpoints are full memory dumps and may contain secrets, tokens, and in-flight data. Protect the checkpoint store accordingly. - Transport security is opt-in. The agent gRPC endpoint (
:9090) defaults to plaintext when TLS certificates are not configured. Enable mTLS before deploying to any non-isolated environment — see the security warning above and Security Architecture for configuration steps. - The controller-manager itself is hardened by contrast (
runAsNonRoot, seccompRuntimeDefault, read-only root filesystem, all capabilities dropped).
See SECURITY.md
in the repository for the full security policy and disclosure process.