Merge pull request #5 from justinsb/rename_cloudup_to_kops_create

Merge cloudup tool into `kops create cluster`
This commit is contained in:
Justin Santa Barbara 2016-06-30 10:27:45 -04:00 committed by GitHub
commit 153320078f
12 changed files with 476 additions and 418 deletions

View File

@ -23,7 +23,6 @@ gofmt:
kops-tar: gocode kops-tar: gocode
rm -rf .build/kops/tar rm -rf .build/kops/tar
mkdir -p .build/kops/tar/kops/ mkdir -p .build/kops/tar/kops/
cp ${GOPATH}/bin/cloudup .build/kops/tar/kops/cloudup
cp ${GOPATH}/bin/kops .build/kops/tar/kops/kops cp ${GOPATH}/bin/kops .build/kops/tar/kops/kops
cp -r models/ .build/kops/tar/kops/models/ cp -r models/ .build/kops/tar/kops/models/
tar czvf .build/kops.tar.gz -C .build/kops/tar/ . tar czvf .build/kops.tar.gz -C .build/kops/tar/ .

View File

@ -42,7 +42,7 @@ you should use Go 1.6 or later)
``` ```
export MYZONE=<kubernetes.myzone.com> export MYZONE=<kubernetes.myzone.com>
export KOPS_STATE_STORE=s3://<somes3bucket> export KOPS_STATE_STORE=s3://<somes3bucket>
${GOPATH}/bin/cloudup --v=0 --logtostderr --cloud=aws --zones=us-east-1c --name=${MYZONE} ${GOPATH}/bin/kops create cluster --v=0 --logtostderr --cloud=aws --zones=us-east-1c --name=${MYZONE}
``` ```
If you have problems, please set `--v=8 --logtostderr` and open an issue, and ping justinsb on slack! If you have problems, please set `--v=8 --logtostderr` and open an issue, and ping justinsb on slack!
@ -96,7 +96,7 @@ the desired state of the world.
Each file in the tree describes a Task. Each file in the tree describes a Task.
On the nodeup side, Tasks can manage files, systemd services, packages etc. On the nodeup side, Tasks can manage files, systemd services, packages etc.
On the cloudup side, Tasks manage cloud resources: instances, networks, disks etc. On the `kops create cluster` side, Tasks manage cloud resources: instances, networks, disks etc.
## Workaround for terraform bug ## Workaround for terraform bug
@ -104,24 +104,24 @@ Terraform currently has a bug where it can't create AWS tags containing a dot.
you can't use terraform to build EC2 resources that are tagged with `k8s.io/...` tags. Thankfully this is only you can't use terraform to build EC2 resources that are tagged with `k8s.io/...` tags. Thankfully this is only
the volumes, and it isn't the worst idea to build these separately anyway. the volumes, and it isn't the worst idea to build these separately anyway.
We divide the 'cloudup' model into three parts: We divide the 'kops create cluster' model into three parts:
* models/config which contains all the options * models/config which contains all the options
* models/proto which sets up the volumes and other data which would be hard to recover (e.g. likely keys & secrets in the near future) * models/proto which sets up the volumes and other data which would be hard to recover (e.g. likely keys & secrets in the near future)
* models/cloudup which is the main cloudup model for configuration everything else * models/cloudup which is the main cloud model for configuring everything else
So you don't use terraform for the 'proto' phase (you can't anyway, because of the bug!): So you don't use terraform for the 'proto' phase (you can't anyway, because of the bug!):
``` ```
export KOPS_STATE_STORE=s3://<somes3bucket> export KOPS_STATE_STORE=s3://<somes3bucket>
export CLUSTER_NAME=<kubernetes.myzone.com> export CLUSTER_NAME=<kubernetes.myzone.com>
${GOPATH}/bin/cloudup --v=0 --logtostderr --cloud=aws --zones=us-east-1c --name=${CLUSTER_NAME} --model=config,proto ${GOPATH}/bin/kops create cluster --v=0 --logtostderr --cloud=aws --zones=us-east-1c --name=${CLUSTER_NAME} --model=config,proto
``` ```
And then you can use terraform to do the remainder of the installation: And then you can use terraform to do the remainder of the installation:
``` ```
export CLUSTER_NAME=<kubernetes.myzone.com> export CLUSTER_NAME=<kubernetes.myzone.com>
${GOPATH}/bin/cloudup --v=0 --logtostderr --cloud=aws --zones=us-east-1c --name=${CLUSTER_NAME} --model=config,cloudup --target=terraform ${GOPATH}/bin/kops create cluster --v=0 --logtostderr --cloud=aws --zones=us-east-1c --name=${CLUSTER_NAME} --model=config,cloudup --target=terraform
``` ```
Then, to apply using terraform: Then, to apply using terraform:

View File

