第一本 Docker 书

第1章 简介

容器(container) 和 管理程序虚拟化(hypervisor virtualization) 不同:

  • HV, 通过中间层将一台或多台独立的机器虚拟运行于物理硬件之上
  • Container, 直接运行在操作系统内核之上的用户空间

容器只能运行与底层宿主机相同或相似的操作系统。

Docker 是为改变容器的复杂性。

1.1 Docker 简介

Docker 是一个能够把开发的应用程序自动部署到容器的开源引擎。所以说是和容器相关联。

Docker 在虚拟化的容器执行环境中增加了一个应用程序部署引擎。

1.1.1 提供一个简单、轻量的建模方式

Docker 依赖于”写时复制”模型。

1.1.2 职责的逻辑分离

1.1.3 快速、高效的开发生命周期

1.1.4 鼓励使用面向服务的架构

1.2 Docker 组件

核心组件:

  • Docker 客户端和服务器,也称为 Docker 引擎
  • Docker 镜像
  • Registry
  • Docker 容器

1.2.1 Docker 客户端和服务器

Docker 是一个客户端/服务器(C/S)架构的程序。

Docker 提供了一个命令行工具 docker 以及一整套 RESTFUL API 来与守护进程交互。

1.2.2 Docker 镜像

用户基于镜像来运行自己的容器。

可以把镜像当做容器的”源代码”.

1.2.3 Registry

Docker 用 Registry 来保存用户构建的镜像。

Registry 分为公有和私有两种。

可在 Docker Hub 上保存自己的私有镜像。

1.2.4 容器

Docker 可以帮用户构建和部署容器,用户只需要把自己的应用程序或服务打包放进容器即可。

Docker 容器是:

  • 一个镜像格式
  • 一系列标准的操作
  • 一个执行环境

Docker 借鉴了集装箱的概念,集装箱运输货物,Docker 运输软件。Docker 是运输工, 对容器进行操作。

每个容器都包含一个软件镜像,也就是容器的”货物”.

所有容器都按照相同的方式将内容”装载”进去,

1.3 能用 Docker 做什么

容器可以为各种测试提供很好的沙盒环境.

1.4 Docker 与配置管理

Docker 很轻量: 镜像是分层的,可以对其进行迅速的迭代.

Docker 一个显著特点就是,对不同的宿主机、应用程序和服务,可能会表现出不同的特性与架构。

1.5 Docker 的技术组件

第2章 安装 Docker

2.1 安装 Docker 的先决条件

2.2 在 Ubuntu 和 Debian 中安装 Docker

2.2.1 检查前提条件

1. 内核

使用:

1
$ uname -a

若下载新内核,可使用命令加载新内核:

1
2
$ sudo update-grub
$ reboot

2. 检查 Device Mapper

使用 Device Mapper 作为存储驱动。

Device Mapper 支持”自动精简配置”(thin-provisioning)的概念,可以在一种文件系统中存储多台虚拟设备(Docker镜像中的层).

确认是否安装:

1
$ ls -l /sys/class/misc/device-mapper

或:

1
$ sudo grep device-mapper /proc/devices

或:

1
$ sudo modprobe dm_mod

2.2.2 安装 Docker

官网查看.

2.2.3 Docker 与 UFW

UWF 即 Uncomplicated Firewall.

Docker 使用一个网桥来管理容器中的网络。

默认情况下,UFW 会丢弃所有转发的数据包,因此需要在 UFW 中启用数据包的转发.

修改 /etc/default/ufw 文件:

1
DEFAULT_FORWARD_POLICY="DROP"

改为:

1
DEFAULT_FORWARD_POLICY="ACCEPT"

重新加载:

1
$ sudo ufw reload

2.9 Docker 守护进程

用户可以使用 docker daemon 命令控制 Docker 守护进程。

守护进程监听 /var/run/docker.sock 这个 UNIX套接字文件,来获取来自客户端的 Docker 请求。

2.9.1 配置 Docker 守护进程

-H 标志调整守护进程绑定监听接口的方式, 可以使用 -H 标志指定不同的网络接口和端口配置:

1
$ sudo docker daemon -H tcp://0.0.0.0:2375

使用 -D 参数来输出 Docker 守护进程的详细信息:

1
$ sudo docker daemon -D

2.10 升级 Docker

1
2
$ sudo apt-get update
$ sudo apt-get install docker-engine

2.11 Docker 用户界面

第3章 Docker 入门

3.1 确保 Docker 已经就绪

1
$ sudo docker info

3.2 运行我们的第一个容器

docker run 命令提供了 Docker 容器的创建到启动的功能。

可以使用 docker help run 获取命令列表。

使用 man 页, man docker-run

1
$ sudo docker run -i -t ubuntu /bin/bash

-i 参数保证容器中 STDIN 是开启的。

-t 参数告诉 Docker 为要创建的容器分配一个伪 tty 终端.

