Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Topology Support (AKA Private Networking) #428 #694

Merged
merged 39 commits into from
Nov 9, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
000e847
Topology Initial Commit
krisnova Oct 23, 2016
8fba14b
Small refactor - getting ready to start YAML
krisnova Oct 25, 2016
8f30225
Switching over branches
krisnova Oct 26, 2016
a1ca6b7
More progress - getting out to Github so I can switch laptops... will…
krisnova Oct 26, 2016
a1c5c77
docs
krisnova Oct 26, 2016
9bd9e30
Adding another large commit after a make codegen
krisnova Oct 27, 2016
db693a6
fitask header fix
krisnova Oct 27, 2016
0f8f2d1
fitask header fix
krisnova Oct 27, 2016
d729596
fitask header fix
krisnova Oct 27, 2016
e90b5fa
fitask header fix
krisnova Oct 27, 2016
c1e8dbe
More work on the network and EIP things
krisnova Oct 28, 2016
a3dd125
Working ElasticIP associations on subnet. Delete and Create!
krisnova Oct 28, 2016
de79ca2
Now that is one fine elastic_ip.go file
krisnova Oct 28, 2016
835e24f
Working EIP and NGW CRUD for private networking..
krisnova Oct 28, 2016
cb31579
Fixing CI build and cluster tests to work with topologies
krisnova Oct 29, 2016
cebdde3
Woo! Time to start playing with private networks in AWS!!
krisnova Oct 29, 2016
e962f9c
Adding bastion support
krisnova Oct 31, 2016
312621b
Pushing up some last minute tweaks before asking for help and feedbac…
krisnova Nov 1, 2016
0857ed1
Working Bastion with ELB - now time to start on the k8s API :) :) :)
krisnova Nov 1, 2016
37f5bb7
Working networking commit!
krisnova Nov 3, 2016
3f4bc39
Yaml Docs cleanup
krisnova Nov 3, 2016
78ecdb2
Moar YAML cleanup and putting finishing touches on k8s debugging for …
krisnova Nov 3, 2016
712882f
K8s API
krisnova Nov 3, 2016
cc2e920
Fix for https://github.com/kubernetes/kops/pull/694#issuecomment-2583…
krisnova Nov 4, 2016
479c778
Fixing DNS annotations on pods / Bumping failed iterations
krisnova Nov 8, 2016
5b81b86
Documentation and CNI requirements
krisnova Nov 8, 2016
c1644cc
Remove refs to `privatemasters`
krisnova Nov 8, 2016
3c92c6a
Fixing verbage for AWS route awstask
krisnova Nov 8, 2016
b8d2301
Header linter fix
krisnova Nov 8, 2016
729598a
Updating error message for invalid topology.
krisnova Nov 8, 2016
95a8c59
Fixing NP panic
krisnova Nov 8, 2016
5f600eb
Note on upgrades being experimental
krisnova Nov 8, 2016
6f78e0c
Flipping associatePublicIP bool for nodes/bastion/master in private t…
krisnova Nov 8, 2016
4610325
Tweaking shell script
krisnova Nov 8, 2016
de9baa2
Cleaning up dev script
krisnova Nov 8, 2016
b1febd9
Stubbing out tests
krisnova Nov 8, 2016
8c41dad
Unit Tests
krisnova Nov 8, 2016
bcbf3df
Remove dev.sh - We have a make target for this :)
krisnova Nov 9, 2016
07eb92f
gofmt on pkg/apis/kops/cluster.go
krisnova Nov 9, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions cmd/kops/create_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ type CreateClusterOptions struct {
AssociatePublicIP bool

// Channel is the location of the api.Channel to use for our defaults
Channel string
Channel string

// The network topology to use
Topology string
}