@ -1,395 +0,0 @@
package main
import (
goflag "flag"
"github.com/golang/glog"
"github.com/spf13/pflag"
"k8s.io/kops/upup/pkg/api"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup"
"k8s.io/kops/upup/pkg/fi/utils"
"k8s.io/kops/upup/pkg/fi/vfs"
"k8s.io/kubernetes/pkg/util/sets"
"os"
"os/exec"
"path"
"strings"
)
var EtcdClusters = []string{"main", "events"}
// zonesToCloud allows us to infer from certain well-known zones to a cloud
// Note it is safe to "overmap" zones that don't exist: we'll check later if the zones actually exist
var zonesToCloud = map[string]fi.CloudProviderID{
"us-east-1a": fi.CloudProviderAWS,
"us-east-1b": fi.CloudProviderAWS,
"us-east-1c": fi.CloudProviderAWS,
"us-east-1d": fi.CloudProviderAWS,
"us-east-1e": fi.CloudProviderAWS,
"us-west-1a": fi.CloudProviderAWS,
"us-west-1b": fi.CloudProviderAWS,
"us-west-1c": fi.CloudProviderAWS,
"us-west-1d": fi.CloudProviderAWS,
"us-west-1e": fi.CloudProviderAWS,
"us-west-2a": fi.CloudProviderAWS,
"us-west-2b": fi.CloudProviderAWS,
"us-west-2c": fi.CloudProviderAWS,
"us-west-2d": fi.CloudProviderAWS,
"us-west-2e": fi.CloudProviderAWS,
"eu-west-1a": fi.CloudProviderAWS,
"eu-west-1b": fi.CloudProviderAWS,
"eu-west-1c": fi.CloudProviderAWS,
"eu-west-1d": fi.CloudProviderAWS,
"eu-west-1e": fi.CloudProviderAWS,
"eu-central-1a": fi.CloudProviderAWS,
"eu-central-1b": fi.CloudProviderAWS,
"eu-central-1c": fi.CloudProviderAWS,
"eu-central-1d": fi.CloudProviderAWS,
"eu-central-1e": fi.CloudProviderAWS,
"ap-southeast-1a": fi.CloudProviderAWS,
"ap-southeast-1b": fi.CloudProviderAWS,
"ap-southeast-1c": fi.CloudProviderAWS,
"ap-southeast-1d": fi.CloudProviderAWS,
"ap-southeast-1e": fi.CloudProviderAWS,
"ap-southeast-2a": fi.CloudProviderAWS,
"ap-southeast-2b": fi.CloudProviderAWS,
"ap-southeast-2c": fi.CloudProviderAWS,
"ap-southeast-2d": fi.CloudProviderAWS,
"ap-southeast-2e": fi.CloudProviderAWS,
"ap-northeast-1a": fi.CloudProviderAWS,
"ap-northeast-1b": fi.CloudProviderAWS,
"ap-northeast-1c": fi.CloudProviderAWS,
"ap-northeast-1d": fi.CloudProviderAWS,
"ap-northeast-1e": fi.CloudProviderAWS,
"ap-northeast-2a": fi.CloudProviderAWS,
"ap-northeast-2b": fi.CloudProviderAWS,
"ap-northeast-2c": fi.CloudProviderAWS,
"ap-northeast-2d": fi.CloudProviderAWS,
"ap-northeast-2e": fi.CloudProviderAWS,
"sa-east-1a": fi.CloudProviderAWS,
"sa-east-1b": fi.CloudProviderAWS,
"sa-east-1c": fi.CloudProviderAWS,
"sa-east-1d": fi.CloudProviderAWS,
"sa-east-1e": fi.CloudProviderAWS,
}
func main() {
executableLocation, err := exec.LookPath(os.Args[0])
if err != nil {
glog.Fatalf("Cannot determine location of cloudup tool: %q. Please report this problem!", os.Args[0])
}
modelsBaseDirDefault := path.Join(path.Dir(executableLocation), "models")
dryrun := pflag.Bool("dryrun", false, "Don't create cloud resources; just show what would be done")
target := pflag.String("target", "direct", "Target - direct, terraform")
//configFile := pflag.String("conf", "", "Configuration file to load")
modelsBaseDir := pflag.String("modelstore", modelsBaseDirDefault, "Source directory where models are stored")
models := pflag.String("model", "config,proto,cloudup", "Models to apply (separate multiple models with commas)")
nodeModel := pflag.String("nodemodel", "nodeup", "Model to use for node configuration")
defaultStateStore := os.Getenv("KOPS_STATE_STORE")
stateLocation := pflag.String("state", defaultStateStore, "Location to use to store configuration state")
cloudProvider := pflag.String("cloud", "", "Cloud provider to use - gce, aws")
zones := pflag.String("zones", "", "Zones in which to run the cluster")
masterZones := pflag.String("master-zones", "", "Zones in which to run masters (must be an odd number)")
project := pflag.String("project", "", "Project to use (must be set on GCE)")
clusterName := pflag.String("name", "", "Name for cluster")
kubernetesVersion := pflag.String("kubernetes-version", "", "Version of kubernetes to run (defaults to latest)")
sshPublicKey := pflag.String("ssh-public-key", "~/.ssh/id_rsa.pub", "SSH public key to use")
nodeSize := pflag.String("node-size", "", "Set instance size for nodes")
masterSize := pflag.String("master-size", "", "Set instance size for masters")
vpcID := pflag.String("vpc", "", "Set to use a shared VPC")
networkCIDR := pflag.String("network-cidr", "", "Set to override the default network CIDR")
nodeCount := pflag.Int("node-count", 0, "Set the number of nodes")
image := pflag.String("image", "", "Image to use")
dnsZone := pflag.String("dns-zone", "", "DNS hosted zone to use (defaults to last two components of cluster name)")
outDir := pflag.String("out", "", "Path to write any local output")
pflag.CommandLine.AddGoFlagSet(goflag.CommandLine)
pflag.Parse()
goflag.CommandLine.Parse([]string{})
isDryrun := false
if *dryrun {
isDryrun = true
*target = "dryrun"
}
if *stateLocation == "" {
glog.Errorf("--state is required")
os.Exit(1)
}
if *clusterName == "" {
glog.Errorf("--name is required")
os.Exit(1)
}
statePath, err := vfs.Context.BuildVfsPath(*stateLocation)
if err != nil {
glog.Errorf("error building state location: %v", err)
os.Exit(1)
}
if *outDir == "" {
*outDir = "out"
}
stateStore, err := fi.NewVFSStateStore(statePath, *clusterName, isDryrun)
if err != nil {
glog.Errorf("error building state store: %v", err)
os.Exit(1)
}
cluster, instanceGroups, err := api.ReadConfig(stateStore)
if err != nil {
glog.Errorf("error loading configuration: %v", err)
os.Exit(1)
}
if *zones != "" {
existingZones := make(map[string]*api.ClusterZoneSpec)
for _, zone := range cluster.Spec.Zones {
existingZones[zone.Name] = zone
}
for _, zone := range parseZoneList(*zones) {
if existingZones[zone] == nil {
cluster.Spec.Zones = append(cluster.Spec.Zones, &api.ClusterZoneSpec{
Name: zone,
})
}
}
}
var masters []*api.InstanceGroup
var nodes []*api.InstanceGroup
for _, group := range instanceGroups {
if group.IsMaster() {
masters = append(masters, group)
} else {
nodes = append(nodes, group)
}
}
if *masterZones == "" {
if len(masters) == 0 {
// Default to putting into every zone
// TODO: just the first 1 or 3 zones; or should we force users to declare?
for _, zone := range cluster.Spec.Zones {
g := &api.InstanceGroup{}
g.Spec.Role = api.InstanceGroupRoleMaster
g.Spec.Zones = []string{zone.Name}
g.Spec.MinSize = fi.Int(1)
g.Spec.MaxSize = fi.Int(1)
g.Name = "master-" + zone.Name // Subsequent masters (if we support that) could be <zone>-1, <zone>-2
instanceGroups = append(instanceGroups, g)
masters = append(masters, g)
}
}
} else {
if len(masters) == 0 {
for _, zone := range parseZoneList(*masterZones) {
g := &api.InstanceGroup{}
g.Spec.Role = api.InstanceGroupRoleMaster
g.Spec.Zones = []string{zone}
g.Spec.MinSize = fi.Int(1)
g.Spec.MaxSize = fi.Int(1)
g.Name = "master-" + zone
instanceGroups = append(instanceGroups, g)
masters = append(masters, g)
}
} else {
// This is hard, because of the etcd cluster
glog.Errorf("Cannot change master-zones from the CLI")
os.Exit(1)
}
}
if len(cluster.Spec.EtcdClusters) == 0 {
zones := sets.NewString()
for _, group := range instanceGroups {
for _, zone := range group.Spec.Zones {
zones.Insert(zone)
}
}
etcdZones := zones.List()
if (len(etcdZones) % 2) == 0 {
// Not technically a requirement, but doesn't really make sense to allow
glog.Errorf("There should be an odd number of master-zones, for etcd's quorum. Hint: Use --zones and --master-zones to declare node zones and master zones separately.")
os.Exit(1)
}
for _, etcdCluster := range EtcdClusters {
etcd := &api.EtcdClusterSpec{}
etcd.Name = etcdCluster
for _, zone := range etcdZones {
m := &api.EtcdMemberSpec{}
m.Name = zone
m.Zone = zone
etcd.Members = append(etcd.Members, m)
}
cluster.Spec.EtcdClusters = append(cluster.Spec.EtcdClusters, etcd)
}
}
if len(nodes) == 0 {
g := &api.InstanceGroup{}
g.Spec.Role = api.InstanceGroupRoleNode
g.Name = "nodes"
instanceGroups = append(instanceGroups, g)
nodes = append(nodes, g)
}
if *nodeSize != "" {
for _, group := range nodes {
group.Spec.MachineType = *nodeSize
}
}
if *image != "" {
for _, group := range instanceGroups {
group.Spec.Image = *image
}
}
if *nodeCount != 0 {
for _, group := range nodes {
group.Spec.MinSize = nodeCount
group.Spec.MaxSize = nodeCount
}
}
if *masterSize != "" {
for _, group := range masters {
group.Spec.MachineType = *masterSize
}
}
if *dnsZone != "" {
cluster.Spec.DNSZone = *dnsZone
}
if *cloudProvider != "" {
cluster.Spec.CloudProvider = *cloudProvider
}
if *project != "" {
cluster.Spec.Project = *project
}
if *clusterName != "" {
cluster.Name = *clusterName
}
if *kubernetesVersion != "" {
cluster.Spec.KubernetesVersion = *kubernetesVersion
}
if *vpcID != "" {
cluster.Spec.NetworkID = *vpcID
}
if *networkCIDR != "" {
cluster.Spec.NetworkCIDR = *networkCIDR
}
if cluster.SharedVPC() && cluster.Spec.NetworkCIDR == "" {
glog.Errorf("Must specify NetworkCIDR when VPC is set")
os.Exit(1)
}
if cluster.Spec.CloudProvider == "" {
for _, zone := range cluster.Spec.Zones {
cloud := zonesToCloud[zone.Name]
if cloud != "" {
glog.Infof("Inferred --cloud=%s from zone %q", cloud, zone.Name)
cluster.Spec.CloudProvider = string(cloud)
break
}
}
}
if *sshPublicKey != "" {
*sshPublicKey = utils.ExpandPath(*sshPublicKey)
}
err = cluster.PerformAssignments()
if err != nil {
glog.Errorf("error populating configuration: %v", err)
os.Exit(1)
}
err = api.PerformAssignmentsInstanceGroups(instanceGroups)
if err != nil {
glog.Errorf("error populating configuration: %v", err)
os.Exit(1)
}
err = api.WriteConfig(stateStore, cluster, instanceGroups)
if err != nil {
glog.Errorf("error writing updated configuration: %v", err)
os.Exit(1)
}
cmd := &cloudup.CreateClusterCmd{
Cluster: cluster,
InstanceGroups: instanceGroups,
ModelStore: *modelsBaseDir,
Models: strings.Split(*models, ","),
StateStore: stateStore,
Target: *target,
NodeModel: *nodeModel,
SSHPublicKey: *sshPublicKey,
OutDir: *outDir,
}
//if *configFile != "" {
// //confFile := path.Join(cmd.StateDir, "kubernetes.yaml")
// err := cmd.LoadConfig(configFile)
// if err != nil {
// glog.Errorf("error loading config: %v", err)
// os.Exit(1)
// }
//}
err = cmd.Run()
if err != nil {
glog.Errorf("error running command: %v", err)
os.Exit(1)
}
glog.Infof("Completed successfully")
}
func parseZoneList(s string) []string {
var filtered []string
for _, v := range strings.Split(s, ",") {
v = strings.TrimSpace(v)
if v == "" {
continue
}
v = strings.ToLower(v)
filtered = append(filtered, v)
}
return filtered
}

