一、概念
1.1 镜像
Docker镜像是一个特殊的文件系统,除了提供容器运行时所需要的程序、库、资源和配置等文件外,还包含了一些运行时准备的参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
1.2 容器
镜像和容器的关系,就像面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。 容器可以被创建、启动、停止、删除、暂停等。
1.3 仓库
镜像构建完成后,可以方便的在宿主主机上运行,但是需要在其他服务器上使用这个镜像,就需要一个集中的存储、分发镜像的服务。Docker Registe就是这样。一个Docker Register可以包含多个仓库,每个仓库可以包含多个标签,每个标签对应一个镜像。
通过<仓库名>:<标签>的格式来指定具体那个版本的镜像,如果不给<标签>,将以latest作为默认标签。
二、使用镜像
2.1 获取镜像
获取镜像命令格式:
$ docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]
运行镜像:
docker run -it --rm ubuntu:18.04 bash
- -it:这是两个参数,-i 交互式,-t 终端
- –rm:容器退出后,随之将其删除,默认情况下不删除,除非手动rm
- ubuntu:18.04:指定镜像为基础启动容器,ubuntu为镜像名,18.04为tag标记
- bash:放在镜像后的是命令
2.2 查看镜像
docker image ls
2.3 删除镜像
docker image rm [选项] <镜像1> [<镜像2> ...]
2.4 利用commit理解镜像构建
参考:https://yeasy.gitbook.io/docker_practice/image/commit
2.5 使用Dockerfile构建镜像
创建Dockerfile:
touch Dockerfile
然后输入下面内容到Dockerfile,每一个指令的前缀都必须是大写:
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
2.5.1 FROM:指定基础镜像
- 指定基础镜像,如果以
scratch
为基础镜像的话,意味着你不以任何镜像为基础; - 不以任何系统为基础,直接将可执行文件复制进镜像的做法并不罕见,对于linux下静态编译的程序来说,并不需要操作系统提供运行时支持,所需要的一切库都在可执行文件里面了,直接以
FROM scratch
会让镜像更小,常用于go语言开发的应用。
2.5.2 RUN:执行命令
- 用于执行命令行命令;
- shell格式:RUN <命令>
- exec 格式:RUN [“可执行文件”, “参数1”, “参数2”]
Dockerfile中每个指令都会建立一层,RUN也不例外,每个RUN的行为就和手工建立镜像一样:新建立一层,在其上执行这些命令,执行结束后,commit这一层的修改,构成新镜像。
Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是不得超过 127 层。???
可以使用&&
和\
把多个命令放在一个RUN指令中:
FROM debian:stretch
RUN set -x; buildDeps='gcc libc6-dev make wget' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDeps
2.5.3 构建镜像
构建镜像命令:
docker build [选项] <上下文路径/URL/->
例如:
docker build -t nginx:v3 .
docker build -t nginx:test -f Dockerfile .
2.5.4 构建镜像上下文(Context)
如果注意,会看到docker bulid 命令最后有一个.
。.
表示当前目录,而Dockerfile就在当前目录,因此不少初学者以为这个路径是在指定Dockerfile所在路径,这么理解其实是不准确的。如果对应上面的命令格式,你可能会发现,这是在指定上下文路径
。那么什么是上下文呢?
首先我们要理解docker bulid的工作原理。Docker在运行时分为Docker引擎(也就是服务端守护进程)和客户端工具。Docker的引擎提供了一组REST API,被称为Docker Remote API,而如docker 命令这样的客户端工具,则是通过这组API与Docker引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种docker功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。也因为这种C/S
设计,让我们操作远程服务器的Docker引擎变得轻而易举。
当我们进行镜像构建的时候,并非所有定制都会通过RUN指令完成,经常会需要将一些本地文件复制进镜像,比如通过COPY
指令、ADD
指令等。**而docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是Docker引擎中构建的。**那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?
这就引入了上下文的概念。**当构建的时候,用户会指定构建镜像上下文的路径,docker build命令得到这个路径后,会将路径下的所有内容打包,然后上传给Docker引擎。**这样Docker引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。
如果在Dockerfile中这么写:COPY ./package.json /app/
,这并不是要复制执行docker build命令所在的目录下的package.json,也不是复制Dockerfile所在目录下的package.json,而是复制上下文(context)目录下的package.json。
因此,COPY这类指令中的源文件的路径都是相对路径。这也是初学者经常会问的为什么COPY ../package.json /app
或者COPY /opt/xxxx /app
无法工作的原因,因为这些路径已经超出了上下文的范围,Docker引擎无法获得这些位置的文件。如果真的需要那些文件,应该将它们复制到上下文目录中去。
现在就可以理解刚才的命令docker build -t nginx:v3 .
中.
,实际上是在指定上下文的目录,docker build命令会将该目录下的内容打包交给Docker引擎以帮助构建镜像。
那么为什么会有人误以为.
是指定Dockerfile所在目录呢?**这是因为在默认情况下,如果不额外指定Dockerfile的话,会将上下文目录下的名为Dockerfile的文件作为Dockerfile。这只是默认行为,实际上Dockerfile的文件名并不要求必须为Dockerfile,而且并不要求必须位于上下文目录中,比如可以用 -f … /Dockerfile.php **参数指定某个文件作为Dockerfile。
2.6 Dockerfile命令详解
- COPY 复制文件
- ADD 更高级的复制文件
- CMD 容器启动命令
- ENTRYPOINT 入口点
- ENV 设置环境变量
- ARG 构建参数
- VOLUME 定义匿名卷
- EXPOSE 暴露端口
- WORKDIR 指定工作目录
- USER 指定当前用户
- HEALTHCHECK 健康检查
- ONBUILD 为他人作嫁衣
三、操作容器
容器的核心为所执行的应用程序,所需要的资源都是应用程序运行所必需的。除此之外,并没有其它的资源。
3.1 启动
启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态(exited)的容器重新启动。
3.1.1 新建并启动
所需要的命令为:docker run,例如:
1 $ docker run ubuntu:18.04 /bin/echo 'Hello world'
2 Hello world
3 $ docker run -t -i ubuntu:18.04 /bin/bash
4 root@af8basedad3:/#
- -t 指docker分配一个伪终端并绑定到标准输入上
- -i 则让容器的标准输入保持打开(也就是交互式)
- 容器启动失败查看:docker logs -f -t --tail 20 【容器名】
当利用 docker run 来创建容器时,Docker 在后台运行的标准操作包括:
- 检查本地是否存在指定的镜像,不存在就从 registry 下载
- 利用镜像创建并启动一个容器
- 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
- 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
- 从地址池配置一个 ip 地址给容器
- 执行用户指定的应用程序
- 执行完毕后容器被终止
3.1.2 启动已终止容器
启动已终止(exited)的容器,可以使用:docker container start
命令。
$ docker container stop 4b87f3074a7b
4b87f3074a7b
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4b87f3074a7b ubuntu "/bin/bash" 2 minutes ago Exited (0) 3 seconds ago focused_germain
$ docker container start 4b87f3074a7b
3.2 守护态运行
更多的时候,需要让 Docker 在后台运行而不是直接把执行命令的结果输出在当前宿主机下。可以通过添加-d
参数,实现运行守护态。例如:
1 $ docker run -d ubuntu:18.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
2 77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a
使用 -d 参数启动后会返回一个唯一的 id,也可以通过 docker container ls
命令来查看容器信息。
3.3 终止
可以使用:docker container stop
来终止一个运行中的容器。此外,当 Docker 容器中指定的应用终结时,容器也自动终止。
终止状态的容器可以用 docker container ls -a 命令看到。
处于终止状态的容器,可以通过 docker container start 命令来重新启动。
此外,docker container restart 命令会将一个运行态的容器终止,然后再重新启动它。
3.4 进入容器
在使用-d参数时,容器启动后会进入后台。某些时候需要进入容器进入容器进行操作,可以使用docker attach
命令或docker exec
命令,推荐使用docker exec命令。
3.4.1 attach 命令
$ 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,会导致容器的停止。(想让其继续在后台运行)
3.4.2 exec 命令
docker exec 后边可以跟多个参数,这里主要说明 -i -t 参数。
只用 -i 参数时,由于没有分配伪终端,界面没有我们熟悉的 Linux 命令提示符,但命令执行结果仍然可以返回。
当 -i -t 参数一起使用时,则可以看到我们熟悉的 Linux 命令提示符。
$ 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 的原因。
3.5 导入和导出
如果要导出本地某个容器,可以使用 docker export 命令。
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7691a814370e ubuntu:18.04 "/bin/bash" 36 hours ago Exited (0) 21 hours ago test
$ docker export 7691a814370e > ubuntu.tar
这样将导出容器快照到本地文件。
可以使用 docker import 从容器快照文件中再导入为镜像,例如
$ 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 或者某个目录来导入,例如
$ docker import http://example.com/exampleimage.tgz example/imagerepo
注:用户既可以使用 docker load 来导入镜像存储文件到本地镜像库,也可以使用 docker import 来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。
3.6 删除
可以使用 docker container rm
来删除一个处于终止状态的容器。例如
$ docker container rm trusting_newton
trusting_newton
如果要删除一个运行中的容器,可以添加 -f 参数。Docker 会发送 SIGKILL 信号给容器。
用 docker container ls -a
命令可以查看所有已经创建的包括终止状态的容器,如果数量太多要一个个删除可能会很麻烦,用下面的命令可以清理掉所有处于终止状态的容器。
$ docker container prune
四、访问仓库
4.1 Docker Hub
- 注册:你可以在 https://hub.docker.com 免费注册一个 Docker 账号。
- 登录:可以通过执行
docker login
命令交互式的输入用户名及密码来完成在命令行界面登录 Docker Hub。你可以通过docker logout
退出登录。 - 拉取镜像:你可以通过
docker search
命令来查找官方仓库中的镜像,并利用docker pull
命令来将它下载到本地。 - 推送镜像:用户也可以在登录后通过
docker push
命令来将自己的镜像推送到 Docker Hub。(需登录)
4.2 私有仓库
https://yeasy.gitbook.io/docker_practice/repository/registry
4.3 私有仓库高级配置
https://yeasy.gitbook.io/docker_practice/repository/registry_auth
五、数据管理
数据卷 是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多有用的特性:
- 数据卷可以在容器之间共享和重用
- 对数据卷的修改会立马生效
- 对数据卷的更新,不会影响到镜像
- 数据卷默认会一直存在,即使容器被删除
注意:数据卷的使用,类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会复制到数据卷中(仅数据卷为空时会复制)。
5.1 数据卷
5.1.1 创建一个数据卷
$ docker volume create my-vol
查看所有的数据卷:docker volume ls
,查看指定数据卷的信息:docker volume inspect my-vol
5.1.2 启动一个挂载数据卷的容器
在用 docker run
命令的时候,使用 --mount
标记来将 数据卷 挂载到容器里。在一次 docker run
中可以挂载多个 数据卷。
下面创建一个名为 web
的容器,并加载一个 数据卷 到容器的 /usr/share/nginx/html
目录。
$ docker run -d -P \
--name web \
# -v my-vol:/usr/share/nginx/html \
--mount source=my-vol,target=/usr/share/nginx/html \
nginx:alpine
source=my-vol,target=/usr/share/nginx/html
表示挂载数据卷my-vol容器的/usr/share/nginx/html目录
5.1.3 查看数据卷的具体信息
在主机里使用以下命令可以查看web容器的信息:
docker inspect web
数据卷信息在Mounts
Key下面
"Mounts": [
{
"Type": "volume",
"Name": "my-vol",
"Source": "/var/lib/docker/volumes/my-vol/_data",
"Destination": "/usr/share/nginx/html",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
这里可以看到:
- Name=my-vol,表示数据卷名字为my-vol
- Source=/var/lib/docker/volumes/my-vol/_data,这个是数据卷在主机的本地文件路径
- Destination=/usr/share/nginx/html,这个是容器内部的路径
5.1.4 删除数据卷
$ docker volume rm my-vol
数据卷是用来持久化数据的,它的生命周期独立于容器,Docker不会在删除容器后自动删除数据卷,如果需要删除容器同时移除数据卷,可以在删除容器的时候使用:docker rm -v
命令。
清理数据卷:$ docker volume prune
5.2 挂载主机目录
5.2.1挂载一个主机目录作为数据卷
使用 --mount 标记可以指定挂载一个本地主机的目录到容器中去。
$ docker run -dit --name ubuntu --mount type=bind,source=/data2,target=/usr/share/data ubuntu:20.04
ad6a52cc58e3b270f1eecab61c9db4d102799b1d09ce01e0f1b5d95779bd4846
$ docker exec -it ad6a52cc58e3 bash
# cd /usr/share/data/
# echo -e "Hello" > hello.txt
# exit
$ cat /data2/hello.txt
Hello
5.2.2 查看数据卷的具体信息
$ docker inspect ubuntu
"Mounts": [
{
"Type": "bind",
"Source": "/data2",
"Destination": "/usr/share/data",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
5.2.3 挂载一个本地主机文件作为数据卷
$ docker run --rm -it --mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history ubuntu:20.04 bash
这样就可以把$HOME/.bash_history文件挂载到容器的/root/.bash_history文件了。
六、使用网络
Docker 允许通过外部访问容器或容器互联的方式来提供网络服务。
6.1 外部访问容器
容器中可以运行一些网络应用,要让外部也可以访问这些应用,可以通过 -P
或 -p
参数来指定端口映射。
当使用-P
标记时,Docker会随机映射一个端口到内部容器开放的网络端口。
使用 docker container ls
可以看到,本地主机的 32768 被映射到了容器的 80 端口。此时访问本机的 32768 端口即可访问容器内 NGINX 默认页面。
$ docker run -d -P nginx:alpine
$ docker container ls -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fae320d08268 nginx:alpine "/docker-entrypoint.…" 24 seconds ago Up 20 seconds 0.0.0.0:32768->80/tcp bold_mcnulty
-p
则可以指定要映射的端口,并且,在一个指定端口上只可以绑定一个容器。支持的格式有
ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort
- 映射所有接口地址。使用 hostPort:containerPort 格式,本地的 80 端口映射到容器的 80 端口,可以执行
docker run -d -p 80:80 nginx:alpine
- 映射到指定地址的指定端口。可以使用 ip:hostPort:containerPort 格式指定映射使用一个特定地址,比如 localhost 地址 127.0.0.1:
docker run -d -p 127.0.0.1:80:80 nginx:alpine
- 映射到指定地址的任意端口。使用 ip::containerPort ,绑定 localhost 的任意端口到容器的 80 端口,本地主机会自动分配一个端口:
docker run -d -p 127.0.0.1::80 nginx:alpine
docker port 【containerID】 【port】
可以查看绑定的地址。
6.2 容器互联
6.2.1 新建网络
下面先创建一个新的 Docker 网络。
$ docker network create -d bridge my-net
-d 参数指定 Docker 网络类型,有 bridge overlay。其中 overlay 网络类型用于 Swarm mode,在本小节中你可以忽略它。
6.2.2 连接容器
运行一个容器并连接到新建的 my-net 网络
$ docker run -it --rm --name busybox1 --network my-net busybox sh
打开新的终端,再运行一个容器并加入到 my-net 网络
$ docker run -it --rm --name busybox2 --network my-net busybox sh
下面通过 ping 来证明 busybox1 容器和 busybox2 容器建立了互联关系。
在 busybox1 容器输入以下命令
/ # ping busybox2
PING busybox2 (172.19.0.3): 56 data bytes
64 bytes from 172.19.0.3: seq=0 ttl=64 time=0.072 ms
64 bytes from 172.19.0.3: seq=1 ttl=64 time=0.118 ms
用 ping 来测试连接 busybox2 容器,它会解析成 172.19.0.3。
同理在 busybox2 容器执行 ping busybox1,也会成功连接到。
/ # ping busybox1
PING busybox1 (172.19.0.2): 56 data bytes
64 bytes from 172.19.0.2: seq=0 ttl=64 time=0.064 ms
64 bytes from 172.19.0.2: seq=1 ttl=64 time=0.143 ms
这样,busybox1 容器和 busybox2 容器建立了互联关系。
如果你有多个容器之间需要互相连接,推荐使用 Docker Compose
6.3 配置DNS
文件挂载方式:
如何自定义配置容器的主机名和 DNS 呢?秘诀就是 Docker 利用虚拟文件来挂载容器的 3 个相关配置文件。在容器中使用 mount 命令可以看到挂载信息。
这种机制可以让宿主主机 DNS 信息发生更新后,所有 Docker 容器的 DNS 配置通过 /etc/resolv.conf 文件立刻得到更新。
配置全部容器的 DNS ,也可以在 /etc/docker/daemon.json 文件中增加以下内容来设置。
{
"dns" : [
"114.114.114.114",
"8.8.8.8"
]
}
这样每次启动的容器 DNS 自动配置为 114.114.114.114
和8.8.8.8
。使用以下命令来证明其已经生效。
$ docker run -it --rm ubuntu:18.04 cat etc/resolv.conf
手动指定方式:
在docker run的时候,添加下面的参数:
-h HOSTNAME
或者 --hostname=HOSTNAME
设定容器的主机名,它会被写到容器内的 /etc/hostname 和 /etc/hosts。但它在容器外部看不到,既不会在 docker container ls 中显示,也不会在其他的容器的 /etc/hosts 看到。
--dns=IP_ADDRESS
添加 DNS 服务器到容器的 /etc/resolv.conf 中,让容器用这个服务器来解析所有不在 /etc/hosts 中的主机名。
--dns-search=DOMAIN
设定容器的搜索域,当设定搜索域为 .example.com 时,在搜索一个名为 host 的主机时,DNS 不仅搜索 host,还会搜索 host.example.com。
注意:如果在容器启动时没有指定最后两个参数,Docker 会默认用主机上的 /etc/resolv.conf 来配置容器。
七、底层实现
Docker 底层的核心技术包括 Linux 上的命名空间(Namespaces)、控制组(Control groups)、Union 文件系统(Union file systems)和容器格式(Container format)。
随着 Linux 系统对于命名空间功能的完善实现,程序员可以让某些进程在彼此隔离的命名空间中运行。大家虽然都共用一个内核和某些运行时环境(例如一些系统命令和系统库),但是彼此却看不到,都以为系统中只有自己的存在。这种机制就是容器(Container),利用命名空间来做权限的隔离控制,利用 cgroups 来做资源分配。
7.1 基本架构
Docker采用了C/S
架构,包括客户端和服务端。Docker守护进程(Daemon)作为服务端接受来自客户端的请求,并处理这些请求(创建、运行、分发容器)
客户端和服务端既可以运行在一个机器上,也可以通过socket
或者RESTful API
来通信。
Docker 守护进程一般在宿主主机后台运行,等待接收来自客户端的消息。
Docker 客户端则为用户提供一系列可执行命令,用户用这些命令实现跟 Docker 守护进程交互。
7.2 命名空间
Linux Namespace是Linux提供的一种内核级别环境隔离的方法。不知道你是否还记得很早以前的Unix有一个叫chroot的系统调用(通过修改根目录把用户jail到一个特定目录下),
chroot提供了一种简单的隔离模式:chroot内部的文件系统无法访问外部的内容。Linux Namespace在此基础上,提供了对UTS、IPC、mount、PID、network、User等的隔离机制。
- pid命名空间
- net命名空间
- ipc命名空间
- mnt命名空间
- uts命名空间
- user命名空间
7.3 控制组
控制组(cgroups)是 Linux 内核的一个特性,主要用来对共享资源进行隔离、限制、审计等。只有能控制分配到容器的资源,才能避免当多个容器同时运行时的对系统资源的竞争。
控制组技术最早是由 Google 的程序员在 2006 年提出,Linux 内核自 2.6.24 开始支持。
控制组可以提供对容器的内存、CPU、磁盘 IO 等资源的限制和审计管理。
7.4 联合文件系统
联合文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。
联合文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
Docker 中使用的 AUFS(Advanced Multi-Layered Unification Filesystem)就是一种联合文件系统。
扩展阅读:
DOCKER基础技术:AUFS:https://coolshell.cn/articles/17061.html
7.5 容器格式
最初,Docker 采用了 LXC 中的容器格式。从 0.7 版本以后开始去除 LXC,转而使用自行开发的 libcontainer,从 1.11 开始,则进一步演进为使用 runC 和 containerd。
7.6 网络
Docker 的网络实现其实就是利用了 Linux 上的 网络命名空间和 虚拟网络设备(特别是 veth pair)。
7.6.1 基本原理
首先,要实现网络通信,机器需要至少一个网络接口(物理接口或虚拟接口)来收发数据包;此外,如果不同子网之间要进行通信,需要路由机制。
Docker 中的网络接口默认都是虚拟的接口。
Docker 容器网络就是在本地主机和容器内分别创建一个虚拟接口,让它们彼此连通。
7.6.2 创建网络参数
Docker 创建一个容器的时候,会执行如下操作:
- 创建一对虚拟接口,分别放到本地主机和新容器中;
- 本地主机一端桥接到默认的 docker0 或指定网桥上,并具有一个唯一的名字,如 veth65f9;
- 容器一端放到新容器中,并修改名字作为 eth0,这个接口只在容器的命名空间可见;
- 从网桥可用地址段中获取一个空闲地址分配给容器的 eth0,并配置默认路由到桥接网卡 veth65f9。
八、Docker Compose
Docker Compose是官方编排项目之一,负责快速的部署分布式应用。
Compose定位是定义和运行多个Docker容器的应用。
通过第一部分中的介绍,我们知道使用一个 Dockerfile 模板文件,可以让用户很方便的定义一个单独的应用容器。然而,在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。
Compose恰好满足了这样的需要。它允许用户通过一个单独的docker-compose.yml模板文件(YAML格式)来定义一组相关联的应用容器为一个项目。
Compose 中有两个重要的概念:
- 服务 (service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。
- 项目 (project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。
Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。
Compose 项目由 Python 编写,实现上调用了 Docker 服务提供的 API 来对容器进行管理。因此,只要所操作的平台支持 Docker API,就可以在其上利用 Compose 来进行编排管理。
8.1 使用
场景:最常见的项目是web网站,该项目应该包含web应用和缓存。
下面用python来建立一个能够记录页面访问次数的web网站。
web应用:
新建文件夹,在该目录中编写app.py文件:
from flask import Flask
from redis import Redis
app = Flask(__name__)
redis = Redis(host='redis', port=6379)
@app.route('/')
def hello():
count = redis.incr('hits')
return 'Hello World! 该页面已被访问 {} 次。\n'.format(count)
if __name__ == "__main__":
app.run(host="0.0.0.0", debug=True)
Dockerfile:
FROM python:3.6-alpine
ADD . /code
WORKDIR /code
RUN pip install redis flask
CMD ["python", "app.py"]
docker-compose.yml:
version: '3'
services:
web:
build: .
ports:
- "5000:5000"
redis:
image: "redis:alpine"
运行compose项目
$ docker-compose up
此时访问本地5000端口,每次刷新页面,计数就会加1.
8.2 命令说明
对于 Compose 来说,大部分命令的对象既可以是项目本身,也可以指定为项目中的服务或者容器。如果没有特别的说明,命令对象将是项目,这意味着项目中所有的服务都会受到命令影响。
执行 docker-compose [COMMAND] --help 或者 docker-compose help [COMMAND] 可以查看具体某个命令的使用格式。
docker-compose命令的基本使用格式是:
docker-compose [-f=<arg>...] [options] [COMMAND] [ARGS...]
命令选项
- -f, --file FILE 指定使用的 Compose 模板文件,默认为 docker-compose.yml,可以多次指定。
- -p, --project-name NAME 指定项目名称,默认将使用所在目录名称作为项目名。
- –verbose 输出更多调试信息。
- -v, --version 打印版本并退出。
命令使用说明
build
格式为docker-compose build [options] [SERVICE...]
服务容器一旦构建后,将会带上一个标记名,例如对于web项目中的一个db容器,可能是web_db。
可以随时在项目目录下运行docker-compose build
来重新构建服务。
选项包括: - –force-rm 删除构建过程中的临时容器。
- –no-cache 构建镜像过程中不使用 cache(这将加长构建过程)。
- –pull 始终尝试通过 pull 来获取更新版本的镜像。
config
验证 Compose 文件格式是否正确,若正确则显示配置,若格式错误显示错误原因。(查看YAML文件)
down
停止up命令启动的容器,并移除网络
exec
进入指定的容器
help
获得一个命令的帮助
images
列出Compose文件中包含的镜像
kill
格式为 docker-compose kill [options] [SERVICE…]。
通过发送 SIGKILL 信号来强制停止服务容器。
支持通过 -s 参数来指定发送的信号,例如通过如下指令发送 SIGINT 信号。
$ docker-compose kill -s SIGINT
logs
格式为docker-compose logs [options] [SERVICE...]
。
查看服务容器的输出
pause
格式为 docker-compose pause [SERVICE...]
。
暂停一个服务容器。
ps
格式为 docker-compose ps [options] [SERVICE...]
。
列出项目中目前的所有容器。
选项:
-q 只打印容器的 ID 信息。
pull
格式为 docker-compose pull [options] [SERVICE...]
。
拉取服务依赖的镜像。
push
推送服务依赖的镜像到 Docker 镜像仓库。
start
格式为 docker-compose start [SERVICE...]
。
启动已经存在的服务容器。
stop
格式为 docker-compose stop [options] [SERVICE...]
。
停止已经处于运行状态的容器,但不删除它。通过 docker-compose start 可以再次启动这些容器。
restart
格式为 docker-compose restart [options] [SERVICE...]
。
重启项目中的服务。
选项:
-t, --timeout
TIMEOUT 指定重启前停止容器的超时(默认为 10 秒)。
top
查看各个服务容器内运行的进程。
rm
格式为docker-compose rm [options] [SERVICE...]
。
删除所有(停止状态的)服务容器。推荐先执行 docker-compose stop 命令来停止容器。
选项:
-f, --force 强制直接删除,包括非停止状态的容器。一般尽量不要使用该选项。
-v 删除容器所挂载的数据卷。
run
格式为 docker-compose run [options] [-p PORT...] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...]
。
在指定服务上执行一个命令。
例如:docker-compose run ubuntu ping docker.com
将会启动一个 ubuntu 服务容器,并执行 ping docker.com 命令。
默认情况下,如果存在关联,则所有关联的服务将会自动被启动,除非这些服务已经在运行中。
如果不希望自动启动关联的容器,可以使用 --no-deps 选项
scale
格式为 docker-compose scale [options] [SERVICE=NUM...]
。
设置指定服务运行的容器个数。
通过 service=num 的参数来设置数量。例如:
$ docker-compose scale web=3 db=2
将启动 3 个容器运行 web 服务,2 个容器运行 db 服务。
一般的,当指定数目多于该服务当前实际运行容器,将新创建并启动容器;反之,将停止容器。
unpause
格式为 docker-compose unpause [SERVICE...]
。
恢复处于暂停状态中的服务。
up
格式为 docker-compose up [options] [SERVICE...]
。
该命令十分强大,它将尝试自动完成包括构建镜像,(重新)创建服务,启动服务,并关联服务相关容器的一系列操作。(修改容器程序后,不会重新构建服务容器,需使用docker-compose build来重新构建服务容器)
链接的服务都将会被自动启动,除非已经处于运行状态。
可以说,大部分时候都可以直接通过该命令来启动一个项目。
默认情况,docker-compose up
启动的容器都在前台,控制台将会同时打印所有容器的输出信息,可以很方便进行调试。
当通过 Ctrl-C 停止命令时,所有容器将会停止。
如果使用 docker-compose up -d
,**将会在后台启动并运行所有的容器。**一般推荐生产环境下使用该选项。
默认情况,如果服务容器已经存在,docker-compose up 将会尝试停止容器,然后重新创建(保持使用 volumes-from 挂载的卷),以保证新启动的服务匹配 docker-compose.yml 文件的最新内容。如果用户不希望容器被停止并重新创建,可以使用 docker-compose up --no-recreate
。这样将只会启动处于停止状态的容器,而忽略已经运行的服务。如果用户只想重新部署某个服务,可以使用 docker-compose up --no-deps -d <SERVICE_NAME>
来重新创建服务并后台停止旧服务,启动新服务,并不会影响到其所依赖的服务。
选项:
- -d 在后台运行服务容器。
- –no-color 不使用颜色来区分不同的服务的控制台输出。
- –no-deps 不启动服务所链接的容器。
- –force-recreate 强制重新创建容器,不能与 --no-recreate 同时使用。
- –no-recreate 如果容器已经存在了,则不重新创建,不能与 --force-recreate 同时使用。
- –no-build 不自动构建缺失的服务镜像。
8.3 Compose模板文件
https://yeasy.gitbook.io/docker_practice/compose/compose_file
模板文件是使用Compose的核心,涉及到的指令关键字也比较多。不过大部分指令跟docker run相关参数的含义类似。
默认的模板文件名称为 docker-compose.yml,格式为 YAML 格式。
注意每个服务都必须通过 image 指令指定镜像或 build 指令(需要 Dockerfile)等来自动构建生成镜像。
如果使用 build 指令,在 Dockerfile 中设置的选项(例如:CMD, EXPOSE, VOLUME, ENV 等) 将会自动被获取,无需在 docker-compose.yml 中重复设置
build
指定 Dockerfile 所在文件夹的路径(可以是绝对路径,或者相对 docker-compose.yml 文件的路径)。 Compose 将会利用它自动构建这个镜像,然后使用这个镜像。
cap_add, cap_drop
指定容器的内核能力(capacity)分配。
例如,让容器拥有所有能力可以指定为:
cap_add:
- ALL
去掉 NET_ADMIN 能力可以指定为:
cap_drop:
- NET_ADMIN
command
覆盖容器启动后默认执行的命令。
cgroup_parent
指定父 cgroup 组,意味着将继承该组的资源限制。
container_name
指定容器名称。默认将会使用 项目名称_服务名称_序号 这样的格式。
container_name: docker-web-container
注意: 指定容器名称后,该服务将无法进行扩展(scale),因为 Docker 不允许多个容器具有相同的名称。
devices
指定设备映射关系。
depends_on
解决容器的依赖、启动先后的问题。以下例子中会先启动 redis db 再启动 web
version: '3'
services:
web:
build: .
depends_on:
- db
- redis
redis:
image: redis
db:
image: postgres
注意:web 服务不会等待 redis db 「完全启动」之后才启动。
dns
自定义 DNS 服务器。可以是一个值,也可以是一个列表。
dns: 8.8.8.8
dns:
- 8.8.8.8
- 114.114.114.114
env_file
从文件中获取环境变量,可以为单独的文件路径或列表。
如果有变量名称与 environment 指令冲突,则按照惯例,以后者为准。
env_file: .env
env_file:
- ./common.env
- ./apps/web.env
- /opt/secrets.env
环境变量文件中每一行必须符合格式,支持 # 开头的注释行.
# common.env: Set development environment
PROG_ENV=development
environment
设置环境变量。你可以使用数组或字典两种格式。
只给定名称的变量会自动获取运行 Compose 主机上对应变量的值,可以用来防止泄露不必要的数据。
environment:
RACK_ENV: development
SESSION_SECRET:
environment:
- RACK_ENV=development
- SESSION_SECRET
如果变量名称或者值中用到 true|false,yes|no 等表达 布尔 含义的词汇,最好放到引号里,避免 YAML 自动解析某些内容为对应的布尔语义。这些特定词汇,包括
y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF
expose
暴露端口,但不映射到宿主机,只被连接的服务访问。
healthcheck
通过命令检查容器是否健康运行。
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 1m30s
timeout: 10s
retries: 3
image
指定为镜像名称或镜像 ID。如果镜像在本地不存在,Compose 将会尝试拉取这个镜像。
image: ubuntu
image: orchardup/postgresql
image: a4bc65fd
logging
配置日志选项。
logging:
driver: syslog
options:
syslog-address: "tcp://192.168.0.42:123"
目前支持三种日志驱动类型。
driver: "json-file"
driver: "syslog"
driver: "none"
options 配置日志驱动的相关参数。
options:
max-size: "200k"
max-file: "10"
network_mode
设置网络模式。使用和 docker run 的 --network 参数一样的值。
network_mode: "bridge"
network_mode: "host"
network_mode: "none"
network_mode: "service:[service name]"
network_mode: "container:[container name/id]"
networks
配置容器连接的网络。
version: "3"
services:
some-service:
networks:
- some-network
- other-network
networks:
some-network:
other-network:
** pid**
跟主机系统共享进程命名空间。打开该选项的容器之间,以及容器和宿主机系统之间可以通过进程 ID 来相互访问和操作。
ports
暴露端口信息。
使用宿主端口:容器端口 (HOST:CONTAINER) 格式,或者仅仅指定容器的端口(宿主将会随机选择端口)都可以。
ports:
- "3000"
- "8000:8000"
- "49100:22"
- "127.0.0.1:8001:8001"
注意:当使用 HOST:CONTAINER 格式来映射端口时,如果你使用的容器端口小于 60 并且没放到引号里,可能会得到错误结果,因为 YAML 会自动解析 xx:yy 这种数字格式为 60 进制。为避免出现这种问题,建议数字串都采用引号包括起来的字符串格式。
secrets
存储敏感数据,例如 mysql 服务密码。
sysctls
配置容器内核参数。
sysctls:
net.core.somaxconn: 1024
net.ipv4.tcp_syncookies: 0
sysctls:
- net.core.somaxconn=1024
- net.ipv4.tcp_syncookies=0
ulimits
指定容器的 ulimits 限制值。
volumes
数据卷所挂载路径设置。可以设置为宿主机路径(HOST:CONTAINER)或者数据卷名称(VOLUME:CONTAINER),并且可以设置访问模式 (HOST:CONTAINER:ro)。