mirror of
https://github.com/kubernetes-sigs/kind.git
synced 2025-11-30 23:16:04 +07:00
Merge pull request #233 from fabriziopandini/multi-master
support n>1 control plane nodes and the external load balancer
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
kind: Config
|
||||
apiVersion: kind.sigs.k8s.io/v1alpha2
|
||||
nodes:
|
||||
- role: control-plane
|
||||
replicas: 3
|
||||
- role: worker
|
||||
replicas: 2
|
||||
- role: control-plane
|
||||
replicas: 3
|
||||
- role: worker
|
||||
replicas: 2
|
||||
- role: external-etcd
|
||||
- role: external-load-balancer
|
||||
- role: external-load-balancer
|
||||
@@ -158,7 +158,7 @@ func (c *Context) Create(cfg *config.Config, retain bool, wait time.Duration) er
|
||||
// Kubernetes cluster; please note that the list of actions automatically
|
||||
// adapt to the topology defined in config
|
||||
// TODO(fabrizio pandini): make the list of executed actions configurable from CLI
|
||||
err = cc.Exec(nodeList, []string{"config", "init", "join"}, wait)
|
||||
err = cc.Exec(nodeList, []string{"haproxy", "config", "init", "join"}, wait)
|
||||
if err != nil {
|
||||
// In case of errors nodes are deleted (except if retain is explicitly set)
|
||||
log.Error(err)
|
||||
|
||||
@@ -124,6 +124,8 @@ func (cc *Context) ProvisionNodes() (nodeList map[string]*nodes.Node, err error)
|
||||
var node *nodes.Node
|
||||
|
||||
switch configNode.Role {
|
||||
case config.ExternalLoadBalancerRole:
|
||||
node, err = nodes.CreateExternalLoadBalancerNode(name, configNode.Image, cc.ClusterLabel())
|
||||
case config.ControlPlaneRole:
|
||||
node, err = nodes.CreateControlPlaneNode(name, configNode.Image, cc.ClusterLabel())
|
||||
case config.WorkerRole:
|
||||
|
||||
@@ -76,13 +76,7 @@ func (d *DerivedConfig) Validate() error {
|
||||
// TODO(fabrizio pandini): this check is temporary / WIP
|
||||
// kind v1alpha config fully supports multi nodes, but the cluster creation logic implemented in
|
||||
// pkg/cluster/contex.go does it only partially (yet).
|
||||
// As soon a external load-balancer and external etcd is implemented in pkg/cluster, this should go away
|
||||
if d.ExternalLoadBalancer() != nil {
|
||||
errs = append(errs, fmt.Errorf("multi node support is still a work in progress, currently external load balancer node is not supported"))
|
||||
}
|
||||
if d.SecondaryControlPlanes() != nil {
|
||||
errs = append(errs, fmt.Errorf("multi node support is still a work in progress, currently only single control-plane node are supported"))
|
||||
}
|
||||
// As soon as external etcd is implemented in pkg/cluster, this should go away
|
||||
if d.ExternalEtcd() != nil {
|
||||
errs = append(errs, fmt.Errorf("multi node support is still a work in progress, currently external etcd node is not supported"))
|
||||
}
|
||||
|
||||
133
pkg/cluster/internal/create/haproxy.go
Normal file
133
pkg/cluster/internal/create/haproxy.go
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
Copyright 2019 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 create
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"sigs.k8s.io/kind/pkg/cluster/internal/haproxy"
|
||||
"sigs.k8s.io/kind/pkg/cluster/internal/kubeadm"
|
||||
)
|
||||
|
||||
// HAProxyAction implements action for configuring and starting the
|
||||
// external load balancer in front of the control-plane nodes.
|
||||
type HAProxyAction struct{}
|
||||
|
||||
func init() {
|
||||
registerAction("haproxy", NewHAProxyAction)
|
||||
}
|
||||
|
||||
// NewHAProxyAction returns a new HAProxyAction
|
||||
func NewHAProxyAction() Action {
|
||||
return &HAProxyAction{}
|
||||
}
|
||||
|
||||
// Tasks returns the list of action tasks
|
||||
func (b *HAProxyAction) Tasks() []Task {
|
||||
return []Task{
|
||||
{
|
||||
Description: "Starting the external load balancer ⛵",
|
||||
TargetNodes: selectExternalLoadBalancerNode,
|
||||
Run: runHAProxy,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// runKubeadmJoin executes haproxy
|
||||
func runHAProxy(ec *execContext, configNode *NodeReplica) error {
|
||||
// collects info about the existing controlplane nodes
|
||||
var backendServers = map[string]string{}
|
||||
for _, n := range ec.ControlPlanes() {
|
||||
// gets the handle for the control plane node
|
||||
controlPlaneHandle, ok := ec.NodeFor(n)
|
||||
if !ok {
|
||||
return errors.Errorf("unable to get the handle for operating on node: %s", n.Name)
|
||||
}
|
||||
|
||||
// gets the IP of the control plane node
|
||||
controlPlaneIP, err := controlPlaneHandle.IP()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get IP for node %s", n.Name)
|
||||
}
|
||||
|
||||
backendServers[n.Name] = fmt.Sprintf("%s:%d", controlPlaneIP, kubeadm.APIServerPort)
|
||||
}
|
||||
|
||||
// create haproxy config file writing a local temp file on the host machine
|
||||
haproxyConfig, err := createHAProxyConfig(
|
||||
&haproxy.ConfigData{
|
||||
ControlPlanePort: haproxy.ControlPlanePort,
|
||||
BackendServers: backendServers,
|
||||
},
|
||||
)
|
||||
defer os.Remove(haproxyConfig)
|
||||
|
||||
if err != nil {
|
||||
// TODO(bentheelder): logging here
|
||||
return errors.Wrap(err, "failed to create kubeadm config")
|
||||
}
|
||||
|
||||
// get the target node for this task (the load balancer node)
|
||||
node, ok := ec.NodeFor(configNode)
|
||||
if !ok {
|
||||
return errors.Errorf("unable to get the handle for operating on node: %s", configNode.Name)
|
||||
}
|
||||
|
||||
// copy the haproxy config file from the host to the node
|
||||
if err := node.CopyTo(haproxyConfig, "/kind/haproxy.cfg"); err != nil {
|
||||
// TODO(bentheelder): logging here
|
||||
return errors.Wrap(err, "failed to copy haproxy config to node")
|
||||
}
|
||||
|
||||
// starts a docker container with HA proxy load balancer
|
||||
if err := node.Command(
|
||||
"/bin/sh", "-c",
|
||||
fmt.Sprintf("docker run -d -v /kind/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro --network host --restart always %s", haproxy.Image),
|
||||
).Run(); err != nil {
|
||||
return errors.Wrap(err, "failed to start haproxy")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createHAProxyConfig(data *haproxy.ConfigData) (path string, err error) {
|
||||
// create haproxy config file
|
||||
f, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to create haproxy config")
|
||||
}
|
||||
path = f.Name()
|
||||
// generate the config contents
|
||||
config, err := haproxy.Config(data)
|
||||
if err != nil {
|
||||
os.Remove(path)
|
||||
return "", err
|
||||
}
|
||||
// write to the file
|
||||
log.Infof("Using haproxy:\n\n%s\n", config)
|
||||
_, err = f.WriteString(config)
|
||||
if err != nil {
|
||||
os.Remove(path)
|
||||
return "", err
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"sigs.k8s.io/kind/pkg/cluster/config"
|
||||
"sigs.k8s.io/kind/pkg/cluster/internal/haproxy"
|
||||
"sigs.k8s.io/kind/pkg/cluster/internal/kubeadm"
|
||||
"sigs.k8s.io/kind/pkg/kustomize"
|
||||
)
|
||||
@@ -70,27 +71,32 @@ func runKubeadmConfig(ec *execContext, configNode *NodeReplica) error {
|
||||
return errors.Wrap(err, "failed to get kubernetes version from node: %v")
|
||||
}
|
||||
|
||||
// get the control plane endpoint, in case the cluster has an external load balancer in
|
||||
// front of the control-plane nodes
|
||||
controlPlaneEndpoint, err := getControlPlaneEndpoint(ec)
|
||||
if err != nil {
|
||||
// TODO(bentheelder): logging here
|
||||
return err
|
||||
}
|
||||
|
||||
// create kubeadm config file writing a local temp file
|
||||
kubeadmConfig, err := createKubeadmConfig(
|
||||
ec.Config,
|
||||
ec.DerivedConfig,
|
||||
kubeadm.ConfigData{
|
||||
ClusterName: ec.Name(),
|
||||
KubernetesVersion: kubeVersion,
|
||||
APIBindPort: kubeadm.APIServerPort,
|
||||
Token: kubeadm.Token,
|
||||
// TODO(fabriziopandini): when external load-balancer will be
|
||||
// implemented also controlPlaneAddress should be added
|
||||
ClusterName: ec.Name(),
|
||||
KubernetesVersion: kubeVersion,
|
||||
ControlPlaneEndpoint: controlPlaneEndpoint,
|
||||
APIBindPort: kubeadm.APIServerPort,
|
||||
Token: kubeadm.Token,
|
||||
},
|
||||
)
|
||||
defer os.Remove(kubeadmConfig)
|
||||
if err != nil {
|
||||
// TODO(bentheelder): logging here
|
||||
return fmt.Errorf("failed to create kubeadm config: %v", err)
|
||||
}
|
||||
|
||||
// defer deletion of the local temp file
|
||||
defer os.Remove(kubeadmConfig)
|
||||
|
||||
// copy the config to the node
|
||||
if err := node.CopyTo(kubeadmConfig, "/kind/kubeadm.conf"); err != nil {
|
||||
// TODO(bentheelder): logging here
|
||||
@@ -100,6 +106,28 @@ func runKubeadmConfig(ec *execContext, configNode *NodeReplica) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// getControlPlaneEndpoint return the control plane endpoint in case the cluster has an external load balancer in
|
||||
// front of the control-plane nodes, otherwise return an empty string.
|
||||
func getControlPlaneEndpoint(ec *execContext) (string, error) {
|
||||
if ec.ExternalLoadBalancer() == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// gets the handle for the load balancer node
|
||||
loadBalancerHandle, ok := ec.NodeFor(ec.ExternalLoadBalancer())
|
||||
if !ok {
|
||||
return "", errors.Errorf("unable to get the handle for operating on node: %s", ec.ExternalLoadBalancer().Name)
|
||||
}
|
||||
|
||||
// gets the IP of the load balancer
|
||||
loadBalancerIP, err := loadBalancerHandle.IP()
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to get IP for node: %s", ec.ExternalLoadBalancer().Name)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s:%d", loadBalancerIP, haproxy.ControlPlanePort), nil
|
||||
}
|
||||
|
||||
// createKubeadmConfig creates the kubeadm config file for the cluster
|
||||
// by running data through the template and writing it to a temp file
|
||||
// the config file path is returned, this file should be removed later
|
||||
|
||||
@@ -18,10 +18,13 @@ package create
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"sigs.k8s.io/kind/pkg/cluster/internal/kubeadm"
|
||||
"sigs.k8s.io/kind/pkg/fs"
|
||||
)
|
||||
|
||||
// kubeadmJoinAction implements action for joining nodes
|
||||
@@ -40,10 +43,14 @@ func newKubeadmJoinAction() Action {
|
||||
// Tasks returns the list of action tasks
|
||||
func (b *kubeadmJoinAction) Tasks() []Task {
|
||||
return []Task{
|
||||
// TODO(fabrizio pandini): add Run kubeadm join --experimental-master
|
||||
// on SecondaryControlPlaneNodes
|
||||
{
|
||||
// Run kubeadm join on the WorkeNodes
|
||||
// Run kubeadm join on the secondary control plane Nodes
|
||||
Description: "Joining control-plane node to Kubernetes ☸",
|
||||
TargetNodes: selectSecondaryControlPlaneNodes,
|
||||
Run: runKubeadmJoinControlPlane,
|
||||
},
|
||||
{
|
||||
// Run kubeadm join on the Worker Nodes
|
||||
Description: "Joining worker node to Kubernetes ☸",
|
||||
TargetNodes: selectWorkerNodes,
|
||||
Run: runKubeadmJoin,
|
||||
@@ -51,38 +58,115 @@ func (b *kubeadmJoinAction) Tasks() []Task {
|
||||
}
|
||||
}
|
||||
|
||||
// runKubeadmJoin executes kubadm join
|
||||
func runKubeadmJoin(ec *execContext, configNode *NodeReplica) error {
|
||||
// before running join, it should be retrived
|
||||
// runKubeadmJoinControlPlane executes kubadm join --control-plane command
|
||||
func runKubeadmJoinControlPlane(ec *execContext, configNode *NodeReplica) error {
|
||||
|
||||
// gets the node where
|
||||
// TODO(fabrizio pandini): when external load-balancer will be
|
||||
// implemented this should be modified accordingly
|
||||
controlPlaneHandle, ok := ec.NodeFor(ec.DerivedConfig.BootStrapControlPlane())
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to get the handle for operating on node: %s", ec.DerivedConfig.BootStrapControlPlane().Name)
|
||||
}
|
||||
|
||||
// gets the IP of the bootstrap master node
|
||||
controlPlaneIP, err := controlPlaneHandle.IP()
|
||||
// get the join addres
|
||||
joinAddress, err := getJoinAddress(ec)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get IP for node")
|
||||
// TODO(bentheelder): logging here
|
||||
return err
|
||||
}
|
||||
|
||||
// get the target node for this task
|
||||
// get the target node for this task (the joining node)
|
||||
node, ok := ec.NodeFor(configNode)
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to get the handle for operating on node: %s", configNode.Name)
|
||||
}
|
||||
|
||||
// TODO(fabrizio pandini): might be we want to run pre-kubeadm hooks on workers too
|
||||
// creates the folder tree for pre-loading necessary cluster certificates
|
||||
// on the joining node
|
||||
if err := node.Command("mkdir", "-p", "/etc/kubernetes/pki/etcd").Run(); err != nil {
|
||||
return errors.Wrap(err, "failed to join node with kubeadm")
|
||||
}
|
||||
|
||||
// run kubeadm
|
||||
// define the list of necessary cluster certificates
|
||||
fileNames := []string{
|
||||
"ca.crt", "ca.key",
|
||||
"front-proxy-ca.crt", "front-proxy-ca.key",
|
||||
"sa.pub", "sa.key",
|
||||
}
|
||||
if ec.ExternalEtcd() == nil {
|
||||
fileNames = append(fileNames, "etcd/ca.crt", "etcd/ca.key")
|
||||
}
|
||||
|
||||
// creates a temporary folder on the host that should acts as a transit area
|
||||
// for moving necessary cluster certificates
|
||||
tmpDir, err := fs.TempDir("", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
err = os.MkdirAll(filepath.Join(tmpDir, "/etcd"), os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get the handle for the bootstrap control plane node (the source for necessary cluster certificates)
|
||||
controlPlaneHandle, ok := ec.NodeFor(ec.BootStrapControlPlane())
|
||||
if !ok {
|
||||
return errors.Errorf("unable to get the handle for operating on node: %s", ec.BootStrapControlPlane().Name)
|
||||
}
|
||||
|
||||
// copies certificates from the bootstrap control plane node to the joining node
|
||||
for _, fileName := range fileNames {
|
||||
// sets the path of the certificate into a node
|
||||
containerPath := filepath.Join("/etc/kubernetes/pki", fileName)
|
||||
// set the path of the certificate into the tmp area on the host
|
||||
tmpPath := filepath.Join(tmpDir, fileName)
|
||||
// copies from bootstrap control plane node to tmp area
|
||||
if err := controlPlaneHandle.CopyFrom(containerPath, tmpPath); err != nil {
|
||||
return errors.Wrapf(err, "failed to copy certificate %s", fileName)
|
||||
}
|
||||
// copies from tmp area to joining node
|
||||
if err := node.CopyTo(tmpPath, containerPath); err != nil {
|
||||
return errors.Wrapf(err, "failed to copy certificate %s", fileName)
|
||||
}
|
||||
}
|
||||
|
||||
// run kubeadm join --control-plane
|
||||
if err := node.Command(
|
||||
"kubeadm", "join",
|
||||
// the control plane address uses the docker ip and a well know APIServerPort that
|
||||
// the join command uses the docker ip and a well know port that
|
||||
// are accessible only inside the docker network
|
||||
fmt.Sprintf("%s:%d", controlPlaneIP, kubeadm.APIServerPort),
|
||||
joinAddress,
|
||||
// set the node to join as control-plane
|
||||
"--experimental-control-plane",
|
||||
// uses a well known token and skips ca certification for automating TLS bootstrap process
|
||||
"--token", kubeadm.Token,
|
||||
"--discovery-token-unsafe-skip-ca-verification",
|
||||
// preflight errors are expected, in particular for swap being enabled
|
||||
// TODO(bentheelder): limit the set of acceptable errors
|
||||
"--ignore-preflight-errors=all",
|
||||
).Run(); err != nil {
|
||||
return errors.Wrap(err, "failed to join a control plane node with kubeadm")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// runKubeadmJoin executes kubadm join command
|
||||
func runKubeadmJoin(ec *execContext, configNode *NodeReplica) error {
|
||||
// get the join addres
|
||||
joinAddress, err := getJoinAddress(ec)
|
||||
if err != nil {
|
||||
// TODO(bentheelder): logging here
|
||||
return err
|
||||
}
|
||||
|
||||
// get the target node for this task (the joining node)
|
||||
node, ok := ec.NodeFor(configNode)
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to get the handle for operating on node: %s", configNode.Name)
|
||||
}
|
||||
|
||||
// run kubeadm join
|
||||
if err := node.Command(
|
||||
"kubeadm", "join",
|
||||
// the join command uses the docker ip and a well know port that
|
||||
// are accessible only inside the docker network
|
||||
joinAddress,
|
||||
// uses a well known token and skipping ca certification for automating TLS bootstrap process
|
||||
"--token", kubeadm.Token,
|
||||
"--discovery-token-unsafe-skip-ca-verification",
|
||||
@@ -93,9 +177,37 @@ func runKubeadmJoin(ec *execContext, configNode *NodeReplica) error {
|
||||
return errors.Wrap(err, "failed to join node with kubeadm")
|
||||
}
|
||||
|
||||
// TODO(fabrizio pandini): might be we want to run post-kubeadm hooks on workers too
|
||||
|
||||
// TODO(fabrizio pandini): might be we want to run post-setup hooks on workers too
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getJoinAddress return the join addres thas is the control plane endpoint in case the cluster has
|
||||
// an external load balancer in front of the control-plane nodes, otherwise the address of the
|
||||
// boostrap control plane node.
|
||||
func getJoinAddress(ec *execContext) (string, error) {
|
||||
// get the control plane endpoint, in case the cluster has an external load balancer in
|
||||
// front of the control-plane nodes
|
||||
controlPlaneEndpoint, err := getControlPlaneEndpoint(ec)
|
||||
if err != nil {
|
||||
// TODO(bentheelder): logging here
|
||||
return "", err
|
||||
}
|
||||
|
||||
// if the control plane endpoint is defined we are using it as a join address
|
||||
if controlPlaneEndpoint != "" {
|
||||
return controlPlaneEndpoint, nil
|
||||
}
|
||||
|
||||
// otherwise, gets the BootStrapControlPlane node
|
||||
controlPlaneHandle, ok := ec.NodeFor(ec.BootStrapControlPlane())
|
||||
if !ok {
|
||||
return "", errors.Errorf("unable to get the handle for operating on node: %s", ec.BootStrapControlPlane().Name)
|
||||
}
|
||||
|
||||
// gets the IP of the bootstrap control plane node
|
||||
controlPlaneIP, err := controlPlaneHandle.IP()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to get IP for node")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s:%d", controlPlaneIP, kubeadm.APIServerPort), nil
|
||||
}
|
||||
|
||||
85
pkg/cluster/internal/haproxy/config.go
Normal file
85
pkg/cluster/internal/haproxy/config.go
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
Copyright 2019 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 haproxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"text/template"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ConfigData is supplied to the haproxy config template
|
||||
type ConfigData struct {
|
||||
ControlPlanePort int
|
||||
BackendServers map[string]string
|
||||
}
|
||||
|
||||
// DefaultConfigTemplate is the haproxy config template
|
||||
const DefaultConfigTemplate = `
|
||||
global
|
||||
log 127.0.0.1 local2
|
||||
daemon
|
||||
|
||||
defaults
|
||||
mode http
|
||||
log global
|
||||
option httplog
|
||||
option dontlognull
|
||||
option http-server-close
|
||||
option forwardfor except 127.0.0.0/8
|
||||
option redispatch
|
||||
retries 3
|
||||
timeout http-request 10s
|
||||
timeout queue 1m
|
||||
timeout connect 10s
|
||||
timeout client 1m
|
||||
timeout server 1m
|
||||
timeout http-keep-alive 10s
|
||||
timeout check 10s
|
||||
maxconn 3000
|
||||
|
||||
frontend controlPlane
|
||||
bind *:{{ .ControlPlanePort }}
|
||||
option tcplog
|
||||
mode tcp
|
||||
default_backend kube-apiservers
|
||||
|
||||
backend kube-apiservers
|
||||
mode tcp
|
||||
balance roundrobin
|
||||
option ssl-hello-chk
|
||||
{{range $server, $address := .BackendServers}}
|
||||
server {{ $server }} {{ $address }} check
|
||||
{{- end}}
|
||||
`
|
||||
|
||||
// Config returns a kubeadm config generated from config data, in particular
|
||||
// the kubernetes version
|
||||
func Config(data *ConfigData) (config string, err error) {
|
||||
t, err := template.New("haproxy-config").Parse(DefaultConfigTemplate)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to parse config template")
|
||||
}
|
||||
// execute the template
|
||||
var buff bytes.Buffer
|
||||
err = t.Execute(&buff, data)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "error executing config template")
|
||||
}
|
||||
return buff.String(), nil
|
||||
}
|
||||
23
pkg/cluster/internal/haproxy/const.go
Normal file
23
pkg/cluster/internal/haproxy/const.go
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
Copyright 2019 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 haproxy
|
||||
|
||||
// ControlPlanePort defines the port where the control plane is listening on the load balancer node
|
||||
const ControlPlanePort = 6443
|
||||
|
||||
// Image defines the haproxy image:tag
|
||||
const Image = "haproxy:1.8.14-alpine"
|
||||
18
pkg/cluster/internal/haproxy/doc.go
Normal file
18
pkg/cluster/internal/haproxy/doc.go
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
Copyright 2019 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 haproxy contains haproxy related constants and configuration
|
||||
package haproxy
|
||||
@@ -30,7 +30,9 @@ import (
|
||||
type ConfigData struct {
|
||||
ClusterName string
|
||||
KubernetesVersion string
|
||||
// The API Server port
|
||||
// The ControlPlaneEndpoint, that is the address of the external loadbalancer, if defined
|
||||
ControlPlaneEndpoint string
|
||||
// The Local API Server port
|
||||
APIBindPort int
|
||||
// The Token for TLS bootstrap
|
||||
Token string
|
||||
@@ -69,6 +71,9 @@ clusterName: "{{.ClusterName}}"
|
||||
# we use a well know token for TLS bootstrap
|
||||
bootstrapTokens:
|
||||
- token: "{{ .Token }}"
|
||||
{{ if .ControlPlaneEndpoint -}}
|
||||
controlPlaneEndpoint: {{ .ControlPlaneEndpoint }}
|
||||
{{- end }}
|
||||
# we use a well know port for making the API server discoverable inside docker network.
|
||||
# from the host machine such port will be accessible via a random local port instead.
|
||||
api:
|
||||
@@ -93,6 +98,9 @@ apiVersion: kubeadm.k8s.io/v1alpha3
|
||||
kind: ClusterConfiguration
|
||||
kubernetesVersion: {{.KubernetesVersion}}
|
||||
clusterName: "{{.ClusterName}}"
|
||||
{{ if .ControlPlaneEndpoint -}}
|
||||
controlPlaneEndpoint: {{ .ControlPlaneEndpoint }}
|
||||
{{- end }}
|
||||
# we need nsswitch.conf so we use /etc/hosts
|
||||
# https://github.com/kubernetes/kubernetes/issues/69195
|
||||
apiServerExtraVolumes:
|
||||
@@ -135,6 +143,9 @@ apiVersion: kubeadm.k8s.io/v1beta1
|
||||
kind: ClusterConfiguration
|
||||
kubernetesVersion: {{.KubernetesVersion}}
|
||||
clusterName: "{{.ClusterName}}"
|
||||
{{ if .ControlPlaneEndpoint -}}
|
||||
controlPlaneEndpoint: {{ .ControlPlaneEndpoint }}
|
||||
{{- end }}
|
||||
# on docker for mac we have to expose the api server via port forward,
|
||||
# so we need to ensure the cert is valid for localhost so we can talk
|
||||
# to the cluster after rewriting the kubeconfig to point to localhost
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kind/pkg/cluster/internal/haproxy"
|
||||
"sigs.k8s.io/kind/pkg/cluster/internal/kubeadm"
|
||||
"sigs.k8s.io/kind/pkg/docker"
|
||||
)
|
||||
@@ -67,6 +68,30 @@ func CreateControlPlaneNode(name, image, clusterLabel string) (node *Node, err e
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// CreateExternalLoadBalancerNode creates an external loab balancer node
|
||||
// and gets ready for exposing the the API server and the load balancer admin console
|
||||
func CreateExternalLoadBalancerNode(name, image, clusterLabel string) (node *Node, err error) {
|
||||
// gets a random host port for control-plane load balancer
|
||||
port, err := getPort()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get port for control-plane load balancer")
|
||||
}
|
||||
|
||||
node, err = createNode(name, image, clusterLabel,
|
||||
// publish selected port for the control plane
|
||||
"--expose", fmt.Sprintf("%d", port),
|
||||
"-p", fmt.Sprintf("%d:%d", port, haproxy.ControlPlanePort),
|
||||
)
|
||||
if err != nil {
|
||||
return node, err
|
||||
}
|
||||
|
||||
// stores the port mapping into the node internal state
|
||||
node.ports = map[int]int{haproxy.ControlPlanePort: port}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// CreateWorkerNode creates a worker node
|
||||
func CreateWorkerNode(name, image, clusterLabel string) (node *Node, err error) {
|
||||
node, err = createNode(name, image, clusterLabel)
|
||||
|
||||
@@ -88,6 +88,14 @@ func (n *Node) CopyTo(source, dest string) error {
|
||||
return docker.CopyTo(source, n.nameOrID, dest)
|
||||
}
|
||||
|
||||
// CopyFrom copies the source file on the node to dest on the host
|
||||
// TODO(fabrizio pandini): note that this does have limitations around symlinks
|
||||
// but this should go away when kubeadm automatic copy certs lands,
|
||||
// otherwise it should be refactored in something more robust in the long term
|
||||
func (n *Node) CopyFrom(source, dest string) error {
|
||||
return docker.CopyFrom(n.nameOrID, source, dest)
|
||||
}
|
||||
|
||||
// WaitForDocker waits for Docker to be ready on the node
|
||||
// it returns true on success, and false on a timeout
|
||||
func (n *Node) WaitForDocker(until time.Time) bool {
|
||||
|
||||
Reference in New Issue
Block a user