第一部分 Docker概览
第1章 容器发展之路
虚拟机是一种具有划时代意义的技术。
虚拟机的缺点:虚拟机依赖其专用的操作系统(OS),OS会占用额外的CPU、RAM和存储,这些资源本可以用于运行更多的应用。虚拟机的启动通常比较慢,可移植性也比较差。
容器跟虚拟机的区别是容器运行不会独占操作系统,这样能节省大量的系统资源。容器具有启动快和便于迁移等优势。
容器技术起源于Linux,容器技术的复杂度是阻止其实际应用的主要原因,直到Docker技术横空出世,容器才真正被大众所接受。
Windows容器已经支持在Windows10以上平台使用,Docker在Windows平台上的使用体验上跟在Linux几乎一致。
运行中的容器共享宿主机的内核,所以Windows容器化应用无法再Linux主机上运行,同样Linux容器化应用也无法在Windows主机上运行。(最新版的Docker可能可以)
Mac容器目前还没出现,但可以使用Docker of Mac来运行Linux容器,这是通过在Mac启动一个轻量级Linux VM,然后再其中无缝地运行Linux容器来实现。
Kubernetes是谷歌的一个开源项目,简称K8S,Kubernetes是保证容器部署和运行的软件体系中很重要的一部分。Kubernetes已经采用Docker作为默认容器运行时。
第2章 走进Docker
Docker
Docker是一种运行与Linux和Windows上的软件,用于创建、管理和编排容器。Docker是在Github上开发的Moby开源项目的一部分。
Docker运行时与编排引擎
Docker引擎是用于运行和编排容器的基础设施工具。Docker引擎是运行容器的核心容器运行时。
开放容器计划
开放容器计划(OCI)在对容器基础架构中的基础组件进行标准化的管理委员会。
第3章 Docker安装
Windows版Docker是社区版本(Community Edition,CE)。
Mac版Docker的Docker daemon(称为Docker引擎或Docker服务端)是运行在一个轻量级的Linux VM之上的。
Windows版Docker和Mac版Docker安装了包括Docker引擎、Docker Compose、Docker Machine以及Docker Notary命令行。
Linux版Docker提供社区版本和企业版本(Enterprise Edition,EE)。
Linux版安装,运行wget -qO- https://get/docker.com/ || sh
,安装之后将用户移到docker组,sudo usermod -aG docker your-user
。
Docker引擎(Engine)升级
Linux上升级
更新apt包
apt-get update
卸载当前Docker
apt-get remove docker docker-engine docker-ce docker.io -y
安装新版本Docker
wget -qO- https://get/docker.com/ || sh
将Docker配置为开机自启动
systemctl enable docker
Docker存储驱动的选择
每个Docker容器都有一个本地存储空间,用于保存层叠的镜像层(Image Layer)以及挂载的容器文件系统。默认情况下,容器的所有读写操作都发生在其镜像层上或挂载的文件系统中,所以存储是每个容器的性能和稳定性不可或缺的一个环节。
在Linux上,Docker可选择的一些存储驱动包括AUFS(最原始也是最老的)、Overlay2(可能是未来的最佳选择)、Device Mapper、Btrfs和ZFS。
Docker在Windows系统上只支持一种存储驱动,Windows Filter。
查看当前的存储驱动类型:docker system info
。Storage Driver 字段显示的当前的驱动类型。
Device Mapper 配置
Device Mapper 存储驱动默认采用loopback mounted sparse file作为底层实现来为Docker提供存储支持。默认方式的性能很差,并不支持生产环境。
为了达到Device Mapper在生产环境中的最佳性能,需要将底层修改为direct-lvm模式。
第4章 纵观Docker
Play With Docker(PWD)是一个基于Web界面的Docker环境,并且可以免费使用。
运维视角
Docker分为两个主要组件:Docker客户端和Docker daemon。

镜像
将Docker镜像理解为一个包含OS文件系统和应用的对象,或者将镜像理解为一个类(Class),镜像实际上等价于未运行的容器。
Docker主机上获取镜像的操作被称为拉取(pulling)。在Linux中,拉取ubuntu:latest镜像,使用docker image pull ubuntu:latest
命令。

