如何在香港服务器上用 RHEL + Kata Containers 把容器安全性拉满

香港葵涌机房线上的一个多租户业务准备扩容,合规审计又刚把“容器逃逸风险”用红笔画了三遍。运维群里很安静,大家都盯着我的终端窗口——我决定在 RHEL 上把 Kata Containers 上进去,给关键 Pod 换一层 “轻虚机” 的硬隔离。
这不是第一次折腾 Kata,但每次到机房落地都像首秀: BIOS 里 VT-x/AMD-V 会不会被托管商关了?RHEL 的 cgroup v2 和 SELinux 会不会给我来点“惊喜”?直到那一刻,我按下回车,风扇声里混入了 QEMU 启动的轻微噪音——这是我要的隔离墙正在立起来的声音。
我们到底要解决什么问题(威胁模型与目标)
威胁模型(简化版)
- 多租户/混部:不同团队、不同敏感级别的容器同 Node 混跑。
- 内核共享风险:传统 runc 共享宿主机内核,内核级漏洞或容器逃逸带来高影响面。
- 合规要求:高敏感工作负载需要“硬隔离”证据链(KVM/轻虚机级别)。
目标
- 用 Kata Containers(轻量虚拟机) 作为指定工作负载的运行时,把关键 Pod 与宿主机内核隔离开。
- 对普通业务维持 runc 的性能与密度,对高敏业务使用 Kata 的安全隔离,做到安全-性能两条线并行。
- 能灰度、可回滚、可观测,现场问题能 30 分钟内定位。
现场环境与硬件清单(香港节点)
| 组件 | 参数/型号 | 备注 |
|---|---|---|
| 机房 | 香港(葵涌),单可用区 + 双路由上联 | 机柜 10GbE 汇聚 |
| 服务器 | 2× Intel Xeon Gold 6330(40C/80T) | 支持 VT-x/VT-d |
| 内存 | 512GB DDR4 | 频繁启动轻虚机内存足够 |
| 系统盘 | 2× NVMe SSD(RAID1,PCIe 3.0 x4) | 用作系统 + 容器镜像缓存 |
| 数据盘 | 2× NVMe SSD(RAID0,PCIe 4.0 x4) | Kata guest rootfs/overlay、冷热分层 |
| 网卡 | 2× 25GbE(Bond LACP) | MTU 9000 内网、对外 1500 |
| OS | RHEL 9.x(实测 9.3/9.4) | SELinux enforcing,cgroup v2 |
| 容器引擎 | containerd 1.7.x | runc + kata 双栈 |
| Kata | 2.6+/v3(以 v2 shim 为例) | 运行时 io.containerd.kata.v2 |
注:OpenShift/CRI-O 也可用 Kata,这里主线用 containerd,文末给 CRI-O 参考配置。
方案总览(架构图解成文字版)
同一台 RHEL 宿主,同时支持两类运行时:
- runc:常规工作负载,高密度。
- Kata(KVM+QEMU 轻虚机):高敏工作负载,强隔离。
- Kubernetes 用 RuntimeClass 精准选择运行时,团队只需在 YAML 里加一行 runtimeClassName: kata 即可“置身轻虚机”。
- 文件系统用 virtio-fs(优先)替代 9p,提高性能;网络默认 tcfilter,必要时配 macvtap/SR-IOV。
- 观测用 kata-monitor + journalctl -u kata* + QEMU 进程可见性,搭配 Node 级 Exporter。
前置检查与准备(现场一步一确认)
1) CPU 虚拟化与内核模块
# CPU 是否支持虚拟化
egrep -i 'vmx|svm' /proc/cpuinfo | wc -l
# KVM 模块是否加载(Intel/AMD 取其一)
lsmod | egrep 'kvm|kvm_intel|kvm_amd'
# 若未加载(RHEL 9)
sudo modprobe kvm
sudo modprobe kvm_intel # 或 kvm_amd
坑点 1:托管商 BIOS 里 VT-x/AMD-V 被关了,KVM 模块加载失败。现场处理:远程约 NOC,BIOS 打开 Intel VT-x/VT-d,AMD 对应 AMD-V/IOMMU。
2) SELinux、cgroup、内核版本
getenforce # 期望:Enforcing
stat -fc %T /sys/fs/cgroup # 期望:cgroup2fs(RHEL9 默认)
uname -r # 确认内核 ≥ 5.x,便于 virtio-fs、cgroupv2 兼容
3) RHEL 订阅与基本包
sudo subscription-manager status
sudo dnf -y install git wget jq vim htop \
qemu-kvm libvirt virt-install \
containerd runc
安装 Kata Containers(稳妥两法)
我选“官方安装脚本 + 审计”:速度快、版本关系处理好。若公司不允许“curl | bash”,就先下载脚本审计后再执行。
方法 A:官方安装脚本(推荐)
# 下载并校验(示例)
curl -fsSLO https://raw.githubusercontent.com/kata-containers/kata-containers/main/tools/install/install_kata.sh
sha256sum install_kata.sh # 与官方校验和比对
sudo bash install_kata.sh # 安装 Kata runtime/guest kernel/rootfs 等
脚本完成后通常会把 io.containerd.kata.v2 shim、guest kernel、镜像、配置文件一并放好(路径视版本而定,如 /usr/share/kata-containers/、/etc/kata-containers/)。
方法 B:RPM 包仓(适合离线/自建镜像)
在内网 YUM 仓或离线介质准备 kata-containers、kata-runtime、qemu-virtiofsd 等包。
注意 RHEL 的 QEMU 路径 多为 /usr/libexec/qemu-kvm,而不是 /usr/bin/qemu-system-x86_64。配置要对应。
关键配置:让 containerd 同时认识 runc 与 kata
1) 生成 containerd 配置并编辑
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml > /dev/null
在 /etc/containerd/config.toml 里,确保:
[plugins."io.containerd.grpc.v1.cri".containerd]
default_runtime_name = "runc"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata]
runtime_type = "io.containerd.kata.v2"
privileged_without_host_devices = false
# cgroup v2 兼容
[plugins."io.containerd.grpc.v1.cri"]
systemd_cgroup = true
坑点 2:把 runtime_type 写成 kata 或者路径写错,Kubelet 起不来 Pod。处理:用 io.containerd.kata.v2,并 journalctl -u containerd -f 看报错。
2) Kata 主配置确认(可选优化)
/etc/kata-containers/configuration.toml(不同版本可能是 configuration-qemu.toml)重点位:
[hypervisor.qemu]
path = "/usr/libexec/qemu-kvm" # RHEL 路径
machine_type = "q35"
enable_iommu = true # DMA 保护
default_vcpus = 1
default_memory = 1024 # 初始内存,后续可调
block_device_driver = "virtio-scsi"
启用 virtio-fs(性能更好):
[hypervisor.qemu]
# ...
shared_fs = "virtio-fs"
virtio_fs_daemon = "/usr/libexec/virtiofsd" # RHEL 9 常见路径(或 /usr/lib/qemu/virtiofsd)
3) 重启并校验
sudo systemctl enable --now containerd
sudo systemctl status containerd
kata-runtime --version # 或 kata-collect-data.sh 查看更详尽信息
单机验证(不进 K8s 先热手)
# 用 crictl 直接跑(确保已安装 crictl 并指向 containerd 的 CRI)
sudo crictl info | jq '.config.containerd.runtimes'
# 运行一个 kata 容器(示例)
sudo crictl runp --runtime=kata <<EOF
{
"metadata": {"name":"sandbox-kata"},
"linux": {"security_context": {}}
}
EOF
更简单,我通常直接用 ctr:
sudo ctr images pull docker.io/library/nginx:1.25
sudo ctr run --rm --runtime io.containerd.kata.v2 \
docker.io/library/nginx:1.25 kata-nginx
在另一端看 QEMU:
ps -ef | egrep 'qemu|kata'
能看到 qemu-kvm 进程就对了。ss -lpn | grep vhost/vsock 能看到 Kata 的通信通道。
接入 Kubernetes(RuntimeClass 一把梭)
前提:Kubelet 指向 containerd(/var/lib/kubelet/kubeadm-flags.env 里有 --container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock)。
1) 定义 RuntimeClass
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: kata
handler: kata
overhead:
podFixed:
memory: "256Mi"
cpu: "100m"
scheduling:
nodeSelector:
node.kubernetes.io/instance-type: hk-biz-kata # 也可用 taints/labels 控制调度
handler: kata 要和 containerd 里 runtimes.kata 一致。
2) 测试 Pod(和 runc 形成对照)
apiVersion: v1
kind: Pod
metadata:
name: web-kata
spec:
runtimeClassName: kata
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
部署后看 Pod 描述:
kubectl describe pod web-kata | egrep 'Runtime|kata'
kubectl exec -it web-kata -- uname -a # 注意看到的是 guest kernel,而非宿主机内核
我们实测的开销与收益(POC 报告摘录)
不同硬件、镜像和业务会有差异,下表是我在香港节点的“冷启动(镜像已拉取)”测得的大致范围,只用于量级参考。
| 指标 | runc | kata(virtio-fs) | 备注 |
|---|---|---|---|
| 冷启动到就绪(nginx) | 250–400 ms | 1.2–2.0 s | Kata 有轻虚机启动成本 |
| 常驻 RSS(单 Pod) | 30–60 MiB | 120–220 MiB | 包含 guest kernel/agent |
| ab -n 5k -c 100(P99) | ~3–5 ms | ~3–6 ms | 纯静态文件几乎无差异 |
| CPU 密度(每 Node) | ★★★★★ | ★★★☆ | Kata 密度略低 |
| 隔离强度 | ★★ | ★★★★★ | Kata=KVM 级硬隔离 |
结论:
- 对高敏感/多租户场景,Kata 的隔离收益远大于启动/内存开销。
- 对高吞吐业务,数据平面性能影响可控(网络/存储路径配置得当时)。
- 策略上采用 双轨:默认 runc,敏感和合规要求强的工作负载走 Kata。
安全增益的落地点
- 内核隔离:Kata guest 用独立内核,宿主与容器不再共享同一内核攻击面。
- 设备与内存隔离:VT-d/IOMMU + vCPU/vMem 边界清晰。
- 策略复用:Pod securityContext、Seccomp、SELinux 仍可用,叠加到 Kata 轻虚机内。
- 镜像供应链:Kata 不解决“镜像本身被植入”的问题,CI/CD 扫描与签名(cosign)照样要做。
(可选)机密计算:有条件的主机可用 TDX/SEV-SNP 搭 Kata Confidential Containers,敏感度再抬一档。
线上踩过的坑与现场处理
KVM 不起 / BIOS 关 VT
现象:modprobe kvm_intel 失败、dmesg 报不支持。
处理:托管商远程开 VT-x/VT-d;云上 VM 要求“支持嵌套虚拟化”的宿主类型。能上裸金属就上裸金属。
SELinux 拦截 virtiofsd
现象:Pod Pending,journalctl -u containerd 有 permission denied。
处理:确认 virtiofsd 路径与 Booleans;RHEL 9 官方包通常已有合规标签。必要时写本地 policy(审慎)。
containerd 配置错位
现象:Kubelet 报不识别 runtime,或 Pod 一直 CreateContainerError。
处理:runtime_type = "io.containerd.kata.v2",不要手写 runtime 二进制路径。
cgroup v2 与内核参数
现象:资源限制不生效、oom 行为异常。
处理:Kubelet、containerd、Kata 全部启用 systemd cgroup;必要时在 guest kernel 加 systemd.unified_cgroup_hierarchy=1。
网络 MTU/Overlay 抖动
现象:跨网段大包丢、gRPC 超时。
处理:Node 内网 MTU 9000、外部 1500;CNI(Calico/Flannel/Cilium)与 Kata 的 tap/bridge 保持一致,必要时把 mtu 固定为 1450/1500。
镜像拉取与共享卷
现象:容器内看不到宿主卷更新。
处理:尽量用 CSI/网络卷;hostPath 在 Kata 下是通过共享文件系统转译的,注意一致性与权限。
时间漂移
现象:日志时间错位,分布式追踪对不上。
处理:宿主启用 chronyd,Kata agent 同步宿主时间;跨可用区统一 NTP。
QEMU 路径与权限
现象:Kata 找不到 hypervisor,或缺 CAP。
处理:RHEL 下 QEMU 常在 /usr/libexec/qemu-kvm;确认文件权限与 SELinux 上下文。
生产化 Checklist(我上线前必过)
- 节点分池:给 Kata 专属 Node 打 nodeSelector/taints,避免所有 Pod 都走 Kata。
- RuntimeClass 政策:高敏命名空间(如 ns: payment)使用 LimitRange + RuntimeClass 默认 Kata。
- 可观测:部署 kata-monitor 暴露 /metrics;Prometheus 抓 QEMU/Kata 进程指标。
- 告警:轻虚机启动失败、vsock 断开、virtiofsd 崩溃、Pod Pending 超 2 分钟报警。
- 日志留痕:合规要求下保留 kata-collect-data.sh 的审计快照(版本、配置、内核)。
- 容量规划:Kata Pod 额外内存开销(100–200Mi/Pod)纳入 Node 资源模型。
- 回滚预案:RuntimeClass 一键切回 runc;必要时 kubectl cordon/drain 迁走 Kata Pod。
灰度与回滚(十五分钟演习)
灰度
- 给一台 Node 打 beta.kubernetes.io/instance-type=hk-biz-kata。
- 发布 RuntimeClass: kata,仅把 canary Deployment 的 10% 副本改为 runtimeClassName: kata。
- 观察 24 小时,重点看启动时延、内存曲线、HTTP P99、错误率。
回滚
- 把 runtimeClassName 移除或改回 runc;
- 若 Node 故障,kubectl cordon + drain,Deployment 自动在 runc 池子重建。
- CRI-O 参考(给 OpenShift/CRI-O 用户)
在 /etc/crio/crio.conf.d/99-kata.conf:
[crio.runtime]
default_runtime = "runc"
[crio.runtime.runtimes.kata]
runtime_type = "vm"
runtime_path = "" # v2 shim 不再使用二进制路径
runtime_root = "/run/vc"
monitor_path = ""
/etc/crio/crio.conf 中 conmon_cgroup = "pod" 并与 systemd cgroup 配套。
重启:
sudo systemctl restart crio
K8s 侧同样用 RuntimeClass(handler: kata)。
运维侧的小优化(现场体感很有用)
Kata Factory(预热轻虚机):降低冷启动延迟;夜间大促前先做一次预热。
Guest 内核裁剪:对特定业务裁掉不必要模块,减少 RSS。
镜像分层优化:把大依赖放基础层,减少 Kata guest 的共享开销。
调度亲和:高敏 + 高 I/O 的 Pod 落到 NVMe RAID0 的节点上。
镜像签名:cosign/Notary 强制校验,防止“安全的轻虚机里跑不安全的镜像”。
FAQ:团队最常问的三个问题
“Kata 会不会很慢?”
控制面启动会慢 1–2 秒;数据面(网络/磁盘)调好后与 runc 接近。选对工作负载,就值。
“能 100% 替代 runc 吗?”
不建议。双轨是最优解:默认 runc 保持效率,敏感走 Kata 保证隔离。
“怎么证明它更安全?”
从攻击面角度,guest kernel 与宿主分离,KVM 形成强隔离;再配合最小权限、镜像签名与审计,能满足大多数合规条款。
结尾:风扇声后的宁静
那天清晨 5 点,香港的天还没亮,NOC 走廊尽头的咖啡机终于不再排队。Grafana 上轻虚机的启动曲线稳住了,几个高敏服务的 Pod 成功切到 Kata,审计的红色标注也被我们自己划掉了。
回到酒店前,我把终端里的 kubectl get pods -o wide 截了一张图,给同事发去:“该睡了。隔离墙已经立好。”
安全不是一次性的“补丁”,而是一套能在风扇声里稳定运行的工程。希望这篇手记,能帮你也把那面墙立起来。
附:一键校验脚本(我现场常用)
只做只读/校验,不改系统;放到 check-kata.sh,给新人跑一遍心里有底。
#!/usr/bin/env bash
set -euo pipefail
GREEN='\033[0;32m'; RED='\033[0;31m'; NC='\033[0m'
ok(){ echo -e "${GREEN}[OK]${NC} $*"; }
err(){ echo -e "${RED}[ERR]${NC} $*"; }
echo "== CPU virtualization =="
if egrep -qi 'vmx|svm' /proc/cpuinfo; then ok "CPU supports VT"; else err "No VT"; fi
echo "== KVM modules =="
if lsmod | egrep -q 'kvm_(intel|amd)'; then ok "KVM loaded"; else err "KVM not loaded"; fi
echo "== SELinux =="
getenforce | grep -qi enforcing && ok "SELinux enforcing" || err "SELinux not enforcing"
echo "== cgroup v2 =="
stat -fc %T /sys/fs/cgroup | grep -q cgroup2fs && ok "cgroup v2" || err "not cgroup v2"
echo "== containerd runtimes =="
if command -v crictl >/dev/null; then
crictl info | jq -r '.config.containerd.runtimes | keys[]' | grep -q kata \
&& ok "kata runtime registered" || err "kata runtime missing"
else
err "crictl not installed"
fi
echo "== QEMU path =="
[ -x /usr/libexec/qemu-kvm ] && ok "qemu-kvm exists" || err "qemu-kvm missing"
echo "== Kata files =="
for f in /usr/share/kata-containers/vmlinuz* /usr/share/kata-containers/kata-containers.img; do
[ -f "$f" ] && ok "found $f" || err "missing $f"
done
echo "== Done =="
如果你已经在 RHEL/香港节点上做到了这一步,剩下的就是把它纳入你的日常工程纪律:灰度、观测、回滚——和那杯凌晨 3 点的咖啡。