16
cmd/kops/create.go Normal file
View File

@ -0,0 +1,16 @@
package main
import (
"github.com/spf13/cobra"
)
// createCmd represents the create command
var createCmd = &cobra.Command{
Use: "create",
Short: "create clusters",
Long: `Create clusters`,
}
func init() {
rootCommand.AddCommand(createCmd)
}

363
cmd/kops/create_cluster.go Normal file
View File

@ -0,0 +1,363 @@
package main
import (
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/kops/upup/pkg/api"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup"
"os"
"os/exec"
"path"
"strings"
"k8s.io/kops/upup/pkg/fi/vfs"
"k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kops/upup/pkg/fi/utils"
"fmt"
)
type CreateClusterCmd struct {
DryRun bool
Target string
ModelsBaseDir string
Models string
NodeModel string
//StateStore string
Cloud string
//Region string
Zones string
MasterZones string
NodeSize string
MasterSize string
NodeCount int
Project string
KubernetesVersion string
OutDir string
Image string
SSHPublicKey string
VPCID string
NetworkCIDR string
DNSZone string
}
var createCluster CreateClusterCmd
func init() {
cmd := &cobra.Command{
Use: "cluster",
Short: "Create cluster",
Long: `Creates a k8s cluster.`,
Run: func(cmd *cobra.Command, args []string) {
err := createCluster.Run()
if err != nil {
glog.Exitf("%v", err)
}
},
}
createCmd.AddCommand(cmd)
executableLocation, err := exec.LookPath(os.Args[0])
if err != nil {
glog.Fatalf("Cannot determine location of kops tool: %q. Please report this problem!", os.Args[0])
}
modelsBaseDirDefault := path.Join(path.Dir(executableLocation), "models")
cmd.Flags().BoolVar(&createCluster.DryRun, "dryrun", false, "Don't create cloud resources; just show what would be done")
cmd.Flags().StringVar(&createCluster.Target, "target", "direct", "Target - direct, terraform")
//configFile := cmd.Flags().StringVar(&createCluster., "conf", "", "Configuration file to load")
cmd.Flags().StringVar(&createCluster.ModelsBaseDir, "modeldir", modelsBaseDirDefault, "Source directory where models are stored")
cmd.Flags().StringVar(&createCluster.Models, "model", "config,proto,cloudup", "Models to apply (separate multiple models with commas)")
cmd.Flags().StringVar(&createCluster.NodeModel, "nodemodel", "nodeup", "Model to use for node configuration")
//defaultStateStore := os.Getenv("KOPS_STATE_STORE")
//cmd.Flags().StringVar(&createCluster.StateStore, "state", defaultStateStore, "Location to use to store configuration state")
cmd.Flags().StringVar(&createCluster.Cloud, "cloud", "", "Cloud provider to use - gce, aws")
cmd.Flags().StringVar(&createCluster.Zones, "zones", "", "Zones in which to run the cluster")
cmd.Flags().StringVar(&createCluster.MasterZones, "master-zones", "", "Zones in which to run masters (must be an odd number)")
cmd.Flags().StringVar(&createCluster.Project, "project", "", "Project to use (must be set on GCE)")
//cmd.Flags().StringVar(&createCluster.Name, "name", "", "Name for cluster")
cmd.Flags().StringVar(&createCluster.KubernetesVersion, "kubernetes-version", "", "Version of kubernetes to run (defaults to latest)")
cmd.Flags().StringVar(&createCluster.SSHPublicKey, "ssh-public-key", "~/.ssh/id_rsa.pub", "SSH public key to use")
cmd.Flags().StringVar(&createCluster.NodeSize, "node-size", "", "Set instance size for nodes")
cmd.Flags().StringVar(&createCluster.MasterSize, "master-size", "", "Set instance size for masters")
cmd.Flags().StringVar(&createCluster.VPCID, "vpc", "", "Set to use a shared VPC")
cmd.Flags().StringVar(&createCluster.NetworkCIDR, "network-cidr", "", "Set to override the default network CIDR")
cmd.Flags().IntVar(&createCluster.NodeCount, "node-count", 0, "Set the number of nodes")
cmd.Flags().StringVar(&createCluster.Image, "image", "", "Image to use")
cmd.Flags().StringVar(&createCluster.DNSZone, "dns-zone", "", "DNS hosted zone to use (defaults to last two components of cluster name)")
cmd.Flags().StringVar(&createCluster.OutDir, "out", "", "Path to write any local output")
}
var EtcdClusters = []string{"main", "events"}
func (c *CreateClusterCmd) Run() error {
isDryrun := false
if c.DryRun {
isDryrun = true
c.Target = "dryrun"
}
stateStoreLocation := rootCommand.stateLocation
if stateStoreLocation == "" {
return fmt.Errorf("--state is required")
}
clusterName := rootCommand.clusterName
if clusterName == "" {
return fmt.Errorf("--name is required")
}
// TODO: Reuse rootCommand stateStore logic?
statePath, err := vfs.Context.BuildVfsPath(stateStoreLocation)
if err != nil {
return fmt.Errorf("error building state location: %v", err)
}
if c.OutDir == "" {
c.OutDir = "out"
}
stateStore, err := fi.NewVFSStateStore(statePath, clusterName, isDryrun)
if err != nil {
return fmt.Errorf("error building state store: %v", err)
}
cluster, instanceGroups, err := api.ReadConfig(stateStore)
if err != nil {
return fmt.Errorf("error loading configuration: %v", err)
}
if c.Zones != "" {
existingZones := make(map[string]*api.ClusterZoneSpec)
for _, zone := range cluster.Spec.Zones {
existingZones[zone.Name] = zone
}
for _, zone := range parseZoneList(c.Zones) {
if existingZones[zone] == nil {
cluster.Spec.Zones = append(cluster.Spec.Zones, &api.ClusterZoneSpec{
Name: zone,
})
}
}
}
if len(cluster.Spec.Zones) == 0 {
return fmt.Errorf("must specify at least one zone for the cluster (use --zones)")
}
var masters []*api.InstanceGroup
var nodes []*api.InstanceGroup
for _, group := range instanceGroups {
if group.IsMaster() {
masters = append(masters, group)
} else {
nodes = append(nodes, group)
}
}
if c.MasterZones == "" {
if len(masters) == 0 {
// Default to putting into every zone
// TODO: just the first 1 or 3 zones; or should we force users to declare?
for _, zone := range cluster.Spec.Zones {
g := &api.InstanceGroup{}
g.Spec.Role = api.InstanceGroupRoleMaster
g.Spec.Zones = []string{zone.Name}
g.Spec.MinSize = fi.Int(1)
g.Spec.MaxSize = fi.Int(1)
g.Name = "master-" + zone.Name // Subsequent masters (if we support that) could be <zone>-1, <zone>-2
instanceGroups = append(instanceGroups, g)
masters = append(masters, g)
}
}
} else {
if len(masters) == 0 {
for _, zone := range parseZoneList(c.MasterZones) {
g := &api.InstanceGroup{}
g.Spec.Role = api.InstanceGroupRoleMaster
g.Spec.Zones = []string{zone}
g.Spec.MinSize = fi.Int(1)
g.Spec.MaxSize = fi.Int(1)
g.Name = "master-" + zone
instanceGroups = append(instanceGroups, g)
masters = append(masters, g)
}
} else {
// This is hard, because of the etcd cluster
glog.Errorf("Cannot change master-zones from the CLI")
os.Exit(1)
}
}
if len(cluster.Spec.EtcdClusters) == 0 {
zones := sets.NewString()
for _, group := range instanceGroups {
for _, zone := range group.Spec.Zones {
zones.Insert(zone)
}
}
etcdZones := zones.List()
if (len(etcdZones) % 2) == 0 {
// Not technically a requirement, but doesn't really make sense to allow
glog.Errorf("There should be an odd number of master-zones, for etcd's quorum. Hint: Use --zones and --master-zones to declare node zones and master zones separately.")
os.Exit(1)
}
for _, etcdCluster := range EtcdClusters {
etcd := &api.EtcdClusterSpec{}
etcd.Name = etcdCluster
for _, zone := range etcdZones {
m := &api.EtcdMemberSpec{}
m.Name = zone
m.Zone = zone
etcd.Members = append(etcd.Members, m)
}
cluster.Spec.EtcdClusters = append(cluster.Spec.EtcdClusters, etcd)
}
}
if len(nodes) == 0 {
g := &api.InstanceGroup{}
g.Spec.Role = api.InstanceGroupRoleNode
g.Name = "nodes"
instanceGroups = append(instanceGroups, g)
nodes = append(nodes, g)
}
if c.NodeSize != "" {
for _, group := range nodes {
group.Spec.MachineType = c.NodeSize
}
}
if c.Image != "" {
for _, group := range instanceGroups {
group.Spec.Image = c.Image
}
}
if c.NodeCount != 0 {
for _, group := range nodes {
group.Spec.MinSize = fi.Int(c.NodeCount)
group.Spec.MaxSize = fi.Int(c.NodeCount)
}
}
if c.MasterSize != "" {
for _, group := range masters {
group.Spec.MachineType = c.MasterSize
}
}
if c.DNSZone != "" {
cluster.Spec.DNSZone = c.DNSZone
}
if c.Cloud != "" {
cluster.Spec.CloudProvider = c.Cloud
}
if c.Project != "" {
cluster.Spec.Project = c.Project
}
if clusterName != "" {
cluster.Name = clusterName
}
if c.KubernetesVersion != "" {
cluster.Spec.KubernetesVersion = c.KubernetesVersion
}
if c.VPCID != "" {
cluster.Spec.NetworkID = c.VPCID
}
if c.NetworkCIDR != "" {
cluster.Spec.NetworkCIDR = c.NetworkCIDR
}
if cluster.SharedVPC() && cluster.Spec.NetworkCIDR == "" {
glog.Errorf("Must specify NetworkCIDR when VPC is set")
os.Exit(1)
}
if cluster.Spec.CloudProvider == "" {
for _, zone := range cluster.Spec.Zones {
cloud, known := fi.GuessCloudForZone(zone.Name)
if known {
glog.Infof("Inferred --cloud=%s from zone %q", cloud, zone.Name)
cluster.Spec.CloudProvider = string(cloud)
break
}
}
}
if c.SSHPublicKey != "" {
c.SSHPublicKey = utils.ExpandPath(c.SSHPublicKey)
}
err = cluster.PerformAssignments()
if err != nil {
return fmt.Errorf("error populating configuration: %v", err)
}
err = api.PerformAssignmentsInstanceGroups(instanceGroups)
if err != nil {
return fmt.Errorf("error populating configuration: %v", err)
}
err = api.WriteConfig(stateStore, cluster, instanceGroups)
if err != nil {
return fmt.Errorf("error writing updated configuration: %v", err)
}
cmd := &cloudup.CreateClusterCmd{
Cluster: cluster,
InstanceGroups: instanceGroups,
ModelStore: c.ModelsBaseDir,
Models: strings.Split(c.Models, ","),
StateStore: stateStore,
Target: c.Target,
NodeModel: c.NodeModel,
SSHPublicKey: c.SSHPublicKey,
OutDir: c.OutDir,
}
//if *configFile != "" {
// //confFile := path.Join(cmd.StateDir, "kubernetes.yaml")
// err := cmd.LoadConfig(configFile)
// if err != nil {
// glog.Errorf("error loading config: %v", err)
// os.Exit(1)
// }
//}
return cmd.Run()
}
func parseZoneList(s string) []string {
var filtered []string
for _, v := range strings.Split(s, ",") {
v = strings.TrimSpace(v)
if v == "" {
continue
}
v = strings.ToLower(v)
filtered = append(filtered, v)
}
return filtered
}