ubuntu 是用来告诉 Docker 基于什么镜像来创建容器,ubuntu 镜像是一个常备镜像,也可以称为”基础”(base) 镜像,由 Docker 公司提供,保存在 Docker Hub Registry 上。

Docker 会检查本地是否存在 ubuntu 镜像,如果没有,Docker 就会连接官方维护的 Docker Hub Registry, 查看 Docker Hub 中是否存在该镜像,Docker 一旦找到该镜像,就会下载到本地。

随后,Docker 在文件系统内部用这个镜像创建了一个新容器,该容器拥有自己的网络、IP地址,以及一个用来和宿主机进行通信的桥接网络接口。

/bin/bash 是我们告诉 Docker 在容器中运行的命令。

3.3 使用第一个容器

我进入的界面为:

1
root@895508d7a56f:/#

这里 root 为 root 账户,895508d7a56f 为容器 ID(即主机名). 其为一个完整的 Ubuntu 系统。

获取主机名:

1
root@895508d7a56f:/# hostname

查看容器的网络配置:

1
root@895508d7a56f:/# ip a

使用 exit 命令可以退出容器。

docker ps -a 查看当前系统中容器的列表。

docker ps -l 列出最后一个运行的容器。

有三种方式可以唯一指定容器:

  • 短 UUID
  • 长 UUID
  • 名称

3.4 容器命名

Docker 会为我们创建的每一个容器自动生成一个随机的名称。

使用 --name 标志来指定名称:

1
$ sudo doker run --name bob_the_container -i -t ubuntu /bin/bash

一个合法的名称只能包括 [a-zA-Z0-9.-]

很多 Docker 命令中,都可以用容器的名称来替代容器 ID.

容器的命名是唯一的。

docker rm 可删掉容器。

3.5 重新启动已经停止的容器

启动停止的容器:

1
$ sudo docker start bob_the_container # 也可以使用容器 ID

重新启动用 docker restart

使用 docker create 创建一个容器但不运行它.

3.6 附着到容器上

Docker 容器重新启动的时候,会沿用 docker run 命令时指定的参数来运行。

也可以用 docker attach 命令重新附着到该容器的会话上:

1
$ sudo docker attach bob_the_container

3.7 创建守护式容器

除交互式运行的容器(interactive container) 还有守护式容器(daemonized container), 其没有交互式会话。

1
$ sudo docker run --name daemon_dave -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"

使用 -d 参数,即放入后台运行。

3.8 容器内部都在干些什么

使用 docker logs 获取容器的日志.

1
$ sudo docker logs daemon_dave

可加入 -f 选项:

1
$ sudo docker logs -f daemon_dave

获取最后十行:

1
$ sudo docker logs --tail 10 daemon_dave

加上时间戳:

1
$ sudo docker logs -ft daemon_dave

3.9 Docker 日志驱动

使用 --log-drive 选项:

1
$ sudo docker run --log-driver="syslog" --name daemon_dwayne -d ubuntu /bash/sh -c "while true; do echo hello world; sleep 1; done"

使用 syslog 时会警用 docker logs 命令,并将所有容器的日志输出都重定向到 Syslog.

若使用 none 则禁用日志。

3.10 查看容器内的进程

使用 docker top 命令:

1
$ sudo docker top daemon_dave

3.11 Docker 统计信息

docker stats 显示一个或多个容器的统计信息:

1
$ sudo docker stats daemon_dave daemon_kate daemon_clare

3.12 在容器内部运行进程

可以再容器内运行的进程有两种类型:

  • 后台任务
  • 交互式任务

使用 docker exec 命令:

1
$ sudo docker exec -d daemon_dave touch /etc/new_config_file

就是对一个正在运行的容器使用。

3.13 停止守护式容器

使用 docker stop 命令:

1
$ sudo docker stop daemon_dave

交互式可以通过 exit

想要快速停止某个容器,使用 docker kill:

显示最后 x 个容器:

1
$ sudo docker ps -n x

3.14 自动重启容器

使用 --restart 标志,磨人的行为是 Docker 不会重启容器。

1
$ sudo docker run --restart=always --name 

另一个 on-failure 参数,即在退出状态码为非 0 值时重启,且可指定重启次数:

1
--restart=on-failure:5

3.15 深入容器

docker inspect 获取更多容器信息:

1
$ sudo docker inspect daemon_dave

使用 -f--format 标志选定查看结果:

1
$ sudo docker inspect --format='{{ .State.Running }}' daemon_dave

可指定多个容器.

3.16 删除容器

使用 docker rm 命令:

1
$ sudo docker rm 80430f8d0921

可传递 -f 参数。

删除所有容器:

1
$ sudo docker rm `sudo docker ps -a -q`

第4章 使用 Docker 镜像和仓库

4.1 什么是 Docker 镜像

Docker 镜像是由文件系统叠加而成。

