Docker 使用指南

[TOC]

介绍

在接触Docker之前,一直使用自建的开发环境来工作。

对于个人来说就是在换电脑的时候麻烦,换电脑了开发环境就得重新部署一次。差不得得要个半天时间…

对于公司团队来说更加麻烦了,每个同事部署的环境不一致,Windows系统与*nix系统的不兼容等.代码跑起来有差异。

Docker是一个类似于虚拟机的设计,一种新兴化的虚拟化方式。

特性Docker虚拟机
启动秒级分钟级
硬盘使用一般为MB一般为GB
性能接近原生弱于原生
系统支持量单主机支持上千个容器一般几十个

Docker有三个关键概念:镜像(images)、容器(container)、仓库(registry)。其中镜像与容器两者关系类似于面向对象设计中的实例一样

镜像是静态的定义,容器是镜像运行是的实体

镜像加速:官方Docker Hub拉取镜像网络不稳定,推荐两个镜像仓库


安装

略~

使用Docker

镜像

拉取镜像
1
docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]

具体的选项可以通过 docker pull --help 命令看到,这里我们说一下镜像名称的格式。

  • Docker 镜像仓库地址:地址的格式一般是 <域名/IP>[:端口号]。默认地址是 Docker Hub。
  • 仓库名:如之前所说,这里的仓库名是两段式名称,即 <用户名>/<软件名>。对于 Docker Hub,如果不给出用户名,则默认为 library,也就是官方镜像。

举个🌰

1
2
3
4
5
6
7
8
9
$ docker pull ubuntu:16.04
16.04: Pulling from library/ubuntu
bf5d46315322: Pull complete
9f13e0ac480c: Pull complete
e8988b5b3097: Pull complete
40af181810e7: Pull complete
e6f7c7e5c03e: Pull complete
Digest: sha256:147913621d9cdea08853f6ba9116c2e27a3ceffecf3b492983ae97c3d643fbbe
Status: Downloaded newer image for ubuntu:16.04
运行

有了镜像之后,就能够以这个镜像为基础启动并运行一个容器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ docker run -it --rm \
ubuntu:16.04 \
bash

root@e7009c6ce357:/# cat /etc/os-release
NAME="Ubuntu"
VERSION="16.04.4 LTS, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04.4 LTS"
VERSION_ID="16.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"

docker run 就是运行容器的命令

  • -it:这是两个参数,一个是 -i:交互式操作,一个是 -t 终端。我们这里打算进入 bash 执行一些命令并查看返回结果,因此我们需要交互式终端。
  • --rm:这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动 docker rm。我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用 --rm 可以避免浪费空间。
  • ubuntu:16.04:这是指用 ubuntu:16.04 镜像为基础来启动容器。
  • bash:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 bash

进入容器后,我们可以在 Shell 下操作,执行任何所需的命令。这里,我们执行了 cat /etc/os-release,这是 Linux 常用的查看当前系统版本的命令,从返回的结果可以看到容器内是 Ubuntu 16.04.4 LTS 系统。

最后我们通过 exit 退出了这个容器。

常用命令
  • 列出镜像

    1
    $ docker image ls
  • 查看镜像、容器、数据卷所占用空间

    1
    $ docker system df
  • 列出所有(包含中间层)镜像

    1
    $ docker image ls -a

容器

启动容器

启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态(stopped)的容器重新启动。

因为 Docker 的容器实在太轻量级了,很多时候用户都是随时删除和新创建容器。

所需要的命令主要为 docker run

例如,下面的命令输出一个 “Hello World”,之后终止容器。

1
2
$ docker run ubuntu:14.04 /bin/echo 'Hello world'
Hello world

这跟在本地直接执行 /bin/echo 'hello world' 几乎感觉不出任何区别。

下面的命令则启动一个 bash 终端,允许用户进行交互。

1
2
$ docker run -t -i ubuntu:14.04 /bin/bash
root@af8bae53bdd3:/#

其中,-t 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上, -i 则让容器的标准输入保持打开。

