Merge pull request #9490 from johngmyers/newcluster-4

Move more cluster creation code to NewCluster()
This commit is contained in:
Kubernetes Prow Robot 2020-07-06 16:23:57 -07:00 committed by GitHub
commit 222756b35d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 283 additions and 359 deletions

View File

@ -73,7 +73,6 @@ go_library(
"//pkg/instancegroups:go_default_library", "//pkg/instancegroups:go_default_library",
"//pkg/kopscodecs:go_default_library", "//pkg/kopscodecs:go_default_library",
"//pkg/kubeconfig:go_default_library", "//pkg/kubeconfig:go_default_library",
"//pkg/model/components:go_default_library",
"//pkg/pki:go_default_library", "//pkg/pki:go_default_library",
"//pkg/pretty:go_default_library", "//pkg/pretty:go_default_library",
"//pkg/resources:go_default_library", "//pkg/resources:go_default_library",
@ -85,8 +84,6 @@ go_library(
"//upup/pkg/fi:go_default_library", "//upup/pkg/fi:go_default_library",
"//upup/pkg/fi/cloudup:go_default_library", "//upup/pkg/fi/cloudup:go_default_library",
"//upup/pkg/fi/cloudup/awsup:go_default_library", "//upup/pkg/fi/cloudup/awsup:go_default_library",
"//upup/pkg/fi/cloudup/gce:go_default_library",
"//upup/pkg/fi/cloudup/openstack:go_default_library",
"//upup/pkg/fi/utils:go_default_library", "//upup/pkg/fi/utils:go_default_library",
"//upup/pkg/kutil:go_default_library", "//upup/pkg/kutil:go_default_library",
"//util/pkg/tables:go_default_library", "//util/pkg/tables:go_default_library",

View File

@ -26,27 +26,20 @@ import (
"os" "os"
"strings" "strings"
"github.com/blang/semver/v4"
"github.com/spf13/cobra" "github.com/spf13/cobra"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/klog" "k8s.io/klog"
"k8s.io/kops/cmd/kops/util" "k8s.io/kops/cmd/kops/util"
api "k8s.io/kops/pkg/apis/kops" api "k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/apis/kops/registry" "k8s.io/kops/pkg/apis/kops/registry"
version "k8s.io/kops/pkg/apis/kops/util"
"k8s.io/kops/pkg/apis/kops/validation" "k8s.io/kops/pkg/apis/kops/validation"
"k8s.io/kops/pkg/assets" "k8s.io/kops/pkg/assets"
"k8s.io/kops/pkg/commands" "k8s.io/kops/pkg/commands"
"k8s.io/kops/pkg/dns" "k8s.io/kops/pkg/dns"
"k8s.io/kops/pkg/featureflag" "k8s.io/kops/pkg/featureflag"
"k8s.io/kops/pkg/model/components"
"k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup" "k8s.io/kops/upup/pkg/fi/cloudup"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/gce"
"k8s.io/kops/upup/pkg/fi/cloudup/openstack"
"k8s.io/kops/upup/pkg/fi/utils" "k8s.io/kops/upup/pkg/fi/utils"
"k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates" "k8s.io/kubectl/pkg/util/templates"
@ -60,20 +53,16 @@ type CreateClusterOptions struct {
MasterSize string MasterSize string
MasterVolumeSize int32 MasterVolumeSize int32
NodeVolumeSize int32 NodeVolumeSize int32
Project string
KubernetesVersion string
ContainerRuntime string ContainerRuntime string
OutDir string OutDir string
Image string Image string
NodeImage string NodeImage string
MasterImage string MasterImage string
UtilitySubnetIDs []string
DisableSubnetTags bool DisableSubnetTags bool
NetworkCIDR string NetworkCIDR string
DNSZone string DNSZone string
AdminAccess []string AdminAccess []string
SSHAccess []string SSHAccess []string
Networking string
NodeSecurityGroups []string NodeSecurityGroups []string
MasterSecurityGroups []string MasterSecurityGroups []string
AssociatePublicIP *bool AssociatePublicIP *bool
@ -84,15 +73,6 @@ type CreateClusterOptions struct {
// Overrides allows settings values direct in the spec // Overrides allows settings values direct in the spec
Overrides []string Overrides []string
// The network topology to use
Topology string
// The DNS type to use (public/private)
DNSType string
// Enable/Disable Bastion Host complete setup
Bastion bool
// Specify tags for AWS instance groups // Specify tags for AWS instance groups
CloudLabels string CloudLabels string
@ -109,15 +89,8 @@ type CreateClusterOptions struct {
// Allow custom public master name // Allow custom public master name
MasterPublicName string MasterPublicName string
// Spotinst options
SpotinstProduct string
SpotinstOrientation string
OpenstackNetworkID string OpenstackNetworkID string
// GCEServiceAccount specifies the service account with which the GCE VM runs
GCEServiceAccount string
// DryRun mode output a cluster manifest of Output type. // DryRun mode output a cluster manifest of Output type.
DryRun bool DryRun bool
// Output type during a DryRun // Output type during a DryRun
@ -129,10 +102,6 @@ func (o *CreateClusterOptions) InitDefaults() {
o.Yes = false o.Yes = false
o.Target = cloudup.TargetDirect o.Target = cloudup.TargetDirect
o.Networking = "kubenet"
o.Topology = api.TopologyPublic
o.DNSType = string(api.DNSTypePublic)
o.Bastion = false
// Default to open API & SSH access // Default to open API & SSH access
o.AdminAccess = []string{"0.0.0.0/0"} o.AdminAccess = []string{"0.0.0.0/0"}
@ -508,265 +477,23 @@ func RunCreateCluster(ctx context.Context, f *util.Factory, out io.Writer, c *Cr
cluster.Spec.DNSZone = c.DNSZone cluster.Spec.DNSZone = c.DNSZone
} }
// TODO: push more of the following logic into cloudup.NewCluster()
channel := clusterResult.Channel
allZones := clusterResult.AllZones
if c.CloudProvider != "" {
if featureflag.Spotinst.Enabled() {
if cluster.Spec.CloudConfig == nil {
cluster.Spec.CloudConfig = &api.CloudConfiguration{}
}
if c.SpotinstProduct != "" {
cluster.Spec.CloudConfig.SpotinstProduct = fi.String(c.SpotinstProduct)
}
if c.SpotinstOrientation != "" {
cluster.Spec.CloudConfig.SpotinstOrientation = fi.String(c.SpotinstOrientation)
}
}
}
// Populate project
if c.Project != "" {
cluster.Spec.Project = c.Project
}
if api.CloudProviderID(cluster.Spec.CloudProvider) == api.CloudProviderGCE {
if cluster.Spec.CloudConfig == nil {
cluster.Spec.CloudConfig = &api.CloudConfiguration{}
}
if cluster.Spec.Project == "" {
project, err := gce.DefaultProject()
if err != nil {
klog.Warningf("unable to get default google cloud project: %v", err)
} else if project == "" {
klog.Warningf("default google cloud project not set (try `gcloud config set project <name>`")
} else {
klog.Infof("using google cloud project: %s", project)
}
cluster.Spec.Project = project
}
if c.GCEServiceAccount != "" {
klog.Infof("VMs will be configured to use specified Service Account: %v", c.GCEServiceAccount)
cluster.Spec.CloudConfig.GCEServiceAccount = c.GCEServiceAccount
} else {
klog.Warning("VMs will be configured to use the GCE default compute Service Account! This is an anti-pattern")
klog.Warning("Use a pre-created Service Account with the flag: --gce-service-account=account@projectname.iam.gserviceaccount.com")
cluster.Spec.CloudConfig.GCEServiceAccount = "default"
}
}
if c.KubernetesVersion != "" {
cluster.Spec.KubernetesVersion = c.KubernetesVersion
}
if c.ContainerRuntime != "" { if c.ContainerRuntime != "" {
cluster.Spec.ContainerRuntime = c.ContainerRuntime cluster.Spec.ContainerRuntime = c.ContainerRuntime
} }
cluster.Spec.Networking = &api.NetworkingSpec{}
switch c.Networking {
case "kubenet":
cluster.Spec.Networking.Kubenet = &api.KubenetNetworkingSpec{}
case "external":
cluster.Spec.Networking.External = &api.ExternalNetworkingSpec{}
case "cni":
cluster.Spec.Networking.CNI = &api.CNINetworkingSpec{}
case "kopeio-vxlan", "kopeio":
cluster.Spec.Networking.Kopeio = &api.KopeioNetworkingSpec{}
case "weave":
cluster.Spec.Networking.Weave = &api.WeaveNetworkingSpec{}
if cluster.Spec.CloudProvider == "aws" {
// AWS supports "jumbo frames" of 9001 bytes and weave adds up to 87 bytes overhead
// sets the default to the largest number that leaves enough overhead and is divisible by 4
jumboFrameMTUSize := int32(8912)
cluster.Spec.Networking.Weave.MTU = &jumboFrameMTUSize
}
case "flannel", "flannel-vxlan":
cluster.Spec.Networking.Flannel = &api.FlannelNetworkingSpec{
Backend: "vxlan",
}
case "flannel-udp":
klog.Warningf("flannel UDP mode is not recommended; consider flannel-vxlan instead")
cluster.Spec.Networking.Flannel = &api.FlannelNetworkingSpec{
Backend: "udp",
}
case "calico":
cluster.Spec.Networking.Calico = &api.CalicoNetworkingSpec{
MajorVersion: "v3",
}
// Validate to check if etcd clusters have an acceptable version
if errList := validation.ValidateEtcdVersionForCalicoV3(cluster.Spec.EtcdClusters[0], cluster.Spec.Networking.Calico.MajorVersion, field.NewPath("spec", "networking", "calico")); len(errList) != 0 {
// This is not a special version but simply of the 3 series
for _, etcd := range cluster.Spec.EtcdClusters {
etcd.Version = components.DefaultEtcd3Version_1_11
}
}
case "canal":
cluster.Spec.Networking.Canal = &api.CanalNetworkingSpec{}
case "kube-router":
cluster.Spec.Networking.Kuberouter = &api.KuberouterNetworkingSpec{}
if cluster.Spec.KubeProxy == nil {
cluster.Spec.KubeProxy = &api.KubeProxyConfig{}
}
enabled := false
cluster.Spec.KubeProxy.Enabled = &enabled
case "amazonvpc", "amazon-vpc-routed-eni":
cluster.Spec.Networking.AmazonVPC = &api.AmazonVPCNetworkingSpec{}
case "cilium":
cilium := &api.CiliumNetworkingSpec{}
cluster.Spec.Networking.Cilium = cilium
nodeport := false
if c.KubernetesVersion == "" {
nodeport = true
} else {
k8sVersion, err := semver.ParseTolerant(c.KubernetesVersion)
if err == nil {
if version.IsKubernetesGTE("1.12", k8sVersion) {
nodeport = true
}
}
}
if nodeport {
cilium.EnableNodePort = true
if cluster.Spec.KubeProxy == nil {
cluster.Spec.KubeProxy = &api.KubeProxyConfig{}
}
enabled := false
cluster.Spec.KubeProxy.Enabled = &enabled
}
case "lyftvpc":
cluster.Spec.Networking.LyftVPC = &api.LyftVPCNetworkingSpec{}
case "gce":
cluster.Spec.Networking.GCE = &api.GCENetworkingSpec{}
default:
return fmt.Errorf("unknown networking mode %q", c.Networking)
}
klog.V(4).Infof("networking mode=%s => %s", c.Networking, fi.DebugAsJsonString(cluster.Spec.Networking))
if c.NetworkCIDR != "" { if c.NetworkCIDR != "" {
cluster.Spec.NetworkCIDR = c.NetworkCIDR cluster.Spec.NetworkCIDR = c.NetworkCIDR
} }
// Network Topology
if c.Topology == "" {
// The flag default should have set this, but we might be being called as a library
klog.Infof("Empty topology. Defaulting to public topology")
c.Topology = api.TopologyPublic
}
cluster.Spec.DisableSubnetTags = c.DisableSubnetTags cluster.Spec.DisableSubnetTags = c.DisableSubnetTags
switch c.Topology {
case api.TopologyPublic:
cluster.Spec.Topology = &api.TopologySpec{
Masters: api.TopologyPublic,
Nodes: api.TopologyPublic,
//Bastion: &api.BastionSpec{Enable: c.Bastion},
}
if c.Bastion {
return fmt.Errorf("Bastion supports --topology='private' only.")
}
for i := range cluster.Spec.Subnets {
cluster.Spec.Subnets[i].Type = api.SubnetTypePublic
}
case api.TopologyPrivate:
if cluster.Spec.Networking.Kubenet != nil {
return fmt.Errorf("invalid networking option %s. Kubenet does not support private topology", c.Networking)
}
cluster.Spec.Topology = &api.TopologySpec{
Masters: api.TopologyPrivate,
Nodes: api.TopologyPrivate,
}
for i := range cluster.Spec.Subnets {
cluster.Spec.Subnets[i].Type = api.SubnetTypePrivate
}
var utilitySubnets []api.ClusterSubnetSpec
var zoneToSubnetProviderID map[string]string
if len(c.Zones) > 0 && len(c.UtilitySubnetIDs) > 0 {
if api.CloudProviderID(cluster.Spec.CloudProvider) == api.CloudProviderAWS {
zoneToSubnetProviderID, err = getZoneToSubnetProviderID(cluster.Spec.NetworkID, c.Zones[0][:len(c.Zones[0])-1], c.UtilitySubnetIDs)
if err != nil {
return err
}
} else if api.CloudProviderID(cluster.Spec.CloudProvider) == api.CloudProviderOpenstack {
tags := make(map[string]string)
tags[openstack.TagClusterName] = c.ClusterName
zoneToSubnetProviderID, err = getSubnetProviderID(&cluster.Spec, allZones.List(), c.UtilitySubnetIDs, tags)
if err != nil {
return err
}
}
}
for _, s := range cluster.Spec.Subnets {
if s.Type == api.SubnetTypeUtility {
continue
}
subnet := api.ClusterSubnetSpec{
Name: "utility-" + s.Name,
Zone: s.Zone,
Type: api.SubnetTypeUtility,
}
if subnetID, ok := zoneToSubnetProviderID[s.Zone]; ok {
subnet.ProviderID = subnetID
}
utilitySubnets = append(utilitySubnets, subnet)
}
cluster.Spec.Subnets = append(cluster.Spec.Subnets, utilitySubnets...)
if c.Bastion {
bastionGroup := &api.InstanceGroup{}
bastionGroup.Spec.Role = api.InstanceGroupRoleBastion
bastionGroup.ObjectMeta.Name = "bastions"
bastionGroup.Spec.Image = c.Image
instanceGroups = append(instanceGroups, bastionGroup)
if !dns.IsGossipHostname(cluster.Name) {
cluster.Spec.Topology.Bastion = &api.BastionSpec{
BastionPublicName: "bastion." + cluster.Name,
}
}
}
default:
return fmt.Errorf("Invalid topology %s.", c.Topology)
}
// DNS
if c.DNSType == "" {
// The flag default should have set this, but we might be being called as a library
klog.Infof("Empty DNS. Defaulting to public DNS")
c.DNSType = string(api.DNSTypePublic)
}
if cluster.Spec.Topology == nil {
cluster.Spec.Topology = &api.TopologySpec{}
}
if cluster.Spec.Topology.DNS == nil {
cluster.Spec.Topology.DNS = &api.DNSSpec{}
}
switch strings.ToLower(c.DNSType) {
case "public":
cluster.Spec.Topology.DNS.Type = api.DNSTypePublic
case "private":
cluster.Spec.Topology.DNS.Type = api.DNSTypePrivate
default:
return fmt.Errorf("unknown DNSType: %q", c.DNSType)
}
if c.MasterPublicName != "" { if c.MasterPublicName != "" {
cluster.Spec.MasterPublicName = c.MasterPublicName cluster.Spec.MasterPublicName = c.MasterPublicName
} }
// TODO: push more of the following logic into cloudup.NewCluster()
channel := clusterResult.Channel
// check if we should set anonymousAuth to false // check if we should set anonymousAuth to false
{ {
if cluster.Spec.Kubelet == nil { if cluster.Spec.Kubelet == nil {
@ -1065,74 +792,6 @@ func initializeOpenstackAPI(c *CreateClusterOptions, cluster *api.Cluster) {
} }
} }
// TODO dedup or remove
func getZoneToSubnetProviderID(VPCID string, region string, subnetIDs []string) (map[string]string, error) {
res := make(map[string]string)
cloudTags := map[string]string{}
awsCloud, err := awsup.NewAWSCloud(region, cloudTags)
if err != nil {
return res, fmt.Errorf("error loading cloud: %v", err)
}
vpcInfo, err := awsCloud.FindVPCInfo(VPCID)
if err != nil {
return res, fmt.Errorf("error describing VPC: %v", err)
}
if vpcInfo == nil {
return res, fmt.Errorf("VPC %q not found", VPCID)
}
subnetByID := make(map[string]*fi.SubnetInfo)
for _, subnetInfo := range vpcInfo.Subnets {
subnetByID[subnetInfo.ID] = subnetInfo
}
for _, subnetID := range subnetIDs {
subnet, ok := subnetByID[subnetID]
if !ok {
return res, fmt.Errorf("subnet %s not found in VPC %s", subnetID, VPCID)
}
if res[subnet.Zone] != "" {
return res, fmt.Errorf("subnet %s and %s have the same zone", subnetID, res[subnet.Zone])
}
res[subnet.Zone] = subnetID
}
return res, nil
}
// TODO dedup or remove
func getSubnetProviderID(spec *api.ClusterSpec, zones []string, subnetIDs []string, tags map[string]string) (map[string]string, error) {
res := make(map[string]string)
osCloud, err := openstack.NewOpenstackCloud(tags, spec)
if err != nil {
return res, fmt.Errorf("error loading cloud: %v", err)
}
osCloud.UseZones(zones)
networkInfo, err := osCloud.FindVPCInfo(spec.NetworkID)
if err != nil {
return res, fmt.Errorf("error describing Network: %v", err)
}
if networkInfo == nil {
return res, fmt.Errorf("network %q not found", spec.NetworkID)
}
subnetByID := make(map[string]*fi.SubnetInfo)
for _, subnetInfo := range networkInfo.Subnets {
subnetByID[subnetInfo.ID] = subnetInfo
}
for _, subnetID := range subnetIDs {
subnet, ok := subnetByID[subnetID]
if !ok {
return res, fmt.Errorf("subnet %s not found in network %s", subnetID, spec.NetworkID)
}
if res[subnet.Zone] != "" {
return res, fmt.Errorf("subnet %s and %s have the same zone", subnetID, res[subnet.Zone])
}
res[subnet.Zone] = subnetID
}
return res, nil
}
func loadSSHPublicKeys(sshPublicKey string) (map[string][]byte, error) { func loadSSHPublicKeys(sshPublicKey string) (map[string][]byte, error) {
sshPublicKeys := make(map[string][]byte) sshPublicKeys := make(map[string][]byte)
if sshPublicKey != "" { if sshPublicKey != "" {

View File

@ -84,6 +84,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//vendor/k8s.io/klog:go_default_library", "//vendor/k8s.io/klog:go_default_library",
], ],
) )

View File

@ -23,14 +23,21 @@ import (
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ec2"
"github.com/blang/semver/v4"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/klog" "k8s.io/klog"
"k8s.io/kops" "k8s.io/kops"
api "k8s.io/kops/pkg/apis/kops" api "k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/apis/kops/model" "k8s.io/kops/pkg/apis/kops/model"
version "k8s.io/kops/pkg/apis/kops/util"
"k8s.io/kops/pkg/apis/kops/validation"
"k8s.io/kops/pkg/client/simple" "k8s.io/kops/pkg/client/simple"
"k8s.io/kops/pkg/dns"
"k8s.io/kops/pkg/featureflag"
"k8s.io/kops/pkg/model/components"
"k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/aliup" "k8s.io/kops/upup/pkg/fi/cloudup/aliup"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup" "k8s.io/kops/upup/pkg/fi/cloudup/awsup"
@ -53,6 +60,8 @@ type NewClusterOptions struct {
Channel string Channel string
// ConfigBase is the location where we will store the configuration. It defaults to the state store. // ConfigBase is the location where we will store the configuration. It defaults to the state store.
ConfigBase string ConfigBase string
// KubernetesVersion is the version of Kubernetes to deploy. It defaults to the version recommended by the channel.
KubernetesVersion string
// CloudProvider is the name of the cloud provider. The default is to guess based on the Zones name. // CloudProvider is the name of the cloud provider. The default is to guess based on the Zones name.
CloudProvider string CloudProvider string
@ -61,12 +70,24 @@ type NewClusterOptions struct {
// MasterZones are the availability zones in which to run the masters. Defaults to the list in the Zones field. // MasterZones are the availability zones in which to run the masters. Defaults to the list in the Zones field.
MasterZones []string MasterZones []string
// Project is the cluster's GCE project.
Project string
// GCEServiceAccount specifies the service account with which the GCE VM runs.
GCEServiceAccount string
// Spotinst options
SpotinstProduct string
SpotinstOrientation string
// NetworkID is the ID of the shared network (VPC). // NetworkID is the ID of the shared network (VPC).
// If empty, SubnetIDs are not empty, and on AWS or OpenStack, determines network ID from the first SubnetID. // If empty, SubnetIDs are not empty, and on AWS or OpenStack, determines network ID from the first SubnetID.
// If empty otherwise, creates a new network/VPC to be owned by the cluster. // If empty otherwise, creates a new network/VPC to be owned by the cluster.
NetworkID string NetworkID string
// SubnetIDs are the IDs of the shared subnets. If empty, creates new subnets to be owned by the cluster. // SubnetIDs are the IDs of the shared subnets.
// If empty, creates new subnets to be owned by the cluster.
SubnetIDs []string SubnetIDs []string
// UtilitySubnetIDs are the IDs of the shared utility subnets. If empty and the topology is "private", creates new subnets to be owned by the cluster.
UtilitySubnetIDs []string
// Egress defines the method of traffic egress for subnets. // Egress defines the method of traffic egress for subnets.
Egress string Egress string
@ -90,11 +111,23 @@ type NewClusterOptions struct {
// NodeCount is the number of nodes to create. Defaults to leaving the count unspecified // NodeCount is the number of nodes to create. Defaults to leaving the count unspecified
// on the InstanceGroup, which results in a count of 2. // on the InstanceGroup, which results in a count of 2.
NodeCount int32 NodeCount int32
// Bastion enables the creation of a Bastion instance.
Bastion bool
// Networking is the networking provider/node to use.
Networking string
// Topology is the network topology to use. Defaults to "public".
Topology string
// DNSType is the DNS type to use; "public" or "private". Defaults to "public".
DNSType string
} }
func (o *NewClusterOptions) InitDefaults() { func (o *NewClusterOptions) InitDefaults() {
o.Channel = api.DefaultChannel o.Channel = api.DefaultChannel
o.Authorization = AuthorizationFlagRBAC o.Authorization = AuthorizationFlagRBAC
o.Networking = "kubenet"
o.Topology = api.TopologyPublic
o.DNSType = string(api.DNSTypePublic)
} }
type NewClusterResult struct { type NewClusterResult struct {
@ -105,7 +138,6 @@ type NewClusterResult struct {
// TODO remove after more create_cluster logic refactored in // TODO remove after more create_cluster logic refactored in
Channel *api.Channel Channel *api.Channel
AllZones sets.String
} }
// NewCluster initializes cluster and instance groups specifications as // NewCluster initializes cluster and instance groups specifications as
@ -138,6 +170,9 @@ func NewCluster(opt *NewClusterOptions, clientset simple.Clientset) (*NewCluster
} }
} }
cluster.Spec.Channel = opt.Channel cluster.Spec.Channel = opt.Channel
if opt.KubernetesVersion != "" {
cluster.Spec.KubernetesVersion = opt.KubernetesVersion
}
cluster.Spec.ConfigBase = opt.ConfigBase cluster.Spec.ConfigBase = opt.ConfigBase
configBase, err := clientset.ConfigBaseFor(&cluster) configBase, err := clientset.ConfigBaseFor(&cluster)
@ -197,14 +232,24 @@ func NewCluster(opt *NewClusterOptions, clientset simple.Clientset) (*NewCluster
return nil, err return nil, err
} }
err = setupNetworking(opt, &cluster)
if err != nil {
return nil, err
}
bastions, err := setupTopology(opt, &cluster, allZones)
if err != nil {
return nil, err
}
instanceGroups := append([]*api.InstanceGroup(nil), masters...) instanceGroups := append([]*api.InstanceGroup(nil), masters...)
instanceGroups = append(instanceGroups, nodes...) instanceGroups = append(instanceGroups, nodes...)
instanceGroups = append(instanceGroups, bastions...)
result := NewClusterResult{ result := NewClusterResult{
Cluster: &cluster, Cluster: &cluster,
InstanceGroups: instanceGroups, InstanceGroups: instanceGroups,
Channel: channel, Channel: channel,
AllZones: allZones,
} }
return &result, nil return &result, nil
} }
@ -232,6 +277,32 @@ func setupVPC(opt *NewClusterOptions, cluster *api.Cluster) error {
cluster.Spec.NetworkID = *res.Subnets[0].VpcId cluster.Spec.NetworkID = *res.Subnets[0].VpcId
} }
case api.CloudProviderGCE:
if cluster.Spec.CloudConfig == nil {
cluster.Spec.CloudConfig = &api.CloudConfiguration{}
}
cluster.Spec.Project = opt.Project
if cluster.Spec.Project == "" {
project, err := gce.DefaultProject()
if err != nil {
klog.Warningf("unable to get default google cloud project: %v", err)
} else if project == "" {
klog.Warningf("default google cloud project not set (try `gcloud config set project <name>`")
} else {
klog.Infof("using google cloud project: %s", project)
}
cluster.Spec.Project = project
}
if opt.GCEServiceAccount != "" {
// TODO remove this logging?
klog.Infof("VMs will be configured to use specified Service Account: %v", opt.GCEServiceAccount)
cluster.Spec.CloudConfig.GCEServiceAccount = opt.GCEServiceAccount
} else {
klog.Warning("VMs will be configured to use the GCE default compute Service Account! This is an anti-pattern")
klog.Warning("Use a pre-created Service Account with the flag: --gce-service-account=account@projectname.iam.gserviceaccount.com")
cluster.Spec.CloudConfig.GCEServiceAccount = "default"
}
case api.CloudProviderOpenstack: case api.CloudProviderOpenstack:
if cluster.Spec.CloudConfig == nil { if cluster.Spec.CloudConfig == nil {
cluster.Spec.CloudConfig = &api.CloudConfiguration{} cluster.Spec.CloudConfig = &api.CloudConfiguration{}
@ -253,7 +324,7 @@ func setupVPC(opt *NewClusterOptions, cluster *api.Cluster) error {
if cluster.Spec.NetworkID == "" && len(opt.SubnetIDs) > 0 { if cluster.Spec.NetworkID == "" && len(opt.SubnetIDs) > 0 {
tags := make(map[string]string) tags := make(map[string]string)
tags[openstack.TagClusterName] = opt.ClusterName tags[openstack.TagClusterName] = cluster.Name
osCloud, err := openstack.NewOpenstackCloud(tags, &cluster.Spec) osCloud, err := openstack.NewOpenstackCloud(tags, &cluster.Spec)
if err != nil { if err != nil {
return fmt.Errorf("error loading cloud: %v", err) return fmt.Errorf("error loading cloud: %v", err)
@ -274,6 +345,18 @@ func setupVPC(opt *NewClusterOptions, cluster *api.Cluster) error {
} }
} }
if featureflag.Spotinst.Enabled() {
if cluster.Spec.CloudConfig == nil {
cluster.Spec.CloudConfig = &api.CloudConfiguration{}
}
if opt.SpotinstProduct != "" {
cluster.Spec.CloudConfig.SpotinstProduct = fi.String(opt.SpotinstProduct)
}
if opt.SpotinstOrientation != "" {
cluster.Spec.CloudConfig.SpotinstOrientation = fi.String(opt.SpotinstOrientation)
}
}
return nil return nil
} }
@ -345,7 +428,7 @@ func setupZones(opt *NewClusterOptions, cluster *api.Cluster, allZones sets.Stri
case api.CloudProviderAWS: case api.CloudProviderAWS:
if len(opt.Zones) > 0 && len(opt.SubnetIDs) > 0 { if len(opt.Zones) > 0 && len(opt.SubnetIDs) > 0 {
zoneToSubnetProviderID, err = getZoneToSubnetProviderID(cluster.Spec.NetworkID, opt.Zones[0][:len(opt.Zones[0])-1], opt.SubnetIDs) zoneToSubnetProviderID, err = getAWSZoneToSubnetProviderID(cluster.Spec.NetworkID, opt.Zones[0][:len(opt.Zones[0])-1], opt.SubnetIDs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -354,8 +437,8 @@ func setupZones(opt *NewClusterOptions, cluster *api.Cluster, allZones sets.Stri
case api.CloudProviderOpenstack: case api.CloudProviderOpenstack:
if len(opt.Zones) > 0 && len(opt.SubnetIDs) > 0 { if len(opt.Zones) > 0 && len(opt.SubnetIDs) > 0 {
tags := make(map[string]string) tags := make(map[string]string)
tags[openstack.TagClusterName] = opt.ClusterName tags[openstack.TagClusterName] = cluster.Name
zoneToSubnetProviderID, err = getSubnetProviderID(&cluster.Spec, allZones.List(), opt.SubnetIDs, tags) zoneToSubnetProviderID, err = getOpenstackZoneToSubnetProviderID(&cluster.Spec, allZones.List(), opt.SubnetIDs, tags)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -384,8 +467,7 @@ func setupZones(opt *NewClusterOptions, cluster *api.Cluster, allZones sets.Stri
return zoneToSubnetMap, nil return zoneToSubnetMap, nil
} }
// TODO rename to getAWSZoneToSubnetProviderID func getAWSZoneToSubnetProviderID(VPCID string, region string, subnetIDs []string) (map[string]string, error) {
func getZoneToSubnetProviderID(VPCID string, region string, subnetIDs []string) (map[string]string, error) {
res := make(map[string]string) res := make(map[string]string)
cloudTags := map[string]string{} cloudTags := map[string]string{}
awsCloud, err := awsup.NewAWSCloud(region, cloudTags) awsCloud, err := awsup.NewAWSCloud(region, cloudTags)
@ -416,8 +498,7 @@ func getZoneToSubnetProviderID(VPCID string, region string, subnetIDs []string)
return res, nil return res, nil
} }
// TODO rename to getOpenstackZoneToSubnetProviderID func getOpenstackZoneToSubnetProviderID(spec *api.ClusterSpec, zones []string, subnetIDs []string, tags map[string]string) (map[string]string, error) {
func getSubnetProviderID(spec *api.ClusterSpec, zones []string, subnetIDs []string, tags map[string]string) (map[string]string, error) {
res := make(map[string]string) res := make(map[string]string)
osCloud, err := openstack.NewOpenstackCloud(tags, spec) osCloud, err := openstack.NewOpenstackCloud(tags, spec)
if err != nil { if err != nil {
@ -644,3 +725,189 @@ func setupNodes(opt *NewClusterOptions, cluster *api.Cluster, zoneToSubnetMap ma
return nodes, nil return nodes, nil
} }
func setupNetworking(opt *NewClusterOptions, cluster *api.Cluster) error {
cluster.Spec.Networking = &api.NetworkingSpec{}
switch opt.Networking {
case "kubenet", "":
cluster.Spec.Networking.Kubenet = &api.KubenetNetworkingSpec{}
case "external":
cluster.Spec.Networking.External = &api.ExternalNetworkingSpec{}
case "cni":
cluster.Spec.Networking.CNI = &api.CNINetworkingSpec{}
case "kopeio-vxlan", "kopeio":
cluster.Spec.Networking.Kopeio = &api.KopeioNetworkingSpec{}
case "weave":
cluster.Spec.Networking.Weave = &api.WeaveNetworkingSpec{}
if cluster.Spec.CloudProvider == "aws" {
// AWS supports "jumbo frames" of 9001 bytes and weave adds up to 87 bytes overhead
// sets the default to the largest number that leaves enough overhead and is divisible by 4
jumboFrameMTUSize := int32(8912)
cluster.Spec.Networking.Weave.MTU = &jumboFrameMTUSize
}
case "flannel", "flannel-vxlan":
cluster.Spec.Networking.Flannel = &api.FlannelNetworkingSpec{
Backend: "vxlan",
}
case "flannel-udp":
klog.Warningf("flannel UDP mode is not recommended; consider flannel-vxlan instead")
cluster.Spec.Networking.Flannel = &api.FlannelNetworkingSpec{
Backend: "udp",
}
case "calico":
cluster.Spec.Networking.Calico = &api.CalicoNetworkingSpec{
MajorVersion: "v3",
}
// Validate to check if etcd clusters have an acceptable version
if errList := validation.ValidateEtcdVersionForCalicoV3(cluster.Spec.EtcdClusters[0], cluster.Spec.Networking.Calico.MajorVersion, field.NewPath("spec", "networking", "calico")); len(errList) != 0 {
// This is not a special version but simply of the 3 series
for _, etcd := range cluster.Spec.EtcdClusters {
etcd.Version = components.DefaultEtcd3Version_1_11
}
}
case "canal":
cluster.Spec.Networking.Canal = &api.CanalNetworkingSpec{}
case "kube-router":
cluster.Spec.Networking.Kuberouter = &api.KuberouterNetworkingSpec{}
if cluster.Spec.KubeProxy == nil {
cluster.Spec.KubeProxy = &api.KubeProxyConfig{}
}
enabled := false
cluster.Spec.KubeProxy.Enabled = &enabled
case "amazonvpc", "amazon-vpc-routed-eni":
cluster.Spec.Networking.AmazonVPC = &api.AmazonVPCNetworkingSpec{}
case "cilium":
cilium := &api.CiliumNetworkingSpec{}
cluster.Spec.Networking.Cilium = cilium
nodeport := false
if cluster.Spec.KubernetesVersion == "" {
nodeport = true
} else {
k8sVersion, err := semver.ParseTolerant(cluster.Spec.KubernetesVersion)
if err == nil {
if version.IsKubernetesGTE("1.12", k8sVersion) {
nodeport = true
}
}
}
if nodeport {
cilium.EnableNodePort = true
if cluster.Spec.KubeProxy == nil {
cluster.Spec.KubeProxy = &api.KubeProxyConfig{}
}
enabled := false
cluster.Spec.KubeProxy.Enabled = &enabled
}
case "lyftvpc":
cluster.Spec.Networking.LyftVPC = &api.LyftVPCNetworkingSpec{}
case "gce":
cluster.Spec.Networking.GCE = &api.GCENetworkingSpec{}
default:
return fmt.Errorf("unknown networking mode %q", opt.Networking)
}
klog.V(4).Infof("networking mode=%s => %s", opt.Networking, fi.DebugAsJsonString(cluster.Spec.Networking))
return nil
}
func setupTopology(opt *NewClusterOptions, cluster *api.Cluster, allZones sets.String) ([]*api.InstanceGroup, error) {
var bastions []*api.InstanceGroup
switch opt.Topology {
case api.TopologyPublic, "":
cluster.Spec.Topology = &api.TopologySpec{
Masters: api.TopologyPublic,
Nodes: api.TopologyPublic,
//Bastion: &api.BastionSpec{Enable: c.Bastion},
}
if opt.Bastion {
return nil, fmt.Errorf("bastion supports --topology='private' only")
}
for i := range cluster.Spec.Subnets {
cluster.Spec.Subnets[i].Type = api.SubnetTypePublic
}
case api.TopologyPrivate:
if cluster.Spec.Networking.Kubenet != nil {
return nil, fmt.Errorf("invalid networking option %s. Kubenet does not support private topology", opt.Networking)
}
cluster.Spec.Topology = &api.TopologySpec{
Masters: api.TopologyPrivate,
Nodes: api.TopologyPrivate,
}
for i := range cluster.Spec.Subnets {
cluster.Spec.Subnets[i].Type = api.SubnetTypePrivate
}
var utilitySubnets []api.ClusterSubnetSpec
var zoneToSubnetProviderID map[string]string
var err error
if len(opt.Zones) > 0 && len(opt.UtilitySubnetIDs) > 0 {
switch api.CloudProviderID(cluster.Spec.CloudProvider) {
case api.CloudProviderAWS:
zoneToSubnetProviderID, err = getAWSZoneToSubnetProviderID(cluster.Spec.NetworkID, opt.Zones[0][:len(opt.Zones[0])-1], opt.UtilitySubnetIDs)
if err != nil {
return nil, err
}
case api.CloudProviderOpenstack:
tags := make(map[string]string)
tags[openstack.TagClusterName] = cluster.Name
zoneToSubnetProviderID, err = getOpenstackZoneToSubnetProviderID(&cluster.Spec, allZones.List(), opt.UtilitySubnetIDs, tags)
if err != nil {
return nil, err
}
}
}
for _, s := range cluster.Spec.Subnets {
if s.Type == api.SubnetTypeUtility {
continue
}
subnet := api.ClusterSubnetSpec{
Name: "utility-" + s.Name,
Zone: s.Zone,
Type: api.SubnetTypeUtility,
}
if subnetID, ok := zoneToSubnetProviderID[s.Zone]; ok {
subnet.ProviderID = subnetID
}
utilitySubnets = append(utilitySubnets, subnet)
}
cluster.Spec.Subnets = append(cluster.Spec.Subnets, utilitySubnets...)
if opt.Bastion {
bastionGroup := &api.InstanceGroup{}
bastionGroup.Spec.Role = api.InstanceGroupRoleBastion
bastionGroup.ObjectMeta.Name = "bastions"
bastions = append(bastions, bastionGroup)
if !dns.IsGossipHostname(cluster.Name) {
cluster.Spec.Topology.Bastion = &api.BastionSpec{
BastionPublicName: "bastion." + cluster.Name,
}
}
}
default:
return nil, fmt.Errorf("invalid topology %s", opt.Topology)
}
cluster.Spec.Topology.DNS = &api.DNSSpec{}
switch strings.ToLower(opt.DNSType) {
case "public", "":
cluster.Spec.Topology.DNS.Type = api.DNSTypePublic
case "private":
cluster.Spec.Topology.DNS.Type = api.DNSTypePrivate
default:
return nil, fmt.Errorf("unknown DNSType: %q", opt.DNSType)
}
return bastions, nil
}