作者:Mirash Gjolaj 和 Re Alvarez-Parmar
近年来,容器已成为部署和管理应用程序的主要方法。容器的广泛采用归因于众多优势,例如隔离、高效的硬件可用性、可扩展性和可移植性。在资源隔离对系统安全至关重要的情况下,许多用户被迫依赖虚拟机 (VM) 来减轻受感染容器对主机或共享主机等其他容器的影响。
在最近的一次用户互动中,我们遇到了一个用例,团队需要保证其容器的防篡改特性。具体来说,他们需要编译代码并使用高度安全的密钥对其进行加密签名。在构建过程中,必须防止未经授权访问此密钥,确保在同一节点上运行的其他容器无法破坏或提取它。这一严格的安全要求使他们无法使用容器在 Kubernetes 中执行构建任务。
Kata Containers
Kata Containers 是一个开源项目,它提供了一个安全的容器运行时,将容器的轻量级特性与虚拟机的安全优势相结合。它使用硬件虚拟化技术作为第二层防御,提供更强大的工作负载隔离。在 Kata Containers 中,每个容器都使用不同的客户操作系统进行有效启动,而传统容器则在工作负载之间共享 Linux 内核,并通过使用命名空间和控制组 (cgroups) 实现容器隔离。虽然传统容器非常适合许多工作负载,但在需要更强的隔离和安全性时,它们就显得力不从心了。
Kata Containers 在精简的 OCI 兼容虚拟机中运行容器,以在共享主机的容器之间提供严格隔离。
Kata Containers 支持 AMD64 和 ARM 等主要架构。它还支持多种虚拟机管理程序,例如 Cloud-Hypervisor 和 Firecracker(一种由 AWS 构建的虚拟机管理程序,由 AWS Lambda 使用并与 containerd 项目集成)。
Kata Containers 通过使用 Kubernetes 编排系统为最终用户提供众所周知的界面,同时提供自定义运行时来运行使用基于 Linux 内核的虚拟机 (KVM) 提供强大工作负载隔离和安全性的特定虚拟机管理程序软件,从而抽象出编排工作负载的复杂性。
Kata Containers 允许您运行与行业标准工具(例如 OCI 容器格式和 Kubernetes CRI 接口)集成的容器。它使用所选的虚拟机管理程序部署您的容器,从而创建一个 VM 来托管 Kata Containers 代理 (kata-agent) 和容器环境中的工作负载。每个 VM 都托管一个 kata-agent,充当管理容器和在这些容器中运行的工作负载的主管。VM 具有单独的客户内核,该内核针对启动时间和最小内存占用进行了高度优化,仅提供容器工作负载所需的服务,该内核基于最新的 Linux 长期支持 (LTS) 内核版本。您可以在其文档页面中找到有关 Kata Containers 架构的详细信息。
通过采用 Kata Containers,用户可以使用 Kubernetes 编排其构建作业,而对其持续集成 (CI) 系统进行最少的配置更改。该实现确保其 Pod 是隔离的,从而提供针对容器突破的强大保护,同时保持容器化工作负载的灵活性和效率。
在 AWS 上运行 Kata Containers
在本文的下一部分中,我们将演示如何使用 Amazon Elastic Kubernetes Service (EKS) 在 AWS 上设置和运行 Kata Containers。在开始之前,请注意,建议从部署在与您的 EKS 集群所在的同一 Amazon Virtual Private Cloud (VPC) 中的堡垒主机运行以下说明。
我们使用 Amazon EKS 运行一个功能齐全的 Kubernetes 集群,该集群使用 Amazon Elastic Compute Cloud (EC2) 裸机实例作为工作节点,以允许生成 KVM。事实上,标准 EC2 实例不允许嵌套虚拟化,因此需要使用裸机。
先决条件
需要满足以下先决条件:
l运行 EKS 集群的 Amazon VPC
l用于远程访问 Amazon VPC 的 Bastion Host
l具有本文档中所述最低限度策略的 AWS 身份和访问管理 (IAM) 角色,与 Bastion Host 关联
l注意:查看上一个文档中描述的策略以授予创建集群和节点组的权限
lAWS 命令行界面 (AWS CLI) v2 – 安装指南
leksctl – 安装指南
lkubectl – 安装指南 (v1.29.0)
配置 EKS 集群
环境准备就绪并可供使用后,您需要通过安全 Shell (SSH) 进入 Bastion Host 执行以下命令(我们建议使用 AWS Systems Manager 启动新会话):
eksctl create cluster \
--name EKS-Kata \
--region ap-southeast-1 \
--version 1.29 \
--vpc-private-subnets subnet-a,subnet-b \
--without-nodegroup
您必须更新此命令以更改要部署集群的子网或 AWS 区域。为了便于理解,我们使用 Kubernetes 版本 1.29,您可以更新该版本以匹配要部署的版本。
此命令创建一个 AWS CloudFormation 堆栈并部署一个新的 EKS 集群。您需要等待几分钟才能激活控制平面,然后才能将其用于其他配置。
配置 EKS 节点
集群正确部署并创建后,我们可以继续添加一个新节点组,其中包含运行工作负载所需的实例。在本练习中,我们使用 i3.metal 实例,但您可以使用任何适合您用例的 metal 实例。
创建 metal-node-group.yaml,内容如下:
cat <<EOF > metal-node-group.yaml
---
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: EKS-Kata
region: ap-southeast-1
managedNodeGroups:
- name: metal-instances
instanceType: i3.metal
amiFamily: Ubuntu2004
requiredCapacity: 1
minSize: 1
maxSize: 1
volumeSize: 150
volumeType: gp3
volumeEncrypted: true
privateNetworking: true
ssh:
enableSsm: true
subnets: ["subnet-a", "subnet-b"]
iam:
withAddonPolicies:
cloudWatch: true
EOF
请注意,在创建此文件之前,您必须更新子网以使用 VPC 中的子网。然后使用以下命令创建节点组:
eksctl create nodegroup -f metal-node-group.yaml
与 create cluster 命令类似,create nodegroup 命令会创建一个新的 CloudFormation 模板,该模板将在几分钟内部署具有所需容量的节点组。
部署 Kata Containers
Kata Deploy 是在 Kubernetes 集群中部署 Kata Containers的更快方法。虽然它是大多数情况下建议的部署方法,但请注意,它提供了一个 Dockerfile,其中包含运行 Kata Containers所需的二进制文件和工件(包括虚拟机管理程序二进制文件)。如果您需要所选虚拟机管理程序或客户机内核映像的自定义版本,我们建议您按照开发人员指南构建自己的二进制文件和基础映像。
从 Bastion Host,我们现在可以使用 Kata Deploy 将 Kata Containers 部署到我们的集群中:
kubectl apply -f https://raw.githubusercontent.com/kata-containers/kata-containers/main/tools/packaging/kata-deploy/kata-rbac/base/kata-rbac.yaml
kubectl apply -f https://raw.githubusercontent.com/kata-containers/kata-containers/main/tools/packaging/kata-deploy/kata-deploy/base/kata-deploy.yaml
然后等待部署正确完成:
kubectl -n kube-system wait --timeout=10m --for=condition=Ready -l name=kata-deploy pod
运行以下 kubectl 命令以应用 Kata Runtime 类:
kubectl apply -f https://raw.githubusercontent.com/kata-containers/kata-containers/main/tools/packaging/kata-deploy/runtimeclasses/kata-runtimeClasses.yaml
运行时类允许您快速创建使用特定虚拟机管理程序运行的 pod。这些是带有注释的预配置类,允许在支持 Kata Containers 运行时的集群节点中部署我们的 pod。Kata Containers 提供多个运行时类来支持 Kata Deploy 部署的虚拟机管理程序。
以下是为 Firecracker 定义的运行时类的示例:
kind:RuntimeClass
apiVersion:node.k8s.io/v1
metadata:
name:kata-fc
handler:kata-fc
overhead:
podFixed:
memory:“130Mi”
cpu:“250m”
scheduling:
nodeSelector:
katacontainers.io/kata-runtime:“true”
部署过程还会自动更新 containerd 配置,以添加由 Kata 提供的运行时类,该类配置为使用自定义运行时 shim 运行。
配置 Firecracker
Firecracker 是一种开源虚拟化技术,专门用于创建和管理安全的多租户容器和基于功能的服务。Firecracker 使您能够在轻量级虚拟机(称为 microVM)中部署工作负载,与传统虚拟机相比,microVM 提供了增强的安全性和工作负载隔离,同时提高了容器的速度和资源效率。Firecracker 是在 AWS 开发的,旨在改善 AWS Lambda 等服务的用户体验。
由于 Firecracker 的虚拟机监视器 (VMM) 不支持 microVM 和主机之间的文件系统级共享,因此需要配置一个快照程序,该快照程序将创建快照作为文件系统映像,这些映像可以作为设备暴露给 Firecracker microVM。containerd 使用快照程序来存储映像和容器数据。
在本节中,我们将了解如何为 Firecracker 配置 devmapper 快照程序。您需要登录到前面步骤中已配置的节点(推荐使用 Systems Manager 方法)。
验证 devmapper 插件是否尚未配置:
sudo ctr plugins ls | grep devmapper
如果此命令的输出是
io.containerd.snapshotter.v1 devmapper linux/amd64 error
那么您必须创建您的 devmapper 快照程序。将以下内容复制到 acreate.sh 脚本文件中:
#!/bin/bash
set -ex
DATA_DIR=/var/lib/containerd/io.containerd.snapshotter.v1.devmapper
POOL_NAME=devpool
mkdir -p ${DATA_DIR}
# 创建数据文件
sudo touch "${DATA_DIR}/data"
sudo truncate -s 100G "${DATA_DIR}/data"
# 创建元数据文件
sudo touch "${DATA_DIR}/meta"
sudo truncate -s 40G "${DATA_DIR}/meta"
# 分配循环设备
DATA_DEV=$(sudo losetup --find --show "${DATA_DIR}/data")
META_DEV=$(sudo losetup --find --show "${DATA_DIR}/meta")
# 定义精简池参数。
# 详情请参阅 https://www.kernel.org/doc/Documentation/device-mapper/thin-provisioning.txt。
SECTOR_SIZE=512
DATA_SIZE="$(sudo blockdev --getsize64 -q ${DATA_DEV})"
LENGTH_IN_SECTORS=$(bc <<< "${DATA_SIZE}/${SECTOR_SIZE}")
DATA_BLOCK_SIZE=128
LOW_WATER_MARK=32768
# 创建精简池设备
sudo dmsetup create "${POOL_NAME}" \
--table "0 ${LENGTH_IN_SECTORS} thin-pool ${META_DEV} ${DATA_DEV} ${DATA_BLOCK_SIZE} ${LOW_WATER_MARK}"
使其可执行并运行脚本:
sudo chmod +x create.sh && sudo ./create.sh
验证是否已成功创建:
sudo dmsetup ls
接下来,更新 containerd 配置/etc/containerd/config.toml 使用您喜欢的编辑器在文件末尾添加以下部分:
[plugins."io.containerd.snapshotter.v1.devmapper"]
pool_name = "devpool"
root_path = "/var/lib/containerd/io.containerd.snapshotter.v1.devmapper"
base_image_size = "40GB"
此外,您还应更新 kata-fc 运行时部分以将 devmapper 快照程序添加到配置文件中:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata-fc]
snapshotter = "devmapper" # 要添加到配置文件中的行
runtime_type = "io.containerd.kata-fc.v2"
完成这两个操作后,使用 sudo systemctl restart containerd 重新启动守护进程。
现在您可以验证插件是否正常运行:
sudo ctr plugins ls | grep devmapper
...
io.containerd.snapshotter.v1 devmapper linux/amd64 ok
上述脚本只需运行一次,即首次为 containerd 设置 devmapper 快照程序时。随后,确保每次重新启动时,精简池都从同一数据目录初始化。这是一个可用于此目的的简单脚本 (reload.sh):
#!/bin/bash
set -ex
DATA_DIR=/var/lib/containerd/io.containerd.snapshotter.v1.devmapper
POOL_NAME=devpool
# 分配循环设备
DATA_DEV=$(sudo losetup --find --show "${DATA_DIR}/data")
META_DEV=$(sudo losetup --find --show "${DATA_DIR}/meta")
# 定义精简池参数。
# 有关详细信息,请参阅 https://www.kernel.org/doc/Documentation/device-mapper/thin-provisioning.txt。
SECTOR_SIZE=512
DATA_SIZE="$(sudo blockdev --getsize64 -q ${DATA_DEV})"
LENGTH_IN_SECTORS=$(bc <<< "${DATA_SIZE}/${SECTOR_SIZE}")
DATA_BLOCK_SIZE=128
LOW_WATER_MARK=32768
# 创建精简池设备
sudo dmsetup create "${POOL_NAME}" \
--table "0 ${LENGTH_IN_SECTORS} 精简池 ${META_DEV} ${DATA_DEV} ${DATA_BLOCK_SIZE} ${LOW_WATER_MARK}"
然后我们让这个文件可执行,并创建一个在每次重启后重新初始化 devpool 的服务:
sudo chmod +x reload.sh && sudo nano /lib/systemd/system/devmapper_reload.service
添加以下内容:
[Unit]
Description=Devmapper reload script
[Service]
ExecStart=/path/to/script/reload.sh
[Install]
WantedBy=multi-user.target
记得更改 reload.sh 脚本的绝对路径,然后启用服务:
sudo systemctl daemon-reload
sudo systemctl enable devmapper_reload.service
sudo systemctl start devmapper_reload.service
在终止 EKS 节点上的会话之前,请注意您正在使用的内核版本。在下一节中,与客户操作系统版本进行比较很有用:
$ uname -a
Linux ip-192-168-118-78 5.15.0-1051-aws
部署工作负载
现在配置已完成,我们可以使用已创建的 Kata Containers 运行时类来运行工作负载。应从您的 Bastion Host 执行以下指令。
您可以通过运行以下命令 kubectl get Runtimeclass 来验证可用的运行时类。在本文中,我们仅展示在 Firecracker 或 Cloud Hypervisor 上运行的示例。但是,如果您想使用其他受支持的虚拟机管理程序,则只需相应地更新运行时类即可。
创建以下 redis-pod.yaml 文件:
cat <<EOF > redis-pod.yaml
---
apiVersion: v1
kind: Pod
metadata:
name: redis-pod
spec:
runtimeClassName: kata-fc
containers:
- name: redis-container
image: public.ecr.aws/docker/library/redis:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 6379
EOF
然后使用 kubectl create -f redis-pod.yaml 将其部署到 Kubernetes 集群上。
要验证部署是否在其自己的 microVM 上运行,您可以运行以下命令来检查操作系统版本:
$ kubectl exec -it redis-pod -- bash -c "uname -a"
Linux redis-pod 6.1.38
microVM 应在与主机操作系统使用的内核版本不同的内核版本中运行。事实上,集群的节点计算机使用的是 5.15.0-1051-aws 内核版本,而客户操作系统使用的是 6.1.38。
您还可以更新 RuntimeClassName 属性以在不同的虚拟机管理程序下运行容器。例如,以下配置描述了如何更新它以使用 Cloud Hypervisor:
cat <<EOF > redis-pod.yaml
---
apiVersion: v1
kind: Pod
metadata:
name: redis-pod
spec:
runningClassName: kata-clh
containers:
- name: redis-container
image: public.ecr.aws/docker/library/redis:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 6379
EOF
从节点机器,您还可以通过使用 ps 命令检查正在运行的进程来确认您的 pod 是否确实在正确的虚拟机管理程序上运行:
例如,对于 Firecracker:
$ ps -aux | grep firecracker
root 195793 4.7 0.0 2104568 131816 ? Sl 12:51 0:00 /firecracker --id 9dab6e65aa7be2e23b2b999b5694d4e1 --start-time-us 35935709841 --start-time-cpu-us 0 --parent-cpu-time-us 6709 --config-file /fcConfig.json
或者,对于 Cloud Hypervisor:
$ ps -aux | grep cloud-hypervisor
root 193537 3.0 0.0 2385448 154092 ? Sl 12:46 0:00 /opt/kata/bin/cloud-hypervisor --api-socket /run/vc/vm/f01ac1ae85101532c1cd93025ee25d408303ba9f74d39b119f2b327adbe16dab/clh-api.sock
每个虚拟机管理程序也有自己的配置文件,位于 /opt/kata/share/defaults/kata-containers/ 文件夹下,可以根据您的要求进行调整。containerd (/etc/containerd/config.toml) 配置指的是特定运行时类部分下的这些 Kata Containers 配置文件。在大多数情况下,默认配置足以让您运行工作负载,但请注意,如果需要特定配置(例如启用调试选项或更新要使用的最大 CPU 数量),则可以从这些配置文件中更新它并通过重新启动 containerd 服务来应用。
清理
完成实验后,您可以通过删除前面描述的 eksctl 命令创建的 CloudFormation 模板来清理 Kubernetes 集群和 EKS 节点。或者,您可以运行删除集群命令:
eksctl delete cluster \
--name EKS-Kata \
--region ap-southeast-1 \
--disable-nodegroup-eviction \
--wait
这会自动从您的 AWS 账户中删除代表节点组和 EKS 集群的 CloudFormation 堆栈。
结论
在这篇文章中,我们详细介绍了使用 Amazon EC2 裸机实例和 Kata Containers在 Amazon EKS 上设置自管理 microVM 基础设施的过程。这种方法将容器编排系统的灵活性与 VM 提供的增强安全性和隔离性相结合。虽然此设置可能适用于小型测试或概念验证,但在将这些工作负载部署到生产中之前,进行彻底的评估、确定此设置是否适合生产用例以及进行成本效益分析至关重要。大规模操作虚拟机管理程序需要深入了解它们对整个堆栈的影响,从应用程序到 Linux 内核。通常透明处理的任务,例如 Amazon EC2 或操作系统中的运行状况检查和主机修补以及 Amazon EKS 中的 Kubernetes 修补,在裸机 EC2 实例上使用自管理节点组时成为用户责任。对于大多数用户,我们建议使用托管解决方案,例如 Amazon EC2、Amazon Elastic Container Service (ECS) 和 Amazon EKS,以实现更顺畅的操作和降低复杂性。