func NewCmdCreateCluster(f *util.Factory, out io.Writer) *cobra.Command {
Expand Down Expand Up @@ -111,6 +114,9 @@ func NewCmdCreateCluster(f *util.Factory, out io.Writer) *cobra.Command {

cmd.Flags().StringVar(&options.Channel, "channel", api.DefaultChannel, "Channel for default versions and configuration to use")

// Network topology
cmd.Flags().StringVarP(&options.Topology, "topology", "t", "public", "Controls network topology for the cluster. public|private. Default is 'public'.")

return cmd
}

Expand Down Expand Up @@ -359,6 +365,23 @@ func RunCreateCluster(f *util.Factory, cmd *cobra.Command, args []string, out io
}
}


// Network Topology
switch c.Topology{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do we add notes on what this actually means? n00b dev saying cool we have Topo but what the hell is Topo?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are notes in the struct definition, in the docs, in the cobra commands, etc :)

case api.TopologyPublic:
cluster.Spec.Topology = &api.TopologySpec{Masters: api.TopologyPublic, Nodes: api.TopologyPublic, BypassBastion: false}
case api.TopologyPrivate:
if c.Networking != "cni" {
return fmt.Errorf("Invalid networking option %s. Currently only '--networking cni' is supported for private topologies", c.Networking)
}
cluster.Spec.Topology = &api.TopologySpec{Masters: api.TopologyPrivate, Nodes: api.TopologyPrivate, BypassBastion: false}
case "":
glog.Warningf("Empty topology. Defaulting to public topology.")
cluster.Spec.Topology = &api.TopologySpec{Masters: api.TopologyPublic, Nodes: api.TopologyPublic, BypassBastion: false}
default:
return fmt.Errorf("Invalid topology %s.", c.Topology)
}

sshPublicKeys := make(map[string][]byte)
if c.SSHPublicKey != "" {
c.SSHPublicKey = utils.ExpandPath(c.SSHPublicKey)
Expand Down Expand Up @@ -505,4 +528,4 @@ func parseZoneList(s string) []string {
filtered = append(filtered, v)
}
return filtered
}
}
7 changes: 6 additions & 1 deletion cmd/kops/update_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"k8s.io/kops/upup/pkg/kutil"
"os"
"strings"
"k8s.io/kops/pkg/apis/kops"
)

