github.com/kubernetes-incubator/kube-aws@v0.16.4/pkg/api/deployment.go (about)

     1  package api
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net"
     7  	"strings"
     8  
     9  	"github.com/Masterminds/semver"
    10  	"github.com/kubernetes-incubator/kube-aws/netutil"
    11  )
    12  
    13  func (s DeploymentSettings) ValidateNodePool(name string) error {
    14  	if err := s.Experimental.Validate(name); err != nil {
    15  		return err
    16  	}
    17  	return nil
    18  }
    19  
    20  // TODO make this less smelly by e.g. moving this to core/nodepool/config
    21  func (c DeploymentSettings) WithDefaultsFrom(main DeploymentSettings) DeploymentSettings {
    22  	c.ClusterName = main.ClusterName
    23  
    24  	if c.KeyName == "" {
    25  		c.KeyName = main.KeyName
    26  	}
    27  
    28  	// No defaulting for AvailabilityZone: It must be set explicitly for high availability
    29  
    30  	// If there was a specific release channel specified for this node pool,
    31  	// the user would want to use the latest AMI for the channel, not the latest AMI for the default release channel
    32  	// specified in the top level of cluster.yaml
    33  	if c.ReleaseChannel == "" {
    34  		c.ReleaseChannel = main.ReleaseChannel
    35  
    36  		if c.AmiId == "" {
    37  			c.AmiId = main.AmiId
    38  		}
    39  	}
    40  
    41  	if c.K8sVer == "" {
    42  		c.K8sVer = main.K8sVer
    43  	}
    44  
    45  	// Use main images if not defined in nodepool configuration
    46  	c.HyperkubeImage.MergeIfEmpty(main.HyperkubeImage)
    47  	c.HyperkubeImage.Tag = c.K8sVer
    48  	c.AWSCliImage.MergeIfEmpty(main.AWSCliImage)
    49  	c.PauseImage.MergeIfEmpty(main.PauseImage)
    50  	c.JournaldCloudWatchLogsImage.MergeIfEmpty(main.JournaldCloudWatchLogsImage)
    51  
    52  	if len(c.SSHAuthorizedKeys) == 0 {
    53  		c.SSHAuthorizedKeys = main.SSHAuthorizedKeys
    54  	}
    55  
    56  	// And assuming that no one wants to differentiate these settings among control plane and node pools, we forbid customization of:
    57  	c.ManageCertificates = main.ManageCertificates
    58  	// And believing it is impossible to mix different values, we also forbid customization of:
    59  	// * Region
    60  	// * ContainerRuntime
    61  	// * KMSKeyARN
    62  	// * ElasticFileSystemID
    63  	c.Region = main.Region
    64  	c.ContainerRuntime = main.ContainerRuntime
    65  	c.KMSKeyARN = main.KMSKeyARN
    66  
    67  	// TODO Allow providing one or more elasticFileSystemId's to be mounted both per-node-pool/cluster-wide
    68  	// TODO Allow providing elasticFileSystemId to a node pool in managed subnets.
    69  	// Currently, per-node-pool elasticFileSystemId requires existing subnets configured by users to have appropriate MountTargets associated
    70  	if c.ElasticFileSystemID == "" {
    71  		c.ElasticFileSystemID = main.ElasticFileSystemID
    72  	}
    73  
    74  	// Inherit main CloudWatchLogging config
    75  	c.CloudWatchLogging.MergeIfEmpty(main.CloudWatchLogging)
    76  
    77  	// Inherit main AmazonSsmAgent config
    78  	c.AmazonSsmAgent = main.AmazonSsmAgent
    79  
    80  	//Inherit main KubeDns config
    81  	c.KubeDns.MergeIfEmpty(main.KubeDns)
    82  
    83  	//Inherit main Kubernetes config (e.g. for Kubernetes.Networking.SelfHosting etc.)
    84  	c.Kubernetes = main.Kubernetes
    85  
    86  	return c
    87  }
    88  
    89  type DeploymentValidationResult struct {
    90  	vpcNet *net.IPNet
    91  }
    92  
    93  func (c DeploymentSettings) Validate() (*DeploymentValidationResult, error) {
    94  	releaseChannelSupported := supportedReleaseChannels[c.ReleaseChannel]
    95  	if !releaseChannelSupported {
    96  		return nil, fmt.Errorf("releaseChannel %s is not supported", c.ReleaseChannel)
    97  	}
    98  
    99  	if c.KeyName == "" && len(c.SSHAuthorizedKeys) == 0 {
   100  		return nil, errors.New("Either keyName or sshAuthorizedKeys must be set")
   101  	}
   102  	if c.ClusterName == "" {
   103  		return nil, errors.New("clusterName must be set")
   104  	}
   105  	if c.S3URI == "" {
   106  		return nil, errors.New("s3URI must be set")
   107  	}
   108  	if c.KMSKeyARN == "" && c.AssetsEncryptionEnabled() {
   109  		return nil, errors.New("kmsKeyArn must be set")
   110  	}
   111  
   112  	if c.Region.IsEmpty() {
   113  		return nil, errors.New("region must be set")
   114  	}
   115  
   116  	_, err := semver.NewVersion(c.K8sVer)
   117  	if err != nil {
   118  		return nil, errors.New("kubernetesVersion must be a valid version")
   119  	}
   120  
   121  	if c.KMSKeyARN != "" && !c.Region.IsEmpty() && !strings.Contains(c.KMSKeyARN, c.Region.String()) {
   122  		return nil, errors.New("kmsKeyArn must reference the same region as the one being deployed to")
   123  	}
   124  
   125  	_, vpcNet, err := net.ParseCIDR(c.VPCCIDR)
   126  	if err != nil {
   127  		return nil, fmt.Errorf("invalid vpcCIDR: %v", err)
   128  	}
   129  
   130  	if len(c.Subnets) == 0 {
   131  		if c.AvailabilityZone == "" {
   132  			return nil, fmt.Errorf("availabilityZone must be set")
   133  		}
   134  		_, instanceCIDR, err := net.ParseCIDR(c.InstanceCIDR)
   135  		if err != nil {
   136  			return nil, fmt.Errorf("invalid instanceCIDR: %v", err)
   137  		}
   138  		if !vpcNet.Contains(instanceCIDR.IP) {
   139  			return nil, fmt.Errorf("vpcCIDR (%s) does not contain instanceCIDR (%s)",
   140  				c.VPCCIDR,
   141  				c.InstanceCIDR,
   142  			)
   143  		}
   144  	} else {
   145  		if c.InstanceCIDR != "" {
   146  			return nil, fmt.Errorf("The top-level instanceCIDR(%s) must be empty when subnets are specified", c.InstanceCIDR)
   147  		}
   148  		if c.AvailabilityZone != "" {
   149  			return nil, fmt.Errorf("The top-level availabilityZone(%s) must be empty when subnets are specified", c.AvailabilityZone)
   150  		}
   151  
   152  		var instanceCIDRs = make([]*net.IPNet, 0)
   153  
   154  		allPrivate := true
   155  		allPublic := true
   156  		allExistingRouteTable := true
   157  
   158  		for i, subnet := range c.Subnets {
   159  			if subnet.Validate(); err != nil {
   160  				return nil, fmt.Errorf("failed to validate subnet: %v", err)
   161  			}
   162  
   163  			allExistingRouteTable = allExistingRouteTable && !subnet.ManageRouteTable()
   164  			allPrivate = allPrivate && subnet.Private
   165  			allPublic = allPublic && subnet.Public()
   166  			if subnet.HasIdentifier() {
   167  				continue
   168  			}
   169  
   170  			if subnet.AvailabilityZone == "" {
   171  				return nil, fmt.Errorf("availabilityZone must be set for subnet #%d", i)
   172  			}
   173  			_, instanceCIDR, err := net.ParseCIDR(subnet.InstanceCIDR)
   174  			if err != nil {
   175  				return nil, fmt.Errorf("invalid instanceCIDR for subnet #%d: %v", i, err)
   176  			}
   177  			instanceCIDRs = append(instanceCIDRs, instanceCIDR)
   178  			if !vpcNet.Contains(instanceCIDR.IP) {
   179  				return nil, fmt.Errorf("vpcCIDR (%s) does not contain instanceCIDR (%s) for subnet #%d",
   180  					c.VPCCIDR,
   181  					c.InstanceCIDR,
   182  					i,
   183  				)
   184  			}
   185  
   186  			if !c.VPC.HasIdentifier() && (subnet.RouteTable.HasIdentifier() || c.InternetGateway.HasIdentifier()) {
   187  				return nil, errors.New("vpcId must be specified if subnets[].routeTable.id or internetGateway.id are specified")
   188  			}
   189  
   190  			if subnet.ManageSubnet() && subnet.Public() && c.VPC.HasIdentifier() && subnet.ManageRouteTable() && !c.InternetGateway.HasIdentifier() {
   191  				return nil, errors.New("internet gateway id can't be omitted when there're one or more managed public subnets in an existing VPC")
   192  			}
   193  		}
   194  
   195  		// All the subnets are explicitly/implicitly(they're public by default) configured to be "public".
   196  		// They're also configured to reuse existing route table(s).
   197  		// However, the IGW, which won't be applied to anywhere, is specified
   198  		if allPublic && allExistingRouteTable && c.InternetGateway.HasIdentifier() {
   199  			return nil, errors.New("internet gateway id can't be specified when all the public subnets have existing route tables associated. kube-aws doesn't try to modify an exisinting route table to include a route to the internet gateway")
   200  		}
   201  
   202  		// All the subnets are explicitly configured to be "private" but the IGW, which won't be applied anywhere, is specified
   203  		if allPrivate && c.InternetGateway.HasIdentifier() {
   204  			return nil, errors.New("internet gateway id can't be specified when all the subnets are existing private subnets")
   205  		}
   206  
   207  		for i, a := range instanceCIDRs {
   208  			for j := i + 1; j < len(instanceCIDRs); j++ {
   209  				b := instanceCIDRs[j]
   210  				if netutil.CidrOverlap(a, b) {
   211  					return nil, fmt.Errorf("CIDR of subnet %d (%s) overlaps with CIDR of subnet %d (%s)", i, a, j, b)
   212  				}
   213  			}
   214  		}
   215  	}
   216  
   217  	if err := c.Experimental.Validate("controller"); err != nil {
   218  		return nil, err
   219  	}
   220  
   221  	for i, ngw := range c.NATGateways() {
   222  		if err := ngw.Validate(); err != nil {
   223  			return nil, fmt.Errorf("NGW %d is not valid: %v", i, err)
   224  		}
   225  	}
   226  
   227  	return &DeploymentValidationResult{vpcNet: vpcNet}, nil
   228  }
   229  
   230  func (c DeploymentSettings) AssetsEncryptionEnabled() bool {
   231  	return c.ManageCertificates && c.Region.SupportsKMS()
   232  }
   233  
   234  func (s DeploymentSettings) AllSubnets() Subnets {
   235  	subnets := s.Subnets
   236  	return subnets
   237  }
   238  
   239  func (c DeploymentSettings) FindSubnetMatching(condition Subnet) Subnet {
   240  	for _, s := range c.Subnets {
   241  		if s.Name == condition.Name {
   242  			return s
   243  		}
   244  	}
   245  	out := ""
   246  	for _, s := range c.Subnets {
   247  		out = fmt.Sprintf("%s%+v ", out, s)
   248  	}
   249  	panic(fmt.Errorf("No subnet matching %v found in %s", condition, out))
   250  }
   251  
   252  func (c DeploymentSettings) PrivateSubnets() Subnets {
   253  	result := []Subnet{}
   254  	for _, s := range c.Subnets {
   255  		if s.Private {
   256  			result = append(result, s)
   257  		}
   258  	}
   259  	return result
   260  }
   261  
   262  func (c DeploymentSettings) PublicSubnets() Subnets {
   263  	result := []Subnet{}
   264  	for _, s := range c.Subnets {
   265  		if !s.Private {
   266  			result = append(result, s)
   267  		}
   268  	}
   269  	return result
   270  }
   271  
   272  func (c DeploymentSettings) FindNATGatewayForPrivateSubnet(s Subnet) (*NATGateway, error) {
   273  	for _, ngw := range c.NATGateways() {
   274  		if ngw.IsConnectedToPrivateSubnet(s) {
   275  			return &ngw, nil
   276  		}
   277  	}
   278  	return nil, fmt.Errorf("No NATGateway found for the subnet %v", s)
   279  }
   280  
   281  func (c DeploymentSettings) NATGateways() []NATGateway {
   282  	ngws := []NATGateway{}
   283  	for _, privateSubnet := range c.PrivateSubnets() {
   284  		var publicSubnet Subnet
   285  		ngwConfig := privateSubnet.NATGateway
   286  		if privateSubnet.ManageNATGateway() {
   287  			publicSubnetFound := false
   288  			for _, s := range c.PublicSubnets() {
   289  				if s.AvailabilityZone == privateSubnet.AvailabilityZone {
   290  					publicSubnet = s
   291  					publicSubnetFound = true
   292  					break
   293  				}
   294  			}
   295  			if !publicSubnetFound {
   296  				panic(fmt.Sprintf("No appropriate public subnet found for a non-preconfigured NAT gateway associated to private subnet %s", privateSubnet.LogicalName()))
   297  			}
   298  			ngw := NewManagedNATGateway(ngwConfig, privateSubnet, publicSubnet)
   299  			ngws = append(ngws, ngw)
   300  		} else if ngwConfig.HasIdentifier() {
   301  			ngw := NewUnmanagedNATGateway(ngwConfig, privateSubnet)
   302  			ngws = append(ngws, ngw)
   303  		}
   304  	}
   305  	return ngws
   306  }