{"value":"### **1.背景**\n\n用户通过 Deployment、Replication Controller 可以方便地在 Kubernetes 中部署一套高可用、可扩展的分布式无状态服务。这类应用不在本地存储数据,通过简单的负载均衡策略可实现请求分发。Deployment、Replication Controller 是为无状态服务而设计的,它们中 Pod 的名称、主机名、存储都是不稳定的,且 Pod 的启动、销毁顺序随机,并不适合数据库这样的有状态应用。\n\n为此,Kubernetes 推出了面向有状态服务的工作负载 StatefulSet,其管理的 Pod 具有如下特点:\n\n- 唯一性 – 对于包含 N 个副本的 StatefulSet,每个 Pod 会被分配一个 [0,N] 范围内的唯一序号。\n- 顺序性 – StatefulSet 中 Pod 的启动、更新、销毁默认都是按顺序进行的。\n- 稳定的网络身份标识 – Pod 的主机名、DNS 地址不会随着 Pod 被重新调度而发生变化。\n- 稳定的持久化存储 – 当 Pod 被重新调度后,仍然能挂载原有的 PersistentVolume,保证了数据的完整性和一致性。\n\n本文针对有状态服务业务场景的数据保护安全需求,旨在从 EKS 内部结合采用 StatefulSet、Snapshot Controller 的方式实现有状态服务的存储加密启用,并在测试环境下验证了本方案的可行性。\n\n### **2.场景案例**\n\n#### **2.1 场景架构**\n\n业务场景:\n\n该业务平台构建在 Amazon 云上,服务客群主要为全国各医院,核心业务价值在于简化医院采购结算环节,帮助医院方降本增效。该平台通过结合医院,医院供应商提供的订单、结算单、发票信息,进行三单验证,并集成银行侧银企直连,进而实现医院的快速透明支付结算、有效提升采购效率。\n\n部署架构:\n\n之前客户在本地 IDC 的 Kubernetes 集群中除了部署了无状态的微服务应用外,还部署了有状态的中间件服务,包括 Redis、Kafka、 MySQL,存储使用的是本地磁盘。由于自建 Kubernetes 集群的升级、管理、安全等各方面的运维工作复杂,需要耗费大量的运维资源;并且在 Kubernetes 集群中运行有状态的应用,特别是业务数据库,存在很大的潜在风险,因而在迁移至 Amazon 云上的过程中,接受我方建议做了相应的架构优化及调整:\n\n- 自建 Kubernetes 调整为托管的 Amazon EKS 服务\n- 从 Kubernetes 集群中移除关系数据库 MySQL,替换为托管的 RDS Aurora 服务\n- EKS 中各业务组件采用无状态部署\n- EKS 中保留 Redis 和 Kafka 等中间件,并采用有状态部署,持久化采用 EBS\n- EKS 中采用 Amazon EFS 作为共享存储,实现存储的安全高可用\n\n架构示意图:\n\n![image.png](https://dev-media.amazoncloud.cn/b6888f53b13f410e99720c852ce0242f_image.png)\n\n#### **2.2 问题及解决方案**\n\n问题描述:\n\n由于涉及到订单、结算单、发票等敏感数据的传输及存储,在业务上线后不久,客户希望加强云上数据安全保护,结合 KMS 对持久化数据进行自动存储加密。之前有状态服务 Redis、Kafka 等在部署上线时所挂载的 EBS 并未启用加密,由于 EBS 在初始未启用加密的情况下无法直接开启加密(见下面技术约束示意图),那么在对业务的影响降至最低的前提下如何实现 EBS 存储加密的开启及业务的平滑迁移呢?\n\n技术约束示意图:\n\n初始未加密 EBS 无法直接启用加密,需对未加密 EBS 做快照,再将未加密快照还原成加密 EBS,如下图所示\n\n![image.png](https://dev-media.amazoncloud.cn/7fc2f3092dae44ebbcc641ad3459aa1b_image.png)\n\n解决方案:\n\n- 方案一:需应用层做数据迁移,EKS 中原 Redis 等有状态服务保持不动,采用 StatefulSet 方式新建 Redis 等有状态服务并启用 EBS 存储加密,在应用层做数据迁移(将敏感数据从原服务未加密 EBS 迁移至新建服务已加密 EBS)后将中间件服务切换至新建 Redis 等有状态服务并下线原服务以实现有状态服务的存储加密;\n- 方案二:不涉及应用层数据迁移,从 Amazon 控制台直接对 EBS 进行相关操作,将未加密 EBS 制作快照后还原成加密 EBS,并将加密 EBS 静态挂载回正确的 Redis 等有状态服务;\n- 方案三:不涉及应用层数据迁移,从 EKS 内部进行相关操作,首先启用 Snapshot Controller, 之后通过该 Controller 对未加密 pvc 制作快照后还原成加密 pvc,新建 Redis StatefulSet 并挂载加密 pvc,引流至新建 Redis 测试一切正常后做服务迁移;\n\n由于客户不希望应用层做数据迁移,以及从 Amazon 控制台操作后如何正确静态挂载回对应的 Pod 存在较大风险,最终采用了方案三进行了相应的测试及部署,并成功启用了 EKS 下 EBS 的存储加密。\n\n### **3.部署与测试**\n\n接下来我们会按照上述方案三进行部署测试,整体的过程如下:\n\n![image.png](https://dev-media.amazoncloud.cn/dbfe97b95917409bb26f267803d04dd8_image.png)\n\n#### **3.1 环境准备**\n\n安装 Amazon CLI, eksctl,kubectl 和 helm;\n\n安装并配置 Amazon CLI:[https://docs.Amazon.amazon.com/cli/latest/userguide/getting-started-install.html](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)\n\n安装 eksctl:[https://docs.Amazon.amazon.com/eks/latest/userguide/eksctl.html](https://docs.aws.amazon.com/eks/latest/userguide/eksctl.html)\n\n安装 kubectl: [https://docs.Amazon.amazon.com/eks/latest/userguide/install-kubectl.html](https://docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html)\n\nHelm 安装:[https://docs.Amazon.amazon.com/eks/latest/userguide/helm.html](https://docs.aws.amazon.com/eks/latest/userguide/helm.html)\n\n运行以下命令创建 EKS 集群,本实验环境中的集群版本为 V1.21\n\n```\neksctl create cluster --name test-cluster\n```\n\n创建完成后,检查集群连接性,应该会看到两个 Ready 状态的节点\n\n```\nkubectl get node\n```\n\n安装 EBS CSI Driver 插件,将 arn 中的 111122223333 替换为您的账户 ID;\n\n```\neksctl utils associate-iam-oidc-provider \\\n --cluster test-cluster \\\n --approve\neksctl create iamserviceaccount \\\n --name ebs-csi-Controller-sa \\\n --namespace kube-system \\\n --cluster test-cluster \\\n --attach-policy-arn arn:AWS:iam::AWS:policy/service-role/AmazonEBSCSIDriverPolicy \\\n --approve \\\n --role-only \\\n --role-name AmazonEKS_EBS_CSI_DriverRole\neksctl create addon --name AWS-ebs-csi-driver --cluster test-cluster --service-account-role-arn arn:AWS:iam::111122223333:role/AmazonEKS_EBS_CSI_DriverRole --force\n```\n\n作为生产环境 EBS 卷加密前的模拟,接下来创建一个 StorageClass,和一个 Redis 的 StatefulSet:\n\n```\ncurl -LJO https://raw.githubusercontent.com/henghengha/hello-world/master/sc.yml\ncurl -LJO https://raw.githubusercontent.com/henghengha/hello-world/master/sts.yml\ncurl -LJO https://raw.githubusercontent.com/henghengha/hello-world/master/rbac.yml\ncurl -LJO https://raw.githubusercontent.com/henghengha/hello-world/master/cm.yml\nkubectl apply -f sc.yml\nkubectl apply -f rbac.yml\nkubectl apply -f cm.yml\nkubectl apply -f sts.yml\n```\n\n检查部署状态\n\n![image.png](https://dev-media.amazoncloud.cn/28b9dd41f3ce413ca47476e4e2eaa553_image.png)\n\n查看 EBS 卷均未加密\n\n![image.png](https://dev-media.amazoncloud.cn/6f8389b544394e07a114224be8db9360_image.png)\n\n![image.png](https://dev-media.amazoncloud.cn/46dfa3b8cee04510a322157f2bcecb64_image.png)\n\n![image.png](https://dev-media.amazoncloud.cn/903a8dd01e1b4e579b480e19d42a7833_image.png)\n\n#### **3.2 方案实现**\n\nEBS 卷是否加密的属性是在 StorageClass 对象中声明的,通过重建 \n StorageClass 可以保证重新创建出来的 pv 即 EBS 卷是加密的,但是不会作用到之前已经存在的 EBS 卷。本部分将重点展示针对有状态对象在启用存储加密后,如何将原有 Pod 的数据备份,从而确保所有的 EBS 卷都已加密。操作步骤如下:\n\n安装 Snapshot Controller,默认安装在 kube-system 命名空间,确保运行正常,参考:\n\n[https://Amazon.amazon.com/blogs/containers/using-ebs-snapshots-for-persistent-storage-with-your-eks-cluster](https://aws.amazon.com/blogs/containers/using-ebs-snapshots-for-persistent-storage-with-your-eks-cluster)\n\n注意:配置文件中Snapshot-Controller 使用的镜像地址为gcr.io/Kubernetes-staging-sig-storage/Snapshot-Controller:v5.0.1,如果在中国区部署,请将该镜像下载下来,可以上传至 ECR,然后修改配置文件中的地址为中国区 ECR 的地址\n\n生产环境中为了尽可能的减少业务受影响的时间,我们后面将使用相同的配置创建一个新的 StatefulSet 对象,命名为 redis1,根据命名规则(该示例 StatefulSet 中的 volumeClaimTemplates 的名字为 data),redis1 生成的 pvc 名字将是 data-redis1-0 和 data-redis1-1。注意这里先不做真正的 StatefulSet 的部署;\n\n删除原有 StorageClass,并使用如下配置重建:\n\n```\napiVersion: storage.Kubernetes.io/v1\nkind: StorageClass\nmetadata:\n name: ebs-sc\nprovisioner: ebs.csi.AWS.com\n parameters:\n encrypted: 'true'\nvolumeBindingMode: WaitForFirstConsumer\n```\n\n本示例使用托管的 Amazon KMS key aws/ebs 来进行加密,因此在 StorageClass 的配置中未指定具体的 key ID。如果需要使用自定义 KMS 密钥,可以在 StorageClass 中添加 kmsKeyId 配置。与此同时,遵循最小权限原则,还需要给 EBS CSI Driver 授权 KMS 的对应权限。创建 IAM policy 并关联至 AmazonEKS_EBS_CSI_DriverRole,策略内容可参考官方文档进行配置:\n\n[https://docs.aws.amazon.com/eks/latest/userguide/csi-iam-role.html](https://docs.aws.amazon.com/eks/latest/userguide/csi-iam-role.html)\n\n为原有的未加密 EBS 卷创建快照,并进行快照恢复到加密卷。首先,为 pvc data-redis-0 和data-redis-1 创建 Snapshot:\n\na.创建 Volume Snapshot Class;\n\n```\ncat <<\"EOF\" > Snapshot-class.yaml\napiVersion: Snapshot.storage.Kubernetes.io/v1\nkind: VolumeSnapshotClass\nmetadata:\n name: test-snapclass\ndriver: ebs.csi.AWS.com\ndeletionPolicy: Delete\nEOF\nkubectl apply -f Snapshot-class.yaml\n```\n\nb.创建 YAML 文件,volume-Snapshot-redis.yaml 内容如下。注意:VolumeSnapshot 中的 persistentVolumeClaimName 要保证与 Pod 对应的 pvc 名称相同,此处分别为 data-redis-0 和data-redis-1. 部署该文件来创建快照;\n\n```\ncat <<\"EOF\" > volume-Snapshot-redis.yaml\napiVersion: Snapshot.storage.Kubernetes.io/v1\nkind: VolumeSnapshot\nmetadata:\n name: redis-0-Snapshot\nspec:\n volumeSnapshotClassName: test-snapclass\n source:\n persistentVolumeClaimName: data-redis-0\n---\napiVersion: Snapshot.storage.Kubernetes.io/v1\nkind: VolumeSnapshot\nmetadata:\n name: redis-1-Snapshot\nspec:\n volumeSnapshotClassName: test-snapclass\n source:\n persistentVolumeClaimName: data-redis-1\nEOF\n\nkubectl apply -f volume-Snapshot-redis.yaml\n```\n\nc.检查快照信息\n\n![image.png](https://dev-media.amazoncloud.cn/6fa4ae16f28448bf8a1a4148b745cf5a_image.png)\n\n使用快照作为数据源创建 pvc,给下一步新创建的 StatefulSet 中的 Pod 使用:\n\n```\ncat <<\"EOF\" > volume-from-Snapshot.yaml\napiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n name: data-redis1-0\nspec:\n storageClassName: ebs-sc\n dataSource:\n name: redis-0-Snapshot\n kind: VolumeSnapshot\n apiGroup: Snapshot.storage.Kubernetes.io\n accessModes:\n - ReadWriteOnce\n resources:\n requests:\n storage: 4Gi\n---\napiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n name: data-redis1-1\nspec:\n storageClassName: ebs-sc\n dataSource:\n name: redis-1-Snapshot\n kind: VolumeSnapshot\n apiGroup: Snapshot.storage.Kubernetes.io\n accessModes:\n - ReadWriteOnce\n resources:\n requests:\n storage: 4Gi\nEOF\n\nkubectl apply -f volume-from-Snapshot.yaml\n```\n\n![image.png](https://dev-media.amazoncloud.cn/34c23c809f674207aa594afd0dc02376_image.png)\n\n部署新的 StatefulSet redis1,并检查状态:\n\n![image.png](https://dev-media.amazoncloud.cn/d6ee72e0084f4015ba5b56b0503e6fb3_image.png)\n\n![image.png](https://dev-media.amazoncloud.cn/c871972eb99e4288ba880b8b43a45fc0_image.png)\n\n![image.png](https://dev-media.amazoncloud.cn/7c31175d8f3e438b903ba1e6da566016_image.png)\n\n![image.png](https://dev-media.amazoncloud.cn/668b4ac7ceac4f699833ec307a5251a4_image.png)\n\n确认 pv 对应的 EBS 卷已加密,并验证数据备份无误之后可以根据具体的配置情况将连接信息切到新的对象上。如果确认所有流量都已转移到新的 Pod 上,即可删除原有的 StatefulSet 资源。\n\n#### **3.3 环境清除**\n\n删除集群中部署的资源,并删除 pvc 和 pv 对象:\n\n```\nkubectl delete -f sts.yml\nkubectl delete -f rbac.yml\nkubectl delete -f cm.yml\n```\n\n删除集群:\n\n```\neksctl delete iamserviceaccount --cluster test-cluster --name ebs-csi-Controller-sa\neksctl delete nodegroup {your-node-group-name} --cluster test-cluster\neksctl delete cluster --name test-cluster\n```\n\n### **4.小结**\n\n在这个博客里我们针对 EKS 上有状态服务的数据保护安全需求,介绍了结合 KMS 服务,在 EKS 中如何使用 StatefulSet、Snapshot Controller 的方式实现有状态服务的存储加密启用,该方式通过从底层存储解决数据加密存储及迁移,无需应用层介入数据迁移,从而简单有效解决了 EKS 上有状态服务的存储加密需求。\n\n### **5.参考**\n\n[https://docs.aws.amazon.com/zh_cn/eks/latest/userguide/ebs-csi.html](https://docs.aws.amazon.com/zh_cn/eks/latest/userguide/ebs-csi.html)\n\n[https://aws.amazon.com/blogs/containers/using-ebs-snapshots-for-persistent-storage-with-your-eks-cluster/](https://aws.amazon.com/blogs/containers/using-ebs-snapshots-for-persistent-storage-with-your-eks-cluster/)\n\n[https://eksctl.io/](https://eksctl.io/)\n\n### **本篇作者**\n\n![image.png](https://dev-media.amazoncloud.cn/20e80d417c594ee08a08472c9e39c8b7_image.png)\n\n#### **李锐**\n\n亚马逊云科技金融行业资深解决方案架构师,负责基于亚马逊的云计算方案架构咨询和设计,擅长安全领域。加入 Amazon 之前,曾任职互联网公司,拥有多年金融云产品及架构设计经验。\n\n![image.png](https://dev-media.amazoncloud.cn/5acecd05938d48eeb4c1257e051d1b4b_image.png)\n\n#### **孙彦巧**\n\n亚马逊云科技金融行业解决方案架构师,负责云计算解决方案的架构设计和咨询。有多年 Amazon 从业经验,热衷于容器、微服务架构及 Devops 方面的研究和应用。","render":"<h3><a id=\"1_0\"></a><strong>1.背景</strong></h3>\n<p>用户通过 Deployment、Replication Controller 可以方便地在 Kubernetes 中部署一套高可用、可扩展的分布式无状态服务。这类应用不在本地存储数据,通过简单的负载均衡策略可实现请求分发。Deployment、Replication Controller 是为无状态服务而设计的,它们中 Pod 的名称、主机名、存储都是不稳定的,且 Pod 的启动、销毁顺序随机,并不适合数据库这样的有状态应用。</p>\n<p>为此,Kubernetes 推出了面向有状态服务的工作负载 StatefulSet,其管理的 Pod 具有如下特点:</p>\n<ul>\n<li>唯一性 – 对于包含 N 个副本的 StatefulSet,每个 Pod 会被分配一个 [0,N] 范围内的唯一序号。</li>\n<li>顺序性 – StatefulSet 中 Pod 的启动、更新、销毁默认都是按顺序进行的。</li>\n<li>稳定的网络身份标识 – Pod 的主机名、DNS 地址不会随着 Pod 被重新调度而发生变化。</li>\n<li>稳定的持久化存储 – 当 Pod 被重新调度后,仍然能挂载原有的 PersistentVolume,保证了数据的完整性和一致性。</li>\n</ul>\n<p>本文针对有状态服务业务场景的数据保护安全需求,旨在从 EKS 内部结合采用 StatefulSet、Snapshot Controller 的方式实现有状态服务的存储加密启用,并在测试环境下验证了本方案的可行性。</p>\n<h3><a id=\"2_13\"></a><strong>2.场景案例</strong></h3>\n<h4><a id=\"21__15\"></a><strong>2.1 场景架构</strong></h4>\n<p>业务场景:</p>\n<p>该业务平台构建在 Amazon 云上,服务客群主要为全国各医院,核心业务价值在于简化医院采购结算环节,帮助医院方降本增效。该平台通过结合医院,医院供应商提供的订单、结算单、发票信息,进行三单验证,并集成银行侧银企直连,进而实现医院的快速透明支付结算、有效提升采购效率。</p>\n<p>部署架构:</p>\n<p>之前客户在本地 IDC 的 Kubernetes 集群中除了部署了无状态的微服务应用外,还部署了有状态的中间件服务,包括 Redis、Kafka、 MySQL,存储使用的是本地磁盘。由于自建 Kubernetes 集群的升级、管理、安全等各方面的运维工作复杂,需要耗费大量的运维资源;并且在 Kubernetes 集群中运行有状态的应用,特别是业务数据库,存在很大的潜在风险,因而在迁移至 Amazon 云上的过程中,接受我方建议做了相应的架构优化及调整:</p>\n<ul>\n<li>自建 Kubernetes 调整为托管的 Amazon EKS 服务</li>\n<li>从 Kubernetes 集群中移除关系数据库 MySQL,替换为托管的 RDS Aurora 服务</li>\n<li>EKS 中各业务组件采用无状态部署</li>\n<li>EKS 中保留 Redis 和 Kafka 等中间件,并采用有状态部署,持久化采用 EBS</li>\n<li>EKS 中采用 Amazon EFS 作为共享存储,实现存储的安全高可用</li>\n</ul>\n<p>架构示意图:</p>\n<p><img src=\"https://dev-media.amazoncloud.cn/b6888f53b13f410e99720c852ce0242f_image.png\" alt=\"image.png\" /></p>\n<h4><a id=\"22__35\"></a><strong>2.2 问题及解决方案</strong></h4>\n<p>问题描述:</p>\n<p>由于涉及到订单、结算单、发票等敏感数据的传输及存储,在业务上线后不久,客户希望加强云上数据安全保护,结合 KMS 对持久化数据进行自动存储加密。之前有状态服务 Redis、Kafka 等在部署上线时所挂载的 EBS 并未启用加密,由于 EBS 在初始未启用加密的情况下无法直接开启加密(见下面技术约束示意图),那么在对业务的影响降至最低的前提下如何实现 EBS 存储加密的开启及业务的平滑迁移呢?</p>\n<p>技术约束示意图:</p>\n<p>初始未加密 EBS 无法直接启用加密,需对未加密 EBS 做快照,再将未加密快照还原成加密 EBS,如下图所示</p>\n<p><img src=\"https://dev-media.amazoncloud.cn/7fc2f3092dae44ebbcc641ad3459aa1b_image.png\" alt=\"image.png\" /></p>\n<p>解决方案:</p>\n<ul>\n<li>方案一:需应用层做数据迁移,EKS 中原 Redis 等有状态服务保持不动,采用 StatefulSet 方式新建 Redis 等有状态服务并启用 EBS 存储加密,在应用层做数据迁移(将敏感数据从原服务未加密 EBS 迁移至新建服务已加密 EBS)后将中间件服务切换至新建 Redis 等有状态服务并下线原服务以实现有状态服务的存储加密;</li>\n<li>方案二:不涉及应用层数据迁移,从 Amazon 控制台直接对 EBS 进行相关操作,将未加密 EBS 制作快照后还原成加密 EBS,并将加密 EBS 静态挂载回正确的 Redis 等有状态服务;</li>\n<li>方案三:不涉及应用层数据迁移,从 EKS 内部进行相关操作,首先启用 Snapshot Controller, 之后通过该 Controller 对未加密 pvc 制作快照后还原成加密 pvc,新建 Redis StatefulSet 并挂载加密 pvc,引流至新建 Redis 测试一切正常后做服务迁移;</li>\n</ul>\n<p>由于客户不希望应用层做数据迁移,以及从 Amazon 控制台操作后如何正确静态挂载回对应的 Pod 存在较大风险,最终采用了方案三进行了相应的测试及部署,并成功启用了 EKS 下 EBS 的存储加密。</p>\n<h3><a id=\"3_55\"></a><strong>3.部署与测试</strong></h3>\n<p>接下来我们会按照上述方案三进行部署测试,整体的过程如下:</p>\n<p><img src=\"https://dev-media.amazoncloud.cn/dbfe97b95917409bb26f267803d04dd8_image.png\" alt=\"image.png\" /></p>\n<h4><a id=\"31__61\"></a><strong>3.1 环境准备</strong></h4>\n<p>安装 Amazon CLI, eksctl,kubectl 和 helm;</p>\n<p>安装并配置 Amazon CLI:<a href=\"https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html\" target=\"_blank\">https://docs.Amazon.amazon.com/cli/latest/userguide/getting-started-install.html</a></p>\n<p>安装 eksctl:<a href=\"https://docs.aws.amazon.com/eks/latest/userguide/eksctl.html\" target=\"_blank\">https://docs.Amazon.amazon.com/eks/latest/userguide/eksctl.html</a></p>\n<p>安装 kubectl: <a href=\"https://docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html\" target=\"_blank\">https://docs.Amazon.amazon.com/eks/latest/userguide/install-kubectl.html</a></p>\n<p>Helm 安装:<a href=\"https://docs.aws.amazon.com/eks/latest/userguide/helm.html\" target=\"_blank\">https://docs.Amazon.amazon.com/eks/latest/userguide/helm.html</a></p>\n<p>运行以下命令创建 EKS 集群,本实验环境中的集群版本为 V1.21</p>\n<pre><code class=\"lang-\">eksctl create cluster --name test-cluster\n</code></pre>\n<p>创建完成后,检查集群连接性,应该会看到两个 Ready 状态的节点</p>\n<pre><code class=\"lang-\">kubectl get node\n</code></pre>\n<p>安装 EBS CSI Driver 插件,将 arn 中的 111122223333 替换为您的账户 ID;</p>\n<pre><code class=\"lang-\">eksctl utils associate-iam-oidc-provider \\\n --cluster test-cluster \\\n --approve\neksctl create iamserviceaccount \\\n --name ebs-csi-Controller-sa \\\n --namespace kube-system \\\n --cluster test-cluster \\\n --attach-policy-arn arn:AWS:iam::AWS:policy/service-role/AmazonEBSCSIDriverPolicy \\\n --approve \\\n --role-only \\\n --role-name AmazonEKS_EBS_CSI_DriverRole\neksctl create addon --name AWS-ebs-csi-driver --cluster test-cluster --service-account-role-arn arn:AWS:iam::111122223333:role/AmazonEKS_EBS_CSI_DriverRole --force\n</code></pre>\n<p>作为生产环境 EBS 卷加密前的模拟,接下来创建一个 StorageClass,和一个 Redis 的 StatefulSet:</p>\n<pre><code class=\"lang-\">curl -LJO https://raw.githubusercontent.com/henghengha/hello-world/master/sc.yml\ncurl -LJO https://raw.githubusercontent.com/henghengha/hello-world/master/sts.yml\ncurl -LJO https://raw.githubusercontent.com/henghengha/hello-world/master/rbac.yml\ncurl -LJO https://raw.githubusercontent.com/henghengha/hello-world/master/cm.yml\nkubectl apply -f sc.yml\nkubectl apply -f rbac.yml\nkubectl apply -f cm.yml\nkubectl apply -f sts.yml\n</code></pre>\n<p>检查部署状态</p>\n<p><img src=\"https://dev-media.amazoncloud.cn/28b9dd41f3ce413ca47476e4e2eaa553_image.png\" alt=\"image.png\" /></p>\n<p>查看 EBS 卷均未加密</p>\n<p><img src=\"https://dev-media.amazoncloud.cn/6f8389b544394e07a114224be8db9360_image.png\" alt=\"image.png\" /></p>\n<p><img src=\"https://dev-media.amazoncloud.cn/46dfa3b8cee04510a322157f2bcecb64_image.png\" alt=\"image.png\" /></p>\n<p><img src=\"https://dev-media.amazoncloud.cn/903a8dd01e1b4e579b480e19d42a7833_image.png\" alt=\"image.png\" /></p>\n<h4><a id=\"32__127\"></a><strong>3.2 方案实现</strong></h4>\n<p>EBS 卷是否加密的属性是在 StorageClass 对象中声明的,通过重建<br />\nStorageClass 可以保证重新创建出来的 pv 即 EBS 卷是加密的,但是不会作用到之前已经存在的 EBS 卷。本部分将重点展示针对有状态对象在启用存储加密后,如何将原有 Pod 的数据备份,从而确保所有的 EBS 卷都已加密。操作步骤如下:</p>\n<p>安装 Snapshot Controller,默认安装在 kube-system 命名空间,确保运行正常,参考:</p>\n<p><a href=\"https://aws.amazon.com/blogs/containers/using-ebs-snapshots-for-persistent-storage-with-your-eks-cluster\" target=\"_blank\">https://Amazon.amazon.com/blogs/containers/using-ebs-snapshots-for-persistent-storage-with-your-eks-cluster</a></p>\n<p>注意:配置文件中Snapshot-Controller 使用的镜像地址为gcr.io/Kubernetes-staging-sig-storage/Snapshot-Controller:v5.0.1,如果在中国区部署,请将该镜像下载下来,可以上传至 ECR,然后修改配置文件中的地址为中国区 ECR 的地址</p>\n<p>生产环境中为了尽可能的减少业务受影响的时间,我们后面将使用相同的配置创建一个新的 StatefulSet 对象,命名为 redis1,根据命名规则(该示例 StatefulSet 中的 volumeClaimTemplates 的名字为 data),redis1 生成的 pvc 名字将是 data-redis1-0 和 data-redis1-1。注意这里先不做真正的 StatefulSet 的部署;</p>\n<p>删除原有 StorageClass,并使用如下配置重建:</p>\n<pre><code class=\"lang-\">apiVersion: storage.Kubernetes.io/v1\nkind: StorageClass\nmetadata:\n name: ebs-sc\nprovisioner: ebs.csi.AWS.com\n parameters:\n encrypted: 'true'\nvolumeBindingMode: WaitForFirstConsumer\n</code></pre>\n<p>本示例使用托管的 Amazon KMS key aws/ebs 来进行加密,因此在 StorageClass 的配置中未指定具体的 key ID。如果需要使用自定义 KMS 密钥,可以在 StorageClass 中添加 kmsKeyId 配置。与此同时,遵循最小权限原则,还需要给 EBS CSI Driver 授权 KMS 的对应权限。创建 IAM policy 并关联至 AmazonEKS_EBS_CSI_DriverRole,策略内容可参考官方文档进行配置:</p>\n<p><a href=\"https://docs.aws.amazon.com/eks/latest/userguide/csi-iam-role.html\" target=\"_blank\">https://docs.aws.amazon.com/eks/latest/userguide/csi-iam-role.html</a></p>\n<p>为原有的未加密 EBS 卷创建快照,并进行快照恢复到加密卷。首先,为 pvc data-redis-0 和data-redis-1 创建 Snapshot:</p>\n<p>a.创建 Volume Snapshot Class;</p>\n<pre><code class=\"lang-\">cat <<"EOF" > Snapshot-class.yaml\napiVersion: Snapshot.storage.Kubernetes.io/v1\nkind: VolumeSnapshotClass\nmetadata:\n name: test-snapclass\ndriver: ebs.csi.AWS.com\ndeletionPolicy: Delete\nEOF\nkubectl apply -f Snapshot-class.yaml\n</code></pre>\n<p>b.创建 YAML 文件,volume-Snapshot-redis.yaml 内容如下。注意:VolumeSnapshot 中的 persistentVolumeClaimName 要保证与 Pod 对应的 pvc 名称相同,此处分别为 data-redis-0 和data-redis-1. 部署该文件来创建快照;</p>\n<pre><code class=\"lang-\">cat <<"EOF" > volume-Snapshot-redis.yaml\napiVersion: Snapshot.storage.Kubernetes.io/v1\nkind: VolumeSnapshot\nmetadata:\n name: redis-0-Snapshot\nspec:\n volumeSnapshotClassName: test-snapclass\n source:\n persistentVolumeClaimName: data-redis-0\n---\napiVersion: Snapshot.storage.Kubernetes.io/v1\nkind: VolumeSnapshot\nmetadata:\n name: redis-1-Snapshot\nspec:\n volumeSnapshotClassName: test-snapclass\n source:\n persistentVolumeClaimName: data-redis-1\nEOF\n\nkubectl apply -f volume-Snapshot-redis.yaml\n</code></pre>\n<p>c.检查快照信息</p>\n<p><img src=\"https://dev-media.amazoncloud.cn/6fa4ae16f28448bf8a1a4148b745cf5a_image.png\" alt=\"image.png\" /></p>\n<p>使用快照作为数据源创建 pvc,给下一步新创建的 StatefulSet 中的 Pod 使用:</p>\n<pre><code class=\"lang-\">cat <<"EOF" > volume-from-Snapshot.yaml\napiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n name: data-redis1-0\nspec:\n storageClassName: ebs-sc\n dataSource:\n name: redis-0-Snapshot\n kind: VolumeSnapshot\n apiGroup: Snapshot.storage.Kubernetes.io\n accessModes:\n - ReadWriteOnce\n resources:\n requests:\n storage: 4Gi\n---\napiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n name: data-redis1-1\nspec:\n storageClassName: ebs-sc\n dataSource:\n name: redis-1-Snapshot\n kind: VolumeSnapshot\n apiGroup: Snapshot.storage.Kubernetes.io\n accessModes:\n - ReadWriteOnce\n resources:\n requests:\n storage: 4Gi\nEOF\n\nkubectl apply -f volume-from-Snapshot.yaml\n</code></pre>\n<p><img src=\"https://dev-media.amazoncloud.cn/34c23c809f674207aa594afd0dc02376_image.png\" alt=\"image.png\" /></p>\n<p>部署新的 StatefulSet redis1,并检查状态:</p>\n<p><img src=\"https://dev-media.amazoncloud.cn/d6ee72e0084f4015ba5b56b0503e6fb3_image.png\" alt=\"image.png\" /></p>\n<p><img src=\"https://dev-media.amazoncloud.cn/c871972eb99e4288ba880b8b43a45fc0_image.png\" alt=\"image.png\" /></p>\n<p><img src=\"https://dev-media.amazoncloud.cn/7c31175d8f3e438b903ba1e6da566016_image.png\" alt=\"image.png\" /></p>\n<p><img src=\"https://dev-media.amazoncloud.cn/668b4ac7ceac4f699833ec307a5251a4_image.png\" alt=\"image.png\" /></p>\n<p>确认 pv 对应的 EBS 卷已加密,并验证数据备份无误之后可以根据具体的配置情况将连接信息切到新的对象上。如果确认所有流量都已转移到新的 Pod 上,即可删除原有的 StatefulSet 资源。</p>\n<h4><a id=\"33__257\"></a><strong>3.3 环境清除</strong></h4>\n<p>删除集群中部署的资源,并删除 pvc 和 pv 对象:</p>\n<pre><code class=\"lang-\">kubectl delete -f sts.yml\nkubectl delete -f rbac.yml\nkubectl delete -f cm.yml\n</code></pre>\n<p>删除集群:</p>\n<pre><code class=\"lang-\">eksctl delete iamserviceaccount --cluster test-cluster --name ebs-csi-Controller-sa\neksctl delete nodegroup {your-node-group-name} --cluster test-cluster\neksctl delete cluster --name test-cluster\n</code></pre>\n<h3><a id=\"4_275\"></a><strong>4.小结</strong></h3>\n<p>在这个博客里我们针对 EKS 上有状态服务的数据保护安全需求,介绍了结合 KMS 服务,在 EKS 中如何使用 StatefulSet、Snapshot Controller 的方式实现有状态服务的存储加密启用,该方式通过从底层存储解决数据加密存储及迁移,无需应用层介入数据迁移,从而简单有效解决了 EKS 上有状态服务的存储加密需求。</p>\n<h3><a id=\"5_279\"></a><strong>5.参考</strong></h3>\n<p><a href=\"https://docs.aws.amazon.com/zh_cn/eks/latest/userguide/ebs-csi.html\" target=\"_blank\">https://docs.aws.amazon.com/zh_cn/eks/latest/userguide/ebs-csi.html</a></p>\n<p><a href=\"https://aws.amazon.com/blogs/containers/using-ebs-snapshots-for-persistent-storage-with-your-eks-cluster/\" target=\"_blank\">https://aws.amazon.com/blogs/containers/using-ebs-snapshots-for-persistent-storage-with-your-eks-cluster/</a></p>\n<p><a href=\"https://eksctl.io/\" target=\"_blank\">https://eksctl.io/</a></p>\n<h3><a id=\"_287\"></a><strong>本篇作者</strong></h3>\n<p><img src=\"https://dev-media.amazoncloud.cn/20e80d417c594ee08a08472c9e39c8b7_image.png\" alt=\"image.png\" /></p>\n<h4><a id=\"_291\"></a><strong>李锐</strong></h4>\n<p>亚马逊云科技金融行业资深解决方案架构师,负责基于亚马逊的云计算方案架构咨询和设计,擅长安全领域。加入 Amazon 之前,曾任职互联网公司,拥有多年金融云产品及架构设计经验。</p>\n<p><img src=\"https://dev-media.amazoncloud.cn/5acecd05938d48eeb4c1257e051d1b4b_image.png\" alt=\"image.png\" /></p>\n<h4><a id=\"_297\"></a><strong>孙彦巧</strong></h4>\n<p>亚马逊云科技金融行业解决方案架构师,负责云计算解决方案的架构设计和咨询。有多年 Amazon 从业经验,热衷于容器、微服务架构及 Devops 方面的研究和应用。</p>\n"}