January 26, 2023

How to connect to microk8s/minikube on WSL from Windows?

After installing Kubernetes on WSL, how do we connect to it from Windows using kubectl or any other developer tool we might be using? As usual it turns out it can be done with just few lines of good old scripting πŸ™‚

Before we start

Not surprisingly most of you probably already have WSL installed, but in case you don’t you can quickly get it directly from Microsoft Store for Windows 10/11. For this tutorial we will be using Ubuntu 22.04 with systemd enabled and microk8s installed. If you are using minikube approach is very similar with some additional steps as described below.

Extract Kube Config from WSL

First to notice is that after installing microk8s inside of a WSL distribution, kubectl is fully configured inside of that distribution. Therefore we will focus on getting that configuration transferred to a Windows host. In order to do that, execute ‘kubectl config view –raw’ and save command output to a file inside our profile folder .kube. Take a look at a sample response in second tab.

wsl -d <DISTRIBUTION_NAME> -e /snap/bin/microk8s kubectl config view --raw > "~/.kube/config"
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: <BASE64_ENCODED_CERT>
    server: https://127.0.0.1:16443
  name: microk8s-cluster
contexts:
- context:
    cluster: microk8s-cluster
    user: admin
  name: microk8s
current-context: microk8s
kind: Config
preferences: {}
users:
- name: admin
  user:
    token: VVRvMXpPdmxVYWFtaENKMXZsOVdub3BDbWdTb09YdXZIa2pTWkR1L04wOD0K

Next open saved file and update address from 127.0.0.1 to localhost. I know, I know, it is the same… but it isn’t πŸ˜‰ Localhost is special to WSL and only that hostname ports are forwarded, so we have to use it.

$distributionConfig = Get-Content -path "~/.kube/config" -Raw
$distributionConfig = $distributionConfig -replace '127.0.0.1','localhost'
$distributionConfig | Set-Content -Path "~/.kube/config"

At this point you are ready to try to connect! Lets type ‘kubectl get all --all-namespaces‘ and see what happens πŸ˜‰ Wait… what? Are you getting an error?!

Unable to connect to the server: x509: certificate is valid for kubernetes, kubernetes.default, kubernetes.default.svc, kubernetes.default.svc.cluster, kubernetes.default.svc.cluster.local, not localhost

Ah yes, we need to update the Kubernetes API certificate to support localhost… host πŸ™‚ Lucky for us, the only thing we need to do is to edit one configuration file csr.conf.template, by adding localhost to alt_names.

sudo nano /var/snap/microk8s/current/certs/csr.conf.template
[ alt_names ]
DNS.1 = kubernetes
DNS.2 = kubernetes.default
DNS.3 = kubernetes.default.svc
DNS.4 = kubernetes.default.svc.cluster
DNS.5 = kubernetes.default.svc.cluster.local
DNS.6 = localhost
IP.1 = 127.0.0.1
IP.2 = 10.152.183.1

Finally save the file, wait some seconds to allow the service to notice modification, generate new certificate and apply all the changes. Now, only thing left is to test again, so verify if you get similar results to the ones on the picture below.

Multiple Kube Configs

In this paragraph lets talk about situation when we work with multiple clusters, each of them requiring different set of credentials. In such case, traditional approach involves either replacing or merging configuration files, but there is a much simpler way. All we need to do is to add multiple files to environmental variable KUBECONFIG.

Personally I recommend to store one config file per environment we work with, so that we don’t clutter single file. In addition it becomes easy to add/remove configurations, especially for our test environments. Reference .kube directory following this recommendation might look like this:

~/.kube
  - cache/
  - config
  - azure.config
  - microk8s.config
  - minikube.config

Now, to achieve this, use the sample script below to update KUBECONFIG variable for the current user. I would recommend saving it inside of a .kube folder, so that every time you are adding new config you can run this script to update the environmental variable quickly. The script works like this:

  • First find all config files in ~/.kube folder
  • Then get current value of KUBECONFIG (in case it points at files outside ~/.kube)
  • Next merge two lists and remove duplicates, update current session’s variable
  • Finally also update in registry for all other future sessions