在交互模式下,用户可以通过所创建的终端来输入命令,例如

1
2
3
4
root@af8bae53bdd3:/# pwd
/
root@af8bae53bdd3:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var

当利用 docker run 来创建容器时,Docker 在后台运行的标准操作包括:

  • 检查本地是否存在指定的镜像,不存在就从公有仓库下载
  • 利用镜像创建并启动一个容器
  • 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
  • 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
  • 从地址池配置一个 ip 地址给容器
  • 执行用户指定的应用程序
  • 执行完毕后容器被终止
启动已终止容器

可以利用 docker container start 命令,直接将一个已经终止的容器启动运行。

后台运行

更多的时候,需要让 Docker 在后台运行而不是直接把执行命令的结果输出在当前宿主机下。此时,可以通过添加 -d 参数来实现。

1
2
$ docker run -d ubuntu:17.10 /bin/sh -c "while true; do echo hello world; sleep 1; done"
77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a
终止容器

可以使用 docker container stop 来终止一个运行中的容器。

此外,当 Docker 容器中指定的应用终结时,容器也自动终止。

例如对于上一章节中只启动了一个终端的容器,用户通过 exit 命令或 Ctrl+d 来退出终端时,所创建的容器立刻终止。

终止状态的容器可以用 docker container ls -a 命令看到。例如

1
2
3
4
docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ba267838cc1b ubuntu:14.04 "/bin/bash" 30 minutes ago Exited (0) About a minute ago trusting_newton
98e5efa7d997 training/webapp:latest "python app.py" About an hour ago Exited (0) 34 minutes ago backstabbing_pike

处于终止状态的容器,可以通过 docker container start 命令来重新启动。

此外,docker container restart 命令会将一个运行态的容器终止,然后再重新启动它。

进入容器

在使用 -d 参数时,容器启动后会进入后台。

某些时候需要进入容器进行操作,包括使用 docker attach 命令或 docker exec 命令,推荐大家使用 docker exec 命令,原因会在下面说明。

1.attach 命令

docker attach 是 Docker 自带的命令。下面示例如何使用该命令。

1
2
3
4
5
6
7
8
9
$ docker run -dit ubuntu
243c32535da7d142fb0e6df616a3c3ada0b8ab417937c853a9e1c251f499f550

$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
243c32535da7 ubuntu:latest "/bin/bash" 18 seconds ago Up 17 seconds nostalgic_hypatia

$ docker attach 243c
root@243c32535da7:/#

注意: 如果从这个 stdin 中 exit,会导致容器的停止。

2.exec 命令

-i -t 参数

docker exec 后边可以跟多个参数,这里主要说明 -i -t 参数。

只用 -i 参数时,由于没有分配伪终端,界面没有我们熟悉的 Linux 命令提示符,但命令执行结果仍然可以返回。

-i -t 参数一起使用时,则可以看到我们熟悉的 Linux 命令提示符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ docker run -dit ubuntu
69d137adef7a8a689cbcb059e94da5489d3cddd240ff675c640c8d96e84fe1f6

$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
69d137adef7a ubuntu:latest "/bin/bash" 18 seconds ago Up 17 seconds zealous_swirles

$ docker exec -i 69d1 bash
ls
bin
boot
dev
...

$ docker exec -it 69d1 bash
root@69d137adef7a:/#

如果从这个 stdin 中 exit,不会导致容器的停止。这就是为什么推荐大家使用 docker exec 的原因。

更多参数说明请使用 docker exec --help 查看。

导出、导入容器
  • 导出容器

    使用 docker export 命令.

    1
    2
    3
    4
    $ docker container ls -a
    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
    7691a814370e ubuntu:14.04 "/bin/bash" 36 hours ago Exited (0) 21 hours ago test
    $ docker export 7691a814370e > ubuntu.tar
  • 导入容器

    可以使用 docker import 从容器快照文件中再导入为镜像,例如

    1
    2
    3
    4
    $ cat ubuntu.tar | docker import - test/ubuntu:v1.0
    $ docker image ls
    REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
    test/ubuntu v1.0 9d37a6082e97 About a minute ago 171.3 MB

    此外,也可以通过指定 URL 或者某个目录来导入,例如

    1
    $ docker import http://example.com/exampleimage.tgz example/imagerepo

    注:用户既可以使用 docker load 来导入镜像存储文件到本地镜像库,也可以使用 docker import 来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。