最低端是一个引导文件系统 bootfs.

当一个容器启动后,它会被移到内存中,而引导文件系统则会被卸载(unmount).

Docker 镜像的第二层是 root 文件系统 rootfs, 其位于引导文件系统之上。

Docker 利用联合加载技术(union mount), 即一次同时加载多个文件系统,但是在外面看起来只能看到一个文件系统。

联合加载会将各层文件系统叠加到一起,这样最终的文件系统会包含所有底层的文件和目录。

Docker 将这样的文件系统称为镜像。一个镜像可以放到另一个镜像的顶部。位于下面的镜像称为父镜像(parent image), 最底层的镜像称为基础镜像(base image).

当从一个镜像启动容器时,Docker 会在该镜像最顶层加载一个读写文件系统,我们在 Docke 中运行的程序就是在这个读写层中执行。

每个只读镜像层都是只读,并且以后永远不会变化。

写时复制(copy on write)机制,想修改一个文件,这个文件首先会从该读写层下面的只读层复制到该读写层,该文件的只读版本依然存在,但是被读写层中的该文件副本所隐藏。

4.2 列出镜像

列出主机上可用的镜像, 使用 docker images:

1
$ sudo docker images

本地镜像都保存在 Docker 宿主机的 /ver/lib/docker 目录下.

镜像从仓库下载,镜像保存在仓库中,仓库存在于 Registry 中。

每个镜像仓库都可以存放很多镜像.

使用 docker pull 命令来拉取仓库中的镜像。

可以通过在仓库名后面加上一个冒号和标签名来指定该仓库中的某一镜像:

1
$ sudo docker run -t -i --name new_container ubuntu:12.04 /bin/bash

一个镜像可以有多个标签。

Docker Hub 中有两种类型的仓库:

  • 用户仓库(user repository), 都是由 Docker 用户创建的, 其命名由用户名和仓库名两部分组成
  • 顶层仓库(top-level repository), 由 Docker 内部的人来管理, 其命名只包含仓库名

4.3 拉取镜像

如:

1
$ sudo docker pull fedora:20

4.4 查找镜像

使用 docker search 命令来查找所有 Docker Hub 上公共的可用镜像。

1
$ sudo docker search puppet

4.5 构建镜像

两种方法:

  • docker commit
  • docker build 和 Dockerfile 文件
    使用 Dockerfile 更加灵活.

4.5.1 创建 Docker Hub

登录到 Docker Hub, 使用 docker login 命令:
$ sudo docker login
用户的个人认证信息会被保存在 $HOME/.docker/config.json 中。

4.5.2 用 Docker 的 commit 命令创建镜像

1
$ sudo docker commit 4aab3ce3cb76 jamtur01/apache2

4aab3ce3cb76 是容器的 ID, jamtur01 是目标镜像仓库, apache2 是镜像名.

docker commit 提交的只是创建容器的镜像与容器的当前状态之间有差异的部分。

可以在提交镜像时指定更多的数据(包括标签):

1
$ sudo docker commit -m"A new custom image" -a"James Turnbull" 4aab3ce3cb76 jamtur01/apache2:webserver

-m 选项是创建镜像的提交信息。

-a 选项是该镜像的作者信息。

webserver 是标签名。

可以使用 docker inspect 命令来查看新创建的镜像的详细信息:

1
$ sudo docker inspect jamtur01/apache2:webserver

4.5.3 用 Dockerfile 构建镜像

一般不推荐使用 docker commit 来构建镜像。

Dockerfile 使用基本的基于 DSL(Domain Specific Language) 语法的指令来构建一个 Docker 镜像。

第一个 Dockerfile

创建的用来保存 Dockerfile 的目录称为构建环境(build environment), Docker 则称此环境为上下文(context)或构建上下文(build context),

Docker 会在构建镜像时将构建上下文和该上下文中的文件和目录上传到 Docker 守护进程。

例子:

1
2
3
4
5
6
7
# Version: 0.0.1
FROM ubuntu:14.04
MAINTAINER james Turnbull "james@example.com"
RUN apt-get update && apt-get install -y nginx
RUN echo 'Hi, I am in your container' \
>/usr/share/nginx/html/index.html
EXPOSE 80

Dockerfile 由指令和参数构成。

每条指令,如 FROM, 都必须为大写字母, 而且后面要跟随一个参数.

每条指令都会创建一个新的镜像层并对镜像进行提交。

运行流程:

  • Docker从基础镜像运行一个容器。
    -执行一条指令,对容器做出修改。
    -执行类似docker commit的操作,提交一个新的镜像层。
    -Docker再基于刚提交的镜像运行一个新容器。
    -执行Dockerfile中的下一条指令,直到所有指令都执行完毕

Dockerfile 中的注释是 # 开头的行.

MAINTAINER 指令告诉 Docker 该镜像的作者是谁,以及作者的电子邮件地址。

