原文
Windows
用户一般通过安装docker-desktop(点击下载),下载完成以后按照提示安装wsl2
,然后才可以正常启用Docker
,但是使用本镜像会导致出现container exit(139)
的错误。
在C:\Users\(用户名)中创建.wslconfig
以我为例,创建C:\Users\zhangsan\.wslconfig
文件中写入下面内容:
[wsl2]
kernelCommandLine = vsyscall=emulate
提示信息:
Docker.ApiServices.WSL2.WslKernelUpdateNotInstalledException: 引发类型为“Docker.ApiServices.WSL2.WslKernelUpdateNotInstalledException”的异常。
在 Docker.ApiServices.WSL2.WslShortLivedCommandResult.LogAndThrowIfUnexpectedExitCode(String prefix, ILogger log, Int32 expectedExitCode) 位置 C:\workspaces\PR-16070\src\github.com\docker\pinata\win\src\Docker.ApiServices\WSL2\WslCommand.cs:行号 140
在 Docker.Engines.WSL2.WSL2Provisioning.d__17.MoveNext() 位置 C:\workspaces\PR-16070\src\github.com\docker\pinata\win\src\Docker.Desktop\Engines\WSL2\WSL2Provisioning.cs:行号 169
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
解决方法:
打开网站 https://czf-net.xyz/res/
下载wsl.msi
一路Next
重启Docker解决!
然后在命令中执行docker -v
命令查看是否安装成功:
启动一个docker
镜像,使用如下命令:
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
OPTIONS说明:
举例:
以界面的形式运行:
docker run -it ubuntu /bin/bash ## 以界面bash界面的形式运行名称为ubuntu的镜像
或者你也可以在后台运行,使用-d
参数,运行成功则会返回一个CONTAINER_ID
:
在命令行中执行如下命令,快速搭建一个docker镜像:
docker run -d -p 80:80 docker/getting-started
然后在浏览器输入http://127.0.0.1,如果出现了如下的界面,则表示安装成功:
创建一个新的目录,结构如下:
每个文件的内容如下:
index.php
phpinfo();
docker-composr-dev.yml //开发环境的配置文件
version: "3" ## 版本号,必须是一个string类型的
services: ## 对应的服务
app-dev: ## 容器名,可可定义
build: ## 做的事情:在当前文件夹执行文件名为Dockerfile-dev的Dockerfile
context: .
dockerfile: Dockerfile-dev
volumes: ## 文件夹映射 格式:宿主机目录:容器目录(注意中间的冒号是固定格式)
- ./src:/var/www/html
ports: ## 端口映射
- 8080:80 # 宿主机端口:容器端口
Dockerfile-dev //开发环境的dockerfile
FROM php:apache
Dockerfile
FROM php:apache
COPY ./src /var/www/html
Docker-compose.yml
version: "3"
services:
app:
build: .
image: gcslaoli/docker-php
ports:
- 81:80
然后进入当前文件夹,执行如下命令:
docker-compose -f .\docker-compose-dev.yml up -d --build ## 启动开发环境
然后在浏览器中输入对应的网址即可:http://127.0.0.1:8080,如果出现了如下界面,则表示成功:
如何关闭开发环境容器呢,执行如下命令:
docker-compose -f .\docker-compose-dev.yml down ## 关闭开发环境
然后再通过docker ps -a
查看当前容器是否关闭:
也可以直接使用容器ID直接关闭,命令如下:
docker stop [CONTAINER_ID] ## 或者
参数:
-t
:关闭容器的限时,如果超时未能关闭则用kill强制关闭,默认值10s,这个时间用于容器的自己保存状态
docker stop -t=60 容器ID或容器名
查看容器ID命令如下:
docker ps -a ## 查看所有运行的容器,可以查看对应的容器ID
或者直接使用下面的命令,直接关闭:
docker kill [CONTAINER_ID]
发布docker
镜像到dockerHub
需要到docker官网注册一个账号,这就类似我们将代码push
到GitHub
上的动作类似。
注册完成之后,首先需要登录,如果你已经再docker Desktop
中登录了,那么在命令使用docker login
测试以下:
然后使用docker tag
命令,将需要push到dockerHub
的镜像进行规范化命名,规范如下:
docker push 注册用户名/镜像名
docker tag [OLD_NAME] [NEW_NAME] # 改名
# 以我为例
docker tag ubuntu-git crazyjums/ubuntu-git
我们将镜像push
到dockerHub
中,首先需要对其中的配置进行修改,否则push
就没有意义了,默认情况下,如果docker
镜像是不会保存当前的更改的,所以,如果需要保存一下自己对当前镜像的更改,之后再push
。
退出当前镜像之后,执行如下命令保存对当前镜像的更改:
docker commit [CONTAINER_ID] [NEW_NAME]
[root@49079ee590cc sbin]# exit
exit
PS C:\Users\user_name> docker commit 49079ee590cc centos3
sha256:5423bf497d8ec85d267726991e1655ade066939f898db7db3d8a4124d72fc241
使用如下命令将自己的镜像push
到dockerHub
中:
docker push crazyjums/ubuntu-git:latest # 后面的latest指的是版本号
这个push的动作根据你的网速的不同,时间也不一定,如果中途中断了,那么就继续,docker会保存已经上传的部分,如果继续会再上次断开的地方继续,不会重新上传,命令还是一样。如果上传完成,可以根据这个地址取网上找一下是否自己的镜像,地址规范:
docker.io/[注册用户名/镜像名]
push完成之后,可以登录自己的账号,既可以看到自己的镜像:
在使用docker之前你要明白两个概念,两个学docker过程中一定会一直强调的概念
这两个是整个docker的基础概念,这里本着不负责任的侥幸心理大概的说一下这两个的区别.
- image是静态的,类比为面向对象就是一个类
- container是动态运行的,类比为面向对象就是一个实例化的对象.
一般,container是可运行的,我们启动一个container之后,这个container里面就是我们的linux环境.
懂得了上面的意思,你就明白了我们要做的事情很简单:找一个合适的image,这个image里面应该包含一切开发时候所需要的东西, 然后启动它,我们就可以在这个container环境上工作了.当然这个时候container应该可以跟宿主共享文件.并且可以在本局域网内可以被访问到.
在继续搭建我们的开发环境之前,我们还是要先学一点docker的命令和概念的.
每个image都有一个唯一的id来标识,同样container也有。这个唯一的id一般很长,比如:c59dc2dfad95
,但是一般我们输入的时候只要输入若干位能标识当前系统内唯一标识某一个image就可以了。比如只要输入c59d
可能就可以标识这个image。除了id,还可以给一个image起名字,这样子也可以通过name来操作一个image。
查看容器的详细信息
docker inspect [CONTAINER_ID]
通过docker run image_name
可以直接启动本地的一个image。这个命令后面可以加很多子参数来开启其他功能。如果本地不存在这个image,那么docker会去官方的仓库去下载,这个仓库你可以理解为github一样的网站,上面存放了许多别人push上去的image。
OPTIONS说明:
注意:
当使用
docker run -d [image_name]
后台运行容器的时候,然后再使用dokcer ps
查看正在运行的容器时发现刚才启动的后台容器没有运行:这是因为容器使用后台运行的模式,就必须要有一个前台进程,如果docker发现没有应用,就会停止这个后台进程,所有使用
docker ps
命令就看不到对应的容器了。
docker exec [OPTIONS] [CONTAINER_ID]
这个命令和docker run
命令类似,都是启动要给容器,但是这个命令和docker run
有些不一样,这个命令是进入一个正在运行的容器,如果这个容器不在运行,那么使用这个命令就会报错,提示没有当前容器。
docker attach [CONTAINER_ID] # 中间不能有参数
这个命令也是打开一个正在执行的容器,但是这个命令和docker exec
也有一定的区别,docker exec
命令是重新打开终端,然后这个终端连接到那个正在执行的容器,而docker attach
命令则是不打开一个新的终端,直接进入到那个运行的终端
将容器中的文件拷贝到宿主机中
docker cp [CONTAINER_ID]:[FILE_PATH] [REAL_HOST]
# 举例,拷贝文件的容器必须可以使用docker ps -a查询到,否则不能执行该命令
docker cp 787dfdaa9877:/home/test.php /home # 将容器787dfdaa9877中的/home/test.php文件拷贝到宿主机在的/home目录中
给镜像起一个名字
docker tag [OLD_IMAGE_NAME] [NEW_IMAGE_NAME]
每个image都有一个名称。除了名称之外还有一个叫做tag的东西,这个称之为标签的东西可以用来标识同一个image的不同版本。如果你没有给一个image指定一个tag,那么docker会默认为这个iamge添加一个名为:latest的tag。如果你使用docker run ubuntu
,那么就会默认运行ubuntu:latest
。如果本地没有这个image,那么就会去从仓库下载ubuntu:latest
的iamge。很多时候你会看到ubuntu:14.04
的image。这个14。04就是代表这个image的tag。只是很多时候image制作者把tag用来标记version了而已。
这个命令会列出本地所有的images。每个image都会有一个独一无二的id。如下面 IMAGE ID
字段。
PS C:\Windows\system32\WindowsPowerShell\v1.0> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu-ok latest 5f93b91bc208 26 hours ago 423.7 MB
ubuntu latest a421b4d8494d 27 hours ago 423.1 MB
ubuntu 14.04 3f755ca42730 2 days ago 188 MB
这个命令会列出所有在运行的container。当运行docker ps -a
就会列出所有的container。包括已经退出的container。
这个命令可以把一个container制作成一个image。
docker rm container_id
可以用来删除一个container。docker rmi image_id/image_name
可以用来删除一个image。
很多文章讲docker都会把这个放到后面一点讲.反正不会在类似"使用docker做开发环境"的文章里面讲. 但是我觉得这个东西是理解docker的关键.所以一定要讲.
AUFS比不是docker独有的,很多Linux的发行版中都用到了这个特性.说起AUFS,这个东西是UFS的升级版,前面的A就是代表advanced的意思.那AUFS/UFS到底是个什么东西?
所谓AUFS,Advanced Union File System 就是把不同物理位置的目录合并mount到同一个目录中.这种技术有一点典型的应用:有些linux发行版只要插入一个光盘就可以直接运行.不用进行安装.你对系统文件进行的增删改只是反映在电脑的硬盘上面,不会影响到光盘的内容.即对光盘只读不写.那么docker是如何使把这个技术应用到docker上?
docker把一个镜像分成了很多层layer.这些层合并在一起才成为了一个完整的image.这样子有什么好处?最直观的一点就是,ubuntu15.04跟ubuntu16.04的image可能只有一点点差别.这点差别体现在第四层layer上.那么ubuntu15.04跟16.04就可以共享前三层layer.这样子如果你本地有了ubuntu15.04的image.那么再pull ubuntu16.06的时候只要把第四层的pull下来就可以了.
而且,image的所有层都是只读的,当你启动一个image当做container运行的时候,docker会在image的只读层上加一层薄薄的可写层.你在container里面做的所有操作都是反映在可写层.当你退出container之后,下次启动同一个image,之前操作的所有东西都会没有掉.一个重新做人的image.
这个时候有一个问题就来了,我们pull一个image,启动了container.好不容易把该安装的软件都安装好了,然后退出了container.之前安装的软件就都没有了!这个时候我们就要使用commit命令了.commit命令可以把当前的可写层合并到image的只读层里面.这样子这个image又多了一层.下次我们启动这个image的时候安装的软件就都还在了.
一个image由好几层layer构成.每个layer都是一个只读层
当启动一个container之后,就会在iamge的只读层基础上添加一个可写层.所有对container执行的操作都反映在container上
这里提一点,当使用docker images
命令查看iamge信息的时候,后面的SIZE是表示当前iamge所占用的大小,但是不意味着所有SIZE相加起来就是占用磁盘空间的总大小.一定要注意,可能有image共享若干层layer.这些layer在相加的时候被计算了好几遍.
PS C:\Windows\system32\WindowsPowerShell\v1.0> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
12e32b701daa 25 minutes ago 188 MB
ubuntu 14.04 3f755ca42730 3 days ago 188 MB
centos 6 8315978ceaaa 6 weeks ago 194.6 MB
docker rm [CONTAINER_ID]
docker rm -f [CONTAINER_ID] #默认情况下不能删除正在运行中的容器,可以使用强制删除
docker rm -f $(docker ps -aq) # 删除所有容器
docker ps -aq | xargs docker rm # 删除所有容器
docker rmi [IMAGE_ID]
上面的命令提到删除有rm跟rmi两个,rm是用来删除一个已经退出的container。rmi是用来删除一个image的。有了上面AUFS的概念之后,要明白的是我们使用docker rm container_id
的时候,其实只是删除掉了一层可写层的数据。因为只读层是container跟image共享的。只要iamge没有被删掉,那么只读层的数据一定也不会被删除掉。
同样,当多个image共享若干层只读层的时候,删除掉一个image。只是删除掉了这个image独有的一层只读层数据。其他共享的数据并没有被删除掉,只有当删除掉所有的image之后,共享的layer层才会被删掉。
执行删除命令的时候会看到如下的信息,这里每一次deleted都是代表删除掉了一层layer。
PS C:\Windows\system32\WindowsPowerShell\v1.0> docker rmi ubuntu-fin
Untagged: ubuntu-fin:latest
Deleted: sha256:9e0728e8edbaf72846c43c629590fba5f46b1d705111d3fb1d79b9cf03a6c50c
Deleted: sha256:d53e457ca7161cd6f2d1b6678ecaafd19043dcaeb1363471867e1047819268fa
Deleted: sha256:496ef4fa137e03d80cf821745f875860d3d3120447326b8609938aa70f2edbd9
Deleted: sha256:12e32b701daa90c435176a273b2b41b4bfb219523c1ae396dc2f7068bbb6c088
Deleted: sha256:e8f29656cf54ad60a17d4b38362d9207b52a846cce3cc13e245fc3b799ff53e9
Deleted: sha256:48f6b521c809e40468886b0a159040503d00a0abb1eabf310451edfea562b459
Deleted: sha256:e94abc94ab1aff00280016eaf0649a75270886a2b60c8fe862ca549a0601949f
Deleted: sha256:3f755ca4273009a8b9d08aa156fbb5e601ed69dc698860938f36b2109c19cc39
Deleted: sha256:565903b66233d5576592815ca4d499bd6fe09a9b4baf83f345aaf64544f1cd78
Deleted: sha256:b653e4373a4b35aa760ff67cfa3de2c9fe3c089823b63ec797eb04de256f86ba
Deleted: sha256:362e536c4e530b94ce4204461e4f8c998705bcb98c91be54dd40b22f50531f3a
Deleted: sha256:b69ad682d83af6a6962b4a60a0b5f033c8d39efcd20dbdf320b6dd8136e50aae
Deleted: sha256:bc224b1b676d12be2a49f99778dda08b90d22747244d0a0afcdf4cfeb7db5d89
我们再删除iamge的时候有时候不能成功删除。大概原因有一下几点:
docker stop container_id
退出当前container再尝试删除。Untagged: ubuntu:14.04
.这个不是没有删除成功.这个是因为有其他image跟这个ubuntu:14.04共享layer层。所以删除时候并没有真正删除掉layer层的数据。ok,有了上面的预备知识,我们现在可以开始准备我们的环境了。刚刚说过,我们退出一个container之后在container所安装的软件,添加的文件等等数据都会丢失掉,所以正确的办法应该是:在一个container环境中配置好所有开发要用到的东西之后,使用docker commit
命令来把当前这个container制作成一个image。然后下次我们启动这个image的时候环境就是我们所需要的了。但是这样子会存在三个问题:
要解决上面的这些问题,就要使用Dockerfile了。所以我们开始之前还要做点功课。
Dockerfile是用来描述如何构建一个image的,Dockerfile由一些指令构成,全部指令大概有20个左右,这里不全部讲解.只讲一些我们下面会用到的.具体Dockerfile的全部用法参考Docker官方出的最佳实践.
我们要制作的image必然是基于某个现有image的基础,from命令就是用来指定使用哪个基础iamge的.像很多ubuntu官方在Docker Hub上维护由官方的image.我们下面开发环境的搭建就是基于ubuntu:14.04的环境下完成的.
copy命令是把宿主机上的文件拷贝到image中.add可以是copy的高级版.
我们待会儿就会用到add指令,因为我们需要使用163的ubuntu源来替换ubuntu原生的apt-get
源.所以我们的Dockerfile会有类似的指令 : ADD http://mirrors.163.com/.help/sources.list.trusty /etc/apt/sources.list
.
这个是指定启动一个container之后,默认执行的命令.我们执行docker run ubuntu:14.04
启动一个container之后,默认就进入了bash界面.这就说明这个ubuntu:14.04的CMD就是bash.
这里要澄清一个概念.使用docker run
之后默认进入了bash会让很多人以为启动container跟启动一个虚拟机没什么区别.其实不是的.docker的container就是为了某个进程而存在的,这个进程就是CMD所指定的程序.比如:CMD /bin/bash
就是启动了bash.当我们退出了bash之后,整个container也就退出了.如果你的CMD写成:CMD service nginx start
.你会发现container执行之后就马上结束了.这是因为整个container只是为了service nginx start
这条命令而存在的,它不会管你这条命令启动了什么.默认启动的bash正好是一直在前台运行,只有你使用exit命令退出bash的时候才结束bash进程.这个时候container才结束.才会让人有container跟虚拟机差不多的错觉.
上面的这个概念很重要,一定要理解透彻.如果没有搞清楚这点.你会一直觉得docker跟虚拟机没有什么区别.
这个命令指定了在构建image时候image中要执行的命令.这么说可能有点蹩脚.举个例子,我们希望我们的镜像构建好的时候就安装好了git.那么我们就可以在Dockerfile里面写RUN apt-get -y install git
.这样子在构建镜像的时候就会去安装git了.待会儿我们要安装的软件都是通过这个命令指定的.也是有了RUN指令,我们就可以知道一个image构建过程中做了一些什么操作.
exit # 直接关闭当前容器并退出
crtl + p + q # 在后台运行,退出交互界面