存储管理

EKS Auto Mode中的 StorageClass 定义了当应用程序请求持久存储时,EBS 卷如何自动创建。通过配置 StorageClass,我们可以为 EBS 卷指定默认设置,包括卷类型、加密、IOPS 和其他存储参数。我们还可以配置 StorageClass 使用 AWS KMS 密钥进行加密管理。

检查SQL数据库

catalogorder 微服务各自使用在集群中单独的pod中运行的SQL数据库。

catalog微服务使用在集群中的pod中运行的MySQL数据库。我们可以检查MySQL数据库pod以查看其当前卷配置:

kubectl describe statefulset catalog-mysql

我们应该看到类似于以下的输出:

image-20250228181302656

我们可以得出以下观察结果:

  • MySQL数据库被部署为具有单个副本的StatefulSet
  • pod模板包含一个mysql容器,带有类型为emptyDirdata卷。

同样,_orders_微服务使用PostgreSQL数据库。我们可以检查PostgreSQL数据库pod以查看其当前卷配置:

kubectl describe statefulset orders-postgresql

我们应该看到类似于以下的输出:

image-20250228181346406

我们可以看到PostgreSQL数据库被部署为具有单个副本的StatefulSet,并使用类型为emptyDirdata卷。

emptyDir的临时性质

当Pod被分配到节点时,首先创建emptyDir卷,并且只要该Pod在该节点上运行,该卷就存在。顾名思义,emptyDir卷最初是空的。Pod中的所有容器都可以读取和写入emptyDir卷中的相同文件,尽管该卷可以在每个容器中挂载在相同或不同的路径上。当pod因任何原因从节点中移除时,emptyDir中的数据将被永久删除。因此,emptyDir不适合我们的SQL数据库。

我们可以通过在PostgreSQL容器内启动shell会话并创建测试文件来演示emptyDir的临时性质。之后,我们将删除在StatefulSet中运行的pod。因为pod使用的是emptyDir而不是PV,所以文件在pod重启后将不会保留。

➤ 首先,让我们在PostgreSQL容器内运行一个命令,在/data/pgdata路径(PostgreSQL保存数据库文件的地方)创建一个文件:

kubectl exec orders-postgresql-0 -- bash -c "echo 123 > /data/pgdata/test.txt"

➤ 现在,让我们验证我们的test.txt文件是否已在/data/pgdata目录中创建:

kubectl exec orders-postgresql-0 -- cat /data/pgdata/test.txt

我们应该看到我们创建的文件内容:

image-20250228181541179

➤ 现在,让我们删除当前的orders-postgresqlpod,强制StatefulSet控制器自动重新创建一个新的orders-postgresqlpod:

kubectl delete pod orders-postgresql-0

➤ 等待pod重新创建:

kubectl wait --for=condition=Ready pod orders-postgresql-0 --timeout=30s

几秒钟后,我们应该看到:

image-20250228181635087

➤ 验证pod是否正在运行:

kubectl get pod orders-postgresql-0

输出应类似于以下内容:

~/kongpingfan > kubectl get pod orders-postgresql-0                                                                                                                                     kube casual-indie-sheepdog
NAME                  READY   STATUS    RESTARTS   AGE
orders-postgresql-0   1/1     Running   0          35s

➤ 检查/data/pgdata目录中是否存在test.txt

kubectl exec orders-postgresql-0 -- cat /data/pgdata/test.txt

输出如下:

cat: /data/pgdata/test.txt: No such file or directory
command terminated with exit code 1

我们可以看到,由于emptyDir卷是临时的,test.txt文件不再存在。

使用 EBS CSI Driver

接下来,我们定义一个默认的StorageClass,并使用它为_catalog_ MySQL数据库创建持久卷。

创建带有默认 KMS 加密的 StorageClass

➤ 首先,按如下方式创建 KMS 密钥:

KEY_ID=$(aws kms create-key --query 'KeyMetadata.KeyId' --output text)
KEY_ARN=$(aws kms describe-key --key-id $KEY_ID --query 'KeyMetadata.Arn' --output text)
echo "Key Id:" $KEY_ID
echo "Key Arn:" $KEY_ARN

➤ 接下来,为 KMS 密钥创建一个 IAM 资源策略 JSON 文档,该文档允许在 EC2 托管实例上运行的 CSI 服务assume该角色,以加密和解密写入 EBS 卷的数据:

AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
AWS_REGION=$(aws configure list | grep region | awk '{print $2}')
cat >key-policy.json <<EOF
{
    "Version": "2012-10-17",
    "Id": "key-auto-policy-3",
    "Statement": [
        {
            "Sid": "iam-kms",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::${AWS_ACCOUNT_ID}:root"
            },
            "Action": "kms:*",
            "Resource": "*"
        },
        {
            "Sid": "ec2-kms",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:ReEncrypt*",
                "kms:GenerateDataKey*",
                "kms:CreateGrant",
                "kms:DescribeKey"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "kms:CallerAccount": "${AWS_ACCOUNT_ID}",
                    "kms:ViaService": "ec2.$AWS_REGION.amazonaws.com"
                }
            }
        }
    ]
}
EOF

➤ 将此策略文档附加到 KMS 密钥:

aws kms put-key-policy --key-id $KEY_ID --policy file://key-policy.json

➤ 最后,使用 KMS 密钥创建一个新的存储类:

cat >ebs-kms-sc.yaml <<EOF
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: eks-auto-ebs-kms-sc
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.eks.amazonaws.com
volumeBindingMode: WaitForFirstConsumer
parameters:
  type: gp3
  encrypted: "true"
  kmsKeyId: ${KEY_ID}
EOF

kubectl apply -f ebs-kms-sc.yaml

➤ 检查我们刚刚创建的 StorageClass

kubectl describe storageclass eks-auto-ebs-kms-sc

这应该产生以下输出:

image-20250228185143391

  • eks-auto-ebs-kms-sc 被配置为默认存储类。
  • 相关的供应商是 ebs.csi.eks.amazonaws.com
  • EBS 卷类型设置为 gp3(默认为 3000 IOPS)。
  • ReclaimPolicy 设置为 Delete,这意味着当关联的 PVC 被删除时,这将导致 Kubernetes 中的 PV 对象以及外部基础设施中的相关存储资产都被删除。
  • 使用我们创建的 KMS 密钥为卷配置了加密。

更新 catalog MySQL 数据库 pod

现在我们已经有了一个默认的 StorageClass,让我们更新 catalog 服务以使用它。由于 StatefulSet 的许多字段(包括 volumeClaimTemplates)无法修改,我们将删除 catalog 组件,然后重新安装它,以便我们可以将卷类型从 emptyDir 更新为持久卷。

➤ 首先,删除当前的 catalog statefulset:

kubectl delete statefulset catalog-mysql

创建一个新的yaml catalog-mysql.yaml

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: catalog-mysql
  labels:
    helm.sh/chart: catalog-1.0.0
    app.kubernetes.io/name: catalog
    app.kubernetes.io/instance: catalog
    app.kubernetes.io/component: mysql
    app.kubernetes.io/owner: retail-store-sample
    app.kubernetes.io/managed-by: Helm
spec:
  replicas: 1
  serviceName: catalog-mysql
  selector:
    matchLabels:
      app.kubernetes.io/name: catalog
      app.kubernetes.io/instance: catalog
      app.kubernetes.io/component: mysql
      app.kubernetes.io/owner: retail-store-sample
  template:
    metadata:
      labels:
        app.kubernetes.io/name: catalog
        app.kubernetes.io/instance: catalog
        app.kubernetes.io/component: mysql
        app.kubernetes.io/owner: retail-store-sample
    spec:
      containers:
        - name: mysql
          image: "public.ecr.aws/docker/library/mysql:8.0"
          imagePullPolicy: IfNotPresent
          env:
            - name: MYSQL_ROOT_PASSWORD
              value: my-secret-pw
            - name: MYSQL_DATABASE
              value: catalog
            - name: MYSQL_USER
              valueFrom:
                secretKeyRef:
                  name: catalog-db
                  key: username  # 根据实例secret中的keys进行替换,因为这个retail app在github上有几个版本
            - name: MYSQL_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: catalog-db
                  key: password  # 根据实例secret中的keys进行替换,因为这个retail app在github上有几个版本
          volumeMounts:
            - name: data
              mountPath: /var/lib/mysql
          ports:
            - name: mysql
              containerPort: 3306
              protocol: TCP
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 30Gi

部署:

kubectl apply -f catalog-mysql.yaml