拉取的ubuntu镜像有一个精简版的Ubuntu Linux文件系统,包含部分Ubuntu常用工具。如果拉取了如nginx或microsoft/iss这样的应用容器,则会得到一个包含操作系统的镜像,镜像中还包括运行Nginx或IIS所需的代码。
Docker的每个镜像都有自己的唯一ID。可以通过引用镜像的ID或名称来使用镜像。
容器
可以使用docker container run
从镜像来启动容器,docker container run -it ubuntu:latest /bin/bash
在Linux中启动容器,其中-it
参数告诉Docker开启容器的交互模式并将读者当前的Shell连接到容器端,接下来unbunt:latest
告诉Docker用户想基于unbunt:latest
镜像启动容器,最后告诉Docker用户想在容器内部运行哪个进程,上面写的是bash shell
进程。
按Ctril+P+Q
可以退出容器并保持容器运行。docker container ls
查看系统内全部处于运行状态的容器,加上-a
选项可以查看所有包括停止中的容器。
连接到运行中的容器
docker container exec
命令将Shell连接到一个运行中的容器终端。

docker container stop
命令停止容器,docker container rm
命令杀死容器。

开发视角
Dockerfile
是一个纯文本文件,其中描述如何将应用构建到Docker镜像当中。Dockerfile
每一行都代表一个用于构建镜像的指令。
# Test web-app to use with Pluralsight courses and Docker Deep Dive book
# Linux x64
FROM alpine
LABEL maintainer="nigelpoulton@hotmail.com"
# Install Node and NPM
RUN apk add --update nodejs npm curl
# Copy app to /src
COPY . /src
WORKDIR /src
# Install dependencies
RUN npm install
EXPOSE 8080
ENTRYPOINT ["node", "./app.js"]
使用docker image build
命令根据Dockerfile
中的指令来创建新的镜像。

第二部分 Docker技术
第5章 Docker引擎
Docker引擎是用来运行和管理容器的核心软件。Docker引擎由许多专用的工具协同工作,从而可以创建和运行容器,例如API、执行驱动、运行时、shim进程等。
Docker引擎由如下的主要的组件构成:Docker客户端(Docker Client)、Docker守护进程(Docker daemon)、containerd以及runc。它们共同负责容器的创建和运行。


runc是OCI容器运行时规范的参考实现。runc只有一个作用——创建容器,runc基于Libcontainer。
containerd的主要任务是管理容器的生命周期——start|stop|pause|rm...
执行docker container run --name ctrl -it alpine:latest sh
时,Docker客户端会将其转换为合适的API格式,并发生到正确的API端点。API是在daemon中实现的,一旦daemon接受到创建容器的命令,它就会像containerd发出调用。containerd将Docker镜像转换为OCI bundle,并让runc基于此创建一个新的容器,runc与操作系统内核接口进行通信,基于所有必要的工具(Namespace、CGroup等)来创建容器。容器进程作为runc的子进程启动,启动完毕后runc将会退出。