容器常用命令
  • 新建并启动

    1
    $ docker run -it ubuntu:16.10
  • 查看容器

    1
    $ docker container ls
  • 启动已终止容器

    1
    $ docker container start [containerID]
  • 后台运行容器

    1
    $ docker run -itd ubuntu:18.10 #添加 -d 参数
  • 终止容器

    1
    $ docker container stop [containerID]
  • 进入容器

    1
    2
    $ docker attach xxx
    $ docker exec -i xxx iterm
  • 导出和导入容器

    1
    2
    3
    4
    5
    #导出
    $ docker export containerID > xxx.tar
    #导入
    $ cat ubuntu.tar | docker import - mytest/ubuntu:v1.0
    $ docker import http://example.com/ubuntu.tgz mynewtest/image
  • 删除容器

    1
    2
    3
    $ docker container rm [containerID]
    #清理所有终止状态容器
    $ docker container prune

Docker数据管理

数据卷(volume)

数据卷 是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多有用的特性:

  • 数据卷 可以在容器之间共享和重用
  • 数据卷 的修改会立马生效
  • 数据卷 的更新,不会影响镜像
  • 数据卷 默认会一直存在,即使容器被删除

注意:数据卷 的使用,类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的 数据卷

创建一个数据卷
1
$ docker volume create my-vol

查看所有的 数据卷

1
2
3
$ docker volume ls

local my-vol

在主机里使用以下命令可以查看指定 数据卷 的信息

1
2
3
4
5
6
7
8
9
10
11
$ docker volume inspect my-vol
[
{
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
"Name": "my-vol",
"Options": {},
"Scope": "local"
}
]
启动一个挂载数据卷的容器

在用 docker run 命令的时候,使用 --mount 标记来将 数据卷 挂载到容器里。在一次 docker run中可以挂载多个 数据卷

下面创建一个名为 web 的容器,并加载一个 数据卷 到容器的 /webapp 目录。

1
2
3
4
5
6
$ docker run -d -P \
--name web \
# -v my-vol:/wepapp \
--mount source=my-vol,target=/webapp \
training/webapp \
python app.py
查看数据卷的具体信息

在主机里使用以下命令可以查看 web 容器的信息

1
$ docker inspect web

数据卷 信息在 “Mounts” Key 下面

