class MU::Cloud::Azure::ContainerCluster
A Kubernetes cluster as configured in {MU::Config::BasketofKittens::container_clusters}
Public Class Methods
Stub method. Azure
resources are cleaned up by removing the parent resource group. @return [void]
# File modules/mu/providers/azure/container_cluster.rb, line 144 def self.cleanup(**args) end
Locate and return cloud provider descriptors of this resource type which match the provided parameters, or all visible resources if no filters are specified. At minimum, implementations of find
must honor credentials
and cloud_id
arguments. We may optionally support other search methods, such as tag_key
and tag_value
, or cloud-specific arguments like project
. See also {MU::MommaCat.findStray}. @param args [Hash]: Hash
of named arguments passed via Ruby's double-splat @return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching resources
# File modules/mu/providers/azure/container_cluster.rb, line 86 def self.find(**args) found = {} # Azure resources are namedspaced by resource group. If we weren't # told one, we may have to search all the ones we can see. resource_groups = if args[:resource_group] [args[:resource_group]] elsif args[:cloud_id] and args[:cloud_id].is_a?(MU::Cloud::Azure::Id) [args[:cloud_id].resource_group] else MU::Cloud::Azure.resources(credentials: args[:credentials]).resource_groups.list.map { |rg| rg.name } end if args[:cloud_id] id_str = args[:cloud_id].is_a?(MU::Cloud::Azure::Id) ? args[:cloud_id].name : args[:cloud_id] resource_groups.each { |rg| resp = MU::Cloud::Azure.containers(credentials: args[:credentials]).managed_clusters.get(rg, id_str) found[Id.new(resp.id)] = resp if resp } else if args[:resource_group] MU::Cloud::Azure.containers(credentials: args[:credentials]).managed_clusters.list_by_resource_group(args[:resource_group]).each { |cluster| found[Id.new(cluster.id)] = cluster } else MU::Cloud::Azure.containers(credentials: args[:credentials]).managed_clusters.list.each { |cluster| found[Id.new(cluster.id)] = cluster } end end found end
Does this resource type exist as a global (cloud-wide) artifact, or is it localized to a region/zone? @return [Boolean]
# File modules/mu/providers/azure/container_cluster.rb, line 131 def self.isGlobal? false end
Initialize this cloud resource object. Calling super
will invoke the initializer defined under {MU::Cloud}, which should set the attribtues listed in {MU::Cloud::PUBLIC_ATTRS} as well as applicable dependency shortcuts, like @vpc
, for us. @param args [Hash]: Hash
of named arguments passed via Ruby's double-splat
# File modules/mu/providers/azure/container_cluster.rb, line 23 def initialize(**args) super # @mu_name = mu_name ? mu_name : @deploy.getResourceName(@config["name"]) if !mu_name.nil? @mu_name = mu_name @cloud_id = Id.new(cloud_desc.id) if @cloud_id else @mu_name ||= @deploy.getResourceName(@config["name"], max_length: 31) end end
Denote whether this resource implementation is experiment, ready for testing, or ready for production use.
# File modules/mu/providers/azure/container_cluster.rb, line 137 def self.quality MU::Cloud::BETA end
Cloud-specific configuration properties. @param _config [MU::Config]: The calling MU::Config
object @return [Array<Array,Hash>]: List of required fields, and json-schema Hash
of cloud-specific configuration parameters for this resource
# File modules/mu/providers/azure/container_cluster.rb, line 150 def self.schema(_config) toplevel_required = [] schema = { "flavor" => { "enum" => ["Kubernetes", "OpenShift", "Swarm", "DC/OS"], "description" => "The Azure container platform to deploy. Currently only +Kubernetes+ is supported.", "default" => "Kubernetes" }, "platform" => { "description" => "The OS platform to deploy for workers and containers.", "default" => "Linux", "enum" => ["Linux", "Windows"] }, "max_pods" => { "type" => "integer", "description" => "Maximum number of pods allowed on this cluster", "default" => 30 }, "kubernetes" => { "default" => { "version" => "1.12.8" } }, "dns_prefix" => { "type" => "string", "description" => "DNS name prefix to use with the hosted Kubernetes API server FQDN. Will default to the global +appname+ value if not specified." }, "disk_size_gb" => { "type" => "integer", "description" => "Size of the disk attached to each worker, specified in GB. The smallest allowed disk size is 30, the largest 1024.", "default" => 100 }, } [toplevel_required, schema] end
Cloud-specific pre-processing of {MU::Config::BasketofKittens::container_clusters}, bare and unvalidated. @param cluster [Hash]: The resource to process and validate @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member @return [Boolean]: True if validation succeeded, False otherwise
# File modules/mu/providers/azure/container_cluster.rb, line 188 def self.validateConfig(cluster, configurator) ok = true # XXX validate k8s versions (master and node) # XXX validate image types # MU::Cloud::Azure.container.get_project_zone_serverconfig(@config["project"], @config['availability_zone']) cluster["dns_prefix"] ||= $myAppName # XXX woof globals wtf cluster['region'] ||= MU::Cloud::Azure.myRegion(cluster['credentials']) if cluster["disk_size_gb"] < 30 or cluster["disk_size_gb"] > 1024 MU.log "Azure ContainerCluster disk_size_gb must be between 30 and 1024.", MU::ERR ok = false end if cluster['min_size'] and cluster['instance_count'] < cluster['min_size'] cluster['instance_count'] = cluster['min_size'] end if cluster['max_size'] and cluster['instance_count'] < cluster['max_size'] cluster['instance_count'] = cluster['max_size'] end cluster['instance_type'] ||= "Standard_DS2_v2" # TODO when Server is implemented, it should have a validateInstanceType method we can use here svcacct_desc = { "name" => cluster["name"]+"user", "region" => cluster["region"], "type" => "service", "cloud" => "Azure", "create_api_key" => true, "credentials" => cluster["credentials"], "roles" => [ "Azure Kubernetes Service Cluster Admin Role" ] } MU::Config.addDependency(cluster, cluster['name']+"user", "user") ok = false if !configurator.insertKitten(svcacct_desc, "users") ok end
Public Instance Methods
Called automatically by {MU::Deploy#createResources} @return [String]: The cloud provider's identifier for this GKE instance.
# File modules/mu/providers/azure/container_cluster.rb, line 38 def create create_update end
Called automatically by {MU::Deploy#createResources}
# File modules/mu/providers/azure/container_cluster.rb, line 43 def groom create_update kube_conf = @deploy.deploy_dir+"/kubeconfig-#{@config['name']}" admin_creds = MU::Cloud::Azure.containers(credentials: @config['credentials']).managed_clusters.list_cluster_admin_credentials( @resource_group, @mu_name ) admin_creds.kubeconfigs.each { |kube| next if kube.name != "clusterAdmin" cfgfile = "" kube.value.each { |ord| cfgfile += ord.chr } File.open(kube_conf, "w"){ |k| k.puts cfgfile } } if @config['kubernetes_resources'] MU::Master.applyKubernetesResources( @config['name'], @config['kubernetes_resources'], kubeconfig: kube_conf, outputdir: @deploy.deploy_dir ) end MU.log %Q{How to interact with your AKS cluster\nkubectl --kubeconfig "#{kube_conf}" get events --all-namespaces\nkubectl --kubeconfig "#{kube_conf}" get all\nkubectl --kubeconfig "#{kube_conf}" create -f some_k8s_deploy.yml\nkubectl --kubeconfig "#{kube_conf}" get nodes}, MU::SUMMARY end
Register a description of this cluster instance with this deployment's metadata.
# File modules/mu/providers/azure/container_cluster.rb, line 121 def notify base = MU.structToHash(cloud_desc) base["cloud_id"] = @cloud_id.name base.merge!(@config.to_h) base end
Private Instance Methods
# File modules/mu/providers/azure/container_cluster.rb, line 230 def create_update need_apply = false ext_cluster = MU::Cloud::Azure.containers(credentials: @config[:credentials]).managed_clusters.get( @resource_group, @mu_name ) if ext_cluster @cloud_id = MU::Cloud::Azure::Id.new(ext_cluster.id) end key_obj = MU::Cloud::Azure.containers(:ContainerServiceSshPublicKey).new key_obj.key_data = @deploy.ssh_public_key ssh_obj = MU::Cloud::Azure.containers(:ContainerServiceSshConfiguration).new ssh_obj.public_keys = [key_obj] os_profile_obj = if !ext_cluster if @config['platform'] == "Windows" os_obj = MU::Cloud::Azure.containers(:ContainerServiceWindowsProfile, model_version: "V2019_02_01").new os_obj.admin_username = "muadmin" # Azure password constraints are extra-annoying winpass = MU.generateWindowsPassword(safe_pattern: '!@#$%^&*()', retries: 150) # TODO store this somewhere the user can get at it os_obj.admin_password = winpass os_obj else os_obj = MU::Cloud::Azure.containers(:ContainerServiceLinuxProfile).new os_obj.admin_username = "muadmin" os_obj.ssh = ssh_obj os_obj end else # Azure does not support updates to this parameter @config['platform'] == "Windows" ? ext_cluster.windows_profile : ext_cluster.linux_profile end svc_principal_obj = MU::Cloud::Azure.containers(:ManagedClusterServicePrincipalProfile).new # XXX this should come from a MU::Cloud::Azure::User object, but right now # there's no way to get the 'secret' field from a user-assigned identity afaict # For now, we'll cheat with Mu's system credentials. creds = MU::Cloud::Azure.credConfig(@config['credentials']) svc_principal_obj.client_id = creds["client_id"] svc_principal_obj.secret = creds["client_secret"] # svc_acct = @deploy.findLitterMate(type: "user", name: @config['name']+"user") # raise MuError, "Failed to locate service account #{@config['name']}user" if !svc_acct # svc_principal_obj.client_id = svc_acct.cloud_desc.client_id # svc_principal_obj.secret = svc_acct.getSecret agent_profiles = if !ext_cluster profile_obj = MU::Cloud::Azure.containers(:ManagedClusterAgentPoolProfile).new profile_obj.name = @deploy.getResourceName(@config["name"], max_length: 11).downcase.gsub(/[^0-9a-z]/, "") if @config['min_size'] and @config['max_size'] # Special API features need to be enabled for scaling MU::Cloud::Azure.ensureFeature("Microsoft.ContainerService/WindowsPreview", credentials: @config['credentials']) MU::Cloud::Azure.ensureFeature("Microsoft.ContainerService/VMSSPreview", credentials: @config['credentials']) profile_obj.min_count = @config['min_size'] profile_obj.max_count = @config['max_size'] profile_obj.enable_auto_scaling = true profile_obj.type = MU::Cloud::Azure.containers(:AgentPoolType)::VirtualMachineScaleSets # XXX if you actually try to do this: # BadRequest: Virtual Machine Scale Set agent nodes are not allowed since feature "Microsoft.ContainerService/WindowsPreview" is not enabled. end profile_obj.count = @config['instance_count'] profile_obj.vm_size = @config['instance_type'] profile_obj.max_pods = @config['max_pods'] profile_obj.os_type = @config['platform'] profile_obj.os_disk_size_gb = @config['disk_size_gb'] # XXX correlate this with the one(s) we configured in @config['vpc'] # profile_obj.vnet_subnet_id = @vpc.subnets.first.cloud_desc.id # XXX has to have its own subnet for k8s apparently [profile_obj] else # Azure does not support adding/removing agent profiles to a live # cluster, but it does support changing some values on an existing # one. profile_obj = ext_cluster.agent_pool_profiles.first nochange_map = { "disk_size_gb" => :os_disk_size_gb, "instance_type" => :vm_size, "platform" => :os_type, "max_pods" => :max_pods, } tried_to_change =[] nochange_map.each_pair { |cfg, attribute| if @config.has_key?(cfg) and @config[cfg] != profile_obj.send(attribute) tried_to_change << cfg end } if @config['min_size'] and @config['max_size'] and !profile_obj.enable_auto_scaling tried_to_change << "enable_auto_scaling" end if tried_to_change.size > 0 MU.log "Changes specified to one or more immutable AKS Agent Pool parameters in cluster #{@mu_name}, ignoring.", MU::NOTICE, details: tried_to_change end if @config['min_size'] and @config['max_size'] and profile_obj.enable_auto_scaling and ( profile_obj.min_count != @config['min_size'] or profile_obj.max_count != @config['max_size'] ) profile_obj.min_count = @config['min_size'] profile_obj.max_count = @config['max_size'] need_apply = true end if profile_obj.count != @config['instance_count'] profile_obj.count = @config['instance_count'] need_apply = true end [profile_obj] end cluster_obj = MU::Cloud::Azure.containers(:ManagedCluster).new if ext_cluster cluster_obj.dns_prefix = ext_cluster.dns_prefix cluster_obj.location = ext_cluster.location else # Azure does not support updates to these parameters cluster_obj.dns_prefix = @config['dns_prefix'] cluster_obj.location = @config['region'] end cluster_obj.tags = @tags cluster_obj.service_principal_profile = svc_principal_obj if @config['platform'] == "Windows" cluster_obj.windows_profile = os_profile_obj else cluster_obj.linux_profile = os_profile_obj end # cluster_obj.api_server_authorized_ipranges = [MU.mu_public_ip+"/32", MU.my_private_ip+"/32"] # XXX only allowed with Microsoft.ContainerService/APIServerSecurityPreview enabled cluster_obj.agent_pool_profiles = agent_profiles if @config['flavor'] == "Kubernetes" cluster_obj.kubernetes_version = @config['kubernetes']['version'].to_s if ext_cluster and @config['kubernetes']['version'] != ext_cluster.kubernetes_version need_apply = true end end # XXX it may be possible to create a new AgentPool and fall forward into it? # API behavior suggests otherwise. Project for later. # pool_obj = MU::Cloud::Azure.containers(:AgentPool).new # pool_obj.count = @config['instance_count'] # pool_obj.vm_size = "Standard_DS2_v2" if !ext_cluster pp cluster_obj MU.log "Creating AKS cluster #{@mu_name}", details: cluster_obj need_apply = true elsif need_apply MU.log "Updating AKS cluster #{@mu_name}", MU::NOTICE, details: cluster_obj end if need_apply resp = MU::Cloud::Azure.containers(credentials: @config['credentials']).managed_clusters.create_or_update( @resource_group, @mu_name, cluster_obj ) @cloud_id = Id.new(resp.id) end end