在 Kubernetes 环境中管理本地存储资源时,实例存储(Instance Store)因其高性能和低延迟特性成为关键选择。实例存储为您的 EC2 实例提供临时块级存储。此存储由已物理附加到主机的磁盘提供。实例存储非常适合临时存储频繁更改的信息,例如缓冲区、缓存、Scratch 数据和其他临时内容。它还可用于存储您在一组实例中复制的临时数据,例如负载均衡的 Web 服务器池。
实例存储由一个或多个显示为块储存设备的实例存储卷组成。实例存储的大小以及可用设备的数量因实例类型和实例大小而异。本文章将讨论使用 HAQM EKS 的 Pod 中使用实例存储,包括通过托管节点组和预启动命令初始化,通过部署本地卷静态配置器 CSI 驱动程序创建 PVC 以及通过 hostpath 使用。 客户可能希望利用本地 NVMe 存储卷实现比通用 HAQM EBS 启动卷更高的性能。
请注意,实例存储卷用于临时存储,当 HAQM EC2 实例停止或终止时,数据就会丢失。 要在实例的整个生命周期内保持实例存储卷中存储的数据,需要在应用层处理复制。
实例存储特性解析
核心特点
- 临时存储性质:数据在实例停止/终止时自动清除
- 高性能优势:NVMe SSD 提供比 EBS 卷更低的延迟
- 物理绑定特性:存储设备直接连接到宿主机物理硬件
- 容量扩展方案:支持通过 RAID-0 实现多盘聚合
方案对比分析
本地盘的挂载有两种方式实现:
- 使用 HostPath 的方式访问:在 Node 初始化的时候挂载 Local SSD,并且 Pod 中指定 HostPath 访问 Local SSD。
- 使用 PVC 的方式访问:Kubernetes PersistentVolume(PV)是一种集群资源,它定义了单个存储卷的功能和位置。一个 PersistentVolumeClaim(PVC)定义了对一定量存储资源的请求。当 Kubernetes Pod 需要存储时,它会引用一个 PVC。然后,Kubernetes 会将 PVC 与可用的 PV 匹配,如果 CSI 驱动程序支持动态调配,则会自动调配新的 PV。
这里我们对两种使用方式进行分析,可以根据这个分析选择适合自己的使用方案:
|
评估维度 |
直接挂载本地盘供 Pod 访问 |
PVC 动态绑定 |
1 |
配置复杂度 |
简单,直接在 Node 启动时初始化,Pod 中指定路径 |
较复杂,需要设置 Static Provisioner,但提供标准化配置 |
2 |
存储可见性 |
直接可见,易于调试,但缺乏抽象 |
通过 PV/PVC 提供更好的抽象,管理性更强 |
3 |
维护成本 |
直接访问维护简单,但大规模部署难度大 |
统一管理接口便于大规模部署,但需维护额外组件 |
4 |
容量隔离 |
可直接控制分配,但缺乏原生隔离机制 |
通过 PVC 更好控制和隔离容量 |
5 |
耦合性 |
与主机路径紧密耦合,可移植性较差 |
降低与具体主机耦合,提高抽象级别 |
6 |
亲和性 |
可直接控制 Pod 与 Node 亲和性,但管理繁琐 |
通过 PV/PVC 更好管理亲和性 |
7 |
监控指标 |
可用主机级监控工具,缺乏 K8s 集成指标 |
可利用 K8s 原生存储类监控指标 |
8 |
适用场景 |
小规模部署、直接访问需求、高性能要求 |
大规模部署、需统一管理和抽象、高存储管理要求 |
方案实现介绍
接下来,让我们一起探索这两种实例存储本地盘的使用方式。
方式一:直接挂载本地盘供 Pod 访问
实现思路
我们需要配置 HAQM EKS 托管节点组,以启动带有 fast-disk-node 标签的 EC2 实例,并在启动时将 NVMe 实例存储卷挂载到 /mnt/fast-disks 目录。
本文介绍了两种选项:
- 多个持久卷,每个 NVMe 实例存储卷对应一个
- 一个单独的持久卷 RAID-0 阵列,跨所有 NVMe 实例存储卷
这两个选项都为您的 Kubernetes Pod 提供高随机 I/O 性能和极低延迟的存储卷。两种方法根据使用场景提供不同的选择。
在选项 1 中,为每个 NVMe 实例存储卷创建一个持久卷。本博客中使用的 i4g.8xlarge 实例有 2 个 NVMe 卷。当多个 Pod 需要快速存储时,选项 1 将创建 2 个持久卷。
选项 2 使用 RAID-0 创建单个持久卷,这在只有一个 Pod 需要快速存储时很有用。
实施步骤
节点初始化本地盘
- 选项 1:多个持久卷,每个 NVMe 实例存储卷对应一个
使用 eksctl 工具,我们创建一个新的 HAQM EKS 托管节点组。在此示例中,我们请求了两个 i3.8xlarge EC2 实例。在元数据中,将 eksworkshop-eksctl 和 us-west-2 替换为您各自的 EKS 集群名称和 AWS 区域。
将以下清单复制并保存为 pv-nvme-nodegroup.yaml
# pv-nvme-nodegroup.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
# Replace with your EKS Cluster's name
name: eksworkshop-eksctl
# Replace with the AWS Region your cluster is deployed in
region: us-west-2
managedNodeGroups:
# Name to give the managed node-group
- name: eks-pv-nvme-ng
# Label the nodes that they contain fast-disks
labels: { fast-disk-node: "pv-nvme" }
instanceType: i3.8xlarge
desiredCapacity: 2
volumeSize: 100 # EBS Boot Volume size
privateNetworking: true
preBootstrapCommands:
- |
# Install NVMe CLI
yum install nvme-cli -y
# Get a list of instance-store NVMe drives
nvme_drives=$(nvme list | grep "HAQM EC2 NVMe Instance Storage" | cut -d " " -f 1 || true)
readarray -t nvme_drives <<< "$nvme_drives"
for disk in "${nvme_drives[@]}"
do
# Format the disk to ext4
mkfs.ext4 -F $disk
# Get disk UUID
uuid=$(blkid -o value -s UUID $disk)
# Create a filesystem path to mount the disk
mount_location="/mnt/fast-disks/${uuid}"
mkdir -p $mount_location
# Mount the disk
mount $disk $mount_location
# Mount the disk during a reboot
echo $disk $mount_location ext4 defaults,noatime 0 2 >> /etc/fstab
done
使用如下命令,创建 EKS 托管节点组:
eksctl create nodegroup -f pv-nvme-nodegroup.yaml
- 选项 2:一个单独的持久卷 RAID-0 阵列,跨所有 NVMe 实例存储卷
与上一个示例类似,我们使用 eksctl 工具并创建一个新的 HAQM EKS 托管节点组。但是,这次我们将在 NVMe 实例存储卷上创建一个软件 RAID-0 阵列。
在元数据中,将 eksworkshop-eksctl 和 us-west-2 替换为您各自的 EKS 集群名称和 AWS 区域。
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
# Replace with your EKS Cluster's name
name: eksworkshop-eksctl
# Replace with the AWS Region your cluster is deployed in
region: us-west-2
managedNodeGroups:
# Name to give the managed node-group
- name: eks-pv-raid-ng
# Label the nodes that they contain fast-disks
labels: { fast-disk-node: "pv-raid" }
instanceType: i3.8xlarge
desiredCapacity: 2
volumeSize: 100 # EBS Boot Volume size
privateNetworking: true
preBootstrapCommands:
- |
# Install NVMe CLI
yum install nvme-cli -y
# Get list of NVMe Drives
nvme_drives=$(nvme list | grep "HAQM EC2 NVMe Instance Storage" | cut -d " " -f 1 || true)
readarray -t nvme_drives <<< "$nvme_drives"
num_drives=${#nvme_drives[@]}
# Install software RAID utility
yum install mdadm -y
# Create RAID-0 array across the instance store NVMe SSDs
mdadm --create /dev/md0 --level=0 --name=md0 --raid-devices=$num_drives "${nvme_drives[@]}"
# Format drive with Ext4
mkfs.ext4 /dev/md0
# Get RAID array's UUID
uuid=$(blkid -o value -s UUID /dev/md0)
# Create a filesystem path to mount the disk
mount_location="/mnt/fast-disks/${uuid}"
mkdir -p $mount_location
# Mount RAID device
mount /dev/md0 $mount_location
# Have disk be mounted on reboot
mdadm --detail --scan >> /etc/mdadm.conf
echo /dev/md0 $mount_location ext4 defaults,noatime 0 2 >> /etc/fstab
使用如下命令,创建 EKS 托管节点组:
eksctl create nodegroup --config-file=pv-raid-nodegroup.yaml
方式二:PVC动态绑定方案
核心组件:Local Volume Static Provisioner
Kubernetes 项目由多个特别兴趣小组(SIG)组成,这些小组专注于 Kubernetes 生态系统的某个特定部分。存储兴趣小组专注于不同类型的存储(块和文件),并确保容器调度时存储可用。存储 SIG 的子项目之一是本地卷静态配置器(Local Volume Static Provisioner),它是一个容器存储接口(CSI)驱动程序,可为实例启动期间连接的持久性磁盘创建 Kubernetes PersistentVolumes。
如果实现需要执行如下步骤:
1. 创建具有集群级权限的服务账户
CSI 驱动程序需要获得向 Kubernetes 控制平面发出 API 调用的权限,以管理 PV 的生命周期。 下面的清单定义了一个 Kubernetes 服务账户,并附加了一个授予必要 Kubernetes API 权限的 Kubernetes 集群角色。
复制下面的清单并保存为 service-account.yaml
# K8s service account for CSI Driver
apiVersion: v1
kind: ServiceAccount
metadata:
name: local-volume-provisioner
namespace: kube-system
---
# List of Permissions
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: local-storage-provisioner-node-clusterrole
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["watch"]
- apiGroups: ["", "events.k8s.io"]
resources: ["events"]
verbs: ["create", "update", "patch"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get"]
---
# Attach the K8s ClusterRole to our K8s ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: local-storage-provisioner-node-binding
namespace: kube-system
subjects:
- kind: ServiceAccount
name: local-volume-provisioner
namespace: kube-system
roleRef:
kind: ClusterRole
name: local-storage-provisioner-node-clusterrole
apiGroup: rbac.authorization.k8s.io
运行以下命令创建 ServiceAccount、ClusterRole 和 ClusterRoleBinding:
kubectl apply -f service-account.yaml
2. 为 CSI 驱动程序创建 ConfigMap
Local Volume Static Provisioner CSI 驱动程序在 Kubernetes ConfigMap 中存储了查找已挂载 EC2 NVMe 实例存储卷的位置,以及如何将它们作为 PV 暴露。 下面的 ConfigMap 指定 Local Volume Static Provisioner 在 /mnt/fast-disk 目录中查找已挂载的 NVMe 实例存储卷。
Kubernetes StorageClass 指定集群中可用的存储类型。 清单包括一个新的存储类别 fast-disks,以标识 PV 与 NVMe 实例存储卷有关。 复制并保存下面的清单为 config-map.yaml:
apiVersion: storage.k8s.io/v1kind: StorageClassmetadata:
name: fast-disksprovisioner: kubernetes.io/no-provisionervolumeBindingMode: WaitForFirstConsumer# Supported policies: Delete, Retain
reclaimPolicy: Retain---
# Configuration for our Local Persistent Volume CSI Driver
apiVersion: v1kind: ConfigMapmetadata:
name: local-volume-provisioner-config
namespace: kube-system
data:
# Adds the node's hostname as a label to each PV created
nodeLabelsForPV: |
- kubernetes.io/hostname
storageClassMap: |
fast-disks:
# Path on the host where local volumes of this storage class
# are mounted under.
hostDir: /mnt/fast-disks
# Optionally specify mount path of local volumes.
# By default, we use same path as hostDir in container.
mountDir: /mnt/fast-disks
# The /scripts/shred.sh is contained in the CSI drivers container
# http://github.com/kubernetes-sigs/sig-storage-local-static-provisioner/blob/master/deployment/docker/scripts/shred.sh
blockCleanerCommand:
- "/scripts/shred.sh"
- "2"
# The volume mode of PV defines whether a device volume is
# intended to use as a formatted filesystem volume or to remain in block
# state. Value of Filesystem is implied when omitted.
volumeMode: Filesystem
fsType: ext4
# name pattern check
# only discover local disk mounted to path matching pattern("*" by default).
namePattern: "*"
运行以下命令创建 StorageClass 和 ConfigMap:
kubectl apply -f config-map.yaml
3. 创建 DaemonSet 以部署 CSI 驱动程序
本地卷静态配置器 CSI 驱动程序在每个需要将其 NVMe 实例存储卷作为 Kubernetes PV 暴露的 HAQM EKS 节点上运行。 通常,Kubernetes 集群中有多种实例类型,其中一些节点可能没有 NVMe 实例存储卷。 以下清单中的 DaemonSet 指定了一个 nodeAffinity 选择器,以便只在 HAQM EKS 节点上调度 DaemonSet,该节点的标签为 fast-disk-node,相应值为 pv-raid 或 pv-nvme。
复制并保存以下清单为 daemonset.yaml:
# The Local Persistent Volume CSI DaemonSet
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: local-volume-provisioner
namespace: kube-system
labels:
app.kubernetes.io/name: local-volume-provisioner
spec:
selector:
matchLabels:
app.kubernetes.io/name: local-volume-provisioner
template:
metadata:
labels:
app.kubernetes.io/name: local-volume-provisioner
spec:
serviceAccountName: local-volume-provisioner
containers:
# The latest version can be found in the changelog.
# In production, one might want to use the container digest hash
# over version for improved security.
# http://github.com/kubernetes-sigs/sig-storage-local-static-provisioner/blob/master/CHANGELOG.md
- image: "registry.k8s.io/sig-storage/local-volume-provisioner:v2.5.0"
# In production you might want to set this to use a locally cached
# image by setting this to: IfNotPresent
imagePullPolicy: "Always"
name: provisioner
securityContext:
privileged: true
env:
- name: MY_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: MY_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
# List of metrics at
# http://github.com/kubernetes-sigs/sig-storage-local-static-provisioner/blob/cee9e228dc28a4355f664b4fe2236b1857fe4eca/pkg/metrics/metrics.go
- name: metrics
containerPort: 8080
volumeMounts:
- name: provisioner-config
mountPath: /etc/provisioner/config
readOnly: true
- mountPath: /mnt/fast-disks
name: fast-disks
mountPropagation: "HostToContainer"
volumes:
- name: provisioner-config
configMap:
name: local-volume-provisioner-config
- name: fast-disks
hostPath:
path: /mnt/fast-disks
# Only run CSI Driver on the `fast-disk` tagged nodegroup
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: fast-disk-node
operator: In
values:
- "pv-raid"
- "pv-nvme"
运行以下命令创建 DaemonSet:
kubectl apply -f daemonset.yaml
4. 在托管节点组中利用 Pre-bootstrap Commands 初始化本地盘
部署的 ConfigMap 中,本地卷静态配置器 CSI 驱动程序会查找挂载在 /mnt/fast-disks 目录中的磁盘,并在标签为 fast-disk-node、值为 pv-raid 或 pv-nvme 的节点上运行。 现在,我们需要配置 HAQM EKS 受管节点组,以启动带有 fast-disk-node 标签的 EC2 实例,并在启动时将 NVMe 实例存储卷挂载到 /mnt/fast-disks 目录。
复制下面的清单并保存为 pv-nodegroup.yaml:
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: eksworkshop-eksctl
region: us-west-2
managedNodeGroups:
- name: storage-nvme
desiredCapacity: 4
instanceType: i4g.large
preBootstrapCommands:
- mkdir -p /mnt/fast-disks
- |
cat <<EOF > /etc/udev/rules.d/90-kubernetes-nvme-symlink.rules
# Discover Instance Storage disks so kubernetes local provisioner can pick them up from
KERNEL=="nvme[0-9]*n[0-9]*", ENV{DEVTYPE}=="disk", ATTRS{model}=="HAQM EC2 NVMe Instance Storage", ATTRS{serial}=="?*", SYMLINK+="/mnt/fast-disks/nvme-\\\$attr{model}_\\\$attr{serial}", OPTIONS="string_escape=replace"
EOF
- udevadm control --reload && udevadm trigger
提示:对于需要极致 IOPS 的场景,推荐使用 RAID-0 阵列模式,可通过 mdadm 工具创建软件 RAID,将多个 NVMe 设备聚合为单个逻辑卷。
运行以下命令创建托管节点组:
eksctl create nodegroup --config-file=pv-nodegroup.yaml
5. 测试创建 PVC 与 Pod
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: fast-pvc
spec:
storageClassName: local-fast
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 500Gi # 容量仅用于匹配PV
---
apiVersion: v1
kind: Pod
metadata:
name: app-with-pvc
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: fast-pvc
该 CSI 驱动自动检测节点挂载点并创建 PV,实现存储资源抽象化。
总结
本文对比了直接访问与 PVC 管理两种本地存储使用方式。对于需要快速上手的简单场景,方案一更为便捷;而在需要动态分配、多应用共享的复杂环境中,方案二展示了 Kubernetes 存储管理的强大能力。开发者可根据实际需求灵活选择,充分发挥实例存储的高性能优势。
本篇作者