验证 catalog MySQL 数据库的 PVC 是否已创建

重新启动的 catalog 服务现在应该有一个关联的 PersistentVolumeClaim

➤ 我们可以通过运行以下命令查看:

kubectl describe statefulset catalog-mysql

这次输出显示:

image-20250228195550685

让我们检查 PVC 资源。要列出所有 PVC,请使用:

kubectl get pvc

我们应该看到:

image-20250228195036927

这是为 MySQL 数据库创建的 PVC。

➤ 使用以下命令检查它:

kubectl describe pvc data-catalog-mysql-0

产生以下输出:

image-20250228195130326

从输出中可以看到,PVC 绑定到特定的 PV,容量为 30Gi

验证 catalog MySQL 数据库的 EBS 卷是否已正确创建

按如下方式获取底层 Amazon EBS 卷 ID:

MYSQL_PV_NAME=$(kubectl get pvc data-catalog-mysql-0 -o jsonpath="{.spec.volumeName}")
MYSQL_EBS_VOL_ID=$(kubectl get pv $MYSQL_PV_NAME -o jsonpath="{.spec.csi.volumeHandle}")
echo EBS Volume ID: $MYSQL_EBS_VOL_ID

显示 EBS 卷的详细信息:

aws ec2 describe-volumes --volume-ids $MYSQL_EBS_VOL_ID 

注意输出的以下部分,证明已启用 KMS 加密并使用了正确的密钥:

~/kongpingfan > aws ec2 describe-volumes --volume-ids $MYSQL_EBS_VOL_ID |jq
{
  "Volumes": [
    {
      "Iops": 3000,
      "Tags": [
        {
          "Key": "kubernetes.io/created-for/pvc/namespace",
          "Value": "default"
        },
        {
          "Key": "CSIVolumeName",
          "Value": "pvc-92946947-aa1c-4927-bba6-f238b63cbb91"
        },
        {
          "Key": "Name",
          "Value": "casual-indie-sheepdog-dynamic-pvc-92946947-aa1c-4927-bba6-f238b63cbb91"
        },
        {
          "Key": "kubernetes.io/created-for/pvc/name",
          "Value": "data-catalog-mysql-0"
        },
        {
          "Key": "ebs.csi.eks.amazonaws.com/cluster",
          "Value": "true"
        },
        {
          "Key": "eks:eks-cluster-name",
          "Value": "casual-indie-sheepdog"
        },
        {
          "Key": "kubernetes.io/cluster/casual-indie-sheepdog",
          "Value": "owned"
        },
        {
          "Key": "KubernetesCluster",
          "Value": "casual-indie-sheepdog"
        },
        {
          "Key": "kubernetes.io/created-for/pv/name",
          "Value": "pvc-92946947-aa1c-4927-bba6-f238b63cbb91"
        }
      ],
      "VolumeType": "gp3",
      "MultiAttachEnabled": false,
      "Throughput": 125,
      "Operator": {
        "Managed": false
      },
      "VolumeId": "vol-0d082c7bdae57b1bf",
      "Size": 30,
      "SnapshotId": "",
      "AvailabilityZone": "us-west-2b",
      "State": "in-use",
      "CreateTime": "2025-02-28T11:50:08.586000+00:00",
      "Attachments": [
        {
          "DeleteOnTermination": false,
          "VolumeId": "vol-0d082c7bdae57b1bf",
          "InstanceId": "i-0b3f9eb336642e97f",
          "Device": "/dev/xvdaa",
          "State": "attached",
          "AttachTime": "2025-02-28T11:50:12+00:00"
        }
      ],
      "Encrypted": true,
      "KmsKeyId": "arn:aws:kms:us-west-2:145197526627:key/c9355090-957a-4157-89ce-da820bc9827d"
    }
  ]
}

总结

在本节中,我们执行了以下步骤:

  • 创建了一个 KMS 密钥。
  • 附加了一个密钥策略,使账户中的 EC2 实例能够使用 KMS 密钥加密 EBS 卷。
  • 创建了一个默认的 StorageClass,配置为使用此密钥进行加密,并使用标准的 gp3 卷。
  • 更新了 catalog 服务的配置,以使用默认的 StorageClass 创建持久卷。
  • 验证了 EBS 卷已正确创建,使用了正确的 KMS 密钥,并且使用了 gp3 卷的默认 IOPS 设置。