维基百科:
适用于Linux的Windows子系统(英语:Windows Subsystem for Linux,简称WSL)是一个为在Windows 10和Windows Server 2019上能够原生运行Linux二进制可执行文件(ELF格式)的兼容层。
也就是说借助于WSL
,在Windows上也能够执行Linux程序。对于开发人员来说,这是很有意义的。
高级语言程序开发一般要依赖图形化集成开发环境(IDE)
,所以一般开发环境系统都是Windows
或者OSX
。OSX
是从Unix
系统发展而来的,所以自带命令行终端,对开发人员来说很友好;而Windows
天生支持图形化操作,虽然有cmd
和PowerShell
终端支持简单的命令行,但终究功能有限,无法替代Linux
功能。
举一个例子,在Windows上进行开发,现在需要将某个目录及其子目录中所有配置文件的xx.xx.12.13
替换为141.151.1.111
,不知道具体哪个配置文件中含有待替换字符串并且这样的配置文件多而且分散。如果在Windows
手动查找或者写bat
脚本都是一件很费劲的事情,而在Linux
中只需要一行命令:
#该命令递归寻找当前目录及子目录后缀是txt的文件并执行替换字符串
find . -type f -name '*.txt' -exec sed -i 's/xx.xx.12.13/141.151.1.111/g' {} \;
可见,如果在Windows
能够丝滑使用Linux
系统程序,开发效率会大大提高。
微软做了在Windows
系统上兼容Unix like
的尝试,先推出了WSL1
,WSL1
在文件系统性能上有很大的问题。后又推出了WSL2
,底层的实现和WSL1
不同,文件系统性能有了很大的改善。WSL1
和WSL2
的区别参考:https://docs.microsoft.com/en-us/windows/wsl/compare-versions。
网上对于WSL
的吐槽很多,认为很难用。笔者亲身实践,使用WSL2
能有丝滑般的开发体验,但也有一些头痛的问题,这里记录解决方案,方便诸位丝滑入坑WSL2
。
如果你想在Windows
系统上运行Linux
发行版,在没有WSL
之前,可以装一个虚拟机。
虚拟机和WSL2
相比较,前者和宿主机之间文件系统是完全隔离的;而后者是相通的。
WSL2
基于Hyper-v
实现了Linux
内核,所以WSL
也算是一个完整的Linux系统。相较于虚拟机,WSL2
会轻量级一些。但是支持的发行版也有限。
建议:如果没有接触过Linux
,要学习Linux
,最好自己装一个Linux
虚拟机;而如果对Linux
非常熟悉了,可以尝试安装WSL2
,对开发人员来说WSL2
会带来更好的开发体验。
WSL2的安装请参考文章:https://dowww.spencerwoo.com/。
WSL2默认安装在Windows的C
盘,随着WSL2使用容量的变大,可能会造成C
盘空间的预警。可以使用上面的办法将WSL2
迁移到其他逻辑卷。实际上,WSL像虚拟机一样,在HOST的存储是一个映像文件,笔者Win10上安装的WSL2-Ubuntu 20
已经使用了40G
的存储,如图:
如果将WSL2从开发环境电脑迁移到家中电脑,也是很方便的,将映像文件copy走,直接导入目标机即可。
笔者WSL2
选择安装的是微软商店的Ubuntu 20.04 LTS
,接下来的操作基于该Ubuntu发行版。
WSL
的启动就是打开对应从微软商店安装的Ubuntu
或者其他发行版(如下图),WSL
关闭可以在PowerShell
中使用:wsl --shutdow
。
WSL2
的网路类似于虚拟机中设置的NAT
网络模式,该模式下WSL2
实例借助于宿主机的网卡访问外部网络,宿主机也可以访问WSL2
实例,但是宿主机所在局域网的其他主机不能访问WSL2
实例。如图:
所谓的
WSL2
实例指的就是安装的某个WSL2
支持的发行版,笔者使用的是Ubuntu 20.04 LTS
。
WSL2
的网络地址是动态变化的(像docker容器一样,重启IP地址发生改变),并且局域网内的其他host都不能访问,这样太不方便了。
为什么说不方便呢?动态IP就不说了,公司内网的电脑肯定不是动态分配的,而是设置静态IP,因为本地环境除了开发之外还会提供服务供外部联调,需要固定的IP。另外,如果你在
WSL2
上启动的服务和同事联调,同事访问不到,就很麻烦了。
怎么解决呢?可以通过Windows
端口映射的方式,将WSL2
的端口映射到host win主机上。
脚本如下:
#清除网络映射
netsh interface portproxy reset
#获取WSL2实例动态IP
$wsl_ip = (wsl hostname -I).trim().split()[0]
#日志信息
Write-Host "WSL Machine IP: ""$wsl_ip"""
#网络映射win:8080 --> wsl2:8080
netsh interface portproxy add v4tov4 listenport=8080 connectport=8080 connectaddress=$wsl_ip
可以直接在PowerShell
终端执行,也可以保存为.ps1
脚本,在PowerShell
终端执行脚本,如图:
如果本地装的是虚拟机,安装docker就很容易了。但是在WSL2
实例上安装,还是要费一些功夫。
官方的解决方案是建议在WSL2
上安装Docker Desktop for Windows
,可以参考:https://docs.microsoft.com/zh-cn/windows/wsl/tutorials/wsl-containers。
如果是不想装Docker Desktop
但又想在WSL2
上使用docker
,参考https://dev.to/bowmanjd/install-docker-on-windows-wsl-without-docker-desktop-34m9给出解决方案,接下来做详细的说明。
这里只针对
WSL2
版本安装docker
,WSL1
版本不支持docker
。
下面的配置针对
WSL2 Ubuntu 20.04 LTS
进行docker配置,其他的WSL2
支持的发行版在用户、权限配置有差别,可参考原文章。
sudo apt remove docker-engine docker docker.io docker-ce docker-ce-cli
sudo apt install --no-install-recommends apt-transport-https ca-certificates curl gnupg2
设置os-release相关环境变量:
#执行完成后,可以在shel中执行$ID验证是否存在该变量
source /etc/os-release
让apt
信任仓库:
#这里的${ID}是上面source命令执行后的环境变量ID,本示例是:ubuntu
curl -fsSL https://download.docker.com/linux/${ID}/gpg | sudo apt-key add -
新增仓库地址并更新仓库列表,以便apt
能够用到:
#本示例变量替换后的结果为:deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable
echo "deb [arch=amd64] https://download.docker.com/linux/${ID} ${VERSION_CODENAME} stable" | sudo tee /etc/apt/sources.list.d/docker.list
sudo apt update
执行命令:
sudo apt install docker-ce docker-ce-cli containerd.io
rm -rf /
的误操作。sudo docker ...
的方式,普通用户使用sudo就会以root
用户的身份来操作。这里将普通用户加上sudo功能是在/etc/sudoers
中配置。WSL2 Ubuntu
实例创建过程中默认将普通创建用户加入了sudoers
中。除此之外,还可以将用户加入某个组下,赋予组执行某个进程免密的设置。
#这里的$USER会解析为当前用户
sudo usermod -aG docker $USER
docker进程在sudoers
中的免密配置如下:
# sudo visudo, 添加下面
%docker ALL=(ALL) NOPASSWD: /usr/bin/dockerd
docker
属组可能是安装docker
的命令创建的,如果访问/etc/group
没有docker
属组,可以用命令sudo groupadd -g 999 docker
。
实际上这里还存在一个困惑,在Ubuntu系统中赋予一个普通用户加入某个属组,该属组可以设置免密以root用户身份操作某个命令。如果命令涉及到数据写入可能会报错
Permission denied
。比如git
,设置完成会后要使用没问题还需要设置:sudo chown -R $USER:$USER $GIT_REPO
。查了一下,原因可能是上面的设置确实是以root
用户来执行命令,但是命令产生的结果会触发新的进程就是以普通用户执行了,所以要对目录有权限。
设置一个docker socket使用的目录,设置权限让docker
属组有权限写入:
DOCKER_DIR=/mnt/wsl/shared-docker
mkdir -pm o=,ug=rwx "$DOCKER_DIR"
chgrp docker "$DOCKER_DIR"
设置dockerd
启动参数,编辑/etc/docker/daemon.json
,配置如下:
{
"hosts": ["unix:///mnt/wsl/shared-docker/docker.sock"],
"insecure-registries":["a.b.com:5000", "m.n.com:9500"]
}
如果没有对应的
/etc/docker/daemon.json
文件,可以手动创建。参数中的insecure-registries
设置表示允许不安全的http
镜像registry,有需要时候可以再配置。
使用命令sudo dockerd
就可以启动了。但是这种方式启动的dockerd
,我们要启动一个容器需要这样:
docker -H unix:///mnt/wsl/shared-docker/docker.sock run --rm hello-world
我们可以用脚本启动dockerd
,使用起来会更方便。
启动脚本:
DOCKER_DISTRO="Ubuntu-20.04"
DOCKER_DIR=/mnt/wsl/shared-docker
DOCKER_SOCK="$DOCKER_DIR/docker.sock"
export DOCKER_HOST="unix://$DOCKER_SOCK"
if [ ! -S "$DOCKER_SOCK" ]; then
mkdir -pm o=,ug=rwx "$DOCKER_DIR"
chgrp docker "$DOCKER_DIR"
/mnt/c/Windows/System32/wsl.exe -d $DOCKER_DISTRO sh -c "nohup sudo -b dockerd < /dev/null > $DOCKER_DIR/dockerd.log 2
将脚本保存在docker-service
放在path
下,本示例放入/usr/bin
中。执行docker-service
即可启动dockerd
。
本WSL2 Ubuntu
使用的默认sh
是zsh
,设置WSL2
开机启动dockerd
可以在/etc/zsh/zprofile
添加source /usr/bin/docker-service
启动脚本:
win上访问WSL文件系统
在wsl实例上执行explorer.exe .
,或者直接打开Windows资源管理器,输入:\\wsl$\Ubuntu-20.04\opt
。如下图:
这种方式打开wsl文件系统目录,Windows用户对WSL
普通用户数据有读写权限,对root用户是只读权限。并且使用这种方式打开文件修改并保存很卡顿,性能有问题。所以,不建议在Windows系统上修改wsl实例文件系统的内容。
WSL上访问win文件系统
WSL2
将Windows的磁盘逻辑卷映射为挂载文件。/mnt/c
为Windows C盘挂载路径;/mnt/d
为Windows D文件挂载路径等如此这种。如果要在WSL
上用vim
打开Win系统上D:\out.txt
,可以直接使用:vim /mnt/d/out.txt
。
在WSL
上操作Windows
文件系统同样会有一定的性能问题,但是没有Windows
操作WSL
文件系统性能延迟严重。如果是像大一点的git仓库,最好克隆到WSL
本地文件系统操作。
WSL
是直接能够读取Windows
系统PAHT
变量并在wsl上直接打开,比如在WSL
上打开ping.exe
:
有些时候这个功能是很有用的,尤其是当你打开图形化应用的时候。比如用
notepad++
打开WSL
系统中的一个文本文件等等。记得Win系统PATH变量中的exe程序才能在WSL中找到。
当然了,在Windows
系统上也能够执行WSL
命令,如在PowerShell
中执行wsl ping
:
二者的命令还可以组合使用,例如:在PowerShell
中执行:wsl ls -la | findstr "git"
。更多请参考:https://docs.microsoft.com/en-us/windows/wsl/filesystems
WSL和你的Windows主机已经互通了,如果能够通过ssh访问WSL,那么就相当于ssh能访问Windows。
基本的配置思路:
配置WSL sshd服务
#修改配置文件
#vim /etc/ssh/sshd_config
#设置需要认证
PasswordAuthentication yes
#设置登录用户,这里是WSL实例用户
AllowUsers ${wsl_yourusername}
#server会60s自动发送信号到Client,Client没有回应会记录下来,达到ClientAliveCountMax的次数
ClientAliveInterval 60
# 600分钟后断开连接
ClientAliveCountMax 600
启动sshd
#查看ssh状态
service ssh status
#启动服务
sudo service start
#重启
sudo service ssh --full-restart
ssh端口映射到Windows主机上
netsh interface portproxy reset
$wsl_ip = (wsl hostname -I).trim().split()[0]
Write-Host "WSL Machine IP: ""$wsl_ip"""
netsh interface portproxy add v4tov4 listenport=${Windows主机端口} connectport=22 connectaddress=$wsl_ip
更多端口映射理解请参考:WSL动态IP,如何从外部访问?
现在就可以通过ssh客户端命令ssh -p ${Windows主机端口} ${wsl_yourusername}@${Windows主机IP}
登录WSL实例了,例如:ssh -p 2222 user@xx.22.28.22
报错:请启用虚拟机平台 Windows 功能并确保在 BIOS 中启用虚拟化。
因为之前在使用VirtualBox时候关闭了Hyper-v
服务。设置自动开启Hyper-v模式:bcdedit /set hypervisorlaunchtype auto
。
Hyper-v服务启动后,需要重启计算机,WSL能正常启动。
重启后启动v2ray程序,发现can not bind to 10809
,但是通过netstat -ano|findstr 10809
没发现端口被占用。查找发现可能是Hyper-v程序预留的端口占用了,解决方法:
netsh int ip show dynamicport tcp #查看端口范围
netsh int ip set dynamicport tcp start=49152 num=16384 #设置新的端口范围,重启计算机
WSL2
的docker上运行基于centos6
镜像的容器有问题,解决办法:在%userprofile%\.wslconfig
上添加:
[wsl2]
kernelCommandLine = vsyscall=emulate
在配置文件%userprofile%\.wslconfig
配置如下内容:
memory=6GB
swap=1GB
localhostForwarding=true
processors=4