shim
shim是实现无daemon的容器不可或缺的工具。
一旦容器进程的父进程runc退出,相关联的containerd-shim进程就会成为容器的父进程。作为容器的父进程,shim的部分职责如下。
- 保持所有STDIN和STDOUT流是开启状态,从而当daemon重启的时候,容器不会因为管道(pipe)的关闭而终止。
- 将容器的退出状态反馈给daemon。
daemon的作用
随着越来越多的功能从daemon中拆解出来并被模块化,daemon目前的主要功能包括镜像管理、镜像构建、REST API、身份验证安全、核心网络以及编排。
第6章 Docker镜像
Docker镜像
镜像可以从镜像仓库服务中拉取镜像,常见的镜像仓库服务是Docker Hub,拉取操作会将镜像下载到本地Docker主机,可以使用镜像启动一个或多个容器。
镜像由多个层组成,每层叠加之后,从外部看来就如一个独立的对象,镜像内部是一个小巧的操作系统(OS),同时还包含应用运行所必须的文件和依赖包。
镜像可以理解为一种构建时(build-time)结构,而容器可以理解为运行时(run-time)结构。
一旦容器从镜像启动后,二者就变成了相互依赖的关系,并且在镜像上启动的容器全部停止之前,镜像是无法被删除的。
Docker镜像的体积通常较小,镜像不包含内核——容器都是共享所在Docker主机的内核。Windows镜像通常比Linux镜像大一些。
Linux Docker主机本地镜像仓库通常位于**/var/lib/docker/<storage-driver>
**下。
镜像仓库服务
Docker镜像存储在镜像仓库服务(Image Registry)中,Docker客户端的镜像仓库服务是可配置的,默认使用Docker Hub。
镜像仓库服务包含多个镜像仓库(Image Repository),一个镜像仓库可以包含多个镜像。
Docker Hub也分为官方仓库(Official Repository)和非官方仓库(Unofficial Repository)。

镜像命名和标签
只需给出镜像的名字和标签,就能在官方仓库中定位一个镜像(采用“:
”分隔符)。
从官方仓库拉取镜像:docker image pull <repository>:<tag>
。
例:docker image pull unbuntu:latest
or docker image pull alpine:latest
。
注意:如果没有指定标签,则Docker会默认用户希望拉取为latest
的镜像。标有latest
标签的也不能保证是仓库中的最新镜像,例如Alpine
仓库最新镜像的标签通常是edge
。
拉取非官方镜像只需在仓库名称前加上Docker Hub的用户名或者组织名称。
例:docker image pull nigelpoulton/tu-demo:v2
,从nigelpoulton这个用户的仓库中拉取tu-demo这个镜像,标签为v2。
拉取第三方镜像仓库中的镜像,只需在镜像仓库名称前加上第三方镜像仓库服务的DNS名称。
例:docker pull gcr.io/nigelpoulton/tu-demo:v2
。
为镜像打多个标签
在docker image pull
命令指定-a
参数可以拉取仓库中的全部镜像。

上图两个不同标签的镜像实际指向同一个镜像,换句话说,这个镜像拥有两个标签。
过滤 docker image ls 的输出内容
--filter
参数过滤返回的镜像列表内容。
例:docker image ls --filter dangling=true
,该指令返回悬虚(dangling)镜像,悬虚镜像是指在列表展示为<none>:<none>
的镜像,这种镜像是因为构建了一个新镜像,然后为镜像打了一个已存在的标签,此时Docker会移除旧镜像上面的标签,旧镜像就称为了悬虚镜像。
docker image prune
命令会移除全部的悬虚镜像,加上-a
则会移除包括没有被使用的镜像。
Docker目前支持如下的过滤器:
dangling
,可以指定true或者false。before
,需要镜像名称或者ID作为参数,返回在指定镜像之前被创建的全部镜像。since
,与before
相似,返回的是指定镜像之后创建的全部镜像。label
,根据标注(label)的名称或值,对镜像进行过滤。reference
,其他的过滤方式使用这个。
例:docker image ls --filter reference="*:latest"
。
通过 CLI 方式搜索Docker Hub
docker seaerch
命令允许通过CLI的方式搜索Docker Hub。通过“NAME”字段的内容进行匹配。
例:docker search nginx
。

--filter "is-official=true"
选项返回官方镜像。--filter "is-automated=true"
返回自动创建的创建。
默认情况下,Docker只返回25条结果,可以指定--limit
参数增加返回的内容条数,最多为100条。
镜像和分层
Docker镜像由一些松耦合的只读镜像层组成。

Docker负责堆叠这些镜像层,并将它们表示为单个统一的镜像。

上图中,一个Pull complete代表一层。
docker image inspect
命令可以查看镜像分层的方式。

所有的 Docker 镜像都起始于一个基础镜像层,当进行修改或增加新的内容时,就会在当前镜像层之上,创建新的镜像层。
假如基于 Ubuntu Linux 16.04创建一个新的镜像,这就是新镜像的第一层;如果在该镜像中添加 Python 包,就会在基础镜像层之上创建第二个镜像层。

