Docker 初探

一、容器(Container)

简单来说,容器是对应用程序及其依赖库的一种封装

乍看上去,容器就像一个轻量级的虚拟机系统(VM),也封装了一个操作系统实例用来运行某些应用程序。
相比于传统的虚拟机,容器的优势主要在以下几个方面:

  • 容器与宿主机共享资源,使得其效率有了很大的提升。与主机中运行的应用程序相比,在容器中运行的应用程序几乎没有任何额外的开销。

  • 容器可移植的特性可以解决由于运行环境的微小变化引发的一系列问题。

  • 容器的轻量级特性意味着开发人员可以同时运行数十个容器,从而可以模拟出生产级别的分布式系统

  • 对于不使用云端应用的用户和开发者,用户可以节省下大量的安装和配置时间,也不用担心系统的依赖冲突等问题。而开发者同时也可以避免由于用户系统环境的差异导致的可用性问题。

总的来说,虚拟机的基本目标,是完整地虚拟出一个外部(独立)的系统环境,而容器是为了达到应用程序的可移植和自成一体。

而 Docker Engine 为运行容器提供了快捷方便的交互接口。

二、Docker 安装(Linux)

Linux 系统上的 Docker 安装,可以直接使用官方提供的安装脚本(https://get.docker.com),命令如下:
$ sudo wget -qO- https://get.docker.com/ | sh

$ sudo curl -sSL https://get.docker.com/ | sh

Mac OS 和 Windows 系统上的 Docker 安装,可参考官方文档 Docker for MacDocker for Windows

Docker 官方镜像访问缓慢,可以使用阿里云提供的加速服务(参考 镜像加速器

Docker 需要特定的权限才能运行,即普通用户执行 docker 命令时需要加上 sudo 。

1
2
3
4
5
6
7
8
9
10
$ docker version
Client:
Version: 18.06.1-ce
API version: 1.38
Go version: go1.10.3
Git commit: e68fc7a
Built: Tue Aug 21 17:24:51 2018
OS/Arch: linux/amd64
Experimental: false
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.38/version: dial unix /var/run/docker.sock: connect: permission denied

可以将本地用户添加到 docker 用户组中,后续使用 docker 命令时即无需加上 sudo 前缀。
$ sudo usermod -aG docker <username>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ docker version
Client:
Version: 18.06.1-ce
API version: 1.38
Go version: go1.10.3
Git commit: e68fc7a
Built: Tue Aug 21 17:24:51 2018
OS/Arch: linux/amd64
Experimental: false

Server:
Engine:
Version: 18.06.1-ce
API version: 1.38 (minimum version 1.12)
Go version: go1.10.3
Git commit: e68fc7a
Built: Tue Aug 21 17:23:15 2018
OS/Arch: linux/amd64
Experimental: false

三、操作入门

Hello World
1
2
3
4
5
6
7
$ docker run debian echo "Hello World"
Unable to find image 'debian:latest' locally
latest: Pulling from library/debian
05d1a5232b46: Pull complete
Digest: sha256:07fe888a6090482fc6e930c1282d1edf67998a39a09a0b339242fbfa2b602fff
Status: Downloaded newer image for debian:latest
Hello World

docker run 命令用于加载容器并执行某个命令。上述命令中的 debian 用于指定所使用的镜像的名字。
如本地磁盘上没有名为 debian 的镜像文件,则 Docker 会检查在线的 Docker Hub 并将最新版本的 Debian 镜像下载到本地。
之后将下载好的镜像转化成运行的容器,并在容器中执行指定的命令。
命令执行完毕后,输出的结果传送到标准输出,容器停止运行。

交互式 Shell
1
2
3
4
5
$ docker run -i -t debian /bin/bash
root@4af9c13b78d7:/# echo "Hello from Container"
Hello from Container
root@4af9c13b78d7:/# exit
exit

上面的命令会在容器中打开一个交互式 Shell (就像是 ssh 到了一个远程主机上)。
-i-t 选项表示打开一个已绑定了 tty 的交互式会话
当使用 exit 命令退出 bash 后,运行中的容器也会停止。

可以使用 docker start -i <Container_name> 命令回到交互式 Shell 中

列出 / 删除容器

docker ps 命令可以列出当前正在运行的容器及其相关信息,如容器ID、使用的镜像、执行的命令、创建时间、当前状态和名称等,加上 -a 选项则列出所有容器(包含已停止运行的容器)

1
2
3
4
5
6
7
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
$
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f74ed03352d3 debian "/bin/bash" 9 minutes ago Exited (0) 8 minutes ago mystifying_beaver
45e9e3f3847b debian "echo 'Hello World'" 9 minutes ago Exited (0) 9 minutes ago quirky_mirzakhani

可以使用 docker rm <Container_name> 命令删除已停止运行的容器。

1
2
3
4
5
$ docker rm mystifying_beaver
mystifying_beaver
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
45e9e3f3847b debian "echo 'Hello World'" 13 minutes ago Exited (0) 13 minutes ago quirky_mirzakhani

docker rm -v $(docker ps -aq -f status=exited) 命令可以删除所有已停止运行的容器

手动创建镜像文件

从 Docker Hub 上拉取的镜像很多为初始的精简系统,运行容器后,可以给容器内的系统安装软件并提交更改,做成新的镜像供后期使用。

使用 docker run 命令运行容器并安装软件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ docker run -it --name cowsay --hostname cowsay debian /bin/bash
root@cowsay:/# apt-get update
...
root@cowsay:/# apt-get install -y cowsay fortune
...
root@cowsay:/# /usr/games/fortune | /usr/games/cowsay
________________________
< Don't get to bragging. >
------------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||

其中 --name 选项用于指定容器的名称,--hostname 选项用于指定容器系统的主机名。

使用 docker commit 命令将容器转换成镜像文件。
1
2
3
4
5
6
$ docker commit cowsay test/cowsayimage
sha256:2dcb2f4d09f824120db19f79d3bfbdb85c24d4888732bcc7eaae79f707d80e32
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test/cowsayimage latest 2dcb2f4d09f8 13 minutes ago 159MB
debian latest f2aae6ff5d89 3 weeks ago 101MB

docker images 命令用来查看已在本地的镜像文件及其信息。

使用生成的镜像文件
1
2
3
4
5
6
7
8
9
10
$ docker run test/cowsayimage /usr/games/fortune | /usr/games/cowsay
_______________________________________
/ Today is the first day of the rest of \
\ the mess. /
---------------------------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
使用 Dockerfile 创建镜像文件

Dockerfile 其实就是一个简单的文本文件,里面包含了创建 Docker 镜像的一系列步骤。相比于手动创建 Docker 镜像,使用 Dockerfile 自动地创建镜像文件,省去了大量重复的操作,同时也便于分享给其他人。

编辑 Dockerfile
1
2
$ mkdir cowsay && cd cowsay
$ vim Dockerfile

然后在新建的 Dockerfile 中填入以下内容:

1
2
FROM debian:wheezy
RUN apt-get update && apt-get install -y cowsay fortune

其中 FROM 用于指定构建时使用的初始镜像文件,RUN 用于指定在镜像中执行的 Shell 命令

构建镜像并测试

使用 docker build 命令创建 Docker 镜像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ docker build -t test/cowsay-dockerfile .
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM debian:wheezy
wheezy: Pulling from library/debian
703d6f3fb41c: Pull complete
Digest: sha256:d00f167f8f2e70ecc2e0f5410a3cb74cd4ad720e33b9810da6a2dcfa81dccfc0
Status: Downloaded newer image for debian:wheezy
---> 94825a89630c
Step 2/2 : RUN apt-get update && apt-get install -y cowsay fortune
---> Running in efcf246bde0f
...
Setting up cowsay (3.03+dfsg1-4) ...
Removing intermediate container efcf246bde0f
---> 88e9a0f834cd
Successfully built 88e9a0f834cd
Successfully tagged test/cowsay-dockerfile:latest

测试刚构建好的 Docker 镜像:

1
2
3
4
5
6
7
8
9
$ docker run test/cowsay-dockerfile /usr/games/cowsay "Moo"
_____
< Moo >
-----
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||

ENTRYPOINT

Dockerfile 中的 ENTRYPOINT 选项可以用来指定容器运行时自动执行的命令。如将 Dockerfile 改为如下内容并重新构建:

1
2
3
FROM debian:wheezy
RUN apt-get update && apt-get install -y cowsay fortune
ENTRYPOINT ["/usr/games/cowsay"]

1
2
3
4
5
6
7
8
9
10
11
$ docker build -t test/cowsay-dockerfile .
...
$ docker run test/cowsay-dockerfile "Moo"
_____
< Moo >
-----
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||

运行上述容器时则不需要再指定命令(/usr/games/cowsay)而只输入命令的参数即可。

可以在当前目录下新建一个 entrypoint.sh 脚本:

1
2
3
4
5
6
#!/bin/bash
if [ $# -eq 0 ]; then
/usr/games/fortune | /usr/games/cowsay
else
/usr/games/cowsay "$@"
fi

上述内容为 Shell 脚本文件,不作详细解释。作用是当 docker run 没有为容器中执行的命令提供参数时,执行 fortune | cowsay ,如提供了参数,则执行 cowsay <参数> 命令。

将 entrypoint.sh 文件添加执行权限:chmod +x entrypoint.sh
将 Dockerfile 改为如下版本:

1
2
3
4
FROM debian:wheezy
RUN apt-get update && apt-get install -y cowsay fortune
COPY entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]

其中 COPY 选项用来将本地主机上的文件复制到镜像的文件系统里(类似于 cp 命令)

最终效果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ docker build -t test/cowsay-dockerfile .
...
$ docker run test/cowsay-dockerfile
_________________________________________
/ You don't become a failure until you're \
\ satisfied with being one. /
-----------------------------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
$ docker run test/cowsay-dockerfile Hello Moo
___________
< Hello Moo >
-----------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||

四、基本使用

端口转发

假如容器中运行着一个 Web 服务器,需要外部世界可以访问。此时可以使用 docker 的 -p-P 选项,将本地主机的端口转发至容器内的端口。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ docker run -d -p 8000:80 nginx
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
...
Status: Downloaded newer image for nginx:latest
be1364f343ae7aff9d5a6f6040b5d6ca69363a4480f04548988dd798ab04ab7b
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
be1364f343ae nginx "nginx -g 'daemon of…" 8 seconds ago Up 7 seconds 0.0.0.0:8000->80/tcp quirky_mclean
$ curl localhost:8000
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
$

其中 docker run -d -p 8000:80 nginx 命令用于在后台-d 选项)启动一个由官方 nginx 镜像创建的容器,并将本地主机的 8000 端口映射到容器的 80 端口(-p 8000:80),即容器中 nginx 服务运行的端口。
可以看到,当使用 curl 命令访问本地主机的 8000 端口时,等同于访问了容器的 80 端口,即容器中的 nginx 服务。

-P 选项可以自动选择空闲的端口进行转发,无需指定本地主机或容器的端口。可参考以下实例:

1
2
3
4
5
6
7
8
9
$ ID=$(docker run -d -P nginx)
$ docker port $ID 80
0.0.0.0:32768
$ curl localhost:32768
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

关联容器

容器关联可以允许同一个主机上的多个容器相互交换数据。当使用默认的网络模型时,这些关联的容器通过其“内部”网络传输数据,即关联容器间的相互交流不会暴露给本地主机。

可参考以下实例:

1
2
3
4
5
6
7
$ docker run --name myredis -d redis
be53967c42fd3292dfd59fd5d15e7025fa436816e46f6e7cbc3c47b06ddb0047
$ docker run --rm -it --link myredis:redis redis /bin/bash
root@b27aca7d8c54:/data# redis-cli -h redis -p 6379
redis:6379> ping
PONG
redis:6379>

其中 docker run --name myredis -d redis 命令用于在后台启动一个 redis 容器,并将其命名为 myredis 。同时返回该容器的 ID 到标准输出。
docker run --rm -it --link myredis:redis redis /bin/bash 命令用于启动另一个 redis 容器,并以交互的方式访问其 Shell (-it /bin/bash)。--rm 选项表示该容器退出后将自动删除。

容器关联的操作则由 --link myredis:redis 选项实现。表示将新容器关联至已存在的 “myredis” 容器,并为 “myredis” 容器设置别名为 “redis” ,即可以在当前的新容器中通过别名(redis)访问。

该选项会在新容器的 /etc/hosts 文件中添加一条主机名为 redis 的记录,并将其指向 “myredis” 容器的 IP 地址。所以在当前容器的 Shell 中使用 redis-cli 命令访问 “myredis” 中的服务时,可以无需指定其 IP 地址,直接使用主机名 “redis” 即可。(redis-cli -h redis -P 6379

存储卷

可以在使用 docker run 时通过 -v 选项指定存储卷:

1
2
3
4
5
6
$ docker run -it --name container-test -h CONTAINER -v /data debian /bin/bash
root@CONTAINER:/# cd /data ; touch test-file
root@CONTAINER:/data# ls
test-file
root@CONTAINER:/data# exit
exit

上面的命令会将容器中的 /data 目录变成一个存储卷,该目录下的任何文件都会被复制到卷中。

可以使用 docker inspect 命令查看该存储卷在本地主机中的位置:

1
2
$ docker inspect -f {{.Mounts}} container-test
[{volume 7ae1... /var/lib/docker/volumes/7ae1.../_data /data local true }]

可以在存储卷对应于本地主机的目录(/var/lib/docker/volumes/7ae1.../_data)中创建文件,容器中对应目录(/data)下则会立即出现同样的文件。

1
2
3
4
5
6
7
$ sudo ls /var/lib/docker/volumes/7ae1.../_data
test-file
$ sudo touch /var/lib/docker/volumes/7ae1.../_data/test-file2
$ docker start -i container-test
root@CONTAINER:/# ls /data
test-file test-file2
root@CONTAINER:/#

存储卷还可以通过 Dockerfile 中的 VOLUME 选项指定。如:VOLUME /data

共享数据

可以使用 -v HOST_DIR:CONTAINER_DIR 选项在本地主机和一(或多)个容器间共享数据。如:
$ docker run -v /home/starky/data:/data debian ls /data

该命令会将本地主机上的 /home/starky/data 目录挂载到容器中的 /data 目录下,所有已经存在于 /home/starky/data 目录下的文件在容器中也同样能够被访问。
但是原本存在于容器的 /data 目录下的文件则被隐藏。
如某些配置文件可以一直存放在本地主机上,并在需要时挂载到通用镜像构建的容器中。

可以使用 docker run 命令的 --volumes-from CONTAINER 选项在容器间共享数据。如:

1
2
3
4
$ docker run -it -h NEWCONTAINER --volumes-from container-test debian /bin/bash
root@NEWCONTAINER:/# ls /data
test-file test-file2
root@NEWCONTAINER:/#

上面的命令创建了一个新的容器,且通过 --volumes-from 选项,该容器可以访问之前的 container-test 容器中的存储卷(/data)。

数据容器

数据容器是一种特殊的容器,其唯一目的是为了方便其他容器之间共享数据。如创建一个 PostgreSQL 的数据容器:
$ docker run --name dbdata postgres echo "Data-only container for postgres"
该命令用于从 postgres 镜像创建一个容器,并初始化镜像里定义的存储卷。

之后可以通过 --volumes-from 选项使用数据容器里的存储卷:
$ docker run -d --volumes-from dbdata --name db1 postgres

参考资料

Using Docker: Developing and Deploying Software with Containers