pkg/operator/options/options.go (122 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 options
import (
"context"
"errors"
"flag"
"fmt"
"hash/fnv"
"math/rand"
"net/url"
"os"
"strings"
"github.com/Azure/karpenter-provider-azure/pkg/consts"
"github.com/Azure/karpenter-provider-azure/pkg/utils"
"k8s.io/apimachinery/pkg/util/sets"
coreoptions "sigs.k8s.io/karpenter/pkg/operator/options"
"sigs.k8s.io/karpenter/pkg/utils/env"
)
func init() {
coreoptions.Injectables = append(coreoptions.Injectables, &Options{})
}
type nodeIdentitiesValue []string
func newNodeIdentitiesValue(val string, p *[]string) *nodeIdentitiesValue {
*p = []string{}
if val != "" {
*p = strings.Split(val, ",")
}
return (*nodeIdentitiesValue)(p)
}
func (s *nodeIdentitiesValue) Set(val string) error {
*s = nodeIdentitiesValue(strings.Split(val, ","))
return nil
}
func (s *nodeIdentitiesValue) Get() any { return []string(*s) }
func (s *nodeIdentitiesValue) String() string { return strings.Join(*s, ",") }
type optionsKey struct{}
type Options struct {
ClusterName string
ClusterEndpoint string // => APIServerName in bootstrap, except needs to be w/o https/port
VMMemoryOverheadPercent float64
ClusterID string
KubeletClientTLSBootstrapToken string // => TLSBootstrapToken in bootstrap (may need to be per node/nodepool)
SSHPublicKey string // ssh.publicKeys.keyData => VM SSH public key // TODO: move to v1alpha2.AKSNodeClass?
NetworkPlugin string // => NetworkPlugin in bootstrap
NetworkPolicy string // => NetworkPolicy in bootstrap
NetworkPluginMode string // => Network Plugin Mode is used to control the mode the network plugin should operate in. For example, "overlay" used with --network-plugin=azure will use an overlay network (non-VNET IPs) for pods in the cluster. Learn more about overlay networking here: https://learn.microsoft.com/en-us/azure/aks/azure-cni-overlay?tabs=kubectl#overview-of-overlay-networking
NetworkDataplane string
NodeIdentities []string // => Applied onto each VM
VnetGUID string // resource guid used by azure cni for identifying the right vnet
SubnetID string // => VnetSubnetID to use (for nodes in Azure CNI Overlay and Azure CNI + pod subnet; for for nodes and pods in Azure CNI), unless overridden via AKSNodeClass
setFlags map[string]bool
ProvisionMode string
NodeBootstrappingServerURL string
UseSIG bool // => UseSIG is true if Karpenter is managed by AKS, false if it is a self-hosted karpenter installation
SIGSubscriptionID string
NodeResourceGroup string
}
func (o *Options) AddFlags(fs *coreoptions.FlagSet) {
fs.StringVar(&o.ClusterName, "cluster-name", env.WithDefaultString("CLUSTER_NAME", ""), "[REQUIRED] The kubernetes cluster name for resource tags.")
fs.StringVar(&o.ClusterEndpoint, "cluster-endpoint", env.WithDefaultString("CLUSTER_ENDPOINT", ""), "[REQUIRED] The external kubernetes cluster endpoint for new nodes to connect with.")
fs.Float64Var(&o.VMMemoryOverheadPercent, "vm-memory-overhead-percent", utils.WithDefaultFloat64("VM_MEMORY_OVERHEAD_PERCENT", 0.075), "The VM memory overhead as a percent that will be subtracted from the total memory for all instance types.")
fs.StringVar(&o.KubeletClientTLSBootstrapToken, "kubelet-bootstrap-token", env.WithDefaultString("KUBELET_BOOTSTRAP_TOKEN", ""), "[REQUIRED] The bootstrap token for new nodes to join the cluster.")
fs.StringVar(&o.SSHPublicKey, "ssh-public-key", env.WithDefaultString("SSH_PUBLIC_KEY", ""), "[REQUIRED] VM SSH public key.")
fs.StringVar(&o.NetworkPlugin, "network-plugin", env.WithDefaultString("NETWORK_PLUGIN", consts.NetworkPluginAzure), "The network plugin used by the cluster.")
fs.StringVar(&o.NetworkPluginMode, "network-plugin-mode", env.WithDefaultString("NETWORK_PLUGIN_MODE", consts.NetworkPluginModeOverlay), "network plugin mode of the cluster.")
fs.StringVar(&o.NetworkPolicy, "network-policy", env.WithDefaultString("NETWORK_POLICY", ""), "The network policy used by the cluster.")
fs.StringVar(&o.NetworkDataplane, "network-dataplane", env.WithDefaultString("NETWORK_DATAPLANE", "cilium"), "The network dataplane used by the cluster.")
fs.StringVar(&o.VnetGUID, "vnet-guid", env.WithDefaultString("VNET_GUID", ""), "The vnet guid of the clusters vnet, only required by azure cni with overlay + byo vnet")
fs.StringVar(&o.SubnetID, "vnet-subnet-id", env.WithDefaultString("VNET_SUBNET_ID", ""), "The default subnet ID to use for new nodes. This must be a valid ARM resource ID for subnet that does not overlap with the service CIDR or the pod CIDR.")
fs.Var(newNodeIdentitiesValue(env.WithDefaultString("NODE_IDENTITIES", ""), &o.NodeIdentities), "node-identities", "User assigned identities for nodes.")
fs.StringVar(&o.ProvisionMode, "provision-mode", env.WithDefaultString("PROVISION_MODE", consts.ProvisionModeAKSScriptless), "[UNSUPPORTED] The provision mode for the cluster.")
fs.StringVar(&o.NodeBootstrappingServerURL, "nodebootstrapping-server-url", env.WithDefaultString("NODEBOOTSTRAPPING_SERVER_URL", ""), "[UNSUPPORTED] The url for the node bootstrapping provider server.")
fs.StringVar(&o.NodeResourceGroup, "node-resource-group", env.WithDefaultString("AZURE_NODE_RESOURCE_GROUP", ""), "[REQUIRED] the resource group created and managed by AKS where the nodes live")
fs.BoolVar(&o.UseSIG, "use-sig", env.WithDefaultBool("USE_SIG", false), "If set to true karpenter will use the AKS managed shared image galleries and the node image versions api. If set to false karpenter will use community image galleries. Only a subset of image features will be available in the community image galleries and this flag is only for the managed node provisioning addon.")
fs.StringVar(&o.SIGSubscriptionID, "sig-subscription-id", env.WithDefaultString("SIG_SUBSCRIPTION_ID", ""), "The subscription ID of the shared image gallery.")
}
func (o Options) GetAPIServerName() string {
endpoint, _ := url.Parse(o.ClusterEndpoint) // assume to already validated
return endpoint.Hostname()
}
func (o *Options) Parse(fs *coreoptions.FlagSet, args ...string) error {
if err := fs.Parse(args); err != nil {
if errors.Is(err, flag.ErrHelp) {
os.Exit(0)
}
return fmt.Errorf("parsing flags, %w", err)
}
// Check if each option has been set. This is a little brute force and better options might exist,
// but this only needs to be here for one version
o.setFlags = map[string]bool{}
cliFlags := sets.New[string]()
fs.Visit(func(f *flag.Flag) {
cliFlags.Insert(f.Name)
})
fs.VisitAll(func(f *flag.Flag) {
envName := strings.ReplaceAll(strings.ToUpper(f.Name), "-", "_")
_, ok := os.LookupEnv(envName)
o.setFlags[f.Name] = ok || cliFlags.Has(f.Name)
})
if err := o.Validate(); err != nil {
return fmt.Errorf("validating options, %w", err)
}
// ClusterID is generated from cluster endpoint
o.ClusterID = getAKSClusterID(o.GetAPIServerName())
return nil
}
func (o *Options) ToContext(ctx context.Context) context.Context {
return ToContext(ctx, o)
}
func ToContext(ctx context.Context, opts *Options) context.Context {
return context.WithValue(ctx, optionsKey{}, opts)
}
func FromContext(ctx context.Context) *Options {
retval := ctx.Value(optionsKey{})
if retval == nil {
return nil
}
return retval.(*Options)
}
// getAKSClusterID returns cluster ID based on the DNS prefix of the cluster.
// The logic comes from AgentBaker and other places, originally from aks-engine
// with the additional assumption of DNS prefix being the first 33 chars of FQDN
func getAKSClusterID(apiServerFQDN string) string {
dnsPrefix := apiServerFQDN[:33]
h := fnv.New64a()
h.Write([]byte(dnsPrefix))
r := rand.New(rand.NewSource(int64(h.Sum64()))) //nolint:gosec
return fmt.Sprintf("%08d", r.Uint32())[:8]
}