mirror of https://github.com/kubernetes/kops.git
403 lines
12 KiB
Go
403 lines
12 KiB
Go
/*
|
|
Copyright 2016 The Kubernetes Authors.
|
|
|
|
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.
|
|
*/
|
|
|
|
/******************************************************************************
|
|
Template Functions are what map functions in the models, to internal logic in
|
|
kops. This is the point where we connect static YAML configuration to dynamic
|
|
runtime values in memory.
|
|
|
|
When defining a new function:
|
|
- Build the new function here
|
|
- Define the new function in AddTo()
|
|
dest["MyNewFunction"] = MyNewFunction // <-- Function Pointer
|
|
******************************************************************************/
|
|
|
|
package cloudup
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"math/big"
|
|
"net"
|
|
"sort"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/golang/glog"
|
|
api "k8s.io/kops/pkg/apis/kops"
|
|
"k8s.io/kops/upup/pkg/fi"
|
|
"k8s.io/kops/util/pkg/vfs"
|
|
"k8s.io/kubernetes/pkg/util/sets"
|
|
)
|
|
|
|
type TemplateFunctions struct {
|
|
cluster *api.Cluster
|
|
instanceGroups []*api.InstanceGroup
|
|
|
|
tags sets.String
|
|
region string
|
|
}
|
|
|
|
func (tf *TemplateFunctions) WellKnownServiceIP(id int) (net.IP, error) {
|
|
_, cidr, err := net.ParseCIDR(tf.cluster.Spec.ServiceClusterIPRange)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing ServiceClusterIPRange %q: %v", tf.cluster.Spec.ServiceClusterIPRange, err)
|
|
}
|
|
|
|
ip4 := cidr.IP.To4()
|
|
if ip4 != nil {
|
|
n := binary.BigEndian.Uint32(ip4)
|
|
n += uint32(id)
|
|
serviceIP := make(net.IP, len(ip4))
|
|
binary.BigEndian.PutUint32(serviceIP, n)
|
|
return serviceIP, nil
|
|
}
|
|
|
|
ip6 := cidr.IP.To16()
|
|
if ip6 != nil {
|
|
baseIPInt := big.NewInt(0)
|
|
baseIPInt.SetBytes(ip6)
|
|
serviceIPInt := big.NewInt(0)
|
|
serviceIPInt.Add(big.NewInt(int64(id)), baseIPInt)
|
|
serviceIP := make(net.IP, len(ip6))
|
|
serviceIPBytes := serviceIPInt.Bytes()
|
|
for i := range serviceIPBytes {
|
|
serviceIP[len(serviceIP)-len(serviceIPBytes)+i] = serviceIPBytes[i]
|
|
}
|
|
return serviceIP, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("Unexpected IP address type for ServiceClusterIPRange: %s", tf.cluster.Spec.ServiceClusterIPRange)
|
|
}
|
|
|
|
// This will define the available functions we can use in our YAML models
|
|
// If we are trying to get a new function implemented it MUST
|
|
// be defined here.
|
|
func (tf *TemplateFunctions) AddTo(dest template.FuncMap) {
|
|
dest["EtcdClusterMemberTags"] = tf.EtcdClusterMemberTags
|
|
dest["SharedVPC"] = tf.SharedVPC
|
|
|
|
// Network topology definitions
|
|
dest["IsTopologyPublic"] = tf.IsTopologyPublic
|
|
dest["IsTopologyPrivate"] = tf.IsTopologyPrivate
|
|
dest["IsTopologyPrivateMasters"] = tf.IsTopologyPrivateMasters
|
|
dest["WithBastion"] = tf.WithBastion
|
|
dest["GetBastionImageId"] = tf.GetBastionImageId
|
|
dest["GetBastionMachineType"] = tf.GetBastionMachineType
|
|
dest["GetBastionZone"] = tf.GetBastionZone
|
|
dest["GetELBName32"] = tf.GetELBName32
|
|
dest["IsBastionDNS"] = tf.IsBastionDNS
|
|
dest["GetBastionDNS"] = tf.GetBastionDNS
|
|
|
|
dest["SharedZone"] = tf.SharedZone
|
|
dest["WellKnownServiceIP"] = tf.WellKnownServiceIP
|
|
dest["AdminCIDR"] = tf.AdminCIDR
|
|
|
|
dest["Base64Encode"] = func(s string) string {
|
|
return base64.StdEncoding.EncodeToString([]byte(s))
|
|
}
|
|
dest["replace"] = func(s, find, replace string) string {
|
|
return strings.Replace(s, find, replace, -1)
|
|
}
|
|
dest["join"] = func(a []string, sep string) string {
|
|
return strings.Join(a, sep)
|
|
}
|
|
|
|
dest["ClusterName"] = func() string {
|
|
return tf.cluster.Name
|
|
}
|
|
|
|
dest["HasTag"] = tf.HasTag
|
|
|
|
dest["IAMServiceEC2"] = tf.IAMServiceEC2
|
|
|
|
dest["Image"] = tf.Image
|
|
|
|
dest["IAMMasterPolicy"] = func() (string, error) {
|
|
return tf.buildAWSIAMPolicy(api.InstanceGroupRoleMaster)
|
|
}
|
|
dest["IAMNodePolicy"] = func() (string, error) {
|
|
return tf.buildAWSIAMPolicy(api.InstanceGroupRoleNode)
|
|
}
|
|
dest["WithDefaultBool"] = func(v *bool, defaultValue bool) bool {
|
|
if v != nil {
|
|
return *v
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
dest["GetInstanceGroup"] = tf.GetInstanceGroup
|
|
|
|
dest["CloudTags"] = tf.CloudTags
|
|
|
|
dest["APIServerCount"] = tf.APIServerCount
|
|
|
|
dest["KubeDNS"] = func() *api.KubeDNSConfig {
|
|
return tf.cluster.Spec.KubeDNS
|
|
}
|
|
|
|
dest["DnsControllerArgv"] = tf.DnsControllerArgv
|
|
}
|
|
|
|
func (tf *TemplateFunctions) EtcdClusterMemberTags(etcd *api.EtcdClusterSpec, m *api.EtcdMemberSpec) map[string]string {
|
|
tags := make(map[string]string)
|
|
|
|
var allMembers []string
|
|
|
|
for _, m := range etcd.Members {
|
|
allMembers = append(allMembers, m.Name)
|
|
}
|
|
|
|
sort.Strings(allMembers)
|
|
|
|
// This is the configuration of the etcd cluster
|
|
tags["k8s.io/etcd/"+etcd.Name] = m.Name + "/" + strings.Join(allMembers, ",")
|
|
|
|
// This says "only mount on a master"
|
|
tags["k8s.io/role/master"] = "1"
|
|
|
|
return tags
|
|
}
|
|
|
|
// SharedVPC is a simple helper function which makes the templates for a shared VPC clearer
|
|
func (tf *TemplateFunctions) SharedVPC() bool {
|
|
return tf.cluster.SharedVPC()
|
|
}
|
|
|
|
// These are the network topology functions. They are boolean logic for checking which type of
|
|
// topology this cluster is set to be deployed with.
|
|
func (tf *TemplateFunctions) IsTopologyPrivate() bool { return tf.cluster.IsTopologyPrivate() }
|
|
func (tf *TemplateFunctions) IsTopologyPublic() bool { return tf.cluster.IsTopologyPublic() }
|
|
func (tf *TemplateFunctions) IsTopologyPrivateMasters() bool {
|
|
return tf.cluster.IsTopologyPrivateMasters()
|
|
}
|
|
|
|
func (tf *TemplateFunctions) WithBastion() bool {
|
|
return tf.cluster.Spec.Topology.Bastion.Enable
|
|
}
|
|
|
|
func (tf *TemplateFunctions) IsBastionDNS() bool {
|
|
if tf.cluster.Spec.Topology.Bastion.PublicName != "" {
|
|
return false
|
|
} else {
|
|
return true
|
|
}
|
|
}
|
|
|
|
func (tf *TemplateFunctions) GetBastionDNS() string {
|
|
return tf.cluster.GetBastionPublicName()
|
|
}
|
|
|
|
// This function is replacing existing yaml
|
|
func (tf *TemplateFunctions) GetBastionZone() (string, error) {
|
|
var name string
|
|
if len(tf.cluster.Spec.Zones) < 1 {
|
|
return "", fmt.Errorf("Unable to detect zone name for bastion")
|
|
} else {
|
|
// If we have a list, always use the first one
|
|
name = tf.cluster.Spec.Zones[0].Name
|
|
}
|
|
return name, nil
|
|
}
|
|
|
|
func (tf *TemplateFunctions) GetBastionMachineType() string {
|
|
return tf.cluster.GetBastionMachineType()
|
|
}
|
|
|
|
// Will attempt to calculate a meaningful name for an ELB given a prefix
|
|
// Will never return a string longer than 32 chars
|
|
func (tf *TemplateFunctions) GetELBName32(prefix string) (string, error) {
|
|
var returnString string
|
|
c := tf.cluster.Name
|
|
s := strings.Split(c, ".")
|
|
if len(s) > 0 {
|
|
returnString = fmt.Sprintf("%s-%s", prefix, s[0])
|
|
} else {
|
|
returnString = fmt.Sprintf("%s-%s", prefix, c)
|
|
}
|
|
if len(returnString) > 32 {
|
|
returnString = returnString[:32]
|
|
}
|
|
return returnString, nil
|
|
}
|
|
|
|
func (tf *TemplateFunctions) GetBastionImageId() (string, error) {
|
|
if len(tf.instanceGroups) == 0 {
|
|
return "", fmt.Errorf("Unable to find AMI in instance group")
|
|
} else if len(tf.instanceGroups) > 0 {
|
|
ami := tf.instanceGroups[0].Spec.Image
|
|
for i := 1; i < len(tf.instanceGroups); i++ {
|
|
// If we can't be sure all AMIs are the same, we don't know which one to use for the bastion host
|
|
if tf.instanceGroups[i].Spec.Image != ami {
|
|
return "", fmt.Errorf("Unable to use multiple image id's with a private bastion")
|
|
}
|
|
}
|
|
return ami, nil
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
// SharedZone is a simple helper function which makes the templates for a shared Zone clearer
|
|
func (tf *TemplateFunctions) SharedZone(zone *api.ClusterZoneSpec) bool {
|
|
return zone.ProviderID != ""
|
|
}
|
|
|
|
// AdminCIDR returns the CIDRs that are allowed to access the admin ports of the cluster
|
|
// (22, 443 on master and 22 on nodes)
|
|
func (tf *TemplateFunctions) AdminCIDR() []string {
|
|
if len(tf.cluster.Spec.AdminAccess) == 0 {
|
|
return []string{"0.0.0.0/0"}
|
|
}
|
|
return tf.cluster.Spec.AdminAccess
|
|
}
|
|
|
|
// IAMServiceEC2 returns the name of the IAM service for EC2 in the current region
|
|
// it is ec2.amazonaws.com everywhere but in cn-north, where it is ec2.amazonaws.com.cn
|
|
func (tf *TemplateFunctions) IAMServiceEC2() string {
|
|
switch tf.region {
|
|
case "cn-north-1":
|
|
return "ec2.amazonaws.com.cn"
|
|
default:
|
|
return "ec2.amazonaws.com"
|
|
}
|
|
}
|
|
|
|
// Image returns the docker image name for the specified component
|
|
func (tf *TemplateFunctions) Image(component string) (string, error) {
|
|
if component == "kube-dns" {
|
|
// TODO: Once we are shipping different versions, start to use them
|
|
return "gcr.io/google_containers/kubedns-amd64:1.3", nil
|
|
}
|
|
|
|
if !isBaseURL(tf.cluster.Spec.KubernetesVersion) {
|
|
return "gcr.io/google_containers/" + component + ":" + "v" + tf.cluster.Spec.KubernetesVersion, nil
|
|
}
|
|
|
|
baseURL := tf.cluster.Spec.KubernetesVersion
|
|
baseURL = strings.TrimSuffix(baseURL, "/")
|
|
|
|
tagURL := baseURL + "/bin/linux/amd64/" + component + ".docker_tag"
|
|
glog.V(2).Infof("Downloading docker tag for %s from: %s", component, tagURL)
|
|
|
|
b, err := vfs.Context.ReadFile(tagURL)
|
|
if err != nil {
|
|
return "", fmt.Errorf("error reading tag file %q: %v", tagURL, err)
|
|
}
|
|
tag := strings.TrimSpace(string(b))
|
|
glog.V(2).Infof("Found tag %q for %q", tag, component)
|
|
|
|
return "gcr.io/google_containers/" + component + ":" + tag, nil
|
|
}
|
|
|
|
// HasTag returns true if the specified tag is set
|
|
func (tf *TemplateFunctions) HasTag(tag string) bool {
|
|
_, found := tf.tags[tag]
|
|
return found
|
|
}
|
|
|
|
// buildAWSIAMPolicy produces the AWS IAM policy for the given role
|
|
func (tf *TemplateFunctions) buildAWSIAMPolicy(role api.InstanceGroupRole) (string, error) {
|
|
b := &IAMPolicyBuilder{
|
|
Cluster: tf.cluster,
|
|
Role: role,
|
|
Region: tf.region,
|
|
}
|
|
|
|
policy, err := b.BuildAWSIAMPolicy()
|
|
if err != nil {
|
|
return "", fmt.Errorf("error building IAM policy: %v", err)
|
|
}
|
|
json, err := policy.AsJSON()
|
|
if err != nil {
|
|
return "", fmt.Errorf("error building IAM policy: %v", err)
|
|
}
|
|
return json, nil
|
|
}
|
|
|
|
// CloudTags computes the tags to apply to instances in the specified InstanceGroup
|
|
func (tf *TemplateFunctions) CloudTags(ig *api.InstanceGroup) (map[string]string, error) {
|
|
labels := make(map[string]string)
|
|
|
|
// Apply any user-specified labels
|
|
for k, v := range ig.Spec.CloudLabels {
|
|
labels[k] = v
|
|
}
|
|
|
|
// The system tags take priority because the cluster likely breaks without them...
|
|
|
|
if ig.Spec.Role == api.InstanceGroupRoleMaster {
|
|
labels["k8s.io/role/master"] = "1"
|
|
}
|
|
|
|
if ig.Spec.Role == api.InstanceGroupRoleNode {
|
|
labels["k8s.io/role/node"] = "1"
|
|
}
|
|
|
|
return labels, nil
|
|
}
|
|
|
|
// GetInstanceGroup returns the instance group with the specified name
|
|
func (tf *TemplateFunctions) GetInstanceGroup(name string) (*api.InstanceGroup, error) {
|
|
for _, ig := range tf.instanceGroups {
|
|
if ig.Name == name {
|
|
return ig, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("InstanceGroup %q not found", name)
|
|
}
|
|
|
|
// APIServerCount returns the value for the apiserver --apiserver-count flag
|
|
func (tf *TemplateFunctions) APIServerCount() int {
|
|
count := 0
|
|
for _, ig := range tf.instanceGroups {
|
|
if !ig.IsMaster() {
|
|
continue
|
|
}
|
|
size := fi.IntValue(ig.Spec.MaxSize)
|
|
if size == 0 {
|
|
size = fi.IntValue(ig.Spec.MinSize)
|
|
}
|
|
count += size
|
|
}
|
|
return count
|
|
}
|
|
|
|
func (tf *TemplateFunctions) DnsControllerArgv() ([]string, error) {
|
|
var argv []string
|
|
|
|
argv = append(argv, "/usr/bin/dns-controller")
|
|
|
|
argv = append(argv, "--watch-ingress=false")
|
|
argv = append(argv, "--dns=aws-route53")
|
|
|
|
zone := tf.cluster.Spec.DNSZone
|
|
if zone != "" {
|
|
if strings.Contains(zone, ".") {
|
|
// match by name
|
|
argv = append(argv, "--zone="+zone)
|
|
} else {
|
|
// match by id
|
|
argv = append(argv, "--zone=*/"+zone)
|
|
}
|
|
}
|
|
// permit wildcard updates
|
|
argv = append(argv, "--zone=*/*")
|
|
argv = append(argv, "-v=8")
|
|
|
|
return argv, nil
|
|
}
|