View File

@ -3,7 +3,7 @@
When launching into a shared VPC, the VPC & the Internet Gateway will be reused, but we create a new subnet per zone, When launching into a shared VPC, the VPC & the Internet Gateway will be reused, but we create a new subnet per zone,
and a new route table. and a new route table.
Use cloudup with the `--vpc` and `--network-cidr` arguments for your existing VPC, with --dryrun so we can see the Use kops create cluster with the `--vpc` and `--network-cidr` arguments for your existing VPC, with --dryrun so we can see the
config before we apply it. config before we apply it.
@ -11,7 +11,7 @@ config before we apply it.
export KOPS_STATE_STORE=s3://<somes3bucket> export KOPS_STATE_STORE=s3://<somes3bucket>
export CLUSTER_NAME=<sharedvpc.mydomain.com> export CLUSTER_NAME=<sharedvpc.mydomain.com>
cloudup --zones=us-east-1b --name=${CLUSTER_NAME} \ kops create cluster --zones=us-east-1b --name=${CLUSTER_NAME} \
--vpc=vpc-a80734c1 --network-cidr=10.100.0.0/16 --dryrun --vpc=vpc-a80734c1 --network-cidr=10.100.0.0/16 --dryrun
``` ```
@ -36,27 +36,27 @@ Verify that networkCIDR & networkID match your VPC CIDR & ID. You likely need t
because subnets in a VPC cannot overlap. because subnets in a VPC cannot overlap.
You can then run cloudup again in dryrun mode (you don't need any arguments, because they're all in the config file): You can then run `kops create cluster` again in dryrun mode (you don't need any arguments, because they're all in the config file):
``` ```
cloudup --dryrun --name=${CLUSTER_NAME} kops create cluster --dryrun --name=${CLUSTER_NAME}
``` ```
Review the changes to make sure they are OK - the Kubernetes settings might not be ones you want on a shared VPC (in which case, Review the changes to make sure they are OK - the Kubernetes settings might not be ones you want on a shared VPC (in which case,
open an issue!) open an issue!)
Note also the Kubernetes VPCs (currently) require `EnableDNSHostnames=true`. Cloudup will detect the required change, Note also the Kubernetes VPCs (currently) require `EnableDNSHostnames=true`. kops will detect the required change,
but refuse to make it automatically because it is a shared VPC. Please review the implications and make the change but refuse to make it automatically because it is a shared VPC. Please review the implications and make the change
to the VPC manually. to the VPC manually.
Once you're happy, you can create the cluster using: Once you're happy, you can create the cluster using:
``` ```
cloudup --name=${CLUSTER_NAME} kops create cluster --name=${CLUSTER_NAME}
``` ```
Finally, if your shared VPC has a KubernetesCluster tag (because it was created with cloudup), you should Finally, if your shared VPC has a KubernetesCluster tag (because it was created with kops), you should
probably remove that tag to indicate to indicate that the resources are not owned by that cluster, and so probably remove that tag to indicate to indicate that the resources are not owned by that cluster, and so
deleting the cluster won't try to delete the VPC. (Deleting the VPC won't succeed anyway, because it's in use, deleting the cluster won't try to delete the VPC. (Deleting the VPC won't succeed anyway, because it's in use,
but it's better to avoid the later confusion!) but it's better to avoid the later confusion!)