在添加额外的镜像层的同时,镜像层保持是当前所有镜像层的组合。

上图中展示了一个三层镜像,在外部看来整个镜像只有6个文件,这是因为上层中的文件7是文件5的一个更新版本,上层镜像层中的文件覆盖了底层镜像层中的文件。

共享镜像层

上图中,那些标 Already exists 结尾的行,就是共享镜像层。当 pull 时,Docker注意到两个镜像的镜像层有一部分已经存在,此时会共享镜像层,这样做的好处是节省存储空间并提升性能。Docker 在 Linux 支持很多存储引擎,每个存储引擎都有自己的镜像分层、镜像层共享以及写时复制技术的具体实现,但其最终效果和用户体验是完全一致的。
根据摘要拉取镜像
镜像摘要(Image Digest),每一个镜像都有一个基于内容的密码散列值。镜像内容的变更一定会导致散列值的变更,每个镜像对应一个摘要,这意味着摘要是不可变的。
docker image ls ---digests
显示镜像的摘要。

上图显示镜像的两个摘要。
镜像散列值(摘要)
镜像本身就是一个配置对象,其中包含了镜像层的列表以及一些元数据信息。
镜像层才是实际数据存储的地方(比如文件等,镜像层之间是完全独立的,并没有从属于某个镜像集合的概念)。
镜像的唯一标识是一个加密ID,即配置对象本身的散列值。每个镜像层也由一个加密ID区分,其值为镜像层本身内容的散列值。这意味着修改镜像的内容或其他任意的镜像层,都会导致加密散列值的变化。所以,镜像和镜像层都是不可变的,任何改动都能很轻松地被辨别。这就是所谓的内容散列(Content Hash)。
在推送和拉取镜像的时候,都会对镜像层进行压缩,压缩会改变镜像内容,这以为着内容散列值会与镜像内容不相符,为了避免这个问题,每个镜像层同时会包含一个分发散列值(Distribution Hash),这是一个压缩版镜像的散列值,该散列值会用于校验拉取的镜像是否被篡改过。
多架构的镜像
多架构镜像(Multi-architecture Image)解决开发者在拉取镜像考虑是否与当前运行环境的架构匹配的问题。
Docker 通过 Manifest 列表来指定某个镜像标签支持的架构列表。其支持的每种架构,都有自己的 Manifest 定义,其中列举了该镜像的构成。

原理:在拉取镜像的时候,客户端会调用 Docker Hub 镜像仓库服务响应的 API 完成拉取,如果该镜像有 Manifest 列表且存在当前运行环境的架构,则 Docker Client 就会找到该架构对应的 Manifest 并解析出组成该镜像的镜像层加密ID,然后从 Docker Hub 二进制存储中拉取每个镜像层。

上图显示拉取 Golang 镜像,以容器方式启动,并执行 go version
命令,输出Go的版本和主机 OS/CPU 架构信息。
删除镜像
docker image rm
命令删除镜像。
删除操作会在当前主机上删除该镜像以及相关的镜像层。如果某个镜像层被多个镜像共享,那么只有当全部依赖该镜像层的镜像被删除后,该镜像层才会被删除。
docker image ls -q
获取全部镜像ID。docker image rm $(docker image ls -q) -f
删除所有镜像。
第7章 Docker 容器
容器是镜像的运行时实例。与虚拟机完整的操作系统之上相比,容器会共享其所在主机的操作系统/内核。
docker container run
命令启动容器,基础格式是docker container run <image> <app>
,指定了启动所需的镜像以及要运行的应用。
-it
参数可以将当前终端连接到容器的Shell终端之上。--name xxx
参数为容器命名。-d
参数指定容器在后台运行。-p
参数将 Docker 主机的端口映射到容器内。
容器会随着其中运行应用的退出而终止。Linux 容器会在Bash Shell退出后终止,Windows 容器会在PowerShell 进程终止后退出。
docker container stop <container-id or container-name>
命令手动停止容器运行。docker container pause
暂停容器。docker container start
再次启动该容器。docker container rm
删除容器。

