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 }