View File

@ -18,14 +18,14 @@ control system).
One of the most important files in the state store is the top-level config file. This file stores the main One of the most important files in the state store is the top-level config file. This file stores the main
configuration for your cluster (instance types, zones, etc)\ configuration for your cluster (instance types, zones, etc)\
When you run cloudup, we create a config file for you based on the command line options you specify. When you run `kops create cluster`, we create a state store entry for you based on the command line options you specify.
For example, when you run with `--node-size=m4.large`, we actually set a line in the config file For example, when you run with `--node-size=m4.large`, we actually set a line in the configuration
that looks like `NodeMachineType: m4.large`. that looks like `NodeMachineType: m4.large`.
The configuration you specify on the command line is actually just a convenient short-cut to The configuration you specify on the command line is actually just a convenient short-cut to
manually editing the config file. Options you specify on the command line are merged into the existing manually editing the configuration. Options you specify on the command line are merged into the existing
configuration. If you want to configure advanced options, or prefer a text-based configuration, you configuration. If you want to configure advanced options, or prefer a text-based configuration, you
may prefer to just edit the config file. may prefer to just edit the config file with `kops edit cluster`.
Because the configuration is merged, this is how you can just specify the changed arguments when Because the configuration is merged, this is how you can just specify the changed arguments when
reconfiguring your cluster - for example just `cloudup` after a dry-run. reconfiguring your cluster - for example just `kops create cluster` after a dry-run.