上图为虚拟机。

上图为容器。
Hypervisor 是硬件虚拟化(Hardware Virtualization)——Hypervisor将硬件物理资源划分为虚拟资源;容器是操作系统虚拟化(OS Virtuallization)——容器将系统资源划分为虚拟资源。
虚拟机的额外开销
虚拟机模型将底层资源划分到虚拟机当中,每个虚拟机都是包含了虚拟CPU、虚拟RAM等等资源,虚拟机的每个操作系统都需要消耗一点CPU、一点RAM、一点存储空间等,每个操作系统都面临被攻击的风险等问题。
容器模型具有在宿主机操作系统中运行的单个内核。容器共享一个操作系统/内核,大大节省资源。
检查Docker daemon
登录 Docker 主机后的第一件事是检查 Docker 是否正在运行。使用docker version
命令。当命令输出包含 Client 和 Server 的内容时,说明 Docker 正在运行。
检查 Docker daemon 是否运行,service docker status
命令,systemctl is-active docker
命令这两条命令都可以检查。
启动一个简单容器
运行命令基础格式:docker container run <options> <image>:<tag> <app>
。
例:docker container run -it ubuntu:latest /bin/bash
。运行一个容器化版本的Ubuntu Linux。
容器进程
启动 Ubuntu 容器之时,让容器运行 Bash Shell。这使得 Bash Shell 称为容器中运行的且唯一运行的进程。

上图中,容器只允许了一个 bash 的进程。这意味着如果通过exit
退出 Bash Shell,那么容器也会退出(终止)。原因是容器如果不运行任何进程则无法存在——杀死容器中的主进程,则容器也会被杀死。
按下 Ctrl+P+Q
组合键会退出容器但不终止容器运行,保持容器在后台运行。
docker container exec
命令将终端重新连接到 Docker。例:docker container exec -it 3027eb644874 bash
。
容器生命周期
容器支持持久化技术,停止容器运行并不会损毁容器或者其中的数据。
容器的生命周期包含停止、启动、暂停以及重启容器,这些操作执行得很快。
优雅地停止容器
停止容器应该先停止容器运行,再进行删除操作,如果突然删除正在运行中的容器,会令容器和应用猝不及防,来不及处理后事。
利用重启策略进行容器的自我修复
重启策略应用于每个容器,可以作为参数被强制传入 docker-container run 命令中,或者在 Compose 文件声明。
容器支持的重启策略包括 always、unless-stopped 和 on-failed。
always 策略,除非容器被docker container stop
命令停止,否则该策略会一直尝试重启处于停止状态的容器。当 Docker daemon 重启时,停止的容器也会被重启。使用--restart always
参数指定该策略。
例:docker container run --name neversaydie -it --restart always alpine sh
。当容器启动时,如果执行exit
命令,容器将在停止后立马重启。
unless-stopped 策略和 always 策略的区别是当 Docker daemon 重启时,unless-stopped 策略的容器不会被重启。
systemctl restart docker
命令重启 Docker daemon。

on-failure 策略会退出容器并且返回值不是0的时候,重启容器。就算容器处于 stopped 状态,在 daemon 重启的时候,容器也会被重启。
Web服务器示例
docker container run -d --name webserver -p 80:8080 nigelpoulton/pluralsight-docker-ci
。将主机80端口映射到容器的8080端口,
查看容器详情
当构建 Docker 镜像的时候,可以通过嵌入指令来列出希望容器运行时启动的默认应用。docker image inspect
命令查看运行容器时的镜像。

