Skip to content

Commit

Permalink
Add option to merge kubeconfig when cluster is created (#196)
Browse files Browse the repository at this point in the history
* pkg/utils/exec: Simplify command invocation and set default PATH variable

Signed-off-by: Din Music <[email protected]>

* kubeconfig: Merge kubeconfig instead of overriding it

Signed-off-by: Din Music <[email protected]>

* pkg: Rename copyKubeconfig to mergeKubeconfig

Signed-off-by: Din Music <[email protected]>

* embed/presets: Set mergeKubeconfig to true for getting-started guide

Signed-off-by: Din Music <[email protected]>

* docs: Replace copyKubeconfig with mergeKubeconfig

Signed-off-by: Din Music <[email protected]>

* scripts: Use mergeKubeconfig in test scripts

Signed-off-by: Din Music <[email protected]>

* pkg/cluster/managers: Rewrite final kubeconfig

Replace default occurences of context/cluster/user in kubeconfig
with a cluster name.

Signed-off-by: Din Music <[email protected]>

---------

Signed-off-by: Din Music <[email protected]>
  • Loading branch information
MusicDin authored Apr 18, 2024
1 parent e307351 commit 3cfdac5
Show file tree
Hide file tree
Showing 18 changed files with 314 additions and 179 deletions.
2 changes: 1 addition & 1 deletion docs/examples/full-example.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ kubernetes:
networkPlugin: calico
dnsMode: coredns # (36)!
other:
copyKubeconfig: false
mergeKubeconfig: true

#
# The 'addons' section contains the configuration of the applications that
Expand Down
37 changes: 26 additions & 11 deletions docs/getting-started/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,20 +245,29 @@ cluster:
7. A static IP address set for this particular instance.
If the `ip` property is omitted, the node requests a DHCP lease during creation.

8. In this example, the amount of RAM allocated to the worker node instance is set to 4 GiB, which overwrites the default value of 8 GiB.
8. In this example, the amount of RAM allocated to the worker node instance is set to 4 GiB,
which overwrites the default value of 8 GiB.

### Step 3.4 - Kubernetes properties

The final section of the cluster configuration contains the Kubernetes properties,
such as the version and network plugin.
such as the Kubernetes version and network plugin.

In addition, you can deploy the Kubernetes cluster either using `kubespray` (default) or `k3s`.

```yaml title="kubitect.yaml"
kubernetes:
manager: kubespray
version: v1.28.6
networkPlugin: calico
other:
mergeKubeconfig: true # (1)
```

1. Kubeconfig can be automatically merged into `~/.kube/config` when a cluster is
created by setting property [mergeKubeconfig](../../user-guide/configuration/kubernetes/#merge-kubeconfig)
to `true` in the cluster's configuration file.

## Step 4 - Create the cluster

Below is the final configuration for our Kubernetes cluster:
Expand Down Expand Up @@ -305,6 +314,8 @@ Below is the final configuration for our Kubernetes cluster:
kubernetes:
version: v1.28.6
networkPlugin: calico
other:
mergeKubeconfig: true
```

To create the cluster, **apply the configuration** file to Kubitect:
Expand Down Expand Up @@ -347,20 +358,24 @@ kubitect list clusters

## Step 5 - Test the cluster

Once you have successfully installed a Kubernetes cluster, the Kubeconfig file can be found in the cluster's directory.
However, you will most likely want to **export the Kubeconfig** to a separate file:

Finally, to confirm that the cluster is ready, you can list its nodes using the `kubectl` command:

```sh
kubitect export kubeconfig --cluster k8s-cluster > kubeconfig.yaml
kubectl --context k8s-cluster get nodes
```

This will create a file named `kubeconfig.yaml` in your current directory.
Finally, to confirm that the cluster is ready, you can list its nodes using the `kubectl` command:
??? question "Where do I find kubeconfig? <i class="click-tip"></i>"

```sh
kubectl get nodes --kubeconfig kubeconfig.yaml
```
Once the Kubernetes cluster is deployed, the Kubeconfig file can be found in the cluster's directory.

You can easily export the Kubeconfig into a separate file using the following command, which creates a file named `kubeconfig.yaml` in your current directory.

```sh
kubitect export kubeconfig --cluster k8s-cluster > kubeconfig.yaml
```

Kubeconfig can be also automatically merged into existing `~/.kube/config`
when a cluster is created by setting property [mergeKubeconfig](../../user-guide/configuration/kubernetes/#merge-kubeconfig) to `true` in the cluster's configuration file.

:clap: Congratulations, you have completed the *getting started* quide.

Expand Down
13 changes: 2 additions & 11 deletions docs/getting-started/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,12 @@ kubitect apply --config cluster.yaml

That's it! The cluster, named `k8s-cluster`, should be up and running in approximately 10 minutes.

### Step 2 - Export kubeconfig

After successfully installing the Kubernetes cluster, a Kubeconfig file will be created within the cluster's directory.
To export the Kubeconfig to a custom file, use the following command:

```sh
kubitect export kubeconfig --cluster k8s-cluster > kubeconfig.yaml
```

### Step 3 - Test the cluster
### Step 2 - Test the cluster

To test that the cluster is up and running, display all cluster nodes using the exported Kubeconfig and the kubectl command:

```sh
kubectl get nodes --kubeconfig kubeconfig.yaml
kubectl --context k8s-cluster get nodes
```

:clap: Congratulations, you have successfully deployed a Kubernetes cluster using Kubitect!
Expand Down
10 changes: 5 additions & 5 deletions docs/user-guide/configuration/kubernetes.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,19 +91,19 @@ kubernetes:
dnsMode: coredns
```

### Copy kubeconfig
### Merge kubeconfig

:material-tag-arrow-up-outline: [v2.0.0][tag 2.0.0]
:material-tag-arrow-up-outline: [v3.4.0][tag 3.4.0]
&ensp;
:octicons-file-symlink-file-24: Default: `false`

Kubitect offers the option to automatically copy the Kubeconfig file to the `~/.kube/config` path.
By default, this feature is disabled to prevent overwriting an existing file.
Kubitect offers an option to merge the resulting Kubeconfig file with the config on path `~/.kube/config`.
This means that whenever a new cluster is created, it can be selected by context which equals the cluster name.

```yaml
kubernetes:
other:
copyKubeconfig: true
mergeKubeconfig: true
```

### Auto renew control plane certificates
Expand Down
5 changes: 2 additions & 3 deletions docs/user-guide/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -779,13 +779,12 @@ Each configuration property is documented with 5 columns: Property name, descrip
</td>
</tr>
<tr>
<td><code>kubernetes.other.copyKubeconfig</code></td>
<td><code>kubernetes.other.mergeKubeconfig</code></td>
<td>boolean</td>
<td>false</td>
<td></td>
<td>
When this property is set to true, the kubeconfig of a new cluster is copied to the <code>~/.kube/config</code>.
Please note that setting this property to true may cause the existing file at the destination to be overwritten.
When this property is set to true, the kubeconfig of a new cluster is merged to the config on path <code>~/.kube/config</code>.
</td>
</tr>
<tr>
Expand Down
16 changes: 0 additions & 16 deletions embed/ansible/kubitect/finalize.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,6 @@
path: "{{ config_dir }}/admin.conf"
mode: 0600

- name: Copy Kubeconfig
block:
- name: Make sure ~/.kube directory exists
file:
path: "~/.kube"
state: directory

- name: Copy kubeconfig file to a ~/.kube dirctory
copy:
src: "{{ config_dir }}/admin.conf"
dest: "~/.kube/config"
mode: 0600
when:
- config.kubernetes.other.copyKubeconfig is defined
- config.kubernetes.other.copyKubeconfig == true

- name: Install addons
hosts: localhost
gather_facts: false
Expand Down
2 changes: 2 additions & 0 deletions embed/presets/getting-started.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ cluster:
kubernetes:
version: v1.28.6
networkPlugin: calico
other:
mergeKubeconfig: true
50 changes: 50 additions & 0 deletions pkg/cluster/managers/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ package managers

import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/MusicDin/kubitect/pkg/cluster/event"
"github.com/MusicDin/kubitect/pkg/models/config"
"github.com/MusicDin/kubitect/pkg/models/infra"
"github.com/MusicDin/kubitect/pkg/tools/ansible"
"github.com/MusicDin/kubitect/pkg/utils/exec"
)

type common struct {
Expand Down Expand Up @@ -34,6 +38,52 @@ func (e common) SshPKey() string {
return e.SshPrivateKeyPath
}

// mergeKubeconfig merges cluster kubeconfig with config default
// config in user directory (~/.kube/config). Note that if kubectl
// is not present locally, the command will fail.
func (e common) mergeKubeconfig() error {
// Get directory of the current user.
home, err := os.UserHomeDir()
if err != nil {
return err
}

defConfigPath := filepath.Join(home, ".kube", "config")
clsConfigPath := filepath.Join(e.ConfigDir, "admin.conf")

exec := exec.NewLocalClient()
exec.SetEnv("KUBECONFIG", fmt.Sprintf("%s:%s", clsConfigPath, defConfigPath))

config, err := exec.Output("kubectl", "config", "view", "--raw")
if err != nil {
return err
}

return os.WriteFile(defConfigPath, config, 0600)
}

// rewriteKubeconfig reads the kubeconfig file and replaces occurrences of map
// keys with corresponding map values.
func (e *common) rewriteKubeconfig(replaces map[string]string) error {
kubeconfigPath := filepath.Join(e.ConfigDir, "admin.conf")
kubeconfig, err := os.ReadFile(kubeconfigPath)
if err != nil {
return err
}

s := []string{}
for k, v := range replaces {
if k == "" || v == "" {
return fmt.Errorf("cannot rewrite config with empty values")
}
s = append(s, k, v)
}

replacer := strings.NewReplacer(s...)
new := replacer.Replace(string(kubeconfig))
return os.WriteFile(kubeconfigPath, []byte(new), 0600)
}

// extractRemovedNodes returns removed node instances extracted from the event changes.
func extractRemovedNodes(events []event.Event) ([]config.Instance, error) {
var nodes []config.Instance
Expand Down
39 changes: 35 additions & 4 deletions pkg/cluster/managers/k3s.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/MusicDin/kubitect/pkg/tools/ansible"
"github.com/MusicDin/kubitect/pkg/tools/git"
"github.com/MusicDin/kubitect/pkg/tools/virtualenv"
"github.com/MusicDin/kubitect/pkg/ui"
"github.com/MusicDin/kubitect/pkg/utils/exec"
)

Expand Down Expand Up @@ -117,7 +118,27 @@ func (e *k3s) Create() error {
return err
}

return e.Finalize()
err = e.Finalize()
if err != nil {
return err
}

// Rewrite kubeconfig before merging to prevent accidental
// overwrite of an existing configuration.
err = e.rewriteKubeconfig()
if err != nil {
return err
}

if e.Config.Kubernetes.Other.MergeKubeconfig {
err := e.mergeKubeconfig()
if err != nil {
// Just warn about failure, since deployment has succeeded.
ui.Print(ui.WARN, "Failed to merge kubeconfig:", err)
}
}

return nil
}

// Upgrades upgrades a Kubernetes cluster by calling appropriate k3s
Expand Down Expand Up @@ -184,17 +205,17 @@ func (e *k3s) ScaleDown(events event.Events) error {
for _, n := range rmNodes {
name := fmt.Sprintf("%s-%s-%s", e.ClusterName, n.GetTypeName(), n.GetID())

err = ssh.Run(exec.NewCommand("kubectl", "cordon", name))
err = ssh.Run("kubectl", "cordon", name)
if err != nil {
return fmt.Errorf("cordon node %q: %v", name, err)
}

err = ssh.Run(exec.NewCommand("kubectl", "drain", name, "--ignore-daemonsets", "--force"))
err = ssh.Run("kubectl", "drain", name, "--ignore-daemonsets", "--force")
if err != nil {
return fmt.Errorf("drain node %q: %v", name, err)
}

err = ssh.Run(exec.NewCommand("kubectl", "delete", "node", name))
err = ssh.Run("kubectl", "delete", "node", name)
if err != nil {
return fmt.Errorf("delete node %q: %v", name, err)
}
Expand All @@ -203,3 +224,13 @@ func (e *k3s) ScaleDown(events event.Events) error {
// No need for further cleanup. This instance will be removed.
return nil
}

// rewriteKubeconfig replaces "default" context/cluster/user in kubeconfig
// with a cluster name.
func (e *k3s) rewriteKubeconfig() error {
replaces := map[string]string{
"default": e.ClusterName,
}

return e.common.rewriteKubeconfig(replaces)
}
44 changes: 42 additions & 2 deletions pkg/cluster/managers/kubespray.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/MusicDin/kubitect/pkg/tools/ansible"
"github.com/MusicDin/kubitect/pkg/tools/git"
"github.com/MusicDin/kubitect/pkg/tools/virtualenv"
"github.com/MusicDin/kubitect/pkg/ui"
"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -102,7 +103,27 @@ func (e *kubespray) Create() error {
return err
}

return e.Finalize()
err = e.Finalize()
if err != nil {
return err
}

// Rewrite kubeconfig before merging to prevent accidental
// overwrite of an existing configuration.
err = e.rewriteKubeconfig()
if err != nil {
return err
}

if e.Config.Kubernetes.Other.MergeKubeconfig {
err := e.mergeKubeconfig()
if err != nil {
// Just warn about failure, since deployment has succeeded.
ui.Print(ui.WARN, "Failed to merge kubeconfig:", err)
}
}

return nil
}

// Upgrades upgrades a Kubernetes cluster by calling appropriate Kubespray
Expand All @@ -113,7 +134,14 @@ func (e *kubespray) Upgrade() error {
return err
}

return e.Finalize()
err = e.Finalize()
if err != nil {
return err
}

// Rewrite kubeconfig on upgrade, because it is re-fetched
// from the server.
return e.rewriteKubeconfig()
}

// ScaleUp adds new nodes to the cluster.
Expand Down Expand Up @@ -207,3 +235,15 @@ func (e *kubespray) generateGroupVars() error {

return nil
}

// rewriteKubeconfig replaces context/cluster/user in kubeconfig with the
// cluster name.
func (e *kubespray) rewriteKubeconfig() error {
replaces := map[string]string{
"[email protected]": e.ClusterName,
"kubernetes-admin": e.ClusterName,
"cluster.local": e.ClusterName,
}

return e.common.rewriteKubeconfig(replaces)
}
Loading

0 comments on commit 3cfdac5

Please sign in to comment.