Add availability zone support for dynamic provisioning Azure managed disks

pull/8/head
Pengfei Ni 2018-07-23 16:38:47 +08:00
parent b258bbad6a
commit 74813d0d26
5 changed files with 144 additions and 20 deletions

View File

@ -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"
) )

View File

@ -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

View File

@ -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) {

View File

@ -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 {

View File

@ -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",
}, },