mirror of
https://github.com/kata-containers/kata-containers.git
synced 2026-03-17 18:22:14 +00:00
Compare commits
18 Commits
topic/kata
...
topic/re-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f97388b0d9 | ||
|
|
481aed7886 | ||
|
|
d9d1073cf1 | ||
|
|
a786582d0b | ||
|
|
cf7f340b39 | ||
|
|
7958be8634 | ||
|
|
7700095ea8 | ||
|
|
472b50fa42 | ||
|
|
f639c3fa17 | ||
|
|
e120dd4cc6 | ||
|
|
976df22119 | ||
|
|
a3c4e0b64f | ||
|
|
83a0bd1360 | ||
|
|
02ed4c99bc | ||
|
|
d37db5f068 | ||
|
|
f1ca547d66 | ||
|
|
6d0bb49716 | ||
|
|
282014000f |
2
.github/workflows/basic-ci-amd64.yaml
vendored
2
.github/workflows/basic-ci-amd64.yaml
vendored
@@ -26,8 +26,6 @@ jobs:
|
||||
matrix:
|
||||
containerd_version: ['active']
|
||||
vmm: ['dragonball', 'cloud-hypervisor', 'qemu-runtime-rs']
|
||||
# TODO: enable me when https://github.com/containerd/containerd/issues/11640 is fixed
|
||||
if: false
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
CONTAINERD_VERSION: ${{ matrix.containerd_version }}
|
||||
|
||||
4
.github/workflows/basic-ci-s390x.yaml
vendored
4
.github/workflows/basic-ci-s390x.yaml
vendored
@@ -26,8 +26,6 @@ jobs:
|
||||
matrix:
|
||||
containerd_version: ['active']
|
||||
vmm: ['qemu-runtime-rs']
|
||||
# TODO: enable me when https://github.com/containerd/containerd/issues/11640 is fixed
|
||||
if: false
|
||||
runs-on: s390x-large
|
||||
env:
|
||||
CONTAINERD_VERSION: ${{ matrix.containerd_version }}
|
||||
@@ -48,7 +46,7 @@ jobs:
|
||||
TARGET_BRANCH: ${{ inputs.target-branch }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: bash tests/integration/cri-containerd/gha-run.sh
|
||||
run: bash tests/integration/cri-containerd/gha-run.sh install-dependencies
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
|
||||
75
.github/workflows/build-helm-image.yaml
vendored
75
.github/workflows/build-helm-image.yaml
vendored
@@ -1,75 +0,0 @@
|
||||
name: Build helm multi-arch image
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run every Sunday at 12:00 UTC (12 hours after kubectl image build)
|
||||
- cron: '0 12 * * 0'
|
||||
workflow_dispatch:
|
||||
# Allow manual triggering
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'tools/packaging/helm/Dockerfile'
|
||||
- '.github/workflows/build-helm-image.yaml'
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
REGISTRY: quay.io
|
||||
IMAGE_NAME: kata-containers/helm
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
name: Build and push multi-arch image
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||
|
||||
- name: Login to Quay.io
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ vars.QUAY_DEPLOYER_USERNAME }}
|
||||
password: ${{ secrets.QUAY_DEPLOYER_PASSWORD }}
|
||||
|
||||
- name: Get helm version
|
||||
id: helm-version
|
||||
run: |
|
||||
HELM_VERSION=$(curl -s https://api.github.com/repos/helm/helm/releases/latest | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/')
|
||||
echo "version=${HELM_VERSION}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Generate image metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=raw,value=latest
|
||||
type=raw,value={{date 'YYYYMMDD'}}
|
||||
type=raw,value=${{ steps.helm-version.outputs.version }}
|
||||
type=sha,prefix=
|
||||
|
||||
- name: Build and push multi-arch image
|
||||
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0
|
||||
with:
|
||||
context: tools/packaging/helm/
|
||||
file: tools/packaging/helm/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
6
.github/workflows/release.yaml
vendored
6
.github/workflows/release.yaml
vendored
@@ -284,15 +284,11 @@ jobs:
|
||||
echo "${QUAY_DEPLOYER_PASSWORD}" | helm registry login quay.io --username "${QUAY_DEPLOYER_USERNAME}" --password-stdin
|
||||
echo "${GITHUB_TOKEN}" | helm registry login ghcr.io --username "${GITHUB_ACTOR}" --password-stdin
|
||||
|
||||
- name: Push helm charts to the OCI registries
|
||||
- name: Push helm chart to the OCI registries
|
||||
run: |
|
||||
release_version=$(./tools/packaging/release/release.sh release-version)
|
||||
# Push kata-deploy chart
|
||||
helm push "kata-deploy-${release_version}.tgz" oci://quay.io/kata-containers/kata-deploy-charts
|
||||
helm push "kata-deploy-${release_version}.tgz" oci://ghcr.io/kata-containers/kata-deploy-charts
|
||||
# Push kata-lifecycle-manager chart
|
||||
helm push "kata-lifecycle-manager-${release_version}.tgz" oci://quay.io/kata-containers/kata-deploy-charts
|
||||
helm push "kata-lifecycle-manager-${release_version}.tgz" oci://ghcr.io/kata-containers/kata-deploy-charts
|
||||
|
||||
publish-release:
|
||||
name: publish-release
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
# Kata Containers Lifecycle Management
|
||||
|
||||
## Overview
|
||||
|
||||
Kata Containers lifecycle management in Kubernetes consists of two operations:
|
||||
|
||||
1. **Installation** - Deploy Kata Containers to cluster nodes
|
||||
2. **Upgrades** - Update Kata Containers to newer versions without disrupting workloads
|
||||
|
||||
The Kata Containers project provides two Helm charts to address these needs:
|
||||
|
||||
| Chart | Purpose |
|
||||
|-------|---------|
|
||||
| `kata-deploy` | Initial installation and configuration |
|
||||
| `kata-lifecycle-manager` | Orchestrated rolling upgrades with verification |
|
||||
|
||||
---
|
||||
|
||||
## Installation with kata-deploy
|
||||
|
||||
The `kata-deploy` Helm chart installs Kata Containers across all (or selected) nodes using a Kubernetes DaemonSet. When deployed, it:
|
||||
|
||||
- Installs Kata runtime binaries on each node
|
||||
- Configures the container runtime (containerd) to use Kata
|
||||
- Registers RuntimeClasses (`kata-qemu-nvidia-gpu-snp`, `kata-qemu-nvidia-gpu-tdx`, `kata-qemu-nvidia-gpu`, etc.)
|
||||
|
||||
After installation, workloads can use Kata isolation by specifying `runtimeClassName: kata-qemu-nvidia-gpu-snp` (or another Kata RuntimeClass) in their pod spec.
|
||||
|
||||
---
|
||||
|
||||
## Upgrades with kata-lifecycle-manager
|
||||
|
||||
### The Problem
|
||||
|
||||
Standard `helm upgrade kata-deploy` updates all nodes simultaneously via the DaemonSet. This approach:
|
||||
|
||||
- Provides no per-node verification
|
||||
- Offers no controlled rollback mechanism
|
||||
- Can leave the cluster in an inconsistent state if something fails
|
||||
|
||||
### The Solution
|
||||
|
||||
The `kata-lifecycle-manager` Helm chart uses Argo Workflows to orchestrate upgrades with the following guarantees:
|
||||
|
||||
| Guarantee | Description |
|
||||
|-----------|-------------|
|
||||
| **Sequential Processing** | Nodes are upgraded one at a time |
|
||||
| **Per-Node Verification** | A user-provided pod validates Kata functionality after each node upgrade |
|
||||
| **Fail-Fast** | If verification fails, the workflow stops immediately |
|
||||
| **Automatic Rollback** | On failure, Helm rollback is executed and the node is restored |
|
||||
|
||||
### Upgrade Flow
|
||||
|
||||
For each node in the cluster:
|
||||
|
||||
1. **Cordon** - Mark node as unschedulable
|
||||
2. **Drain** (optional) - Evict existing workloads
|
||||
3. **Upgrade** - Run `helm upgrade kata-deploy` targeting this node
|
||||
4. **Wait** - Ensure kata-deploy DaemonSet pod is ready
|
||||
5. **Verify** - Run verification pod to confirm Kata works
|
||||
6. **Uncordon** - Mark node as schedulable again
|
||||
|
||||
If verification fails on any node, the workflow:
|
||||
- Rolls back the Helm release
|
||||
- Uncordons the node
|
||||
- Stops processing (remaining nodes are not upgraded)
|
||||
|
||||
### Verification Pod
|
||||
|
||||
Users must provide a verification pod that tests Kata functionality. This pod:
|
||||
|
||||
- Uses a Kata RuntimeClass
|
||||
- Is scheduled on the specific node being verified
|
||||
- Runs whatever validation logic the user requires (smoke tests, attestation checks, etc.)
|
||||
|
||||
**Basic GPU Verification Example:**
|
||||
|
||||
For clusters with NVIDIA GPUs, the CUDA VectorAdd sample provides a more comprehensive verification:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: ${TEST_POD}
|
||||
spec:
|
||||
runtimeClassName: kata-qemu-nvidia-gpu-snp # or kata-qemu-nvidia-gpu-tdx
|
||||
restartPolicy: Never
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: ${NODE}
|
||||
containers:
|
||||
- name: cuda-vectoradd
|
||||
image: nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda12.5.0-ubuntu22.04
|
||||
resources:
|
||||
limits:
|
||||
nvidia.com/pgpu: "1"
|
||||
memory: 16Gi
|
||||
```
|
||||
|
||||
This verifies that GPU passthrough works correctly with the upgraded Kata runtime.
|
||||
|
||||
The placeholders `${NODE}` and `${TEST_POD}` are substituted at runtime.
|
||||
|
||||
---
|
||||
|
||||
## Demo Recordings
|
||||
|
||||
| Demo | Description | Link |
|
||||
|------|-------------|------|
|
||||
| Sunny Path | Successful upgrade from 3.24.0 to 3.25.0 | [TODO] |
|
||||
| Rainy Path | Failed verification triggers rollback | [TODO] |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [kata-deploy Helm Chart](tools/packaging/kata-deploy/helm-chart/README.md)
|
||||
- [kata-lifecycle-manager Helm Chart](tools/packaging/kata-deploy/helm-chart/kata-lifecycle-manager/README.md)
|
||||
- [kata-lifecycle-manager Design Document](docs/design/kata-lifecycle-manager-design.md)
|
||||
@@ -28,15 +28,13 @@ Bug fixes are released as part of `MINOR` or `MAJOR` releases only. `PATCH` is a
|
||||
|
||||
## Release Process
|
||||
|
||||
### Bump the `VERSION` and `Chart.yaml` files
|
||||
### Bump the `VERSION` and `Chart.yaml` file
|
||||
|
||||
When the `kata-containers/kata-containers` repository is ready for a new release,
|
||||
first create a PR to set the release in the [`VERSION`](./../VERSION) file and update the
|
||||
`version` and `appVersion` in the following `Chart.yaml` files:
|
||||
- [`kata-deploy/Chart.yaml`](./../tools/packaging/kata-deploy/helm-chart/kata-deploy/Chart.yaml)
|
||||
- [`kata-lifecycle-manager/Chart.yaml`](./../tools/packaging/kata-deploy/helm-chart/kata-lifecycle-manager/Chart.yaml)
|
||||
|
||||
Have the PR merged before proceeding.
|
||||
`version` and `appVersion` in the
|
||||
[`Chart.yaml`](./../tools/packaging/kata-deploy/helm-chart/kata-deploy/Chart.yaml) file and
|
||||
have it merged.
|
||||
|
||||
### Lock the `main` branch
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ Kata Containers design documents:
|
||||
- [Design for direct-assigned volume](direct-blk-device-assignment.md)
|
||||
- [Design for core-scheduling](core-scheduling.md)
|
||||
- [Virtualization Reference Architecture](kata-vra.md)
|
||||
- [Design for kata-lifecycle-manager Helm chart](kata-lifecycle-manager-design.md)
|
||||
---
|
||||
|
||||
- [Design proposals](proposals)
|
||||
|
||||
@@ -1,502 +0,0 @@
|
||||
# Kata Containers Lifecycle Manager Design
|
||||
|
||||
## Summary
|
||||
|
||||
This document proposes a Helm chart-based orchestration solution for Kata Containers that
|
||||
enables controlled, node-by-node upgrades with verification and rollback capabilities
|
||||
using Argo Workflows.
|
||||
|
||||
## Motivation
|
||||
|
||||
### Problem Statement
|
||||
|
||||
Upgrading Kata Containers in a production Kubernetes cluster presents several challenges:
|
||||
|
||||
1. **Workload Scheduling Control**: New Kata workloads should not be scheduled on a node
|
||||
during upgrade until the new runtime is verified.
|
||||
|
||||
2. **Verification Gap**: There is no standardized way to verify that Kata is working correctly
|
||||
after an upgrade before allowing workloads to return to the node. This solution addresses
|
||||
the gap by running a user-provided verification pod on each upgraded node.
|
||||
|
||||
3. **Rollback Complexity**: If an upgrade fails, administrators must manually coordinate
|
||||
rollback across multiple nodes.
|
||||
|
||||
4. **Controlled Rollout**: Operators need the ability to upgrade nodes incrementally
|
||||
(canary approach) with fail-fast behavior if any node fails verification.
|
||||
|
||||
5. **Multi-Architecture Support**: The upgrade tooling must work across all architectures
|
||||
supported by Kata Containers (amd64, arm64, s390x, ppc64le).
|
||||
|
||||
### Current State
|
||||
|
||||
The `kata-deploy` Helm chart provides installation and configuration of Kata Containers,
|
||||
including a post-install verification job. However, there is no built-in mechanism for
|
||||
orchestrating upgrades across nodes in a controlled manner.
|
||||
|
||||
## Goals
|
||||
|
||||
1. Provide a standardized, automated way to upgrade Kata Containers node-by-node
|
||||
2. Ensure each node is verified before returning to service
|
||||
3. Support user-defined verification logic
|
||||
4. Automatically rollback if verification fails
|
||||
5. Work with the existing `kata-deploy` Helm chart
|
||||
6. Support all Kata-supported architectures
|
||||
|
||||
## Non-Goals
|
||||
|
||||
1. Initial Kata Containers installation (use kata-deploy Helm chart for that)
|
||||
2. Managing Kubernetes cluster upgrades
|
||||
3. Providing Kata-specific verification logic (this is user responsibility)
|
||||
4. Managing Argo Workflows installation
|
||||
|
||||
## Argo Workflows Dependency
|
||||
|
||||
### What Works Without Argo
|
||||
|
||||
The following components work independently of Argo Workflows:
|
||||
|
||||
| Component | Description |
|
||||
|-----------|-------------|
|
||||
| **kata-deploy Helm chart** | Full installation, configuration, `RuntimeClasses` |
|
||||
| **Post-install verification** | Helm hook runs verification pod after install |
|
||||
| **Label-gated deployment** | Progressive rollout via node labels |
|
||||
| **Manual upgrades** | User can script: cordon, helm upgrade, verify, `uncordon` |
|
||||
|
||||
Users who do not want Argo can still:
|
||||
- Install and configure Kata via kata-deploy
|
||||
- Perform upgrades manually or with custom scripts
|
||||
- Use the verification pod pattern in their own automation
|
||||
|
||||
### What Requires Argo
|
||||
|
||||
The kata-lifecycle-manager Helm chart provides orchestration via Argo Workflows:
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| **Automated node-by-node upgrades** | Sequential processing with fail-fast |
|
||||
| **Taint-based node selection** | Select nodes by taint key/value |
|
||||
| **`WorkflowTemplate`** | Reusable upgrade workflow |
|
||||
| **Rollback entrypoint** | `argo submit --entrypoint rollback-node` |
|
||||
| **Status tracking** | Node annotations updated at each phase |
|
||||
|
||||
### For Users Already Using Argo
|
||||
|
||||
If your cluster already has Argo Workflows installed:
|
||||
|
||||
```bash
|
||||
# Install kata-lifecycle-manager - integrates with your existing Argo installation
|
||||
helm install kata-lifecycle-manager oci://ghcr.io/kata-containers/kata-deploy-charts/kata-lifecycle-manager \
|
||||
--set argoNamespace=argo \
|
||||
--set-file defaults.verificationPod=./verification-pod.yaml
|
||||
|
||||
# Trigger upgrades via argo CLI or integrate with existing workflows
|
||||
argo submit -n argo --from workflowtemplate/kata-lifecycle-manager -p target-version=3.25.0
|
||||
```
|
||||
|
||||
kata-lifecycle-manager can also be triggered by other Argo workflows, CI/CD pipelines, or `GitOps`
|
||||
tools that support Argo.
|
||||
|
||||
### For Users Not Wanting Argo
|
||||
|
||||
If you prefer not to use Argo Workflows:
|
||||
|
||||
1. **Use kata-deploy directly** - handles installation and basic verification
|
||||
2. **Script your own orchestration** - example approach:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Manual upgrade script (no Argo required)
|
||||
set -euo pipefail
|
||||
|
||||
VERSION="3.25.0"
|
||||
|
||||
# Upgrade each node with Kata runtime
|
||||
kubectl get nodes -l katacontainers.io/kata-runtime=true -o name | while read -r node_path; do
|
||||
NODE="${node_path#node/}"
|
||||
echo "Upgrading $NODE..."
|
||||
kubectl cordon "$NODE"
|
||||
|
||||
helm upgrade kata-deploy oci://ghcr.io/kata-containers/kata-deploy-charts/kata-deploy \
|
||||
--namespace kube-system \
|
||||
--version "$VERSION" \
|
||||
--reuse-values \
|
||||
--wait
|
||||
|
||||
# Wait for DaemonSet pod on this node
|
||||
kubectl rollout status daemonset/kata-deploy -n kube-system
|
||||
|
||||
# Run verification (apply your pod, wait, check exit code)
|
||||
kubectl apply -f verification-pod.yaml
|
||||
kubectl wait pod/kata-verify --for=jsonpath='{.status.phase}'=Succeeded --timeout=180s
|
||||
kubectl delete pod/kata-verify
|
||||
|
||||
kubectl uncordon "$NODE"
|
||||
echo "$NODE upgraded successfully"
|
||||
done
|
||||
```
|
||||
|
||||
This approach requires more manual effort but avoids the Argo dependency.
|
||||
|
||||
## Proposed Design
|
||||
|
||||
### Architecture Overview
|
||||
|
||||
```text
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Argo Workflows Controller │
|
||||
│ (pre-installed) │
|
||||
└────────────────────────────┬────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ kata-lifecycle-manager Helm Chart │
|
||||
│ ┌────────────────────────────────────────────────────────┐ │
|
||||
│ │ WorkflowTemplate │ │
|
||||
│ │ - upgrade-all-nodes (entrypoint) │ │
|
||||
│ │ - upgrade-single-node (per-node steps) │ │
|
||||
│ │ - rollback-node (manual recovery) │ │
|
||||
│ └────────────────────────────────────────────────────────┘ │
|
||||
│ ┌────────────────────────────────────────────────────────┐ │
|
||||
│ │ RBAC Resources │ │
|
||||
│ │ - ServiceAccount │ │
|
||||
│ │ - ClusterRole (node, pod, helm operations) │ │
|
||||
│ │ - ClusterRoleBinding │ │
|
||||
│ └────────────────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ kata-deploy Helm Chart │
|
||||
│ (existing installation) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Upgrade Flow
|
||||
|
||||
For each node selected by the upgrade label:
|
||||
|
||||
```text
|
||||
┌────────────┐ ┌──────────────┐ ┌────────────┐ ┌────────────┐
|
||||
│ Prepare │───▶│ Cordon │───▶│ Upgrade │───▶│Wait Ready │
|
||||
│ (annotate) │ │ (mark │ │ (helm │ │(kata-deploy│
|
||||
│ │ │unschedulable)│ │ upgrade) │ │ DaemonSet) │
|
||||
└────────────┘ └──────────────┘ └────────────┘ └────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────┐ ┌──────────────┐ ┌────────────┐
|
||||
│ Complete │◀───│ Uncordon │◀───│ Verify │
|
||||
│ (annotate │ │ (mark │ │ (user pod)│
|
||||
│ version) │ │schedulable) │ │ │
|
||||
└────────────┘ └──────────────┘ └────────────┘
|
||||
```
|
||||
|
||||
**Note:** Drain is not required for Kata upgrades. Running Kata VMs continue using
|
||||
the in-memory binaries. Only new workloads use the upgraded binaries. Cordon ensures
|
||||
the verification pod runs before any new workloads are scheduled with the new runtime.
|
||||
|
||||
**Optional Drain:** For users who prefer to evict workloads before any maintenance
|
||||
operation, an optional drain step can be enabled via `drain-enabled=true`. When
|
||||
enabled, an additional drain step runs after cordon and before upgrade.
|
||||
|
||||
### Node Selection Model
|
||||
|
||||
Nodes can be selected for upgrade using **labels**, **taints**, or **both**.
|
||||
|
||||
**Label-based selection:**
|
||||
|
||||
```bash
|
||||
# Select nodes by label
|
||||
argo submit -n argo --from workflowtemplate/kata-lifecycle-manager \
|
||||
-p target-version=3.25.0 \
|
||||
-p node-selector="katacontainers.io/kata-lifecycle-manager-window=true"
|
||||
```
|
||||
|
||||
**Taint-based selection:**
|
||||
|
||||
Some organizations use taints to mark nodes for maintenance. The workflow supports
|
||||
selecting nodes by taint key and optionally taint value:
|
||||
|
||||
```bash
|
||||
# Select nodes with a specific taint
|
||||
kubectl taint nodes worker-1 kata-lifecycle-manager=pending:NoSchedule
|
||||
|
||||
argo submit -n argo --from workflowtemplate/kata-lifecycle-manager \
|
||||
-p target-version=3.25.0 \
|
||||
-p node-taint-key=kata-lifecycle-manager \
|
||||
-p node-taint-value=pending
|
||||
```
|
||||
|
||||
**Combined selection:**
|
||||
|
||||
Labels and taints can be used together for precise targeting:
|
||||
|
||||
```bash
|
||||
argo submit -n argo --from workflowtemplate/kata-lifecycle-manager \
|
||||
-p target-version=3.25.0 \
|
||||
-p node-selector="node-pool=kata-pool" \
|
||||
-p node-taint-key=maintenance
|
||||
```
|
||||
|
||||
This allows operators to:
|
||||
1. Upgrade a single canary node first
|
||||
2. Gradually add nodes to the upgrade window
|
||||
3. Control upgrade timing via `GitOps` or automation
|
||||
4. Integrate with existing taint-based maintenance workflows
|
||||
|
||||
### Node Pool Support
|
||||
|
||||
The node selector and taint selector parameters enable basic node pool targeting:
|
||||
|
||||
```bash
|
||||
# Upgrade only nodes matching a specific node pool label
|
||||
argo submit -n argo --from workflowtemplate/kata-lifecycle-manager \
|
||||
-p target-version=3.25.0 \
|
||||
-p node-selector="node-pool=kata-pool"
|
||||
```
|
||||
|
||||
**Current Capabilities:**
|
||||
|
||||
| Feature | Status | Chart | Notes |
|
||||
|---------|--------|-------|-------|
|
||||
| Label-based selection | Supported | kata-lifecycle-manager | Works with any label combination |
|
||||
| Taint-based selection | Supported | kata-lifecycle-manager | Select by taint key/value |
|
||||
| Sequential upgrades | Supported | kata-lifecycle-manager | One node at a time with fail-fast |
|
||||
| Pool-specific verification pods | Not supported | kata-lifecycle-manager | Same verification for all nodes |
|
||||
| Pool-ordered upgrades | Not supported | kata-lifecycle-manager | Upgrade pool A before pool B |
|
||||
|
||||
See the [Potential Enhancements](#potential-enhancements) section for future work.
|
||||
|
||||
### Verification Model
|
||||
|
||||
**Verification runs on each node that is upgraded.** The node is only `uncordoned` after
|
||||
its verification pod succeeds. If verification fails, automatic rollback is triggered
|
||||
to restore the previous version before `uncordoning` the node.
|
||||
|
||||
**Common failure modes detected by verification:**
|
||||
- Pod stuck in Pending/`ContainerCreating` (runtime can't start VM)
|
||||
- Pod crashes immediately (containerd/CRI-O configuration issues)
|
||||
- Pod times out (resource issues, image pull failures)
|
||||
- Pod exits with non-zero code (verification logic failed)
|
||||
|
||||
All of these trigger automatic rollback. The workflow logs include pod status, events,
|
||||
and logs to help diagnose the issue.
|
||||
|
||||
The user provides a complete Pod YAML that:
|
||||
- Uses the Kata runtime class they want to verify
|
||||
- Contains their verification logic (e.g., attestation checks)
|
||||
- Exits 0 on success, non-zero on failure
|
||||
- Includes tolerations for cordoned nodes (verification runs while node is cordoned)
|
||||
- Includes a `nodeSelector` to ensure it runs on the specific node being upgraded
|
||||
|
||||
When upgrading multiple nodes (via label selector), nodes are processed sequentially.
|
||||
For each node, the following placeholders are substituted with that node's specific values,
|
||||
ensuring the verification pod runs on the exact node that was just upgraded:
|
||||
|
||||
- `${NODE}` - The hostname of the node being upgraded/verified
|
||||
- `${TEST_POD}` - A generated unique pod name
|
||||
|
||||
Example verification pod:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: ${TEST_POD}
|
||||
spec:
|
||||
runtimeClassName: kata-qemu
|
||||
restartPolicy: Never
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: ${NODE}
|
||||
tolerations:
|
||||
- operator: Exists # Required: node is cordoned during verification
|
||||
containers:
|
||||
- name: verify
|
||||
image: quay.io/kata-containers/alpine-bash-curl:latest
|
||||
command: ["uname", "-a"]
|
||||
```
|
||||
|
||||
This design keeps verification logic entirely in the user's domain, supporting:
|
||||
- Different runtime classes (`kata-qemu`, `kata-qemu-snp`, `kata-qemu-tdx`, etc.)
|
||||
- TEE-specific attestation verification
|
||||
- GPU/accelerator validation
|
||||
- Custom application smoke tests
|
||||
|
||||
### Sequential Execution with Fail-Fast
|
||||
|
||||
Nodes are upgraded strictly sequentially using recursive Argo templates. This design
|
||||
ensures that if any node fails verification, the workflow stops immediately before
|
||||
touching remaining nodes, preventing a mixed-version fleet.
|
||||
|
||||
Alternative approaches considered:
|
||||
- **`withParam` + semaphore**: Provides cleaner UI but semaphore only controls concurrency,
|
||||
not failure propagation. Other nodes would still proceed after one fails.
|
||||
- **`withParam` + `failFast`**: Would be ideal, but Argo only supports `failFast` for DAG
|
||||
tasks, not for steps with `withParam`.
|
||||
|
||||
The recursive template approach (`upgrade-node-chain`) naturally provides fail-fast
|
||||
behavior because if any step in the chain fails, the recursion stops.
|
||||
|
||||
### Status Tracking
|
||||
|
||||
Node upgrade status is tracked via Kubernetes annotations:
|
||||
|
||||
| Annotation | Values |
|
||||
|------------|--------|
|
||||
| `katacontainers.io/kata-lifecycle-manager-status` | preparing, cordoned, draining, upgrading, verifying, completed, rolling-back, rolled-back |
|
||||
| `katacontainers.io/kata-current-version` | Version string (e.g., "3.25.0") |
|
||||
|
||||
This enables:
|
||||
- Monitoring upgrade progress via `kubectl get nodes`
|
||||
- Integration with external monitoring systems
|
||||
- Recovery from interrupted upgrades
|
||||
|
||||
### Rollback Support
|
||||
|
||||
**Automatic rollback on verification failure:** If the verification pod fails (non-zero exit),
|
||||
kata-lifecycle-manager automatically:
|
||||
1. Runs `helm rollback` to revert to the previous Helm release
|
||||
2. Waits for kata-deploy DaemonSet to be ready with the previous version
|
||||
3. `Uncordons` the node
|
||||
4. Annotates the node with `rolled-back` status
|
||||
|
||||
This ensures nodes are never left in a broken state.
|
||||
|
||||
**Manual rollback:** For cases where you need to rollback a successfully upgraded node:
|
||||
|
||||
```bash
|
||||
argo submit -n argo --from workflowtemplate/kata-lifecycle-manager \
|
||||
--entrypoint rollback-node \
|
||||
-p node-name=worker-1
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
### Container Images
|
||||
|
||||
Two multi-architecture container images are built and published:
|
||||
|
||||
| Image | Purpose | Architectures |
|
||||
|-------|---------|---------------|
|
||||
| `quay.io/kata-containers/kubectl:latest` | Kubernetes operations | amd64, arm64, s390x, ppc64le |
|
||||
| `quay.io/kata-containers/helm:latest` | Helm operations | amd64, arm64, s390x, ppc64le |
|
||||
|
||||
Images are rebuilt weekly to pick up security updates and tool version upgrades.
|
||||
|
||||
### Helm Chart Structure
|
||||
|
||||
```text
|
||||
kata-lifecycle-manager/
|
||||
├── Chart.yaml # Chart metadata
|
||||
├── values.yaml # Configurable defaults
|
||||
├── README.md # Usage documentation
|
||||
└── templates/
|
||||
├── _helpers.tpl # Template helpers
|
||||
├── rbac.yaml # ServiceAccount, ClusterRole, ClusterRoleBinding
|
||||
└── workflow-template.yaml # Argo `WorkflowTemplate`
|
||||
```
|
||||
|
||||
### RBAC Requirements
|
||||
|
||||
The workflow requires the following permissions:
|
||||
|
||||
| Resource | Verbs | Purpose |
|
||||
|----------|-------|---------|
|
||||
| nodes | get, list, watch, patch | `cordon`/`uncordon`, annotations |
|
||||
| pods | get, list, watch, create, delete | Verification pods |
|
||||
| pods/log | get | Verification output |
|
||||
| `daemonsets` | get, list, watch | Wait for `kata-deploy` |
|
||||
|
||||
## User Experience
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Install kata-lifecycle-manager with verification config
|
||||
helm install kata-lifecycle-manager oci://ghcr.io/kata-containers/kata-deploy-charts/kata-lifecycle-manager \
|
||||
--set-file defaults.verificationPod=/path/to/verification-pod.yaml
|
||||
```
|
||||
|
||||
### Triggering an Upgrade
|
||||
|
||||
```bash
|
||||
# Label nodes for upgrade
|
||||
kubectl label node worker-1 katacontainers.io/kata-lifecycle-manager-window=true
|
||||
|
||||
# Submit upgrade workflow
|
||||
argo submit -n argo --from workflowtemplate/kata-lifecycle-manager \
|
||||
-p target-version=3.25.0
|
||||
|
||||
# Watch progress
|
||||
argo watch @latest
|
||||
```
|
||||
|
||||
### Monitoring
|
||||
|
||||
```bash
|
||||
kubectl get nodes \
|
||||
-L katacontainers.io/kata-runtime \
|
||||
-L katacontainers.io/kata-lifecycle-manager-status \
|
||||
-L katacontainers.io/kata-current-version
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Namespace-Scoped Templates**: The chart creates a `WorkflowTemplate` (namespace-scoped)
|
||||
rather than `ClusterWorkflowTemplate` by default, reducing blast radius.
|
||||
|
||||
2. **Required Verification**: The chart fails to install if `defaults.verificationPod` is
|
||||
not provided, ensuring upgrades are always verified.
|
||||
|
||||
3. **Minimal RBAC**: The `ServiceAccount` has only the permissions required for upgrade
|
||||
operations.
|
||||
|
||||
4. **User-Controlled Verification**: Verification logic is entirely user-defined, avoiding
|
||||
any hardcoded assumptions about what "working" means.
|
||||
|
||||
## Integration with Release Process
|
||||
|
||||
The `kata-lifecycle-manager` chart is:
|
||||
- Packaged alongside `kata-deploy` during releases
|
||||
- Published to the same OCI registries (`quay.io`, `ghcr.io`)
|
||||
- Versioned to match `kata-deploy`
|
||||
|
||||
## Potential Enhancements
|
||||
|
||||
The following enhancements could be considered if needed:
|
||||
|
||||
### kata-lifecycle-manager
|
||||
|
||||
1. **Pool-Specific Verification**: Different verification pods for different node pools
|
||||
(e.g., GPU nodes vs. CPU-only nodes).
|
||||
|
||||
2. **Ordered Pool Upgrades**: Upgrade node pool A completely before starting pool B.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
### 1. DaemonSet-Based Upgrades
|
||||
|
||||
Using a DaemonSet to coordinate upgrades on each node.
|
||||
|
||||
**Rejected because**: DaemonSets don't provide the node-by-node sequencing and
|
||||
verification workflow needed for controlled upgrades.
|
||||
|
||||
### 2. Operator Pattern
|
||||
|
||||
Building a Kubernetes Operator to manage upgrades.
|
||||
|
||||
**Rejected because**: Adds significant complexity and maintenance burden. Argo Workflows
|
||||
is already widely adopted and provides the orchestration primitives needed.
|
||||
|
||||
### 3. Shell Script Orchestration
|
||||
|
||||
Providing a shell script that loops through nodes.
|
||||
|
||||
**Rejected because**: Less reliable, harder to monitor, no built-in retry/recovery,
|
||||
and doesn't integrate with Kubernetes-native tooling.
|
||||
|
||||
## References
|
||||
|
||||
- [kata-deploy Helm Chart](https://github.com/kata-containers/kata-containers/tree/main/tools/packaging/kata-deploy/helm-chart/kata-deploy)
|
||||
- [Argo Workflows](https://argoproj.github.io/argo-workflows/)
|
||||
- [Helm Documentation](https://helm.sh/docs/)
|
||||
@@ -149,6 +149,9 @@ pub const KATA_ANNO_CFG_HYPERVISOR_KERNEL_HASH: &str =
|
||||
/// A sandbox annotation for passing additional guest kernel parameters.
|
||||
pub const KATA_ANNO_CFG_HYPERVISOR_KERNEL_PARAMS: &str =
|
||||
"io.katacontainers.config.hypervisor.kernel_params";
|
||||
/// A sandbox annotation for passing guest dm-verity parameters.
|
||||
pub const KATA_ANNO_CFG_HYPERVISOR_KERNEL_VERITY_PARAMS: &str =
|
||||
"io.katacontainers.config.hypervisor.kernel_verity_params";
|
||||
/// A sandbox annotation for passing a container guest image path.
|
||||
pub const KATA_ANNO_CFG_HYPERVISOR_IMAGE_PATH: &str = "io.katacontainers.config.hypervisor.image";
|
||||
/// A sandbox annotation for passing a container guest image SHA-512 hash value.
|
||||
@@ -630,6 +633,9 @@ impl Annotation {
|
||||
KATA_ANNO_CFG_HYPERVISOR_KERNEL_PARAMS => {
|
||||
hv.boot_info.replace_kernel_params(value);
|
||||
}
|
||||
KATA_ANNO_CFG_HYPERVISOR_KERNEL_VERITY_PARAMS => {
|
||||
hv.boot_info.replace_kernel_verity_params(value)?;
|
||||
}
|
||||
KATA_ANNO_CFG_HYPERVISOR_IMAGE_PATH => {
|
||||
hv.boot_info.validate_boot_path(value)?;
|
||||
hv.boot_info.image = value.to_string();
|
||||
|
||||
@@ -76,6 +76,134 @@ const VIRTIO_FS_INLINE: &str = "inline-virtio-fs";
|
||||
const MAX_BRIDGE_SIZE: u32 = 5;
|
||||
|
||||
const KERNEL_PARAM_DELIMITER: &str = " ";
|
||||
/// Block size (in bytes) used by dm-verity block size validation.
|
||||
pub const VERITY_BLOCK_SIZE_BYTES: u64 = 512;
|
||||
/// Parsed kernel dm-verity parameters.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct KernelVerityParams {
|
||||
/// Root hash value.
|
||||
pub root_hash: String,
|
||||
/// Salt used to generate verity hash tree.
|
||||
pub salt: String,
|
||||
/// Number of data blocks in the verity mapping.
|
||||
pub data_blocks: u64,
|
||||
/// Data block size in bytes.
|
||||
pub data_block_size: u64,
|
||||
/// Hash block size in bytes.
|
||||
pub hash_block_size: u64,
|
||||
}
|
||||
|
||||
/// Parse and validate kernel dm-verity parameters.
|
||||
pub fn parse_kernel_verity_params(params: &str) -> Result<Option<KernelVerityParams>> {
|
||||
if params.trim().is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut values = HashMap::new();
|
||||
for field in params.split(',') {
|
||||
let field = field.trim();
|
||||
if field.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let mut parts = field.splitn(2, '=');
|
||||
let key = parts.next().unwrap_or("");
|
||||
let value = parts.next().ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("Invalid kernel_verity_params entry: {field}"),
|
||||
)
|
||||
})?;
|
||||
if key.is_empty() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("Invalid kernel_verity_params entry: {field}"),
|
||||
));
|
||||
}
|
||||
values.insert(key.to_string(), value.to_string());
|
||||
}
|
||||
|
||||
let root_hash = values
|
||||
.get("root_hash")
|
||||
.ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"Missing kernel_verity_params root_hash",
|
||||
)
|
||||
})?
|
||||
.to_string();
|
||||
|
||||
let salt = values.get("salt").cloned().unwrap_or_default();
|
||||
|
||||
let parse_uint_field = |name: &str| -> Result<u64> {
|
||||
match values.get(name) {
|
||||
Some(value) if !value.is_empty() => value.parse::<u64>().map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("Invalid kernel_verity_params {} '{}': {}", name, value, e),
|
||||
)
|
||||
}),
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("Missing kernel_verity_params {name}"),
|
||||
)),
|
||||
}
|
||||
};
|
||||
|
||||
let data_blocks = parse_uint_field("data_blocks")?;
|
||||
let data_block_size = parse_uint_field("data_block_size")?;
|
||||
let hash_block_size = parse_uint_field("hash_block_size")?;
|
||||
|
||||
if salt.is_empty() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"Missing kernel_verity_params salt",
|
||||
));
|
||||
}
|
||||
if data_blocks == 0 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"Invalid kernel_verity_params data_blocks: must be non-zero",
|
||||
));
|
||||
}
|
||||
if data_block_size == 0 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"Invalid kernel_verity_params data_block_size: must be non-zero",
|
||||
));
|
||||
}
|
||||
if hash_block_size == 0 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"Invalid kernel_verity_params hash_block_size: must be non-zero",
|
||||
));
|
||||
}
|
||||
if data_block_size % VERITY_BLOCK_SIZE_BYTES != 0 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!(
|
||||
"Invalid kernel_verity_params data_block_size: must be multiple of {}",
|
||||
VERITY_BLOCK_SIZE_BYTES
|
||||
),
|
||||
));
|
||||
}
|
||||
if hash_block_size % VERITY_BLOCK_SIZE_BYTES != 0 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!(
|
||||
"Invalid kernel_verity_params hash_block_size: must be multiple of {}",
|
||||
VERITY_BLOCK_SIZE_BYTES
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Some(KernelVerityParams {
|
||||
root_hash,
|
||||
salt,
|
||||
data_blocks,
|
||||
data_block_size,
|
||||
hash_block_size,
|
||||
}))
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref HYPERVISOR_PLUGINS: Mutex<HashMap<String, Arc<dyn ConfigPlugin>>> =
|
||||
@@ -294,6 +422,10 @@ pub struct BootInfo {
|
||||
#[serde(default)]
|
||||
pub kernel_params: String,
|
||||
|
||||
/// Guest kernel dm-verity parameters.
|
||||
#[serde(default)]
|
||||
pub kernel_verity_params: String,
|
||||
|
||||
/// Path to initrd file on host.
|
||||
#[serde(default)]
|
||||
pub initrd: String,
|
||||
@@ -441,6 +573,17 @@ impl BootInfo {
|
||||
self.kernel_params = all_params.join(KERNEL_PARAM_DELIMITER);
|
||||
}
|
||||
|
||||
/// Replace kernel dm-verity parameters after validation.
|
||||
pub fn replace_kernel_verity_params(&mut self, new_params: &str) -> Result<()> {
|
||||
if new_params.trim().is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
parse_kernel_verity_params(new_params)?;
|
||||
self.kernel_verity_params = new_params.to_string();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate guest kernel image annotation.
|
||||
pub fn validate_boot_path(&self, path: &str) -> Result<()> {
|
||||
validate_path!(path, "path {} is invalid{}")?;
|
||||
|
||||
@@ -148,8 +148,7 @@ ifneq (,$(QEMUCMD))
|
||||
endif
|
||||
endif
|
||||
|
||||
ROOTMEASURECONFIG ?= ""
|
||||
KERNELTDXPARAMS += $(ROOTMEASURECONFIG)
|
||||
KERNELVERITYPARAMS ?= ""
|
||||
|
||||
# TDX
|
||||
DEFSHAREDFS_QEMU_TDX_VIRTIOFS := none
|
||||
@@ -176,8 +175,8 @@ DEFMEMSLOTS := 10
|
||||
DEFMAXMEMSZ := 0
|
||||
##VAR DEFBRIDGES=<number> Default number of bridges
|
||||
DEFBRIDGES := 1
|
||||
DEFENABLEANNOTATIONS := [\"enable_iommu\", \"virtio_fs_extra_args\", \"kernel_params\", \"default_vcpus\", \"default_memory\"]
|
||||
DEFENABLEANNOTATIONS_COCO := [\"enable_iommu\", \"virtio_fs_extra_args\", \"kernel_params\", \"default_vcpus\", \"default_memory\", \"cc_init_data\"]
|
||||
DEFENABLEANNOTATIONS := [\"enable_iommu\", \"virtio_fs_extra_args\", \"kernel_params\", \"kernel_verity_params\", \"default_vcpus\", \"default_memory\"]
|
||||
DEFENABLEANNOTATIONS_COCO := [\"enable_iommu\", \"virtio_fs_extra_args\", \"kernel_params\", \"kernel_verity_params\", \"default_vcpus\", \"default_memory\", \"cc_init_data\"]
|
||||
DEFDISABLEGUESTSECCOMP := true
|
||||
DEFDISABLEGUESTEMPTYDIR := false
|
||||
##VAR DEFAULTEXPFEATURES=[features] Default experimental features enabled
|
||||
@@ -527,6 +526,7 @@ USER_VARS += MACHINEACCELERATORS
|
||||
USER_VARS += CPUFEATURES
|
||||
USER_VARS += DEFMACHINETYPE_CLH
|
||||
USER_VARS += KERNELPARAMS
|
||||
USER_VARS += KERNELVERITYPARAMS
|
||||
USER_VARS += KERNELPARAMS_DB
|
||||
USER_VARS += KERNELPARAMS_FC
|
||||
USER_VARS += LIBEXECDIR
|
||||
|
||||
@@ -74,6 +74,11 @@ valid_hypervisor_paths = @QEMUVALIDHYPERVISORPATHS@
|
||||
# container and look for 'default-kernel-parameters' log entries.
|
||||
kernel_params = "@KERNELTDXPARAMS@"
|
||||
|
||||
# Optional dm-verity parameters (comma-separated key=value list):
|
||||
# root_hash=...,salt=...,data_blocks=...,data_block_size=...,hash_block_size=...
|
||||
# These are used by the runtime to assemble dm-verity kernel params.
|
||||
kernel_verity_params = "@KERNELVERITYPARAMS@"
|
||||
|
||||
# Path to the firmware.
|
||||
# If you want that qemu uses the default firmware leave this option empty
|
||||
firmware = "@FIRMWARETDXPATH@"
|
||||
|
||||
@@ -151,7 +151,11 @@ impl CloudHypervisorInner {
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
let console_param_debug = KernelParams::from_string("console=ttyAMA0,115200n8");
|
||||
|
||||
let mut rootfs_param = KernelParams::new_rootfs_kernel_params(rootfs_driver, rootfs_type)?;
|
||||
let mut rootfs_params = KernelParams::new_rootfs_kernel_params(
|
||||
&cfg.boot_info.kernel_verity_params,
|
||||
rootfs_driver,
|
||||
rootfs_type,
|
||||
)?;
|
||||
|
||||
let mut console_params = if enable_debug {
|
||||
if confidential_guest {
|
||||
@@ -165,8 +169,7 @@ impl CloudHypervisorInner {
|
||||
|
||||
params.append(&mut console_params);
|
||||
|
||||
// Add the rootfs device
|
||||
params.append(&mut rootfs_param);
|
||||
params.append(&mut rootfs_params);
|
||||
|
||||
// Now add some additional options required for CH
|
||||
let extra_options = [
|
||||
|
||||
@@ -144,13 +144,14 @@ impl DragonballInner {
|
||||
let mut kernel_params = KernelParams::new(self.config.debug_info.enable_debug);
|
||||
|
||||
if self.config.boot_info.initrd.is_empty() {
|
||||
// get rootfs driver
|
||||
// When booting from the image, add rootfs and verity parameters here.
|
||||
let rootfs_driver = self.config.blockdev_info.block_device_driver.clone();
|
||||
|
||||
kernel_params.append(&mut KernelParams::new_rootfs_kernel_params(
|
||||
let mut rootfs_params = KernelParams::new_rootfs_kernel_params(
|
||||
&self.config.boot_info.kernel_verity_params,
|
||||
&rootfs_driver,
|
||||
&self.config.boot_info.rootfs_type,
|
||||
)?);
|
||||
)?;
|
||||
kernel_params.append(&mut rootfs_params);
|
||||
}
|
||||
|
||||
kernel_params.append(&mut KernelParams::from_string(
|
||||
|
||||
@@ -86,12 +86,12 @@ impl FcInner {
|
||||
let mut kernel_params = KernelParams::new(self.config.debug_info.enable_debug);
|
||||
kernel_params.push(Param::new("pci", "off"));
|
||||
kernel_params.push(Param::new("iommu", "off"));
|
||||
let rootfs_driver = self.config.blockdev_info.block_device_driver.clone();
|
||||
|
||||
kernel_params.append(&mut KernelParams::new_rootfs_kernel_params(
|
||||
&rootfs_driver,
|
||||
let mut rootfs_params = KernelParams::new_rootfs_kernel_params(
|
||||
&self.config.boot_info.kernel_verity_params,
|
||||
&self.config.blockdev_info.block_device_driver,
|
||||
&self.config.boot_info.rootfs_type,
|
||||
)?);
|
||||
)?;
|
||||
kernel_params.append(&mut rootfs_params);
|
||||
kernel_params.append(&mut KernelParams::from_string(
|
||||
&self.config.boot_info.kernel_params,
|
||||
));
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::{
|
||||
VM_ROOTFS_ROOT_BLK, VM_ROOTFS_ROOT_PMEM,
|
||||
};
|
||||
use kata_types::config::LOG_VPORT_OPTION;
|
||||
use kata_types::config::hypervisor::{parse_kernel_verity_params, VERITY_BLOCK_SIZE_BYTES};
|
||||
use kata_types::fs::{
|
||||
VM_ROOTFS_FILESYSTEM_EROFS, VM_ROOTFS_FILESYSTEM_EXT4, VM_ROOTFS_FILESYSTEM_XFS,
|
||||
};
|
||||
@@ -20,7 +21,76 @@ use kata_types::fs::{
|
||||
const VSOCK_LOGS_PORT: &str = "1025";
|
||||
|
||||
const KERNEL_KV_DELIMITER: &str = "=";
|
||||
const KERNEL_PARAM_DELIMITER: &str = " ";
|
||||
const KERNEL_PARAM_DELIMITER: char = ' ';
|
||||
|
||||
// Split kernel params on spaces, but keep quoted substrings intact.
|
||||
// Example: dm-mod.create="dm-verity,,,ro,0 736328 verity 1 /dev/vda1 /dev/vda2 ...".
|
||||
fn split_kernel_params(params_string: &str) -> Vec<String> {
|
||||
let mut params = Vec::new();
|
||||
let mut current = String::new();
|
||||
let mut in_quote = false;
|
||||
|
||||
for c in params_string.chars() {
|
||||
if c == '"' {
|
||||
in_quote = !in_quote;
|
||||
current.push(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
if c == KERNEL_PARAM_DELIMITER && !in_quote {
|
||||
let trimmed = current.trim();
|
||||
if !trimmed.is_empty() {
|
||||
params.push(trimmed.to_string());
|
||||
}
|
||||
current.clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
current.push(c);
|
||||
}
|
||||
|
||||
let trimmed = current.trim();
|
||||
if !trimmed.is_empty() {
|
||||
params.push(trimmed.to_string());
|
||||
}
|
||||
|
||||
params
|
||||
}
|
||||
|
||||
struct KernelVerityConfig {
|
||||
root_hash: String,
|
||||
salt: String,
|
||||
data_blocks: u64,
|
||||
data_block_size: u64,
|
||||
hash_block_size: u64,
|
||||
}
|
||||
|
||||
fn new_kernel_verity_params(params_string: &str) -> Result<Option<KernelVerityConfig>> {
|
||||
let cfg = parse_kernel_verity_params(params_string)
|
||||
.map_err(|err| anyhow!(err.to_string()))?;
|
||||
|
||||
Ok(cfg.map(|params| KernelVerityConfig {
|
||||
root_hash: params.root_hash,
|
||||
salt: params.salt,
|
||||
data_blocks: params.data_blocks,
|
||||
data_block_size: params.data_block_size,
|
||||
hash_block_size: params.hash_block_size,
|
||||
}))
|
||||
}
|
||||
|
||||
fn kernel_verity_root_flags(rootfs_type: &str) -> Result<String> {
|
||||
let normalized = if rootfs_type.is_empty() {
|
||||
VM_ROOTFS_FILESYSTEM_EXT4
|
||||
} else {
|
||||
rootfs_type
|
||||
};
|
||||
|
||||
match normalized {
|
||||
VM_ROOTFS_FILESYSTEM_EXT4 => Ok("data=ordered,errors=remount-ro ro".to_string()),
|
||||
VM_ROOTFS_FILESYSTEM_XFS | VM_ROOTFS_FILESYSTEM_EROFS => Ok("ro".to_string()),
|
||||
_ => Err(anyhow!("Unsupported rootfs type {}", rootfs_type)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Param {
|
||||
@@ -71,7 +141,11 @@ impl KernelParams {
|
||||
Self { params }
|
||||
}
|
||||
|
||||
pub(crate) fn new_rootfs_kernel_params(rootfs_driver: &str, rootfs_type: &str) -> Result<Self> {
|
||||
pub(crate) fn new_rootfs_kernel_params(
|
||||
kernel_verity_params: &str,
|
||||
rootfs_driver: &str,
|
||||
rootfs_type: &str,
|
||||
) -> Result<Self> {
|
||||
let mut params = vec![];
|
||||
|
||||
match rootfs_driver {
|
||||
@@ -119,7 +193,52 @@ impl KernelParams {
|
||||
|
||||
params.push(Param::new("rootfstype", rootfs_type));
|
||||
|
||||
Ok(Self { params })
|
||||
let mut params = Self { params };
|
||||
let cfg = match new_kernel_verity_params(kernel_verity_params)? {
|
||||
Some(cfg) => cfg,
|
||||
None => return Ok(params),
|
||||
};
|
||||
|
||||
let (root_device, hash_device) = match rootfs_driver {
|
||||
VM_ROOTFS_DRIVER_PMEM => ("/dev/pmem0p1", "/dev/pmem0p2"),
|
||||
VM_ROOTFS_DRIVER_BLK | VM_ROOTFS_DRIVER_BLK_CCW | VM_ROOTFS_DRIVER_MMIO => {
|
||||
("/dev/vda1", "/dev/vda2")
|
||||
}
|
||||
_ => return Err(anyhow!("Unsupported rootfs driver {}", rootfs_driver)),
|
||||
};
|
||||
|
||||
let data_sectors = (cfg.data_block_size / VERITY_BLOCK_SIZE_BYTES) * cfg.data_blocks;
|
||||
let root_flags = kernel_verity_root_flags(rootfs_type)?;
|
||||
|
||||
let dm_cmd = format!(
|
||||
"dm-verity,,,ro,0 {} verity 1 {} {} {} {} {} 0 sha256 {} {}",
|
||||
data_sectors,
|
||||
root_device,
|
||||
hash_device,
|
||||
cfg.data_block_size,
|
||||
cfg.hash_block_size,
|
||||
cfg.data_blocks,
|
||||
cfg.root_hash,
|
||||
cfg.salt
|
||||
);
|
||||
|
||||
params.remove_all_by_key("root".to_string());
|
||||
params.remove_all_by_key("rootflags".to_string());
|
||||
params.remove_all_by_key("rootfstype".to_string());
|
||||
|
||||
params.push(Param {
|
||||
key: "dm-mod.create".to_string(),
|
||||
value: format!("\"{}\"", dm_cmd),
|
||||
});
|
||||
params.push(Param::new("root", "/dev/dm-0"));
|
||||
params.push(Param::new("rootflags", &root_flags));
|
||||
if rootfs_type.is_empty() {
|
||||
params.push(Param::new("rootfstype", VM_ROOTFS_FILESYSTEM_EXT4));
|
||||
} else {
|
||||
params.push(Param::new("rootfstype", rootfs_type));
|
||||
}
|
||||
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
pub(crate) fn append(&mut self, params: &mut KernelParams) {
|
||||
@@ -138,7 +257,7 @@ impl KernelParams {
|
||||
pub(crate) fn from_string(params_string: &str) -> Self {
|
||||
let mut params = vec![];
|
||||
|
||||
let parameters_vec: Vec<&str> = params_string.split(KERNEL_PARAM_DELIMITER).collect();
|
||||
let parameters_vec = split_kernel_params(params_string);
|
||||
|
||||
for param in parameters_vec.iter() {
|
||||
if param.is_empty() {
|
||||
@@ -170,7 +289,7 @@ impl KernelParams {
|
||||
parameters.push(param.to_string()?);
|
||||
}
|
||||
|
||||
Ok(parameters.join(KERNEL_PARAM_DELIMITER))
|
||||
Ok(parameters.join(&KERNEL_PARAM_DELIMITER.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,7 +466,8 @@ mod tests {
|
||||
|
||||
for (i, t) in tests.iter().enumerate() {
|
||||
let msg = format!("test[{i}]: {t:?}");
|
||||
let result = KernelParams::new_rootfs_kernel_params(t.rootfs_driver, t.rootfs_type);
|
||||
let result =
|
||||
KernelParams::new_rootfs_kernel_params("", t.rootfs_driver, t.rootfs_type);
|
||||
let msg = format!("{msg}, result: {result:?}");
|
||||
if t.result.is_ok() {
|
||||
assert!(result.is_ok(), "{}", msg);
|
||||
@@ -359,4 +479,55 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kernel_verity_params() -> Result<()> {
|
||||
let params = KernelParams::new_rootfs_kernel_params(
|
||||
"root_hash=abc,salt=def,data_blocks=1,data_block_size=4096,hash_block_size=4096",
|
||||
VM_ROOTFS_DRIVER_BLK,
|
||||
VM_ROOTFS_FILESYSTEM_EXT4,
|
||||
)?;
|
||||
let params_string = params.to_string()?;
|
||||
assert!(params_string.contains("dm-mod.create="));
|
||||
assert!(params_string.contains("root=/dev/dm-0"));
|
||||
assert!(params_string.contains("rootfstype=ext4"));
|
||||
|
||||
let err = KernelParams::new_rootfs_kernel_params(
|
||||
"root_hash=abc,data_blocks=1,data_block_size=4096,hash_block_size=4096",
|
||||
VM_ROOTFS_DRIVER_BLK,
|
||||
VM_ROOTFS_FILESYSTEM_EXT4,
|
||||
)
|
||||
.err()
|
||||
.expect("expected missing salt error");
|
||||
assert!(format!("{err}").contains("Missing kernel_verity_params salt"));
|
||||
|
||||
let err = KernelParams::new_rootfs_kernel_params(
|
||||
"root_hash=abc,salt=def,data_block_size=4096,hash_block_size=4096",
|
||||
VM_ROOTFS_DRIVER_BLK,
|
||||
VM_ROOTFS_FILESYSTEM_EXT4,
|
||||
)
|
||||
.err()
|
||||
.expect("expected missing data_blocks error");
|
||||
assert!(format!("{err}").contains("Missing kernel_verity_params data_blocks"));
|
||||
|
||||
let err = KernelParams::new_rootfs_kernel_params(
|
||||
"root_hash=abc,salt=def,data_blocks=foo,data_block_size=4096,hash_block_size=4096",
|
||||
VM_ROOTFS_DRIVER_BLK,
|
||||
VM_ROOTFS_FILESYSTEM_EXT4,
|
||||
)
|
||||
.err()
|
||||
.expect("expected invalid data_blocks error");
|
||||
assert!(format!("{err}").contains("Invalid kernel_verity_params data_blocks"));
|
||||
|
||||
let err = KernelParams::new_rootfs_kernel_params(
|
||||
"root_hash=abc,salt=def,data_blocks=1,data_block_size=4096,hash_block_size=4096,badfield",
|
||||
VM_ROOTFS_DRIVER_BLK,
|
||||
VM_ROOTFS_FILESYSTEM_EXT4,
|
||||
)
|
||||
.err()
|
||||
.expect("expected invalid entry error");
|
||||
assert!(format!("{err}").contains("Invalid kernel_verity_params entry"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,16 +179,13 @@ impl Kernel {
|
||||
let mut kernel_params = KernelParams::new(config.debug_info.enable_debug);
|
||||
|
||||
if config.boot_info.initrd.is_empty() {
|
||||
// QemuConfig::validate() has already made sure that if initrd is
|
||||
// empty, image cannot be so we don't need to re-check that here
|
||||
|
||||
kernel_params.append(
|
||||
&mut KernelParams::new_rootfs_kernel_params(
|
||||
&config.boot_info.vm_rootfs_driver,
|
||||
&config.boot_info.rootfs_type,
|
||||
)
|
||||
.context("adding rootfs params failed")?,
|
||||
);
|
||||
let mut rootfs_params = KernelParams::new_rootfs_kernel_params(
|
||||
&config.boot_info.kernel_verity_params,
|
||||
&config.boot_info.vm_rootfs_driver,
|
||||
&config.boot_info.rootfs_type,
|
||||
)
|
||||
.context("adding rootfs/verity params failed")?;
|
||||
kernel_params.append(&mut rootfs_params);
|
||||
}
|
||||
|
||||
kernel_params.append(&mut KernelParams::from_string(
|
||||
|
||||
@@ -152,9 +152,9 @@ FIRMWARETDVFVOLUMEPATH :=
|
||||
|
||||
FIRMWARESNPPATH := $(PREFIXDEPS)/share/ovmf/AMDSEV.fd
|
||||
|
||||
ROOTMEASURECONFIG ?= ""
|
||||
KERNELTDXPARAMS += $(ROOTMEASURECONFIG)
|
||||
KERNELQEMUCOCODEVPARAMS += $(ROOTMEASURECONFIG)
|
||||
KERNELVERITYPARAMS ?= ""
|
||||
KERNELVERITYPARAMS_NV ?= ""
|
||||
KERNELVERITYPARAMS_CONFIDENTIAL_NV ?= ""
|
||||
|
||||
# Name of default configuration file the runtime will use.
|
||||
CONFIG_FILE = configuration.toml
|
||||
@@ -217,8 +217,8 @@ DEFMEMSLOTS := 10
|
||||
DEFMAXMEMSZ := 0
|
||||
#Default number of bridges
|
||||
DEFBRIDGES := 1
|
||||
DEFENABLEANNOTATIONS := [\"enable_iommu\", \"virtio_fs_extra_args\", \"kernel_params\"]
|
||||
DEFENABLEANNOTATIONS_COCO := [\"enable_iommu\", \"virtio_fs_extra_args\", \"kernel_params\", \"default_vcpus\", \"default_memory\", \"cc_init_data\"]
|
||||
DEFENABLEANNOTATIONS := [\"enable_iommu\", \"virtio_fs_extra_args\", \"kernel_params\", \"kernel_verity_params\"]
|
||||
DEFENABLEANNOTATIONS_COCO := [\"enable_iommu\", \"virtio_fs_extra_args\", \"kernel_params\", \"kernel_verity_params\", \"default_vcpus\", \"default_memory\", \"cc_init_data\"]
|
||||
DEFDISABLEGUESTSECCOMP := true
|
||||
DEFDISABLEGUESTEMPTYDIR := false
|
||||
#Default experimental features enabled
|
||||
@@ -655,6 +655,8 @@ USER_VARS += DEFAULTMEMORY_NV
|
||||
USER_VARS += DEFAULTVFIOPORT_NV
|
||||
USER_VARS += DEFAULTPCIEROOTPORT_NV
|
||||
USER_VARS += KERNELPARAMS_NV
|
||||
USER_VARS += KERNELVERITYPARAMS_NV
|
||||
USER_VARS += KERNELVERITYPARAMS_CONFIDENTIAL_NV
|
||||
USER_VARS += DEFAULTTIMEOUT_NV
|
||||
USER_VARS += DEFSANDBOXCGROUPONLY_NV
|
||||
USER_VARS += DEFROOTFSTYPE
|
||||
@@ -681,6 +683,7 @@ USER_VARS += TDXCPUFEATURES
|
||||
USER_VARS += DEFMACHINETYPE_CLH
|
||||
USER_VARS += DEFMACHINETYPE_STRATOVIRT
|
||||
USER_VARS += KERNELPARAMS
|
||||
USER_VARS += KERNELVERITYPARAMS
|
||||
USER_VARS += KERNELTDXPARAMS
|
||||
USER_VARS += KERNELQEMUCOCODEVPARAMS
|
||||
USER_VARS += LIBEXECDIR
|
||||
|
||||
@@ -52,6 +52,11 @@ valid_hypervisor_paths = @QEMUVALIDHYPERVISORPATHS@
|
||||
# container and look for 'default-kernel-parameters' log entries.
|
||||
kernel_params = "@KERNELQEMUCOCODEVPARAMS@"
|
||||
|
||||
# Optional dm-verity parameters (comma-separated key=value list):
|
||||
# root_hash=...,salt=...,data_blocks=...,data_block_size=...,hash_block_size=...
|
||||
# These are used by the runtime to assemble dm-verity kernel params.
|
||||
kernel_verity_params = "@KERNELVERITYPARAMS@"
|
||||
|
||||
# Path to the firmware.
|
||||
# If you want that qemu uses the default firmware leave this option empty
|
||||
firmware = "@FIRMWAREPATH@"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
[hypervisor.qemu]
|
||||
path = "@QEMUSNPPATH@"
|
||||
kernel = "@KERNELPATH_CONFIDENTIAL_NV@"
|
||||
initrd = "@INITRDPATH_CONFIDENTIAL_NV@"
|
||||
image = "@IMAGEPATH_CONFIDENTIAL_NV@"
|
||||
|
||||
machine_type = "@MACHINETYPE@"
|
||||
|
||||
@@ -92,6 +92,11 @@ snp_guest_policy = 196608
|
||||
# container and look for 'default-kernel-parameters' log entries.
|
||||
kernel_params = "@KERNELPARAMS_NV@"
|
||||
|
||||
# Optional dm-verity parameters (comma-separated key=value list):
|
||||
# root_hash=...,salt=...,data_blocks=...,data_block_size=...,hash_block_size=...
|
||||
# These are used by the runtime to assemble dm-verity kernel params.
|
||||
kernel_verity_params = "@KERNELVERITYPARAMS_CONFIDENTIAL_NV@"
|
||||
|
||||
# Path to the firmware.
|
||||
# If you want that qemu uses the default firmware leave this option empty
|
||||
firmware = "@FIRMWARESNPPATH@"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
[hypervisor.qemu]
|
||||
path = "@QEMUTDXEXPERIMENTALPATH@"
|
||||
kernel = "@KERNELPATH_CONFIDENTIAL_NV@"
|
||||
initrd = "@INITRDPATH_CONFIDENTIAL_NV@"
|
||||
image = "@IMAGEPATH_CONFIDENTIAL_NV@"
|
||||
|
||||
machine_type = "@MACHINETYPE@"
|
||||
tdx_quote_generation_service_socket_port = @QEMUTDXQUOTEGENERATIONSERVICESOCKETPORT@
|
||||
@@ -69,6 +69,11 @@ valid_hypervisor_paths = @QEMUTDXEXPERIMENTALVALIDHYPERVISORPATHS@
|
||||
# container and look for 'default-kernel-parameters' log entries.
|
||||
kernel_params = "@KERNELPARAMS_NV@"
|
||||
|
||||
# Optional dm-verity parameters (comma-separated key=value list):
|
||||
# root_hash=...,salt=...,data_blocks=...,data_block_size=...,hash_block_size=...
|
||||
# These are used by the runtime to assemble dm-verity kernel params.
|
||||
kernel_verity_params = "@KERNELVERITYPARAMS_CONFIDENTIAL_NV@"
|
||||
|
||||
# Path to the firmware.
|
||||
# If you want that qemu uses the default firmware leave this option empty
|
||||
firmware = "@FIRMWARETDVFPATH@"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
[hypervisor.qemu]
|
||||
path = "@QEMUPATH@"
|
||||
kernel = "@KERNELPATH_NV@"
|
||||
initrd = "@INITRDPATH_NV@"
|
||||
image = "@IMAGEPATH_NV@"
|
||||
machine_type = "@MACHINETYPE@"
|
||||
|
||||
# rootfs filesystem type:
|
||||
@@ -51,6 +51,11 @@ valid_hypervisor_paths = @QEMUVALIDHYPERVISORPATHS@
|
||||
# container and look for 'default-kernel-parameters' log entries.
|
||||
kernel_params = "@KERNELPARAMS_NV@"
|
||||
|
||||
# Optional dm-verity parameters (comma-separated key=value list):
|
||||
# root_hash=...,salt=...,data_blocks=...,data_block_size=...,hash_block_size=...
|
||||
# These are used by the runtime to assemble dm-verity kernel params.
|
||||
kernel_verity_params = "@KERNELVERITYPARAMS_NV@"
|
||||
|
||||
# Path to the firmware.
|
||||
# If you want that qemu uses the default firmware leave this option empty
|
||||
firmware = "@FIRMWAREPATH@"
|
||||
|
||||
@@ -68,6 +68,11 @@ valid_hypervisor_paths = @QEMUVALIDHYPERVISORPATHS@
|
||||
# container and look for 'default-kernel-parameters' log entries.
|
||||
kernel_params = "@KERNELTDXPARAMS@"
|
||||
|
||||
# Optional dm-verity parameters (comma-separated key=value list):
|
||||
# root_hash=...,salt=...,data_blocks=...,data_block_size=...,hash_block_size=...
|
||||
# These are used by the runtime to assemble dm-verity kernel params.
|
||||
kernel_verity_params = "@KERNELVERITYPARAMS@"
|
||||
|
||||
# Path to the firmware.
|
||||
# If you want that qemu uses the default firmware leave this option empty
|
||||
firmware = "@FIRMWARETDVFPATH@"
|
||||
|
||||
@@ -93,6 +93,7 @@ type hypervisor struct {
|
||||
MachineAccelerators string `toml:"machine_accelerators"`
|
||||
CPUFeatures string `toml:"cpu_features"`
|
||||
KernelParams string `toml:"kernel_params"`
|
||||
KernelVerityParams string `toml:"kernel_verity_params"`
|
||||
MachineType string `toml:"machine_type"`
|
||||
QgsPort uint32 `toml:"tdx_quote_generation_service_socket_port"`
|
||||
BlockDeviceDriver string `toml:"block_device_driver"`
|
||||
@@ -387,6 +388,10 @@ func (h hypervisor) kernelParams() string {
|
||||
return h.KernelParams
|
||||
}
|
||||
|
||||
func (h hypervisor) kernelVerityParams() string {
|
||||
return h.KernelVerityParams
|
||||
}
|
||||
|
||||
func (h hypervisor) machineType() string {
|
||||
if h.MachineType == "" {
|
||||
return defaultMachineType
|
||||
@@ -814,6 +819,7 @@ func newFirecrackerHypervisorConfig(h hypervisor) (vc.HypervisorConfig, error) {
|
||||
RootfsType: rootfsType,
|
||||
FirmwarePath: firmware,
|
||||
KernelParams: vc.DeserializeParams(vc.KernelParamFields(kernelParams)),
|
||||
KernelVerityParams: h.kernelVerityParams(),
|
||||
NumVCPUsF: h.defaultVCPUs(),
|
||||
DefaultMaxVCPUs: h.defaultMaxVCPUs(),
|
||||
MemorySize: h.defaultMemSz(),
|
||||
@@ -948,6 +954,7 @@ func newQemuHypervisorConfig(h hypervisor) (vc.HypervisorConfig, error) {
|
||||
MachineAccelerators: machineAccelerators,
|
||||
CPUFeatures: cpuFeatures,
|
||||
KernelParams: vc.DeserializeParams(vc.KernelParamFields(kernelParams)),
|
||||
KernelVerityParams: h.kernelVerityParams(),
|
||||
HypervisorMachineType: machineType,
|
||||
QgsPort: h.qgsPort(),
|
||||
NumVCPUsF: h.defaultVCPUs(),
|
||||
@@ -1088,6 +1095,7 @@ func newClhHypervisorConfig(h hypervisor) (vc.HypervisorConfig, error) {
|
||||
FirmwarePath: firmware,
|
||||
MachineAccelerators: machineAccelerators,
|
||||
KernelParams: vc.DeserializeParams(vc.KernelParamFields(kernelParams)),
|
||||
KernelVerityParams: h.kernelVerityParams(),
|
||||
HypervisorMachineType: machineType,
|
||||
NumVCPUsF: h.defaultVCPUs(),
|
||||
DefaultMaxVCPUs: h.defaultMaxVCPUs(),
|
||||
@@ -1165,16 +1173,17 @@ func newDragonballHypervisorConfig(h hypervisor) (vc.HypervisorConfig, error) {
|
||||
kernelParams := h.kernelParams()
|
||||
|
||||
return vc.HypervisorConfig{
|
||||
KernelPath: kernel,
|
||||
ImagePath: image,
|
||||
RootfsType: rootfsType,
|
||||
KernelParams: vc.DeserializeParams(vc.KernelParamFields(kernelParams)),
|
||||
NumVCPUsF: h.defaultVCPUs(),
|
||||
DefaultMaxVCPUs: h.defaultMaxVCPUs(),
|
||||
MemorySize: h.defaultMemSz(),
|
||||
MemSlots: h.defaultMemSlots(),
|
||||
EntropySource: h.GetEntropySource(),
|
||||
Debug: h.Debug,
|
||||
KernelPath: kernel,
|
||||
ImagePath: image,
|
||||
RootfsType: rootfsType,
|
||||
KernelParams: vc.DeserializeParams(vc.KernelParamFields(kernelParams)),
|
||||
KernelVerityParams: h.kernelVerityParams(),
|
||||
NumVCPUsF: h.defaultVCPUs(),
|
||||
DefaultMaxVCPUs: h.defaultMaxVCPUs(),
|
||||
MemorySize: h.defaultMemSz(),
|
||||
MemSlots: h.defaultMemSlots(),
|
||||
EntropySource: h.GetEntropySource(),
|
||||
Debug: h.Debug,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1249,6 +1258,7 @@ func newStratovirtHypervisorConfig(h hypervisor) (vc.HypervisorConfig, error) {
|
||||
ImagePath: image,
|
||||
RootfsType: rootfsType,
|
||||
KernelParams: vc.DeserializeParams(strings.Fields(kernelParams)),
|
||||
KernelVerityParams: h.kernelVerityParams(),
|
||||
HypervisorMachineType: machineType,
|
||||
NumVCPUsF: h.defaultVCPUs(),
|
||||
DefaultMaxVCPUs: h.defaultMaxVCPUs(),
|
||||
|
||||
@@ -636,6 +636,15 @@ func addHypervisorPathOverrides(ocispec specs.Spec, config *vc.SandboxConfig, ru
|
||||
}
|
||||
}
|
||||
|
||||
if value, ok := ocispec.Annotations[vcAnnotations.KernelVerityParams]; ok {
|
||||
if value != "" {
|
||||
if _, err := vc.ParseKernelVerityParams(value); err != nil {
|
||||
return fmt.Errorf("invalid kernel_verity_params in annotation: %w", err)
|
||||
}
|
||||
config.HypervisorConfig.KernelVerityParams = value
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -466,8 +466,8 @@ func (clh *cloudHypervisor) enableProtection() error {
|
||||
}
|
||||
}
|
||||
|
||||
func getNonUserDefinedKernelParams(rootfstype string, disableNvdimm bool, dax bool, debug bool, confidential bool, iommu bool) ([]Param, error) {
|
||||
params, err := GetKernelRootParams(rootfstype, disableNvdimm, dax)
|
||||
func getNonUserDefinedKernelParams(rootfstype string, disableNvdimm bool, dax bool, debug bool, confidential bool, iommu bool, kernelVerityParams string) ([]Param, error) {
|
||||
params, err := GetKernelRootParams(rootfstype, disableNvdimm, dax, kernelVerityParams)
|
||||
if err != nil {
|
||||
return []Param{}, err
|
||||
}
|
||||
@@ -587,7 +587,7 @@ func (clh *cloudHypervisor) CreateVM(ctx context.Context, id string, network Net
|
||||
disableNvdimm := (clh.config.DisableImageNvdimm || clh.config.ConfidentialGuest)
|
||||
enableDax := !disableNvdimm
|
||||
|
||||
params, err := getNonUserDefinedKernelParams(hypervisorConfig.RootfsType, disableNvdimm, enableDax, clh.config.Debug, clh.config.ConfidentialGuest, clh.config.IOMMU)
|
||||
params, err := getNonUserDefinedKernelParams(hypervisorConfig.RootfsType, disableNvdimm, enableDax, clh.config.Debug, clh.config.ConfidentialGuest, clh.config.IOMMU, hypervisorConfig.KernelVerityParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -699,7 +699,12 @@ func (fc *firecracker) fcInitConfiguration(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
params, err := GetKernelRootParams(fc.config.RootfsType, true, false)
|
||||
params, err := GetKernelRootParams(
|
||||
fc.config.RootfsType,
|
||||
true,
|
||||
false,
|
||||
fc.config.KernelVerityParams,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -126,18 +127,56 @@ const (
|
||||
EROFS RootfsType = "erofs"
|
||||
)
|
||||
|
||||
func GetKernelRootParams(rootfstype string, disableNvdimm bool, dax bool) ([]Param, error) {
|
||||
var kernelRootParams []Param
|
||||
func GetKernelRootParams(rootfstype string, disableNvdimm bool, dax bool, kernelVerityParams string) ([]Param, error) {
|
||||
cfg, err := ParseKernelVerityParams(kernelVerityParams)
|
||||
if err != nil {
|
||||
return []Param{}, err
|
||||
}
|
||||
|
||||
// EXT4 filesystem is used by default.
|
||||
if rootfstype == "" {
|
||||
rootfstype = string(EXT4)
|
||||
}
|
||||
|
||||
if cfg != nil {
|
||||
rootDevice := "/dev/pmem0p1"
|
||||
hashDevice := "/dev/pmem0p2"
|
||||
if disableNvdimm {
|
||||
rootDevice = "/dev/vda1"
|
||||
hashDevice = "/dev/vda2"
|
||||
}
|
||||
|
||||
dataSectors := (cfg.dataBlockSize / 512) * cfg.dataBlocks
|
||||
verityCmd := fmt.Sprintf(
|
||||
"dm-verity,,,ro,0 %d verity 1 %s %s %d %d %d 0 sha256 %s %s",
|
||||
dataSectors,
|
||||
rootDevice,
|
||||
hashDevice,
|
||||
cfg.dataBlockSize,
|
||||
cfg.hashBlockSize,
|
||||
cfg.dataBlocks,
|
||||
cfg.rootHash,
|
||||
cfg.salt,
|
||||
)
|
||||
|
||||
rootFlags, err := kernelVerityRootFlags(rootfstype)
|
||||
if err != nil {
|
||||
return []Param{}, err
|
||||
}
|
||||
|
||||
return []Param{
|
||||
{Key: "dm-mod.create", Value: fmt.Sprintf("\"%s\"", verityCmd)},
|
||||
{Key: "root", Value: "/dev/dm-0"},
|
||||
{Key: "rootflags", Value: rootFlags},
|
||||
{Key: "rootfstype", Value: rootfstype},
|
||||
}, nil
|
||||
}
|
||||
|
||||
if disableNvdimm && dax {
|
||||
return []Param{}, fmt.Errorf("Virtio-Blk does not support DAX")
|
||||
}
|
||||
|
||||
kernelRootParams := []Param{}
|
||||
if disableNvdimm {
|
||||
// Virtio-Blk
|
||||
kernelRootParams = append(kernelRootParams, Param{"root", string(VirtioBlk)})
|
||||
@@ -171,10 +210,116 @@ func GetKernelRootParams(rootfstype string, disableNvdimm bool, dax bool) ([]Par
|
||||
}
|
||||
|
||||
kernelRootParams = append(kernelRootParams, Param{"rootfstype", rootfstype})
|
||||
|
||||
return kernelRootParams, nil
|
||||
}
|
||||
|
||||
const (
|
||||
verityBlockSizeBytes = 512
|
||||
)
|
||||
|
||||
type kernelVerityConfig struct {
|
||||
rootHash string
|
||||
salt string
|
||||
dataBlocks uint64
|
||||
dataBlockSize uint64
|
||||
hashBlockSize uint64
|
||||
}
|
||||
|
||||
func ParseKernelVerityParams(params string) (*kernelVerityConfig, error) {
|
||||
if strings.TrimSpace(params) == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
values := map[string]string{}
|
||||
for _, field := range strings.Split(params, ",") {
|
||||
field = strings.TrimSpace(field)
|
||||
if field == "" {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(field, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid kernel_verity_params entry: %q", field)
|
||||
}
|
||||
values[parts[0]] = parts[1]
|
||||
}
|
||||
|
||||
cfg := &kernelVerityConfig{
|
||||
rootHash: values["root_hash"],
|
||||
salt: values["salt"],
|
||||
}
|
||||
if cfg.rootHash == "" {
|
||||
return nil, fmt.Errorf("missing kernel_verity_params root_hash")
|
||||
}
|
||||
|
||||
parseUintField := func(name string) (uint64, error) {
|
||||
value, ok := values[name]
|
||||
if !ok || value == "" {
|
||||
return 0, fmt.Errorf("missing kernel_verity_params %s", name)
|
||||
}
|
||||
parsed, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid kernel_verity_params %s %q: %w", name, value, err)
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
dataBlocks, err := parseUintField("data_blocks")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dataBlockSize, err := parseUintField("data_block_size")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hashBlockSize, err := parseUintField("hash_block_size")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cfg.salt == "" {
|
||||
return nil, fmt.Errorf("missing kernel_verity_params salt")
|
||||
}
|
||||
if dataBlocks == 0 {
|
||||
return nil, fmt.Errorf("invalid kernel_verity_params data_blocks: must be non-zero")
|
||||
}
|
||||
if dataBlockSize == 0 {
|
||||
return nil, fmt.Errorf("invalid kernel_verity_params data_block_size: must be non-zero")
|
||||
}
|
||||
if hashBlockSize == 0 {
|
||||
return nil, fmt.Errorf("invalid kernel_verity_params hash_block_size: must be non-zero")
|
||||
}
|
||||
if dataBlockSize%verityBlockSizeBytes != 0 {
|
||||
return nil, fmt.Errorf("invalid kernel_verity_params data_block_size: must be multiple of %d", verityBlockSizeBytes)
|
||||
}
|
||||
if hashBlockSize%verityBlockSizeBytes != 0 {
|
||||
return nil, fmt.Errorf("invalid kernel_verity_params hash_block_size: must be multiple of %d", verityBlockSizeBytes)
|
||||
}
|
||||
|
||||
cfg.dataBlocks = dataBlocks
|
||||
cfg.dataBlockSize = dataBlockSize
|
||||
cfg.hashBlockSize = hashBlockSize
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func kernelVerityRootFlags(rootfstype string) (string, error) {
|
||||
// EXT4 filesystem is used by default.
|
||||
if rootfstype == "" {
|
||||
rootfstype = string(EXT4)
|
||||
}
|
||||
|
||||
switch RootfsType(rootfstype) {
|
||||
case EROFS:
|
||||
return "ro", nil
|
||||
case XFS:
|
||||
return "ro", nil
|
||||
case EXT4:
|
||||
return "data=ordered,errors=remount-ro ro", nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported rootfs type")
|
||||
}
|
||||
}
|
||||
|
||||
// DeviceType describes a virtualized device type.
|
||||
type DeviceType int
|
||||
|
||||
@@ -483,6 +628,9 @@ type HypervisorConfig struct {
|
||||
// KernelParams are additional guest kernel parameters.
|
||||
KernelParams []Param
|
||||
|
||||
// KernelVerityParams are additional guest dm-verity parameters.
|
||||
KernelVerityParams string
|
||||
|
||||
// HypervisorParams are additional hypervisor parameters.
|
||||
HypervisorParams []Param
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ func TestGetKernelRootParams(t *testing.T) {
|
||||
expected []Param
|
||||
disableNvdimm bool
|
||||
dax bool
|
||||
verityParams string
|
||||
error bool
|
||||
}{
|
||||
// EXT4
|
||||
@@ -34,6 +35,7 @@ func TestGetKernelRootParams(t *testing.T) {
|
||||
},
|
||||
disableNvdimm: false,
|
||||
dax: false,
|
||||
verityParams: "",
|
||||
error: false,
|
||||
},
|
||||
{
|
||||
@@ -45,6 +47,7 @@ func TestGetKernelRootParams(t *testing.T) {
|
||||
},
|
||||
disableNvdimm: false,
|
||||
dax: true,
|
||||
verityParams: "",
|
||||
error: false,
|
||||
},
|
||||
{
|
||||
@@ -56,6 +59,7 @@ func TestGetKernelRootParams(t *testing.T) {
|
||||
},
|
||||
disableNvdimm: true,
|
||||
dax: false,
|
||||
verityParams: "",
|
||||
error: false,
|
||||
},
|
||||
|
||||
@@ -69,6 +73,7 @@ func TestGetKernelRootParams(t *testing.T) {
|
||||
},
|
||||
disableNvdimm: false,
|
||||
dax: false,
|
||||
verityParams: "",
|
||||
error: false,
|
||||
},
|
||||
{
|
||||
@@ -80,6 +85,7 @@ func TestGetKernelRootParams(t *testing.T) {
|
||||
},
|
||||
disableNvdimm: false,
|
||||
dax: true,
|
||||
verityParams: "",
|
||||
error: false,
|
||||
},
|
||||
{
|
||||
@@ -91,6 +97,7 @@ func TestGetKernelRootParams(t *testing.T) {
|
||||
},
|
||||
disableNvdimm: true,
|
||||
dax: false,
|
||||
verityParams: "",
|
||||
error: false,
|
||||
},
|
||||
|
||||
@@ -104,6 +111,7 @@ func TestGetKernelRootParams(t *testing.T) {
|
||||
},
|
||||
disableNvdimm: false,
|
||||
dax: false,
|
||||
verityParams: "",
|
||||
error: false,
|
||||
},
|
||||
{
|
||||
@@ -115,6 +123,7 @@ func TestGetKernelRootParams(t *testing.T) {
|
||||
},
|
||||
disableNvdimm: false,
|
||||
dax: true,
|
||||
verityParams: "",
|
||||
error: false,
|
||||
},
|
||||
{
|
||||
@@ -126,6 +135,7 @@ func TestGetKernelRootParams(t *testing.T) {
|
||||
},
|
||||
disableNvdimm: true,
|
||||
dax: false,
|
||||
verityParams: "",
|
||||
error: false,
|
||||
},
|
||||
|
||||
@@ -139,6 +149,7 @@ func TestGetKernelRootParams(t *testing.T) {
|
||||
},
|
||||
disableNvdimm: false,
|
||||
dax: false,
|
||||
verityParams: "",
|
||||
error: true,
|
||||
},
|
||||
|
||||
@@ -152,12 +163,61 @@ func TestGetKernelRootParams(t *testing.T) {
|
||||
},
|
||||
disableNvdimm: true,
|
||||
dax: true,
|
||||
verityParams: "",
|
||||
error: true,
|
||||
},
|
||||
{
|
||||
rootfstype: string(EXT4),
|
||||
expected: []Param{
|
||||
{
|
||||
Key: "dm-mod.create",
|
||||
Value: "\"dm-verity,,,ro,0 8 verity 1 /dev/vda1 /dev/vda2 4096 4096 1 0 sha256 abc def\"",
|
||||
},
|
||||
{Key: "root", Value: "/dev/dm-0"},
|
||||
{Key: "rootflags", Value: "data=ordered,errors=remount-ro ro"},
|
||||
{Key: "rootfstype", Value: string(EXT4)},
|
||||
},
|
||||
disableNvdimm: true,
|
||||
dax: false,
|
||||
verityParams: "root_hash=abc,salt=def,data_blocks=1,data_block_size=4096,hash_block_size=4096",
|
||||
error: false,
|
||||
},
|
||||
{
|
||||
rootfstype: string(EXT4),
|
||||
expected: []Param{},
|
||||
disableNvdimm: false,
|
||||
dax: false,
|
||||
verityParams: "root_hash=abc,data_blocks=1,data_block_size=4096,hash_block_size=4096",
|
||||
error: true,
|
||||
},
|
||||
{
|
||||
rootfstype: string(EXT4),
|
||||
expected: []Param{},
|
||||
disableNvdimm: false,
|
||||
dax: false,
|
||||
verityParams: "root_hash=abc,salt=def,data_block_size=4096,hash_block_size=4096",
|
||||
error: true,
|
||||
},
|
||||
{
|
||||
rootfstype: string(EXT4),
|
||||
expected: []Param{},
|
||||
disableNvdimm: false,
|
||||
dax: false,
|
||||
verityParams: "root_hash=abc,salt=def,data_blocks=foo,data_block_size=4096,hash_block_size=4096",
|
||||
error: true,
|
||||
},
|
||||
{
|
||||
rootfstype: string(EXT4),
|
||||
expected: []Param{},
|
||||
disableNvdimm: false,
|
||||
dax: false,
|
||||
verityParams: "root_hash=abc,salt=def,data_blocks=1,data_block_size=4096,hash_block_size=4096,badfield",
|
||||
error: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, t := range tests {
|
||||
kernelRootParams, err := GetKernelRootParams(t.rootfstype, t.disableNvdimm, t.dax)
|
||||
kernelRootParams, err := GetKernelRootParams(t.rootfstype, t.disableNvdimm, t.dax, t.verityParams)
|
||||
if t.error {
|
||||
assert.Error(err)
|
||||
continue
|
||||
|
||||
@@ -84,6 +84,9 @@ const (
|
||||
// KernelParams is a sandbox annotation for passing additional guest kernel parameters.
|
||||
KernelParams = kataAnnotHypervisorPrefix + "kernel_params"
|
||||
|
||||
// KernelVerityParams is a sandbox annotation for passing guest dm-verity parameters.
|
||||
KernelVerityParams = kataAnnotHypervisorPrefix + "kernel_verity_params"
|
||||
|
||||
// MachineType is a sandbox annotation to specify the type of machine being emulated by the hypervisor.
|
||||
MachineType = kataAnnotHypervisorPrefix + "machine_type"
|
||||
|
||||
|
||||
@@ -773,7 +773,12 @@ func (q *qemuArchBase) setEndpointDevicePath(endpoint Endpoint, bridgeAddr int,
|
||||
|
||||
func (q *qemuArchBase) handleImagePath(config HypervisorConfig) error {
|
||||
if config.ImagePath != "" {
|
||||
kernelRootParams, err := GetKernelRootParams(config.RootfsType, q.disableNvdimm, false)
|
||||
kernelRootParams, err := GetKernelRootParams(
|
||||
config.RootfsType,
|
||||
q.disableNvdimm,
|
||||
false,
|
||||
config.KernelVerityParams,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -781,7 +786,12 @@ func (q *qemuArchBase) handleImagePath(config HypervisorConfig) error {
|
||||
q.qemuMachine.Options = strings.Join([]string{
|
||||
q.qemuMachine.Options, qemuNvdimmOption,
|
||||
}, ",")
|
||||
kernelRootParams, err = GetKernelRootParams(config.RootfsType, q.disableNvdimm, q.dax)
|
||||
kernelRootParams, err = GetKernelRootParams(
|
||||
config.RootfsType,
|
||||
q.disableNvdimm,
|
||||
q.dax,
|
||||
config.KernelVerityParams,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -83,7 +83,12 @@ func newQemuArch(config HypervisorConfig) (qemuArch, error) {
|
||||
}
|
||||
|
||||
if config.ImagePath != "" {
|
||||
kernelParams, err := GetKernelRootParams(config.RootfsType, true, false)
|
||||
kernelParams, err := GetKernelRootParams(
|
||||
config.RootfsType,
|
||||
true,
|
||||
false,
|
||||
config.KernelVerityParams,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -337,7 +337,12 @@ func (s *stratovirt) getKernelParams(machineType string, initrdPath string) (str
|
||||
var kernelParams []Param
|
||||
|
||||
if initrdPath == "" {
|
||||
params, err := GetKernelRootParams(s.config.RootfsType, true, false)
|
||||
params, err := GetKernelRootParams(
|
||||
s.config.RootfsType,
|
||||
true,
|
||||
false,
|
||||
s.config.KernelVerityParams,
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -151,26 +151,30 @@ setup() {
|
||||
[[ -n "$qemu_cmd" ]] || { echo "Could not find QEMU command line"; return 1; }
|
||||
|
||||
kernel_path=$(echo "$qemu_cmd" | grep -oP -- '-kernel \K[^ ]+')
|
||||
initrd_path=$(echo "$qemu_cmd" | grep -oP -- '-initrd \K[^ ]+')
|
||||
initrd_path=$(echo "$qemu_cmd" | grep -oP -- '-initrd \K[^ ]+' || true)
|
||||
firmware_path=$(echo "$qemu_cmd" | grep -oP -- '-bios \K[^ ]+')
|
||||
vcpu_count=$(echo "$qemu_cmd" | grep -oP -- '-smp \K\d+')
|
||||
append=$(echo "$qemu_cmd" | sed -n 's/.*-append \(.*\) -bios.*/\1/p')
|
||||
# Remove escape backslashes for quotes from output for dm-mod.create parameters
|
||||
append="${append//\\\"/\"}"
|
||||
|
||||
launch_measurement=$(PATH="${PATH}:${HOME}/.local/bin" sev-snp-measure \
|
||||
--mode=snp \
|
||||
--vcpus="${vcpu_count}" \
|
||||
--vcpu-type=EPYC-v4 \
|
||||
--output-format=base64 \
|
||||
--ovmf="${firmware_path}" \
|
||||
--kernel="${kernel_path}" \
|
||||
--initrd="${initrd_path}" \
|
||||
--append="${append}" \
|
||||
measure_args=(
|
||||
--mode=snp
|
||||
--vcpus="${vcpu_count}"
|
||||
--vcpu-type=EPYC-v4
|
||||
--output-format=base64
|
||||
--ovmf="${firmware_path}"
|
||||
--kernel="${kernel_path}"
|
||||
--append="${append}"
|
||||
)
|
||||
if [[ -n "${initrd_path}" ]]; then
|
||||
measure_args+=(--initrd="${initrd_path}")
|
||||
fi
|
||||
launch_measurement=$(PATH="${PATH}:${HOME}/.local/bin" sev-snp-measure "${measure_args[@]}")
|
||||
|
||||
# set launch measurement as reference value
|
||||
# set launch measurement as reference value
|
||||
kbs_config_command set-sample-reference-value snp_launch_measurement "${launch_measurement}"
|
||||
|
||||
|
||||
# Get the reported firmware version(s) for this machine
|
||||
firmware=$(sudo snphost show tcb | grep -A 5 "Reported TCB")
|
||||
|
||||
@@ -204,7 +208,6 @@ setup() {
|
||||
kbs_config_command set-sample-reference-value snp_launch_measurement abcd
|
||||
|
||||
[ "$result" -eq 0 ]
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,11 @@ load "${BATS_TEST_DIRNAME}/../../common.bash"
|
||||
load "${BATS_TEST_DIRNAME}/lib.sh"
|
||||
load "${BATS_TEST_DIRNAME}/tests_common.sh"
|
||||
|
||||
# Currently only the Go runtime provides the config path used here.
|
||||
# If a Rust hypervisor runs this test, mirror the enabling_hypervisor
|
||||
# pattern in tests/common.bash to select the correct runtime-rs config.
|
||||
shim_config_file="/opt/kata/share/defaults/kata-containers/configuration-${KATA_HYPERVISOR}.toml"
|
||||
|
||||
check_and_skip() {
|
||||
case "${KATA_HYPERVISOR}" in
|
||||
qemu-tdx|qemu-coco-dev)
|
||||
@@ -29,26 +34,29 @@ setup() {
|
||||
setup_common || die "setup_common failed"
|
||||
}
|
||||
|
||||
@test "Test cannnot launch pod with measured boot enabled and incorrect hash" {
|
||||
@test "Test cannot launch pod with measured boot enabled and incorrect hash" {
|
||||
pod_config="$(new_pod_config nginx "kata-${KATA_HYPERVISOR}")"
|
||||
auto_generate_policy "${pod_config_dir}" "${pod_config}"
|
||||
|
||||
incorrect_hash="1111111111111111111111111111111111111111111111111111111111111111"
|
||||
|
||||
# To avoid editing that file on the worker node, here it will be
|
||||
# enabled via pod annotations.
|
||||
# Read verity parameters from config, then override via annotations.
|
||||
kernel_verity_params=$(exec_host "$node" "sed -n 's/^kernel_verity_params = \"\\(.*\\)\"/\\1/p' ${shim_config_file}" || true)
|
||||
[ -n "${kernel_verity_params}" ] || die "Missing kernel_verity_params in ${shim_config_file}"
|
||||
|
||||
kernel_verity_params=$(printf '%s\n' "$kernel_verity_params" | sed -E "s/root_hash=[^,]*/root_hash=${incorrect_hash}/")
|
||||
set_metadata_annotation "$pod_config" \
|
||||
"io.katacontainers.config.hypervisor.kernel_params" \
|
||||
"rootfs_verity.scheme=dm-verity rootfs_verity.hash=$incorrect_hash"
|
||||
"io.katacontainers.config.hypervisor.kernel_verity_params" \
|
||||
"${kernel_verity_params}"
|
||||
# Run on a specific node so we know from where to inspect the logs
|
||||
set_node "$pod_config" "$node"
|
||||
|
||||
# For debug sake
|
||||
echo "Pod $pod_config file:"
|
||||
cat $pod_config
|
||||
kubectl apply -f $pod_config
|
||||
|
||||
waitForProcess "60" "3" "exec_host $node journalctl -t kata | grep \"verity: .* metadata block .* is corrupted\""
|
||||
assert_pod_container_creating "$pod_config"
|
||||
assert_logs_contain "$node" kata "${node_start_time}" "verity: .* metadata block .* is corrupted"
|
||||
}
|
||||
|
||||
teardown() {
|
||||
|
||||
@@ -179,7 +179,7 @@ assert_pod_fail() {
|
||||
local container_config="$1"
|
||||
local duration="${2:-120}"
|
||||
|
||||
echo "In assert_pod_fail: $container_config"
|
||||
echo "In assert_pod_fail: ${container_config}"
|
||||
echo "Attempt to create the container but it should fail"
|
||||
|
||||
retry_kubectl_apply "${container_config}"
|
||||
@@ -192,13 +192,13 @@ assert_pod_fail() {
|
||||
local sleep_time=5
|
||||
while true; do
|
||||
echo "Waiting for a container to fail"
|
||||
sleep ${sleep_time}
|
||||
sleep "${sleep_time}"
|
||||
elapsed_time=$((elapsed_time+sleep_time))
|
||||
if [[ $(kubectl get pod "${pod_name}" \
|
||||
-o jsonpath='{.status.containerStatuses[0].state.waiting.reason}') = *BackOff* ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [ $elapsed_time -gt $duration ]; then
|
||||
if [[ "${elapsed_time}" -gt "${duration}" ]]; then
|
||||
echo "The container does not get into a failing state" >&2
|
||||
break
|
||||
fi
|
||||
@@ -207,6 +207,46 @@ assert_pod_fail() {
|
||||
|
||||
}
|
||||
|
||||
# Create a pod then assert it remains in ContainerCreating.
|
||||
#
|
||||
# Parameters:
|
||||
# $1 - the pod configuration file.
|
||||
# $2 - the duration to wait (seconds). Defaults to 60. (optional)
|
||||
#
|
||||
assert_pod_container_creating() {
|
||||
local container_config="$1"
|
||||
local duration="${2:-60}"
|
||||
|
||||
echo "In assert_pod_container_creating: ${container_config}"
|
||||
echo "Attempt to create the container but it should stay in creating state"
|
||||
|
||||
retry_kubectl_apply "${container_config}"
|
||||
if ! pod_name=$(kubectl get pods -o jsonpath='{.items..metadata.name}'); then
|
||||
echo "Failed to create the pod"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local elapsed_time=0
|
||||
local sleep_time=5
|
||||
while true; do
|
||||
sleep "${sleep_time}"
|
||||
elapsed_time=$((elapsed_time+sleep_time))
|
||||
reason=$(kubectl get pod "${pod_name}" -o jsonpath='{.status.containerStatuses[0].state.waiting.reason}' 2>/dev/null || true)
|
||||
phase=$(kubectl get pod "${pod_name}" -o jsonpath='{.status.phase}' 2>/dev/null || true)
|
||||
if [[ "${phase}" != "Pending" ]]; then
|
||||
echo "Expected pod to remain Pending, got phase: ${phase}" >&2
|
||||
return 1
|
||||
fi
|
||||
if [[ -n "${reason}" && "${reason}" != "ContainerCreating" ]]; then
|
||||
echo "Expected ContainerCreating, got: ${reason}" >&2
|
||||
return 1
|
||||
fi
|
||||
if [[ "${elapsed_time}" -ge "${duration}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Check the pulled rootfs on host for given node and sandbox_id
|
||||
#
|
||||
# Parameters:
|
||||
@@ -381,4 +421,3 @@ get_node_kata_sandbox_id() {
|
||||
done
|
||||
echo $kata_sandbox_id
|
||||
}
|
||||
|
||||
|
||||
1
tools/osbuilder/.gitignore
vendored
1
tools/osbuilder/.gitignore
vendored
@@ -7,5 +7,6 @@ dracut/dracut.conf.d/15-extra-libs.conf
|
||||
kata-centos-dnf.conf
|
||||
kata-containers-initrd.img
|
||||
kata-containers.img
|
||||
root_hash*.txt
|
||||
rootfs-builder/centos/RPM-GPG-KEY-*
|
||||
typescript
|
||||
|
||||
@@ -23,6 +23,7 @@ ARCH=${ARCH:-$(uname -m)}
|
||||
[ "${TARGET_ARCH}" == "aarch64" ] && TARGET_ARCH=arm64
|
||||
TARGET_OS=${TARGET_OS:-linux}
|
||||
[ "${CROSS_BUILD}" == "true" ] && BUILDX=buildx && PLATFORM="--platform=${TARGET_OS}/${TARGET_ARCH}"
|
||||
BUILD_VARIANT=${BUILD_VARIANT:-}
|
||||
|
||||
readonly script_name="${0##*/}"
|
||||
readonly script_dir=$(dirname "$(readlink -f "$0")")
|
||||
@@ -177,6 +178,7 @@ build_with_container() {
|
||||
--env USER="$(id -u)" \
|
||||
--env GROUP="$(id -g)" \
|
||||
--env IMAGE_SIZE_ALIGNMENT_MB="${IMAGE_SIZE_ALIGNMENT_MB}" \
|
||||
--env BUILD_VARIANT="${BUILD_VARIANT}" \
|
||||
-v /dev:/dev \
|
||||
-v "${script_dir}":"/osbuilder" \
|
||||
-v "${script_dir}/../scripts":"/scripts" \
|
||||
@@ -394,7 +396,7 @@ create_disk() {
|
||||
else
|
||||
rootfs_end_unit="MiB"
|
||||
fi
|
||||
if [ "${MEASURED_ROOTFS}" == "yes" ]; then
|
||||
if [[ "${MEASURED_ROOTFS}" == "yes" ]]; then
|
||||
info "Creating partitions with hash device"
|
||||
# The hash data will take less than one percent disk space to store
|
||||
hash_start=$(echo $img_size | awk '{print $1 * 0.99}' |cut -d $(locale decimal_point) -f 1)
|
||||
@@ -484,10 +486,49 @@ create_rootfs_image() {
|
||||
fsck.ext4 -D -y "${device}p1"
|
||||
fi
|
||||
|
||||
if [ "${MEASURED_ROOTFS}" == "yes" ] && [ -b "${device}p2" ]; then
|
||||
if [[ "${MEASURED_ROOTFS}" == "yes" ]] && [[ -b "${device}p2" ]]; then
|
||||
info "veritysetup format rootfs device: ${device}p1, hash device: ${device}p2"
|
||||
local image_dir=$(dirname "${image}")
|
||||
veritysetup format "${device}p1" "${device}p2" > "${image_dir}"/root_hash.txt 2>&1
|
||||
local -r image_dir=$(dirname "${image}")
|
||||
local verity_output
|
||||
verity_output=$(veritysetup format --no-superblock "${device}p1" "${device}p2" 2>&1)
|
||||
build_kernel_verity_params() {
|
||||
local -r output="$1"
|
||||
local root_hash
|
||||
local salt
|
||||
local data_blocks
|
||||
local data_block_size
|
||||
local hash_block_size
|
||||
|
||||
read_verity_field() {
|
||||
local -r label="$1"
|
||||
local value
|
||||
|
||||
value=$(printf '%s\n' "${output}" | sed -n "s/^${label}:[[:space:]]*//p")
|
||||
value="${value// \[*/}"
|
||||
[[ -n "${value}" ]] || die "Missing '${label}' in verity output for ${image}"
|
||||
|
||||
echo "${value}"
|
||||
}
|
||||
|
||||
root_hash=$(read_verity_field "Root hash")
|
||||
salt=$(read_verity_field "Salt")
|
||||
data_blocks=$(read_verity_field "Data blocks")
|
||||
data_block_size=$(read_verity_field "Data block size")
|
||||
hash_block_size=$(read_verity_field "Hash block size")
|
||||
|
||||
printf 'root_hash=%s,salt=%s,data_blocks=%s,data_block_size=%s,hash_block_size=%s' \
|
||||
"${root_hash}" \
|
||||
"${salt}" \
|
||||
"${data_blocks}" \
|
||||
"${data_block_size}" \
|
||||
"${hash_block_size}"
|
||||
}
|
||||
|
||||
local kernel_verity_params
|
||||
kernel_verity_params="$(build_kernel_verity_params "${verity_output}")"
|
||||
|
||||
printf '%s\n' "${kernel_verity_params}" > "${image_dir}"/root_hash_"${BUILD_VARIANT}".txt
|
||||
OK "Root hash file created for variant: ${BUILD_VARIANT}"
|
||||
fi
|
||||
|
||||
losetup -d "${device}"
|
||||
@@ -500,7 +541,7 @@ create_erofs_rootfs_image() {
|
||||
local block_size="$3"
|
||||
local agent_bin="$4"
|
||||
|
||||
if [ "$block_size" -ne 4096 ]; then
|
||||
if [[ "${block_size}" -ne 4096 ]]; then
|
||||
die "Invalid block size for erofs"
|
||||
fi
|
||||
|
||||
@@ -508,7 +549,8 @@ create_erofs_rootfs_image() {
|
||||
die "Could not setup loop device"
|
||||
fi
|
||||
|
||||
local mount_dir=$(mktemp -p "${TMPDIR:-/tmp}" -d osbuilder-mount-dir.XXXX)
|
||||
local mount_dir
|
||||
mount_dir=$(mktemp -p "${TMPDIR:-/tmp}" -d osbuilder-mount-dir.XXXX)
|
||||
|
||||
info "Copying content from rootfs to root partition"
|
||||
cp -a "${rootfs}"/* "${mount_dir}"
|
||||
@@ -522,10 +564,10 @@ create_erofs_rootfs_image() {
|
||||
info "Setup systemd"
|
||||
setup_systemd "${mount_dir}"
|
||||
|
||||
readonly fsimage="$(mktemp)"
|
||||
local -r fsimage="$(mktemp)"
|
||||
mkfs.erofs -Enoinline_data "${fsimage}" "${mount_dir}"
|
||||
local img_size="$(stat -c"%s" "${fsimage}")"
|
||||
local img_size_mb="$(((("${img_size}" + 1048576) / 1048576) + 1 + "${rootfs_start}"))"
|
||||
local -r img_size="$(stat -c"%s" "${fsimage}")"
|
||||
local -r img_size_mb="$(((("${img_size}" + 1048576) / 1048576) + 1 + "${rootfs_start}"))"
|
||||
|
||||
create_disk "${image}" "${img_size_mb}" "ext4" "${rootfs_start}"
|
||||
|
||||
|
||||
@@ -147,6 +147,18 @@ install_nvidia_dcgm() {
|
||||
datacenter-gpu-manager-exporter
|
||||
}
|
||||
|
||||
install_devkit_packages() {
|
||||
is_feature_enabled "devkit" || {
|
||||
echo "chroot: Skipping devkit packages installation"
|
||||
return
|
||||
}
|
||||
|
||||
echo "chroot: Install devkit packages"
|
||||
|
||||
eval "${APT_INSTALL}" kmod
|
||||
apt-mark hold kmod
|
||||
}
|
||||
|
||||
cleanup_rootfs() {
|
||||
echo "chroot: Cleanup NVIDIA GPU rootfs"
|
||||
|
||||
@@ -174,4 +186,5 @@ install_userspace_components
|
||||
install_nvidia_fabricmanager
|
||||
install_nvidia_ctk
|
||||
install_nvidia_dcgm
|
||||
install_devkit_packages
|
||||
cleanup_rootfs
|
||||
|
||||
@@ -24,7 +24,7 @@ KBUILD_SIGN_PIN=${KBUILD_SIGN_PIN:-}
|
||||
AGENT_POLICY="${AGENT_POLICY:-no}"
|
||||
|
||||
NVIDIA_GPU_STACK=${NVIDIA_GPU_STACK:?NVIDIA_GPU_STACK must be set}
|
||||
VARIANT=${VARIANT:?VARIANT must be set}
|
||||
BUILD_VARIANT=${BUILD_VARIANT:?BUILD_VARIANT must be set}
|
||||
ARCH=${ARCH:?ARCH must be set}
|
||||
|
||||
machine_arch="${ARCH}"
|
||||
@@ -37,7 +37,7 @@ else
|
||||
die "Unsupported architecture: ${machine_arch}"
|
||||
fi
|
||||
|
||||
readonly stage_one="${BUILD_DIR:?}/rootfs-${VARIANT:?}-stage-one"
|
||||
readonly stage_one="${BUILD_DIR:?}/rootfs-${BUILD_VARIANT:?}-stage-one"
|
||||
|
||||
setup_nvidia-nvrc() {
|
||||
local url ver
|
||||
|
||||
@@ -60,9 +60,9 @@ REPO_COMPONENTS=${REPO_COMPONENTS:-""}
|
||||
|
||||
KBUILD_SIGN_PIN=${KBUILD_SIGN_PIN:-""}
|
||||
NVIDIA_GPU_STACK=${NVIDIA_GPU_STACK:-""}
|
||||
VARIANT=${VARIANT:-""}
|
||||
BUILD_VARIANT=${BUILD_VARIANT:-""}
|
||||
|
||||
[[ "${VARIANT}" == "nvidia-gpu"* ]] && source "${script_dir}/nvidia/nvidia_rootfs.sh"
|
||||
[[ "${BUILD_VARIANT}" == "nvidia-gpu"* ]] && source "${script_dir}/nvidia/nvidia_rootfs.sh"
|
||||
|
||||
#For cross build
|
||||
CROSS_BUILD=${CROSS_BUILD:-false}
|
||||
@@ -571,7 +571,7 @@ build_rootfs_distro()
|
||||
--env REPO_COMPONENTS="${REPO_COMPONENTS}" \
|
||||
--env OSBUILDER_VERSION="${OSBUILDER_VERSION}" \
|
||||
--env OS_VERSION="${OS_VERSION}" \
|
||||
--env VARIANT="${VARIANT}" \
|
||||
--env BUILD_VARIANT="${BUILD_VARIANT}" \
|
||||
--env INSIDE_CONTAINER=1 \
|
||||
--env SECCOMP="${SECCOMP}" \
|
||||
--env SELINUX="${SELINUX}" \
|
||||
@@ -913,13 +913,13 @@ main()
|
||||
init="${ROOTFS_DIR}/sbin/init"
|
||||
setup_rootfs
|
||||
|
||||
if [ "${VARIANT}" = "nvidia-gpu" ]; then
|
||||
if [ "${BUILD_VARIANT}" = "nvidia-gpu" ]; then
|
||||
setup_nvidia_gpu_rootfs_stage_one
|
||||
setup_nvidia_gpu_rootfs_stage_two
|
||||
return $?
|
||||
fi
|
||||
|
||||
if [ "${VARIANT}" = "nvidia-gpu-confidential" ]; then
|
||||
if [ "${BUILD_VARIANT}" = "nvidia-gpu-confidential" ]; then
|
||||
setup_nvidia_gpu_rootfs_stage_one "confidential"
|
||||
setup_nvidia_gpu_rootfs_stage_two "confidential"
|
||||
return $?
|
||||
|
||||
@@ -10,7 +10,8 @@ OS_VERSION=${OS_VERSION:-""}
|
||||
[ -z "$OS_VERSION" ] && echo "OS_VERSION is required, but was not set" && exit 1
|
||||
PACKAGES="chrony iptables dbus"
|
||||
[ "$AGENT_INIT" = no ] && PACKAGES+=" init"
|
||||
[ "$MEASURED_ROOTFS" = yes ] && PACKAGES+=" cryptsetup-bin e2fsprogs"
|
||||
# CDH secure storage feature requires these tools in the guest
|
||||
[[ "${CONFIDENTIAL_GUEST:-no}" = "yes" ]] && PACKAGES+=" cryptsetup-bin e2fsprogs"
|
||||
[ "$SECCOMP" = yes ] && PACKAGES+=" libseccomp2"
|
||||
[ "$(uname -m)" = "s390x" ] && PACKAGES+=" libcurl4 libnghttp2-14"
|
||||
REPO_COMPONENTS=${REPO_COMPONENTS:-main}
|
||||
|
||||
@@ -38,7 +38,7 @@ build_initrd() {
|
||||
info "initrd os: $os_name"
|
||||
info "initrd os version: $os_version"
|
||||
make initrd \
|
||||
VARIANT="${image_initrd_suffix}" \
|
||||
BUILD_VARIANT="${image_initrd_suffix}" \
|
||||
DISTRO="$os_name" \
|
||||
DEBUG="${DEBUG:-}" \
|
||||
OS_VERSION="${os_version}" \
|
||||
@@ -68,7 +68,7 @@ build_image() {
|
||||
info "image os: $os_name"
|
||||
info "image os version: $os_version"
|
||||
make image \
|
||||
VARIANT="${image_initrd_suffix}" \
|
||||
BUILD_VARIANT="${image_initrd_suffix}" \
|
||||
DISTRO="${os_name}" \
|
||||
DEBUG="${DEBUG:-}" \
|
||||
USE_DOCKER="1" \
|
||||
@@ -86,8 +86,9 @@ build_image() {
|
||||
fi
|
||||
|
||||
mv -f "kata-containers.img" "${install_dir}/${artifact_name}"
|
||||
if [ -e "root_hash.txt" ]; then
|
||||
cp root_hash.txt "${install_dir}/"
|
||||
if [[ -e "root_hash_${image_initrd_suffix}.txt" ]]; then
|
||||
info "Copying root hash file for variant: ${image_initrd_suffix} ${PWD}"
|
||||
cp "root_hash_${image_initrd_suffix}.txt" "${install_dir}/"
|
||||
fi
|
||||
(
|
||||
cd "${install_dir}"
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
# Copyright (c) 2026 Kata Contributors
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Helm image based on kata-containers/kubectl for multi-arch support
|
||||
# Used for kata-lifecycle-manager workflows and other helm operations
|
||||
|
||||
# hadolint ignore=DL3007
|
||||
FROM quay.io/kata-containers/kubectl:latest
|
||||
|
||||
# Use bash with pipefail for safer pipe handling
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
# Download and install helm
|
||||
# hadolint ignore=DL3018
|
||||
RUN ARCH=$(uname -m) && \
|
||||
case "${ARCH}" in \
|
||||
x86_64) HELM_ARCH=amd64 ;; \
|
||||
aarch64) HELM_ARCH=arm64 ;; \
|
||||
ppc64le) HELM_ARCH=ppc64le ;; \
|
||||
s390x) HELM_ARCH=s390x ;; \
|
||||
*) echo "Unsupported architecture: ${ARCH}" && exit 1 ;; \
|
||||
esac && \
|
||||
HELM_VERSION=$(curl -s https://api.github.com/repos/helm/helm/releases/latest | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/') && \
|
||||
curl -fL --progress-bar -o /tmp/helm.tar.gz \
|
||||
"https://get.helm.sh/helm-${HELM_VERSION}-linux-${HELM_ARCH}.tar.gz" && \
|
||||
tar -xzf /tmp/helm.tar.gz -C /tmp && \
|
||||
mv "/tmp/linux-${HELM_ARCH}/helm" /usr/local/bin/helm && \
|
||||
rm -rf /tmp/helm.tar.gz "/tmp/linux-${HELM_ARCH}" && \
|
||||
chmod +x /usr/local/bin/helm && \
|
||||
helm version --short
|
||||
|
||||
# Default to bash shell
|
||||
CMD ["/bin/bash"]
|
||||
@@ -448,7 +448,3 @@ kata-qemu-snp-cicd kata-qemu-snp-cicd 77s
|
||||
kata-qemu-tdx-cicd kata-qemu-tdx-cicd 77s
|
||||
kata-stratovirt-cicd kata-stratovirt-cicd 77s
|
||||
```
|
||||
|
||||
## Related Charts
|
||||
|
||||
- [kata-lifecycle-manager](kata-lifecycle-manager/README.md) - Argo Workflows-based lifecycle management for controlled, node-by-node upgrades with verification and automatic rollback
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
# Copyright (c) 2026 The Kata Containers Authors
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
apiVersion: v2
|
||||
name: kata-lifecycle-manager
|
||||
description: Argo Workflows-based lifecycle management for Kata Containers
|
||||
|
||||
type: application
|
||||
|
||||
# Chart version - follows kata-containers versioning
|
||||
version: "3.26.0"
|
||||
|
||||
# App version - matches kata-containers
|
||||
appVersion: "3.26.0"
|
||||
|
||||
keywords:
|
||||
- kata-containers
|
||||
- lifecycle-management
|
||||
- upgrade
|
||||
- argo-workflows
|
||||
- gitops
|
||||
|
||||
home: https://katacontainers.io
|
||||
|
||||
sources:
|
||||
- https://github.com/kata-containers/kata-containers
|
||||
|
||||
maintainers:
|
||||
- name: Kata Containers Community
|
||||
|
||||
annotations:
|
||||
kata-containers.io/companion-chart: kata-deploy
|
||||
@@ -1,333 +0,0 @@
|
||||
# Kata Lifecycle Manager Helm Chart
|
||||
|
||||
Argo Workflows-based lifecycle management for Kata Containers.
|
||||
|
||||
This chart installs a namespace-scoped `WorkflowTemplate` that performs controlled,
|
||||
node-by-node upgrades of kata-deploy with verification and automatic rollback on failure.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Kubernetes cluster with kata-deploy installed via Helm
|
||||
- [Argo Workflows](https://argoproj.github.io/argo-workflows/) v3.4+ installed
|
||||
- `helm` and `argo` CLI tools
|
||||
- **Verification pod spec** (see [Verification Pod](#verification-pod-required))
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# From OCI registry (when published)
|
||||
helm install kata-lifecycle-manager oci://ghcr.io/kata-containers/kata-deploy-charts/kata-lifecycle-manager
|
||||
|
||||
# From local source
|
||||
helm install kata-lifecycle-manager ./kata-lifecycle-manager
|
||||
```
|
||||
|
||||
## Verification Pod (Required)
|
||||
|
||||
A verification pod is **required** to validate each node after upgrade. The chart
|
||||
will fail to install without one.
|
||||
|
||||
### Option A: Bake into kata-lifecycle-manager (recommended)
|
||||
|
||||
Provide the verification pod when installing the chart:
|
||||
|
||||
```bash
|
||||
helm install kata-lifecycle-manager ./kata-lifecycle-manager \
|
||||
--set-file defaults.verificationPod=./my-verification-pod.yaml
|
||||
```
|
||||
|
||||
This verification pod is baked into the `WorkflowTemplate` and used for all upgrades.
|
||||
|
||||
### Option B: Override at workflow submission
|
||||
|
||||
One-off override for a specific upgrade run. The pod spec must be base64-encoded
|
||||
because Argo workflow parameters don't handle multi-line YAML reliably:
|
||||
|
||||
```bash
|
||||
argo submit -n argo --from workflowtemplate/kata-lifecycle-manager \
|
||||
-p target-version=3.25.0 \
|
||||
-p verification-pod="$(base64 -w0 < ./my-verification-pod.yaml)"
|
||||
```
|
||||
|
||||
**Note:** During helm upgrade, kata-`kata-deploy`'s own verification is disabled
|
||||
(`--set verification.pod=""`). This is because kata-`kata-deploy`'s verification is
|
||||
cluster-wide (designed for initial install), while kata-lifecycle-manager performs
|
||||
per-node verification with proper placeholder substitution.
|
||||
|
||||
### Verification Pod Spec
|
||||
|
||||
Create a pod spec that validates your Kata deployment. The pod should exit 0 on success,
|
||||
non-zero on failure.
|
||||
|
||||
**Example (`my-verification-pod.yaml`):**
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: ${TEST_POD}
|
||||
spec:
|
||||
runtimeClassName: kata-qemu
|
||||
restartPolicy: Never
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: ${NODE}
|
||||
tolerations:
|
||||
- operator: Exists
|
||||
containers:
|
||||
- name: verify
|
||||
image: quay.io/kata-containers/alpine-bash-curl:latest
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
echo "=== Kata Verification ==="
|
||||
echo "Node: ${NODE}"
|
||||
echo "Kernel: $(uname -r)"
|
||||
echo "SUCCESS: Pod running with Kata runtime"
|
||||
```
|
||||
|
||||
### Placeholders
|
||||
|
||||
| Placeholder | Description |
|
||||
|-------------|-------------|
|
||||
| `${NODE}` | Node hostname being upgraded/verified |
|
||||
| `${TEST_POD}` | Generated unique pod name |
|
||||
|
||||
**You are responsible for:**
|
||||
- Setting the `runtimeClassName` in your pod spec
|
||||
- Defining the verification logic in your container
|
||||
- Using the exit code to indicate success (0) or failure (non-zero)
|
||||
|
||||
**Failure modes detected:**
|
||||
- Pod stuck in Pending/`ContainerCreating` (runtime can't start VM)
|
||||
- Pod crashes immediately (containerd/CRI-O configuration issues)
|
||||
- Pod times out (resource issues, image pull failures)
|
||||
- Pod exits with non-zero code (verification logic failed)
|
||||
|
||||
All of these trigger automatic rollback.
|
||||
|
||||
## Usage
|
||||
|
||||
### 1. Select Nodes for Upgrade
|
||||
|
||||
Nodes can be selected using **labels**, **taints**, or **both**.
|
||||
|
||||
**Option A: Label-based selection (default)**
|
||||
|
||||
```bash
|
||||
# Label nodes for upgrade
|
||||
kubectl label node worker-1 katacontainers.io/kata-lifecycle-manager-window=true
|
||||
|
||||
# Trigger upgrade
|
||||
argo submit -n argo --from workflowtemplate/kata-lifecycle-manager \
|
||||
-p target-version=3.25.0 \
|
||||
-p node-selector="katacontainers.io/kata-lifecycle-manager-window=true"
|
||||
```
|
||||
|
||||
**Option B: Taint-based selection**
|
||||
|
||||
```bash
|
||||
# Taint nodes for upgrade
|
||||
kubectl taint nodes worker-1 kata-lifecycle-manager=pending:NoSchedule
|
||||
|
||||
# Trigger upgrade using taint selector
|
||||
argo submit -n argo --from workflowtemplate/kata-lifecycle-manager \
|
||||
-p target-version=3.25.0 \
|
||||
-p node-taint-key=kata-lifecycle-manager \
|
||||
-p node-taint-value=pending
|
||||
```
|
||||
|
||||
**Option C: Combined selection**
|
||||
|
||||
```bash
|
||||
# Use both labels and taints for precise targeting
|
||||
argo submit -n argo --from workflowtemplate/kata-lifecycle-manager \
|
||||
-p target-version=3.25.0 \
|
||||
-p node-selector="node-pool=kata-pool" \
|
||||
-p node-taint-key=kata-lifecycle-manager
|
||||
```
|
||||
|
||||
### 2. Trigger Upgrade
|
||||
|
||||
```bash
|
||||
argo submit -n argo --from workflowtemplate/kata-lifecycle-manager \
|
||||
-p target-version=3.25.0
|
||||
|
||||
# Watch progress
|
||||
argo watch @latest
|
||||
```
|
||||
|
||||
### 3. Sequential Upgrade Behavior
|
||||
|
||||
Nodes are upgraded **sequentially** (one at a time) to ensure fleet consistency.
|
||||
If any node fails verification, the workflow stops immediately and that node is
|
||||
rolled back. This prevents ending up with a mixed fleet where some nodes have
|
||||
the new version and others have the old version.
|
||||
|
||||
## Configuration
|
||||
|
||||
| Parameter | Description | Default |
|
||||
|-----------|-------------|---------|
|
||||
| `argoNamespace` | Namespace for Argo resources | `argo` |
|
||||
| `defaults.helmRelease` | kata-deploy Helm release name | `kata-deploy` |
|
||||
| `defaults.helmNamespace` | kata-deploy namespace | `kube-system` |
|
||||
| `defaults.nodeSelector` | Node label selector (optional if using taints) | `""` |
|
||||
| `defaults.nodeTaintKey` | Taint key for node selection | `""` |
|
||||
| `defaults.nodeTaintValue` | Taint value filter (optional) | `""` |
|
||||
| `defaults.verificationNamespace` | Namespace for verification pods | `default` |
|
||||
| `defaults.verificationPod` | Pod YAML for verification **(required)** | `""` |
|
||||
| `defaults.drainEnabled` | Enable node drain before upgrade | `false` |
|
||||
| `defaults.drainTimeout` | Timeout for drain operation | `300s` |
|
||||
| `images.helm` | Helm container image (multi-arch) | `quay.io/kata-containers/helm:latest` |
|
||||
| `images.kubectl` | `kubectl` container image (multi-arch) | `quay.io/kata-containers/kubectl:latest` |
|
||||
|
||||
## Workflow Parameters
|
||||
|
||||
When submitting a workflow, you can override:
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| `target-version` | **Required** - Target Kata version |
|
||||
| `helm-release` | Helm release name |
|
||||
| `helm-namespace` | Namespace of kata-deploy |
|
||||
| `node-selector` | Label selector for nodes |
|
||||
| `node-taint-key` | Taint key for node selection |
|
||||
| `node-taint-value` | Taint value filter |
|
||||
| `verification-namespace` | Namespace for verification pods |
|
||||
| `verification-pod` | Pod YAML with placeholders |
|
||||
| `drain-enabled` | Whether to drain nodes before upgrade |
|
||||
| `drain-timeout` | Timeout for drain operation |
|
||||
|
||||
## Upgrade Flow
|
||||
|
||||
For each node selected by the node-selector label:
|
||||
|
||||
1. **Prepare**: Annotate node with upgrade status
|
||||
2. **Cordon**: Mark node as `unschedulable`
|
||||
3. **Drain** (optional): Evict pods if `drain-enabled=true`
|
||||
4. **Upgrade**: Run `helm upgrade` for kata-deploy
|
||||
5. **Wait**: Wait for kata-deploy DaemonSet pod to be ready
|
||||
6. **Verify**: Run verification pod and check exit code
|
||||
7. **On Success**: `Uncordon` node, proceed to next node
|
||||
8. **On Failure**: Automatic rollback, `uncordon`, workflow stops
|
||||
|
||||
Nodes are upgraded **sequentially** (one at a time). If verification fails on any node,
|
||||
the workflow stops immediately, preventing a mixed-version fleet.
|
||||
|
||||
### When to Use Drain
|
||||
|
||||
**Default (drain disabled):** Drain is not required for Kata upgrades. Running Kata
|
||||
VMs continue using the in-memory binaries. Only new workloads use the upgraded
|
||||
binaries.
|
||||
|
||||
**Optional drain:** Enable drain if you prefer to evict all workloads before any
|
||||
maintenance operation, or if your organization's operational policies require it:
|
||||
|
||||
```bash
|
||||
# Enable drain when installing the chart
|
||||
helm install kata-lifecycle-manager ./kata-lifecycle-manager \
|
||||
--set defaults.drainEnabled=true \
|
||||
--set defaults.drainTimeout=600s \
|
||||
--set-file defaults.verificationPod=./my-verification-pod.yaml
|
||||
|
||||
# Or override at workflow submission time
|
||||
argo submit -n argo --from workflowtemplate/kata-lifecycle-manager \
|
||||
-p target-version=3.25.0 \
|
||||
-p drain-enabled=true \
|
||||
-p drain-timeout=600s
|
||||
```
|
||||
|
||||
## Rollback
|
||||
|
||||
**Automatic rollback on verification failure:** If the verification pod fails (non-zero exit),
|
||||
kata-lifecycle-manager automatically:
|
||||
1. Runs `helm rollback` to revert to the previous Helm release
|
||||
2. Waits for kata-deploy DaemonSet to be ready with the previous version
|
||||
3. `Uncordons` the node
|
||||
4. Annotates the node with `rolled-back` status
|
||||
|
||||
This ensures nodes are never left in a broken state.
|
||||
|
||||
**Manual rollback:** For cases where you need to rollback a successfully upgraded node:
|
||||
|
||||
```bash
|
||||
argo submit -n argo --from workflowtemplate/kata-lifecycle-manager \
|
||||
--entrypoint rollback-node \
|
||||
-p node-name=worker-1
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
Check node annotations to monitor upgrade progress:
|
||||
|
||||
```bash
|
||||
kubectl get nodes \
|
||||
-L katacontainers.io/kata-lifecycle-manager-status \
|
||||
-L katacontainers.io/kata-current-version
|
||||
```
|
||||
|
||||
| Annotation | Description |
|
||||
|------------|-------------|
|
||||
| `katacontainers.io/kata-lifecycle-manager-status` | Current upgrade phase |
|
||||
| `katacontainers.io/kata-current-version` | Version after successful upgrade |
|
||||
|
||||
Status values:
|
||||
- `preparing` - Upgrade starting
|
||||
- `cordoned` - Node marked `unschedulable`
|
||||
- `draining` - Draining pods (only if drain-enabled=true)
|
||||
- `upgrading` - Helm upgrade in progress
|
||||
- `verifying` - Verification pod running
|
||||
- `completed` - Upgrade successful
|
||||
- `rolling-back` - Rollback in progress (automatic on verification failure)
|
||||
- `rolled-back` - Rollback completed
|
||||
|
||||
## Known Limitations
|
||||
|
||||
### Cluster-Wide DaemonSet Updates
|
||||
|
||||
The kata-deploy Helm chart uses a DaemonSet, which means `helm upgrade` updates
|
||||
all nodes simultaneously at the Kubernetes level, even though this workflow
|
||||
processes nodes sequentially for verification.
|
||||
|
||||
**Current behavior:**
|
||||
|
||||
1. Node A is cordoned and upgraded
|
||||
2. Node A verification passes, Node A is `uncordoned`
|
||||
3. New workloads can now start on Node A using the **new** Kata version
|
||||
4. Node B verification fails
|
||||
5. Automatic rollback reverts kata-deploy cluster-wide to the **old** version
|
||||
6. Workloads that started on Node A between steps 2-5 continue running with
|
||||
the new version's in-memory binaries, while new workloads use the old version
|
||||
|
||||
This is generally acceptable because:
|
||||
- Running Kata VMs continue functioning (they use in-memory binaries)
|
||||
- New workloads use the rolled-back version
|
||||
- The cluster reaches a consistent state for new workloads
|
||||
|
||||
**Future improvement:** A two-phase approach could cordon all target nodes upfront,
|
||||
perform the upgrade, verify all nodes, and only `uncordon` after all verifications
|
||||
pass. This would prevent any new workloads from using the new version until the
|
||||
entire upgrade is validated, at the cost of longer node unavailability.
|
||||
|
||||
## For Projects Using kata-deploy
|
||||
|
||||
Any project that uses the kata-deploy Helm chart can install this companion chart
|
||||
to get upgrade orchestration:
|
||||
|
||||
```bash
|
||||
# Install kata-deploy
|
||||
helm install kata-deploy oci://ghcr.io/kata-containers/kata-deploy-charts/kata-deploy \
|
||||
--namespace kube-system
|
||||
|
||||
# Install upgrade tooling with your verification config
|
||||
helm install kata-lifecycle-manager oci://ghcr.io/kata-containers/kata-deploy-charts/kata-lifecycle-manager \
|
||||
--set-file defaults.verificationPod=./my-verification-pod.yaml
|
||||
|
||||
# Trigger upgrade
|
||||
argo submit -n argo --from workflowtemplate/kata-lifecycle-manager \
|
||||
-p target-version=3.25.0
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Apache License 2.0
|
||||
@@ -1,46 +0,0 @@
|
||||
{{/*
|
||||
Copyright (c) 2026 The Kata Containers Authors
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/}}
|
||||
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "kata-lifecycle-manager.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
*/}}
|
||||
{{- define "kata-lifecycle-manager.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "kata-lifecycle-manager.labels" -}}
|
||||
helm.sh/chart: {{ include "kata-lifecycle-manager.name" . }}-{{ .Chart.Version }}
|
||||
app.kubernetes.io/name: {{ include "kata-lifecycle-manager.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
app.kubernetes.io/part-of: kata-containers
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
ServiceAccount name
|
||||
*/}}
|
||||
{{- define "kata-lifecycle-manager.serviceAccountName" -}}
|
||||
{{- include "kata-lifecycle-manager.fullname" . }}
|
||||
{{- end }}
|
||||
@@ -1,89 +0,0 @@
|
||||
{{/*
|
||||
Copyright (c) 2026 The Kata Containers Authors
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/}}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "kata-lifecycle-manager.serviceAccountName" . }}
|
||||
namespace: {{ .Values.argoNamespace }}
|
||||
labels:
|
||||
{{- include "kata-lifecycle-manager.labels" . | nindent 4 }}
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: {{ include "kata-lifecycle-manager.fullname" . }}
|
||||
labels:
|
||||
{{- include "kata-lifecycle-manager.labels" . | nindent 4 }}
|
||||
rules:
|
||||
# Node operations (cordon, drain, label)
|
||||
- apiGroups: [""]
|
||||
resources: ["nodes"]
|
||||
verbs: ["get", "list", "watch", "patch", "update"]
|
||||
# Pod operations (eviction for drain, verification pods, Argo output parameters)
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "list", "watch", "create", "delete", "patch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods/log"]
|
||||
verbs: ["get", "list", "watch", "create", "delete"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods/eviction"]
|
||||
verbs: ["create"]
|
||||
# DaemonSet operations (kata-deploy is a DaemonSet)
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["daemonsets"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
# RuntimeClass operations (kata-deploy creates RuntimeClasses)
|
||||
- apiGroups: ["node.k8s.io"]
|
||||
resources: ["runtimeclasses"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
# Helm needs these for release management
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets", "configmaps"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
# kata-deploy creates a ServiceAccount
|
||||
- apiGroups: [""]
|
||||
resources: ["serviceaccounts"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
# kata-deploy creates RBAC resources
|
||||
- apiGroups: ["rbac.authorization.k8s.io"]
|
||||
resources: ["clusterroles", "clusterrolebindings", "roles", "rolebindings"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
# Jobs (kata-deploy may have post-install/post-delete hooks)
|
||||
- apiGroups: ["batch"]
|
||||
resources: ["jobs"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
# Events for debugging
|
||||
- apiGroups: [""]
|
||||
resources: ["events"]
|
||||
verbs: ["get", "list", "watch", "create"]
|
||||
# Argo Workflows task results (for output parameters)
|
||||
- apiGroups: ["argoproj.io"]
|
||||
resources: ["workflowtaskresults"]
|
||||
verbs: ["create", "patch"]
|
||||
# CRDs (kata-deploy may reference NFD CRDs)
|
||||
- apiGroups: ["apiextensions.k8s.io"]
|
||||
resources: ["customresourcedefinitions"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
# Node Feature Discovery resources (kata-deploy NFD integration)
|
||||
- apiGroups: ["nfd.k8s-sigs.io"]
|
||||
resources: ["nodefeaturerules"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: {{ include "kata-lifecycle-manager.fullname" . }}
|
||||
labels:
|
||||
{{- include "kata-lifecycle-manager.labels" . | nindent 4 }}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ include "kata-lifecycle-manager.serviceAccountName" . }}
|
||||
namespace: {{ .Values.argoNamespace }}
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: {{ include "kata-lifecycle-manager.fullname" . }}
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
@@ -1,775 +0,0 @@
|
||||
{{/*
|
||||
Copyright (c) 2026 The Kata Containers Authors
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
Argo WorkflowTemplate for orchestrating kata-deploy upgrades.
|
||||
Uses native Argo resource templates where possible, and standard
|
||||
helm/kubectl images for operations requiring those tools.
|
||||
*/}}
|
||||
{{- if not .Values.defaults.verificationPod }}
|
||||
{{- fail "defaults.verificationPod is required. Provide a pod spec that validates your Kata deployment using --set-file defaults.verificationPod=./your-pod.yaml" }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: WorkflowTemplate
|
||||
metadata:
|
||||
name: kata-lifecycle-manager
|
||||
namespace: {{ .Values.argoNamespace }}
|
||||
labels:
|
||||
{{- include "kata-lifecycle-manager.labels" . | nindent 4 }}
|
||||
spec:
|
||||
entrypoint: upgrade-all-nodes
|
||||
serviceAccountName: {{ include "kata-lifecycle-manager.serviceAccountName" . }}
|
||||
|
||||
podGC:
|
||||
strategy: OnWorkflowSuccess
|
||||
|
||||
arguments:
|
||||
parameters:
|
||||
- name: target-version
|
||||
description: "Target Kata Containers version (e.g., 3.25.0)"
|
||||
- name: helm-release
|
||||
value: {{ .Values.defaults.helmRelease | quote }}
|
||||
description: "Helm release name"
|
||||
- name: helm-namespace
|
||||
value: {{ .Values.defaults.helmNamespace | quote }}
|
||||
description: "Namespace where kata-deploy is installed"
|
||||
- name: node-selector
|
||||
value: {{ .Values.defaults.nodeSelector | quote }}
|
||||
description: "Label selector for nodes to upgrade (optional if using taint selection)"
|
||||
- name: node-taint-key
|
||||
value: {{ .Values.defaults.nodeTaintKey | quote }}
|
||||
description: "Taint key for node selection (optional, alternative to label selector)"
|
||||
- name: node-taint-value
|
||||
value: {{ .Values.defaults.nodeTaintValue | quote }}
|
||||
description: "Taint value filter (optional, only used with node-taint-key)"
|
||||
- name: helm-image
|
||||
value: {{ .Values.images.helm | quote }}
|
||||
description: "Helm container image"
|
||||
- name: kubectl-image
|
||||
value: {{ .Values.images.kubectl | quote }}
|
||||
description: "Kubectl container image"
|
||||
- name: verification-namespace
|
||||
value: {{ .Values.defaults.verificationNamespace | quote }}
|
||||
description: "Namespace for verification pods"
|
||||
- name: verification-pod
|
||||
value: {{ .Values.defaults.verificationPod | b64enc | quote }}
|
||||
description: "Base64-encoded pod YAML for verification (uses placeholders NODE, TEST_POD)"
|
||||
- name: drain-enabled
|
||||
value: {{ .Values.defaults.drainEnabled | quote }}
|
||||
description: "Whether to drain nodes before upgrade"
|
||||
- name: drain-timeout
|
||||
value: {{ .Values.defaults.drainTimeout | quote }}
|
||||
description: "Timeout for node drain"
|
||||
|
||||
templates:
|
||||
# =========================================================================
|
||||
# MAIN ENTRYPOINT
|
||||
# =========================================================================
|
||||
- name: upgrade-all-nodes
|
||||
steps:
|
||||
- - name: validate-prerequisites
|
||||
template: check-prerequisites
|
||||
- - name: get-nodes
|
||||
template: get-target-nodes
|
||||
- - name: show-upgrade-plan
|
||||
template: print-upgrade-plan
|
||||
arguments:
|
||||
parameters:
|
||||
- name: nodes-json
|
||||
value: "{{`{{steps.get-nodes.outputs.parameters.nodes}}`}}"
|
||||
- name: node-count
|
||||
value: "{{`{{steps.get-nodes.outputs.parameters.node-count}}`}}"
|
||||
- - name: upgrade-nodes-sequentially
|
||||
template: upgrade-node-chain
|
||||
arguments:
|
||||
parameters:
|
||||
- name: nodes-json
|
||||
value: "{{`{{steps.get-nodes.outputs.parameters.nodes}}`}}"
|
||||
- name: current-index
|
||||
value: "0"
|
||||
- - name: summary
|
||||
template: print-summary
|
||||
|
||||
# =========================================================================
|
||||
# CHECK PREREQUISITES (fail fast if verification pod not configured)
|
||||
# =========================================================================
|
||||
- name: check-prerequisites
|
||||
tolerations:
|
||||
- key: node.kubernetes.io/unschedulable
|
||||
operator: Exists
|
||||
- key: node.kubernetes.io/disk-pressure
|
||||
operator: Exists
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
script:
|
||||
image: "{{`{{workflow.parameters.helm-image}}`}}"
|
||||
command: [sh]
|
||||
source: |
|
||||
set -e
|
||||
|
||||
RELEASE="{{`{{workflow.parameters.helm-release}}`}}"
|
||||
NS="{{`{{workflow.parameters.helm-namespace}}`}}"
|
||||
VERIFICATION_POD="{{`{{workflow.parameters.verification-pod}}`}}"
|
||||
|
||||
echo "=============================================="
|
||||
echo " VALIDATING PREREQUISITES"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
|
||||
# Check verification pod availability
|
||||
echo "Checking verification pod configuration..."
|
||||
|
||||
if [ -n "$VERIFICATION_POD" ]; then
|
||||
echo "✓ Verification pod configured"
|
||||
else
|
||||
echo ""
|
||||
echo "ERROR: No verification pod configured!"
|
||||
echo ""
|
||||
echo "The upgrade cannot proceed without a verification pod."
|
||||
echo ""
|
||||
echo "This should not happen if kata-lifecycle-manager was installed correctly."
|
||||
echo "Reinstall with: helm upgrade kata-lifecycle-manager ... --set-file defaults.verificationPod=<path>"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check kata-deploy release exists
|
||||
echo "Checking kata-deploy Helm release..."
|
||||
if helm status "$RELEASE" -n "$NS" &>/dev/null; then
|
||||
CURRENT_VERSION=$(helm get metadata "$RELEASE" -n "$NS" -o json 2>/dev/null | grep -o '"version":"[^"]*"' | cut -d'"' -f4 || echo "unknown")
|
||||
echo "✓ Found Helm release: $RELEASE (chart version: $CURRENT_VERSION)"
|
||||
else
|
||||
echo ""
|
||||
echo "ERROR: Helm release '$RELEASE' not found in namespace '$NS'"
|
||||
echo ""
|
||||
echo "Make sure kata-deploy is installed via Helm before running upgrade."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=============================================="
|
||||
echo " ALL PREREQUISITES PASSED"
|
||||
echo "=============================================="
|
||||
|
||||
# =========================================================================
|
||||
# PRINT UPGRADE PLAN (shows all nodes before starting)
|
||||
# =========================================================================
|
||||
- name: print-upgrade-plan
|
||||
inputs:
|
||||
parameters:
|
||||
- name: nodes-json
|
||||
- name: node-count
|
||||
tolerations:
|
||||
- key: node.kubernetes.io/unschedulable
|
||||
operator: Exists
|
||||
- key: node.kubernetes.io/disk-pressure
|
||||
operator: Exists
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
script:
|
||||
image: "{{`{{workflow.parameters.kubectl-image}}`}}"
|
||||
command: [sh]
|
||||
source: |
|
||||
apk add --no-cache -q jq 2>/dev/null || true
|
||||
|
||||
NODES_JSON='{{`{{inputs.parameters.nodes-json}}`}}'
|
||||
NODE_COUNT={{`{{inputs.parameters.node-count}}`}}
|
||||
VERSION="{{`{{workflow.parameters.target-version}}`}}"
|
||||
|
||||
echo "=============================================="
|
||||
echo " KATA CONTAINERS UPGRADE PLAN"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
echo "Target Version: $VERSION"
|
||||
echo "Total Nodes: $NODE_COUNT"
|
||||
echo "Mode: Sequential (one at a time)"
|
||||
echo ""
|
||||
echo "Nodes to upgrade (in order):"
|
||||
echo "----------------------------------------------"
|
||||
|
||||
INDEX=1
|
||||
echo "$NODES_JSON" | jq -r '.[]' | while read NODE; do
|
||||
echo " $INDEX. $NODE"
|
||||
INDEX=$((INDEX + 1))
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "----------------------------------------------"
|
||||
echo "Upgrade will stop immediately on first failure"
|
||||
echo "=============================================="
|
||||
|
||||
# =========================================================================
|
||||
# SEQUENTIAL NODE CHAIN (recursive: upgrades one node, then next)
|
||||
# Stops immediately on first failure - no mixed fleet possible
|
||||
# =========================================================================
|
||||
- name: upgrade-node-chain
|
||||
inputs:
|
||||
parameters:
|
||||
- name: nodes-json
|
||||
- name: current-index
|
||||
steps:
|
||||
# Extract current node info
|
||||
- - name: node-info
|
||||
template: get-node-at-index
|
||||
arguments:
|
||||
parameters:
|
||||
- name: nodes-json
|
||||
value: "{{`{{inputs.parameters.nodes-json}}`}}"
|
||||
- name: index
|
||||
value: "{{`{{inputs.parameters.current-index}}`}}"
|
||||
# Upgrade current node (shows all sub-steps: prepare, cordon, upgrade, verify...)
|
||||
- - name: upgrade
|
||||
template: upgrade-single-node
|
||||
arguments:
|
||||
parameters:
|
||||
- name: node-name
|
||||
value: "{{`{{steps.node-info.outputs.parameters.node-name}}`}}"
|
||||
# Continue to next node only if this one succeeded and more nodes exist
|
||||
- - name: next
|
||||
template: upgrade-node-chain
|
||||
when: "{{`{{steps.node-info.outputs.parameters.has-more}}`}} == true"
|
||||
arguments:
|
||||
parameters:
|
||||
- name: nodes-json
|
||||
value: "{{`{{inputs.parameters.nodes-json}}`}}"
|
||||
- name: current-index
|
||||
value: "{{`{{steps.node-info.outputs.parameters.next-index}}`}}"
|
||||
|
||||
# =========================================================================
|
||||
# GET NODE AT INDEX (helper for sequential chain)
|
||||
# =========================================================================
|
||||
- name: get-node-at-index
|
||||
inputs:
|
||||
parameters:
|
||||
- name: nodes-json
|
||||
- name: index
|
||||
outputs:
|
||||
parameters:
|
||||
- name: node-name
|
||||
valueFrom:
|
||||
path: /tmp/node-name.txt
|
||||
- name: has-more
|
||||
valueFrom:
|
||||
path: /tmp/has-more.txt
|
||||
- name: next-index
|
||||
valueFrom:
|
||||
path: /tmp/next-index.txt
|
||||
tolerations:
|
||||
- key: node.kubernetes.io/unschedulable
|
||||
operator: Exists
|
||||
- key: node.kubernetes.io/disk-pressure
|
||||
operator: Exists
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
script:
|
||||
image: "{{`{{workflow.parameters.kubectl-image}}`}}"
|
||||
command: [sh]
|
||||
source: |
|
||||
set -e
|
||||
apk add --no-cache -q jq 2>/dev/null || true
|
||||
NODES_JSON='{{`{{inputs.parameters.nodes-json}}`}}'
|
||||
INDEX={{`{{inputs.parameters.index}}`}}
|
||||
|
||||
NODE=$(echo "$NODES_JSON" | jq -r ".[$INDEX]")
|
||||
TOTAL=$(echo "$NODES_JSON" | jq 'length')
|
||||
NEXT=$((INDEX + 1))
|
||||
|
||||
echo "$NODE" > /tmp/node-name.txt
|
||||
echo "$NEXT" > /tmp/next-index.txt
|
||||
|
||||
if [ "$NEXT" -lt "$TOTAL" ]; then
|
||||
echo "true" > /tmp/has-more.txt
|
||||
else
|
||||
echo "false" > /tmp/has-more.txt
|
||||
fi
|
||||
|
||||
echo "=== Node $((INDEX + 1)) of $TOTAL: $NODE ==="
|
||||
|
||||
# =========================================================================
|
||||
# GET TARGET NODES (supports label selector, taint selector, or both)
|
||||
# =========================================================================
|
||||
- name: get-target-nodes
|
||||
# Tolerate system taints so workflow pods can schedule during upgrades
|
||||
tolerations:
|
||||
- key: node.kubernetes.io/unschedulable
|
||||
operator: Exists
|
||||
- key: node.kubernetes.io/disk-pressure
|
||||
operator: Exists
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
outputs:
|
||||
parameters:
|
||||
- name: nodes
|
||||
valueFrom:
|
||||
path: /tmp/nodes.json
|
||||
- name: node-count
|
||||
valueFrom:
|
||||
path: /tmp/count.txt
|
||||
script:
|
||||
image: "{{`{{workflow.parameters.kubectl-image}}`}}"
|
||||
command: [bash]
|
||||
source: |
|
||||
set -e
|
||||
LABEL_SELECTOR="{{`{{workflow.parameters.node-selector}}`}}"
|
||||
TAINT_KEY="{{`{{workflow.parameters.node-taint-key}}`}}"
|
||||
TAINT_VALUE="{{`{{workflow.parameters.node-taint-value}}`}}"
|
||||
|
||||
# Get nodes based on label selector (or all nodes if no selector)
|
||||
if [[ -n "$LABEL_SELECTOR" ]]; then
|
||||
NODE_NAMES=$(kubectl get nodes -l "$LABEL_SELECTOR" -o jsonpath='{.items[*].metadata.name}')
|
||||
else
|
||||
NODE_NAMES=$(kubectl get nodes -o jsonpath='{.items[*].metadata.name}')
|
||||
fi
|
||||
|
||||
if [[ -z "$NODE_NAMES" ]]; then
|
||||
echo "[]" > /tmp/nodes.json
|
||||
echo "0" > /tmp/count.txt
|
||||
echo "No nodes found matching label selector"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# If taint key specified, filter nodes by taint
|
||||
if [[ -n "$TAINT_KEY" ]]; then
|
||||
TAINT_FILTERED=""
|
||||
for node in $NODE_NAMES; do
|
||||
# Get taints for this node
|
||||
if [[ -n "$TAINT_VALUE" ]]; then
|
||||
# Check for specific taint key=value
|
||||
HAS_TAINT=$(kubectl get node "$node" -o jsonpath="{.spec.taints[?(@.key=='$TAINT_KEY' && @.value=='$TAINT_VALUE')].key}" 2>/dev/null || echo "")
|
||||
else
|
||||
# Check for taint key only
|
||||
HAS_TAINT=$(kubectl get node "$node" -o jsonpath="{.spec.taints[?(@.key=='$TAINT_KEY')].key}" 2>/dev/null || echo "")
|
||||
fi
|
||||
if [[ -n "$HAS_TAINT" ]]; then
|
||||
TAINT_FILTERED="$TAINT_FILTERED $node"
|
||||
fi
|
||||
done
|
||||
NODE_NAMES=$(echo "$TAINT_FILTERED" | xargs)
|
||||
fi
|
||||
|
||||
if [[ -z "$NODE_NAMES" ]]; then
|
||||
echo "[]" > /tmp/nodes.json
|
||||
echo "0" > /tmp/count.txt
|
||||
echo "No nodes found matching selection criteria"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Convert space-separated names to sorted newline-separated
|
||||
NODE_LIST=$(echo "$NODE_NAMES" | tr ' ' '\n' | sort)
|
||||
NODE_COUNT=$(echo "$NODE_LIST" | wc -l)
|
||||
|
||||
# Output as JSON array (manually build it without jq)
|
||||
JSON_ARRAY="["
|
||||
FIRST=true
|
||||
for node in $NODE_LIST; do
|
||||
if [[ "$FIRST" == "true" ]]; then
|
||||
JSON_ARRAY="${JSON_ARRAY}\"${node}\""
|
||||
FIRST=false
|
||||
else
|
||||
JSON_ARRAY="${JSON_ARRAY},\"${node}\""
|
||||
fi
|
||||
done
|
||||
JSON_ARRAY="${JSON_ARRAY}]"
|
||||
|
||||
echo "$JSON_ARRAY" > /tmp/nodes.json
|
||||
echo "$NODE_COUNT" > /tmp/count.txt
|
||||
|
||||
echo "Found $NODE_COUNT nodes for upgrade:"
|
||||
echo "$NODE_LIST"
|
||||
|
||||
# =========================================================================
|
||||
# UPGRADE SINGLE NODE
|
||||
# =========================================================================
|
||||
- name: upgrade-single-node
|
||||
inputs:
|
||||
parameters:
|
||||
- name: node-name
|
||||
steps:
|
||||
- - name: prepare
|
||||
template: prepare-node
|
||||
arguments:
|
||||
parameters:
|
||||
- name: node-name
|
||||
value: "{{`{{inputs.parameters.node-name}}`}}"
|
||||
- - name: cordon
|
||||
template: cordon-node
|
||||
arguments:
|
||||
parameters:
|
||||
- name: node-name
|
||||
value: "{{`{{inputs.parameters.node-name}}`}}"
|
||||
- - name: drain
|
||||
template: drain-node
|
||||
when: "{{`{{workflow.parameters.drain-enabled}}`}} == true"
|
||||
arguments:
|
||||
parameters:
|
||||
- name: node-name
|
||||
value: "{{`{{inputs.parameters.node-name}}`}}"
|
||||
- - name: upgrade
|
||||
template: helm-upgrade
|
||||
arguments:
|
||||
parameters:
|
||||
- name: node-name
|
||||
value: "{{`{{inputs.parameters.node-name}}`}}"
|
||||
- - name: wait-ready
|
||||
template: wait-kata-ready
|
||||
arguments:
|
||||
parameters:
|
||||
- name: node-name
|
||||
value: "{{`{{inputs.parameters.node-name}}`}}"
|
||||
- - name: verify-and-complete
|
||||
template: verify-and-complete-node
|
||||
arguments:
|
||||
parameters:
|
||||
- name: node-name
|
||||
value: "{{`{{inputs.parameters.node-name}}`}}"
|
||||
|
||||
# =========================================================================
|
||||
# PREPARE NODE
|
||||
# =========================================================================
|
||||
- name: prepare-node
|
||||
inputs:
|
||||
parameters:
|
||||
- name: node-name
|
||||
resource:
|
||||
action: patch
|
||||
mergeStrategy: merge
|
||||
manifest: |
|
||||
apiVersion: v1
|
||||
kind: Node
|
||||
metadata:
|
||||
name: "{{`{{inputs.parameters.node-name}}`}}"
|
||||
annotations:
|
||||
katacontainers.io/kata-lifecycle-manager-status: "preparing"
|
||||
|
||||
# =========================================================================
|
||||
# CORDON NODE
|
||||
# =========================================================================
|
||||
- name: cordon-node
|
||||
inputs:
|
||||
parameters:
|
||||
- name: node-name
|
||||
resource:
|
||||
action: patch
|
||||
mergeStrategy: merge
|
||||
manifest: |
|
||||
apiVersion: v1
|
||||
kind: Node
|
||||
metadata:
|
||||
name: "{{`{{inputs.parameters.node-name}}`}}"
|
||||
annotations:
|
||||
katacontainers.io/kata-lifecycle-manager-status: "cordoned"
|
||||
spec:
|
||||
unschedulable: true
|
||||
|
||||
# =========================================================================
|
||||
# DRAIN NODE (optional, only runs if drain-enabled is true)
|
||||
# =========================================================================
|
||||
- name: drain-node
|
||||
inputs:
|
||||
parameters:
|
||||
- name: node-name
|
||||
tolerations:
|
||||
- key: node.kubernetes.io/unschedulable
|
||||
operator: Exists
|
||||
- key: node.kubernetes.io/disk-pressure
|
||||
operator: Exists
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
script:
|
||||
image: "{{`{{workflow.parameters.kubectl-image}}`}}"
|
||||
command: [bash]
|
||||
source: |
|
||||
set -e
|
||||
NODE="{{`{{inputs.parameters.node-name}}`}}"
|
||||
TIMEOUT="{{`{{workflow.parameters.drain-timeout}}`}}"
|
||||
|
||||
# On failure: mark node as failed and uncordon
|
||||
cleanup_on_failure() {
|
||||
echo "ERROR: Drain failed, cleaning up node $NODE"
|
||||
kubectl annotate node "$NODE" --overwrite katacontainers.io/kata-lifecycle-manager-status="failed" || true
|
||||
kubectl uncordon "$NODE" || true
|
||||
}
|
||||
trap cleanup_on_failure EXIT
|
||||
|
||||
kubectl annotate node "$NODE" --overwrite katacontainers.io/kata-lifecycle-manager-status="draining"
|
||||
kubectl drain "$NODE" \
|
||||
--ignore-daemonsets \
|
||||
--delete-emptydir-data \
|
||||
--force \
|
||||
--timeout="$TIMEOUT"
|
||||
|
||||
# Success - remove the trap
|
||||
trap - EXIT
|
||||
|
||||
# =========================================================================
|
||||
# HELM UPGRADE
|
||||
# =========================================================================
|
||||
- name: helm-upgrade
|
||||
inputs:
|
||||
parameters:
|
||||
- name: node-name
|
||||
tolerations:
|
||||
- key: node.kubernetes.io/unschedulable
|
||||
operator: Exists
|
||||
- key: node.kubernetes.io/disk-pressure
|
||||
operator: Exists
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
script:
|
||||
image: "{{`{{workflow.parameters.helm-image}}`}}"
|
||||
command: [bash]
|
||||
source: |
|
||||
set -e
|
||||
NODE="{{`{{inputs.parameters.node-name}}`}}"
|
||||
VERSION="{{`{{workflow.parameters.target-version}}`}}"
|
||||
RELEASE="{{`{{workflow.parameters.helm-release}}`}}"
|
||||
NS="{{`{{workflow.parameters.helm-namespace}}`}}"
|
||||
CHART="oci://ghcr.io/kata-containers/kata-deploy-charts/kata-deploy"
|
||||
|
||||
apk add --no-cache -q kubectl
|
||||
kubectl annotate node "$NODE" --overwrite katacontainers.io/kata-lifecycle-manager-status="upgrading"
|
||||
|
||||
# On failure: mark node as failed and uncordon
|
||||
cleanup_on_failure() {
|
||||
echo "ERROR: Helm upgrade failed, cleaning up node $NODE"
|
||||
kubectl annotate node "$NODE" --overwrite katacontainers.io/kata-lifecycle-manager-status="failed" || true
|
||||
kubectl uncordon "$NODE" || true
|
||||
}
|
||||
trap cleanup_on_failure EXIT
|
||||
|
||||
# Disable kata-deploy's verification (--set verification.pod="") because:
|
||||
# - kata-deploy verification is cluster-wide (runs once after helm upgrade)
|
||||
# - kata-lifecycle-manager does per-node verification in verify-and-complete-node
|
||||
# The per-node verification is more appropriate for rolling upgrades.
|
||||
helm upgrade "$RELEASE" "$CHART" \
|
||||
--namespace "$NS" \
|
||||
--version "$VERSION" \
|
||||
--reuse-values \
|
||||
--set verification.pod="" \
|
||||
--rollback-on-failure \
|
||||
--timeout 10m \
|
||||
--wait
|
||||
|
||||
# Success - remove the trap so we don't run cleanup
|
||||
trap - EXIT
|
||||
|
||||
# =========================================================================
|
||||
# WAIT FOR KATA-DEPLOY READY
|
||||
# =========================================================================
|
||||
- name: wait-kata-ready
|
||||
inputs:
|
||||
parameters:
|
||||
- name: node-name
|
||||
tolerations:
|
||||
- key: node.kubernetes.io/unschedulable
|
||||
operator: Exists
|
||||
- key: node.kubernetes.io/disk-pressure
|
||||
operator: Exists
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
script:
|
||||
image: "{{`{{workflow.parameters.kubectl-image}}`}}"
|
||||
command: [bash]
|
||||
source: |
|
||||
set -e
|
||||
NODE="{{`{{inputs.parameters.node-name}}`}}"
|
||||
NS="{{`{{workflow.parameters.helm-namespace}}`}}"
|
||||
|
||||
# On failure: mark node as failed and uncordon
|
||||
cleanup_on_failure() {
|
||||
echo "ERROR: Timed out waiting for kata-deploy, cleaning up node $NODE"
|
||||
kubectl annotate node "$NODE" --overwrite katacontainers.io/kata-lifecycle-manager-status="failed" || true
|
||||
kubectl uncordon "$NODE" || true
|
||||
}
|
||||
trap cleanup_on_failure EXIT
|
||||
|
||||
for i in $(seq 1 60); do
|
||||
POD=$(kubectl get pods -n "$NS" -l name=kata-deploy \
|
||||
--field-selector spec.nodeName="$NODE" \
|
||||
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "")
|
||||
if [[ -n "$POD" ]]; then
|
||||
if kubectl wait pod "$POD" -n "$NS" --for=condition=Ready --timeout=10s 2>/dev/null; then
|
||||
echo "kata-deploy pod $POD is ready"
|
||||
trap - EXIT
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
echo "Waiting... ($i/60)"
|
||||
sleep 5
|
||||
done
|
||||
exit 1
|
||||
|
||||
# =========================================================================
|
||||
# VERIFY AND COMPLETE NODE (with automatic rollback on failure)
|
||||
# =========================================================================
|
||||
- name: verify-and-complete-node
|
||||
inputs:
|
||||
parameters:
|
||||
- name: node-name
|
||||
tolerations:
|
||||
- key: node.kubernetes.io/unschedulable
|
||||
operator: Exists
|
||||
- key: node.kubernetes.io/disk-pressure
|
||||
operator: Exists
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
script:
|
||||
image: "{{`{{workflow.parameters.helm-image}}`}}"
|
||||
command: [bash]
|
||||
source: |
|
||||
# Don't use set -e - we need to reach rollback logic even on errors
|
||||
NODE="{{`{{inputs.parameters.node-name}}`}}"
|
||||
VERIFY_NS="{{`{{workflow.parameters.verification-namespace}}`}}"
|
||||
RELEASE="{{`{{workflow.parameters.helm-release}}`}}"
|
||||
NS="{{`{{workflow.parameters.helm-namespace}}`}}"
|
||||
VERSION="{{`{{workflow.parameters.target-version}}`}}"
|
||||
VERIFICATION_POD="{{`{{workflow.parameters.verification-pod}}`}}"
|
||||
TEST_POD="kata-verify-${NODE}-$(date +%s)"
|
||||
|
||||
# Install kubectl (helm image is based on kubectl image but just in case)
|
||||
apk add --no-cache -q kubectl 2>/dev/null || true
|
||||
|
||||
kubectl annotate node "$NODE" --overwrite katacontainers.io/kata-lifecycle-manager-status="verifying"
|
||||
|
||||
# Decode verification pod spec (base64-encoded)
|
||||
echo "Using verification pod from workflow parameters"
|
||||
echo "$VERIFICATION_POD" | base64 -d > /tmp/verify-pod.yaml
|
||||
|
||||
# Apply verification pod with placeholder substitution
|
||||
sed -i "s|\${NODE}|$NODE|g" /tmp/verify-pod.yaml
|
||||
sed -i "s|\${TEST_POD}|$TEST_POD|g" /tmp/verify-pod.yaml
|
||||
|
||||
if ! kubectl apply -n "$VERIFY_NS" -f /tmp/verify-pod.yaml; then
|
||||
echo "ERROR: Failed to create verification pod"
|
||||
VERIFY_SUCCESS=false
|
||||
fi
|
||||
|
||||
# Cleanup function for verification pod
|
||||
cleanup_pod() {
|
||||
kubectl delete pod "$TEST_POD" -n "$VERIFY_NS" --ignore-not-found --wait=false
|
||||
}
|
||||
trap cleanup_pod EXIT
|
||||
|
||||
# Wait for verification pod to complete (only if pod was created)
|
||||
# This catches all failure modes:
|
||||
# - Pod stuck in Pending/ContainerCreating (runtime can't start VM)
|
||||
# - Pod crashes immediately (containerd/CRI-O config issues)
|
||||
# - Pod times out (resource issues, image pull failures)
|
||||
# - Pod exits with non-zero code (verification logic failed)
|
||||
if [ "${VERIFY_SUCCESS:-}" != "false" ]; then
|
||||
VERIFY_SUCCESS=false
|
||||
if kubectl wait pod "$TEST_POD" -n "$VERIFY_NS" --for=jsonpath='{.status.phase}'=Succeeded --timeout=180s; then
|
||||
echo "=== Verification Succeeded ==="
|
||||
kubectl logs "$TEST_POD" -n "$VERIFY_NS" || true
|
||||
VERIFY_SUCCESS=true
|
||||
else
|
||||
echo "=== Verification Failed ==="
|
||||
echo ""
|
||||
echo "Pod status:"
|
||||
kubectl get pod "$TEST_POD" -n "$VERIFY_NS" -o wide || true
|
||||
echo ""
|
||||
echo "Pod events and details:"
|
||||
kubectl describe pod "$TEST_POD" -n "$VERIFY_NS" || true
|
||||
echo ""
|
||||
echo "Pod logs (if available):"
|
||||
kubectl logs "$TEST_POD" -n "$VERIFY_NS" || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Clean up verification pod
|
||||
cleanup_pod
|
||||
trap - EXIT
|
||||
|
||||
if [ "$VERIFY_SUCCESS" = "true" ]; then
|
||||
# Success path: uncordon and mark complete
|
||||
echo "Uncordoning node $NODE..."
|
||||
kubectl uncordon "$NODE"
|
||||
kubectl annotate node "$NODE" --overwrite \
|
||||
katacontainers.io/kata-lifecycle-manager-status="completed" \
|
||||
katacontainers.io/kata-current-version="$VERSION"
|
||||
echo "Node $NODE upgrade completed successfully"
|
||||
exit 0
|
||||
else
|
||||
# Failure path: automatic rollback
|
||||
echo "Initiating automatic rollback for node $NODE..."
|
||||
kubectl annotate node "$NODE" --overwrite katacontainers.io/kata-lifecycle-manager-status="rolling-back"
|
||||
|
||||
helm rollback "$RELEASE" -n "$NS" --wait --timeout 10m
|
||||
|
||||
# Wait for kata-deploy to be ready after rollback
|
||||
echo "Waiting for kata-deploy to be ready after rollback..."
|
||||
for i in $(seq 1 60); do
|
||||
POD=$(kubectl get pods -n "$NS" -l name=kata-deploy \
|
||||
--field-selector spec.nodeName="$NODE" \
|
||||
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "")
|
||||
if [[ -n "$POD" ]]; then
|
||||
if kubectl wait pod "$POD" -n "$NS" --for=condition=Ready --timeout=10s 2>/dev/null; then
|
||||
echo "kata-deploy pod $POD is ready after rollback"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
echo "Waiting for rollback to complete... ($i/60)"
|
||||
sleep 5
|
||||
done
|
||||
|
||||
# Uncordon and mark as rolled back
|
||||
kubectl uncordon "$NODE"
|
||||
kubectl annotate node "$NODE" --overwrite katacontainers.io/kata-lifecycle-manager-status="rolled-back"
|
||||
echo "Node $NODE rolled back to previous version"
|
||||
|
||||
# Exit with error so workflow shows the failure
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# =========================================================================
|
||||
# PRINT SUMMARY
|
||||
# =========================================================================
|
||||
- name: print-summary
|
||||
tolerations:
|
||||
- key: node.kubernetes.io/unschedulable
|
||||
operator: Exists
|
||||
- key: node.kubernetes.io/disk-pressure
|
||||
operator: Exists
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
script:
|
||||
image: "{{`{{workflow.parameters.kubectl-image}}`}}"
|
||||
command: [bash]
|
||||
source: |
|
||||
echo "=== KATA UPGRADE SUMMARY ==="
|
||||
kubectl get nodes \
|
||||
-L katacontainers.io/kata-runtime \
|
||||
-L katacontainers.io/kata-lifecycle-manager-status \
|
||||
-L katacontainers.io/kata-current-version
|
||||
|
||||
# =========================================================================
|
||||
# ROLLBACK (can be called manually)
|
||||
# =========================================================================
|
||||
- name: rollback-node
|
||||
inputs:
|
||||
parameters:
|
||||
- name: node-name
|
||||
tolerations:
|
||||
- key: node.kubernetes.io/unschedulable
|
||||
operator: Exists
|
||||
- key: node.kubernetes.io/disk-pressure
|
||||
operator: Exists
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
script:
|
||||
image: "{{`{{workflow.parameters.helm-image}}`}}"
|
||||
command: [sh]
|
||||
source: |
|
||||
set -e
|
||||
NODE="{{`{{inputs.parameters.node-name}}`}}"
|
||||
RELEASE="{{`{{workflow.parameters.helm-release}}`}}"
|
||||
NS="{{`{{workflow.parameters.helm-namespace}}`}}"
|
||||
|
||||
apk add --no-cache -q kubectl
|
||||
kubectl annotate node "$NODE" --overwrite katacontainers.io/kata-lifecycle-manager-status="rolling-back"
|
||||
|
||||
helm rollback "$RELEASE" -n "$NS" --wait --timeout 10m
|
||||
|
||||
kubectl annotate node "$NODE" --overwrite katacontainers.io/kata-lifecycle-manager-status="rolled-back"
|
||||
kubectl uncordon "$NODE"
|
||||
@@ -1,63 +0,0 @@
|
||||
# Copyright (c) 2026 The Kata Containers Authors
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Argo Workflows namespace where the WorkflowTemplate will be created
|
||||
argoNamespace: argo
|
||||
|
||||
# Default workflow parameters (can be overridden when submitting workflows)
|
||||
defaults:
|
||||
# Helm release name for kata-deploy
|
||||
helmRelease: kata-deploy
|
||||
|
||||
# Namespace where kata-deploy is installed
|
||||
helmNamespace: kube-system
|
||||
|
||||
# Label selector for nodes to upgrade (optional if using taint selection)
|
||||
nodeSelector: ""
|
||||
|
||||
# Taint-based node selection (optional, alternative to label selector)
|
||||
# Select nodes that have a taint with this key
|
||||
nodeTaintKey: ""
|
||||
# Optional: filter by taint value (only used if nodeTaintKey is set)
|
||||
nodeTaintValue: ""
|
||||
|
||||
# Namespace for verification pods
|
||||
verificationNamespace: default
|
||||
|
||||
# Verification pod spec (REQUIRED)
|
||||
#
|
||||
# A verification pod is required to validate each node after upgrade.
|
||||
# The chart will fail to install without one.
|
||||
#
|
||||
# Provide via:
|
||||
# helm install kata-lifecycle-manager ... \
|
||||
# --set-file defaults.verificationPod=/path/to/your-verification-pod.yaml
|
||||
#
|
||||
# Or override at workflow submission (base64-encoded):
|
||||
# argo submit ... -p verification-pod="$(base64 -w0 < pod.yaml)"
|
||||
#
|
||||
# Note: kata-deploy's own verification is disabled during upgrade because
|
||||
# it is cluster-wide (designed for initial install), while kata-lifecycle-manager
|
||||
# performs per-node verification with proper placeholder substitution.
|
||||
#
|
||||
# Placeholders substituted at runtime:
|
||||
# ${NODE} - the node being upgraded/verified
|
||||
# ${TEST_POD} - generated unique pod name
|
||||
#
|
||||
verificationPod: ""
|
||||
|
||||
# Optional: Drain nodes before upgrade (default: false)
|
||||
# Not required for Kata upgrades since running VMs use in-memory binaries.
|
||||
# Enable if you prefer to evict workloads before upgrading.
|
||||
drainEnabled: false
|
||||
|
||||
# Timeout for node drain (only used if drainEnabled is true)
|
||||
drainTimeout: "300s"
|
||||
|
||||
# Container images used by workflow steps
|
||||
images:
|
||||
# Helm image for helm upgrade operations (multi-arch)
|
||||
helm: quay.io/kata-containers/helm:latest
|
||||
|
||||
# Kubectl image for kubernetes operations (multi-arch)
|
||||
kubectl: quay.io/kata-containers/kubectl:latest
|
||||
@@ -99,7 +99,7 @@ TDSHIM_CONTAINER_BUILDER="${TDSHIM_CONTAINER_BUILDER:-}"
|
||||
TOOLS_CONTAINER_BUILDER="${TOOLS_CONTAINER_BUILDER:-}"
|
||||
VIRTIOFSD_CONTAINER_BUILDER="${VIRTIOFSD_CONTAINER_BUILDER:-}"
|
||||
AGENT_INIT="${AGENT_INIT:-no}"
|
||||
MEASURED_ROOTFS="${MEASURED_ROOTFS:-}"
|
||||
MEASURED_ROOTFS="${MEASURED_ROOTFS:-no}"
|
||||
USE_CACHE="${USE_CACHE:-}"
|
||||
BUSYBOX_CONF_FILE=${BUSYBOX_CONF_FILE:-}
|
||||
NVIDIA_GPU_STACK="${NVIDIA_GPU_STACK:-}"
|
||||
|
||||
@@ -185,7 +185,10 @@ get_kernel_modules_dir() {
|
||||
}
|
||||
|
||||
cleanup_and_fail_shim_v2_specifics() {
|
||||
rm -f "${repo_root_dir}/tools/packaging/kata-deploy/local-build/build/shim-v2-root_hash.txt"
|
||||
for variant in confidential nvidia-gpu nvidia-gpu-confidential; do
|
||||
local root_hash_file="${repo_root_dir}/tools/packaging/kata-deploy/local-build/build/shim-v2-root_hash_${variant}.txt"
|
||||
[[ -f "${root_hash_file}" ]] && rm -f "${root_hash_file}"
|
||||
done
|
||||
|
||||
return $(cleanup_and_fail "${1:-}" "${2:-}")
|
||||
}
|
||||
@@ -209,31 +212,37 @@ cleanup_and_fail() {
|
||||
}
|
||||
|
||||
install_cached_shim_v2_tarball_get_root_hash() {
|
||||
if [ "${MEASURED_ROOTFS}" != "yes" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local tarball_dir="${repo_root_dir}/tools/packaging/kata-deploy/local-build/build"
|
||||
local image_conf_tarball="kata-static-rootfs-image-confidential.tar.zst"
|
||||
|
||||
local root_hash_basedir="./opt/kata/share/kata-containers/"
|
||||
|
||||
tar --zstd -xvf "${tarball_dir}/${image_conf_tarball}" ${root_hash_basedir}root_hash.txt --transform s,${root_hash_basedir},,
|
||||
mv root_hash.txt "${tarball_dir}/root_hash.txt"
|
||||
for variant in confidential nvidia-gpu nvidia-gpu-confidential; do
|
||||
local image_conf_tarball="kata-static-rootfs-image-${variant}.tar.zst"
|
||||
local tarball_path="${tarball_dir}/${image_conf_tarball}"
|
||||
local root_hash_path="${root_hash_basedir}root_hash_${variant}.txt"
|
||||
|
||||
# If variant does not exist we skip the current iteration.
|
||||
[[ ! -f "${tarball_path}" ]] && continue
|
||||
|
||||
tar --zstd -tf "${tarball_path}" "${root_hash_path}" >/dev/null 2>&1 || continue
|
||||
tar --zstd -xvf "${tarball_path}" "${root_hash_path}" --transform s,"${root_hash_basedir}",, || die "Failed to extract root hash from ${tarball_path}"
|
||||
mv "root_hash_${variant}.txt" "${tarball_dir}/"
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
install_cached_shim_v2_tarball_compare_root_hashes() {
|
||||
if [ "${MEASURED_ROOTFS}" != "yes" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local found_any=""
|
||||
local tarball_dir="${repo_root_dir}/tools/packaging/kata-deploy/local-build/build"
|
||||
|
||||
[ -f shim-v2-root_hash.txt ] || return 1
|
||||
for variant in confidential nvidia-gpu nvidia-gpu-confidential; do
|
||||
# Skip if one or the other does not exist.
|
||||
[[ ! -f "${tarball_dir}/root_hash_${variant}.txt" ]] && continue
|
||||
|
||||
diff "${tarball_dir}/root_hash.txt" shim-v2-root_hash.txt || return 1
|
||||
diff "${tarball_dir}/root_hash_${variant}.txt" "shim-v2-root_hash_${variant}.txt" || return 1
|
||||
found_any="yes"
|
||||
done
|
||||
[[ -z "${found_any}" ]] && return 0
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -473,9 +482,9 @@ install_image() {
|
||||
#Install guest image for confidential guests
|
||||
install_image_confidential() {
|
||||
if [ "${ARCH}" == "s390x" ]; then
|
||||
export MEASURED_ROOTFS=no
|
||||
export MEASURED_ROOTFS="no"
|
||||
else
|
||||
export MEASURED_ROOTFS=yes
|
||||
export MEASURED_ROOTFS="yes"
|
||||
fi
|
||||
install_image "confidential"
|
||||
}
|
||||
@@ -583,7 +592,7 @@ install_initrd() {
|
||||
|
||||
#Install guest initrd for confidential guests
|
||||
install_initrd_confidential() {
|
||||
export MEASURED_ROOTFS=no
|
||||
export MEASURED_ROOTFS="no"
|
||||
install_initrd "confidential"
|
||||
}
|
||||
|
||||
@@ -610,6 +619,7 @@ install_initrd_confidential() {
|
||||
# Install NVIDIA GPU image
|
||||
install_image_nvidia_gpu() {
|
||||
export AGENT_POLICY
|
||||
export MEASURED_ROOTFS="yes"
|
||||
local version=$(get_from_kata_deps .externals.nvidia.driver.version)
|
||||
EXTRA_PKGS="apt curl ${EXTRA_PKGS}"
|
||||
NVIDIA_GPU_STACK=${NVIDIA_GPU_STACK:-"driver=${version},compute,dcgm"}
|
||||
@@ -619,6 +629,7 @@ install_image_nvidia_gpu() {
|
||||
# Install NVIDIA GPU initrd
|
||||
install_initrd_nvidia_gpu() {
|
||||
export AGENT_POLICY
|
||||
export MEASURED_ROOTFS="no"
|
||||
local version=$(get_from_kata_deps .externals.nvidia.driver.version)
|
||||
EXTRA_PKGS="apt curl ${EXTRA_PKGS}"
|
||||
NVIDIA_GPU_STACK=${NVIDIA_GPU_STACK:-"driver=${version},compute,dcgm"}
|
||||
@@ -628,9 +639,9 @@ install_initrd_nvidia_gpu() {
|
||||
# Instal NVIDIA GPU confidential image
|
||||
install_image_nvidia_gpu_confidential() {
|
||||
export AGENT_POLICY
|
||||
export MEASURED_ROOTFS="yes"
|
||||
local version=$(get_from_kata_deps .externals.nvidia.driver.version)
|
||||
EXTRA_PKGS="apt curl ${EXTRA_PKGS}"
|
||||
# TODO: export MEASURED_ROOTFS=yes
|
||||
NVIDIA_GPU_STACK=${NVIDIA_GPU_STACK:-"driver=${version},compute,dcgm"}
|
||||
install_image "nvidia-gpu-confidential"
|
||||
}
|
||||
@@ -638,9 +649,9 @@ install_image_nvidia_gpu_confidential() {
|
||||
# Install NVIDIA GPU confidential initrd
|
||||
install_initrd_nvidia_gpu_confidential() {
|
||||
export AGENT_POLICY
|
||||
export MEASURED_ROOTFS="no"
|
||||
local version=$(get_from_kata_deps .externals.nvidia.driver.version)
|
||||
EXTRA_PKGS="apt curl ${EXTRA_PKGS}"
|
||||
# TODO: export MEASURED_ROOTFS=yes
|
||||
NVIDIA_GPU_STACK=${NVIDIA_GPU_STACK:-"driver=${version},compute,dcgm"}
|
||||
install_initrd "nvidia-gpu-confidential"
|
||||
}
|
||||
@@ -743,9 +754,9 @@ install_kernel() {
|
||||
|
||||
install_kernel_confidential() {
|
||||
if [ "${ARCH}" == "s390x" ]; then
|
||||
export MEASURED_ROOTFS=no
|
||||
export MEASURED_ROOTFS="no"
|
||||
else
|
||||
export MEASURED_ROOTFS=yes
|
||||
export MEASURED_ROOTFS="yes"
|
||||
fi
|
||||
|
||||
install_kernel_helper \
|
||||
@@ -755,7 +766,7 @@ install_kernel_confidential() {
|
||||
}
|
||||
|
||||
install_kernel_cca_confidential() {
|
||||
export MEASURED_ROOTFS=yes
|
||||
export MEASURED_ROOTFS="yes"
|
||||
|
||||
install_kernel_helper \
|
||||
"assets.kernel-arm-experimental.confidential" \
|
||||
@@ -779,6 +790,7 @@ install_kernel_nvidia_gpu_dragonball_experimental() {
|
||||
|
||||
#Install GPU enabled kernel asset
|
||||
install_kernel_nvidia_gpu() {
|
||||
export MEASURED_ROOTFS="yes"
|
||||
install_kernel_helper \
|
||||
"assets.kernel.nvidia" \
|
||||
"kernel-nvidia-gpu" \
|
||||
@@ -787,6 +799,7 @@ install_kernel_nvidia_gpu() {
|
||||
|
||||
#Install GPU and TEE enabled kernel asset
|
||||
install_kernel_nvidia_gpu_confidential() {
|
||||
export MEASURED_ROOTFS="yes"
|
||||
install_kernel_helper \
|
||||
"assets.kernel.nvidia-confidential" \
|
||||
"kernel-nvidia-gpu-confidential" \
|
||||
@@ -1019,19 +1032,22 @@ install_shimv2() {
|
||||
export MEASURED_ROOTFS
|
||||
export RUNTIME_CHOICE
|
||||
|
||||
if [ "${MEASURED_ROOTFS}" = "yes" ]; then
|
||||
local image_conf_tarball="${workdir}/kata-static-rootfs-image-confidential.tar.zst"
|
||||
if [ ! -f "${image_conf_tarball}" ]; then
|
||||
die "Building the shim-v2 with MEASURED_ROOTFS support requires a rootfs confidential image tarball"
|
||||
fi
|
||||
for variant in confidential nvidia-gpu nvidia-gpu-confidential; do
|
||||
local image_conf_tarball
|
||||
image_conf_tarball="$(find "${workdir}" -maxdepth 1 -name "kata-static-rootfs-image-${variant}.tar.zst" 2>/dev/null | head -n 1)"
|
||||
# Only one variant may be built at a time so we need to
|
||||
# skip one or the other if not available.
|
||||
[[ -f "${image_conf_tarball}" ]] || continue
|
||||
|
||||
local root_hash_basedir="./opt/kata/share/kata-containers/"
|
||||
if ! tar --zstd -xvf ${image_conf_tarball} --transform s,${root_hash_basedir},, ${root_hash_basedir}root_hash.txt; then
|
||||
die "Building the shim-v2 with MEASURED_ROOTFS support requires a rootfs confidential image tarball built with MEASURED_ROOTFS support"
|
||||
local root_hash_path="${root_hash_basedir}root_hash_${variant}.txt"
|
||||
tar --zstd -tf "${image_conf_tarball}" "${root_hash_path}" >/dev/null 2>&1 || continue
|
||||
if ! tar --zstd -xvf "${image_conf_tarball}" --transform s,"${root_hash_basedir}",, "${root_hash_path}"; then
|
||||
die "Cannot extract root hash from ${image_conf_tarball}"
|
||||
fi
|
||||
|
||||
mv root_hash.txt ${workdir}/root_hash.txt
|
||||
fi
|
||||
mv "root_hash_${variant}.txt" "${workdir}/root_hash_${variant}.txt"
|
||||
done
|
||||
|
||||
DESTDIR="${destdir}" PREFIX="${prefix}" "${shimv2_builder}"
|
||||
}
|
||||
@@ -1462,8 +1478,10 @@ handle_build() {
|
||||
tar --zstd -tvf "${modules_final_tarball_path}"
|
||||
;;
|
||||
shim-v2)
|
||||
if [ "${MEASURED_ROOTFS}" = "yes" ]; then
|
||||
mv ${workdir}/root_hash.txt ${workdir}/shim-v2-root_hash.txt
|
||||
if [[ "${MEASURED_ROOTFS}" == "yes" ]]; then
|
||||
for variant in confidential nvidia-gpu nvidia-gpu-confidential; do
|
||||
[[ -f "${workdir}/root_hash_${variant}.txt" ]] && mv "${workdir}/root_hash_${variant}.txt" "${workdir}/shim-v2-root_hash_${variant}.txt"
|
||||
done
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
@@ -1521,10 +1539,15 @@ handle_build() {
|
||||
)
|
||||
;;
|
||||
shim-v2)
|
||||
if [ "${MEASURED_ROOTFS}" = "yes" ]; then
|
||||
files_to_push+=(
|
||||
"shim-v2-root_hash.txt"
|
||||
)
|
||||
if [[ "${MEASURED_ROOTFS}" == "yes" ]]; then
|
||||
local found_any=""
|
||||
for variant in confidential nvidia-gpu nvidia-gpu-confidential; do
|
||||
# The variants could be built independently we need to check if
|
||||
# they exist and then push them to the registry
|
||||
[[ -f "${workdir}/shim-v2-root_hash_${variant}.txt" ]] && files_to_push+=("shim-v2-root_hash_${variant}.txt")
|
||||
found_any="yes"
|
||||
done
|
||||
[[ -z "${found_any}" ]] && die "No files to push for shim-v2 with MEASURED_ROOTFS support"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
|
||||
@@ -28,7 +28,6 @@ readonly default_kernel_config_dir="${script_dir}/configs"
|
||||
# Default path to search for kernel config fragments
|
||||
readonly default_config_frags_dir="${script_dir}/configs/fragments"
|
||||
readonly default_config_whitelist="${script_dir}/configs/fragments/whitelist.conf"
|
||||
readonly default_initramfs="${script_dir}/initramfs.cpio.gz"
|
||||
# xPU vendor
|
||||
readonly VENDOR_INTEL="intel"
|
||||
readonly VENDOR_NVIDIA="nvidia"
|
||||
@@ -135,12 +134,6 @@ arch_to_kernel() {
|
||||
esac
|
||||
}
|
||||
|
||||
# When building for measured rootfs the initramfs image should be previously built.
|
||||
check_initramfs_or_die() {
|
||||
[ -f "${default_initramfs}" ] || \
|
||||
die "Initramfs for measured rootfs not found at ${default_initramfs}"
|
||||
}
|
||||
|
||||
get_git_kernel() {
|
||||
local kernel_path="${2:-}"
|
||||
|
||||
@@ -312,15 +305,11 @@ get_kernel_frag_path() {
|
||||
all_configs="${all_configs} ${dpu_configs}"
|
||||
fi
|
||||
|
||||
if [ "${measured_rootfs}" == "true" ]; then
|
||||
if [[ "${measured_rootfs}" == "true" ]]; then
|
||||
info "Enabling config for confidential guest trust storage protection"
|
||||
local cryptsetup_configs="$(ls ${common_path}/confidential_containers/cryptsetup.conf)"
|
||||
local cryptsetup_configs
|
||||
cryptsetup_configs="$(ls "${common_path}"/confidential_containers/cryptsetup.conf)"
|
||||
all_configs="${all_configs} ${cryptsetup_configs}"
|
||||
|
||||
check_initramfs_or_die
|
||||
info "Enabling config for confidential guest measured boot"
|
||||
local initramfs_configs="$(ls ${common_path}/confidential_containers/initramfs.conf)"
|
||||
all_configs="${all_configs} ${initramfs_configs}"
|
||||
fi
|
||||
|
||||
if [[ "${conf_guest}" != "" ]];then
|
||||
@@ -504,12 +493,6 @@ setup_kernel() {
|
||||
[ -n "${hypervisor_target}" ] || hypervisor_target="kvm"
|
||||
[ -n "${kernel_config_path}" ] || kernel_config_path=$(get_default_kernel_config "${kernel_version}" "${hypervisor_target}" "${arch_target}" "${kernel_path}")
|
||||
|
||||
if [ "${measured_rootfs}" == "true" ]; then
|
||||
check_initramfs_or_die
|
||||
info "Copying initramfs from: ${default_initramfs}"
|
||||
cp "${default_initramfs}" ./
|
||||
fi
|
||||
|
||||
info "Copying config file from: ${kernel_config_path}"
|
||||
cp "${kernel_config_path}" ./.config
|
||||
ARCH=${arch_target} make oldconfig ${CROSS_BUILD_ARG}
|
||||
|
||||
@@ -4,6 +4,7 @@ CONFIG_BLK_DEV_DM=y
|
||||
CONFIG_DM_CRYPT=y
|
||||
CONFIG_DM_VERITY=y
|
||||
CONFIG_DM_INTEGRITY=y
|
||||
CONFIG_DM_INIT=y
|
||||
CONFIG_CRYPTO_AEAD=y
|
||||
CONFIG_CRYPTO_AEAD2=y
|
||||
CONFIG_CRYPTO_AKCIPHER2=y
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
CONFIG_INITRAMFS_SOURCE="initramfs.cpio.gz"
|
||||
@@ -1 +1 @@
|
||||
177
|
||||
178
|
||||
|
||||
@@ -231,14 +231,9 @@ function _upload_helm_chart_tarball()
|
||||
|
||||
RELEASE_VERSION="$(_release_version)"
|
||||
|
||||
# Package and upload kata-deploy chart
|
||||
helm dependencies update ${repo_root_dir}/tools/packaging/kata-deploy/helm-chart/kata-deploy
|
||||
helm package ${repo_root_dir}/tools/packaging/kata-deploy/helm-chart/kata-deploy
|
||||
gh release upload "${RELEASE_VERSION}" "kata-deploy-${RELEASE_VERSION}.tgz"
|
||||
|
||||
# Package and upload kata-lifecycle-manager chart
|
||||
helm package ${repo_root_dir}/tools/packaging/kata-deploy/helm-chart/kata-lifecycle-manager
|
||||
gh release upload "${RELEASE_VERSION}" "kata-lifecycle-manager-${RELEASE_VERSION}.tgz"
|
||||
}
|
||||
|
||||
function main()
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
# Copyright (c) 2022 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG cryptsetup_repo=${cryptsetup_repo}
|
||||
ARG cryptsetup_version=${cryptsetup_version}
|
||||
ARG lvm2_repo=${lvm2_repo}
|
||||
ARG lvm2_version=${lvm2_version}
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
ENV TZ=UTC
|
||||
RUN apt-get update && apt-get upgrade -y && \
|
||||
apt-get --no-install-recommends install -y \
|
||||
apt-utils \
|
||||
asciidoctor \
|
||||
autoconf \
|
||||
autopoint \
|
||||
automake \
|
||||
busybox-static \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gcc \
|
||||
gettext \
|
||||
git \
|
||||
libaio-dev \
|
||||
libblkid-dev \
|
||||
libselinux1-dev \
|
||||
libtool \
|
||||
libpopt-dev \
|
||||
libjson-c-dev \
|
||||
libssl-dev \
|
||||
make \
|
||||
ninja-build \
|
||||
pkg-config \
|
||||
uuid-dev \
|
||||
libseccomp-dev \
|
||||
libseccomp2 \
|
||||
zlib1g-dev &&\
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/ && \
|
||||
build_root=$(mktemp -d) && \
|
||||
pushd ${build_root} && \
|
||||
echo "Build ${lvm2_repo} version: ${lvm2_version}" && \
|
||||
git clone --depth 1 --branch "${lvm2_version}" "${lvm2_repo}" lvm2 && \
|
||||
pushd lvm2 && \
|
||||
./configure --enable-static_link --disable-selinux && \
|
||||
make && make install && \
|
||||
cp ./libdm/libdevmapper.pc /usr/lib/pkgconfig/devmapper.pc && \
|
||||
popd && \
|
||||
echo "Build ${cryptsetup_repo} version: ${cryptsetup_version}" && \
|
||||
git clone --depth 1 --branch "${cryptsetup_version}" "${cryptsetup_repo}" cryptsetup && \
|
||||
pushd cryptsetup && \
|
||||
./autogen.sh && \
|
||||
./configure --enable-static --enable-static-cryptsetup --disable-udev --disable-external-tokens --disable-ssh-token && \
|
||||
make && make install && \
|
||||
strip /usr/sbin/veritysetup.static && \
|
||||
popd && \
|
||||
echo "Build gen_init_cpio tool" && \
|
||||
git clone --depth 1 --filter=blob:none --sparse https://github.com/torvalds/linux.git && \
|
||||
pushd linux && \
|
||||
git sparse-checkout add usr && cd usr && make gen_init_cpio && \
|
||||
install gen_init_cpio /usr/sbin/ && \
|
||||
popd && \
|
||||
popd && \
|
||||
rm -rf ${build_root}
|
||||
|
||||
COPY init.sh /usr/sbin/init.sh
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (c) 2022 Intel
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${script_dir}/../../scripts/lib.sh"
|
||||
install_dir="${1:-.}"
|
||||
|
||||
tmpfile="$(mktemp -t initramfs.XXXXXX.cpio)"
|
||||
trap 'rm -f "$tmpfile"' EXIT
|
||||
|
||||
if ! gen_init_cpio "${script_dir}/initramfs.list" > "${tmpfile}"; then
|
||||
echo "gen_init_cpio failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
gzip -9 -n -c "${tmpfile}" > "${install_dir}"/initramfs.cpio.gz
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Copyright (c) 2022 Intel
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
repo_root_dir="$(cd "${script_dir}/../../../.." && pwd)"
|
||||
readonly initramfs_builder="${script_dir}/build-initramfs.sh"
|
||||
readonly default_install_dir="$(cd "${script_dir}/../../kernel" && pwd)"
|
||||
|
||||
source "${script_dir}/../../scripts/lib.sh"
|
||||
|
||||
kata_version="${kata_version:-}"
|
||||
cryptsetup_repo="${cryptsetup_repo:-}"
|
||||
cryptsetup_version="${cryptsetup_version:-}"
|
||||
lvm2_repo="${lvm2_repo:-}"
|
||||
lvm2_version="${lvm2_version:-}"
|
||||
package_output_dir="${package_output_dir:-}"
|
||||
|
||||
[ -n "${cryptsetup_repo}" ] || cryptsetup_repo=$(get_from_kata_deps ".externals.cryptsetup.url")
|
||||
[ -n "${cryptsetup_version}" ] || cryptsetup_version=$(get_from_kata_deps ".externals.cryptsetup.version")
|
||||
[ -n "${lvm2_repo}" ] || lvm2_repo=$(get_from_kata_deps ".externals.lvm2.url")
|
||||
[ -n "${lvm2_version}" ] || lvm2_version=$(get_from_kata_deps ".externals.lvm2.version")
|
||||
|
||||
[ -n "${cryptsetup_repo}" ] || die "Failed to get cryptsetup repo"
|
||||
[ -n "${cryptsetup_version}" ] || die "Failed to get cryptsetup version"
|
||||
[ -n "${lvm2_repo}" ] || die "Failed to get lvm2 repo"
|
||||
[ -n "${lvm2_version}" ] || die "Failed to get lvm2 version"
|
||||
|
||||
container_image="${BUILDER_REGISTRY}:initramfs-cryptsetup-${cryptsetup_version}-lvm2-${lvm2_version}-$(get_last_modification ${repo_root_dir} ${script_dir})-$(uname -m)"
|
||||
|
||||
docker pull ${container_image} || (docker build \
|
||||
--build-arg cryptsetup_repo="${cryptsetup_repo}" \
|
||||
--build-arg cryptsetup_version="${cryptsetup_version}" \
|
||||
--build-arg lvm2_repo="${lvm2_repo}" \
|
||||
--build-arg lvm2_version="${lvm2_version}" \
|
||||
-t "${container_image}" "${script_dir}" && \
|
||||
# No-op unless PUSH_TO_REGISTRY is exported as "yes"
|
||||
push_to_registry "${container_image}")
|
||||
|
||||
docker run --rm -i -v "${repo_root_dir}:${repo_root_dir}" \
|
||||
-w "${PWD}" \
|
||||
"${container_image}" \
|
||||
bash -c "${initramfs_builder} ${default_install_dir}"
|
||||
@@ -1,60 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2022 Intel
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
[ -d /dev ] || mkdir -m 0755 /dev
|
||||
[ -d /root ] || mkdir -m 0700 /root
|
||||
[ -d /sys ] || mkdir /sys
|
||||
[ -d /proc ] || mkdir /proc
|
||||
[ -d /mnt ] || mkdir /mnt
|
||||
[ -d /tmp ] || mkdir /tmp
|
||||
|
||||
mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
|
||||
mount -t proc -o nodev,noexec,nosuid proc /proc
|
||||
|
||||
echo "/sbin/mdev" > /proc/sys/kernel/hotplug
|
||||
mdev -s
|
||||
|
||||
get_option() {
|
||||
local value
|
||||
value=" $(cat /proc/cmdline) "
|
||||
value="${value##* ${1}=}"
|
||||
value="${value%% *}"
|
||||
[ "${value}" != "" ] && echo "${value}"
|
||||
}
|
||||
|
||||
rootfs_verifier=$(get_option rootfs_verity.scheme)
|
||||
rootfs_hash=$(get_option rootfs_verity.hash)
|
||||
root_device=$(get_option root)
|
||||
hash_device=${root_device%?}2
|
||||
|
||||
# The root device should exist to be either verified then mounted or
|
||||
# just mounted when verification is disabled.
|
||||
if [ ! -e "${root_device}" ]
|
||||
then
|
||||
echo "No root device ${root_device} found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${rootfs_verifier}" = "dm-verity" ]
|
||||
then
|
||||
echo "Verify the root device with ${rootfs_verifier}"
|
||||
|
||||
if [ ! -e "${hash_device}" ]
|
||||
then
|
||||
echo "No hash device ${hash_device} found. Cannot verify the root device"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
veritysetup open --panic-on-corruption "${root_device}" root "${hash_device}" "${rootfs_hash}"
|
||||
mount /dev/mapper/root /mnt
|
||||
else
|
||||
echo "No LUKS device found"
|
||||
mount "${root_device}" /mnt
|
||||
fi
|
||||
|
||||
umount /proc
|
||||
umount /sys
|
||||
exec switch_root /mnt /sbin/init
|
||||
@@ -1,21 +0,0 @@
|
||||
# Copyright (c) 2022 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# initramfs to setup verified boot for rootfs
|
||||
dir /dev 0755 0 0
|
||||
dir /root 0700 0 0
|
||||
dir /sbin 0755 0 0
|
||||
dir /bin 0755 0 0
|
||||
dir /run 0755 0 0
|
||||
dir /mnt 0755 0 0
|
||||
file /init /usr/sbin/init.sh 0755 0 0
|
||||
file /sbin/busybox /usr/bin/busybox 0755 0 0
|
||||
file /sbin/veritysetup /usr/sbin/veritysetup.static 0755 0 0
|
||||
slink /bin/sh /sbin/busybox 0755 0 0
|
||||
slink /sbin/mount /sbin/busybox 0755 0 0
|
||||
slink /bin/mkdir /sbin/busybox 0755 0 0
|
||||
slink /sbin/mdev /sbin/busybox 0755 0 0
|
||||
slink /sbin/switch_root /sbin/busybox 0755 0 0
|
||||
slink /sbin/umount /sbin/busybox 0755 0 0
|
||||
slink /sbin/cat /sbin/busybox 0755 0 0
|
||||
@@ -18,7 +18,6 @@ repo_root_dir="${repo_root_dir:-}"
|
||||
|
||||
[[ -n "${repo_root_dir}" ]] || die "repo_root_dir is not set"
|
||||
readonly kernel_builder="${repo_root_dir}/tools/packaging/kernel/build-kernel.sh"
|
||||
readonly initramfs_builder="${repo_root_dir}/tools/packaging/static-build/initramfs/build.sh"
|
||||
|
||||
BUILDX=
|
||||
PLATFORM=
|
||||
@@ -32,10 +31,6 @@ kernel_builder_args="-a ${ARCH:-} $*"
|
||||
KERNEL_DEBUG_ENABLED=${KERNEL_DEBUG_ENABLED:-"no"}
|
||||
|
||||
if [[ "${MEASURED_ROOTFS}" == "yes" ]]; then
|
||||
info "build initramfs for cc kernel"
|
||||
"${initramfs_builder}"
|
||||
# Turn on the flag to build the kernel with support to
|
||||
# measured rootfs.
|
||||
kernel_builder_args+=" -m"
|
||||
fi
|
||||
|
||||
|
||||
@@ -36,18 +36,31 @@ case "${RUNTIME_CHOICE}" in
|
||||
esac
|
||||
|
||||
[ "${CROSS_BUILD}" == "true" ] && container_image_bk="${container_image}" && container_image="${container_image}-cross-build"
|
||||
if [ "${MEASURED_ROOTFS}" == "yes" ]; then
|
||||
info "Enable rootfs measurement config"
|
||||
|
||||
root_hash_file="${repo_root_dir}/tools/packaging/kata-deploy/local-build/build/root_hash.txt"
|
||||
# Variants (targets) that build a measured rootfs as of now are:
|
||||
# - rootfs-image-confidential
|
||||
# - rootfs-image-nvidia-gpu
|
||||
# - rootfs-image-nvidia-gpu-confidential
|
||||
#
|
||||
root_hash_dir="${repo_root_dir}/tools/packaging/kata-deploy/local-build/build"
|
||||
verity_variants=(
|
||||
"confidential:KERNELVERITYPARAMS"
|
||||
"nvidia-gpu:KERNELVERITYPARAMS_NV"
|
||||
"nvidia-gpu-confidential:KERNELVERITYPARAMS_CONFIDENTIAL_NV"
|
||||
)
|
||||
for entry in "${verity_variants[@]}"; do
|
||||
variant="${entry%%:*}"
|
||||
param_var="${entry#*:}"
|
||||
root_hash_file="${root_hash_dir}/root_hash_${variant}.txt"
|
||||
[[ -f "${root_hash_file}" ]] || continue
|
||||
|
||||
[ -f "$root_hash_file" ] || \
|
||||
die "Root hash file for measured rootfs not found at ${root_hash_file}"
|
||||
# root_hash_*.txt contains a single kernel_verity_params line.
|
||||
IFS= read -r root_measure_config < "${root_hash_file}"
|
||||
root_measure_config="${root_measure_config%$'\r'}"
|
||||
[[ -n "${root_measure_config}" ]] || die "Empty kernel verity params in ${root_hash_file}"
|
||||
|
||||
root_hash=$(sed -e 's/Root hash:\s*//g;t;d' "${root_hash_file}")
|
||||
root_measure_config="rootfs_verity.scheme=dm-verity rootfs_verity.hash=${root_hash}"
|
||||
EXTRA_OPTS+=" ROOTMEASURECONFIG=\"${root_measure_config}\""
|
||||
fi
|
||||
EXTRA_OPTS+=" ${param_var}=${root_measure_config}"
|
||||
done
|
||||
|
||||
docker pull ${container_image} || \
|
||||
(docker ${BUILDX} build ${PLATFORM} \
|
||||
@@ -76,7 +89,7 @@ case "${RUNTIME_CHOICE}" in
|
||||
-w "${repo_root_dir}/src/runtime-rs" \
|
||||
--user "$(id -u)":"$(id -g)" \
|
||||
"${container_image}" \
|
||||
bash -c "make clean-generated-files && make PREFIX=${PREFIX} QEMUCMD=qemu-system-${arch}"
|
||||
bash -c "make clean-generated-files && make PREFIX=${PREFIX} QEMUCMD=qemu-system-${arch} ${EXTRA_OPTS}"
|
||||
|
||||
docker run --rm -i -v "${repo_root_dir}:${repo_root_dir}" \
|
||||
--env CROSS_BUILD=${CROSS_BUILD} \
|
||||
@@ -85,7 +98,7 @@ case "${RUNTIME_CHOICE}" in
|
||||
-w "${repo_root_dir}/src/runtime-rs" \
|
||||
--user "$(id -u)":"$(id -g)" \
|
||||
"${container_image}" \
|
||||
bash -c "make PREFIX="${PREFIX}" DESTDIR="${DESTDIR}" install"
|
||||
bash -c "make PREFIX="${PREFIX}" DESTDIR="${DESTDIR}" ${EXTRA_OPTS} install"
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
@@ -317,7 +317,7 @@ externals:
|
||||
# version older than them.
|
||||
version: "v1.7.25"
|
||||
lts: "v1.7"
|
||||
active: "v2.1"
|
||||
active: "v2.2"
|
||||
# keep the latest version to make the current PR ci work, once it was
|
||||
# merged,we can remove the latest version.
|
||||
latest: "v2.2"
|
||||
|
||||
Reference in New Issue
Block a user