1.背景

用户通过 Deployment, Replication Controller 可以方便地在 Kubernetes 中部署一套高可用、可扩展的分布式无状态服务。这类应用不在本地存储数据,通过简单的负载均衡策略可实现请求分发。

Deployment 以及 Replication Controller 是为无状态服务而设计的,它们中 Pod 的名称、主机名、存储都是不稳定的,且 Pod 的启动、销毁顺序随机,并不适合数据库这样的有状态应用。

亚马逊云科技开发者社区为开发者们提供全球的开发技术资源。这里有技术文档、开发案例、技术专栏、培训视频、活动与竞赛等。帮助中国开发者对接世界最前沿技术,观点,和项目,并将中国优秀开发者或技术推荐给全球云社区。如果你还没有关注/收藏,看到这里请一定不要匆匆划过,点这里让它成为你的技术宝库!

为此,Kubernetes 推出了面向有状态服务的工作负载 StatefulSet,管理的 Pod 具有如下特点:

  • 唯一性 – 对于包含 N 个副本的 StatefulSet,每个 Pod 会被分配一个 [0,N] 范围内的唯一序号。
  • 顺序性 – StatefulSet 中 Pod 的启动、更新、销毁默认都是按顺序进行的。
  • 稳定的网络身份标识 – Pod 的主机名、DNS 地址不会随着 Pod 被重新调度而发生变化。
  • 稳定的持久化存储 – 当 Pod 被重新调度后,仍然能挂载原有的 PersistentVolume,保证了数据的完整性和一致性。

本文针对有状态服务业务场景的数据保护安全需求,旨在从 Amazon EKS 内部结合采用 StatefulSet, Snapshot Controller 的方式实现有状态服务的存储加密启用,并在测试环境下验证了本方案的可行性。

2.场景案例

2.1 场景架构

业务场景:

该业务平台构建在亚马逊云上,服务客群主要为全国各医院,核心业务价值在于简化医院采购结算环节,帮助医院方降本增效。该平台通过结合医院,医院供应商提供的订单、结算单、发票信息,进行三单验证,并集成银行侧银企直连,进而实现医院的快速透明支付结算、有效提升采购效率。

部署架构:

之前客户在本地 IDC 的 Kubernetes 集群中除了部署了无状态的微服务应用外,还部署了有状态的中间件服务,包括 Redis, Kafka, MySQL,存储使用的是本地磁盘。由于自建 Kubernetes 集群的升级、管理、安全等各方面的运维工作复杂,需要耗费大量的运维资源;并且在 Kubernetes 集群中运行有状态的应用,特别是业务数据库,存在很大的潜在风险,因而在迁移至亚马逊云上的过程中,接受我方建议做了相应的架构优化及调整:

自建 Kubernetes 调整为托管的 EKS 服务;

  • 从 Kubernetes 集群中移除关系数据库 MySQL,替换为托管的 RDS Aurora 服务;
  • EKS 中各业务组件采用无状态部署;
  • EKS 中保留 Redis 和 Kafka 等中间件,并采用有状态部署,持久化采用 EBS;
  • EKS 中采用 EFS 作为共享存储,实现存储的安全高可用。

架构示意图:

2.2 问题及解决方案

问题描述:

由于涉及到订单、结算单、发票等敏感数据的传输及存储,在业务上线后不久,客户希望加强云上数据安全保护,结合 KMS 对持久化数据进行自动存储加密。之前有状态服务 Redis, Kafka 等在部署上线时所挂载的 EBS 并未启用加密,由于 EBS 在初始未启用加密的情况下无法直接开启加密(见下面技术约束示意图),那么在对业务的影响降至最低的前提下如何实现 EBS 存储加密的开启及业务的平滑迁移呢?

技术约束示意图:

初始未加密EBS无法直接启用加密,需对未加密 EBS 做快照,再将未加密快照还原成加密 EBS,如下图所示:

解决方案:

  • 方案一:需应用层做数据迁移,EKS 中原 Redis 等有状态服务保持不动,采用 StatefulSet 方式新建 Redis 等有状态服务并启用 EBS 存储加密,在应用层做数据迁移(将敏感数据从原服务未加密 EBS 迁移至新建服务已加密 EBS)后将中间件服务切换至新建 Redis 等有状态服务并下线原服务以实现有状态服务的存储加密;
  • 方案二:不涉及应用层数据迁移,从亚马逊云科技控制台直接对 EBS 进行相关操作,将未加密EBS制作快照后还原成加密 EBS,并将加密 EBS 静态挂载回正确的 Redis 等有状态服务;
  • 方案三:不涉及应用层数据迁移,从 EKS 内部进行相关操作,首先启用 Snapshot Controller,之后通过该 Controller 对未加密 pvc 制作快照后还原成加密pvc,新建 Redis StatefulSet 并挂载加密 pvc,引流至新建 Redis 测试一切正常后做服务迁移;