type UpdateClusterOptions struct {
Expand Down Expand Up @@ -188,7 +189,11 @@ func RunUpdateCluster(f *util.Factory, cmd *cobra.Command, args []string, out io
fmt.Printf("\n")
fmt.Printf("Suggestions:\n")
fmt.Printf(" * list nodes: kubectl get nodes --show-labels\n")
fmt.Printf(" * ssh to the master: ssh -i ~/.ssh/id_rsa admin@%s\n", cluster.Spec.MasterPublicName)
if cluster.Spec.Topology.Masters == kops.TopologyPublic {
fmt.Printf(" * ssh to the master: ssh -i ~/.ssh/id_rsa admin@%s\n", cluster.Spec.MasterPublicName)
}else {
fmt.Printf(" * ssh to the bastion: ssh -i ~/.ssh/id_rsa admin@%s\n", cluster.Spec.MasterPublicName)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be dryer to just use a var here:
`
someZestyVarName := "bastion"

if cluster....... {
someZestyVarName = "master"
}

fmt.Printf(" * ssh to the %s: ssh -i ~/.ssh/id_rsa admin@%s\n", someZestyVarName, cluster.Spec.MasterPublicName)
`

Copy link
Contributor Author

@krisnova krisnova Nov 8, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that would work - but for the sake of keeping the two error messages decoupled I think it's fine to define the repeating text.

The Bastion has some work that will be coming in a future PR #836 that will more than likely effect this string anyway.

Thanks for the review!

fmt.Printf(" * read about installing addons: https://github.com/kubernetes/kops/blob/master/docs/addons.md\n")
fmt.Printf("\n")
}
Expand Down
45 changes: 45 additions & 0 deletions docs/topology.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Network Topologies in Kops

Kops supports a number of pre defined network topologies. They are separated into commonly used scenarios, or topologies.

Each of the supported topologies are listed below, with an example on how to deploy them.

# AWS

Kops supports the following topologies on AWS

| Topology | Value | Description |
| ----------------- |----------- | ----------------------------------------------------------------------------------------------------------- |
| Public Cluster | public | All masters/nodes will be launched in a **public subnet** in the VPC |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a column listing GA / Alpha release.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When this is merged it will go into both - we aren't calling out differences (yet) do we really need this in this PR? Maybe another PR for documenting the APIs?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no we need to release this into master as alpha supported. Not api based, but this is alpha, be warned. Lets talk about this on zoom.

| Private Cluster | private | All masters/nodes will be launched in a **private subnet** in the VPC |


[More information](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Subnets.html) on Public and Private subnets in AWS

Notes on subnets

##### Public Subnet
If a subnet's traffic is routed to an Internet gateway, the subnet is known as a public subnet.

##### Private Subnet
If a subnet doesn't have a route to the Internet gateway, the subnet is known as a private subnet.

Private topologies *will* have public access via the Kubernetes API and an (optional) SSH bastion instance.

# Defining a topology on create

To specify a topology use the `--topology` or `-t` flag as in :

```
kops create cluster ... --topology public|private
```

# Troubleshooting

- Right now we require `-networking cni` for all private topologies.
- Right now a manual install of weave is required for private topologies.
- Right now upgrading from a public cluster to a private cluster is considered very **experimental**

```
kubectl create -f https://git.io/weave-kube
```
79 changes: 66 additions & 13 deletions pkg/apis/kops/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (

type Cluster struct {
unversioned.TypeMeta `json:",inline"`
ObjectMeta `json:"metadata,omitempty"`
ObjectMeta `json:"metadata,omitempty"`

Spec ClusterSpec `json:"spec,omitempty"`
}
Expand Down Expand Up @@ -77,6 +77,11 @@ type ClusterSpec struct {
// NetworkID is an identifier of a network, if we want to reuse/share an existing network (e.g. an AWS VPC)
NetworkID string `json:"networkID,omitempty"`

// Topology defines the type of network topology to use on the cluster - default public
// This is heavily weighted towards AWS for the time being, but should also be agnostic enough
// to port out to GCE later if needed
Topology *TopologySpec `json:"topology,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think - just as in mainline k8s, you have to copy-paste API changes into v1alpha1 now also.

It's fine to add fields (as long as the default is consistent with the existing behaviour), but removing them or changing their meaning requires a new API version and we need to create custom conversion code, so let's avoid that for now if we can :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done and done - see the latest commit


// SecretStore is the VFS path to where secrets are stored
SecretStore string `json:"secretStore,omitempty"`
// KeyStore is the VFS path to where SSL keys and certificates are stored
Expand Down Expand Up @@ -275,7 +280,14 @@ type EtcdMemberSpec struct {

type ClusterZoneSpec struct {
Name string `json:"name,omitempty"`
CIDR string `json:"cidr,omitempty"`

// For Private network topologies we need to have 2
// CIDR blocks.
// 1 - Utility (Public) Subnets
// 2 - Operating (Private) Subnets

PrivateCIDR string `json:"privateCIDR,omitempty"`
CIDR string `json:"cidr,omitempty"`

// ProviderID is the cloud provider id for the objects associated with the zone (the subnet on AWS)
ProviderID string `json:"id,omitempty"`
Expand Down Expand Up @@ -341,10 +353,10 @@ func (c *Cluster) FillDefaults() error {
// OK
} else if c.Spec.Networking.Kubenet != nil {
// OK
} else if c.Spec.Networking.External != nil {
// OK
} else if c.Spec.Networking.CNI != nil {
// OK
} else if c.Spec.Networking.External != nil {
// OK
} else {
// No networking model selected; choose Kubenet
c.Spec.Networking.Kubenet = &KubenetNetworkingSpec{}
Expand Down Expand Up @@ -414,21 +426,27 @@ func FindLatestKubernetesVersion() (string, error) {

func (z *ClusterZoneSpec) performAssignments(c *Cluster) error {
if z.CIDR == "" {
cidr, err := z.assignCIDR(c)
err := z.assignCIDR(c)
if err != nil {
return err
}
glog.Infof("Assigned CIDR %s to zone %s", cidr, z.Name)
z.CIDR = cidr
}

return nil
}

func (z *ClusterZoneSpec) assignCIDR(c *Cluster) (string, error) {
// Will generate a CIDR block based on the last character in
// the cluster.Spec.Zones structure.
//
func (z *ClusterZoneSpec) assignCIDR(c *Cluster) error {
// TODO: We probably could query for the existing subnets & allocate appropriately
// for now we'll require users to set CIDRs themselves

// Used in calculating private subnet blocks (if needed only)
needsPrivateBlock := false
if c.Spec.Topology.Masters == TopologyPrivate || c.Spec.Topology.Nodes == TopologyPrivate {
needsPrivateBlock = true
}

lastCharMap := make(map[byte]bool)
for _, nodeZone := range c.Spec.Zones {
lastChar := nodeZone.Name[len(nodeZone.Name)-1]
Expand All @@ -452,13 +470,13 @@ func (z *ClusterZoneSpec) assignCIDR(c *Cluster) (string, error) {
}
}
if index == -1 {
return "", fmt.Errorf("zone not configured: %q", z.Name)
return fmt.Errorf("zone not configured: %q", z.Name)
}
}

_, cidr, err := net.ParseCIDR(c.Spec.NetworkCIDR)
if err != nil {
return "", fmt.Errorf("Invalid NetworkCIDR: %q", c.Spec.NetworkCIDR)
return fmt.Errorf("Invalid NetworkCIDR: %q", c.Spec.NetworkCIDR)
}
networkLength, _ := cidr.Mask.Size()

Expand All @@ -475,14 +493,49 @@ func (z *ClusterZoneSpec) assignCIDR(c *Cluster) (string, error) {
subnetIP := make(net.IP, len(ip4))
binary.BigEndian.PutUint32(subnetIP, n)
subnetCIDR := subnetIP.String() + "/" + strconv.Itoa(networkLength)
z.CIDR = subnetCIDR
glog.V(2).Infof("Computed CIDR for subnet in zone %q as %q", z.Name, subnetCIDR)
return subnetCIDR, nil
glog.Infof("Assigned CIDR %s to zone %s", subnetCIDR, z.Name)

if needsPrivateBlock {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, joy network math. Umm, how does this work 😀

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you asking how we are calculating the subnet? Or how we know we need to calculate another block?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check out lines 446-449

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this code block work? Just high level. Please document.

m := binary.BigEndian.Uint32(ip4)
// All Private CIDR blocks are at the end of our range
m += uint32(index+len(c.Spec.Zones)) << uint(32-networkLength)
privSubnetIp := make(net.IP, len(ip4))
binary.BigEndian.PutUint32(privSubnetIp, m)
privCIDR := privSubnetIp.String() + "/" + strconv.Itoa(networkLength)
z.PrivateCIDR = privCIDR
glog.V(2).Infof("Computed Private CIDR for subnet in zone %q as %q", z.Name, privCIDR)
glog.Infof("Assigned Private CIDR %s to zone %s", privCIDR, z.Name)
}

return nil
}

return "", fmt.Errorf("Unexpected IP address type for NetworkCIDR: %s", c.Spec.NetworkCIDR)
return fmt.Errorf("Unexpected IP address type for NetworkCIDR: %s", c.Spec.NetworkCIDR)
}

// SharedVPC is a simple helper function which makes the templates for a shared VPC clearer
func (c *Cluster) SharedVPC() bool {
return c.Spec.NetworkID != ""
}

// --------------------------------------------------------------------------------------------
// Network Topology functions for template parsing
//
// Each of these functions can be used in the model templates
// The go template package currently only supports boolean
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can do eq Topology "private" I think.

In general, it might be easier to replace the network.yaml with go code. I have a prototype of it somewhere that I'll paste a link to if I find it. But the whole reliance on templates just seems pretty fragile and seems to have turned out to be harder for people to change, not easier.

But completely up to you if you want to do this for this PR. Just if you're fighting templates you might find it easier to dump them :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should continue to use the templates and follow suit here - if we want to move away from them later we can.. But I think that is a larger effort.

They work for now, so lets just keep it consistent

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// operations, so the logic is mapped here as *Cluster functions.
//
// A function will need to be defined for all new topologies, if we plan to use them in the
// model templates.
// --------------------------------------------------------------------------------------------
func (c *Cluster) IsTopologyPrivate() bool {
return (c.Spec.Topology.Masters == TopologyPrivate && c.Spec.Topology.Nodes == TopologyPrivate)
}
func (c *Cluster) IsTopologyPublic() bool {
return (c.Spec.Topology.Masters == TopologyPublic && c.Spec.Topology.Nodes == TopologyPublic)
}
func (c *Cluster) IsTopologyPrivateMasters() bool {
return (c.Spec.Topology.Masters == TopologyPrivate && c.Spec.Topology.Nodes == TopologyPublic)
}
35 changes: 35 additions & 0 deletions pkg/apis/kops/topology.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
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.
*/

package kops

const (
TopologyPublic = "public"
TopologyPrivate = "private"
)

type TopologySpec struct {
// The environment to launch the Kubernetes masters in public|private
Masters string `json:"masters,omitempty"`

// The environment to launch the Kubernetes nodes in public|private
Nodes string `json:"nodes,omitempty"`

// Controls if a private topology should deploy a bastion host or not
// The bastion host is designed to be a simple, and secure bridge between
// the public subnet and the private subnet
BypassBastion bool `json:"bypassBastion,omitempty"`
}
5 changes: 5 additions & 0 deletions pkg/apis/kops/v1alpha1/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ type ClusterSpec struct {
// NetworkID is an identifier of a network, if we want to reuse/share an existing network (e.g. an AWS VPC)
NetworkID string `json:"networkID,omitempty"`

// Topology defines the type of network topology to use on the cluster - default public
// This is heavily weighted towards AWS for the time being, but should also be agnostic enough
// to port out to GCE later if needed
Topology *TopologySpec `json:"topology,omitempty"`

// SecretStore is the VFS path to where secrets are stored
SecretStore string `json:"secretStore,omitempty"`
// KeyStore is the VFS path to where SSL keys and certificates are stored
Expand Down
35 changes: 35 additions & 0 deletions pkg/apis/kops/v1alpha1/topology.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
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.
*/

package v1alpha1

const (
TopologyPublic = "public"
TopologyPrivate = "private"
)

type TopologySpec struct {
// The environment to launch the Kubernetes masters in public|private
Masters string `json:"masters,omitempty"`

// The environment to launch the Kubernetes nodes in public|private
Nodes string `json:"nodes,omitempty"`

// Controls if a private topology should deploy a bastion host or not
// The bastion host is designed to be a simple, and secure bridge between
// the public subnet and the private subnet
BypassBastion bool `json:"bypassBastion,omitempty"`
}
15 changes: 15 additions & 0 deletions pkg/apis/kops/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,21 @@ func (c *Cluster) Validate(strict bool) error {
}
}

// Topology support
if c.Spec.Topology.Masters != "" && c.Spec.Topology.Nodes != "" {
if c.Spec.Topology.Masters != TopologyPublic && c.Spec.Topology.Masters != TopologyPrivate {
return fmt.Errorf("Invalid Masters value for Topology")
} else if c.Spec.Topology.Nodes != TopologyPublic && c.Spec.Topology.Nodes != TopologyPrivate {
return fmt.Errorf("Invalid Nodes value for Topology")
// Until we support other topologies - these must match
} else if c.Spec.Topology.Masters != c.Spec.Topology.Nodes {
return fmt.Errorf("Topology Nodes must match Topology Masters")
}

}else{
return fmt.Errorf("Topology requires non-nil values for Masters and Nodes")
}

// Etcd
{
if len(c.Spec.EtcdClusters) == 0 {
Expand Down
Loading