1
2
3
4
5
6
7
8
9
10
11
12
"Mounts": [
{
"Type": "volume",
"Name": "my-vol",
"Source": "/var/lib/docker/volumes/my-vol/_data",
"Destination": "/app",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
删除数据卷
1
$ docker volume rm my-vol

数据卷 是被设计用来持久化数据的,它的生命周期独立于容器,Docker 不会在容器被删除后自动删除 数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的 数据卷。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用 docker rm -v 这个命令。

无主的数据卷可能会占据很多空间,要清理请使用以下命令

1
$ docker volume prune
挂载主机目录
挂载一个主机目录作为数据卷

使用 --mount 标记可以指定挂载一个本地主机的目录到容器中去。

1
2
3
4
5
6
$ docker run -d -P \
--name web \
# -v /src/webapp:/opt/webapp \
--mount type=bind,source=/src/webapp,target=/opt/webapp \
training/webapp \
python app.py

上面的命令加载主机的 /src/webapp 目录到容器的 /opt/webapp目录。这个功能在进行测试的时候十分方便,比如用户可以放置一些程序到本地目录中,来查看容器是否正常工作。本地目录的路径必须是绝对路径,以前使用 -v 参数时如果本地目录不存在 Docker 会自动为你创建一个文件夹,现在使用 --mount参数时如果本地目录不存在,Docker 会报错。

Docker 挂载主机目录的默认权限是 读写,用户也可以通过增加 readonly 指定为 只读

1
2
3
4
5
6
$ docker run -d -P \
--name web \
# -v /src/webapp:/opt/webapp:ro \
--mount type=bind,source=/src/webapp,target=/opt/webapp,readonly \
training/webapp \
python app.py

加了 readonly 之后,就挂载为 只读 了。如果你在容器内 /opt/webapp 目录新建文件,会显示如下错误

1
2
/opt/webapp # touch new.txt
touch: new.txt: Read-only file system
查看数据卷的具体信息

在主机里使用以下命令可以查看 web 容器的信息

1
$ docker inspect web

挂载主机目录 的配置信息在 “Mounts” Key 下面

1
2
3
4
5
6
7
8
9
10
"Mounts": [
{
"Type": "bind",
"Source": "/src/webapp",
"Destination": "/opt/webapp",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
挂载一个本地主机文件作为数据卷

--mount 标记也可以从主机挂载单个文件到容器中

1
2
3
4
5
6
7
8
9
$ docker run --rm -it \
# -v $HOME/.bash_history:/root/.bash_history \
--mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history \
ubuntu:17.10 \
bash

root@2affd44b4667:/# history
1 ls
2 diskutil list

这样就可以记录在容器输入过的命令了。

网络

外部访问容器

容器中可以运行一些网络应用,要让外部也可以访问这些应用,可以通过 -P-p 参数来指定端口映射。

当使用 -P 标记时,Docker 会随机映射一个 49000~49900 的端口到内部容器开放的网络端口。

使用 docker container ls 可以看到,本地主机的 49155 被映射到了容器的 5000 端口。此时访问本机的 49155 端口即可访问容器内 web 应用提供的界面。

1
2
3
4
5
$ docker run -d -P training/webapp python app.py

$ docker container ls -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bc533791f3f5 training/webapp:latest python app.py 5 seconds ago Up 2 seconds 0.0.0.0:49155->5000/tcp nostalgic_morse

同样的,可以通过 docker logs 命令来查看应用的信息。

1
2
3
4
$ docker logs -f nostalgic_morse
* Running on http://0.0.0.0:5000/
10.0.2.2 - - [23/May/2014 20:16:31] "GET / HTTP/1.1" 200 -
10.0.2.2 - - [23/May/2014 20:16:31] "GET /favicon.ico HTTP/1.1" 404 -

-p 则可以指定要映射的端口,并且,在一个指定端口上只可以绑定一个容器。支持的格式有 ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort

映射所有接口地址

使用 hostPort:containerPort 格式本地的 5000 端口映射到容器的 5000 端口,可以执行

1
$ docker run -d -p 5000:5000 training/webapp python app.py

此时默认会绑定本地所有接口上的所有地址。

映射到指定地址的指定端口

可以使用 ip:hostPort:containerPort 格式指定映射使用一个特定地址,比如 localhost 地址 127.0.0.1

1
$ docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py
映射到指定地址的任意端口

使用 ip::containerPort 绑定 localhost 的任意端口到容器的 5000 端口,本地主机会自动分配一个端口。

1
$ docker run -d -p 127.0.0.1::5000 training/webapp python app.py

还可以使用 udp 标记来指定 udp 端口

1
$ docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py
查看映射端口配置

使用 docker port 来查看当前映射的端口配置,也可以查看到绑定的地址

1
2
$ docker port nostalgic_morse 5000
127.0.0.1:49155.

注意:

  • 容器有自己的内部网络和 ip 地址(使用 docker inspect 可以获取所有的变量,Docker 还可以有一个可变的网络配置。)
  • -p 标记可以多次使用来绑定多个端口

例如

1
2
3
4
5
$ docker run -d \
-p 5000:5000 \
-p 3000:80 \
training/webapp \
python app.py

或者

1
$ docker container run -p 8000:3000 -it koa-demo /bin/bash

参考资料

  1. Docker—从入门到实践

  2. Docker 入门教程

  3. Docker-中文手册

  4. Docker-Documentation