The main difference between WSL1 and WSL2 that later is hyper-v VM based Microsoft Linux version, it sounds like excited news at least it is running full Linux kernel but there are too many issues if using as devops box.
Here are some outstanding pain points:
- default systemd is not enabled, which needed to install packages
- default network is NAT, always assign dynamic ip after reboot, will give trouble for app like k8s api server
Lab env
- win10 host 32GB RAM
- WSL2 Ubuntu 22.04
- Docker 25.0.4 on WSL2
- k8s 1.29.3 on WSL2
Install WSL2 Ubuntu
Install WSL2 from CMD or PS on win host, following official doc,
https://docs.microsoft.com/en-us/windows/wsl/install
wsl -l -o
The following is a list of valid distributions that can be installed.
Install using 'wsl.exe --install <Distro>'.
NAME FRIENDLY NAME
Ubuntu Ubuntu
Debian Debian GNU/Linux
kali-linux Kali Linux Rolling
Ubuntu-18.04 Ubuntu 18.04 LTS
Ubuntu-20.04 Ubuntu 20.04 LTS
Ubuntu-22.04 Ubuntu 22.04 LTS
OracleLinux_7_9 Oracle Linux 7.9
OracleLinux_8_7 Oracle Linux 8.7
OracleLinux_9_1 Oracle Linux 9.1
openSUSE-Leap-15.5 openSUSE Leap 15.5
SUSE-Linux-Enterprise-Server-15-SP4 SUSE Linux Enterprise Server 15 SP4
SUSE-Linux-Enterprise-15-SP5 SUSE Linux Enterprise 15 SP5
openSUSE-Tumbleweed openSUSE Tumbleweed
wsl --install Ubuntu-22.04
Launch WSL2 instance from CMD or PS shell,
wsl -d Ubuntu-22.04
Launch WSL2 instance from SecureCRT client
- create wsl2.bat under C:\tools, including below line
wsl -d Ubuntu-22.04
- SecureCRT->Session->Connection/Protocol:'Local Shell', Connection->Local Shell/Shell path: C:\tools\wsl2.bat
WSL2 config
There are two kind of config for WSL2:
- global config for all WSL2 instance on win host,
C:\Users\oldhorse\.wslconfig
[wsl2]
memory=16GB
processors=4
swap=0
- config per one WSL2 instance inside vm,
/etc/wsl.conf
[boot]
systemd=true
[network]
hostname = wsl2
generateHosts = false
generateResolvConf = false
This will enable systemd, setup hostname and don't generate /etc/resolv.conf.
Remedy for "static" ip
After WSL2 instance launched, will see NAT ip as below,
oldhorse@wsl2:~$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1350 qdisc mq state UP group default qlen 1000
link/ether 00:15:5d:ea:e8:22 brd ff:ff:ff:ff:ff:ff
inet 172.30.73.22/20 brd 172.30.79.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::215:5dff:feea:e822/64 scope link
valid_lft forever preferred_lft forever
Assume we want to keep static ip 192.168.80.2 for WSL2 Ubuntu, then need to manually assign ip to WSL2 eth0 inside WSL2 VM, and recreate NAT network adaptor for WSL on win host end, it is in fact the network type of Host-only + NAT.
Run below batch script as Administrator on windows host, you can type wsl2ip.bat from CMD, PS or place shortcut icon on desktop.
wsl2ip.bat
date /t
time /t
wsl -d Ubuntu-22.04 -u root ip addr del $(ip addr show eth0 ^| grep 'inet\b' ^| awk '{print $2}' ^| head -n 1) dev eth0
wsl -d Ubuntu-22.04 -u root ip addr add 192.168.80.2/24 broadcast 192.168.80.255 dev eth0
wsl -d Ubuntu-22.04 -u root ip route add 0.0.0.0/0 via 192.168.80.1 dev eth0
wsl -d Ubuntu-22.04 -u root echo nameserver 8.8.8.8 ^> /etc/resolv.conf
wsl -d Ubuntu-22.04 -u root chattr +i /etc/resolv.conf
powershell -c "Get-NetAdapter 'vEthernet (WSL)' | Get-NetIPAddress | Remove-NetIPAddress -Confirm:$False; New-NetIPAddress -IPAddress 192.168.80.1 -PrefixLength 24 -InterfaceAlias 'vEthernet (WSL)'; Get-NetNat | ? Name -Eq WSLNat | Remove-NetNat -Confirm:$False; New-NetNat -Name WSLNat -InternalIPInterfaceAddressPrefix 192.168.80.0/24;"
date /t
time /t
pause
The default generated /etc/resolv.conf won't work for DNS for Internet access, so that is why we disable generateResolvConf in /etc/wsl.conf, add 8.8.8.8 to it, and changed it to read-only file.
After run above script, you can access from host to WSL2 due to it is host-only static ip now.
PS C:\Users\oldhorse> Get-NetAdapter 'vEthernet (WSL)' | Get-NetIPAddress
IPAddress : 192.168.80.1
InterfaceIndex : 24
InterfaceAlias : vEthernet (WSL)
AddressFamily : IPv4
Type : Unicast
PrefixLength : 24
PrefixOrigin : Manual
SuffixOrigin : Manual
AddressState : Preferred
ValidLifetime : Infinite ([TimeSpan]::MaxValue)
PreferredLifetime : Infinite ([TimeSpan]::MaxValue)
SkipAsSource : False
PolicyStore : ActiveStore
PS C:\Users\oldhorse> Get-NetNat 'WSLNat'
Name : WSLNat
ExternalIPInterfaceAddressPrefix :
InternalIPInterfaceAddressPrefix : 192.168.80.0/24
IcmpQueryTimeout : 30
TcpEstablishedConnectionTimeout : 1800
TcpTransientConnectionTimeout : 120
TcpFilteringBehavior : AddressDependentFiltering
UdpFilteringBehavior : AddressDependentFiltering
UdpIdleSessionTimeout : 120
UdpInboundRefresh : False
Store : Local
Active : True
PS C:\Users\oldhorse> ipconfig
Ethernet adapter vEthernet (WSL):
Connection-specific DNS Suffix . :
IPv4 Address. . . . . . . . . . . : 192.168.80.1
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . :
PS C:\Users\oldhorse> ping 192.168.80.2
Pinging 192.168.80.2 with 32 bytes of data:
Reply from 192.168.80.2: bytes=32 time=1ms TTL=64
Reply from 192.168.80.2: bytes=32 time=1ms TTL=64
also you can access from WSL2 to host, Internet like google as well,
oldhorse@wsl2:~$ ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1350 qdisc mq state UP group default qlen 1000
link/ether 00:15:5d:ea:e8:22 brd ff:ff:ff:ff:ff:ff
inet 192.168.80.2/24 brd 192.168.80.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::215:5dff:feea:e822/64 scope link
valid_lft forever preferred_lft forever
oldhorse@wsl2:~$ ip r
default via 192.168.80.1 dev eth0
192.168.80.0/24 dev eth0 proto kernel scope link src 192.168.80.2
oldhorse@wsl2:~$ ping google.ca
PING google.ca (142.251.40.99) 56(84) bytes of data.
64 bytes from lga25s79-in-f3.1e100.net (142.251.40.99): icmp_seq=1 ttl=105 time=69.4 ms
64 bytes from lga25s79-in-f3.1e100.net (142.251.40.99): icmp_seq=2 ttl=105 time=60.5 ms
oldhorse@wsl2:~$ cat /etc/resolv.conf
nameserver 8.8.8.8
Common issue for WSL2 NAT
Since we changed WSL2 NAT manually, sometimes it hanging on wsl cli, as remedy run below reset batch file as admin from win host.
wslreset.bat
taskkill /f /im wslservice.exe
wsl -l
pause
From now on the WSL2 with static ip and Internet access ready for k8s installation.
k8s installation automation
It is possible to install k8s installation in one shot using my handy script toolkit.
First of all, download it via git clone, or manually download from github.
git clone git@github.com:robertluwang/hands-on-nativecloud.git
cd ./hands-on-nativecloud/src/k8s-cri-dockerd
Here is complete step to install a single node k8s cluster on WSL2.
bash docker-server.sh
exit and enter to WSL2 instance, to make sure non-root user to access docker.
bash cri-dockerd.sh
bash k8s-install.sh
bash k8s-init.sh 192.168.80.2
Next I will explain more details for each step as below.
Linux native Docker server on WSL2
The systemd is ready so just following the docker doc to install docker on Ubuntu,
https://docs.docker.com/engine/install/
I make it as handy script, docker-install.sh.
docker-server.sh
# docker-server.sh
# handy script to install docker on ubuntu
# run on k8s cluster node (master/worker)
# By Robert Wang @github.com/robertluwang
# Nov 21, 2022
echo === $(date) Provisioning - docker-server.sh by $(whoami) start
sudo apt-get update -y
sudo apt-get install -y ca-certificates curl gnupg lsb-release
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update -y
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
sudo groupadd docker
sudo usermod -aG docker $USER
# turn off swap
sudo swapoff -a
sudo sed -i '/swap/d' /etc/fstab
sudo mkdir /etc/docker
cat <<EOF | sudo tee /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
EOF
sudo systemctl enable docker
sudo systemctl daemon-reload
sudo systemctl restart docker
sleep 30
sudo systemctl restart docker
echo === $(date) Provisioning - docker-server.sh by $(whoami) end
Let's run it,
bash docker-server.sh
re-login to WSL2, docker working for non-root user, in my case it is oldhorse
.
docker info
docker ps
verify docker daemon,
oldhorse@wsl2:~$ systemctl status docker
● docker.service - Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2024-04-14 12:07:34 EDT; 1h 10min ago
TriggeredBy: ● docker.socket
Docs: https://docs.docker.com
Main PID: 253 (dockerd)
Tasks: 104
Memory: 208.5M
CGroup: /system.slice/docker.service
├─253 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
docker bridge docker0 ready,
oldhorse@wsl2:~$ ip addr show docker0
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:a7:dc:0d:c9 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
cri-dockerd setup for k8s + docker on WSL2
As we known the docker support removed since k8s 1.24+, so need cri-dockerd installed to have cri interface with Docker Engine API for k8s cluster. More details please see https://github.com/Mirantis/cri-dockerd.
You might wonder why we want to still keep docker for k8s setup, because I would like get benefit from pure docker test and k8s test in same local lab.
I also make handy script for setup - cri-dockerd.sh
cri-dockerd.sh
# cri-dockerd.sh
# handy script to install cri-dockerd on ubuntu
# run on k8s cluster node (master/worker)
# By Robert Wang @github.com/robertluwang
# Nov 26, 2022
echo === $(date) Provisioning - cri-dockerd by $(whoami) start
# cri-dockerd
VER=$(curl -s https://api.github.com/repos/Mirantis/cri-dockerd/releases/latest|grep tag_name | cut -d '"' -f 4|sed 's/v//g')
wget https://github.com/Mirantis/cri-dockerd/releases/download/v${VER}/cri-dockerd-${VER}.amd64.tgz
tar xvf cri-dockerd-${VER}.amd64.tgz
sudo mv cri-dockerd/cri-dockerd /usr/local/bin/
sudo chmod +x /usr/local/bin/cri-dockerd
wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.service
wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.socket
sudo mv cri-docker.socket cri-docker.service /etc/systemd/system/
sudo chown root:root /etc/systemd/system/cri-docker.*
sudo sed -i -e 's,/usr/bin/cri-dockerd,/usr/local/bin/cri-dockerd,' /etc/systemd/system/cri-docker.service
sudo systemctl daemon-reload
sudo systemctl enable cri-docker.service
sudo systemctl enable --now cri-docker.socket
sleep 30
sudo systemctl restart cri-docker.service
echo === $(date) Provisioning - cri-dockerd by $(whoami) end
Let's run it,
bash cri-dockerd.sh
Verify it is active running,
oldhorse@wsl2:~$ systemctl status cri-docker
● cri-docker.service - CRI Interface for Docker Application Container Engine
Loaded: loaded (/etc/systemd/system/cri-docker.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2024-04-14 12:07:35 EDT; 1h 25min ago
TriggeredBy: ● cri-docker.socket
Docs: https://docs.mirantis.com
Main PID: 880 (cri-dockerd)
Tasks: 17
Memory: 100.8M
CGroup: /system.slice/cri-docker.service
└─880 /usr/local/bin/cri-dockerd --container-runtime-endpoint fd://
k8s packages installation on WSL2
It is time to install k8s following up official k8s doc.
Install k8s packages using below handy script k8s-install.sh,
https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
k8s-install.sh
# k8s-install.sh
# handy script to install k8s on ubuntu
# run on all k8s cluster node (master/worker)
# By Robert Wang @github.com/robertluwang
# Oct 30, 2021
echo === $(date) Provisioning - k8s-install.sh by $(whoami) start
sudo apt-get update -y
sudo apt-get install -y apt-transport-https ca-certificates curl gpg
sudo curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.29/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update -y
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
# cli completion
sudo apt-get install bash-completion -y
source <(kubectl completion bash)
echo "source <(kubectl completion bash)" >> ~/.bashrc
echo 'alias k=kubectl' >>~/.bashrc
echo 'complete -F __start_kubectl k' >>~/.bashrc
echo "export do='--dry-run=client -o yaml'" >>~/.bashrc
echo === $(date) Provisioning - k8s-install.sh by $(whoami) end
Let's run it,
bash k8s-install.sh
Verify installed package version,
oldhorse@wsl2:~/dev$ kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"29", GitVersion:"v1.29.3", GitCommit:"6813625b7cd706db5bc7388921be03071e1a492d", GitTreeState:"clean", BuildDate:"2024-03-15T00:06:16Z", GoVersion:"
go1.21.8", Compiler:"gc", Platform:"linux/amd64"}
o
ldhorse@wsl2:~/dev$ kubectl version
Client Version: v1.29.3
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Server Version: v1.29.3
oldhorse@wsl2:~/dev$ kubelet --version
Kubernetes v1.29.3
k8s cluster init on WSL2
Run handy script k8s-init.sh for first time setup k8s cluster on WSL2,
k8s-init.sh
# k8s-init.sh
# handy script to init k8s cluster on ubuntu
# run on k8s master node only
# By Robert Wang @github.com/robertluwang
# Nov 21, 2022
# $1 - master/api server ip
echo === $(date) Provisioning - k8s-init.sh $1 by $(whoami) start
if [ -z "$1" ];then
sudo kubeadm init --pod-network-cidr=192.168.0.0/16 --ignore-preflight-errors=NumCPU --ignore-preflight-errors=Mem --cri-socket unix:///var/run/cri-dockerd.sock | tee /var/tmp/kubeadm.log
else
sudo kubeadm init --pod-network-cidr=192.168.0.0/16 --apiserver-advertise-address=$1 --ignore-preflight-errors=NumCPU --ignore-preflight-errors=Mem --cri-socket unix:///var/run/cri-dockerd.sock | tee /var/tmp/kubeadm.log
fi
# allow normal user to run kubectl
if [ -d $HOME/.kube ]; then
rm -r $HOME/.kube
fi
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
# install calico network addon
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.24.5/manifests/tigera-operator.yaml
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.24.5/manifests/custom-resources.yaml
# allow run on master
kubectl taint nodes --all node-role.kubernetes.io/control-plane-
echo === $(date) Provisioning - k8s-init.sh $1 by $(whoami) end
We need to give eth0 static ip 192.168.80.2 as parameter to k8s-init.sh, let the k8s api server pointing to eth0 ip.
bash k8s-init.sh 192.168.80.2
k8s cluster reset on WSL2
Run handy script k8s-reset.sh to quickly rebuild k8s cluster on WSL2 if anything mess up,
k8s-reset.sh
# k8s-reset.sh
# handy script to reset k8s cluster on ubuntu
# run on k8s cluster node (master/worker)
# By Robert Wang @github.com/robertluwang
# Nov 21, 2022
echo === $(date) Provisioning - k8s-reset.sh by $(whoami) start
sudo kubeadm reset -f --cri-socket unix:///var/run/cri-dockerd.sock
echo === $(date) Provisioning - k8s-reset.sh by $(whoami) end
For example,
bash k8s-reset.sh
then following k8s-init.sh
with eth0 static ip,
bash k8s-init.sh 192.168.80.2
k8s cluster test on WSL2
oldhorse@wsl2:~$ k get node
NAME STATUS ROLES AGE VERSION
wsl2 Ready control-plane 7d3h v1.29.3
oldhorse@wsl2:~$ k get pod -A
NAMESPACE NAME READY STATUS RESTARTS AGE
calico-apiserver calico-apiserver-99d8c8bc6-5csh6 1/1 Running 9 (104m ago) 7d3h
calico-apiserver calico-apiserver-99d8c8bc6-lgdh4 1/1 Running 9 (104m ago) 7d3h
calico-system calico-kube-controllers-78788579b8-jdhj4 1/1 Running 9 (104m ago) 7d3h
calico-system calico-node-vzhwj 1/1 Running 9 (104m ago) 7d3h
calico-system calico-typha-7647b5ff5b-dkxks 1/1 Running 15 (40m ago) 7d3h
kube-system coredns-76f75df574-4dg8d 1/1 Running 9 (104m ago) 7d3h
kube-system coredns-76f75df574-5cmgf 1/1 Running 9 (104m ago) 7d3h
kube-system etcd-wsl2 1/1 Running 32 (46m ago) 7d3h
kube-system kube-apiserver-wsl2 1/1 Running 30 (46m ago) 7d3h
kube-system kube-controller-manager-wsl2 1/1 Running 10 (104m ago) 7d3h
kube-system kube-proxy-x7mxt 1/1 Running 9 (104m ago) 7d3h
kube-system kube-scheduler-wsl2 1/1 Running 10 (104m ago) 7d3h
tigera-operator tigera-operator-6fbc4f6f8d-d5fgp 1/1 Running 16 (40m ago) 7d3h
Do dry run on new pod,
oldhorse@wsl2:~$ k run test --image=nginx $do
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: test
name: test
spec:
containers:
- image: nginx
name: test
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
Let's launch test pod if everything looks good,
oldhorse@wsl2:~$ k run test --image=nginx
pod/test created
oldhorse@wsl2:~$ k get pod
NAME READY STATUS RESTARTS AGE
test 0/1 ContainerCreating 0
oldhorse@wsl2:~$ k get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
test 1/1 Running 0 48s 192.168.9.254 wsl2 <none> <none>
oldhorse@wsl2:~$ curl 192.168.9.254
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
Conclusion
So far the one single node k8s cluster lab is ready on WSL2:
- k8s cluster has static ip, can be accessed from inside and outside of WSL2
- it is docker based so same lab env for any docker related test
- it is ready for local LLM AI test on both docker and k8s
About Me
Hey! I am Robert Wang, live in Montreal.
More simple and more efficient.
GitHub: robertluwang
Twitter: robertluwang
LinkedIn: robertluwang
Medium: robertluwang
Dev.to: robertluwang
Web: dreamcloud.artark.ca
Top comments (0)