Cmd 一项中展示了容器将会执行的命令或应用,除非在启动的时候读者指定另外的应用。
在构建镜像时指定默认命令是一种很普遍的做法,因为这样可以简化容器的启动。
快速清理
docker container rm $(docker container ls -aq) -f
可以快速清除全部容器,这种操作一定不能在生产环境或者运行着重要容器的系统上运行!
容器——命令
docker container run
docker container ls
docker container exec
docker container stop
docker container start
docker container rm
docker container inspect
第8章 应用的容器化
Docker 的核心思想就是如何将应用整合到容器中,并且能在容器中实际运行。将应用整合到容器中并且运行起来的这个过程,称为“容器化”(Containerizing),有时也叫“Docker化”(Dockerizing)。
容器能够简化应用的构建、部署和运行过程。
完整的应用容器化主要分为以下几个步骤:
- 编写应用代码
- 创建一个 Dockerfile,其中包含应用的描述、依赖以及该如何运行这个应用。
- 对该 Dockerfile 执行
docker image build
命令。 - 等待 Docker 将应用程序构建到 Docker 镜像中。

单体应用容器化
Dockerfile 通常放到构建应用的根目录下。
Dockerfile 的两个主要用途:
- 对当前应用的描述。
- 指导 Docker 完成应用的容器化。
# 将 alpine 作为当前镜像基础
FROM alpine
# 指定维护者为nigelpoulton@hotmail.com
LABEL maintainer="nigelpoulton@hotmail.com"
# Install Node and NPM
# 安装 Node 和 Npm
RUN apk add --update nodejs npm curl
# Copy app to /src
# 将代码复制到镜像当中
COPY . /src
# 设置新的工作目录
WORKDIR /src
# Install dependencies
RUN npm install
# 记录应用的网络端口
EXPOSE 8080
# 将 app.js 设置为默认运行应用
ENTRYPOINT ["node", "./app.js"]
- 每个 Dockerfile 文件第一行都是
FROM
命令,FROM
命令指定镜像作为当前镜像的一个基础镜像层,当前应用的剩余内容会作为新增镜像层添加到基础镜像层之上。 LABEL
设置镜像的标签,每个标签是一个键值对,在一个镜像中可以通过添加标签的方式来为镜像添加自定义元数据。备注维护者信息又助于为该镜像的潜在使用者提供沟通途径。RUN
指令会在FROM
指定的镜像基础之上运行命令。COPY
指令将应用相关文件从构建上下文复制到当前镜像中,并且新建一个镜像层来存储。WORKDIR
指令为 Dockerfile 中尚未执行的指令设置工作目录。该指令通过镜像元数据的形式保存下来,不会新增镜像层。EXPOSE
指令完成相应端口的设置。该指令通过镜像元数据的形式保存下来,不会新增镜像层。ENTRYPOINT
指令来指定当前镜像的入口程序。该指令通过镜像元数据的形式保存下来,不会新增镜像层。

例:docker image build -t web:latest .
容器化该应用。
-t
参数指定要创建的目标镜像。
注意:一定要在命令最后包含这个点,.表示Dockerfile文件路径。
推送镜像到仓库
docker login
命令登录到 Docker Hub。
推送 Docker 镜像之前,还需要为镜像打标签。这是因为 Docker 在镜像推送的过程中需要如下信息:
- Registry(镜像仓库服务)。
- Repository(镜像仓库)。
- Tag(镜像标签)。
当没有为 Registry 和 Tag 指定值时,Docker 默认Registry=docker.io、Tag=latest
。
docker image tag <current-tag> <new-tag>
为镜像打标签。
docker image push <current-tag>
推送镜像到Docker Hub。

运行容器应用
docker container run -d --name c1 -p 80:8080 web:latest
运行容器并将容器的8080端口映射到主机80端口。

详述
Dockerfile 的指令不区分大小写,通常都是大写的方式。
关于如何区分命令是否会新建镜像层,一个基本原则是,如果指令的作用是向镜像中增添新的文件或者程序,那么这条指令就会新建镜像层;如果只是告诉 Docker 如何完成构建或者如何运行应用程序,那么就只会添加镜像的元数据。
docker image histroy xxx
查看在构建镜像的过程中都执行了哪些指令。