View File

@ -72,7 +72,7 @@ If you now list the clusters, you should see both the old cluster & the new clus
Use the normal tool to bring up the new cluster: Use the normal tool to bring up the new cluster:
``` ```
cloudup --name ${NEW_NAME} --dryrun kops create cluster --name ${NEW_NAME} --dryrun
``` ```
Things to check are that it is reusing the existing volume for the _main_ etcd cluster (but not the events clusters). Things to check are that it is reusing the existing volume for the _main_ etcd cluster (but not the events clusters).
@ -80,7 +80,7 @@ Things to check are that it is reusing the existing volume for the _main_ etcd c
And then when you are happy: And then when you are happy:
``` ```
cloudup --name ${NEW_NAME} kops create cluster --name ${NEW_NAME}
``` ```
@ -149,7 +149,7 @@ Due to a limitation in ELBs (you can't replace all the subnets), if you have ELB
* `kops edit cluster --name ${NEW_NAME}` * `kops edit cluster --name ${NEW_NAME}`
* Add a zone to the `zones` section and save the file (it normally suffices to just add `- name: us-west-2b` or whatever * Add a zone to the `zones` section and save the file (it normally suffices to just add `- name: us-west-2b` or whatever
zone you are adding; kops will auto-populate the CIDR. zone you are adding; kops will auto-populate the CIDR.
* cloudup --name ${NEW_NAME} * kops create cluster --name ${NEW_NAME}
In the AWS control panel open the "Load Balancers" section, and for each ELB: In the AWS control panel open the "Load Balancers" section, and for each ELB:

View File

@ -8,3 +8,75 @@ const CloudProviderGCE CloudProviderID = "gce"
type Cloud interface { type Cloud interface {
ProviderID() CloudProviderID ProviderID() CloudProviderID
} }
// zonesToCloud allows us to infer from certain well-known zones to a cloud
// Note it is safe to "overmap" zones that don't exist: we'll check later if the zones actually exist
var zonesToCloud = map[string]CloudProviderID{
"us-east-1a": CloudProviderAWS,
"us-east-1b": CloudProviderAWS,
"us-east-1c": CloudProviderAWS,
"us-east-1d": CloudProviderAWS,
"us-east-1e": CloudProviderAWS,
"us-west-1a": CloudProviderAWS,
"us-west-1b": CloudProviderAWS,
"us-west-1c": CloudProviderAWS,
"us-west-1d": CloudProviderAWS,
"us-west-1e": CloudProviderAWS,
"us-west-2a": CloudProviderAWS,
"us-west-2b": CloudProviderAWS,
"us-west-2c": CloudProviderAWS,
"us-west-2d": CloudProviderAWS,
"us-west-2e": CloudProviderAWS,
"eu-west-1a": CloudProviderAWS,
"eu-west-1b": CloudProviderAWS,
"eu-west-1c": CloudProviderAWS,
"eu-west-1d": CloudProviderAWS,
"eu-west-1e": CloudProviderAWS,
"eu-central-1a": CloudProviderAWS,
"eu-central-1b": CloudProviderAWS,
"eu-central-1c": CloudProviderAWS,
"eu-central-1d": CloudProviderAWS,
"eu-central-1e": CloudProviderAWS,
"ap-southeast-1a": CloudProviderAWS,
"ap-southeast-1b": CloudProviderAWS,
"ap-southeast-1c": CloudProviderAWS,
"ap-southeast-1d": CloudProviderAWS,
"ap-southeast-1e": CloudProviderAWS,
"ap-southeast-2a": CloudProviderAWS,
"ap-southeast-2b": CloudProviderAWS,
"ap-southeast-2c": CloudProviderAWS,
"ap-southeast-2d": CloudProviderAWS,
"ap-southeast-2e": CloudProviderAWS,
"ap-northeast-1a": CloudProviderAWS,
"ap-northeast-1b": CloudProviderAWS,
"ap-northeast-1c": CloudProviderAWS,
"ap-northeast-1d": CloudProviderAWS,
"ap-northeast-1e": CloudProviderAWS,
"ap-northeast-2a": CloudProviderAWS,
"ap-northeast-2b": CloudProviderAWS,
"ap-northeast-2c": CloudProviderAWS,
"ap-northeast-2d": CloudProviderAWS,
"ap-northeast-2e": CloudProviderAWS,
"sa-east-1a": CloudProviderAWS,
"sa-east-1b": CloudProviderAWS,
"sa-east-1c": CloudProviderAWS,
"sa-east-1d": CloudProviderAWS,
"sa-east-1e": CloudProviderAWS,
}
// GuessCloudForZone tries to infer the cloudprovider from the zone name
func GuessCloudForZone(zone string) (CloudProviderID, bool) {
c, found := zonesToCloud[zone]
return c, found
}

View File

@ -78,7 +78,7 @@ func (c *AWSCloud) Tags() map[string]string {
func isTagsEventualConsistencyError(err error) bool { func isTagsEventualConsistencyError(err error) bool {
if awsErr, ok := err.(awserr.Error); ok { if awsErr, ok := err.(awserr.Error); ok {
switch awsErr.Code() { switch awsErr.Code() {
case "InvalidInstanceID.NotFound", "InvalidRouteTableID.NotFound", "InvalidVpcID.NotFound", "InvalidGroup.NotFound": case "InvalidInstanceID.NotFound", "InvalidRouteTableID.NotFound", "InvalidVpcID.NotFound", "InvalidGroup.NotFound", "InvalidSubnetID.NotFound":
return true return true
default: default:

View File

@ -383,6 +383,9 @@ func (c *CreateClusterCmd) Run() error {
case "aws": case "aws":
{ {
awsCloud := cloud.(*awsup.AWSCloud)
region = awsCloud.Region
tags["_aws"] = struct{}{} tags["_aws"] = struct{}{}
c.NodeUpTags = append(c.NodeUpTags, "_aws") c.NodeUpTags = append(c.NodeUpTags, "_aws")