在 Azure Kubernetes 服务 (AKS) 上从树内存储类迁移到 CSI 驱动程序
从版本 1.21 开始,Azure Kubernetes 服务 (AKS) 引入了容器存储接口 (CSI) 驱动程序的实现。 采用 CSI 并将其作为标准时,应迁移或升级使用树内永久性卷 (PV) 的现有有状态工作负载,以使用 CSI 驱动程序。
为了使此过程尽可能简单,并确保不会丢失数据,本文提供了不同的迁移选项。 这些选项包括有助于确保从树内磁盘顺利迁移到 Azure 磁盘和 Azure 文件 CSI 驱动程序的脚本。
开始之前
- Azure CLI 版本 2.37.0 或更高版本。 可通过运行
az --version
查找版本,运行az upgrade
升级版本。 如果需要进行安装或升级,请参阅安装 Azure CLI。 - Kubectl 和群集管理员有权创建、获取、列出、删除对 PVC 或 PV、卷快照或卷快照内容的访问权限。 对于已启用 Microsoft Entra RBAC 的群集,你是 Azure Kubernetes 服务 RBAC 群集管理员角色的成员。
迁移磁盘卷
注意
标签 failure-domain.beta.kubernetes.io/zone
和 failure-domain.beta.kubernetes.io/region
在 AKS 1.24 中已被弃用,在 1.28 中已被删除。 如果现有永久性卷仍在使用与这两个标签匹配的 nodeAffinity,则需要将其更改为新的永久性卷设置中的 topology.kubernetes.io/zone
和 topology.kubernetes.io/region
标签。
使用两个迁移选项支持从树内迁移到 CSI:
- 创建静态卷
- 创建动态卷
创建静态卷
使用此选项,可以通过将 claimRef
静态分配给稍后要创建的新 PVC 来创建 PV,并为 PersistentVolumeClaim 指定 volumeName
。
此方法的优点包括:
- 此工作流很简单,而且可以自动化。
- 无需使用树内存储类清理原始配置。
- 风险低,因为仅执行 Kubernetes PV/PVC 的逻辑删除,不会删除实际的物理数据。
- 无额外费用,因为不需要创建额外的 Azure 对象,如磁盘、快照等。
以下是要评估的重要注意事项:
- 从原始动态样式卷转换到静态卷时需要为所有选项手动构造和管理 PV 对象。
- 根据新的 PVC 对象重新部署新应用程序时潜在的应用程序停机时间。
迁移
运行以下命令,将现有 PV
ReclaimPolicy
从“删除”更新为“保留”:kubectl patch pv pvName -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
将“pvName”替换为所选 PersistentVolume 的名称。 或者,如果要更新多个 PV 的 reclaimPolicy,请创建一个名为 patchReclaimPVs.sh 的文件,并复制到以下代码中。
#!/bin/bash # Patch the Persistent Volume in case ReclaimPolicy is Delete NAMESPACE=$1 i=1 for PVC in $(kubectl get pvc -n $NAMESPACE | awk '{ print $1}'); do # Ignore first record as it contains header if [ $i -eq 1 ]; then i=$((i + 1)) else PV="$(kubectl get pvc $PVC -n $NAMESPACE -o jsonpath='{.spec.volumeName}')" RECLAIMPOLICY="$(kubectl get pv $PV -n $NAMESPACE -o jsonpath='{.spec.persistentVolumeReclaimPolicy}')" echo "Reclaim Policy for Persistent Volume $PV is $RECLAIMPOLICY" if [[ $RECLAIMPOLICY == "Delete" ]]; then echo "Updating ReclaimPolicy for $pv to Retain" kubectl patch pv $PV -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}' fi fi done
使用
namespace
参数执行脚本以指定群集命名空间./PatchReclaimPolicy.sh <namespace>
。运行以下命令,获取命名空间中按 creationTimestamp 排序的所有 PVC 的列表。 使用
--namespace
参数和实际群集命名空间设置命名空间。kubectl get pvc -n <namespace> --sort-by=.metadata.creationTimestamp -o custom-columns=NAME:.metadata.name,CreationTime:.metadata.creationTimestamp,StorageClass:.spec.storageClassName,Size:.spec.resources.requests.storage
如果你有大量需要迁移的 PV,并且想要一次迁移几个 PV,则此步骤非常有用。 运行此命令可以确定在给定时间范围内创建了哪些 PVC。 运行 CreatePV.sh 脚本时,其中两个参数是开始时间和结束时间,使你能够仅在该时间段内迁移 PVC。
创建一个名为 CreatePV.sh 的文件并将以下 YAML 复制到其中。 此脚本执行以下任务:
- 为存储类
storageClassName
的命名空间中的所有 PersistentVolume 创建名为existing-pv-csi
的新 PersistentVolume。 - 将新 PVC 名称配置为
existing-pvc-csi
。 - 使用指定的 PV 名称创建新的 PVC。
#!/bin/bash #kubectl get pvc -n <namespace> --sort-by=.metadata.creationTimestamp -o custom-columns=NAME:.metadata.name,CreationTime:.metadata.creationTimestamp,StorageClass:.spec.storageClassName,Size:.spec.resources.requests.storage # TimeFormat 2022-04-20T13:19:56Z NAMESPACE=$1 FILENAME=$(date +%Y%m%d%H%M)-$NAMESPACE EXISTING_STORAGE_CLASS=$2 STORAGE_CLASS_NEW=$3 STARTTIMESTAMP=$4 ENDTIMESTAMP=$5 i=1 for PVC in $(kubectl get pvc -n $NAMESPACE | awk '{ print $1}'); do # Ignore first record as it contains header if [ $i -eq 1 ]; then i=$((i + 1)) else PVC_CREATION_TIME=$(kubectl get pvc $PVC -n $NAMESPACE -o jsonpath='{.metadata.creationTimestamp}') if [[ $PVC_CREATION_TIME >= $STARTTIMESTAMP ]]; then if [[ $ENDTIMESTAMP > $PVC_CREATION_TIME ]]; then PV="$(kubectl get pvc $PVC -n $NAMESPACE -o jsonpath='{.spec.volumeName}')" RECLAIM_POLICY="$(kubectl get pv $PV -n $NAMESPACE -o jsonpath='{.spec.persistentVolumeReclaimPolicy}')" STORAGECLASS="$(kubectl get pv $PV -n $NAMESPACE -o jsonpath='{.spec.storageClassName}')" echo $PVC RECLAIM_POLICY="$(kubectl get pv $PV -n $NAMESPACE -o jsonpath='{.spec.persistentVolumeReclaimPolicy}')" if [[ $RECLAIM_POLICY == "Retain" ]]; then if [[ $STORAGECLASS == $EXISTING_STORAGE_CLASS ]]; then STORAGE_SIZE="$(kubectl get pv $PV -n $NAMESPACE -o jsonpath='{.spec.capacity.storage}')" SKU_NAME="$(kubectl get storageClass $STORAGE_CLASS_NEW -o jsonpath='{.parameters.skuname}')" DISK_URI="$(kubectl get pv $PV -n $NAMESPACE -o jsonpath='{.spec.azureDisk.diskURI}')" PERSISTENT_VOLUME_RECLAIM_POLICY="$(kubectl get pv $PV -n $NAMESPACE -o jsonpath='{.spec.persistentVolumeReclaimPolicy}')" cat >$PVC-csi.yaml <<EOF apiVersion: v1 kind: PersistentVolume metadata: annotations: pv.kubernetes.io/provisioned-by: disk.csi.azure.com name: $PV-csi spec: accessModes: - ReadWriteOnce capacity: storage: $STORAGE_SIZE claimRef: apiVersion: v1 kind: PersistentVolumeClaim name: $PVC-csi namespace: $NAMESPACE csi: driver: disk.csi.azure.com volumeAttributes: csi.storage.k8s.io/pv/name: $PV-csi csi.storage.k8s.io/pvc/name: $PVC-csi csi.storage.k8s.io/pvc/namespace: $NAMESPACE requestedsizegib: "$STORAGE_SIZE" skuname: $SKU_NAME volumeHandle: $DISK_URI persistentVolumeReclaimPolicy: $PERSISTENT_VOLUME_RECLAIM_POLICY storageClassName: $STORAGE_CLASS_NEW --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: $PVC-csi namespace: $NAMESPACE spec: accessModes: - ReadWriteOnce storageClassName: $STORAGE_CLASS_NEW resources: requests: storage: $STORAGE_SIZE volumeName: $PV-csi EOF kubectl apply -f $PVC-csi.yaml LINE="PVC:$PVC,PV:$PV,StorageClassTarget:$STORAGE_CLASS_NEW" printf '%s\n' "$LINE" >>$FILENAME fi fi fi fi fi done
- 为存储类
若要为命名空间中的所有 PersistentVolumes 创建新的 PersistentVolume,请使用以下参数执行脚本 CreatePV.sh:
namespace
- 群集命名空间sourceStorageClass
- 基于树内存储驱动程序的 StorageClasstargetCSIStorageClass
- 基于 CSI 存储驱动程序的 StorageClass,可以是将预配器设置为 disk.csi.azure.com 或 file.csi.azure.com 的默认存储类之一。 或者,可以创建自定义存储类,只要它设置为这两个预配器之一。startTimeStamp
- 以 yyyy-mm-ddthh:mm:ssz 格式提供 PVC 创建时间之前的开始时间endTimeStamp
- 提供格式为 yyyy-mm-ddthh:mm:ssz 的结束时间。
./CreatePV.sh <namespace> <sourceIntreeStorageClass> <targetCSIStorageClass> <startTimestamp> <endTimestamp>
更新应用程序以使用新 PVC。
创建动态卷
使用此选项,可以根据永久性卷声明动态创建永久性卷。
此方法的优点包括:
风险较低,因为所有新对象都是在保留包含快照的其他副本的同时创建的。
无需单独构造 PV 并在 PVC 清单中添加卷名称。
以下是要评估的重要注意事项:
虽然这种方法的风险较低,但它确实会创建多个对象,这会增加存储成本。
在创建新卷期间,应用程序不可用。
应谨慎执行删除步骤。 临时资源锁可以应用于资源组,直到迁移完成并成功验证应用程序。
根据快照创建新磁盘时执行数据验证。
迁移
在继续之前,请验证以下事项:
对于数据在写入磁盘之前先写入内存的特定工作负载,应停止应用程序并允许将内存中的数据刷新到磁盘。
VolumeSnapshot
类应存在,如以下示例 YAML 所示:apiVersion: snapshot.storage.k8s.io/v1 kind: VolumeSnapshotClass metadata: name: custom-disk-snapshot-sc driver: disk.csi.azure.com deletionPolicy: Delete parameters: incremental: "false"
运行以下命令,获取特定命名空间中按 creationTimestamp 排序的所有 PVC 的列表。 使用
--namespace
参数和实际群集命名空间设置命名空间。kubectl get pvc --namespace <namespace> --sort-by=.metadata.creationTimestamp -o custom-columns=NAME:.metadata.name,CreationTime:.metadata.creationTimestamp,StorageClass:.spec.storageClassName,Size:.spec.resources.requests.storage
如果你有大量需要迁移的 PV,并且想要一次迁移几个 PV,则此步骤非常有用。 运行此命令可以确定在给定时间范围内创建了哪些 PVC。 运行 MigrateCSI.sh 脚本时,其中两个参数是开始时间和结束时间,使你能够仅在该时间段内迁移 PVC。
创建一个名为 MigrateCSI.sh 的文件并将以下 YAML 复制到其中。 此脚本执行以下任务:
- 使用 Azure CLI 创建完整磁盘快照
- 创建
VolumesnapshotContent
- 创建
VolumeSnapshot
- 从
VolumeSnapshot
创建一个新的 PVC - 创建一个文件名为
<namespace>-timestamp
的新文件,其中包含需要清理的所有旧资源的列表。
#!/bin/bash #kubectl get pvc -n <namespace> --sort-by=.metadata.creationTimestamp -o custom-columns=NAME:.metadata.name,CreationTime:.metadata.creationTimestamp,StorageClass:.spec.storageClassName,Size:.spec.resources.requests.storage # TimeFormat 2022-04-20T13:19:56Z NAMESPACE=$1 FILENAME=$NAMESPACE-$(date +%Y%m%d%H%M) EXISTING_STORAGE_CLASS=$2 STORAGE_CLASS_NEW=$3 VOLUME_STORAGE_CLASS=$4 START_TIME_STAMP=$5 END_TIME_STAMP=$6 i=1 for PVC in $(kubectl get pvc -n $NAMESPACE | awk '{ print $1}'); do # Ignore first record as it contains header if [ $i -eq 1 ]; then i=$((i + 1)) else PVC_CREATION_TIME=$(kubectl get pvc $PVC -n $NAMESPACE -o jsonpath='{.metadata.creationTimestamp}') if [[ $PVC_CREATION_TIME > $START_TIME_STAMP ]]; then if [[ $END_TIME_STAMP > $PVC_CREATION_TIME ]]; then PV="$(kubectl get pvc $PVC -n $NAMESPACE -o jsonpath='{.spec.volumeName}')" RECLAIM_POLICY="$(kubectl get pv $PV -n $NAMESPACE -o jsonpath='{.spec.persistentVolumeReclaimPolicy}')" STORAGE_CLASS="$(kubectl get pv $PV -n $NAMESPACE -o jsonpath='{.spec.storageClassName}')" echo $PVC RECLAIM_POLICY="$(kubectl get pv $PV -n $NAMESPACE -o jsonpath='{.spec.persistentVolumeReclaimPolicy}')" if [[ $STORAGE_CLASS == $EXISTING_STORAGE_CLASS ]]; then STORAGE_SIZE="$(kubectl get pv $PV -n $NAMESPACE -o jsonpath='{.spec.capacity.storage}')" SKU_NAME="$(kubectl get storageClass $STORAGE_CLASS_NEW -o jsonpath='{.parameters.skuname}')" DISK_URI="$(kubectl get pv $PV -n $NAMESPACE -o jsonpath='{.spec.azureDisk.diskURI}')" TARGET_RESOURCE_GROUP="$(cut -d'/' -f5 <<<"$DISK_URI")" echo $DISK_URI SUBSCRIPTION_ID="$(echo $DISK_URI | grep -o 'subscriptions/[^/]*' | sed 's#subscriptions/##g')" echo $TARGET_RESOURCE_GROUP PERSISTENT_VOLUME_RECLAIM_POLICY="$(kubectl get pv $PV -n $NAMESPACE -o jsonpath='{.spec.persistentVolumeReclaimPolicy}')" az snapshot create --resource-group $TARGET_RESOURCE_GROUP --name $PVC-$FILENAME --source "$DISK_URI" --subscription ${SUBSCRIPTION_ID} SNAPSHOT_PATH=$(az snapshot list --resource-group $TARGET_RESOURCE_GROUP --query "[?name == '$PVC-$FILENAME'].id | [0]" --subscription ${SUBSCRIPTION_ID}) SNAPSHOT_HANDLE=$(echo "$SNAPSHOT_PATH" | tr -d '"') echo $SNAPSHOT_HANDLE sleep 10 # Create Restore File cat <<EOF >$PVC-csi.yml apiVersion: snapshot.storage.k8s.io/v1 kind: VolumeSnapshotContent metadata: name: $PVC-$FILENAME spec: deletionPolicy: 'Delete' driver: 'disk.csi.azure.com' volumeSnapshotClassName: $VOLUME_STORAGE_CLASS source: snapshotHandle: $SNAPSHOT_HANDLE volumeSnapshotRef: apiVersion: snapshot.storage.k8s.io/v1 kind: VolumeSnapshot name: $PVC-$FILENAME namespace: $1 --- apiVersion: snapshot.storage.k8s.io/v1 kind: VolumeSnapshot metadata: name: $PVC-$FILENAME namespace: $1 spec: volumeSnapshotClassName: $VOLUME_STORAGE_CLASS source: volumeSnapshotContentName: $PVC-$FILENAME --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: csi-$PVC namespace: $1 spec: accessModes: - ReadWriteOnce storageClassName: $STORAGE_CLASS_NEW resources: requests: storage: $STORAGE_SIZE dataSource: name: $PVC-$FILENAME kind: VolumeSnapshot apiGroup: snapshot.storage.k8s.io EOF kubectl create -f $PVC-csi.yml LINE="OLDPVC:$PVC,OLDPV:$PV,VolumeSnapshotContent:volumeSnapshotContent-$FILENAME,VolumeSnapshot:volumesnapshot$FILENAME,OLDdisk:$DISK_URI" printf '%s\n' "$LINE" >>$FILENAME fi fi fi fi done
若要迁移磁盘卷,请使用以下参数执行脚本 MigrateToCSI.sh:
namespace
- 群集命名空间sourceStorageClass
- 基于树内存储驱动程序的 StorageClasstargetCSIStorageClass
- 基于 CSI 存储驱动程序的 StorageClassvolumeSnapshotClass
- 卷快照类的名称。 例如custom-disk-snapshot-sc
。startTimeStamp
- 提供格式为 yyyy-mm-ddthh:mm:ssz 的开始时间。endTimeStamp
- 提供格式为 yyyy-mm-ddthh:mm:ssz 的结束时间。
./MigrateToCSI.sh <namespace> <sourceStorageClass> <TargetCSIstorageClass> <VolumeSnapshotClass> <startTimestamp> <endTimestamp>
更新应用程序以使用新 PVC。
手动删除较旧的资源,包括树内 PVC/PV、VolumeSnapshot 和 VolumeSnapshotContent。 否则,维护树内 PVC/PC 和快照对象会产生更多成本。
迁移文件共享卷
通过创建静态卷,可以从树内迁移到 CSI:
- 无需使用树内存储类清理原始配置。
- 风险低,因为仅执行 Kubernetes PV/PVC 的逻辑删除,不会删除实际的物理数据。
- 无额外费用,因为不需要创建额外的 Azure 对象,如文件共享等。
迁移
运行以下命令,将现有 PV
ReclaimPolicy
从“删除”更新为“保留”:kubectl patch pv pvName -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
将“pvName”替换为所选 PersistentVolume 的名称。 或者,如果要更新多个 PV 的 reclaimPolicy,请创建一个名为 patchReclaimPVs.sh 的文件,并复制到以下代码中。
#!/bin/bash # Patch the Persistent Volume in case ReclaimPolicy is Delete namespace=$1 i=1 for pvc in $(kubectl get pvc -n $namespace | awk '{ print $1}'); do # Ignore first record as it contains header if [ $i -eq 1 ]; then i=$((i + 1)) else pv="$(kubectl get pvc $pvc -n $namespace -o jsonpath='{.spec.volumeName}')" reclaimPolicy="$(kubectl get pv $pv -n $namespace -o jsonpath='{.spec.persistentVolumeReclaimPolicy}')" echo "Reclaim Policy for Persistent Volume $pv is $reclaimPolicy" if [[ $reclaimPolicy == "Delete" ]]; then echo "Updating ReclaimPolicy for $pv to Retain" kubectl patch pv $pv -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}' fi fi done
使用
namespace
参数执行脚本以指定群集命名空间./PatchReclaimPolicy.sh <namespace>
。创建一个新的存储类并将预配器设置为
file.csi.azure.com
,或者将其中一个默认 StorageClasses 与 CSI 文件预配器配合使用。通过运行以下命令,从现有 PersistentVolumes 中获取
secretName
和shareName
:kubectl describe pv pvName
使用新的 StorageClass、
shareName
和secretName
从树内 PV 创建新的 PV。 创建一个名为 azurefile-mount-pv.yaml 的文件并将以下代码复制到其中。 在csi
下,更新resourceGroup
、volumeHandle
和shareName
。 对于装载选项,fileMode 和 dirMode 的默认值为 0777。fileMode
和dirMode
的默认值为 0777。apiVersion: v1 kind: PersistentVolume metadata: name: azurefile spec: capacity: storage: 5Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain storageClassName: azurefile-csi csi: driver: file.csi.azure.com readOnly: false volumeHandle: unique-volumeid # make sure volumeid is unique for every identical share in the cluster volumeAttributes: resourceGroup: EXISTING_RESOURCE_GROUP_NAME # optional, only set this when storage account is not in the same resource group as the cluster nodes shareName: aksshare nodeStageSecretRef: name: azure-secret namespace: default mountOptions: - dir_mode=0777 - file_mode=0777 - uid=0 - gid=0 - mfsymlinks - cache=strict - nosharesock - nobrl # disable sending byte range lock requests to the server and for applications which have challenges with posix locks
使用以下代码创建一个名为 azurefile-mount-pvc.yaml 的文件,其中包含使用 PersistentVolumeClaim 的 PersistentVolume。
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: azurefile spec: accessModes: - ReadWriteMany storageClassName: azurefile-csi volumeName: azurefile resources: requests: storage: 5Gi
使用
kubectl
命令创建 PersistentVolume。kubectl apply -f azurefile-mount-pv.yaml
使用
kubectl
命令创建 PersistentVolumeClaim。kubectl apply -f azurefile-mount-pvc.yaml
通过运行以下命令,验证 PersistentVolumeClaim 是否已创建并绑定到 PersistentVolume。
kubectl get pvc azurefile
输出如下所示:
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE azurefile Bound azurefile 5Gi RWX azurefile 5s
更新容器规范以引用 PersistentVolumeClaim并更新 Pod。 例如,复制以下代码并创建名为 azure-files-pod.yaml 的文件。
... volumes: - name: azure persistentVolumeClaim: claimName: azurefile
无法及时更新 Pod 规范。 使用以下
kubectl
命令删除 Pod,然后再重新创建 Pod。kubectl delete pod mypod
kubectl apply -f azure-files-pod.yaml
后续步骤
- 有关存储最佳做法的详细信息,请参阅有关 Azure Kubernetes 服务中存储和备份的最佳做法。