上图中,顺序是自下而上的。SIZE有大小的指令会新建镜像层。
使用FROM
引用官方基础镜像是一个很好的习惯,因为官方的镜像通常会遵循一些最佳实践。
生产环境的多阶段构建
对于 Docker 镜像来说,过大的体积并不好。越大则越慢,这意味着更难使用,而且可能更加脆弱,更容易遭受攻击。
Docker 镜像应该尽量小,对于生产环境镜像来说,目标是将其缩小到仅包含运行应用所必须的内容即可。
**多阶段构建(Multi-Stage Build)**能够在不增加复杂性的情况下优化构建过程。
多阶段构建方式使用一个Dockerfile,其中包含多个FROM
指令。每一个FORM
指令都是一个新的构建阶段(Build Stage),并且可以方便地复制之前阶段的构件。
# 第一个构建阶段,别名 storefront
FROM node:latest AS storefront
WORKDIR /usr/src/atsea/app/react-app
COPY react-app .
RUN npm install
RUN npm run build
# 第二个构建阶段,别名 appserver
FROM maven:latest AS appserver
WORKDIR /usr/src/atsea
COPY pom.xml .
RUN mvn -B -f pom.xml -s /usr/share/maven/ref/settings-docker.xml dependency:resolve
COPY . .
RUN mvn -B -s /usr/share/maven/ref/settings-docker.xml package -DskipTests
# 第三个构建阶段,别名 production
FROM java:8-jdk-alpine AS production
RUN adduser -Dh /home/gordon gordon
WORKDIR /static
# 从 storefront 阶段生成的镜像中复制一些应用代码过来
COPY --from=storefront /usr/src/atsea/app/react-app/build/ .
WORKDIR /app
# 从 appserver 阶段生成的镜像中复制应用相关的代码
COPY --from=appserver /usr/src/atsea/target/AtSea-0.0.1-SNAPSHOT.jar .
ENTRYPOINT ["java", "-jar", "/app/AtSea-0.0.1-SNAPSHOT.jar"]
CMD ["--spring.profiles.active=postgres"]
COPY --from
指令从之前的阶段构建的镜像中,仅复制生产环境相关的应用代码,不会复制生产环境不需要的构件。

