文章出处:https://blog.csdn.net/xc_zhou/article/details/80952307
1 Docker 容器
容器是 Docker 又一核心概念。
简单的说,容器是独立运行的一个或一组应用,以及它们的运行态环境。对应的,虚拟机可以理解为模拟运行的一整套操作系统(提供了运行态环境和其他系统环境)和跑在上面的应用。
2 启动容器
启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态(stopped)的容器重新启动。
因为 Docker 的容器实在太轻量级了,很多时候用户都是随时删除和新创建容器。
2.1 新建并启动
所需要的命令主要为 docker run
。
例如,下面的命令输出一个 “Hello World”,之后终止容器。
1 | $ docker run ubuntu:14.04 /bin/echo 'Hello world' |
这跟在本地直接执行 /bin/echo 'hello world'
几乎感觉不出任何区别。
下面的命令则启动一个 bash 终端,允许用户进行交互。
1 | $ docker run -t -i ubuntu:14.04 /bin/bash root@af8bae53bdd3:/# |
其中,-t
选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上, -i
则让容器的标准输入保持打开。
在交互模式下,用户可以通过所创建的终端来输入命令,例如
1 | root@af8bae53bdd3:/# pwd |
容器的核心为所执行的应用程序,所需要的资源都是应用程序运行所必需的。除此之外,并没有其它的资源。可以在伪终端中利用 ps
或 top
来查看进程信息。
1 | root@ba267838cc1b:/# ps |
可见,容器中仅运行了指定的 bash 应用。这种特点使得 Docker 对资源的利用率极高,是货真价实的轻量级虚拟化。
当利用 docker run
来创建容器时,Docker 在后台运行的标准操作包括:
- 检查本地是否存在指定的镜像,不存在就从公有仓库下载
- 利用镜像创建并启动一个容器
- 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
- 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
- 从地址池配置一个 ip 地址给容器
- 执行用户指定的应用程序
- 执行完毕后容器被终止
2.2 查看正在运行中的容器
利用 docker ps
命令可以查看正在运行中的容器
1 | $ docker ps |
2.3 查看所有容器
利用 docker ps -a
命令可以查看所有容器
1 | $ docker ps -a |
2.4 启动已终止的容器
可以利用 docker start
命令和上面使用 docker ps -a
查看到的 CONTAINER ID
或 NAMES
,直接将一个已经终止的容器启动运行。
1 | $ docker start relaxed_kilby |
这里把 新建并启动 章节中的容器又启动了一次,这次这个容器和之前不一样,他启动之后就会被终止,不会输出一个 “Hello World”,之后才终止容器。可以看 STATUS
输出,这个容器的确被启动过.
2.5 容器后台运行
更多的时候,需要让 Docker在后台运行而不是直接把执行命令的结果输出在当前宿主机下。此时,可以通过添加 -d
参数来实现。
下面举两个例子来说明一下。
如果不使用 -d
参数运行容器。
1 | $ sudo docker run ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done" |
容器会把输出的结果(STDOUT)打印到宿主机上面
如果使用了 -d
参数运行容器。
1 | $ sudo docker run -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done" 77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a |
此时容器会在后台运行并不会把输出的结果(STDOUT)打印到宿主机上面(输出结果可以用docker logs 查看)。
注: 容器是否会长久运行,是和docker run指定的命令有关,和 -d
参数无关。
使用 -d
参数启动后会返回一个唯一的 id,也可以通过 docker ps
命令来查看容器信息。
1 | $ sudo docker ps |
要获取容器的输出信息,可以通过 docker logs
命令。
1 | $ sudo docker logs [container ID or NAMES] hello world |
2.6 终止容器
可以使用 docker stop
命令和上面使用的 docker ps -a
查看到的 CONTAINER ID
或 NAMES
,来终止一个运行中的容器。
1 | $ docker stop web2 |
此外,当 Docker 容器中指定的应用终结时,容器也自动终止。例如启动了一个终端的容器,用户通过 exit
命令或 Ctrl+d
来退出终端时,所创建的容器立刻终止
2.7 重启容器
docker restart
命令会将一个运行态的容器终止,然后再重新启动它。
3 进入容器
在使用 -d
参数时,容器启动后会进入后台。
某些时候需要进入容器进行操作,有很多种方法,包括使用 docker attach
命令或 nsenter
工具等。
3.1 attach 命令
docker attach
是Docker自带的命令。下面示例如何使用该命令。
1 | $ sudo docker run -idt ubuntu 243c32535da7d142fb0e6df616a3c3ada0b8ab417937c853a9e1c251f499f550 |
是使用 attach
命令有时候并不方便。当多个窗口同时 attach 到同一个容器的时候,所有窗口都会同步显示。当某个窗口因命令阻塞时,其他窗口也无法执行操作了。
3.2 nsenter 命令
3.2.1 安装
nsenter
工具在 util-linux 包2.23版本后包含。
可以使用 nsenter -V
查看系统是否安装了 nsenter
工具
1 | $ nsenter -V |
如果系统中 util-linux 包没有该命令,可以按照下面的方法从源码安装。
1 | $ cd /tmp; curl https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz | tar -zxf-; cd util-linux-2.24; |
3.2.2 使用
senter
启动一个新的shell进程(默认是/bin/bash), 同时会把这个新进程切换到和目标(target)进程相同的命名空间,这样就相当于进入了容器内部。nsenter 要正常工作需要有 root 权限。
为了连接到容器,你还需要找到容器的第一个进程的 PID,可以通过下面的命令获取。
1 | PID=$(docker inspect --format "{{ .State.Pid }}" <container>) |
通过这个 PID,就可以连接到这个容器:
1 | $ nsenter --target $PID --mount --uts --ipc --net --pid |
如果无法通过以上命令连接到这个容器,有可能是因为宿主的默认 shell 在容器中并不存在,比如zsh,可以使用如下命令显式地使用bash
1 | $ nsenter --target $pid --mount --uts --ipc --net --pid -- /usr/bin/env \ --ignore-environment HOME=/root /bin/bash --login |
下面给出一个完整的例子。
1 | $ sudo docker run -idt ubunt |
4 导出和导入容器快照
4.1 导出容器快照
如果要导出本地某个容器,可以使用 docker export
命令。
1 | $ sudo docker ps -a |
这样将导出容器快照到本地文件。
4.2 导入容器快照
可以使用 docker import
从容器快照文件中再导入为镜像,例如
1 | $ cat ubuntu.tar | sudo docker import - test/ubuntu:v1.0 |
此外,也可以通过指定 URL 或者某个目录来导入,例如
1 | $sudo docker import http://example.com/exampleimage.tgz example/imagerepo |
*注:用户既可以使用 docker load
来导入镜像存储文件到本地镜像库,也可以使用 docker import
来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也$sudo docker rm trusting_newton trusting_newton要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。
5 删除容器
可以使用 docker rm
来删除一个处于终止状态的容器。
1 | $sudo docker rm trusting_newton trusting_newton |
如果要删除一个运行中的容器,可以添加 -f
参数。Docker 会发送 SIGKILL
信号给容器。
6 清理所有处于终止状态的容器(不建议使用)
用 docker ps -a
命令可以查看所有已经创建的包括终止状态的容器,如果数量太多要一个个删除可能会很麻烦,用 docker rm $(docker ps -a -q)
可以全部清理掉。
*注意:这个命令其实会试图删除所有的包括还在运行中的容器,不过就像上面提过的 docker rm
默认并不会删除运行中的容器
7 私有仓库
有时候使用阿里云这样的公共仓库可能不方便,用户可以创建一个本地仓库供自己使用。
7.1 如何使用本地仓库
docker-registry
是官方提供的工具,可以用于构建私有的镜像仓库
7.2 安装运行 docker-registry
7.2.1 容器中运行 docker-registry
在安装了 Docker 后,可以通过获取官方 registry 镜像来运行。
1 | $ sudo docker run -d -p 5000:5000 registry |
这将使用官方的 registry 镜像来启动本地的私有仓库。
用户可以通过指定参数来配置私有仓库位置,例如配置镜像存储到 Amazon S3 服务。
1 | $ sudo docker run \ |
此外,还可以指定本地路径(如 /home/user/registry-conf
)下的配置文件。
1 | $ sudo docker run -d -p 5000:5000 -v /home/user/registry-conf:/registry-conf -e DOCKER_REGISTRY_CONFIG=/registry-conf/config.yml registry |
默认情况下,仓库会被创建在容器的 /var/lib/registry
(v1 中是/tmp/registry
)下。可以通过 -v
参数来将镜像文件存放在本地的指定路径。
例如下面的例子将上传的镜像放到 /opt/data/registry
目录。
1 | $ sudo docker run -d -p 5000:5000 -v /opt/data/registry:/var/lib/registry registry |
7.2.2 本地安装 docker-registry
对于 CentOS 发行版,可以直接通过源安装。
1 | $ sudo yum install -y python-devel libevent-devel python-pip gcc xz-devel |
也可以从 docker-registry 项目下载源码进行安装。
1 | $ sudo apt-get install build-essential python-dev libevent-dev python-pip libssl-dev liblzma-dev libffi-dev |
然后修改配置文件,主要修改 dev 模板段的 storage_path
到本地的存储仓库的路径。
1 | $ cp config/config_sample.yml config/config.yml |
之后启动 Web 服务。
1 | $ sudo gunicorn -c contrib/gunicorn.py docker_registry.wsgi:application |
或者
1 | $ sudo gunicorn --access-logfile - --error-logfile - -k gevent -b 0.0.0.0:5000 -w 4 --max-requests 100 docker_registry.wsgi:application |
此时使用 curl 访问本地的 5000 端口,看到输出 docker-registry 的版本信息说明运行成功
*注:config/config_sample.yml
文件是示例配置文件。
7.3 在私有仓库上传、下载、搜索镜像
创建好私有仓库之后,就可以使用 docker tag
来标记一个镜像,然后推送它到仓库,别的机器上就可以下载下来了。例如私有仓库地址为 192.168.7.26:5000
。
先在本机查看已有的镜像。
1 | $ sudo docker images |
使用docker tag
将 ba58
这个镜像标记为 192.168.7.26:5000/test
(格式为 docker tag IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
)。
1 | $ sudo docker tag ba58 192.168.7.26:5000/test |
使用 docker push
上传标记的镜像。
1 | $ sudo docker push 192.168.7.26:5000/test |
用 curl 查看仓库中的镜像。
1 | $ curl http://192.168.7.26:5000/v1/search |
这里可以看到 {"description": "", "name": "library/test"}
,表明镜像已经被成功上传了。
现在可以到另外一台机器去下载这个镜像。
1 | $ sudo docker pull 192.168.7.26:5000/test |
8 Docker 数据管理
8.1 数据卷
数据卷是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多有用的特性:
- 数据卷可以在容器之间共享和重用
- 对数据卷的修改会立马生效
- 对数据卷的更新,不会影响镜像
- 数据卷默认会一直存在,即使容器被删除
- 注意:数据卷的使用,类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的数据卷。
8.2 创建一个数据卷
在用 docker run
命令的时候,使用 -v
标记来创建一个数据卷并挂载到容器里。在一次 run 中多次使用可以挂载多个数据卷。
下面创建一个名为 web 的容器,并加载一个数据卷到容器的 /webapp
目录。
1 | $ sudo docker run -d -P --name web -v /webapp training/webapp python app.py |
*注意:也可以在 Dockerfile 中使用 VOLUME
来添加一个或者多个新的卷到由该镜像创建的任意容器。
8.3 删除数据卷
数据卷是被设计用来持久化数据的,它的生命周期独立于容器,Docker不会在容器被删除后自动删除数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的数据卷。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用 docker rm -v
这个命令。无主的数据卷可能会占据很多空间,要清理会很麻烦。Docker官方正在试图解决这个问题,相关工作的进度可以查看这个PR。
8.4 挂载一个主机目录作为数据卷
使用 -v
标记也可以指定挂载一个本地主机的目录到容器中去。
1 | $ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py |
上面的命令加载主机的 /src/webapp
目录到容器的 /opt/webapp
目录。这个功能在进行测试的时候十分方便,比如用户可以放置一些程序到本地目录中,来查看容器是否正常工作。本地目录的路径必须是绝对路径,如果目录不存在 Docker 会自动为你创建它。
*注意:Dockerfile 中不支持这种用法,这是因为 Dockerfile 是为了移植和分享用的。然而,不同操作系统的路径格式不一样,所以目前还不能支持。
Docker 挂载数据卷的默认权限是读写,用户也可以通过 :ro
指定为只读。
1 | $ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp:ro training/webapp python app.py |
加了 :ro
之后,就挂载为只读了。
8.5 查看数据卷的具体信息
在主机里使用以下命令可以查看指定容器的信息
1 | $ docker inspect web |
在输出的内容中找到其中和数据卷相关的部分,可以看到所有的数据卷都是创建在主机的/var/lib/docker/volumes/
下面的
1 | "Volumes": { "/webapp": "/var/lib/docker/volumes/fac362...80535" |
注:从Docker 1.8.0起,数据卷配置在”Mounts”Key下面,可以看到所有的数据卷都是创建在主机的/mnt/sda1/var/lib/docker/volumes/....
下面了。
1 | Mounts": [ |
8.6 挂载一个本地主机文件作为数据卷
-v
标记也可以从主机挂载单个文件到容器中
1 | $ sudo docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash |
这样就可以记录在容器输入过的命令了。
*注意:如果直接挂载一个文件,很多文件编辑工具,包括 vi
或者 sed --in-place
,可能会造成文件 inode 的改变,从 Docker 1.1
.0起,这会导致报错误信息。所以最简单的办法就直接挂载文件的父目录。
8.7 数据卷容器
如果你有一些持续更新的数据需要在容器之间共享,最好创建数据卷容器。
数据卷容器,其实就是一个正常的容器,专门用来提供数据卷供其它容器挂载的。
首先,创建一个名为 dbdata 的数据卷容器:
1 | $ sudo docker run -d -v /dbdata --name dbdata training/postgres echo Data-only container for postgres |
然后,在其他容器中使用 --volumes-from
来挂载 dbdata 容器中的数据卷。
1 | $ sudo docker run -d --volumes-from dbdata --name db1 training/postgres |
可以使用超过一个的 --volumes-from
参数来指定从多个容器挂载不同的数据卷。
也可以从其他已经挂载了数据卷的容器来级联挂载数据卷。
1 | $ sudo docker run -d --name db3 --volumes-from db1 training/postgres |
*注意:使用 --volumes-from
参数所挂载数据卷的容器自己并不需要保持在运行状态。
如果删除了挂载的容器(包括 dbdata、db1 和 db2),数据卷并不会被自动删除。如果要删除一个数据卷,必须在删除最后一个还挂载着它的容器时使用 docker rm -v
命令来指定同时删除关联的容器。
这可以让用户在容器之间升级和移动数据卷。
9 利用数据卷容器来备份、恢复、迁移数据卷
可以利用数据卷对其中的数据进行进行备份、恢复和迁移
9.1 备份
首先使用 --volumes-from
标记来创建一个加载 dbdata 容器卷的容器,并从主机挂载当前目录到容器的 /backup 目录。命令如下:
1 | $ sudo docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata |
容器启动后,使用了 tar
命令来将 dbdata 卷备份为容器中 /backup/backup.tar 文件,也就是主机当前目录下的名为 backup.tar
的文件。
9.2 恢复
如果要恢复数据到一个容器,首先创建一个带有空数据卷的容器 dbdata2。
1 | $ sudo docker run -v /dbdata --name dbdata2 ubuntu /bin/bash |
然后创建另一个容器,挂载 dbdata2 容器卷中的数据卷,并使用 untar
解压备份文件到挂载的容器卷中。
1 | $ sudo docker run --volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar |
为了查看/验证恢复的数据,可以再启动一个容器挂载同样的容器卷来查看
1 | $ sudo docker run --volumes-from dbdata2 busybox /bin/ls /dbdata |
10 Docker 中的网络功能介绍
Docker 允许通过外部访问容器或容器互联的方式来提供网络服务。
10.1 外部访问容器
容器中可以运行一些网络应用,要让外部也可以访问这些应用,可以通过 -P
或 -p
参数来指定端口映射。
当使用 -P 标记时,Docker 会随机映射一个 49000~49900
的端口到内部容器开放的网络端口。
使用 docker ps
可以看到,本地主机的 49155 被映射到了容器的 5000 端口。此时访问本机的 49155 端口即可访问容器内 web 应用提供的界面。
1 | $ sudo docker run -d -P training/webapp python app.py |
同样的,可以通过 docker logs
命令来查看应用的信息。
1 | $ sudo docker logs -f nostalgic_morse |
-p(小写的)则可以指定要映射的端口,并且,在一个指定端口上只可以绑定一个容器。支持的格式有 ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort
。
10.2 映射所有接口地址
使用 hostPort:containerPort
格式本地的 5000 端口映射到容器的 5000 端口,可以执行
1 | $ sudo docker run -d -p 5000:5000 training/webapp python app.py |
10.3 映射到指定地址的指定端口
可以使用 ip:hostPort:containerPort
格式指定映射使用一个特定地址,比如 localhost 地址 127.0.0.1
1 | $ sudo docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py |
10.4 映射到指定地址的任意端口
使用 ip::containerPort
绑定 localhost 的任意端口到容器的 5000 端口,本地主机会自动分配一个端口。
1 | $ sudo docker run -d -p 127.0.0.1::5000 training/webapp python app.py |
还可以使用 udp 标记来指定 udp 端口
1 | $ sudo docker run -d -p 使用 `docker port` 来查看当前映射的端口配置,也可以查看到绑定的地址127.0.0.1:5000:5000/udp training/webapp python app.py |
10.5 查看映射端口配置
使用 docker port
来查看当前映射的端口配置,也可以查看到绑定的地址
1 | $ docker port nostalgic_morse 5000 |
注意:
- 容器有自己的内部网络和 ip 地址(使用
docker inspect
可以获取所有的变量,Docker 还可以有一个可变的网络配置。) - -p 标记可以多次使用来绑定多个端口
例如1
$ sudo docker run -d -p 5000:5000 -p 3000:80 training/webapp python app.py
11 容器互联
容器的连接(linking)系统是除了端口映射外,另一种跟容器中应用交互的方式。
该系统会在源和接收容器之间创建一个隧道,接收容器可以看到源容器指定的信息
11.1 自定义容器命名
连接系统依据容器的名称来执行。因此,首先需要自定义一个好记的容器命名。
虽然当创建容器的时候,系统默认会分配一个名字。自定义命名容器有2个好处
- 自定义的命名,比较好记,比如一个web应用容器我们可以给它起名叫web
- 当要连接其他容器时候,可以作为一个有用的参考点,比如连接web容器到db容器
使用 --name
标记可以为容器自定义命名。
1 | $ sudo docker run -d -P --name web training/webapp python app.py |
使用 docker ps
来验证设定的命名
1 | $ sudo docker ps -l |
也可以使用 docker inspect
来查看容器的名字
1 | $ sudo docker inspect -f "{{ .Name }}" aed84ee21bde |
注意:容器的名称是唯一的。如果已经命名了一个叫 web 的容器,当你要再次使用 web 这个名称的时候,需要先用docker rm
来删除之前创建的同名容器。
在执行 docker run
的时候如果添加 --rm
标记,则容器在终止后会立刻删除。注意,--rm
和 -d
参数不能同时使用。
11.2 容器互联
使用 --link
参数可以让容器之间安全的进行交互。
下面先创建一个新的数据库容器。
1 | $ sudo docker run -d --name db training/postgres |
除之前创建的 web 容器
1 | $ docker rm -f web |
然后创建一个新的 web 容器,并将它连接到 db 容器
1 | $ sudo docker run -d -P --name web --link db:db training/webapp python app.py |
此时,db 容器和 web 容器建立互联关系。
--link
参数的格式为 --link name:alias
,其中 name
是要链接的容器的名称,alias
是这个连接的别名。
使用 docker ps
来查看容器的连接
1 | $ docker ps |
可以看到自定义命名的容器,db 和 web,db 容器的 names 列有 db 也有 web/db。这表示 web 容器链接到 db 容器,web 容器将被允许访问 db 容器的信息。
Docker 在两个互联的容器之间创建了一个安全隧道,而且不用映射它们的端口到宿主主机上。在启动 db 容器的时候并没有使用 -p
和 -P
标记,从而避免了暴露数据库端口到外部网络上。
Docker 通过 2 种方式为容器公开连接信息
- 环境变量
- 更新
/etc/hosts
文件
使用env
命令来查看 web 容器的环境变量其中 DB_ 开头的环境变量是供 web 容器连接 db 容器使用,前缀采用大写的连接别名。1
2
3
4$ sudo docker run --rm --name web2 --link db:db training/webapp env
. . .
DB_NAME=/web2/db DB_PORT=tcp://172.17.0.5:5432 DB_PORT_5000_TCP=tcp://172.17.0.5:5432 DB_PORT_5000_TCP_PROTO=tcp DB_PORT_5000_TCP_PORT=5432 DB_PORT_5000_TCP_ADDR=172.17.0.5
. . .
除了环境变量,Docker 还添加 host 信息到父容器的 /etc/hosts
的文件。下面是父容器 web 的 hosts 文件
1 | $ sudo docker run -t -i --rm --link db:db training/webapp /bin/bash |
这里有 2 个 hosts,第一个是 web 容器,web 容器用 id 作为他的主机名,第二个是 db 容器的 ip 和主机名。
可以在 web 容器中安装 ping 命令来测试跟db容器的连通。
1 | root@aed84ee21bde:/opt/webapp# apt-get install -yqq inetutils-ping root@aed84ee21bde:/opt/webapp# ping db |
用 ping 来测试db容器,它会解析成 172.17.0.5
。
*注意:官方的 ubuntu 镜像默认没有安装 ping,需要自行安装。
用户可以链接多个父容器到子容器,比如可以链接多个 web 到 db 容器上。