在 Amazon EKS 上使用 Amazon EFS CSI 实现可扩展的多功能存储解决方案
容器化应用的存储解决方案需要仔细地规划和执行。Amazon Elastic File System (EFS) 有助于完成 Kubernetes 工作负载,如内容管理系统和视频转码。Amazon EFS 可以提供无服务器、完全弹性的文件存储。使用 Amazon EFS,您无需预配或管理存储容量和性能就是实现文件数据共享。Amazon EFS 非常适合需要跨节点甚至跨可用区共享存储的应用。相比之下,Amazon Elastic Block Store (EBS) 则需要配置存储卷,并且存储卷有大小和区域限制。您的工作负载通过 Amazon EFS CSI 驱动程序插件访问 EFS 文件系统,从而实现这种复杂的可扩展的多功能存储解决方案。
此次试验中,需要在 Amazon EKS 集群上设置 Amazon EFS CSI 驱动程序并为容器工作负载配置持久共享存储。更具体地说,是将 Amazon EFS CSI 驱动程序安装为 Amazon EKS 的插件。Amazon EKS 插件系统自动化安装和管理 EKS 集群的插件。需要配置 EFS CSI 驱动程序来处理轻量级应用(类似微服务)和更重要的系统(如数据库或用户身份认证系统)的数据,实现无缝存储管理。
前提条件
开始本教程学习之前,您需要:
- 安装最新版本的 kubectl。运行以下命令检查您的 kubectl 版本:kubectl version --short。
- 安装最新版本的 eksctl。运行以下命令检查您的 eksctl 版本:eksctl info。
概览
本教程是关于在 Amazon EKS 中优化有状态应用程序系列的第二篇,重点介绍 Amazon EFS CSI 驱动程序,用于管理需要跨可用区持久化和共享存储内容的复杂容器化应用程序。本教程不仅会指导您如何在 kube-system 命名空间中配置 Amazon EFS CSI 驱动程序,还深入探讨了集群内部存储管理的复杂性。本教程包括以下部分:
- 身份认证:使用与 OpenID Connect (OIDC) 端点集成的 Amazon EFS CSI 驱动程序的预配置 IAM 角色来确保 Kubernetes Pod 和亚马逊云科技服务之间的安全通信。
- Amazon EFS CSI 驱动程序设置:在 Amazon EKS 集群内部部署 Amazon EFS CSI 驱动程序。重点关注自定义资源定义 (CRD) 和驱动程序本身的安装。
- 示例应用部署:构建并部署一个有状态的应用程序,将当前日期写入共享的 EFS 卷。根据 CentOS 镜像为持久卷声明 (PVC) 定义路由规则和注释。利用 PVC 的自定义注释,特别是 accessMode 和 storageClassName,指示 EFS CSI 驱动程序处理存储请求。使用 storageClassName 注释自动创建持久卷 (PV),实现动态预配置。
- EFS 访问点和安全性:探索 Amazon EFS CSI 驱动程序如何将 Amazon EFS 访问点作为特定应用程序的入口。解释端口 2049 对 NFS 流量的作用以及如何配置安全组允许或限制指定 CIDR 范围的访问。这些访问点加强了用户身份和根目录安全,提供根据特定子网进一步细化访问控制的灵活性。通过细粒度安全组设置,指定允许通过此端口的流量,允许指定 CIDR 范围内的实例或 Pod 与共享文件系统交互。这种细粒度控制不仅保证了相关集群资源可访问 EFS 文件系统,还可以基于特定子网进一步限制访问。
- 挂载目标:介绍如何为 EFS 文件系统创建挂载目标。挂载目标是 VPC 内 EC2 实例的关键连接点。请注意,无论一个可用区中配置了多少个子网,仅可以在该可用区中为同一个 EFS 文件系统创建一个挂载目标。如果您使用的 EKS 集群的节点位于私有子网中,我们建议您在这些子网中创建挂载目标。
注意:如果您的账户创建于 12 个月内,您可以使用 EFS 提供的免费的 5 GB 标准存储额度。
步骤 1:设置环境变量
在使用命令行工具与您的 Amazon EKS 集群交互之前,请务必定义封装集群详细信息的环境变量。这些变量将在后续操作命令中指定正确的集群和资源。
- 首先,确认集群环境。这确保了命令被发送到正确的目标 Kubernetes 集群。您可以通过执行以下命令来验证当前环境:
kubectl config current-context
- 定义 EKS 集群的环境变量 CLUSTER_NAME。请将 region 的值修改为实际值。
export CLUSTER_NAME=$(aws eks describe-cluster --region us-east-2 --name managednodes-quickstart --query "cluster.name" --output text)
- 定义 EKS 集群的环境变量 CLUSTER_REGION。请将 region 的值修改为实际值。
export CLUSTER_REGION=$(aws eks describe-cluster --name ${CLUSTER_NAME} --query "cluster.arn" --output text | cut -d: -f4)
- 定义 EKS 集群的环境变量 CLUSTER_VPC。
export CLUSTER_VPC=$(aws eks describe-cluster --name ${CLUSTER_NAME} --region ${CLUSTER_REGION} --query "cluster.resourcesVpcConfig.vpcId" --output text)
步骤 2:验证或创建服务帐户所需的 IAM 角色
验证 Amazon EKS 集群中是否已配置服务帐户所需的 IAM 角色。这些 IAM 角色用于实现亚马逊云科技服务与 Kubernetes 的无缝交互,允许在您的 Pod 中使用亚马逊云科技功能。
确认您的集群中已正确设置所需的服务帐户。
kubectl get sa -A | egrep "efs-csi-controller"
输出结果应如下所示:
kube-system efs-csi-controller-sa 0 30m
(可选)如果您还未设置 “efs-csi-controller-sa” 服务帐户,或者您收到错误提示,使用以下命令创建服务帐户。请注意,在运行这些命令之前,确认您的集群已经关联了一个 OpenID Connect (OIDC) 端点。
定义 ROLE_NAME 环境变量:
export ROLE_NAME=AmazonEKS_EFS_CSI_DriverRole
运行以下命令创建 IAM 角色、Kubernetes 服务帐户,并将亚马逊云科技管理的策略与该角色关联:
eksctl create iamserviceaccount \
--name efs-csi-controller-sa \
--namespace kube-system \
--cluster $CLUSTER_NAME \
--region $CLUSTER_REGION \
--role-name $ROLE_NAME \
--role-only \
--attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEFSCSIDriverPolicy \
--approve
此过程需要几分钟才能完成。输出结果应如下所示:
2023-08-16 15:27:19 [ℹ] 4 existing iamserviceaccount(s) (cert-manager/cert-manager,default/external-dns,kube-system/aws-load-balancer-controller,kube-system/efs-csi-controller-sa) will be excluded
2023-08-16 15:27:19 [ℹ] 1 iamserviceaccount (kube-system/efs-csi-controller-sa) was excluded (based on the include/exclude rules)
2023-08-16 15:27:19 [!] serviceaccounts in Kubernetes will not be created or modified, since the option --role-only is used
2023-08-16 15:27:19 [ℹ] no tasks
运行以下命令将 Kubernetes 服务帐户名称添加到 IAM 角色的信任策略中:
export TRUST_POLICY=$(aws iam get-role --role-name $ROLE_NAME --query 'Role.AssumeRolePolicyDocument' | \
sed -e 's/efs-csi-controller-sa/efs-csi-*/' -e 's/StringEquals/StringLike/')
更新 IAM 角色:
aws iam update-assume-role-policy --role-name $ROLE_NAME --policy-document "$TRUST_POLICY" --region $CLUSTER_REGION
步骤 3:验证或安装 EFS CSI 驱动程序插件
验证 EFS CSI 驱动程序管理的插件是否已正确安装并在您的 Amazon EKS 集群上处于激活状态。EFS CSI 驱动程序对于 Amazon EFS 与 Kubernetes 间的无缝协作至关重要。有了 EFS CSI 驱动程序,您可以将 EFS 文件系统作为批量工作负载的持久卷。
检查您的集群上是否已安装 EFS CSI 驱动程序插件:
eksctl get addon --cluster ${CLUSTER_NAME} --region ${CLUSTER_REGION} | grep efs
输出结果应如下所示:
aws-efs-csi-driver v1.5.8-eksbuild.1 ACTIVE 0
(可选)如果集群上未安装 EFS CSI 驱动程序插件,或者您收到错误提示,您可以运行以下命令显示安装步骤。请注意,在运行这些命令之前,您的集群必须已经关联了一个 OpenID Connect (OIDC) 端点。
列出 eksctl 中可用的插件。请将 kubernetes-version 的值替换为实际值。
eksctl utils describe-addon-versions --kubernetes-version 1.27 | grep AddonName
输出结果应如下所示:
"AddonName": "coredns",
"AddonName": "aws-guardduty-agent",
"AddonName": "aws-ebs-csi-driver",
"AddonName": "vpc-cni",
"AddonName": "kube-proxy",
"AddonName": "aws-efs-csi-driver",
"AddonName": "adot",
"AddonName": "kubecost_kubecost",
设置 aws-efs-csi-driver 插件的环境变量:
export ADD_ON=aws-efs-csi-driver
列出适用于 Kubernetes v1.27 的插件版本:
eksctl utils describe-addon-versions --kubernetes-version 1.27 --name $ADD_ON | grep AddonVersion
输出结果应如下所示:
"AddonVersions": [
"AddonVersion": "v1.5.8-eksbuild.1",
检索前面步骤中创建的 AmazonEKS_EFS_CSI_DriverRole 的 Arn:
aws iam get-role --role-name AmazonEKS_EFS_CSI_DriverRole | grep Arn
输出结果应如下所示:
"Arn": "arn:aws:iam::xxxxxxxxxxxx:role/AmazonEKS_EFS_CSI_DriverRole",
运行以下命令安装 EFS CSI 驱动程序插件。将 service-account-role-arn 替换为上一步中获得的 ARN。
eksctl create addon --cluster $CLUSTER_NAME --name $ADD_ON --version latest \
--service-account-role-arn arn:aws:iam::xxxxxxxxxxxx:role/AmazonEKS_EFS_CSI_DriverRole
输出结果应如下所示:
[ℹ] Kubernetes version "1.27" in use by cluster "CLUSTER_NAME"
[ℹ] using provided ServiceAccountRoleARN "arn:aws:iam::xxxxxxxxxxxx:role/AmazonEKS_EFS_CSI_DriverRole"
[ℹ] creating addon
[ℹ] addon "aws-efs-csi-driver" active
步骤 4:创建 EFS 文件系统
创建一个 EFS 文件系统并为它创建一个安全组。该安全组允许来自集群所属 VPC 的 CIDR 的请求访问 您的 EFS 文件系统。并在安全组规则中,限制端口为 2049,即 NFS 的标准端口。
- 检索集群所属 VPC 的 CIDR 范围并将其存储在环境变量中:
export CIDR_RANGE=$(aws ec2 describe-vpcs \
--vpc-ids $CLUSTER_VPC \
--query "Vpcs[].CidrBlock" \
--output text \
--region $CLUSTER_REGION)
- 创建一个带有允许访问您的 Amazon EFS 挂载点的入站规则的安全组:
export SECURITY_GROUP_ID=$(aws ec2 create-security-group \
--group-name MyEfsSecurityGroup \
--description "My EFS security group" \
--vpc-id $CLUSTER_VPC \
--region $CLUSTER_REGION \
--output text)
- 创建一个允许来自集群所属 VPC CIDR 访问流量的入站规则:
aws ec2 authorize-security-group-ingress \
--group-id $SECURITY_GROUP_ID \
--protocol tcp \
--port 2049 \
--cidr $CIDR_RANGE \
--region $CLUSTER_REGION
输出结果应如下所示:
{
"Return": true,
"SecurityGroupRules": [
{
"SecurityGroupRuleId": "sgr-0106e3f85cd3b82d9",
"GroupId": "sg-0a0a11596fe0ae56f",
"GroupOwnerId": "000866617021",
"IsEgress": false,
"IpProtocol": "tcp",
"FromPort": 2049,
"ToPort": 2049,
"CidrIpv4": "192.168.0.0/16"
}
]
}
为 Amazon EKS 集群创建一个 Amazon EFS 文件系统:
export FILE_SYSTEM_ID=$(aws efs create-file-system \
--region $CLUSTER_REGION \
--performance-mode generalPurpose \
--query 'FileSystemId' \
--output text)
步骤 5:配置 EFS 文件系统的挂载目标
EFS 仅允许在每个可用区中创建一个挂载目标,因此您需要将挂载目标配置在适当的子网中。如果您的工作节点位于私有子网中,则在该子网中创建挂载目标。下面,我们将为 EFS 文件系统创建挂载目标。挂载目标是位于接受 NFS 流量的 VPC 子网中的 IP 地址。Kubernetes 节点将使用挂载目标的 IP 地址建立 NFS 连接。
- 获取集群节点的 IP 地址:
kubectl get nodes
输出结果应如下所示:
NAME STATUS ROLES AGE VERSION
ip-192-168-56-0.region-code.compute.internal Ready <none> 19m v1.XX.X-eks-49a6c0
- 获取 VPC 中子网 ID 和子网所在的可用区:
aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=$CLUSTER_VPC" \
--region $CLUSTER_REGION \
--query 'Subnets[*].{SubnetId: SubnetId,AvailabilityZone: AvailabilityZone,CidrBlock: CidrBlock}' \
--output table
输出结果应如下所示:
| DescribeSubnets |
+------------------+--------------------+----------------------------+
| AvailabilityZone | CidrBlock | SubnetId |
+------------------+--------------------+----------------------------+
| region-codec | 192.168.128.0/19 | subnet-EXAMPLE6e421a0e97 |
| region-codeb | 192.168.96.0/19 | subnet-EXAMPLEd0503db0ec |
| region-codec | 192.168.32.0/19 | subnet-EXAMPLEe2ba886490 |
| region-codeb | 192.168.0.0/19 | subnet-EXAMPLE123c7c5182 |
| region-codea | 192.168.160.0/19 | subnet-EXAMPLE0416ce588p |
+------------------+--------------------+----------------------------+
- 在托管节点的子网中添加挂载目标。运行以下命令并指定子网创建挂载目标。例如,如果集群有一个节点的 IP 地址为 192.168.56.0,并且这个地址落在子网 ID subnet-EXAMPLEe2ba886490 的 CIDR 内。您需要为这个子网创建一个挂载目标。重复此过程,为每个节点所在的可用区中的每个子网创建挂载目标。
aws efs create-mount-target \
--file-system-id $FILE_SYSTEM_ID \
--subnet-id subnet-EXAMPLEe2ba886490 \
--security-groups $SECURITY_GROUP_ID \
--region $CLUSTER_REGION
注意:无论一个可用区中配置了多少个子网,仅可以在该可用区中为同一个 EFS 文件系统创建一个挂载目标如果您的 EKS 集群,并且工作节点位于私有子网中,建议为相同子网创建挂载目标。
步骤 6:为示例应用设置存储类
Kubernetes 支持两种容器应用存储预配方式:静态预配和动态预配。静态预配是集群管理员手动创建持久卷 (PV),为集群用户指定可用的存储。可以调用 Kubernetes API 轻松访问 PV。动态预配是自动化创建 PV。当 Pod 通过 PersistentVolumeClaims 请求存储时,Kubernetes 使用指定的存储类自动生成 PV。这种方法简化了配置过程,并可以满足应用的具体存储需求。要了解更多,请参考 Kubernetes 文档中的 PersistentVolumes。此次实验中,我们将使用动态预配方法。
- 首先,下载存储类清单:
curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-efs-csi-driver/master/examples/kubernetes/dynamic_provisioning/specs/storageclass.yaml
- 确认已配置环境变量 $FILE_SYSTEM_ID:
echo $FILE_SYSTEM_ID
- 更新 storageclass.yaml 清单中的文件系统标识符:
sed -i 's/fs-92107410/$FILE_SYSTEM_ID/g' storageclass.yaml
- (可选)如果您在 macOS 上进行此操作,您可以运行以下命令更改文件系统:
sed -i '' 's/fs-92107410/'"$FILE_SYSTEM_ID"'/g' storageclass.yaml
- 部署存储类:
kubectl apply -f storageclass.yaml
输出结果应如下所示:
storageclass.storage.k8s.io/efs-sc created
步骤 7:部署示例应用
部署一个示例应用程序。该应用程序将当前日期写入到 EFS 共享卷上的一个共享位置中。部署过程的第一步是下载一个清单文件,其中包含持久卷声明 (PVC) 规格和基于 CentOS 镜像 Pod 规格。
- 首先,下载久卷声明清单:
curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-efs-csi-driver/master/examples/kubernetes/dynamic_provisioning/specs/pod.yaml
注意:此清单包括基于 CentOs 镜像的示例 Pod 定义。它包含用于记录当前日期的特定参数,并将日期数据存储在 EFS 共享卷的 /data/out 目录中。此外,此清单包括一个 PVC 对象,该对象从上一步建立的存储类请求 5GB 存储。
- 现在,部署将当前日期写入共享位置的示例应用程序:
kubectl apply -f pod.yaml
- 运行以下命令以确认示例应用程序部署成功且正常运行:
kubectl get pods -o wide
输出结果应如下所示:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
efs-app 1/1 Running 0 10m 192.168.78.156 ip-192-168-73-191.region-code.compute.internal <none><none>
注意:大约几分钟时间后,Pod 状态会变为 Running。如果 Pod 一直处于 ContainerCreating 状态,请检查节点所在子网的挂载目标是否存在(见步骤 2)。如果没有在该子网中创建挂载目标,Pod 将一直卡在 ContainerCreating 状态。
- 验证数据是否正在写入共享 EFS 卷中的 /data/out 路径。运行以下命令进行验证:
kubectl exec efs-app -- bash -c "cat data/out"
输出结果应如下所示:
[...]
Fri Aug 4 09:13:40 UTC 2023
Fri Aug 4 09:13:45 UTC 2023
Fri Aug 4 09:13:50 UTC 2023
Fri Aug 4 09:13:55 UTC 2023
[...]
您可以选择终止托管 Pod 的节点并等待重新调度托管Pod 的资源。或者,您可以删除 Pod 并重新部署它。然后,重复上一步,确认输出中包含之前的输出。
清理资源
为避免持续产生费用,建议您及时删除在本试验过程中创建的资源。
# Delete the pod
kubectl delete -f pod.yaml
# Delete the Storage Class
kubectl delete -f storageclass.yaml
# Delete the EFS CSI add-on
eksctl delete addon --cluster $CLUSTER_NAME --name $ADD_ON
# Delete the IAM Role
aws iam delete-role --role-name AmazonEKS_EFS_CSI_DriverRole
总结
您已成功为基于 EKS 的容器工作负载配置了 Amazon EFS 持久存储,配置了基于 CentOS 镜像的示例 Pod,用于捕获当前日期并将日期数据存储在 EFS 共享卷的 /data/out 目录中。根据最佳实践,我们建议在私有子网上运行您的容器工作负载,仅将入口控制器暴露在公共子网中。此外,务必在 EKS 集群所在的所有可用区都建立 EFS 挂载目标;否则,您可能会遇到 "Failed to Resolve"(解析失败)错误。若出现 EFS 文件系统挂载问题,请参考挂载问题排查获取详细的故障排除指南。您可以继续学习如何将有状态的工作负载(如 批处理过程 或机器学习训练数据)存储到您的 EFS 卷中。