上面经过三个阶段的构建,会产生多个镜像,最终的production镜像将是精简的生产环境镜像。
最佳实践
利用构建缓存
Docker 的构建过程利用了缓存机制,会第二次构建开始将重复利用第一次缓存下来的镜像层,节省构建时间。
Docker 会检查 Dockerfile 每一条指令是否命中与该指令对应的镜像层,如果有则缓存命中(Cache Hit)。
一旦有指令在缓存中未命中,则后续的整个构建过程将不再使用缓存。
对
docker image build
指令加上--no-cache=true
参数强制忽略对缓存的使用。合并镜像
当进行中层数太多时,合并是一个不错的优化方式。
合并镜像的缺点是无法共享镜像层。这会导致存储空间的低效利用。
执行
docker image build
命令时添加--squash
参数来创建一个合并的镜像。使用 no-install-recommends
使用apt包管理器时,应该在执行
apt-get install
时添加no-install-recommends
参数,确保apt仅安装核心依赖。
第9章 使用 Docker Compose 部署
多数现代应用通过多个更小的服务互相协同来组成一个完整可用的应用。部署和管理繁多的服务是困难的,这正是Docker Compose要解决的问题。
Docker Compose通过一个声明式的配置文件描述整个应用,从而使用一条命令完成部署。应用部署成功后,还可以通过一系列简单的命令实现对齐完整生命周期的管理。
背景
Docker Compose的前身是Fig,Fig是由 Orchard 公司开发的一个基于Docker的Python工具,允许用户基于一个YAML文件定义多容器应用,可以使用fig
命令行工具进行应用的部署,还可以对应用进行全生命周期的管理。
2014年Docker公司收购了 Orchard 公司,将Fig更名为Docker Compose。命令行工具也从fig
更名为docker-compose
。
安装
Docker Compose在 mac 会随 Docker 安装附带上,Linux 需要自行安装。
Compose文件
Docker Compose使用YAML文件来定义多服务的应用,YAML是JSON的一个子集,因此也可以使用JSON。
Docker Compose默认使用文件名docker-compose.yml
,也可以使用-f
参数指定具体文件。
# 版本号3.5
version: "3.5"
# 定义两个服务,一个是web-fe,另一个是redis
services:
# web-fe作为容器名
web-fe:
# 指定Docker基于当前目录下Dockerfile(在下面的代码块中)中定义的指令来构建一个新镜像。该镜像会被用于启动该服务的容器。
build: .
# 指定Docker在容器中执行名为app.py的Python脚本作为主程序。不过这一点在Dockerfile中可以做到。
command: python app.py
# 指定Docker将容器内(-target)的5000端口映射到主机(published)的5000端口。
ports:
- target: 5000
published: 5000
# 使得Docker可以将服务连接到指定的网络上。这个网络应该是已经存在的,或者是networks一级key中定义的网络。
networks:
- counter-net
# 指定Docker将counter-vol卷(source:)挂载到容器的/code(target:)。
volumes:
- type: volume
source: counter-vol
target: /code
# redis作为容器名
redis:
# 基于redis:alpine镜像启动一个名为redis的容器。
image: "redis:alpine"
# 配置redis容器连接到counter-net网络。
networks:
counter-net:
# 定义了一个名为counter-net的网络
networks:
counter-net:
# 定义一个名为counter-vol的卷
volumes:
counter-vol:
# 基础镜像
FROM python:3.7-alpine
# 将app复制到镜像中
ADD . /code
WORKDIR /code
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
version
必须指定,且总是位于文件的第一行,它定义Compose文件格式的版本。services
用于定义不同的应用服务。Docker Compose会将每个服务部署在各自的容器中。networks
用于指引 Docker 创建新的网络。默认情况下Docker Compose会创建bridge网络。这是一种单主机网络,只能够实现同一主机上容器的连接。可以使用driver
属性来指定不同的网络类型。volumes
用于指引 Docker 来创建新的卷。
常用的启动一个Compose应用(通过Compose文件定义的多容器应用称为“Compose应用”)的方式就是docker-compose up
命令。它会构建所需的镜像,创建网络和卷,并启动容器。
默认情况下,docker-compose up
会查找名为docker-compose.yml
或docker-compose.yaml
的Compose文件。Docker Compose会将项目名称和Compose文件中定义的资源名称连起来,作为新构建的镜像的名称。

-d
参数在后台启动应用。
docker network ls
查看docker网络。docker volume ls
查看卷。

使用 Docker Compose 管理应用
docker-compose down
命令停止应用。
停止应用并不会删除卷,因为卷应该是用于数据长期持久化存储的。
docker-compose ps
命令查看应用状态。

命令
docker-compose up
部署一个Compose应用。docker-compose stop
命令停止Compose应用相关的所有容器。docker-compose restart
命令重新启动。如果用户在停止该应用后对其进行了变更,变更的内容不会反映在重启后的应用中,这时需要重新部署应用使变更生效。docker-compose rm
命令用于删除已停止的Compose应用。它会删除容器和网络,但是不会删除卷和镜像。docker-compose ps
命令用于列出Compose应用中的各个容器。docker-compose down
会停止并删除运行中的Compose应用。它会删除容器和网络,但不会删除卷和镜像。
第10章 Docker Swarm
跳过
第11章 Docker网络
Docker 在容器内部运行应用,这些应用之间的交互依赖于大量不同的网络。
Docker 网络架构源自一种叫作容器网络模型(CNM)的方案。Libnetwork 是 Docker 对 CNM 的一种实现,提供了 Docker 核心网络架构的全部功能。
Docker 封装了一系列本地驱动,其中包括单机桥接网络(Single-Host Bridge Network)、多机覆盖网络(Multi-Host Overlay),并且支持接入现有 VLAN(虚拟局域网)。
(暂时学到这,网络这块因为基础比较薄弱,看起来吃力,打算先看《计算机网络》,后续如果Docker用得多了会打算回来看这部分)。