由于客户不希望应用层做数据迁移,以及从亚马逊云科技控制台操作后如何正确静态挂载回对应的 Pod 存在较大风险,最终采用了方案三进行了相应的测试及部署,并成功启用了 EKS 下 EBS 的存储加密。

3.部署与测试

接下来我们会按照上述方案三进行部署测试,整体的过程如下:

3.1 环境准备

安装 Amazon CLI, eksctl, kubectl 和 helm; 安装并配置 Amazon CLI: https://docs.AWS.amazon.com/cli/latest/userguide/getting-started-install.html 安装eksctl: https://docs.AWS.amazon.com/eks/latest/userguide/eksctl.html 安装kubectl: https://docs.AWS.amazon.com/eks/latest/userguide/install-kubectl.html Helm 安装: https://docs.AWS.amazon.com/eks/latest/userguide/helm.html 运行以下命令创建 EKS 集群,本实验环境中的集群版本为 V1.21

eksctl create cluster --name test-cluster

创建完成后,检查集群连接性,应该会看到两个 Ready 状态的节点;

kubectl get node

安装 EBS CSI Driver 插件,将 arn 中的 111122223333 替换为您的账户 ID;

eksctl utils associate-iam-oidc-provider \
  --cluster test-cluster \
  --approve
eksctl create iamserviceaccount \
  --name ebs-csi-Controller-sa \
  --namespace kube-system \
  --cluster test-cluster \
  --attach-policy-arn arn:AWS:iam::AWS:policy/service-role/AmazonEBSCSIDriverPolicy \
  --approve \
  --role-only \
  --role-name AmazonEKS_EBS_CSI_DriverRole
eksctl create addon --name AWS-ebs-csi-driver --cluster test-cluster --service-account-role-arn arn:AWS:iam::111122223333:role/AmazonEKS_EBS_CSI_DriverRole --force

作为生产环境 EBS 卷加密前的模拟,接下来创建一个 StorageClass,和一个 Redis 的 StatefulSet:

curl -LJO https://raw.githubusercontent.com/henghengha/hello-world/master/sc.yml
curl -LJO https://raw.githubusercontent.com/henghengha/hello-world/master/sts.yml
curl -LJO https://raw.githubusercontent.com/henghengha/hello-world/master/rbac.yml
curl -LJO https://raw.githubusercontent.com/henghengha/hello-world/master/cm.yml
kubectl apply -f sc.yml
kubectl apply -f rbac.yml
kubectl apply -f cm.yml
kubectl apply -f sts.yml

检查部署状态

查看 EBS 卷均未加密

3.2 方案实现

EBS 卷是否加密的属性是在 StorageClass 对象中声明的,通过重建 StorageClass 可以保证重新创建出来的 pv 即 EBS 卷是加密的,但是不会作用到之前已经存在的 EBS 卷。本部分将重点展示针对有状态对象在启用存储加密后,如何将原有 Pod 的数据备份,从而确保所有的 EBS 卷都已加密。操作步骤如下:

安装 Snapshot Controller,默认安装在 kube-system 命名空间,确保运行正常,参考:

https://aws.amazon.com/cn/blogs/containers/using-ebs-snapshots-for-persistent-storage-with-your-eks-cluster/

注意:配置文件中 Snapshot-Controller 使用的镜像地址为 gcr.io/Kubernetes-staging-sig-storage/Snapshot-Controller:v5.0.1,如果在中国区部署,请将该镜像下载下来,可以上传至 ECR,然后修改配置文件中的地址为中国区 ECR 的地址。

生产环境中为了尽可能的减少业务受影响的时间,我们后面将使用相同的配置创建一个新的 StatefulSet 对象,命名为 redis1,根据命名规则(该示例 StatefulSet 中的 volumeClaimTemplates 的名字为 data),redis1 生成的 pvc 名字将是 data-redis1-0 和 data-redis1-1。注意这里先不做真正的 StatefulSet 的部署;

删除原有 StorageClass,并使用如下配置重建:

apiVersion: storage.Kubernetes.io/v1
kind: StorageClass
metadata:
  name: ebs-sc
provisioner: ebs.csi.AWS.com
  parameters:
    encrypted: 'true'
volumeBindingMode: WaitForFirstConsumer

本示例使用托管的 Amazon KMS Key aws/ebs 来进行加密,因此在 StorageClass 的配置中未指定具体的 Key ID。如果需要使用自定义 KMS 密钥,可以在 StorageClass 中添加 KMS Key ID 配置。与此同时,遵循最小权限原则,还需要给 EBS CSI Driver 授权 KMS 的对应权限。创建 IAM policy 并关联至 AmazonEKS_EBS_CSI_DriverRole,策略内容可参考官方文档进行配置: https://docs.aws.amazon.com/eks/latest/userguide/csi-iam-role.html