RUN 指令会在当前镜像中运行指定的命令. 每条 RUN 指令都会创建一个新的镜像层。

默认情况下,RUN 指令会在 shell 里使用 /bin/sh -c 来执行。

若在不支持 shell 的平台上,使用 exec 格式的 RUN 指令:

1
RUN ["appt-get", " install", "-y", "nginx"]

这里的 -y 参数是 yes 的意思。

EXPOSE 指令,告诉 Docker 该容器内的应用程序将会使用容器的指定端口。

可以指定多个 EXPOSE 指令来向外部公开多个端口。

4.5.4 基于 Dockerfile 构建新镜像

执行 docker build 命令时,Dockerfile 中的所有指令都会被执行并且提交,并且在该命令成功结束后返回一个新镜像。

使用 -t 选项为新镜像设置仓库和名称以及标签:

1
$ sudo docker build -t="jamtur01/static_web:v1" .

如果没有制定任何标签,Docker 将会自动为镜像设置一个 latest 标签.

最后一个 . 告诉 Docker 到本地目录中找 Dockerfile 文件,也可指定一个 Git 仓库的源地址来指定 Dockerfile 的位置.

1
$ sudo docker build -t="jamtur01/static_web:v1" git@github.com:jamtur01/docker-static_web

-f 参数指定文件:

1
$ sudo docker build -t="jamtur01/static_web" -f path/to/file

构建目录中的 .dockerignore 类似于 .gitignore

4.5.5 指令失败时会怎样

4.5.6 Dockerfile 和构建缓存

它会将之前的镜像层看做缓存,意思是第二次 docker build 时,会接着从上次结束时运行。

忽略缓存功能,使用 docker build --no-cache

1
$ sudo docker build --no-cache -t="jamtur01/static_web"

4.5.7 基于构建缓存的 Dockerfile 模板

一般在 Dockerfile 文件顶部使用相同的指令集模板:

1
2
3
4
FROM ubuntu:14.04
MAINTAINER James Turnbull "james@example.com"
ENV REFRESHED_AT 2014-07-01
RUN apt-get -qq update

ENV 指令用于在镜像中设置环境变量。

模板实际上就是自己常用的配置。

4.5.8 查看新镜像

深入探求镜像是如何被构建出来的,使用 docker history

1
$ sudo docker history 22d47c8cb6e5

4.5.9 从新镜像启动容器

1
$ sudo docker run -d -p 80 --name static_web jamtur01/static_web nginx -g "daemon off;"

-d 选项告诉 Docker 以分离(detached)的方式在后台运行。

-p 选项,用来控制 Docker 在运行时应该公开哪些网络端口给外部(宿主机).

Docker 可以通过两种方法来在宿主机上分配端口:

  • Docker 可以在宿主机上随机选择一个位于 32768~61000 的比较大的端口号来映射到容器的 80 端口上
  • 可以在 Docker 宿主机中指定一个具体的端口号来映射到容器中的 80 端口上

docker run 命令会在 Docker 宿主机上随即打开一个端口,连接到容器中的 80 端口上。

可用 docker ps 命令查看容器的端口分配情况:

1
$ sudo docker ps -l

也可通过 docker port 命令来查看容器的端口映射情况:

1
$ sudo docker port 6751b94bb5c0 80

使用 -p 选项指定容器中的端口映射到 Docker 宿主机的某一特定端口上:

1
$ sudo docker run -d -p 80:80 --name static_web

前者是宿主机端口,后者是容器端口。

将端口绑定限制在特定的网络接口(即IP地址)上:

1
$ sudo docker run -d -p 127.0.0.1:80:80 --name static_web jamtur01/static_web nginx -g "daemon off;"

其将容器内的 80 端口绑定到本地宿主机的 127.0.0.1 这个 IP 的 80 端口上. 不指定如 80 就是随机端口。

使用 -P 参数将 Dockerfile 中通过 EXPOSE 指令指定的端口公开:

1
$ sudo docker run -d -P --name static_web jamtur01/static_web nginx -g "daemon off;"

并绑定到随机端口。

有了这个端口号,就可以使用本地宿主机的 IP 地址连接到运行中的容器.

4.5.10 Dockerfile 指令

