从WSL2到docker
从WSL2到docker
首先参考官方文档。
[适用于 Linux 的 Windows 子系统文档 | Microsoft Docs](https://docs.microsoft.com/zh-cn/windows/wsl/) |
从微软的介绍来看,WSL是在NT内核上做了关于linux内核调用的转译,让两种不同生态的程序跑在一个地址空间里面,资源也是共享的,优点是能使用大部分linux下的命令行工具,缺点就是转译太复杂,而且几乎没办法提供linux内核的那些高级功能,比如namespace,cgroup,selinux,netfilter等等;而WSL2采用虚拟化的技术,开启windows的hyper-v虚拟化之后,windows NT内核和linux内核都是运行在虚拟层上,优点是拥有完整的linux内核,缺点是网卡地址跟Windows不共享,内存占用更大。
共同的问题:磁盘io性能差,修改不同系统的文件还会遇到权限问题。无论怎样都不推荐Linux操作Windows文件,以及Windows操作Linux文件。在wsl中,windows的c,d,e等都是挂载在/mnt目录下,宁可通过cp命令复制压缩包,也不要直接操作相互的文件。
WSL1变更为WSL2
可打开 PowerShell 命令行并输入以下命令(仅在 Windows 内部版本 18362 或更高版本中可用),检查分配给每个已安装的 Linux 分发版的 WSL 版本:wsl -l -v
PowerShell复制
wsl --list --verbose
若要将分发版设置为受某一 WSL 版本支持,请运行:
PowerShell复制
wsl --set-version <distribution name> <versionNumber>
请确保将 <distribution name>
替换为你的分发版的实际名称,并将 <versionNumber>
替换为数字“1”或“2”。 可以随时更改回 WSL 1,方法是运行与上面相同的命令,但将“2”替换为“1”。
此外,如果要使 WSL 2 成为你的默认体系结构,可以通过此命令执行该操作:
PowerShell复制
wsl --set-default-version 2
这会将安装的任何新分发版的版本设置为 WSL 2。
WSL2的网络问题
使用WSL2的时候,有时会出现以下以下异常。
参考的对象类型不支持尝试的操作。(The attempted operation is not supported for the type of object referenced.)
这时候需要以管理员身份打开PowerShell,然后执行以下命令,提示必须重启,实际上不需要重启。 此问题是vpn软件导致的。
netsh winsock reset
关于使用WSL2出现“参考的对象类型不支持尝试的操作”的解决方法。 - 知乎 (zhihu.com)
WSL 2 上的 Docker 容器
Docker 是一种工具,用于创建、部署和运行应用程序(通过使用容器)。 容器使开发人员可以将应用与需要的所有部件(库、框架、依赖项等)打包为一个包一起交付。 使用容器可确保此应用的运行与之前相同,而不受任何自定义设置或运行该应用的计算机上先前安装的库的影响(运行应用的计算机可能与用于编写和测试应用代码的计算机不同)。 这使开发人员可以专注于编写代码,而无需操心将运行代码的系统。
Docker 容器与虚拟机类似,但不会创建整个虚拟操作系统。 相反,Docker 允许应用使用与运行它的系统相同的 Linux 内核。 这使得应用包能够仅要求主计算机上尚未安装的部件,从而降低包大小以及提高性能。
官网安装方式
地址[Install Docker Desktop on Windows | Docker Documentation](https://docs.docker.com/docker-for-windows/install/) |
值得注意的一点是,无论是基于WSL2还是基于hyper-V,都要启用windows的虚拟化功能,即docker需要一个linux内核环境。一般的,现阶段所说的docker容器都是指基于linux内核的容器。所以,并不能在windows-nt内核上安装docker容器,也不能在docker容器里构建一个windows-nt内核的镜像。docker关于windows容器的功能还是处于实验性阶段。
labs/windows/windows-containers at master · docker/labs · GitHub
相对应的,微软类似的Windows Containers,只是应用不多。
docker 共享内核
Docker容器本质上是宿主机上的进程。Docker通过namespace实现了资源隔离,通过cgroups实现了资源限制,通过写时复制机制(copy-on-write)实现了高效的文件操作。
linux内核实现namespace的一个主要目的,就是为了实现轻量级虚拟化(容器)技术服务。在同一个namespace下的进程可以感知彼此的变化,而对外界的进程一无所知。这样就可以让容器中的进程产生错觉,仿佛自己置身一个独立的系统环境中,以达到隔离的目的。
意味着,Docker从不使用其他内核:内核始终是宿主机内核。如果宿主机内核与要运行的容器中的软件“足够兼容”,则它将起作用,否则就不会。
docker基础镜像
FROM scratch
官方说明:该镜像是一个空的镜像,可以执行二进制文件或者用于构建busybox等超小镜像,可以说是真正的从零开始构建属于自己的镜像,或者执行。要知道,一个官方的ubuntu镜像有60MB+,CentOS镜像有70MB+
注意:scratch不可用被pull
FROM scratch专门用于构建最小镜像,直接pull会报以下错误,scratch是一个保留名称
docker pull scratch
Using default tag: latest
Error response from daemon: 'scratch' is a reserved name
如何制作大小为0 的镜像
既然scratch不能被拉取,如何做到docker image ls
看到一个0字节的镜像
官方给出了下面方法:
tar cv --files-from /dev/null | docker import - scratch
docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
scratch latest 775bfce21429 9 minutes ago 0B
改写官方的helloword
hello.c
cat hello.c
//#include <unistd.h>
#include <sys/syscall.h>
#ifndef DOCKER_GREETING
#define DOCKER_GREETING "Hello from Docker!"
#endif
const char message[] =
DOCKER_GREETING "\n";
void _start() {
//write(1, message, sizeof(message) - 1);
syscall(SYS_write, 1, message, sizeof(message) - 1);
//_exit(0);
syscall(SYS_exit, 0);
}
编译
bash: gcc -static -Os -nostartfiles -fno-asynchronous-unwind-tables -o './hello' 'hello.c'
bash: strip -R .comment -s 'hello'
bash: ./hello
Hello from Docker!
dockerfile
CopyFROM scratch
COPY hello /
CMD ["/hello"]
构建运行
bash: docker build -t hello .
bash: docker image ls hello
REPOSITORY TAG IMAGE ID CREATED SIZE
hello latest 3b204a40c8cb 14 seconds ago 1.06kB
bash: docker run --rm hello
Hello from Docker!
制作基础的debian镜像
新建一个文件夹,用wget下载rootfs.tar.xz压缩包,包含了操作系统大部分指令。
wget -O rootfs.tar.xz https://github.com/debuerreotype/docker-debian-artifacts/raw/b024a792c752a5c6ccc422152ab0fd7197ae8860/jessie/rootfs.tar.xz
新建一个dockerfile文件
FROM scratch
# add and unpack an archive that contains a Debian root filesystem
ADD rootfs.tar.xz /
dockerfile文件的编写
Dockfile是一种被Docker程序解释的脚本,Dockerfile由一条一条的指令组成,每条指令对应Linux下面的一条命令。Docker程序将这些Dockerfile指令翻译真正的Linux命令。Dockerfile有自己书写格式和支持的命令,Docker程序解决这些命令间的依赖关系,类似于Makefile。Docker程序将读取Dockerfile,根据指令生成定制的image。相比image这种黑盒子,Dockerfile这种显而易见的脚本更容易被使用者接受,它明确的表明image是怎么产生的。有了Dockerfile,当我们需要定制自己额外的需求时,只需在Dockerfile上添加或者修改指令,重新生成image即可,省去了敲命令的麻烦。
官方文档:[Dockerfile reference | Docker Documentation](https://docs.docker.com/engine/reference/builder/) |
指令 | 说明 |
---|---|
FROM | 设置镜像使用的基础镜像 |
RUN | 编译镜像时运行的脚本 |
CMD | 设置容器的启动命令 |
EXPOSE | 设置镜像暴露的端口 |
ENV | 设置容器的环境变量 |
ADD | 编译时复制文件到镜像中(可解压缩) |
COPY | 编译时复制文件到镜像中 |
ENTRYPOIN | 设置容器的入口程序 |
WORKDIR | 设置RUN CMD ENTRYPOIN COPY ADD指令的工作目录 |
一个centos+jdk+tomcat+war包dockerfile示例:
FROM centos
ADD jdk-8u251-linux-x64.tar.gz /usr/local/
ADD apache-tomcat-8.5.56.tar.gz /usr/local/
RUN mv /usr/local/apache-tomcat-8.5.56 /usr/local/tomcat8
COPY springboot.war /usr/local/tomcat8/webapps/
WORKDIR /usr/local/tomcat8/conf/
RUN sed -i 's|"8080"|"80"|' server.xml
ENV MYPATH /usr/local
WORKDIR $MYPATH
#配置jdk 和tomcat环境变量
ENV JAVA_HOME /usr/local/jdk1.8.0_251
ENV CATALINA_HOME /usr/local/tomcat8
ENV CATALINA_BASE /usr/local/tomcat8
ENV CLASSPATH $JAVA_HOME/lib/
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin
#运行tomcat
EXPOSE 80
# 设置时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone
CMD tomcat8/bin/startup.sh && tail -f tomcat8/logs/catalina.out
然后,可以使用docker build -t springboot . 编译和docker run 命令启动,结合docker desktop使用更方便。值得注意的时,容器内要保存数据的话,需要挂一个宿主机目录到容器。
[Manage data in Docker | Docker Documentation](https://docs.docker.com/storage/) |
参考:
-
指令:FROM 功能描述:设置基础镜像 语法:FROM < image>[:< tag> | @< digest>] 提示:镜像都是从一个基础镜像(操作系统或其他镜像)生成,可以在一个Dockerfile中添加多条FROM指令,一次生成多个镜像 注意:如果忽略tag选项,会使用latest镜像
-
指令:MAINTAINER 功能描述:设置镜像作者 语法:MAINTAINER < name>
-
指令:RUN 功能描述: 语法:RUN < command> RUN [“executable”,”param1”,”param2”] 提示:RUN指令会生成容器,在容器中执行脚本,容器使用当前镜像,脚本指令完成后,Docker Daemon会将该容器提交为一个中间镜像,供后面的指令使用 补充:RUN指令第一种方式为shell方式,使用/bin/sh -c < command>运行脚本,可以在其中使用\将脚本分为多行 RUN指令第二种方式为exec方式,镜像中没有/bin/sh或者要使用其他shell时使用该方式,其不会调用shell命令 例子:RUN source $HOME/.bashrc;
echo $HOMERUN [“/bin/bash”,”-c”,”echo hello”] RUN [“sh”,”-c”,”echo”,”$HOME”] 使用第二种方式调用shell读取环境变量
-
指令:CMD 功能描述:设置容器的启动命令 语法:CMD [“executable”,”param1”,”param2”] CMD [“param1”,”param2”] CMD < command> 提示:CMD第一种、第三种方式和RUN类似,第二种方式为ENTRYPOINT参数方式,为entrypoint提供参数列表 注意:Dockerfile中只能有一条CMD命令,如果写了多条则最后一条生效
-
指令:LABEL 功能描述:设置镜像的标签 延伸:镜像标签可以通过docker inspect查看 格式:LABEL < key>=< value> < key>=< value> … 提示:不同标签之间通过空格隔开 注意:每条指令都会生成一个镜像层,Docker中镜像最多只能有127层,如果超出Docker Daemon就会报错,如LABEL ..=.. <假装这里有个换行> LABEL ..=..合在一起用空格分隔就可以减少镜像层数量,同样,可以使用连接符\将脚本分为多行 镜像会继承基础镜像中的标签,如果存在同名标签则会覆盖假装这里有个换行>
-
指令:EXPOSE 功能描述:设置镜像暴露端口,记录容器启动时监听哪些端口 语法:EXPOSE < port> < port> … 延伸:镜像暴露端口可以通过docker inspect查看 提示:容器启动时,Docker Daemon会扫描镜像中暴露的端口,如果加入-P参数,Docker Daemon会把镜像中所有暴露端口导出,并为每个暴露端口分配一个随机的主机端口(暴露端口是容器监听端口,主机端口为外部访问容器的端口) 注意:EXPOSE只设置暴露端口并不导出端口,只有启动容器时使用-P/-p才导出端口,这个时候才能通过外部访问容器提供的服务
-
指令:ENV 功能描述:设置镜像中的环境变量 语法:ENV < key>=< value>…|< key> < value> 注意:环境变量在整个编译周期都有效,第一种方式可设置多个环境变量,第二种方式只设置一个环境变量 提示:通过${变量名}或者 $变量名使用变量,使用方式${变量名}时可以用${变量名:-default} ${变量名:+cover}设定默认值或者覆盖值 ENV设置的变量值在整个编译过程中总是保持不变的
-
指令:ADD 功能描述:复制文件到镜像中 语法:ADD < src>… < dest>|[“< src>”,… “< dest>”] 注意:当路径中有空格时,需要使用第二种方式 当src为文件或目录时,Docker Daemon会从编译目录寻找这些文件或目录,而dest为镜像中的绝对路径或者相对于WORKDIR的路径 提示:src为目录时,复制目录中所有内容,包括文件系统的元数据,但不包括目录本身 src为压缩文件,并且压缩方式为gzip,bzip2或xz时,指令会将其解压为目录 如果src为文件,则复制文件和元数据 如果dest不存在,指令会自动创建dest和缺失的上级目录
-
指令:COPY 功能描述:复制文件到镜像中 语法:COPY < src>… < dest>|[“< src>”,… “< dest>”] 提示:指令逻辑和ADD十分相似,同样Docker Daemon会从编译目录寻找文件或目录,dest为镜像中的绝对路径或者相对于WORKDIR的路径
-
指令:ENTRYPOINT 功能描述:设置容器的入口程序 语法:ENTRYPOINT [“executable”,”param1”,”param2”] ENTRYPOINT command param1 param2(shell方式) 提示:入口程序是容器启动时执行的程序,docker run中最后的命令将作为参数传递给入口程序 入口程序有两种格式:exec、shell,其中shell使用/bin/sh -c运行入口程序,此时入口程序不能接收信号量 当Dockerfile有多条ENTRYPOINT时只有最后的ENTRYPOINT指令生效 如果使用脚本作为入口程序,需要保证脚本的最后一个程序能够接收信号量,可以在脚本最后使用exec或gosu启动传入脚本的命令 注意:通过shell方式启动入口程序时,会忽略CMD指令和docker run中的参数 为了保证容器能够接受docker stop发送的信号量,需要通过exec启动程序;如果没有加入exec命令,则在启动容器时容器会出现两个进程,并且使用docker stop命令容器无法正常退出(无法接受SIGTERM信号),超时后docker stop发送SIGKILL,强制停止容器 例子:FROM ubuntu <换行> ENTRYPOINT exec top -b换行>
-
指令:VOLUME 功能描述:设置容器的挂载点 语法:VOLUME [“/data”] VOLUME /data1 /data2 提示:启动容器时,Docker Daemon会新建挂载点,并用镜像中的数据初始化挂载点,可以将主机目录或数据卷容器挂载到这些挂载点
-
指令:USER 功能描述:设置RUN CMD ENTRYPOINT的用户名或UID 语法:USER < name>
-
指令:WORKDIR 功能描述:设置RUN CMD ENTRYPOINT ADD COPY指令的工作目录 语法:WORKDIR < Path> 提示:如果工作目录不存在,则Docker Daemon会自动创建 Dockerfile中多个地方都可以调用WORKDIR,如果后面跟的是相对位置,则会跟在上条WORKDIR指定路径后(如WORKDIR /A WORKDIR B WORKDIR C,最终路径为/A/B/C)
-
指令:ARG 功能描述:设置编译变量 语法:ARG < name>[=< defaultValue>] 注意:ARG从定义它的地方开始生效而不是调用的地方,在ARG之前调用编译变量总为空,在编译镜像时,可以通过docker build –build-arg < var>=< value>设置变量,如果var没有通过ARG定义则Daemon会报错 可以使用ENV或ARG设置RUN使用的变量,如果同名则ENV定义的值会覆盖ARG定义的值,与ENV不同,ARG的变量值在编译过程中是可变的,会对比使用编译缓存造成影响(ARG值不同则编译过程也不同) 例子:ARG CONT_IMAG_VER <换行> RUN echo $CONT_IMG_VER ARG CONT_IMAG_VER <换行> RUN echo hello 当编译时给ARG变量赋值hello,则两个Dockerfile可以使用相同的中间镜像,如果不为hello,则不能使用同一个中间镜像换行>换行>
-
指令:ONBUILD 功能描述:设置自径想的编译钩子指令 语法:ONBUILD [INSTRUCTION] 提示:从该镜像生成子镜像,在子镜像的编译过程中,首先会执行父镜像中的ONBUILD指令,所有编译指令都可以成为钩子指令
-
指令:STOPSIGNAL 功能描述:设置容器退出时,Docker Daemon向容器发送的信号量 语法:STOPSIGNAL signal 提示:信号量可以是数字或者信号量的名字,如9或者SIGKILL,信号量的数字说明在Linux系统管理中有简单介绍
补充:ONBUILD流程
- 编译时,读取所有ONBUILD镜像并记录下来,在当前编译过程中不执行指令
- 生成镜像时将所有ONBUILD指令记录在镜像的配置文件OnBuild关键字中
- 子镜像在执行FROM指令时会读取基础镜像中的ONBUILD指令并顺序执行,如果执行过程中失败则编译中断;当所有ONBUILD执行成功后开始执行子镜像中的指令
- 子镜像不会继承基础镜像中的ONBUILD指令
补充:CMD ENTRYPOINT和RUN的区别
RUN指令是设置编译镜像时执行的脚本和程序,镜像编译完成后,RUN指令的生命周期结束
容器启动时,可以通过CMD和ENTRYPOINT设置启动项,其中CMD叫做容器默认启动命令,如果在docker run命令末尾添加command,则会替换镜像中CMD设置的启动程序;ENRTYPOINT叫做入口程序,不能被docker run命令末尾的command替换,而是将command当作字符串,传递给ENTRYPOINT作为参数
FROM ubuntu
ENTRYPOINT ["ps"]
//通过命令docker run --rm test启动容器,打印ps的输出
//通过命令docker run --rm test -ef启动容器,打印ps -ef的输出
在docker run中,可以通过–entrypoint替换镜像中的入口程序,在Dockerfile中,应该至少有一条CMD或者ENTRYPOINT指令,如果同时定义了CMD和ENTRYPOINT则CMD会作为参数传递给ENTRYPOINT
FROM ubuntu
ENTRYPOINT ["ps"]
CMD ["-ef"]
//通过命令docker run --rm test启动容器,打印ps -ef的输出
留下评论