Docker@wsl
docker是什么?
docker相对于虚拟机是更新一代的虚拟化技术
Docker和虚拟机的区别? - 知乎 (zhihu.com)
基础设施就是笔记本电脑
主操作系统就是windows11
Hypervisor虚拟机管理系统比如windows的Hyper-V,还有VMWare,VritualBox等等
从操作系统就是虚拟机,比如VMWare上的windows server 2003,又如Hyper-V上的WSL.
每个从操作系统都占有独立的内存,CPU等硬件资源.这体现在VMware虚拟机或者WSL运行前可以设置使用CPU数量,内存大小等待
在Docker中,Docker守护进程取代了虚拟机管理类系统,docker是运行在操作系统之上的后台进程,管理docker容器
所有Docker容器都经过Docker守护进程使用CPU,内存等硬件资源.而守护进程是主操作系统的一个后台进程,受主操作系统的调度.
简单的说,docker就是更先进的更轻量级的虚拟技术?
docker四个核心组件
docker的四个核心组件:
客户端与服务器
镜像
仓库
容器
C/S体系
docker可以作为工作站使用,也可以作为客户端使用.
就是计网中的C/S模型
docker镜像
"一个docker镜像可以包含一个完整的ubuntu操作系统环境,里面仅安装了Apache或用户需要的其他应用程序"
可以类比.exe文件,exe在磁盘中躺着的时候,就是一个文件.但是加载进入内存之后就成了进程映像.
docker镜像和.exe文件一个性质,而docker容器和进程映像一个性质
docker镜像是只读的,这好理解,就好比一个已经编译链接生成的.exe文件也是只读的一个道理.因为它就没有被改变的理由.如果要"修改"docker镜像,只能是重新生成或者制作
docker容器
容器是从镜像创建的运行实例.
镜像是一个静态概念,而容器就是一个动态概念
docker中的概念 | 目标文件中的概念 |
---|---|
docker镜像 | 文件系统中的.exe文件 |
docker容器 | 内存中的进程映像 |
到此不禁要考虑一个问题,如果本次在docker容器中写了一个test.txt文件并保存,
关机之后,下次再启动这个docker容器,这个test.txt还存在吗?如果存在,显然是保存在了磁盘中,保存在那里了?docker镜像中吗?还是主操作系统中?
如果导出这个容器,制作一个新镜像,然后新镜像导入成其他电脑的docker容器,那么这个容器中还有test.txt吗?
实际上docker在镜像上面还有一可写入层,镜像被多个容器共用,每个容器创建后私自的改动写入自己的可写入层
docker仓库
集中存放docker镜像文件的地方
仓库的概念可以类比git,有本地的git仓库,也有远程的公共仓库服务器github,gitee
docker概念 git概念 docker镜像 仓库中的文件 docker本地仓库 git本地仓库 docker远程仓库 github中的仓库 docker注册仓库服务器 github
四组件关系
现阶段用不到Dockerfile,甚至Docker仓库也只是白嫖一下官方的镜像,上传什么的以后再说
wsl上使用docker
ubuntu wsl上的docker安装见
换docker下载源
在使用docker之前,先把下载源换成国内源,会方便很多
如果梯子流量管够并且梯子够快,当然不用换下载源.
这里换元不是指wsl的apt命令下载源,也不是指docker中容器的apt命令下载源.
而是指docker从哪里拉取镜像
在wsl中vim /etc/docker/daemon.json
然后添加下载源
1 | { |
保存后重启docker服务
1 | service docker restart |
docker命令
docker基本命令
版本信息
1 | docker version |
1 | root@Executor:/home/docker# docker version |
系统信息
1 | root@Executor:/home/docker# docker info |
docker镜像的使用
查找注册仓库中的镜像
1 | docker search <镜像> |
1 | root@Executor:/home/docker# docker search httpd |
其中STARS表明该镜像的权威性
OFFICIAL表明该镜像是否为官方镜像
从仓库拉取镜像
要私人定制一个花里胡哨的ubuntu镜像,首先得有一个干净的基础镜像
1 | docker pull <仓库名>:<标签> |
这里仓库名一般就是操作系统名比如ubuntu,标签就是操作系统版本比如20.04
因为docker仓库一般以操作系统命名,其中的镜像文件一般以对应操作系统版本号命名
1 | root@Executor:~# docker pull ubuntu:20.04 |
查看本地镜像列表
1 | root@Executor:~# docker images |
栏目 | 意义 |
---|---|
RESPOSITORY | 镜像仓库源 |
TAG | 镜像标签 |
IMAGE ID | 镜像ID |
CREATED | 镜像创建时间 |
SIZE | 镜像大小 |
可以使用仓库源:镜像标签
来指定一个唯一的镜像,也可以直接使用镜像ID指定一个唯一的镜像
删除本地镜像
1 | docker rmi <镜像> |
这里镜像或者是仓库原:镜像标签指定,或者是镜像ID指定
甚至不用输全信息就可以指定唯一一个镜像
比如只输入镜像ID的前两位或者前三位
1 | root@Executor:~# docker images |
这里使用ubuntu:18.04
镜像的ID号删除,只需要指定前2位
正在被引用的镜像不能被删除(该镜像有运行容器时不能删除)
创建镜像
1 | docker commit |
在一个ubuntu20.04的容器中(不是wsl中,是wsl的docker容器中)安装ssh之后创建镜像,然后使用新镜像创建容器,看看ssh还有没有
1 | root@Executor:/home/docker# docker commit -m="ssh" -a="dustball" 49e5c3d8f7c6 ssh/ubuntu |
-m提交描述信息
-a指定作者
ssh/ubuntu:镜像名称
sha256码表明镜像创建完毕,使用docker images
观察本地所有镜像
1 | root@Executor:/home/docker# docker images |
新镜像ssh/ubuntu显然比安装ssh之前的镜像ubuntu大好多
用新镜像创建容器并尝试运行ssh,发现是有的
1 | root@Executor:/home/docker# docker run -it ssh/ubuntu bash |
1 | patchelf --set-interpreter /home/kali/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --set-rpath /home/kali/glibc-all-in-one/libs/2.23-0ubuntu3_amd64 target_file |
1 | $ patchelf --set-interpreter /home/kali/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --set-rpath /home/kali/glibc-all-in-one/libs/2.23-0ubuntu3_amd64 test |
docker容器的使用
创建/删除容器
创建容器
1 | docker create [启动选项] <镜像> [命令行] |
比如创建一个终端上交互启动,默认自动执行/bin/bash命令的ubuntu:20.04容器
1 | root@Executor:~# docker create -t -i ubuntu:20.04 /bin/bash |
已经创建但是从未运行过的容器,其状态为"Created"
这里命令行的作用是,规定容器启动后要自动执行的命令
/bin/bash
意思是启动后立刻执行/bin/bash
这条命令,如果之前默认的shell是bash则无所谓,如果之前默认的shell是sh,tsh等等其他shell,则改为使用bash
关于"启动选项"和"命令行参数"
一定要分清两个东西,前者规定的是如何启动容器,后者规定的是启动后容器立刻自动干啥
删除容器前提是该容器没有被运行
1 | docker rm <容器> |
运行状态的容器需要强制删除
1 | docker rm -f <容器> |
删除所有容器
1 | root@Executor:~# docker rm $(docker ps -a -q) |
或者使用docker container prune
查看所有容器
1 | docker ps -a |
1 | root@Executor:~# docker ps -a |
栏目 | 意义 | 备注 |
---|---|---|
CONTAINER ID | 容器 | 注意不是镜像id,本地所有容器都有一个自己独特的id,用于区分和索引 |
IMAGE | 镜像 | 容器是从哪个镜像创建的 |
COMMAND | 命令行 | create创建或者run创建并启动时的命令行参数 |
CREATED | 创建时间 | 使用docker run创建该容器的时间 |
STATUS | 状态 | created(已创建) restarting(重启中) running(运行中) removing(迁移中) paused(暂停) exited(停止) dead(死亡) |
PORTS | 端口 | 容器的端口信息和使用的连接类型(tcp) |
NAMES | 名称 | Docker自动给每个容器都起一个名字,格式是:形容词__名 awesome_austin festive_lamarr ... 如果要人为给容器起名字,需要在docker run的命令行上加上 --name=<名字> |
运行/停止容器
容器用create创建之后不会执行,需要start命令执行
1 | docker start [启动选项] <容器> |
启动选项要么是-a(attach)
,要么是-i(interactive)
对于stop
(或者kill
)指令,其停止选项可以是多长时间之后停止
关于"启动选项"和"命令行参数"
一定要分清两个东西,前者规定的是如何启动容器,后者规定的是启动后容器立刻自动干啥
ps -a列出的COMMAND栏目,这是docker create时指定的命令行参数,它告诉容器启动后自动做什么
而docker start的启动选项只有两种,-a或者-i,区别是:
-a只会将容器的标准输出绑定到当前终端,
docker start -a <容器>
执行后,这时候可以看到容器的输出root@容器id
但是尝试在终端输入什么命令,都不会有结果,但是终端也没有阻塞,可以一直输入.
这是因为-a启动的容器,其标准输入并没有绑定到终端,因此我们在终端的任何输入都没有发往容器.
也就是说我们可以从终端上接收docker容器的输出,但是没法给他输入
这貌似很奇怪,因为没有输入哪来的输出呢?
实际上可以在创建容器的时候指定命令行参数,让容器启动就自动执行一些命令,此时这些命令的输出就可以打印到终端
比如创建一个在后台自动每1秒执行一次打印"helloworld"的容器,然后用
-a
启动参数启动它,就可以看到它一直向终端输出helloworld
了
1
2
3
4
5
6 root@Executor:~# docker create ubuntu:20.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
167d2e45de172117166431467875f3e44f81a29fbaa1e786fecf2caf77227d25
root@Executor:~# docker start -a 167
hello world
hello world
...-i会将容器的标准输入绑定到当前终端
使用docker start
默认后台运行容器,该命令执行后终端环境还是在wsl中,相应容器已经在后台运行
如果想要让容器在前台与我们交互,可以使用-i
启动选项(前提是,create创建容器的时候有加入-it选项,否则start使用-i选项无效)
即:
使用docker create -it <容器>
这样创建的容器,使用docker start <容器>
会在后台运行(即使没有长期任务也会一直运行),使用docker start -i <容器>
会在前台交互.
使用docker create <容器>
这样创建的容器,最多可以docker start -a <容器>
绑定输出(前提是create时命令行上有长期的任务),但是docker start -i <容器>
无效,如果创建容器时命令行上没有给容器指定一个长期的任务,那么后台运行的容器会立刻停止
创建+运行容器
1 | docker run [OPTIONS] IMAGE [COMMAND] [ARG...] |
其作用相当于
1
2 docker create [OPTIONS] <镜像> [COMMAND][ARG...]
docker start [OPTIONS] <容器>
OPTIONS是docker start 的启动选项啊
COMMAND是docker create的命令行参数
一定要分清两者
1 | docker run -t -i <镜像> /bin/bash |
这里镜像可以是镜像id,也可以是仓库源:镜像标签
-it一起用,意思是让docker分配一个伪终端,并绑定到容器的标准输入
说人话-t作用是加上
root@容器号:
这种命令提示,不用-t,只有-i也可以进行命令交互,但是不会有这种提示后面命令行/bin/bash意思是,启动后自动运行bash(虽然默认也是运行的bash,但是可能有些容器默认运行sh),使用bash这个shell进行交互
1 | root@Executor:~# docker run -t -i ubuntu:20.04 |
可以发现主机名已经由Executor
编变成了容器的id号,现在的终端已经是docker内部环境了
这里容器id和镜像id不同,本地的每个容器都有一个不同的id
如果要退出容器到wsl,只需要在docker容器的终端中输入exit
1 | root@Executor:~# docker run -t -i ubuntu:20.04 |
主机名又变回Executor说明目前已经在wsl中了
Exit命令执行后,该docker容器立刻停止.后来如果还想继续使用则需要在wsl中使用docker start命令
创建容器时Docker干了啥
单从"桥接一个虚拟接口到容器",就知道物理机和docker容器在同一个网段
后台运行
刚才已经知道,直接用docker run -t -i ubuntu:20.04
这种命令创建容器时,创建容器后终端环境是容器中环境,主机名会变成docker容器号
而现在只想让docker容器运行,但是终端依然是wsl的环境,不进入docker容器,应该咋整呢?
1 | docker run -d <镜像> <命令行参数> |
1 | root@Executor:~# docker run -d -it ubuntu:20.04 |
可以发现,run -d
创建容器并运行之后,终端上还是显示root@Executor
表明仍然在wsl中,没有进到容器里
相当于docker create -it <容器>
然后docker run <容器>
进入/退出容器
对于正在后台运行的容器,如果想要进入容器与之交互,需要使用
1 | docker attach [选项] <正在运行的容器> [命令行] |
两者的区别是,
使用attach进入容器,在容器中用exit退出时,容器会停止运行
使用exec进入容器,在容器中用exit退出时,容器会继续运行
由于在wsl中可以使用docker stop
命令停止容器,因此只使用exec
命令即可
1 | root@Executor:~# docker ps -a |
退出容器使用exit
,或者ctrl+d
导出/导入容器
将容器导出成tar包
1 | docker export <容器> > <位置> |
比如将49e5c3d8f7c6
这个容器(已安装ssh)导出到当前目录
1 | root@Executor:/home/docker# docker export 49 > ssh-ubuntu.tar |
将tar包导入成镜像(不能直接导入成容器)
1 | docker import [OPTIONS] <tar包> <镜像> |
将tar包导入为指定名称的镜像
1 | root@Executor:/home/docker# ls |
后面使用该镜像只需要docker run -it <镜像> bash
docker网络
参考
Docker 网络模式详解及容器间网络通信 - 知乎 (zhihu.com)
Docker:网络模式详解 - Gringer - 博客园 (cnblogs.com)
四个模式
host
docker容器与wsl共用ip地址,docker服务直接使用wsl的端口地址空间,外界可以通过ip:port方式访问docker容器
none
无网络链接
bridge
桥接模式,安装Docker时会为wsl创建一个叫做docker0的虚拟网桥
这个网桥只对桥接的docker容器还有wsl本身可见,作用是wsl和桥接容器之间的通信
两个接到同一网桥的容器或者容器和网桥之间均可以相互通信
对于容器来说,wsl上的网桥就相当于一个交换机
wsl:
1 | root@Executor:~# ifconfig |
容器1:
1 | root@2c98c979b54a:/# ifconfig |
容器2:
1 | root@7cf02bb435be:/var/www/html# ifconfig |
wsl的docker0网卡,两个容器的eth0网卡,其IP地址都是172.17.0.X
(实际上从子网掩码看出该网段的IP地址可以是172.17.X.X
)
docker的桥接和虚拟机的桥接不同
虚拟机和主机桥接之后,虚拟机,主机,主机连接的路由器,都在同一网段
docker容器和docker主机的桥接,所有docker容器和docker主机的docker0网卡在同一网段,docker主机的另一张网卡eth0和路由器在同一网段
docker容器和docker0网桥相连使用了veth pair技术,每个容器的eth0都与docker0上的一个veth*(一个数字)配对,两个虚拟网卡组成数据通道,从一个进去必然从另一出来
说是"桥接",实际上相当于docker主机进行了一个NAT变换
所有容器的数据通过自己的虚拟网卡eth0发往docker主机的虚拟 网卡docker0,然后docker主机用"外网"网卡eth0上网(这里外网是相对于docker0虚拟网桥而言的,当然相对于路由器来说eth0还是"内网")
container
两个容器共享同一网络空间,包括ip地址和端口号
目前用不到,用到时再说
docker网络相关命令
1 | docker network ... |
查看网络模式
1 | docker network list |
列出三个默认的网络模式
1 | root@Executor:~# docker network ls |
目前只用到docker network list
命令,其他需要以后再说
配置apache2+php环境
由于官方的镜像不能满足要求,我需要修改官方镜像创建满足我们要求的镜像
容器内基本配置
首先要从官方的镜像改造一个满足我们要求的镜像
这需要经历一个官方镜像->改造容器->导出新镜像
的过程
拉取apache+php镜像
1 | docker pull php:7.0-apache |
创建临时容器
说他是"临时容器",是因为我们需要用这个基础镜像创建容器进行一些改造,然后生成我们需要的镜像
1 | docker run -it php:7.0-apache bash |
换docker容器的apt下载源
备份
备份原来的下载源
1 | /etc/apt/sources.list /etc/apt/sources.list.bak |
更新
更新下载源,由于此时没有vi没有vim,只能用cat等shell命令完成
换元之前先看一下容器的操作系统版本
1
2 root@06ca8ea43c1c:/var/www/html# cat /etc/issue
Debian GNU/Linux 9 \n \l然后上网搜对应版本的国内源进行更换
适用于Debian GNU/LINUX 9
的阿里云源:
1 | cat > /etc/apt/sources.list << EOF |
配置网站
在该容器的网站根目录/var/www/html
下面,写一个info.php
,打印php信息作为测试
1 | phpinfo(); |
在系统根目录下写一个/start.sh
1 | /etc/init.d/apache2 restart |
第一条作用是启动apache2服务器,第二条作用是,让执行这条命令的容器一直有前台进程在忙,防止docker容器自动停止
安装ssh等其他可有可无的服务...
导出镜像
1 | docker commit <容器> apache/ubuntu:latest |
将该容器导出为一个叫"apache/ubuntu:latest"的容器
从新镜像创建新容器
现在使用docker run
命令,从新apache/ubuntu:latest
镜像上创建并启动新容器
1 | docker run -dit -p 10011:80 apache/ubuntu:latest /bin/bash -c "/start.sh" |
-d后台运行
-it启动交互终端
-p 10011:80,wsl的10011端口映射到容器的80端口
apache/ubuntu:latest我们自定义的镜像名
/bin/bash -c "/start.sh"命令行参数,启动后立刻自动执行
没有指定网络模式默认为桥接模式
没有指定端口上的传输层服务类型默认为TCP服务
1 | root@Executor:~# docker run -dit -p 10011:80 apache/ubuntu /bin/bash -c "/start.sh" |
此时新容器就创建好了,可以看到PORTS栏目里面,wsl的10011端口已经映射给了docker的80端口,其上运行了一个tcp服务
访问容器
怎么访问这个容器呢?
访问这个容器就是访问wsl的10011号端口,可以使用ip:port
方式进行访问
在wsl上使用ifconfig看一下wsl的ip地址是多少
1 | root@Executor:~# ifconfig |
发现wsl在windows主机NAT后面的ip地址是172.31.174.19
虽然wsl和windows主机之间的网络模式是NAT模式,与windows主机同路由器局域网内的其他设备是无法访问wsl的,
但是windows主机是可以访问wsl的,windows主机相对于wsl就是"外网环境",我们只需要在windows主机浏览器上访问172.31.174.19:10011
或者windows主机上curl 172.31.174.19:10011/info.php
可以看见,windows主机是可以访问docker容器的apache2服务的
站在windows主机的角度,怎样看出这是一个docker容器提供的apache2服务,而不是wsl提供的apache2服务呢?
从该phpinfo的回显结果可以隐约看出这是一个docker容器提供的服务
首先这个System名,谁会起一个edc1b61f6c7a作为主机名啊?这个数是一个docker容器id
1
2
3 root@Executor:~# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
edc1b61f6c7a apache/ubuntu "docker-php-entrypoi…" 12 minutes ago Up 11 minutes 0.0.0.0:10011->80/tcp, :::10011->80/tcp friendly_northcutt其次,后面的configuration->apache2handler中
这里ip地址和端口号均与wsl不一样,如果是wsl直接提供apache2服务,这里应是
172.31.174.19:80
1 | deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse |