mirror of https://github.com/k3s-io/k3s
Add availability zone support for dynamic provisioning Azure managed disks
parent
b258bbad6a
commit
74813d0d26
|
@ -32,6 +32,7 @@ import (
|
||||||
"github.com/Azure/go-autorest/autorest/to"
|
"github.com/Azure/go-autorest/autorest/to"
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"github.com/rubiojr/go-vhd/vhd"
|
"github.com/rubiojr/go-vhd/vhd"
|
||||||
|
|
||||||
kwait "k8s.io/apimachinery/pkg/util/wait"
|
kwait "k8s.io/apimachinery/pkg/util/wait"
|
||||||
"k8s.io/kubernetes/pkg/volume"
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
)
|
)
|
||||||
|
|
|
@ -40,22 +40,55 @@ type ManagedDiskController struct {
|
||||||
common *controllerCommon
|
common *controllerCommon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ManagedDiskOptions specifies the options of managed disks.
|
||||||
|
type ManagedDiskOptions struct {
|
||||||
|
DiskName string
|
||||||
|
SizeGB int
|
||||||
|
PVCName string
|
||||||
|
ResourceGroup string
|
||||||
|
Zoned bool
|
||||||
|
ZonePresent bool
|
||||||
|
ZonesPresent bool
|
||||||
|
AvailabilityZone string
|
||||||
|
AvailabilityZones string
|
||||||
|
Tags map[string]string
|
||||||
|
StorageAccountType storage.SkuName
|
||||||
|
}
|
||||||
|
|
||||||
func newManagedDiskController(common *controllerCommon) (*ManagedDiskController, error) {
|
func newManagedDiskController(common *controllerCommon) (*ManagedDiskController, error) {
|
||||||
return &ManagedDiskController{common: common}, nil
|
return &ManagedDiskController{common: common}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//CreateManagedDisk : create managed disk
|
//CreateManagedDisk : create managed disk
|
||||||
func (c *ManagedDiskController) CreateManagedDisk(diskName string, storageAccountType storage.SkuName, resourceGroup string,
|
func (c *ManagedDiskController) CreateManagedDisk(options *ManagedDiskOptions) (string, error) {
|
||||||
sizeGB int, tags map[string]string) (string, error) {
|
glog.V(4).Infof("azureDisk - creating new managed Name:%s StorageAccountType:%s Size:%v", options.DiskName, options.StorageAccountType, options.SizeGB)
|
||||||
glog.V(4).Infof("azureDisk - creating new managed Name:%s StorageAccountType:%s Size:%v", diskName, storageAccountType, sizeGB)
|
|
||||||
|
|
||||||
|
// Validate and choose availability zone for creating disk.
|
||||||
|
var createAZ string
|
||||||
|
if options.Zoned && !options.ZonePresent && !options.ZonesPresent {
|
||||||
|
// TODO: get zones from active zones that with running nodes.
|
||||||
|
}
|
||||||
|
if !options.ZonePresent && options.ZonesPresent {
|
||||||
|
// Choose zone from specified zones.
|
||||||
|
if adminSetOfZones, err := util.ZonesToSet(options.AvailabilityZones); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else {
|
||||||
|
createAZ = util.ChooseZoneForVolume(adminSetOfZones, options.PVCName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options.ZonePresent && !options.ZonesPresent {
|
||||||
|
if err := util.ValidateZone(options.AvailabilityZone); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
createAZ = options.AvailabilityZone
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert original tags to newTags
|
||||||
newTags := make(map[string]*string)
|
newTags := make(map[string]*string)
|
||||||
azureDDTag := "kubernetes-azure-dd"
|
azureDDTag := "kubernetes-azure-dd"
|
||||||
newTags["created-by"] = &azureDDTag
|
newTags["created-by"] = &azureDDTag
|
||||||
|
if options.Tags != nil {
|
||||||
// insert original tags to newTags
|
for k, v := range options.Tags {
|
||||||
if tags != nil {
|
|
||||||
for k, v := range tags {
|
|
||||||
// Azure won't allow / (forward slash) in tags
|
// Azure won't allow / (forward slash) in tags
|
||||||
newKey := strings.Replace(k, "/", "-", -1)
|
newKey := strings.Replace(k, "/", "-", -1)
|
||||||
newValue := strings.Replace(v, "/", "-", -1)
|
newValue := strings.Replace(v, "/", "-", -1)
|
||||||
|
@ -63,25 +96,30 @@ func (c *ManagedDiskController) CreateManagedDisk(diskName string, storageAccoun
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
diskSizeGB := int32(sizeGB)
|
diskSizeGB := int32(options.SizeGB)
|
||||||
model := compute.Disk{
|
model := compute.Disk{
|
||||||
Location: &c.common.location,
|
Location: &c.common.location,
|
||||||
Tags: newTags,
|
Tags: newTags,
|
||||||
Sku: &compute.DiskSku{
|
Sku: &compute.DiskSku{
|
||||||
Name: compute.StorageAccountTypes(storageAccountType),
|
Name: compute.StorageAccountTypes(options.StorageAccountType),
|
||||||
},
|
},
|
||||||
DiskProperties: &compute.DiskProperties{
|
DiskProperties: &compute.DiskProperties{
|
||||||
DiskSizeGB: &diskSizeGB,
|
DiskSizeGB: &diskSizeGB,
|
||||||
CreationData: &compute.CreationData{CreateOption: compute.Empty},
|
CreationData: &compute.CreationData{CreateOption: compute.Empty},
|
||||||
}}
|
},
|
||||||
|
}
|
||||||
|
|
||||||
if resourceGroup == "" {
|
if options.ResourceGroup == "" {
|
||||||
resourceGroup = c.common.resourceGroup
|
options.ResourceGroup = c.common.resourceGroup
|
||||||
|
}
|
||||||
|
if createAZ != "" {
|
||||||
|
createZones := []string{createAZ}
|
||||||
|
model.Zones = &createZones
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := getContextWithCancel()
|
ctx, cancel := getContextWithCancel()
|
||||||
defer cancel()
|
defer cancel()
|
||||||
_, err := c.common.cloud.DisksClient.CreateOrUpdate(ctx, resourceGroup, diskName, model)
|
_, err := c.common.cloud.DisksClient.CreateOrUpdate(ctx, options.ResourceGroup, options.DiskName, model)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -89,7 +127,7 @@ func (c *ManagedDiskController) CreateManagedDisk(diskName string, storageAccoun
|
||||||
diskID := ""
|
diskID := ""
|
||||||
|
|
||||||
err = kwait.ExponentialBackoff(defaultBackOff, func() (bool, error) {
|
err = kwait.ExponentialBackoff(defaultBackOff, func() (bool, error) {
|
||||||
provisionState, id, err := c.getDisk(resourceGroup, diskName)
|
provisionState, id, err := c.getDisk(options.ResourceGroup, options.DiskName)
|
||||||
diskID = id
|
diskID = id
|
||||||
// We are waiting for provisioningState==Succeeded
|
// We are waiting for provisioningState==Succeeded
|
||||||
// We don't want to hand-off managed disks to k8s while they are
|
// We don't want to hand-off managed disks to k8s while they are
|
||||||
|
@ -104,9 +142,9 @@ func (c *ManagedDiskController) CreateManagedDisk(diskName string, storageAccoun
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.V(2).Infof("azureDisk - created new MD Name:%s StorageAccountType:%s Size:%v but was unable to confirm provisioningState in poll process", diskName, storageAccountType, sizeGB)
|
glog.V(2).Infof("azureDisk - created new MD Name:%s StorageAccountType:%s Size:%v but was unable to confirm provisioningState in poll process", options.DiskName, options.StorageAccountType, options.SizeGB)
|
||||||
} else {
|
} else {
|
||||||
glog.V(2).Infof("azureDisk - created new MD Name:%s StorageAccountType:%s Size:%v", diskName, storageAccountType, sizeGB)
|
glog.V(2).Infof("azureDisk - created new MD Name:%s StorageAccountType:%s Size:%v", options.DiskName, options.StorageAccountType, options.SizeGB)
|
||||||
}
|
}
|
||||||
|
|
||||||
return diskID, nil
|
return diskID, nil
|
||||||
|
|
|
@ -41,6 +41,20 @@ func (az *Cloud) makeZone(zoneID int) string {
|
||||||
return fmt.Sprintf("%s-%d", strings.ToLower(az.Location), zoneID)
|
return fmt.Sprintf("%s-%d", strings.ToLower(az.Location), zoneID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isAvailabilityZone returns true if the zone is in format of <region>-<zone-id>.
|
||||||
|
func (az *Cloud) isAvailabilityZone(zone string) bool {
|
||||||
|
return strings.HasPrefix(zone, fmt.Sprintf("%s-", az.Location))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetZoneID returns the ID of zone from node's zone label.
|
||||||
|
func (az *Cloud) GetZoneID(zoneLabel string) string {
|
||||||
|
if !az.isAvailabilityZone(zoneLabel) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimPrefix(zoneLabel, fmt.Sprintf("%s-", az.Location))
|
||||||
|
}
|
||||||
|
|
||||||
// GetZone returns the Zone containing the current availability zone and locality region that the program is running in.
|
// GetZone returns the Zone containing the current availability zone and locality region that the program is running in.
|
||||||
// If the node is not running with availability zones, then it will fall back to fault domain.
|
// If the node is not running with availability zones, then it will fall back to fault domain.
|
||||||
func (az *Cloud) GetZone(ctx context.Context) (cloudprovider.Zone, error) {
|
func (az *Cloud) GetZone(ctx context.Context) (cloudprovider.Zone, error) {
|
||||||
|
|
|
@ -35,7 +35,7 @@ type DiskController interface {
|
||||||
CreateBlobDisk(dataDiskName string, storageAccountType storage.SkuName, sizeGB int) (string, error)
|
CreateBlobDisk(dataDiskName string, storageAccountType storage.SkuName, sizeGB int) (string, error)
|
||||||
DeleteBlobDisk(diskUri string) error
|
DeleteBlobDisk(diskUri string) error
|
||||||
|
|
||||||
CreateManagedDisk(diskName string, storageAccountType storage.SkuName, resourceGroup string, sizeGB int, tags map[string]string) (string, error)
|
CreateManagedDisk(options *azure.ManagedDiskOptions) (string, error)
|
||||||
DeleteManagedDisk(diskURI string) error
|
DeleteManagedDisk(diskURI string) error
|
||||||
|
|
||||||
// Attaches the disk to the host machine.
|
// Attaches the disk to the host machine.
|
||||||
|
@ -58,6 +58,9 @@ type DiskController interface {
|
||||||
|
|
||||||
// Expand the disk to new size
|
// Expand the disk to new size
|
||||||
ResizeDisk(diskURI string, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error)
|
ResizeDisk(diskURI string, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error)
|
||||||
|
|
||||||
|
// GetAzureDiskLabels gets availability zone labels for Azuredisk.
|
||||||
|
GetAzureDiskLabels(diskURI string) (map[string]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type azureDataDiskPlugin struct {
|
type azureDataDiskPlugin struct {
|
||||||
|
|
|
@ -19,12 +19,14 @@ package azure_dd
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
"k8s.io/kubernetes/pkg/cloudprovider/providers/azure"
|
||||||
"k8s.io/kubernetes/pkg/features"
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/volume"
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
"k8s.io/kubernetes/pkg/volume/util"
|
"k8s.io/kubernetes/pkg/volume/util"
|
||||||
|
@ -68,6 +70,21 @@ func (d *azureDiskDeleter) Delete() error {
|
||||||
return diskController.DeleteBlobDisk(volumeSource.DataDiskURI)
|
return diskController.DeleteBlobDisk(volumeSource.DataDiskURI)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseZoned parsed 'zoned' for storage class. If zoned is not specified (empty string),
|
||||||
|
// then it defaults to true for managed disks.
|
||||||
|
func parseZoned(zonedString string, kind v1.AzureDataDiskKind) (bool, error) {
|
||||||
|
if zonedString == "" {
|
||||||
|
return kind == v1.AzureManagedDisk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
zoned, err := strconv.ParseBool(zonedString)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to parse 'zoned': %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return zoned, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
||||||
if !util.AccessModesContainedInAll(p.plugin.GetAccessModes(), p.options.PVC.Spec.AccessModes) {
|
if !util.AccessModesContainedInAll(p.plugin.GetAccessModes(), p.options.PVC.Spec.AccessModes) {
|
||||||
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", p.options.PVC.Spec.AccessModes, p.plugin.GetAccessModes())
|
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", p.options.PVC.Spec.AccessModes, p.plugin.GetAccessModes())
|
||||||
|
@ -85,7 +102,7 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie
|
||||||
|
|
||||||
if len(p.options.PVC.Spec.AccessModes) == 1 {
|
if len(p.options.PVC.Spec.AccessModes) == 1 {
|
||||||
if p.options.PVC.Spec.AccessModes[0] != supportedModes[0] {
|
if p.options.PVC.Spec.AccessModes[0] != supportedModes[0] {
|
||||||
return nil, fmt.Errorf("AzureDisk - mode %s is not supporetd by AzureDisk plugin supported mode is %s", p.options.PVC.Spec.AccessModes[0], supportedModes)
|
return nil, fmt.Errorf("AzureDisk - mode %s is not supported by AzureDisk plugin (supported mode is %s)", p.options.PVC.Spec.AccessModes[0], supportedModes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +113,13 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie
|
||||||
strKind string
|
strKind string
|
||||||
err error
|
err error
|
||||||
resourceGroup string
|
resourceGroup string
|
||||||
|
|
||||||
|
zoned bool
|
||||||
|
zonePresent bool
|
||||||
|
zonesPresent bool
|
||||||
|
strZoned string
|
||||||
|
availabilityZone string
|
||||||
|
availabilityZones string
|
||||||
)
|
)
|
||||||
// maxLength = 79 - (4 for ".vhd") = 75
|
// maxLength = 79 - (4 for ".vhd") = 75
|
||||||
name := util.GenerateVolumeName(p.options.ClusterName, p.options.PVName, 75)
|
name := util.GenerateVolumeName(p.options.ClusterName, p.options.PVName, 75)
|
||||||
|
@ -123,6 +147,14 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie
|
||||||
fsType = strings.ToLower(v)
|
fsType = strings.ToLower(v)
|
||||||
case "resourcegroup":
|
case "resourcegroup":
|
||||||
resourceGroup = v
|
resourceGroup = v
|
||||||
|
case "zone":
|
||||||
|
zonePresent = true
|
||||||
|
availabilityZone = v
|
||||||
|
case "zones":
|
||||||
|
zonesPresent = true
|
||||||
|
availabilityZones = v
|
||||||
|
case "zoned":
|
||||||
|
strZoned = v
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("AzureDisk - invalid option %s in storage class", k)
|
return nil, fmt.Errorf("AzureDisk - invalid option %s in storage class", k)
|
||||||
}
|
}
|
||||||
|
@ -139,6 +171,19 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zoned, err = parseZoned(strZoned, kind)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !zoned && (zonePresent || zonesPresent) {
|
||||||
|
return nil, fmt.Errorf("zone or zones StorageClass parameters must be used together with zoned parameter")
|
||||||
|
}
|
||||||
|
|
||||||
|
if zonePresent && zonesPresent {
|
||||||
|
return nil, fmt.Errorf("both zone and zones StorageClass parameters must not be used at the same time")
|
||||||
|
}
|
||||||
|
|
||||||
if cachingMode, err = normalizeCachingMode(cachingMode); err != nil {
|
if cachingMode, err = normalizeCachingMode(cachingMode); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -154,16 +199,39 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie
|
||||||
|
|
||||||
// create disk
|
// create disk
|
||||||
diskURI := ""
|
diskURI := ""
|
||||||
|
labels := map[string]string{}
|
||||||
if kind == v1.AzureManagedDisk {
|
if kind == v1.AzureManagedDisk {
|
||||||
tags := make(map[string]string)
|
tags := make(map[string]string)
|
||||||
if p.options.CloudTags != nil {
|
if p.options.CloudTags != nil {
|
||||||
tags = *(p.options.CloudTags)
|
tags = *(p.options.CloudTags)
|
||||||
}
|
}
|
||||||
diskURI, err = diskController.CreateManagedDisk(name, skuName, resourceGroup, requestGiB, tags)
|
|
||||||
|
volumeOptions := &azure.ManagedDiskOptions{
|
||||||
|
DiskName: name,
|
||||||
|
StorageAccountType: skuName,
|
||||||
|
ResourceGroup: resourceGroup,
|
||||||
|
PVCName: p.options.PVC.Name,
|
||||||
|
SizeGB: requestGiB,
|
||||||
|
Tags: tags,
|
||||||
|
Zoned: zoned,
|
||||||
|
ZonePresent: zonePresent,
|
||||||
|
ZonesPresent: zonesPresent,
|
||||||
|
AvailabilityZone: availabilityZone,
|
||||||
|
AvailabilityZones: availabilityZones,
|
||||||
|
}
|
||||||
|
diskURI, err = diskController.CreateManagedDisk(volumeOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
labels, err = diskController.GetAzureDiskLabels(diskURI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if zoned {
|
||||||
|
return nil, errors.New("zoned parameter is only supported for managed disks")
|
||||||
|
}
|
||||||
|
|
||||||
if kind == v1.AzureDedicatedBlobDisk {
|
if kind == v1.AzureDedicatedBlobDisk {
|
||||||
_, diskURI, _, err = diskController.CreateVolume(name, account, storageAccountType, location, requestGiB)
|
_, diskURI, _, err = diskController.CreateVolume(name, account, storageAccountType, location, requestGiB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -189,7 +257,7 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie
|
||||||
pv := &v1.PersistentVolume{
|
pv := &v1.PersistentVolume{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: p.options.PVName,
|
Name: p.options.PVName,
|
||||||
Labels: map[string]string{},
|
Labels: labels,
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"volumehelper.VolumeDynamicallyCreatedByKey": "azure-disk-dynamic-provisioner",
|
"volumehelper.VolumeDynamicallyCreatedByKey": "azure-disk-dynamic-provisioner",
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue