This topic explains how to use persistent volumes and storage classes to implement dynamic storage for Tanzu Kubernetes Grid (TKG) workload clusters.
Within a Kubernetes cluster, PersistentVolume
(PV) objects provide shared storage for cluster pods that is unaffected by pod lifecycles. Storage is provisioned to the PV through a PersistentVolumeClaim
(PVC) object, which defines how much and how the pod accesses the underlying storage. For more information, see Persistent Volumes in the Kubernetes documentation.
Cluster administrators can define StorageClass
objects that let cluster users dynamically create PVC and PV objects with different storage types and rules. Tanzu Kubernetes Grid also provides default StorageClass
objects that let users provision persistent storage in a turnkey environment.
StorageClass
objects include a provisioner
field identifying the internal or external service plug-in that provisions PVs, and a parameters
field that associates the Kubernetes storage class with storage options defined at the infrastructure level, such as VM Storage Policies in vSphere. For more information, see Storage Classes in the Kubernetes documentation.
Tanzu Kubernetes Grid supports StorageClass
objects for different storage types, provisioned by Kubernetes internal (“in-tree”) or external (“out-of-tree”) plug-ins.
Storage Types
NotevSphere CSI does not support Storage DRS, and supports Storage vMotion with conditions described in vSphere Functionality Supported by vSphere Container Storage Plug-in in the vSphere CSI documentation.
vSphere CSI support Storage vMotion with conditions, please read CSI doc for more details.
See Default Storage Class for the vSphere CNS default storage class.
External Provisioning
In TKG v2.5, all default storage classes use external (“out-of-tree”) storage provisioning, not “in-tree” provisioning.
StorageClass
object provisioner
values are not prefixed with kubernetes.io
.Tanzu Kubernetes Grid provides a default StorageClass
object that lets workload cluster users provision persistent storage on their infrastructure in a turnkey environment, without needing StorageClass
objects created by a cluster administrator.
The ENABLE_DEFAULT_STORAGE_CLASS
variable is set to true
by default in the cluster configuration file passed to --file
option of tanzu cluster create
, to enable the default storage class for a workload cluster.
ImportantDo not modify the default storage class definition. To customize a storage class, create a new
StorageClass
definition with a differentname
instead of modifying the default object created by TKG.
The Tanzu Kubernetes Grid default storage class definition is below:
vSphere CNS
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: default
annotations:
storageclass.kubernetes.io/is-default-class: "true"
kapp.k14s.io/update-strategy: skip
provisioner: csi.vsphere.vmware.com
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
parameters:
storagePolicyName: optional
See the vSphere CSI storage class parameters in the Kubernetes documentation.
The default storage class settings can be customized. Since most StorageClass
fields are immutable, it is necessary to delete and re-create the default storage class:
default
, and it must have the storageclass.kubernetes.io/is-default-class: "true"
annotation.kubectl delete storageclass default
.kubectl create -f
.Note, creation of the TKG default storage class can also be disabled during cluster creation by specifying ENABLE_DEFAULT_STORAGE_CLASS: false
in the cluster configuration file.
vSphere administrators can set up vSphere CNS and create storage policies for virtual disk (VMDK) storage, based on the needs of Tanzu Kubernetes Grid cluster users.
You can use either vSAN or local VMFS (Virtual Machine File System) for persistent storage in a Kubernetes cluster, as follows:
vSAN Storage:
To create a storage policy for vSAN storage in the vSphere Client, browse to Home > Policies and Profiles > VM Storage Policies and click Create to launch the Create VM Storage Policy wizard.
Follow the instructions in Create a Storage Policy in the vSphere documentation. Make sure to:
storagePolicyName
value in StorageClass
objects.Local VMFS Storage:
To create a storage policy for local storage, apply a tag to the storage and create a storage policy based on the tag as follows:
From the top-level vSphere menu, select Tags & Custom Attributes
In the Tags pane, select Categories and click New.
Enter a category name, such as tkg-storage
. Use the checkboxes to associate it with Datacenter and the storage objects, Folder and Datastore. Click Create.
From the top-level Storage view, select your VMFS volume, and in its Summary pane, click Tags > Assign….
From the Assign Tag popup, click Add Tag.
From the Create Tag popup, give the tag a name, such as tkg-storage-ds1
and assign it the Category you created. Click OK.
From Assign Tag, select the tag and click Assign.
From top-level vSphere, select VM Storage Policies > Create a Storage Policy. A configuration wizard starts.
In the Name and description pane, enter a name for your storage policy. Record the storage policy name for reference as the storagePolicyName
value in StorageClass
objects.
In the Policy structure pane, under Datastore specific rules, select Enable tag-based placement rules.
In the Tag based placement pane, click Add Tag Rule and configure:
Use storage tagged with
Confirm and configure other panes or accept defaults as needed, then click Review and finish. Finish to create the storage policy.
Cluster administrators can create a new storage class as follows:
StorageClass
.
StorageClass
configuration .yaml
with provisioner
, parameters
, and other options.
storagePolicyName
parameter to the vSphere storage policy name, as a double-quoted string.kubectl create -f
kubectl describe storageclass <storageclass metadata.name>
.For example, see Enabling Dynamic Provisioning in the Kubernetes documentation.
For vSphere CSI information and resources, see VMware vSphere Container Storage Plug-in Documentation.
To provision persistent storage for their cluster nodes that does not use one of the Default Storage Classes described above, cluster users include a custom storage class in a pod configuration as follows:
Set the context of kubectl
to the cluster. For example:
kubectl config use-context my-cluster-admin@my-cluster
Select or create a storage class.
kubectl get storageclass
.Create a PVC and its PV:
PersistentVolumeClaim
configuration .yaml
with spec.storageClassName
set to the metadata.name
value of your StorageClass
object. For an example, see Enabling Dynamic Provisioning in the Kubernetes documentation.kubectl create -f
kubectl describe pvc <pvc metadata.name>
to verify the PVC.kubectl describe pvc
output after Successfully provisioned volume
.kubectl describe pv <pv unique name>
to verify the PV.Create a pod using the PVC:
Pod
configuration .yaml
that sets spec.volumes
to include your PVC under persistentVolumeClaim.claimName
. For an example, see Dynamically Provision a Block Volume with vSphere Container Storage Plug-in in the vSphere Container Storage Plug-in documentation.kubectl create -f
kubectl get pod <pod metadata.name>
to verify the pod.To enable volume expansion for vSphere CSI storage used by workload clusters, you need to add a csi-resizer
sidecar pod to the cluster’s CSI processes.
The CSI configuration for workload clusters is encoded as a Kubernetes secret. This procedure adds the csi-resizer
process by revising the CSI configuration secret. It adds to the secret a stringData
definition that combines two encoded configuration data strings: a values.yaml
string containing the secret’s prior CSI configuration data, and a new overlays.yaml
string that deploys the csi-resizer
pod.
NoteOnline volume expansion is supported in vSphere 7.0 as of Update 2.
Log into the management cluster for the workload cluster you are changing, and run tanzu cluster list
if you need to retrieve the name of the workload cluster.
Retrieve the name of the CSI secret for the workload cluster, using label selectors vsphere-csi
and the cluster name:
$ kubectl get secret \
-l tkg.tanzu.vmware.com/cluster-name=NAME_OF_WORKLOAD_CLUSTER \
-l tkg.tanzu.vmware.com/addon-name=vsphere-csi
my-wc-vsphere-csi-secret
Save a backup of the secret’s content, in YAML format, to vsphere-csi-secret.yaml
:
kubectl get secret my-wc-vsphere-csi-secret -o yaml > vsphere-csi-secret.yaml
Output the secret’s current content again, with the data.values
values base64
-decoded into plain YAML.
$ kubectl get secret my-wc-vsphere-csi-secret -o jsonpath={.data.values\\.yaml} | base64 -d
#@data/values
#@overlay/match-child-defaults missing_ok=True
---
vsphereCSI:
CSIAttacherImage:
repository: projects.registry.vmware.com/tkg
path: csi/csi-attacher
tag: v3.0.0_vmware.1
pullPolicy: IfNotPresent
vsphereCSIControllerImage:
repository: projects.registry.vmware.com/tkg
path: csi/vsphere-block-csi-driver
tag: v2.1.1_vmware.1
pullPolicy: IfNotPresent
livenessProbeImage:
repository: projects.registry.vmware.com/tkg
path: csi/csi-livenessprobe
tag: v2.1.1_vmware.1
pullPolicy: IfNotPresent
vsphereSyncerImage:
repository: projects.registry.vmware.com/tkg
path: csi/volume-metadata-syncer
tag: v2.1.1_vmware.1
pullPolicy: IfNotPresent
CSIProvisionerImage:
repository: projects.registry.vmware.com/tkg
path: csi/csi-provisioner
tag: v2.0.0_vmware.1
pullPolicy: IfNotPresent
CSINodeDriverRegistrarImage:
repository: projects.registry.vmware.com/tkg
path: csi/csi-node-driver-registrar
tag: v2.0.1_vmware.1
pullPolicy: IfNotPresent
namespace: kube-system
clusterName: wc-1
server: 10.170.104.114
datacenter: /dc0
publicNetwork: VM Network
username: <MY-VSPHERE-USERNAME>
password: <MY-VSPHERE-PASSWORD>
Open vsphere-csi-secret.yaml
in an editor and do the following to make it look like the code below:
values.yaml
, which is a long string.stringData
and indent values.yaml
to make it the first element.data.values
output from the previous step.data.values
output and indent it as the value of values.yaml
.values.yaml
definition, add another stringData
definition for overlays.yaml
as shown below. Do not modify other definitions in the file.apiVersion: v1
stringData:
values.yaml: |
#@data/values
#@overlay/match-child-defaults missing_ok=True
---
vsphereCSI:
CSIAttacherImage:
repository: projects.registry.vmware.com/tkg
path: csi/csi-attacher
tag: v3.0.0_vmware.1
pullPolicy: IfNotPresent
vsphereCSIControllerImage:
repository: projects.registry.vmware.com/tkg
path: csi/vsphere-block-csi-driver
tag: v2.1.1_vmware.1
pullPolicy: IfNotPresent
livenessProbeImage:
repository: projects.registry.vmware.com/tkg
path: csi/csi-livenessprobe
tag: v2.1.1_vmware.1
pullPolicy: IfNotPresent
vsphereSyncerImage:
repository: projects.registry.vmware.com/tkg
path: csi/volume-metadata-syncer
tag: v2.1.1_vmware.1
pullPolicy: IfNotPresent
CSIProvisionerImage:
repository: projects.registry.vmware.com/tkg
path: csi/csi-provisioner
tag: v2.0.0_vmware.1
pullPolicy: IfNotPresent
CSINodeDriverRegistrarImage:
repository: projects.registry.vmware.com/tkg
path: csi/csi-node-driver-registrar
tag: v2.0.1_vmware.1
pullPolicy: IfNotPresent
namespace: kube-system
clusterName: wc-1
server: 10.170.104.114
datacenter: /dc0
publicNetwork: VM Network
username: <MY-VSPHERE-USERNAME>
password: <MY-VSPHERE-PASSWORD>
overlays.yaml: |
#@ load("@ytt:overlay", "overlay")
#@overlay/match by=overlay.subset({"kind": "Deployment", "metadata": {"name": "vsphere-csi-controller"}})
---
spec:
template:
spec:
containers:
#@overlay/append
- name: csi-resizer
image: projects.registry.vmware.com/tkg/kubernetes-csi_external-resizer:v1.0.0_vmware.1
args:
- "--v=4"
- "--timeout=300s"
- "--csi-address=$(ADDRESS)"
- "--leader-election"
env:
- name: ADDRESS
value: /csi/csi.sock
volumeMounts:
- mountPath: /csi
name: socket-dir
kind: Secret
...
Run kubectl apply
to update the cluster’s secret with the revised definitions and re-create the csi-controller
pod:
kubectl apply -f vsphere-csi-secret.yaml
To verify that the vsphere-csi-controller
and external resizer are working on the cluster:
Confirm that vsphere-csi-controller
is running on the workload cluster with six healthy pods:
$ kubectl get pods -n kube-system -l app=vsphere-csi-controller
NAME READY STATUS RESTARTS AGE
vsphere-csi-controller-<ID-HASH> 6/6 Running 0 6m49s
Check the logs of the vsphere-csi-controller
to see that the external resizer started.
$ kubectl logs vsphere-csi-controller-<ID-HASH> -n kube-system -c csi-resizer
I0308 23:44:45.035254 1 main.go:79] Version : v1.0.0-0-gb22717d
I0308 23:44:45.037856 1 connection.go:153] Connecting to unix:///csi/csi.sock
I0308 23:44:45.038572 1 common.go:111] Probing CSI driver for readiness
I0308 23:44:45.040579 1 csi_resizer.go:77] CSI driver name: "csi.vsphere.vmware.com"
W0308 23:44:45.040612 1 metrics.go:142] metrics endpoint will not be started because `metrics-address` was not specified.
I0308 23:44:45.042184 1 controller.go:117] Register Pod informer for resizer csi.vsphere.vmware.com
I0308 23:44:45.043182 1 leaderelection.go:243] attempting to acquire leader lease kube-system/external-resizer-csi-vsphere-vmware-com...
I0308 23:44:45.073383 1 leaderelection.go:253] successfully acquired lease kube-system/external-resizer-csi-vsphere-vmware-com
I0308 23:44:45.076028 1 leader_election.go:172] new leader detected, current leader: vsphere-csi-controller-87d7dcf48-jcht2
I0308 23:44:45.079332 1 leader_election.go:165] became leader, starting
I0308 23:44:45.079638 1 controller.go:241] Starting external resizer csi.vsphere.vmware.com
For workload clusters created from a standalone management cluster, you can configure topology-aware local storage volume provisioning. Topology-aware volume provisioning allows Kubernetes to make intelligent decisions when dynamically provisioning volumes. Kubernetes gets scheduler input on the best place to provision a volume for a pod.
Create an availability zone (AZ):
Follow the instruction in Running Clusters Across Multiple Availability Zones to create the following in vSphere:
Add rules to limit the VM group in the host group.
One VM group and host group is used to run the AZ in the workload cluster.
NoteThe limit rules are for local volume configurations only. You do not need to create limit rules if you are deploying to multiple availability zones.
Deploy the custom resource definitions (CRDs) VSphereFailureDomain
and VSphereDeploymentZone
in the standalone management cluster.
The AZ will map to a VSphereDeploymentZone
and then to a host group in vSphere.
Add a new AZ. You can add a new AZ using a ytt
overlay configuration or using Tanzu CLI.
ytt
ytt
overlay configuration in a legacy cluster and a classy cluster. For information about how to download and install
ytt
, see
Install the Carvel Tools.
Legacy cluster
#@overlay/match by=overlay.subset({"kind":"MachineDeployment", "metadata":{"name": "${CLUSTER_NAME}-md-0"}})
---
apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineDeployment
metadata:
name: #@ "{}-md-0".format(data.values.CLUSTER_NAME)
spec:
clusterName: #@ data.values.CLUSTER_NAME
replicas: #@ data.values.WORKER_MACHINE_COUNT_0
selector:
matchLabels: null
strategy:
type: #@ verify_and_configure_machine_deployment_rollout_strategy(data.values.WORKER_ROLLOUT_STRATEGY)
template:
metadata:
labels:
node-pool: #@ "{}-worker-pool".format(data.values.CLUSTER_NAME)
spec:
clusterName: #@ data.values.CLUSTER_NAME
version: #@ data.values.KUBERNETES_VERSION
bootstrap:
configRef:
name: #@ "{}-md-0".format(data.values.CLUSTER_NAME)
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
infrastructureRef:
name: #@ "{}-md-0".format(data.values.CLUSTER_NAME)
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: AWSMachineTemplate
failureDomain: #@ default_az_0
Classy cluster
workers:
machineDeployments:
#@overlay/match by=overlay.index(0)
- class: tkg-worker
name: md-0
replicas: #@ data.values.WORKER_MACHINE_COUNT_0
#@overlay/match missing_ok=True
failureDomain: #@ default_az_0
metadata:
annotations:
run.tanzu.vmware.com/resolve-os-image: #@ "ami-region={},os-name={},os-arch={}".format(data.values.AWS_REGION, data.values.OS_NAME, data.values.OS_ARCH)
Legacy cluster
tanzu cl node-pool set cl-name -f node-pool.yaml
# node-pool.yaml
name: vsphere-wc-1-manual-node-pool
replicas: 1
az: "rack4"
Classy cluster
See Set (Create) in Node Pools Configuration for ClusterClass-based clusters.