Skip to content

Commit

Permalink
Modify kubeconfig for multiple clusters (kubernetes-sigs#112)
Browse files Browse the repository at this point in the history
kubeadm does not currently allow to configure a user reference id,
and instead always uses kubernetes-admin.  This causes a problem
when we create multiple clusters with Kind and want to use
each corresponding kubeconfig file at the same time in KUBECONFIG.

Until kubeadm supports configuring a user reference id, the only
option to fix this for Kind is to modify the kubeconfig file that
kubeadm provides.  As Kind already did this with the server name,
it made sense to take the logic further and also make the user
reference id unique to a cluster.

Three approaches were considered:

1- continue with the current approach of parsing each line of
   the admin.conf kubeconfig file, and make the modifications
   necessary.  After implementing this approach, the solution
   seemed quite brittle as it uses regex but no yaml structure.

2- use the go package yaml.v2 to -fully- parse the yaml of the
   admin.conf kubeconfig file, make the modifications, and then
   output the new yaml kubeconfig file.  This solution requires
   to define a detailed struct of every field contained in the
   original yaml file. Having to map every field in advance is
   brittle as any modification that kubeadm may make to the file
   in the future would require adaptation in Kind.

3- use the go package yaml.v2 to -generically- parse the yaml of
   the admin.conf kubeconfig file, make the modifications, and then
   output the new yaml kubeconfig file.  This solution only
   accesses the yaml fields that are required to be modified.
   Although any future changes from kubeadm to those fields would
   require modifications in Kind, modifications to all other fields
   would not.

This commit implements solution #3 which was felt to be the most
future-proof and least brittle of the three.

Signed-off-by: Marc Khouzam <[email protected]>
marckhouzam committed May 9, 2019

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 0f75759 commit e03994e
Showing 2 changed files with 43 additions and 4 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@ require (
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.2.1
k8s.io/api v0.0.0-20181026145037-6e4b5aa967ee // indirect
k8s.io/apimachinery v0.0.0-20181126191516-4a9a8137c0a1
k8s.io/client-go v7.0.0+incompatible
46 changes: 42 additions & 4 deletions pkg/cluster/internal/create/actions/kubeadminit/init.go
Original file line number Diff line number Diff line change
@@ -34,7 +34,7 @@ import (
"sigs.k8s.io/kind/pkg/cluster/nodes"
"sigs.k8s.io/kind/pkg/exec"

yaml "gopkg.in/yaml.v2"
"gopkg.in/yaml.v2"
)

// kubeadmInitAction implements action for executing the kubadm init
@@ -94,7 +94,8 @@ func (a *action) Execute(ctx *actions.ActionContext) error {
}

kubeConfigPath := ctx.ClusterContext.KubeConfigPath()
if err := writeKubeConfig(node, kubeConfigPath, hostPort); err != nil {
clusterName := ctx.ClusterContext.Name()
if err := writeKubeConfig(node, kubeConfigPath, hostPort, clusterName); err != nil {
return errors.Wrap(err, "failed to get kubeconfig from node")
}

@@ -122,7 +123,9 @@ type genericYaml map[string]interface{}
// While copying to the host machine the control plane address
// is replaced with local host and the control plane port with
// a randomly generated port reserved during node creation.
func writeKubeConfig(n *nodes.Node, dest string, hostPort int32) error {
// We also make the user reference id unique to allow to use
// multiple kubeconfig files in the KUBECONFIG variable
func writeKubeConfig(n *nodes.Node, dest string, hostPort int32, userSuffix string) error {
cmd := n.Command("cat", "/etc/kubernetes/admin.conf")
buff, err := exec.Output(cmd)
if err != nil {
@@ -134,7 +137,7 @@ func writeKubeConfig(n *nodes.Node, dest string, hostPort int32) error {
return errors.Wrap(err, "failed to parse kubeconfig file")
}

// fix the config file, swapping out the server for the forwarded localhost:port
// Swap out the server for the forwarded localhost:port
cluster := config["clusters"].([]interface{})[0].(map[interface{}]interface{})
for k, v := range cluster {
if k == "cluster" {
@@ -149,6 +152,41 @@ func writeKubeConfig(n *nodes.Node, dest string, hostPort int32) error {
}
}

// Must make the user reference id unique to our cluster to allow
// for using the kubeconfig file of multiple clusters at the same
// time in the KUBECONFIG variable.

// Add a suffix to the user reference id in the context section
// which is in the "contexts[0].context.user" field.
context := config["contexts"].([]interface{})[0].(map[interface{}]interface{})
var newUserName, oldUserName string
for k, v := range context {
if k == "context" {
contextMap := v.(map[interface{}]interface{})
for k, v = range contextMap {
if k == "user" {
oldUserName = fmt.Sprintf("%s", v)
newUserName = fmt.Sprintf("%s-%s", oldUserName, userSuffix)
contextMap[k] = newUserName
break
}
}
break
}
}

// Use the new user reference id in the users section
// which is in the "users[0].name" field.
// In the same loop, add the 'username' field in the "users[0].user" section.
user := config["users"].([]interface{})[0].(map[interface{}]interface{})
for k := range user {
if k == "name" {
user[k] = newUserName
} else if k == "user" {
userMap := user[k].(map[interface{}]interface{})
userMap["username"] = oldUserName
}
}
buff, err = yaml.Marshal(config)
if err != nil {
return errors.Wrap(err, "failed to create new yaml output for kubeconfig")

0 comments on commit e03994e

Please sign in to comment.