Static Pod Deployment

Static pod deployment is appropriate for kubeadm-style environments and image-based control-plane management. It keeps the provider under kubelet management alongside the API server. In this model, kubelet, the container runtime, and local image availability become part of the KMS provider boot path.

For the model selection rationale see Deployment: Choosing A Model . For the user, group, and file ownership model see Deployment: Linux Identity Model .

Constraints

A static pod manifest is read from the host filesystem by kubelet. Static pods cannot depend on Kubernetes API objects such as ConfigMaps, Secrets, or ServiceAccounts. The protected API server may need the KMS provider before those API objects are reachable.

The plugin static pod mounts everything it needs from the host:

  • configuration file,
  • CA bundle,
  • configured auth material such as a JWT file, certificate chain, or PKCS#11 PIN file,
  • runtime socket directory,
  • optional local state directory.

Example Manifest

The maintained sample manifest lives at deploy/static-pod/bao-kms-provider.yaml in the repository. Replace the placeholder image digest with the verified digest from the selected release, and replace the supplemental group GID before deploying. Do not deploy a tag-only image reference.

apiVersion: v1
kind: Pod
metadata:
  name: bao-kms-provider
  namespace: kube-system
  labels:
    app.kubernetes.io/name: bao-kms-provider
    app.kubernetes.io/component: kms-provider
spec:
  # Required during early control-plane boot because CNI may not be available
  # when the provider has to reach OpenBao.
  hostNetwork: true
  priorityClassName: system-node-critical
  automountServiceAccountToken: false
  securityContext:
    runAsNonRoot: true
    runAsUser: 65532
    runAsGroup: 65532
    supplementalGroups:
      # Replace with the host openbao-kms-socket GID from:
      # getent group openbao-kms-socket
      - 1234
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: bao-kms-provider
      # Replace with the verified image digest from the selected release.
      image: ghcr.io/dc-tec/bao-kms-provider@sha256:0000000000000000000000000000000000000000000000000000000000000000
      imagePullPolicy: IfNotPresent
      args:
        - serve
        - --config=/etc/openbao-kms/config.yaml
      ports:
        - name: metrics
          containerPort: 8081
          protocol: TCP
        - name: health
          containerPort: 8082
          protocol: TCP
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop:
            - ALL
      volumeMounts:
        - name: config
          mountPath: /etc/openbao-kms/config.yaml
          readOnly: true
        - name: tls
          mountPath: /etc/openbao-kms/tls
          readOnly: true
        - name: jwt
          mountPath: /var/lib/openbao-kms/identity.jwt
          readOnly: true
        - name: run
          mountPath: /run/openbao-kms
        - name: state
          mountPath: /var/lib/openbao-kms/state
      livenessProbe:
        httpGet:
          host: 127.0.0.1
          path: /live
          port: 8082
        initialDelaySeconds: 5
        periodSeconds: 10
      readinessProbe:
        httpGet:
          host: 127.0.0.1
          path: /ready
          port: 8082
        initialDelaySeconds: 5
        periodSeconds: 10
  volumes:
    - name: config
      hostPath:
        path: /etc/openbao-kms/config.yaml
        type: File
    - name: tls
      hostPath:
        path: /etc/openbao-kms/tls
        type: Directory
    - name: jwt
      hostPath:
        path: /var/lib/openbao-kms/identity.jwt
        type: File
    - name: run
      hostPath:
        path: /run/openbao-kms
        type: Directory
    - name: state
      hostPath:
        path: /var/lib/openbao-kms/state
        type: Directory

The final manifest depends on the host socket group GID and the released image digest recorded for the selected release. The sample uses UID and GID 65532:65532, matching the distroless non-root image user.

Pod Hardening

SettingPurpose
hostNetwork: trueAvoids CNI availability as an early-boot dependency.
automountServiceAccountToken: falsePrevents accidental dependency on protected-cluster ServiceAccount tokens.
runAsNonRoot: true and runAsUser: 65532Runs as the distroless non-root image user.
supplementalGroupsGives the container access to the host socket group without exposing provider auth material to the API server.
seccompProfile: RuntimeDefaultUses the runtime default syscall filter.
allowPrivilegeEscalation: falseBlocks privilege escalation inside the container.
readOnlyRootFilesystem: trueForces writes into explicit hostPath mounts.
capabilities.drop: [ALL]Runs without Linux capabilities.
immutable image digestPrevents image drift during recovery.
liveness and readiness probesLets kubelet report provider process and dependency health.

Image Availability

For air-gapped or bootstrap-sensitive control planes, preload the image on every control-plane node:

  • use immutable image digests from the selected release,
  • avoid Always pulls in recovery-sensitive deployments,
  • keep the previous image available for rollback,
  • document image import steps for node replacement.

imagePullPolicy: IfNotPresent is appropriate only when the exact digest has already been imported or is reliably pullable during node recovery. Do not rely on tag movement for upgrade or rollback.

Host Preparation

Every control-plane node must have:

/etc/openbao-kms/config.yaml
/etc/openbao-kms/tls/ca.crt
/var/lib/openbao-kms/identity.jwt
/etc/openbao-kms/client/client-chain.pem
/etc/openbao-kms/pkcs11/pin
/var/lib/openbao-kms/state
/run/openbao-kms

The JWT path is needed only for auth.method: jwt. PKCS#11 certificate-auth deployments should instead mount the configured certificate chain, PKCS#11 PIN file, and PKCS#11 module path.

The API server must be able to access the socket created under /run/openbao-kms. The container user must own the socket directory, or an equally narrow provider-only identity must be the only writer. The API server’s socket access group needs execute permission on the directory and write permission on kms.sock; it must not have write permission on the directory itself.

The provider configuration used by the static pod sets server.socketGroup to the same numeric host GID listed in supplementalGroups. See deploy/config/provider-static-pod.yaml for the matching configuration sample.

Every provider in a multi-control-plane cluster must use the same identity-bearing configuration: provider name, cluster ID, OpenBao instance ID, Transit mount ID, key lineage ID, Transit mount path, and Transit key name. Multi-control-plane validation exercises this model with one node-local provider per API server.

kubeadm Placement

Typical kubeadm static pod path:

/etc/kubernetes/manifests/bao-kms-provider.yaml

The kubelet watches this directory and starts the static pod.

Bootstrap Risks

Static pod mode depends on:

  • kubelet,
  • the container runtime,
  • local image availability,
  • hostPath mounts,
  • container networking and DNS,
  • file permissions inside the container.

If kubelet or the container runtime is broken, the KMS plugin may not start and the API server may be unable to decrypt existing resources.

The provider retries its initial status probe for bootstrap.graceTimeout before exiting. Static pod deployments should keep this enabled because auth material, container networking, DNS, OpenBao availability, and clock sync can settle after the container process starts.

For single-node control planes, systemd is usually safer. See Deployment: Choosing A Model .

Verification

Before enabling API server encryption:

  1. Place the static pod manifest under /etc/kubernetes/manifests/.
  2. Confirm the pod is running through kubelet or container runtime tooling.
  3. Confirm /run/openbao-kms/kms.sock exists on the host.
  4. Run bao-kms-provider doctor on the host or in an equivalent debug container.
  5. Confirm kube-apiserver can connect to the socket.

After the Kubernetes EncryptionConfiguration is staged, include it in doctor:

bao-kms-provider doctor \
  --config /etc/openbao-kms/config.yaml \
  --encryption-config /etc/kubernetes/encryption-config.yaml

Source References