1 安装docker
Windows
用户一般通过安装docker-desktop(点击下载),下载完成以后按照提示安装wsl2
,然后才可以正常启用Docker
,但是使用本镜像会导致出现container exit(139)
的错误。
在C:\Users\(用户名)中创建.wslconfig
以我为例,创建C:\Users\zhangsan\.wslconfig
文件中写入下面内容:
1 | [wsl2] |
1.1 Windows10启动docker失败解决办法
提示信息:
1 | Docker.ApiServices.WSL2.WslKernelUpdateNotInstalledException: 引发类型为“Docker.ApiServices.WSL2.WslKernelUpdateNotInstalledException”的异常。 |
解决方法:
下载wsl.msi
一路Next
重启Docker解决!
然后在命令中执行docker -v
命令查看是否安装成功:
1.2 启动docker镜像
启动一个docker
镜像,使用如下命令:
1 | docker run [OPTIONS] IMAGE [COMMAND] [ARG...] |
OPTIONS说明:
- -a stdin: 指定标准输入输出内容类型,可选 STDIN/STDOUT/STDERR 三项;
- -d: 后台运行容器,并返回容器ID;
- -i: 以交互模式运行容器,通常与 -t 同时使用;
- -P: 随机端口映射,容器内部端口随机映射到主机的端口
- -p: 指定端口映射,格式为:主机(宿主)端口:容器端口
- -t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用;
- –name=”nginx-lb”: 为容器指定一个名称;
- –dns 8.8.8.8: 指定容器使用的DNS服务器,默认和宿主一致;
- –dns-search example.com: 指定容器DNS搜索域名,默认和宿主一致;
- -h “mars”: 指定容器的hostname;
- -e username=”ritchie”: 设置环境变量;
- –env-file=[]: 从指定文件读入环境变量;
- –cpuset=”0-2” or –cpuset=”0,1,2”: 绑定容器到指定CPU运行;
- **-m :**设置容器使用内存最大值;
- –net=”bridge”: 指定容器的网络连接类型,支持 bridge/host/none/container: 四种类型;
- –link=[]: 添加链接到另一个容器;
- –expose=[]: 开放一个端口或一组端口;
- –volume , -v: 绑定一个卷
举例:
以界面的形式运行:
1 | docker run -it ubuntu /bin/bash ## 以界面bash界面的形式运行名称为ubuntu的镜像 |
或者你也可以在后台运行,使用-d
参数,运行成功则会返回一个CONTAINER_ID
:
2 快速拉取docker镜像
2.1 helloworld镜像
在命令行中执行如下命令,快速搭建一个docker镜像:
1 | docker run -d -p 80:80 docker/getting-started |
然后在浏览器输入http://127.0.0.1,如果出现了如下的界面,则表示安装成功:
2.2 PHP镜像
创建一个新的目录,结构如下:
每个文件的内容如下:
index.php
1
2
3
phpinfo();docker-composr-dev.yml //开发环境的配置文件
1
2
3
4
5
6
7
8
9
10version: "3" ## 版本号,必须是一个string类型的
services: ## 对应的服务
app-dev: ## 容器名,可可定义
build: ## 做的事情:在当前文件夹执行文件名为Dockerfile-dev的Dockerfile
context: .
dockerfile: Dockerfile-dev
volumes: ## 文件夹映射 格式:宿主机目录:容器目录(注意中间的冒号是固定格式)
- ./src:/var/www/html
ports: ## 端口映射
- 8080:80 # 宿主机端口:容器端口Dockerfile-dev //开发环境的dockerfile
1
FROM php:apache
Dockerfile
1
2FROM php:apache
COPY ./src /var/www/htmlDocker-compose.yml
1
2
3
4
5
6
7version: "3"
services:
app:
build: .
image: gcslaoli/docker-php
ports:
- 81:80
然后进入当前文件夹,执行如下命令:
1 | docker-compose -f .\docker-compose-dev.yml up -d --build ## 启动开发环境 |
然后在浏览器中输入对应的网址即可:http://127.0.0.1:8080,如果出现了如下界面,则表示成功:
如何关闭开发环境容器呢,执行如下命令:
1 | docker-compose -f .\docker-compose-dev.yml down ## 关闭开发环境 |
然后再通过docker ps -a
查看当前容器是否关闭:
也可以直接使用容器ID直接关闭,命令如下:
1 | docker stop [CONTAINER_ID] ## 或者 |
参数:
-t
:关闭容器的限时,如果超时未能关闭则用kill强制关闭,默认值10s,这个时间用于容器的自己保存状态1
docker stop -t=60 容器ID或容器名
查看容器ID命令如下:
1 | docker ps -a ## 查看所有运行的容器,可以查看对应的容器ID |
或者直接使用下面的命令,直接关闭:
1 | docker kill [CONTAINER_ID] |
3 发布镜像到dockerHub
3.1 注册账号
发布docker
镜像到dockerHub
需要到docker官网注册一个账号,这就类似我们将代码push
到GitHub
上的动作类似。
注册完成之后,首先需要登录,如果你已经再docker Desktop
中登录了,那么在命令使用docker login
测试以下:
3.2 规范化命名
然后使用docker tag
命令,将需要push到dockerHub
的镜像进行规范化命名,规范如下:
1 | docker push 注册用户名/镜像名 |
3.3 保存当前镜像
我们将镜像push
到dockerHub
中,首先需要对其中的配置进行修改,否则push
就没有意义了,默认情况下,如果docker
镜像是不会保存当前的更改的,所以,如果需要保存一下自己对当前镜像的更改,之后再push
。
退出当前镜像之后,执行如下命令保存对当前镜像的更改:
1 | docker commit [CONTAINER_ID] [NEW_NAME] |
1 | [root@49079ee590cc sbin]# exit |
3.4 push
使用如下命令将自己的镜像push
到dockerHub
中:
1 | docker push crazyjums/ubuntu-git:latest # 后面的latest指的是版本号 |
这个push的动作根据你的网速的不同,时间也不一定,如果中途中断了,那么就继续,docker会保存已经上传的部分,如果继续会再上次断开的地方继续,不会重新上传,命令还是一样。如果上传完成,可以根据这个地址取网上找一下是否自己的镜像,地址规范:
1 | docker.io/[注册用户名/镜像名] |
push完成之后,可以登录自己的账号,既可以看到自己的镜像:
4 相关概念
在使用docker之前你要明白两个概念,两个学docker过程中一定会一直强调的概念
- image
- container (这种术语直接使用英文,不做翻译)
这两个是整个docker的基础概念,这里本着不负责任的侥幸心理大概的说一下这两个的区别.
- image是静态的,类比为面向对象就是一个类
- container是动态运行的,类比为面向对象就是一个实例化的对象.
一般,container是可运行的,我们启动一个container之后,这个container里面就是我们的linux环境.
懂得了上面的意思,你就明白了我们要做的事情很简单:找一个合适的image,这个image里面应该包含一切开发时候所需要的东西, 然后启动它,我们就可以在这个container环境上工作了.当然这个时候container应该可以跟宿主共享文件.并且可以在本局域网内可以被访问到.
在继续搭建我们的开发环境之前,我们还是要先学一点docker的命令和概念的.
4.1 相关命令
id&&name
每个image都有一个唯一的id来标识,同样container也有。这个唯一的id一般很长,比如:c59dc2dfad95
,但是一般我们输入的时候只要输入若干位能标识当前系统内唯一标识某一个image就可以了。比如只要输入c59d
可能就可以标识这个image。除了id,还可以给一个image起名字,这样子也可以通过name来操作一个image。
docker inspect
查看容器的详细信息
1 | docker inspect [CONTAINER_ID] |
docker run
通过docker run image_name
可以直接启动本地的一个image。这个命令后面可以加很多子参数来开启其他功能。如果本地不存在这个image,那么docker会去官方的仓库去下载,这个仓库你可以理解为github一样的网站,上面存放了许多别人push上去的image。
OPTIONS说明:
- -a stdin: 指定标准输入输出内容类型,可选 STDIN/STDOUT/STDERR 三项;
- -d: 后台运行容器,并返回容器ID;
- -i: 以交互模式运行容器,通常与 -t 同时使用;
- -P: 随机端口映射,容器内部端口随机映射到主机的端口
- -p: 指定端口映射,格式为:主机(宿主)端口:容器端口
- -t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用;
- –name=”nginx-lb”: 为容器指定一个名称;
- –dns 8.8.8.8: 指定容器使用的DNS服务器,默认和宿主一致;
- –dns-search example.com: 指定容器DNS搜索域名,默认和宿主一致;
- -h “mars”: 指定容器的hostname;
- -e username=”ritchie”: 设置环境变量;
- –env-file=[]: 从指定文件读入环境变量;
- –cpuset=”0-2” or –cpuset=”0,1,2”: 绑定容器到指定CPU运行;
- **-m :**设置容器使用内存最大值;
- –net=”bridge”: 指定容器的网络连接类型,支持 bridge/host/none/container: 四种类型;
- –link=[]: 添加链接到另一个容器;
- –expose=[]: 开放一个端口或一组端口;
- –volume , -v: 绑定一个卷
注意:
当使用
docker run -d [image_name]
后台运行容器的时候,然后再使用dokcer ps
查看正在运行的容器时发现刚才启动的后台容器没有运行:这是因为容器使用后台运行的模式,就必须要有一个前台进程,如果docker发现没有应用,就会停止这个后台进程,所有使用
docker ps
命令就看不到对应的容器了。
docker exec
1 | docker exec [OPTIONS] [CONTAINER_ID] |
这个命令和docker run
命令类似,都是启动要给容器,但是这个命令和docker run
有些不一样,这个命令是进入一个正在运行的容器,如果这个容器不在运行,那么使用这个命令就会报错,提示没有当前容器。
docker attach
1 | docker attach [CONTAINER_ID] # 中间不能有参数 |
这个命令也是打开一个正在执行的容器,但是这个命令和docker exec
也有一定的区别,docker exec
命令是重新打开终端,然后这个终端连接到那个正在执行的容器,而docker attach
命令则是不打开一个新的终端,直接进入到那个运行的终端
docker cp
将容器中的文件拷贝到宿主机中
1 | docker cp [CONTAINER_ID]:[FILE_PATH] [REAL_HOST] |
docker tag
给镜像起一个名字
1 | 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了而已。
docker images
这个命令会列出本地所有的images。每个image都会有一个独一无二的id。如下面 IMAGE ID
字段。
1 | PS C:\Windows\system32\WindowsPowerShell\v1.0> docker images |
docker ps
这个命令会列出所有在运行的container。当运行docker ps -a
就会列出所有的container。包括已经退出的container。
docker commit
这个命令可以把一个container制作成一个image。
docker rm && docker rmi
docker rm container_id
可以用来删除一个container。docker rmi image_id/image_name
可以用来删除一个image。
4.2 AUFS
很多文章讲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在相加的时候被计算了好几遍.
1 | PS C:\Windows\system32\WindowsPowerShell\v1.0> docker images |
4.3 删除
(1)删除容器
1 | docker rm [CONTAINER_ID] |
(2)删除镜像
1 | 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。
1 | PS C:\Windows\system32\WindowsPowerShell\v1.0> docker rmi ubuntu-fin |
我们再删除iamge的时候有时候不能成功删除。大概原因有一下几点:
- container正在运行,你删除这个container会失败。应该使用
docker stop container_id
退出当前container再尝试删除。 - container退出了,删除当前image也会失败。因为container虽然退出,当前container保存着运行环境等数据。container是在iamge的基础上添加了一层可写层。所以他们是共享只读层的。
- 删除一个iamge会有
Untagged: ubuntu:14.04
.这个不是没有删除成功.这个是因为有其他image跟这个ubuntu:14.04共享layer层。所以删除时候并没有真正删除掉layer层的数据。
ok,有了上面的预备知识,我们现在可以开始准备我们的环境了。刚刚说过,我们退出一个container之后在container所安装的软件,添加的文件等等数据都会丢失掉,所以正确的办法应该是:在一个container环境中配置好所有开发要用到的东西之后,使用docker commit
命令来把当前这个container制作成一个image。然后下次我们启动这个image的时候环境就是我们所需要的了。但是这样子会存在三个问题:
- 当别人给你一个image之后,你知道这个image里面安装了哪些文件,修改了哪些数据么?
- 每次commit都会形成一个新的只读层。commit次数多了会使得image变得越来越臃肿。
- 再着,一个image动辄2、3G。带着这么大个文件跑也不优雅。
要解决上面的这些问题,就要使用Dockerfile了。所以我们开始之前还要做点功课。
4.4 Dockerfile
Dockerfile是用来描述如何构建一个image的,Dockerfile由一些指令构成,全部指令大概有20个左右,这里不全部讲解.只讲一些我们下面会用到的.具体Dockerfile的全部用法参考Docker官方出的最佳实践.
FROM
我们要制作的image必然是基于某个现有image的基础,from命令就是用来指定使用哪个基础iamge的.像很多ubuntu官方在Docker Hub上维护由官方的image.我们下面开发环境的搭建就是基于ubuntu:14.04的环境下完成的.
COPY && ADD
copy命令是把宿主机上的文件拷贝到image中.add可以是copy的高级版.
- copy要求拷贝的文件在宿主机上存在
- add可以指定一个url座位源文件,docker会自动去下载这个url的文件, 然后拷贝到image中.
我们待会儿就会用到add指令,因为我们需要使用163的ubuntu源来替换ubuntu原生的apt-get
源.所以我们的Dockerfile会有类似的指令 : ADD http://mirrors.163.com/.help/sources.list.trusty /etc/apt/sources.list
.
CMD
这个是指定启动一个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跟虚拟机没有什么区别.
RUN
这个命令指定了在构建image时候image中要执行的命令.这么说可能有点蹩脚.举个例子,我们希望我们的镜像构建好的时候就安装好了git.那么我们就可以在Dockerfile里面写RUN apt-get -y install git
.这样子在构建镜像的时候就会去安装git了.待会儿我们要安装的软件都是通过这个命令指定的.也是有了RUN指令,我们就可以知道一个image构建过程中做了一些什么操作.
4.5 退出容器
1 | exit # 直接关闭当前容器并退出 |
5 相关问题
5.1 在windows系统映射文件到docker错误/失败/映射不上
在windows向docker映射文件目录的时候, 需要把盘符变成 /
比如D:/abc/def
, 需要将windows的路径写成//d/abd/def
注意:这里最前面是两个
/
,不要少写,如果少写了一个/
副符号,则会发送很诡异的现象:- 少写一个
/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16$ docker run --name nginx -d -p 8888:80 -v /e/vue/vue_nginx:/usr/share/nginx/html nginx
5bbee0915396ceadb00e8afa3daec0d2e968319e13a10dbacaec8b0641f4a065
$ docker inspect 5b ### 查看挂载情况
...
"Mounts": [
{
"Type": "bind",
"Source": "E:\\vue\\vue_nginx;D",
"Destination": "\\MyApps\\git\\Git\\usr\\share\\nginx\\html",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
...从
mounts
挂载信息可以看出,docker
自动帮我们创建了一个文件夹,而这个文件夹的命名是我们指定的本地文件夹+’;D
‘,且目标目录并不是docker容器中的目录,而是本地的git
目录文件夹中的某个目录。- 正确写法(写两个
/
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16$ docker run --name nginx -d -p 8888:80 -v //e/vue/vue_nginx:/usr/share/nginx/html nginx
cd99ad83d62bde42e872709b878252cb056397ad3b689a55468eb21ae62fdd23
$ docker inspect cd
...
"Mounts": [
{
"Type": "bind",
"Source": "//e/vue/vue_nginx",
"Destination": "/usr/share/nginx/html",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
...如果使用两个
/
,从mounts
挂载信息可以看出,我们可以得到正确的映射结果。- 少写一个
docker默认安装的是在C盘, 第一种写法不会报错,但是可能会出现文件没有映射到docker里面的问题.
需要把要映射的文件放在C盘User目录下, 比如桌面如果还是不可以, 修改docker软件里面的映射盘符
点击添加, 选择你要映射的盘符….
5.2 docker 配置nginx镜像出现 403 Forbidden的问题
(1)docker 配置nginx镜像的时候,将映射文件配置到当前宿主机上,启动nginx镜像,通过域名访问,出现 403
查看nginx error.log日志,发现出现
(2)nginx镜像文件的配置:
1 | docker run -p 80:80 --name nginx \ |
解决方案:
- 如果在
/usr/share/nginx/html
下面没有index.html
,直接访问域名,找不到文件,会报403 forbidden - 因此需要在
/usr/share/nginx/html
目录下存在index.html
文件(对本次配置,对应于宿主机上/data/nginx/html
目录下存在index.html
文件)
5.3 docker 在容器外执行某个容器内的某个命令
有时候我们想执行某个容器的某条命令,但又不想进入容器内。那该怎么办?
于是脚本可以这样写
1 | !/bin/bash |
Reference
- 使用docker搭建开发环境
- docker–从入门到实践
- https://www.cnblogs.com/cuijunfeng/p/15011540.html
- 三分钟学不会的docker教程014-php开发环境搭建及打包-上
- 三分钟学不会的docker教程015-php开发环境搭建及打包-下
- https://blog.csdn.net/boonya/article/details/74906927
- docker官方文档
- https://blog.csdn.net/Jarvis_F/article/details/108334404
- https://blog.csdn.net/lvjia_it/article/details/107152578
- https://www.yangshuaibin.com/detail/376869
- https://blog.csdn.net/weixin_32820767/article/details/80643091
写在最后
欢迎大家关注鄙人的公众号【麦田里的守望者zhg】,让我们一起成长,谢谢。