- 一、创建并运行你的第一个容器
- 1、安装Docker
- 2、我们究竟都安装了什么?
- 3、启动自己的第一个容器
- 二、容器里究竟有些什么东西
- 1、两种访问inspect结果的方法
- 三、清理和容器有关的资源
- 1、如何删除容器
- 2、如何删除镜像
- 四、如何与容器交互
- 1、恢复已经结束的容器
- 五、通过容器启动一个Nginx server
- 1、安装并启动Nginx
- 2、通过host访问容器中的Web服务
- 六、通过Volumn共享文件
一、创建并运行你的第一个容器
1、安装Docker
安装社区版(Docker CE)后,启动Docker,就会在系统状态栏上看到一个鲸鱼图标:
在这个菜单中,有4个内容是我们常用的:
- Docker is running表示当前Docker的执行状态;
- Preferences...是Docker设置,通常我更习惯通过命令行来处理;
- Kitematic是一个GUI的容器镜像管理界面,同样,我更习惯用命令行操作,大家感兴趣可以自己去看看;
- Repositories可以理解成是Docker版的Github,上面有各种来自官方的和第三方的容器镜像。是我们安装各种环境的主要来源;
2、我们究竟都安装了什么?
接下来,我们看一下通过Docker的安装包,我们究竟都安装了那些东西。打开Terminal,输入docker按Tab
,可以看到,实际上我们安装了4个工具:
其中:
- docker:是docker的管理程序,我们创建和销毁容器、管理docker镜像都是通过这个命令完成的;
- docker-compose:可以理解为是docker容器的自动化工具,我们可以通过一个脚本来自动化构建一个多容器环境的构建;
- docker-machine:在本地环境,我们很少使用它。简单来说,它可以用让我们像使用本地容器一样的来管理远程容器;
- docker-credential-osxkeychain:用来把docker使用的证书保存到OS X的keychain,暂时我们不会用到它;
3、启动自己的第一个容器
所谓Docker里的Hello World,当然就是创建并运行一个自己的容器。在现在这个阶段,我们把容器就理解成操作系统中的进程就好了,甚至docker的一些命令都和本地进程管理命令类似,例如:
- 查看当前正在执行的容器:
docker ps
,当然如果现在执行什么都不会显示,因为我们还没有任何容器; - 查看当前所有容器(包括正在执行的和已经结束的):
docker ps -a
,同样,现在执行,我们也不会看到任何结果; - 查看本地已经安装的docker镜像:
docker images
;
当然,现在你可能还还分不清楚什么是容器,什么是镜像,这很正常。接下来,我们执行:docker run ubuntu:latest ls -l
执行一个ubuntu 18.04容器。第一次执行这条命令的时候,由于本地还没有ubuntu 18.04的安装镜像,因此docker会自动从Docker Hub上下载,下载完成后:
上图列出的目录,就是在ubuntu 18.04中执行ls -l
命令的结果,这样我们就执行了自己的第一个容器。
现在,重新执行docker ps
,还是什么结果都没有。但是,重新执行docker ps -a
就会看到下面的结果:
通过这个表,可以得到下面这些信息:
首先,CONTAINER ID,这是docker给每一个容器分配的唯一ID,我们可以使用这个ID管理容器。
其次,IMAGE,这是Docker镜像的名称。所谓镜像,指的是执行Docker容器所需要的文件。它类似虚拟机的镜像文件。这里多说一句关于镜像的命名方式。在Docker Hub上可以看到,我们有很多版本的Ubuntu镜像可以使用:
-w556
例如,为了使用ubuntu 18.04,基于官方提供的这些信息,我们可以使用下面三种命令:
-
docker run ubuntu:18.04
; -
docker run ubuntu:latest
; -
docker run ubuntu:bionic
;
它们是完全一样的。理解了这种方式之后,执行其它版本的Ubuntu的方法是类似的,我们就不重复了。大家只要理解版本号的使用方法就好了。
第三,COMMAND,指的是容器执行的命令。
第四,CREATED,指的是容器的创建时间。
第五,STATUS,指的是容器的状态,从这里可以看到,它已经结束了,实际上我们创建的这个容器的生命周期非常短,就是执行ls -l
这条命令的时间,命令结束了,容器也就结束了。
第六,PORTS,指的是容器的端口映射规则,等用到的时候我们再说。
最后,NAMES,它和CONTAINER ID类似,Docker生成的容器名称。
接下来,我们再执行下docker images
:
可以看到,这就是我们为了在ubuntu 18.04中执行ls -l
命令要下载的文件,相比虚拟机,它可小多了。
二、容器里究竟有些什么东西
在一个容器里,究竟有哪些内容呢?为了帮助我们了解这个信息,docker提供了一个命令:docker inspect
,而它就是我们这一节的主角。
这个命令需要接受一个容器ID或者NAME作为参数。因此,我们可以先执行docker ps -a
,可以看到这就是我们在上一节视频中执行过的容器:
接下来,我们执行:docker inspect 1d6b9c71eeb9
或者docker inspect priceless_hugle
,就会看到类似下面这样的结果:
这是一个JSON对象,包含了一个容器的全部内容。当然我们没必要逐一去讲每一个字段的含义,大家在终端里执行一下,然后自己去看看就好。
1、两种访问inspect结果的方法
接下来我们要介绍的,是两种可以从这个JSON中提取信息的方法,毕竟每次都从这么大一堆东西里找内容就太不方便了。
第一种方法,是docker inspect
原生支持的,它有一个-f
参数,允许我们用go template的语法选择要使用的JSON字段。
例如,为了读取容器IP地址字段,我们可以这样:
image
当然,由于当前这个容器没有执行,因此我们在终端上只会看到一个空行。如果我们把IPAddress
换成SandboxID
就会看到结果了。
注意在template里,双大括号不要与中间的内容有空格。
如果你觉得Go template的语法用起来并不方便,这里再向大家介绍一个工具:jq。它是一个很方便的基于命令行的JSON parser,我们直接执行brew install jq
安装。完成后,执行docker inspect 1d6b9c71eeb9 | jq
:
可以看到,之前黑白的JSON结果变成彩色的了。并且,和docker inspect
的-f
参数类似,它接受一个-r
参数也可以让我们访问指定的字段,例如之前的访问IP地址,现在可以这样:
docker inspect 1d6b9c71eeb9 | jq -r ".[0].NetworkSettings.IPAddress"
也就是说,我们从根节点数组中,选取了第一个元素,然后访问了它的NetworkSettings.IPAddress
节点。类似的,为了bridge网卡的IP地址,我们可以这样:
docker inspect 1d6b9c71eeb9 | jq -r ".[0].NetworkSettings.Networks.bridge.IPAddress"
大家可以在这里找到jq的官方示例,我们就不在这里重复它的具体用法了。
在我看来,相比go template的语法,jq用起来还是更自然一些。当然,它们的结果是一样的,大家只要在查看容器信息的时候,选择一种自己用的惯的,就好了。
三、清理和容器有关的资源
如果你刚开始学习docker不久,一个最常见的问题就是,在反复试验和练习中,无意间就创造大量已经执行结束的容器对象。在这一节,我们就来看和清理docker镜像和容器相关的操作。
1、如何删除容器
为了演示这个场景,我们再执行一次之前的容器:docker run ubuntu:latest ls -l
。完成后,我们执行docker ps -a
,就会看到下面这样的结果:
可以看到,我们已经有两个执行结束的容器了。当我们随意练习的时候,很容易就积累了很多无意义的容器对象。为此,我们可以执行:
docker rm 842dd8c58cbe
docker rm distracted_cray
删掉它们。同样,使用容器的ID和名字,都是可以的。或者,如果你想清空容器列表,还可以执行:
docker rm $(docker ps -a -q)
这里,-q
是让docker ps
命令只显示容器ID列表。我们也可以把-a -q
写成-aq
。这时,你可能会想,每次执行完一个容器之后还要记得删掉很麻烦啊,能不能执行完就自动删掉呢?为此,我们可以这样:
docker run --rm ubuntu:latest ls -l
再执行容器的时候添加--rm
参数,就可以了。这时,如果你执行docker ps -a
就看不到刚才执行过的容器了。
2、如何删除镜像
了解了如何删除容器之后,我们再来看如何删除镜像。首先,执行docker images
查看下当前的镜像列表:
可以看到,我们下载了三个镜像。为了删掉ubuntu镜像我们可以执行:
docker rmi ubuntu:latest
这里,我们要使用name:tag
的形式来指定镜像。实际上,删除镜像也可以使用镜像ID,只不过我们要用docker images -q
命令来查看,然后把它组合到docker rmi
就好了:
docker rmi $(docker images -q)
image
四、如何与容器交互
如何能“登录”到正在执行中的容器中看看呢?就像我们登录到虚拟机中一样。显然执行ls -l
命令这样的容器很难。为了能“登录”到容器内部,我们至少得让容器执行一个稍微活的长一些的程序,例如:bash
。
首先,如果我们直接执行:docker run ubuntu:latest bash
,就会发现,还是执行一下就退出了。然后,执行docker ps -a
查看:
就能看到我们刚才这个容器了。为了能和容器里这个bash交互,我们需要在执行的时候,给docker传递两个命令行参数,像这样:
docker run -i -t ubuntu:latest bash
其中:
-
-i
是interactive,表示我们要和容器交互; -
-t
是tty,让docker创建一个虚拟终端,这样我们就能在屏幕上看到来自容器的控制台输出了;
当我们执行之后,就会看到类似这样的结果:
-w437
这样,我们就“登录”到容器内部了。此时我们在终端中执行的命令,都是在ubuntu 18.04这个容器中执行的。这时,我们执行一下ps aux
会看到这样的结果:
看到了吧,除了正在打印消息的ps aux
进程外,整个容器里只有一个bash进程。这是容器区别于虚拟机最明显的一个地方,容器就像一个沙箱一样,把进程隔离在容器里,而进程本身对此却不知情,以为自己就是这个操作系统中的唯一进程。
接下来,要退出这个容器,我们只要执行exit
离开bash就好了。Bash执行结束了,容器也就结束了。
1、恢复已经结束的容器
下次,我们再想在容器中执行bash的时候,除了使用docker run
新启动一个容器之外,还可以重新启动之前退出的容器。例如,我们执行:docker start e7e7092afdc4
:
这次,从docker ps
的结果,我们知道这个容器已经在运行了。但是,我们却没法和它交互。为此,我们同样要以交互模式重新启动这个容器:docker start -i e7e7092afdc4
。
从上面的结果中,我们知道:
- 可以用
docker stop ID
停止一个在后台执行的容器; - 可以用
-i
参数以交互模式恢复容器的执行;
五、通过容器启动一个Nginx server
1、安装并启动Nginx
还是基于上一节的结果,我们以交互模式启动一个执行bash
的容器:docker run -i -t ubuntu:latest bash
。接下来,用起来就和普通的Ubuntu一样了,我们先在Shell中执行下面的命令安装一下Nginx:
apt-get update && apt-get install -y nginx
安装完成后,和普通的Ubuntu不同的是,Nginx不会自动启动,我们需要手工执行一下nginx
命令。然后,当我们执行ps aux
的时候,就会看到Nginx启动了:
2、通过host访问容器中的Web服务
接下来,该如何在host中访问容器中的Nginx服务呢?保持这个Nginx容器运行的条件下,我们新建一个终端,然后用之前说过的方式查看一下它的IP地址:
docker inspect e7e7092afdc4 | jq -r ".[0].NetworkSettings.IPAddress"
在我的Mac上,这个值是172.17.0.2
。然后,如果我们直接请求这个地址就会发现并不能访问。道理很简答,Nginx的80端口是开放在容器内的,我们当然无法直接通过host请求到。
为此,我们得在启动容器的时候,把这个容器内的端口,映射到容器外面来。首先,执行exit
从容器里退出来,然后执行下面的命令启动一个开启了端口映射的容器:
docker run -it -p 8080:80 ubuntu:latest bash
这里,-p
参数可以让我们用host_port:container_port
的格式指定容器内外的端口映射规则。因此,上面的命令,就是帮助我们把容器内的80端口,映射到了host上的8080端口。
但是,先别着急访问。因为我们重新执行了容器之后,这仍旧是一个执行Bash的容器,这里面并没有Nginx执行,因此,我们还得重新执行一下apt-get update && apt-get install nginx -y
安装一下。完成后,执行nginx
命令启动Nginx。
当然,你可能觉得每次都这么重装很麻烦。在后面的视频里,我们会讲到如何通过Dockerfile自动化容器的构建,以及保存已有修改的方法。当前我们这个例子,重点还是放在端口映射上。
现在,我们只要访问http://localhost:8080
,就能请求到容器内的Nginx了。
六、通过Volumn共享文件
之前我们启动的Nginx容器,使用的是容器内默认的文件系统。那么,我们该如何让这个Nginx使用Host上我们指定目录中的内容呢?
当然,用某种方法把文件拷贝到容器内部算是一种方法,但是一来,这种方法并不经济;二来,让host和container保持内容的同步也不容易。最好的办法,则是可以像端口映射一样,把Host上的某个目录映射到容器里。为此,我们可以在启动容器的时候,使用-v
参数。
具体怎么做呢?由于容器中的Nginx默认的web根目录是/var/www/html
,最简单的,我们把这个目录映射出来就好了。
首先,在host中,创建一个/tmp/web
目录,并在其中添加一个demo.html文件。这里为了演示,我们只是添加了一句话而已:
其次,执行下面的命令启动docker:
docker run -it -p 8080:80 -v /tmp/web:/var/www/html ubuntu:latest bash
这里,我们使用了-v host_dir:container_dir
进行了目录映射。映射之后,在容器中,之前/var/www/html
目录指定的内容就无法访问了。现在/var/www/html
访问的,是host上的/tmp/web
目录。
实际上,我们也可以用这样的方式给容器添加多个目录映射,只要使用多个
-v
参数就好了。
第三,在容器里指定apt-get update && apt-get install nginx -y
安装nginx。
最后,在容器里,执行nginx
启动。这样,容器里的Nginx就使用host上的/tmp/web
作为服务器的根目录了。
我们打开浏览器,访问http://localhost:8080/demo.html
,就可以看到下面的结果了:
网友评论