pkg/providers/launchtemplate/launchtemplate.go (188 lines of code) (raw):
/*
Portions Copyright (c) Microsoft Corporation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package launchtemplate
import (
"context"
"strconv"
"strings"
"github.com/Azure/go-autorest/autorest/to"
"github.com/Azure/karpenter-provider-azure/pkg/providers/imagefamily"
"github.com/Azure/karpenter-provider-azure/pkg/providers/launchtemplate/parameters"
"github.com/Azure/karpenter-provider-azure/pkg/utils"
"github.com/samber/lo"
v1 "k8s.io/api/core/v1"
"github.com/Azure/karpenter-provider-azure/pkg/apis/v1alpha2"
"github.com/Azure/karpenter-provider-azure/pkg/consts"
"github.com/Azure/karpenter-provider-azure/pkg/operator/options"
karpv1 "sigs.k8s.io/karpenter/pkg/apis/v1"
"sigs.k8s.io/karpenter/pkg/cloudprovider"
"sigs.k8s.io/karpenter/pkg/scheduling"
)
const (
karpenterManagedTagKey = "karpenter.azure.com/cluster"
dataplaneLabel = "kubernetes.azure.com/ebpf-dataplane"
azureCNIOverlayLabel = "kubernetes.azure.com/azure-cni-overlay"
subnetNameLabel = "kubernetes.azure.com/network-subnet"
vnetGUIDLabel = "kubernetes.azure.com/nodenetwork-vnetguid"
podNetworkTypeLabel = "kubernetes.azure.com/podnetwork-type"
)
type Template struct {
ScriptlessCustomData string
ImageID string
SubnetID string
Tags map[string]*string
CustomScriptsCustomData string
CustomScriptsCSE string
IsWindows bool
StorageProfile string
}
type Provider struct {
imageFamily imagefamily.Resolver
imageProvider *imagefamily.Provider
caBundle *string
clusterEndpoint string
tenantID string
subscriptionID string
kubeletIdentityClientID string
resourceGroup string
clusterResourceGroup string
location string
vnetGUID string
provisionMode string
}
// TODO: add caching of launch templates
func NewProvider(_ context.Context, imageFamily imagefamily.Resolver, imageProvider *imagefamily.Provider, caBundle *string, clusterEndpoint string,
tenantID, subscriptionID, clusterResourceGroup string, kubeletIdentityClientID, resourceGroup, location, vnetGUID, provisionMode string,
) *Provider {
return &Provider{
imageFamily: imageFamily,
imageProvider: imageProvider,
caBundle: caBundle,
clusterEndpoint: clusterEndpoint,
tenantID: tenantID,
subscriptionID: subscriptionID,
kubeletIdentityClientID: kubeletIdentityClientID,
resourceGroup: resourceGroup,
clusterResourceGroup: clusterResourceGroup,
location: location,
vnetGUID: vnetGUID,
provisionMode: provisionMode,
}
}
func (p *Provider) GetTemplate(ctx context.Context, nodeClass *v1alpha2.AKSNodeClass, nodeClaim *karpv1.NodeClaim,
instanceType *cloudprovider.InstanceType, additionalLabels map[string]string) (*Template, error) {
staticParameters, err := p.getStaticParameters(ctx, instanceType, nodeClass, lo.Assign(nodeClaim.Labels, additionalLabels))
if err != nil {
return nil, err
}
kubernetesVersion, err := nodeClass.GetKubernetesVersion()
if err != nil {
// Note: we check GetKubernetesVersion for errors at the start of the Create call, so this case should not happen.
return nil, err
}
staticParameters.KubernetesVersion = kubernetesVersion
templateParameters, err := p.imageFamily.Resolve(ctx, nodeClass, nodeClaim, instanceType, staticParameters)
if err != nil {
return nil, err
}
launchTemplate, err := p.createLaunchTemplate(ctx, templateParameters)
if err != nil {
return nil, err
}
return launchTemplate, nil
}
func (p *Provider) getStaticParameters(ctx context.Context, instanceType *cloudprovider.InstanceType, nodeClass *v1alpha2.AKSNodeClass, labels map[string]string) (*parameters.StaticParameters, error) {
var arch string = karpv1.ArchitectureAmd64
if err := instanceType.Requirements.Compatible(scheduling.NewRequirements(scheduling.NewRequirement(v1.LabelArchStable, v1.NodeSelectorOpIn, karpv1.ArchitectureArm64))); err == nil {
arch = karpv1.ArchitectureArm64
}
subnetID := lo.Ternary(nodeClass.Spec.VNETSubnetID != nil, lo.FromPtr(nodeClass.Spec.VNETSubnetID), options.FromContext(ctx).SubnetID)
if isAzureCNIOverlay(ctx) {
// TODO: make conditional on pod subnet
vnetLabels, err := p.getVnetInfoLabels(subnetID)
if err != nil {
return nil, err
}
labels = lo.Assign(labels, vnetLabels)
}
if options.FromContext(ctx).NetworkDataplane == consts.NetworkDataplaneCilium {
// This label is required for the cilium agent daemonset because
// we select the nodes for the daemonset based on this label
// - key: kubernetes.azure.com/ebpf-dataplane
// operator: In
// values:
// - cilium
labels[dataplaneLabel] = consts.NetworkDataplaneCilium
}
return ¶meters.StaticParameters{
ClusterName: options.FromContext(ctx).ClusterName,
ClusterEndpoint: p.clusterEndpoint,
Tags: nodeClass.Spec.Tags,
Labels: labels,
CABundle: p.caBundle,
Arch: arch,
GPUNode: utils.IsNvidiaEnabledSKU(instanceType.Name),
GPUDriverVersion: utils.GetGPUDriverVersion(instanceType.Name),
GPUDriverType: utils.GetGPUDriverType(instanceType.Name),
GPUImageSHA: utils.GetAKSGPUImageSHA(instanceType.Name),
TenantID: p.tenantID,
SubscriptionID: p.subscriptionID,
KubeletIdentityClientID: p.kubeletIdentityClientID,
ResourceGroup: p.resourceGroup,
Location: p.location,
ClusterID: options.FromContext(ctx).ClusterID,
APIServerName: options.FromContext(ctx).GetAPIServerName(),
KubeletClientTLSBootstrapToken: options.FromContext(ctx).KubeletClientTLSBootstrapToken,
NetworkPlugin: getAgentbakerNetworkPlugin(ctx),
NetworkPolicy: options.FromContext(ctx).NetworkPolicy,
SubnetID: subnetID,
ClusterResourceGroup: p.clusterResourceGroup,
}, nil
}
func getAgentbakerNetworkPlugin(ctx context.Context) string {
if isAzureCNIOverlay(ctx) || isCiliumNodeSubnet(ctx) || isNetworkPluginNone(ctx) {
return consts.NetworkPluginNone
}
return consts.NetworkPluginAzure
}
func isNetworkPluginNone(ctx context.Context) bool {
return options.FromContext(ctx).NetworkPlugin == consts.NetworkPluginNone
}
func isCiliumNodeSubnet(ctx context.Context) bool {
return options.FromContext(ctx).NetworkPlugin == consts.NetworkPluginAzure && options.FromContext(ctx).NetworkPluginMode == consts.NetworkPluginModeNone && options.FromContext(ctx).NetworkDataplane == consts.NetworkDataplaneCilium
}
func isAzureCNIOverlay(ctx context.Context) bool {
return options.FromContext(ctx).NetworkPlugin == consts.NetworkPluginAzure && options.FromContext(ctx).NetworkPluginMode == consts.NetworkPluginModeOverlay
}
func (p *Provider) createLaunchTemplate(ctx context.Context, params *parameters.Parameters) (*Template, error) {
// merge and convert to ARM tags
azureTags := mergeTags(params.Tags, map[string]string{karpenterManagedTagKey: params.ClusterName})
template := &Template{
ImageID: params.ImageID,
Tags: azureTags,
SubnetID: params.SubnetID,
IsWindows: params.IsWindows,
StorageProfile: params.StorageProfile,
}
if p.provisionMode == consts.ProvisionModeBootstrappingClient {
customData, cse, err := params.CustomScriptsNodeBootstrapping.GetCustomDataAndCSE(ctx)
if err != nil {
return nil, err
}
template.CustomScriptsCustomData = customData
template.CustomScriptsCSE = cse
} else {
// render user data
userData, err := params.ScriptlessCustomData.Script()
if err != nil {
return nil, err
}
template.ScriptlessCustomData = userData
}
return template, nil
}
// MergeTags takes a variadic list of maps and merges them together
// with format acceptable to ARM (no / in keys, pointer to strings as values)
func mergeTags(tags ...map[string]string) (result map[string]*string) {
return lo.MapEntries(lo.Assign(tags...), func(key string, value string) (string, *string) {
return strings.ReplaceAll(key, "/", "_"), to.StringPtr(value)
})
}
func (p *Provider) getVnetInfoLabels(subnetID string) (map[string]string, error) {
vnetSubnetComponents, err := utils.GetVnetSubnetIDComponents(subnetID)
if err != nil {
return nil, err
}
vnetLabels := map[string]string{
subnetNameLabel: vnetSubnetComponents.SubnetName,
vnetGUIDLabel: p.vnetGUID,
azureCNIOverlayLabel: strconv.FormatBool(true),
podNetworkTypeLabel: consts.NetworkPluginModeOverlay,
}
return vnetLabels, nil
}