$discoveredConfigPath = Get-ChildItem "~/.kube/*config" | % { $_.FullName}
$currentConfigPath = $null -ne $env:KUBECONFIG ? @($env:KUBECONFIG -split ';') : @()
$env:KUBECONFIG = ($currentConfigPath + $discoveredConfigPath) | Sort-Object -Unique | Join-String -Separator ';'
Set-ItemProperty Registry::HKEY_CURRENT_USER\Environment -Name KUBECONFIG -Value $env:KUBECONFIG

After script finishes, view merged kube config by running kubectl config view and choose active context by running kubectl config use-context <CONTEXT-NAME>.

Minikube version – final

All the above examples are made based on microk8s, but what if you are running minikube? Not a big deal, the only thing extra we need to do is to inline base64 encoded certificates πŸ™‚

$distributionName = "Ubuntu-22.04"
$distroConfigPath = "~/.kube/minikube.config"
wsl -d $distributionName -e /snap/bin/kubectl config view --raw > $distroConfigPath

# get base64 encoded certificates
$caCert     = wsl -d $distributionName -e bash -c "cat /root/.minikube/ca.crt | base64 -w0"
$clientCert = wsl -d $distributionName -e bash -c "cat /root/.minikube/profiles/minikube/client.crt | base64 -w0"
$clientKey  = wsl -d $distributionName -e bash -c "cat /root/.minikube/profiles/minikube/client.key | base64 -w0"

# inline certs, update host and naming
$distributionConfig = Get-Content -path $distroConfigPath -Raw
$distributionConfig = $distributionConfig -replace "client-certificate.*","client-certificate-data: $clientCert"
$distributionConfig = $distributionConfig -replace "client-key.*","client-key-data: $clientKey"
$distributionConfig = $distributionConfig -replace "certificate-authority.*","certificate-authority-data: $caCert"
$distributionConfig = $distributionConfig -replace "minikube","minikube-$distributionName"
$distributionConfig = $distributionConfig -replace '127.0.0.1','localhost'
$distributionConfig | Set-Content -Path $distroConfigPath
$distributionConfig

#update kubectl config path
$currentConfigPath = $null -ne $env:KUBECONFIG ? @($env:KUBECONFIG -split ';') : @()
$discoveredConfigPath = Get-ChildItem "~/.kube/*config" | % { $_.FullName}
$env:KUBECONFIG = ($currentConfigPath + $discoveredConfigPath) | Sort-Object -Unique | Join-String -Separator ';'
Set-ItemProperty Registry::HKEY_CURRENT_USER\Environment -Name KUBECONFIG -Value $env:KUBECONFIG

#set current distro as main
kubectl config use-context "minikube-$distributionName"

Microk8s version – final

In case you just want to get going, below is the full script that will get you started in no time πŸ˜‰

$distributionName = "Ubuntu-22.04"
$distroConfigPath = "~/.kube/microk8s.config"

# extract kube config from WSL
wsl -d $distributionName -e /snap/bin/microk8s kubectl config view --raw > $distroConfigPath

# patch configuration file
$distributionConfig = Get-Content -path $distroConfigPath -Raw
$distributionConfig = $distributionConfig -replace "microk8s","microk8s-$distributionName"
$distributionConfig = $distributionConfig -replace "admin","admin-$distributionName"
$distributionConfig = $distributionConfig -replace '127.0.0.1','localhost'
$distributionConfig | Set-Content -Path $distroConfigPath

# update kubectl config path
$currentConfigPath = $null -ne $env:KUBECONFIG ? @($env:KUBECONFIG -split ';') : @()
$discoveredConfigPath = Get-ChildItem "~/.kube/*config" | % { $_.FullName}
$env:KUBECONFIG = ($currentConfigPath + $discoveredConfigPath) | Sort-Object -Unique | Join-String -Separator ';'
Set-ItemProperty Registry::HKEY_CURRENT_USER\Environment -Name KUBECONFIG -Value $env:KUBECONFIG

# set current distro as main
kubectl config use-context "microk8s-$distributionName"

Summary

To sum it all up, it turns out that it is rather simple to get connected from Windows to WSL deployed Kubernetes. We were able to achieve it in 3 simple steps: copy the config file, adjust values, update environmental variable. At the end we can now easily access our cluster from any development tool we prefer. Personally I work quite a lot in Visual Studio Code, so it is rather important for me to have that as smooth as possible πŸ˜‰

References

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.