Install c8s

Prepare an external measured CDS, generate its component allowlist, and install c8s onto an existing confidential Kubernetes cluster.

Install c8s onto an existing confidential Kubernetes cluster after preparing its external Certificate Distribution Service (CDS). The CDS runs in its own confidential VM (CVM); Helm installs the cluster components that trust it.

If your node image already owns the installation and labels c8s-system with confidential.ai/baked=true, c8s install refuses a second installation. Use its signed launch configuration and fetch its kubeconfig.

Prerequisites

  • A Kubernetes 1.30 or newer cluster on AMD SEV-SNP or Intel TDX confidential nodes, with kubectl configured for it. Choose the matching deployment shape below.
  • A Kubernetes identity authorized to install and update c8s's cluster-scoped chart resources, including its RBAC resources, and to create or update component namespaces and apply their privileged pod-security labels. Permission to list nodes alone does not establish these powers.
  • Every node must publish at least one pod range in spec.podCIDR or spec.podCIDRs.
  • c8s, helm, kubectl, crane, openssl, and genisoimage on your workstation's PATH, plus access to the component registries. The installer uses crane to resolve component images and read their configurations.
  • CDS build outputs supplied by your image producer: disk.raw, manifest.json, and the matching boot artifacts from one build. The source entrypoint is images/cds-guest/build in the c8s checkout; its builder writes these together under output/cds. For SEV-SNP, retain the guest-smp<N>.igvm matching the chosen vCPU count. Obtain this complete bundle before starting; <CDS_MANIFEST_JSON> below names its manifest.json.
  • A VM provisioning mechanism for that CDS bundle that accepts an opkeydata configuration ISO and the platform-specific launch settings described below. The VM must be reachable from your workstation and cluster on its CDS client port, normally TCP 8443. CDS must reach each admitted node's inventory on TCP 1019, using addresses in your node CIDRs.
  • Vendor verification collateral available in the evidence, local cache, or through network access when retrieval is needed. A collateral retrieval failure returns verification exit code 3; resolve it before continuing.
  • An independently approved JSON image policy for the nodes CDS may admit, and the cluster's node, pod, and service CIDRs. Use complete launch identities; obtain them from approved image and launch artifacts. Use a measured-node bundle's peers.json as <NODE_POLICY_JSON> only when it describes exactly the nodes you intend CDS to admit. See the bundle's policy files.
  • An approved policy for the nodes hosting the router, used as <ROUTER_POLICY_JSON>. Reuse <NODE_POLICY_JSON> when its entire node set is acceptable for the router; otherwise prepare a policy restricted to the intended router nodes before starting.

Before generating the seed or preparing CDS, check every node:

kubectl get nodes \
  -o custom-columns='NAME:.metadata.name,POD_CIDR:.spec.podCIDR,POD_CIDRS:.spec.podCIDRs'

Each row must contain a pod CIDR in at least one of the two pod-range columns. Stop if any node has neither; resolve the cluster's pod-range publication before proceeding. Supplying --pod-cidr to CDS configuration does not satisfy this node-object requirement. Seed generation can succeed before the final install checks it.

To build the CLI from an existing c8s source checkout, use Go 1.27.3 or a compatible newer toolchain and run:

make install

This uses go install. Add go env GOBIN, or $(go env GOPATH)/bin when GOBIN is empty, to your PATH.

Choosing the install shape

Pass both --cvm-mode and --hardware-platform in each installation command:

  • bare-metal: self-managed nodes with the baked attestation API and NRI image-policy plugin. Confirm your prepared image against the node CVM launch contract. Its published artifacts supply this stack. Current image-owned installations follow the kubeconfig path above; an arbitrary stock confidential VM does not supply the required stack.
  • gke: the GKE confidential-node shape.
  • aks: Azure confidential nodes using the vTPM report path.

--hardware-platform selects sev-snp or tdx for the nodes. The external CDS selects its own platform separately. Each node CVM is one trust domain; see trust boundaries.

Choose release, namespace, image tag, optional components, and any -f values files before rendering the seed. Use the same choices for the final install. If another ingress owns host port 443, set router.hostPort.enabled: false in a values file and arrange reachable ingress to the router Service. Pass that file to both commands below. Computed installer values take precedence over your values files; see the CLI reference.

Prepare CDS and install

This procedure creates a singleton CDS. For a replicated CDS, use the CDS configuration and HA commands, including signed Genesis initialization, before installing cluster components.

Create the operator credential

umask 077
openssl ecparam -name prime256v1 -genkey -noout -out operator.key
openssl ec -in operator.key -pubout -out operator.pub

Keep operator.key private. The public key goes into the CDS configuration to authorize policy writes. The installer's --operator-keys supplies router route-verification keys; it does not change the external CDS's authority. CDS refuses a configuration with no operator keys.

Generate the component seed

c8s install --cvm-mode '<CVM_MODE>' --hardware-platform '<NODE_TEE>' \
  --image-tag '<IMAGE_TAG>' --operator-keys operator.pub \
  --allowlist-seed-out component-seed.json

Replace <CVM_MODE> with the shape above, <NODE_TEE> with sev-snp or tdx, and <IMAGE_TAG> with the component release tag you intend to install. This reads the cluster and registries, writes component-seed.json, and stops before installation. The seed describes the component images and launch settings that the image allowlist must admit.

Generate the measured CDS configuration

c8s cds config new --out cds-bootstrap --cluster-id '<CLUSTER_ID>' \
  --operator-keys operator.pub --allowlist-seed component-seed.json \
  --node-pins '<NODE_POLICY_JSON>' \
  --node-cidr '<NODE_CIDR>' --pod-cidr '<POD_CIDR>' --service-cidr '<SERVICE_CIDR>' \
  --image-manifest '<CDS_MANIFEST_JSON>' --platform sev-snp \
  --vcpus '<CDS_VCPU_COUNT>'

Choose <CLUSTER_ID> yourself, for example inference-prod, using an RFC 1123 label; it is the identity you assign this CDS configuration, not a value discovered from Kubernetes. Replace <NODE_POLICY_JSON> with the approved node-policy file and the three CIDRs with your cluster's ranges. Repeat each CIDR flag for additional ranges. Node inventory subnets must exclude pod and service ranges. <CDS_MANIFEST_JSON> is the exact CDS image's manifest; <CDS_VCPU_COUNT> is a launch shape that manifest supports. For a TDX CDS, use --platform tdx and omit --vcpus.

The new cds-bootstrap directory contains cds-config.yaml and cds-pin.json. The pin binds the CDS image and exact configuration bytes. Keep the host-readable configuration limited to public inputs; private keys and secrets do not belong on this disk.

Launch and verify the external CDS

Prepare the configuration disk's contents:

mkdir cds-opkeydata
cp cds-bootstrap/cds-config.yaml cds-opkeydata/pubkey
sha256sum cds-opkeydata/pubkey
genisoimage -quiet -rock -joliet -volid opkeydata \
  -output cds-opkeydata.iso cds-opkeydata/

Hand cds-opkeydata.iso and the CDS build bundle to your VM provisioning mechanism. Attach the ISO as the configuration disk and boot the CDS image selected above. For SEV-SNP, set the VM's HOST_DATA to the printed SHA-256 and use the selected vCPU count. The TDX boot path binds the configuration in RTMR[3]. The guest stages it at /run/confai/operator-pubkey; CDS checks its exact bytes against a self-report before serving.

After the VM is reachable, verify its identity, admitted node set, and operator keys:

c8s cds verify 'https://<CDS_HOST>:8443' \
  --image-policy-file cds-bootstrap/cds-pin.json \
  --served-policy-file '<NODE_POLICY_JSON>' --operator-keys operator.pub && \
c8s cds ca --url 'https://<CDS_HOST>:8443' \
  --image-policy-file cds-bootstrap/cds-pin.json --out mesh-ca.pem

Replace <CDS_HOST> with its reachable DNS name or IPv4 address, and adjust the port if needed. Use the same independently intended node policy as above. For HA, Genesis can override the configuration's initial node set, and later signed commands can change it: verify against the intended current set. A CDS identity pin alone does not check that set.

Require verification exit code 0 before fetching the CA or installing components. The && gates the CA read on that result. Any other verification status, including partial verification or unavailable collateral, stops this procedure. Continue only after the CA read also succeeds.

Retain mesh-ca.pem as the deployment's mesh CA anchor. Compare subsequent CA reads with your retained anchor before replacing it; a singleton restart replaces its volatile CA and secrets.

Install the cluster components

c8s install --cvm-mode '<CVM_MODE>' --hardware-platform '<NODE_TEE>' \
  --image-tag '<IMAGE_TAG>' --operator-keys operator.pub \
  --cds-address '<CDS_HOST>:8443' --cds-pin cds-bootstrap/cds-pin.json

Reuse the seed command's release, namespace, image tag, values files, and component options. The installer checks the named CDS configuration against the required component seed and the cluster's pod ranges. It runs helm upgrade --install with --wait by default. If Helm fails, inspect the retained resources, correct the cause, and rerun the command.

Verify the install

Check the default component namespaces:

kubectl get pods -n c8s-system
kubectl get pods -n c8s-router
kubectl get mutatingwebhookconfiguration c8s-pod-injector \
  -o jsonpath='{.webhooks[*].clientConfig.caBundle}'

Expect component pods to become ready and the webhook CA bundle to be nonempty. Substitute your release namespace and <RELEASE>-pod-injector name if you changed those defaults. The external CDS does not appear in either pod listing. Readiness confirms deployment health; it does not prove confidentiality.

From outside the cluster, verify the reachable router with its own approved node policy and the mesh CA fetched from the pinned CDS:

c8s verify 'https://<ROUTER_HOST>' --kind lb \
  --image-policy-file '<ROUTER_POLICY_JSON>' --mesh-ca mesh-ca.pem

<ROUTER_HOST> is your configured reachable router ingress. <ROUTER_POLICY_JSON> is the independently approved complete policy for the node images hosting the router, including required launch bindings and TDX registers. It is distinct from cds-pin.json. Expect exit code 0; treat partial verification (exit 4) as an incomplete check. Follow Verify a deployment to interpret the verdict and add workload, policy, and route checks; this command alone does not prove the complete live policy.

The router needs a signed route manifest before it can serve application requests. Continue with Run a confidential workload and Manage the allowlist. Applications serve their ordinary TCP/HTTP port; the injected mesh endpoint carries authenticated TLS and keeps mesh credentials private. A covered pod's injected init containers begin with c8s-mesh, c8s-cert, then c8s-cert-wait.

For failures, use Troubleshoot. To remove the installation, see Uninstall c8s.