[清单](http://docs.docker.com/ reference /builder/)

CMD

用于指定一个容器启动时要运行的命令.

1
CMD ["/bin/bash", "-l"]

后面为参数.

这是存储在一个数组结构中.

docker run 命令后若指定要运行的命令, 会覆盖 Dockerfile 中的 CMD 指令.

在 Dockerfile 中只能指定一条 CMD 命令.

ENTRYPOINT

docker run 命令行中指定的任何参数都会被当做参数再次传递给 ENTRYPOINT 指令中指定的命令.

如:

1
ENTERYPOINT ["/usr/sbin/ nginx"]

然后:

1
2
$ sudo docker build -t="jamtur01/static_web"
$ sudo docker run -t -i jamtur01/static_web -g "daemon off;"

其中 -g "daemon off;" 就会传递给 ENTEYPOINT

可用 docker run--entrypoint 选项来覆盖 ENTRYPOINT 指令.

WORKDIR

用来从镜像创建一个容器时,在容器内部设置一个工作目录,ENTRYPOINT/CMD 指定的程序会在这个目录下执行.

可以设置不同的工作目录:

1
2
3
4
WORKDIR /opt/webapp/db
RUN bundle install
WORKDIR /opt/webapp
ENTRYPOINT ["rackup"]

可以通过 -w 选项在运行时设置工作目录:

1
$ sudo docker run -ti -w /var/log ubuntu pwd

ENV

用于设置环境变量:

1
ENV RVM_PATH /home/rvm/

也可通过 docker run 命令的 -e 参数来传递环境变量:

1
$ sudo docker run -ti -e "WEB_PORT=8000" ubuntu env

USER

指定该镜像以什么样的用户去执行:

1
USER nginx

可以指定用户名或 UID 以及组或 GID:

1
2
3
4
5
6
USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group

默认用户为 root, 可以用 docker run-u 选项覆盖.

VOLUME

作用

在没有在 docker run 时使用 -v 选项时, 创建一个隐藏数据卷.

什么是隐藏数据卷

也就是没有指定宿主机上面的某一个目录, 因此自动在 /var/lib/docker/volumes 目录下自动创建的一个卷.

VOLUME 的作用

比如我们在 Dockerfile 中写 VOLUME [ "/Blog" ], 并且在 run 的时候没有添加 -v 选项, 那么在容器创建后, 会生成 /Blog 这个目录, 以及在 /var/lib/docker/volumes 下生成一个卷, 这两个相互映射.
用来向基于镜像创建的容器添加卷, 一个卷是可以存在于一个或者多个容器内的特定的目录.

特点:

  • 卷可以在容器间共享和重用
  • 一个容器可以不是必须和其他容器共享卷
  • 对卷的修改是立时生效的
  • 对卷的修改不会对更新镜像产生影响
  • 卷会一直存在知道没有任何容器再使用它.

卷功能可以让我们将内容添加到镜像而不提交.

1
VOLUME ["/opt/project"]

为基于此镜像创建的任何容器创建一个名为 /opt/project 的挂载点.

docker cp 允许从容器复制文件和复制文件到容器上.

指定多个卷:

1
VOLUME ["/opt/project", "/data"]

ADD

用来将构建环境下的文件和目录复制到镜像中.

1
ADD software.lic /opt/application/software.lic

前者为原文件位置,后者为目的文件位置.

源文件位置也可以是 URL.

Docker 通过目的地址参数末尾的字符来判断文件源是目录还是文件:

  • 目的地址以 / 结尾, 则为目录
  • 不以, 则为文件

如果将压缩文件指定为源文件,会将其解压:

1
ADD latest.tar.gz /var/www/wordpress/

目前 Docker 还不支持以 URL 方式指定的源文件位置中使用归档文件.

ADD 指令会使构建缓存无效.

COPY

类似于 ADD, 但 COPY 只关心在构建上下文中复制本地文件,而不会 extraction 和 decompression.

1
COPY conf.d/ /etc/apache2/

文件源路径必须是一个与当前构建环境相对的文件或者目录, 本地文件都放到和 Dockerfile 同一个目录下,不能复制该目录之外的任何文件.

LABEL

用于为 Docker 镜像添加元数据. 元数据以键值对的形式展现:

1
2
LABEL version="1.0"
LAVEL location="New York" type="Data Center" role="Web Server"

推荐将所有元数据都放到一条 LABEL 指令中.

然后通过 docker inspect 命令来查看 Docker 镜像中的标签信息.

STOPSIGNAL

用来设置停止容器时发送什么系统调用给容器. 这个信号必须是内核系统调用表中合法的数或者 SIGNAME 格式中的信号名称.

ARG

用来定义可以在 docker build 命令运行时传递给构建运行时的变量, 只需在构建时使用 --build-arg 参数, 用户只能在构建时指定在 Dockerfile 文件中定义过的参数:

1
2
ARG build
ARG webapp_user=user

第二条指定了默认值:

1
$ sudo docker build --build-arg build=1234 -t jamtur01/webapp

不要使用 ARG 来传递证书或者密钥.

Docker 预定义了一组 ARG 变量:

1
2
3
4
5
6
7
8
HTTP_PROXY
http_proxy
HTTPS_PROXY
https_proxy
ftp_proxy
FTP_PROXY
NO_PROXY
no_proxy

ONBUILD

为镜像添加触发器(trigger). 当一个镜像被用作其他镜像的基础镜像时, 该镜像中的触发器将会被执行.

触发器会在构建过程中插入新指令, 我们可以认为这些指令是紧跟在 FROM 之后指定的:

1
2
ONBUILD ADD . /app/arc
ONBUILD RUN cd /app/src && make

ONBUILD 指令可以在镜像上运行 docker inspect 查看.

ONBUILD 触发器会按照父镜像中指定的顺序执行, 并且只能被继承一次.

有部分指令不能用在 ONBUILD 中, 防止递归调用.

4.6 将镜像推送到 Docker Hub

使用 docker push 命令:

1
$ sudo docker push jamtur01/static_web

应该要先登录, 用 docker login

自动构建(Automated Builds)

将 Github 或 BitBucket 中含有 Dockerfile 文件的仓库连接到 Docker Hub 即可. 具体参考书籍.

4.7 删除镜像

使用 docker rmi:

1
$ sudo docker rmi jamtur01/static_web

每一个 Deleted: 行都代表一个镜像层被删除.

4.8 运行自己的 Docker Registry

Registry 就相当于 repository.

4.8.1 从容器运行 Registry

从容器安装一个 Registry:

1
$ sudo docker run -p 5000:5000 registry:2

启动一个运行 Registry 应用 2.0 版本的容器.

4.8.2 测试新 Registry

先通过 docker images 找到镜像 ID:

1
$ sudo docker images jamtur01/static_web

然后用 Registry 给镜像打上标签:

1
$ sudo docker tag 22d47c8cb6e5

最后通过 docker push 将其推送到 Registry 中, 为了指定新的 Registry 的地址, 需要在镜像名前加上主机名和端口前缀:

1
$ sudo docker push docker.example.com:5000/jamtur01/static_web

使用其构建新容器:

1
$ sudo docker run -t -i docker.example.com:5000/jamtur01/static_web

4.9 其他可选的 Registry 服务

第5章 在测试中使用 Docker

5.1 使用 Docker 测试静态网站

将 Docker 作为本地 Web 开发环境是 Docker 的一个最简单的应用场景。

要想保持 Docker 容器的活跃状态,需要其中运行的进程不能中断,默认情况下,Nginx 会以守护进程的方式启动,这会导致容器只是短暂运行,在守护进程被 fork 后,发起守护进程的原始进程就会退出,这时容器就停止运行了.

5.1.2 构建 Sample 网站和 Nginx 镜像

5.1.3 从 Sample 网站和 Nginx 镜像构建容器

1
$ sudo docker run -d -p 80 --name website -v $PWD/website:/var/www/html/website jamtur01/nginx nginx

这里的 -v 选项允许我们将宿主机的目录作为卷,挂载到容器里。

两个目录用 : 分隔,前者为宿主机目录,后者为容器目录,如果容器目录不存在,Docker 会自动创建一个。

可以通过在目录后面加上 rwro 来指定容器内目录的读写状态:

1
$ sudo docker run -d -p 80 --name website -v $PWD/website:/var/www/html/website:ro jamtur01/nginx nginx

卷的概念

卷是在一个或者多个容器内被选定的目录,可以绕过分层的联合文件系统,为 Docker 提供持久数据或者共享数据。

对卷的修改会立即生效,并绕过镜像,当提交或者创建镜像时,卷不被包含在镜像里。

卷是从宿主机而来。

5.1.4 修改网站

直接修改本地宿主机的 website 目录下的 index.html 文件, 也就是卷内的文件:

1
$ vim $PWD/website/index.html

5.2 使用 Docker 构建并测试 Web 应用程序

Sinatra 是一个基于 Ruby 的 Web 应用框架,它包含一个 Web 应用库,以及简单的领域专用语言 (DSL) 来构建 Web 应用。

Sinatra 非常适合用来创建一个小型的示例应用进行测试。

5.2.1 构建 Sinatra 应用程序

5.2.4 将 Sinatra 应用程序连接到 Redis 容器

两种比较现实的连接 Docker 容器的方法是:

  • Docker Networking, Docker 1.9 及更新版本推荐使用
  • Docker link, Docker 1.9 之前版本推荐使用

Docker Networking 和 Docker link 的区别:

  • Docker Networking 可以将容器连接到不同宿主机上的容器
  • 通过 Docker Networking 连接的容器可以在无需更新连接的情况下,对停止、启动或者重启容器。
  • 使用 Docker Networking, 不必事先创建容器再去连接它。同样不必关心容器的运行顺序。

5.2.5 Docker 内部连网

在安装 Docker 时,会创建一个新的网络接口,名字是 docker0.

每个 Docker 容器都会在这个接口上分配一个 IP 地址。

启动 IPv6, 在运行 Docker 守护进程时加上 --ipv6 标志.

接口本身的地址是这个 Docker 网络的网关地址,也是所有 Docker 容器的网关地址。

Docker 会默认使用 172.17.x.x 作为子网地址,除非被占用,Docker 就会在 172.16~172.30 这个范围内尝试创建子网.

Docker 每创建一个容器就会创建一组互联的网络接口,其中一端作为容器里的 eth0 接口,而另一端统一命名为 vethec6a 这种名字,作为宿主机的一个端口.

通过把每个 veth* 接口绑定到 docker0 网桥,Docker 创建了一个虚拟子网,这个子网由宿主机和所有的 Docker 容器共享。

从容器内跟踪路由:

1
2
# apt-get -yqq update && apt-get install -yqq traceroute
# traceroute google.com

查看宿主机的 IPTables NAT 配置:

1
$ sudo iptables -t nat -L -n

Docker 默认会把公开的端口绑定到所有的网络接口上。

如果重启容器,Docker 会改变容器的 IP 地址.

5.2.6 Docker Networking

容器之间的连接用网络创建,被称为 Docker Networking.

Docker Networking 允许用户创建自己的网络,容器可以通过这个网上互相通信.

使用 Docker Networking 需先创建一个网络,然后在这个网络下启动容器:

1
$ sudo docker network create app

create 后接名称.

查看新创建的网络:

1
$ sudo docker network inspect app

Docker 多宿主机网络文档

列出当前系统中的所有网络:

1
$ docker network ls

删除一个 Docker 网络:

1
$ docker network rm

添加容器:

1
$ sudo docker run -d --net==app --name db jamtur01/redis

--net 标志指定了新容器将会在哪个网络中运行。

Docker 会感知所有在这个网络下运行的容器,并且通过 /etc/hosts 文件将这些容器的地址保存到本地 DNS 中。

app 网络内部的任何主机都可以使用 hostname.app 形式来被解析。

在宿主机下如:

1
$ ping db.app

将已有容器连接到 Docker 网络

1
$ sudo docker network connect app db2

这里 app 为网络名称,db2 为容器名称.

断开一个容器与指定网络的链接

1
$ sudo docker network disconnect app db2

一个容器可以同时隶属多个 Docker Networking.

1
$ sudo docker run -p 4567 --name webapp --link redis:db -t -i -v $PWD/webapp_redis:/opt/webapp jamtur01/sinatra /bin/bash

--link 标志,用于创建两个容器间的客户-服务链接。其需要两个参数,前者是要链接的容器的名称,后一个是这个链接的别名。这里 webapp 是客户,redis 是”服务”.

通过把容器链接在一起,可以让客户容器直接访问任意服务容器的公开端口.

只有使用 --link 标志链接到这个容器的容器才能连接到这个端口。

可以把多个容器链接在一起:

1
2
$ sudo docker run -p 4567 --name webapp2 --link redis:db ...
$ sudo docker run -p 4567 --name webapp3 --link redis:db ...

在运行容器时指定 --add-host 选项,可以在 /etc/hosts 文件中添加相应的记录:

1
$ sudo docker run -p 4567 --add-host=docker:10.0.0.1 ...

在容器中可以用 env 命令查看新创建的环境变量的信息.

5.2.7 使用容器连接来通信

5.3 Docker 用于持续集成

即在多开发者的持续集成测试场景中使用 Docker.

第6章 使用 Docker 构建服务

6.1 构建第一个应用

6.1.1 JekyII 基础镜像

6.1.2 构建 Jekyll 基础镜像

6.1.5 启动 Jekyll 网站

启动一个叫做 james_blog 的新容器, 把本地的 james_blog 目录作为 /data/ 卷挂载到容器里:

1
2
$ sudo docker run -v /home/james/james_blog:/data/ \
--name james_blog jamtur01/jekyll

卷在 Docker 宿主机的 /var/lib/docker/volumes 目录中, 可以通过 docker inspect 命令查看某个卷的具体位置.

如果想在另一个容器里使用 /var/www/html/ 卷里编译好的网站, 可以建立一个新的链接到这个卷的容器:

1
$ sudo docker run -d -P --volumes-from jemes_blog jamtur01/apache 091570cc2267

--volumes-from 标志, 把指定容器里的所有卷都加入新创建的容器里.

卷 这个概念感觉实际上就是挂载, 你在 Docker 里面修改了这个 卷 里的内容, 宿主机上面的目录也会更新.

6.1.7 备份 Jekyll 卷

如:

1
2
3
$ sudo docker run --rm --volumes-from james_blog \
-v $(pwd):/backup ubuntu \
tar cvf /backup/james_blog_backup.tar /var/www/html

上面命令的解释为, --rm 参数表明会在容器的进程运行完毕后, 自动删除容器, --volumes-from james_blog 指定从哪个容器中获取 volumes, -v $(pwd):/backup 将当前目录 (即用来存备份文件的目录) 挂载到容器中的 /backup 目录下, ubuntu 是 image 的名称, tar cvf /backup/james_blog_backup.tar /var/www/html 是将 /var/www/html 打包为 /backup/james_blog_backup.tar 文件.

6.1.8 扩展 Jekyll 示例网站

6.2 使用 Docker 构建一个 Java 应用服务

6.2.1 WAR 文件的获取程序

6.3 多容器的应用栈

docker run 中的 -h 标志, 用于指定容器的主机名.

6.4 不使用 SSH 管理 Docker 容器

传统上讲, 通过 SSH 登入运行环境或者虚拟机来管理服务.

在 Docker 里, 大部分容器都只运行一个进程, 所以不能使用这种访问方法.

需要登入容器时, 可以使用 nsenter 工具.

nsenter 可以进入一个已经存在的容器的 shell, 即便这个容器没有运行 SSH 或任何类似目的的守护进程. 可以通过 Docker 容器安装 nsenter:

1
$ sudo docker run -v /usr/local/bin:/target jpetazzo/nsenter

具体见书.

第7章 Docker 编配和服务发现

Docker Compose 是 Docker 的编配工具.

7.1 Docker Compose

使用 Docker Compose, 可以用一个 YAML 文件定义一组要启动的容器, 以及容器运行时的属性. Docker Compose 称这些容器为 “服务”.

7.1.1 安装 Docker Compose

三种方法:

  • 直接安装
  • Docker Toolbox
  • Python Pip

如:

1
sudo pip install -U docker-compose

具体见书.

7.1.2 获取示例应用

7.1.3 docker-compose.yml 文件

执行 docker-compose up 命令, Compose 会启动这些容器, 使用指定的参数来执行, 并将所有日志输出合并到一起.

示例 docker-compose.yml 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
version: '3'
services:
web:
image: jamtur01/composeapp
command: python app.py
ports:
- "5000:5000"
volumes:
- .:/composeapp
links:
- redis
redis:
image: redis

links 指定了要连接到服务的其他服务.

可以在 Docker Compose 官网查看 docker-compose.yml 所有可用指令列表. 位置

7.1.4 运行 Compose

创建并启动服务:

1
$ docker-compose up

为了保证服务是唯一的, Compose 将 docker-compose.yml 文件中指定的服务名称加上目录名作为前缀, 并分别使用数字作为后缀.

Compose 之后接管了每个服务输出的日志, 输出的日志每一行都使用缩短的服务名称最为前缀, 并交替输出在一起.

服务和 Compose 交替运行, 这意味着, 如果使用 Ctrl+C 来停止 Compose 运行, 也会停止运行的服务.

可以在运行 Compose 时指定 -d 标志, 其将以守护进程来运行服务.

1
$ docker-compose up -d

7.1.5 使用 Compose

可以用 docker-compose ps 命令来查看服务的运行状态:

1
$ docker-copose ps

使用 docker-compose help +命令 来查看相关用法.

docker-compose stop 可以停止正在运行的服务.

docker-compose kill 强制杀死服务.

使用 docker-compose start 命令重启.

docker-compose rm 来删除这些服务.

7.3 Docker Swarm

Docker Swarm 是一个原生的 Docker 集群管理工具.

Swarm 将一组 Docker 主机作为一个虚拟的 Docker 主机来管理.

7.3.1 安装 Swarm

拉取 Docker 公司提供的 swarm 镜像即可:

1
$ docker pull swarm

运行 Swarm 的所有 Docker 节点都必须运行统一个版本的 Docker.

7.3.2 创建 Swarm 集群

集群中的每台主机上都运行着一个 Swarm 节点代理 , 每个代理都将改主机上的相关 Docker 守护进程注册到集群中.

和节点代理相对的是 Swarm 的管理者, 用于对集群进行管理.

通过集群发现后端 (discovery backend, 如 Docker Hub) 来实现集群注册. 默认为 Docker Hub.

在 Docker Hub 中注册一个集群, 然后返回一个集群 ID, 之后使用这个集群 ID 向集群添加额外的节点.

1
$ docker run --rm swarm create

此命令会返回一个 字符串, 即集群 ID.

运行 Swarm 代理:

1
2
smoker $ docker run -d swarm join --addr=10.0.0.125:2375 \
token://b811b0bc438sifjvajhfiakfidjcbaj83hai

使用 join 并用 --addr 传递本机 IP 地址.

token://b811b0bc438sifjvajhfiakfidjcbaj83hai 这一串就是指定2集群 ID.

查看代理节点的列表:

1
$ docker run --rm swarm list token://b811b0bc438sifjvajhfiakfidjcbaj83hai

可在任意安装了 Docker 的主机上执行.

创建 Swarm 集群管理者:

1
$ docker run -d -p 2380:2375 swarm manage token://b811b0bc438sifjvajhfiakfidjcbaj83hai

第一本 Docker 书
http://example.com/2022/08/25/第一本-Docker-书/
作者
Jie
发布于
2022年8月25日
许可协议