为原有的未加密 EBS 卷创建快照,并进行快照恢复到加密卷。首先,为 pvc data-redis-0 和data-redis-1 创建 Snapshot:

a.创建 Volume Snapshot Class;

cat <<"EOF" > Snapshot-class.yaml
apiVersion: Snapshot.storage.Kubernetes.io/v1
kind: VolumeSnapshotClass
metadata:
  name: test-snapclass
driver: ebs.csi.AWS.com
deletionPolicy: Delete
EOF
kubectl apply -f Snapshot-class.yaml

b.创建 YAML 文件,volume-Snapshot-redis.yaml 内容如下;

注意:VolumeSnapshot 中的 persistentVolumeClaimName 要保证与 Pod 对应的 pvc 名称相同,此处分别为 data-redis-0 和 data-redis-1。部署该文件来创建快照;

cat <<"EOF" > volume-Snapshot-redis.yaml
apiVersion: Snapshot.storage.Kubernetes.io/v1
kind: VolumeSnapshot
metadata:
  name: redis-0-Snapshot
spec:
  volumeSnapshotClassName: test-snapclass
  source:
    persistentVolumeClaimName: data-redis-0
---
apiVersion: Snapshot.storage.Kubernetes.io/v1
kind: VolumeSnapshot
metadata:
  name: redis-1-Snapshot
spec:
  volumeSnapshotClassName: test-snapclass
  source:
    persistentVolumeClaimName: data-redis-1
EOF

kubectl apply -f volume-Snapshot-redis.yaml

c.检查快照信息;

使用快照作为数据源创建 pvc,给下一步新创建的 StatefulSet 中的 Pod 使用:

cat <<"EOF" > volume-from-Snapshot.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data-redis1-0
spec:
  storageClassName: ebs-sc
  dataSource:
    name: redis-0-Snapshot
    kind: VolumeSnapshot
    apiGroup: Snapshot.storage.Kubernetes.io
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 4Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data-redis1-1
spec:
  storageClassName: ebs-sc
  dataSource:
    name: redis-1-Snapshot
    kind: VolumeSnapshot
    apiGroup: Snapshot.storage.Kubernetes.io
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 4Gi
EOF

kubectl apply -f volume-from-Snapshot.yaml

部署新的 StatefulSet redis1,并检查状态:

确认 pv 对应的 EBS 卷已加密,并验证数据备份无误之后可以根据具体的配置情况将连接信息切到新的对象上。

如果确认所有流量都已转移到新的 Pod 上,即可删除原有的 StatefulSet 资源。

3.3 环境清除

删除集群中部署的资源,并删除 pvc 和 pv 对象:

kubectl delete -f sts.yml
kubectl delete -f rbac.yml
kubectl delete -f cm.yml

删除集群:

eksctl delete iamserviceaccount --cluster test-cluster --name ebs-csi-Controller-sa
eksctl delete nodegroup {your-node-group-name} --cluster test-cluster
eksctl delete cluster --name test-cluster

4.参考

https://docs.aws.amazon.com/zh_cn/eks/latest/userguide/ebs-csi.html

https://aws.amazon.com/blogs/containers/using-ebs-snapshots-for-persistent-storage-with-your-eks-cluster/

https://eksctl.io/

5.小结

本文我们针对 Amazon EKS 上有状态服务的数据保护安全需求,介绍了结合 KMS 服务,在 Amazon EKS 中如何使用 StatefulSet, Snapshot Controller 的方式实现有状态服务的存储加密启用,该方式通过从底层存储解决数据加密存储及迁移,无需应用层介入数据迁移,从而简单有效解决了 Amazon EKS 上有状态服务的存储加密需求。

本篇作者

李锐亚马逊云科技金融行业资深解决方案架构师,负责基于亚马逊的云计算方案架构咨询和设计,擅长安全领域。曾任职互联网公司,拥有多年金融云产品及架构设计经验。

孙彦巧 亚马逊云科技金融行业解决方案架构师,负责云计算解决方案的架构设计和咨询。有多年亚马逊云科技从业经验,热衷于容器、微服务架构及 Devops 方面的研究和应用。

阅读原文

文章来源:https://dev.amazoncloud.cn/column/article/631478a1323d8e18f89...


亚马逊云开发者
2.9k 声望9.6k 粉丝

亚马逊云开发者社区是面向开发者交流与互动的平台。在这里,你可以分享和获取有关云计算、人工智能、IoT、区块链等相关技术和前沿知识,也可以与同行或爱好者们交流探讨,共同成长。