Docker 是什么

Docker 是一个开源的应用容器平台(PaaS),基于 Go 语言开发,并遵从 Apache2.0 协议开源。Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何的宿主机器上。容器使用沙箱的机制,容器之间互相隔离,而且容器性能开销极低,并且可以在不同宿主环境之间互相迁移。

Docker 的优势在于:

  • 更高效的利用系统资源
  • 更快的启用时间
  • 一致的运行环境
  • 持续交付和部署
  • 更轻松的迁移
  • 更轻松地维护和扩展

Docker 原理

Docker 是基于 linux 内核实现的,它利用了多种 Linux 操作系统的机制来实现容器之间的互相隔离。

Namespace

linux 提供了一种叫 命名空间(Namespace)的机制,可以给进程、用户、网络等分配一个命名空间,这个命名空间下的资源都是独立命名的。

比如 PID namespace,也就是进程的命名空间,它会使命名空间内的这个进程 id 变为 1,而 linux 的初始进程的 id 就是 1,所以这个命名空间内它就是所有进程的父进程了。

IPC namespace 限制只有这个命名空间内的进程可以相互通信,不能和命名空间外的进程通信。

而 Mount namespace 会创建一个新的文件系统,命名空间内的文件访问都是在这个文件系统之上。

类似这样的 namespace 一共有 6 种:

  • PID(Process Identification) namespace:进程 ID 的命名空间,提供进程隔离能力
  • IPC(Inter-Process Communication) namespace:进程通信的命名空间,提供进程间通信的隔离能力
  • MNT(Mount) namespace:文件系统挂载的命名空间,提供磁盘挂载点和文件系统的隔离能力
  • Net(Network) namespace:网络的命名空间,提供网络隔离能力
  • User namespace:用户和用户组的命名空间,提供用户隔离能力
  • UTS(UNIX Timesharing System) namespace:主机名和域名的命名空间,提供主机名隔离能力

Docker 通过这 6 种命名空间,可以实现资源的隔离。

Control Group(CGroup)

linux 提供了一种叫 控制组(Control Group, CGroup)的机制可以通过给 控制组 指定参数,来限制控制组可以获取到的资源。比如 cpu 用多少、内存用多少、磁盘用多少。

创建容器的时候先创建一个 控制组,指定资源的限制,然后把容器进程加到这个 控制组 里,就不会有容器占用过多资源的问题了。

Docker 通过控制组的机制,可以限制容器对资源的访问,即资源访问限制。

UnionFS

联合文件系统(Union File System,UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下。镜像可以通过分层来进行继承,基于基础镜像,可以制作各种具体的应用容器。这样如果有多个容器内做了文件修改,只要创建不同的层即可,底层的基础镜像是一样的。

Docker 通过 UnionFS 的这种分层的镜像存储,写时复制(Copy-on-Write)的机制,极大的减少了文件系统的磁盘占用。

Docker 的架构

Docker 的架构图如下:

docker_architecture_inner
Docker 的一些基本概念:

  • Docker 守护进程(Docker daemon, dockerd): Docker 守护进程(dockerd)侦听 Docker API 请求并管理 Docker 对象,如镜像、容器、网络和 volumes。守护进程还可以与其他守护进程通信以管理 Docker 服务。
  • Docker 客户端(docker): Docker 客户端(docker)是许多 Docker 用户与 Docker 交互的主要方式。当 Docker 用户使用诸如 docker run 之类的命令时,客户端将这些命令发送到 dockerd,dockerd 执行这些命令。Docker 命令使用 Docker API 。Docker 客户端可以与多个守护进程通信。
  • Docker 对象(Docker objects): Docker 对象是在使用 Docker 过程中控制与管理的一些对象。
    • 镜像(Docker images): 镜像是一个只读的模板,包含创建 Docker 容器的说明。通常,一个镜像基于另一个镜像,并带有一些额外的定制内容。例如,Docker 用户可以构建一个基于 ubuntu 镜像的镜像,但是安装有 Apache web 服务器和应用程序,以及运行应用程序所需要的配置详细信息。Docker 用户可以创建自己的镜像,也可以只使用其他人创建并在 Docker registry 中发布的镜像。
    • 容器(Docker containers): 容器是镜像的可运行实例。Docker 用户可以使用 Docker API 或者 CLI 管理容器。默认情况下,容器与其他容器及其主机相对良好地隔离。Docker 用户可以控制容器的网络、存储或其他底层子系统与其他容器或主机的隔离程度。容器由其映像以及创建或启动时提供给它的任何配置选项定义。移除容器后,未存储在持久存储中的对其状态的任何更改都将消失。
    • Docker volumes: Volume 提供了将容器的特定文件系统路径连接回主机的能力(数据持久化的能力)。如果挂载(mounting)了容器中的目录,则在主机上也可以看到该目录中的更改。如果我们跨容器重新启动挂载相同的目录,我们将看到相同的文件。
  • Docker 桌面端(Docker Desktop): Docker Desktop 是一款适用于 Mac、Windows 或 Linux 环境的易于安装的应用程序,使 Docker 用户能够构建和共享容器化应用程序和微服务。Docker 桌面端包括 Docker 守护进程(dockerd)、Docker 客户端(docker)、Docker Compose、Docker Content Trust、Kubernetes 和凭证助手(Credential Helper)。
  • Docker 仓库(Docker repository): Docker 仓库可看成一个 Docker 镜像的控制中心。每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。
  • Docker registry: Docker registry 是 Docker 镜像存储的地方,Docker 用户可以在 Docker registry 获取和发布镜像。默认是 Docker Hub
  • 主机(Docker Host): 一个物理主机或者虚拟机,用于运行 Docker 守护进程和容器。
  • Docker Compose:Docker Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose,可以使用 YML 文件来配置应用程序需要的所有服务。然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有服务。
  • Dockerfile 文件: Dockerfile 是一个基于文本的指令脚本,它使用简单的语法定义创建镜像并运行镜像所需的步骤。Dockerfile 中的每个指令在镜像中创建一个层(layer)。更改 Dockerfile 并重建镜像时,仅重建已更改的层。
  • docker-compose.yml 文件: docker-compose.yml 是 docker compose 命令的参数配置文件。
  • .dockerignore 文件: .dockerignore 文件表明了在 Dockerfile 文件 的 COPY 指令中需要缓存的文件。

Docker 和 虚拟机 的区别

虚拟机:虚拟机是通过 Hypervisor (虚拟机管理系统,常见的有 VMWare workstation、VirtualBox),虚拟出网卡、cpu、内存等虚拟硬件,再在其上建立虚拟机,每个虚拟机是个独立的操作系统,拥有自己的系统内核。

容器:容器是利用 namespace 机制将文件系统、进程、网络、设备等资源进行隔离,利用 cgroup 机制对权限、cpu资源进行限制,最终让容器之间互不影响,容器无法影响宿主机。

docker_virtual_inner

对比项 Docker(容器化) 虚拟机
启动速度 秒级别 分钟级别
硬盘使用 一般为MB 一般为GB
性能 接近原生 弱于原生
系统支持量级 单机支持上千个容器 一般支持几十个
隔离性 进程级别的隔离 系统级别隔离
安全性 与宿主机共享内核、文件系统等资源,一旦容器内的用户从普通用户权限提升为 root 权限,有可能对其他容器、宿主机造成影响 虚拟机租户 root 权限和宿主机的 root 虚拟机权限是分离的,甚至使用了硬件隔离,可以防止虚拟机突破和彼此交互

我们可以看到,Docker 的技术优势体现在 _效率_ 、 _性能_ 和 资源消耗 上,在 安全性隔离级别 上与虚拟机相比较弱。

Docker 开始教程

在开始之前,首先需要先 下载并安装 Docker。Docker 是 Docker Inc.(原 dotCloud 公司) 的产品,它包括了 Docker EE (企业版)和 Docker CE(社区版)。需要注意的是,Docker 的开源代码仓库叫做 Moby

clone

首先,克隆一个仓库

Getting Started 项目是一个简单的 Github 仓库,它包含了你需要编译镜像并作为容器运行的所有内容。

通过在一个容器中运行 Git 来克隆这个仓库。

1
2
docker run --name repo alpine/git clone https://github.com/docker/getting-started.git
docker cp repo:/git/getting-started/ .

build

现在,编译镜像

一个 Docker 镜像是一个只为容器服务的私有的文件系统,它提供了容器所需要的所有文件与代码。

1
2
cd getting-started
docker build -t docker101tutorial .

run

运行你的第一个容器

基于你上一步编译的镜像启动一个容器。运行一个容器会以它私有的资源启动应用程序,与机器的其他部分安全的隔离开来。

1
docker run -d -p 80:80 --name docker-tutorial docker101tutorial

你会发现我们使用了一些参数。这里包含了这些参数的一些信息:

-d - 以独立模式(在后台中)运行容器。
-p 80:80 - 将容器中的80端口与主机的80端口一一映射。打开网络浏览器并导航到 http://localhost:80 来访问教程应用。如果已经有服务监听了 80 端口,你可以指定另一个端口。例如,指定 -p 3000:80 然后通过 http://localhost:3000 来访问应用。
--name docker-tutorial - 给容器起一个名字 docker-tutorial
docker101tutorial - 指出使用的镜像。

share

现在保存并分享你的镜像

保存并分享你的镜像到 Docker Registry(默认 Registry 为 Docker Hub,需要通过 docker login 命令登录)来使其他用户可以容易地在任何目标机器下载和运行镜像。

1
2
docker tag docker101tutorial  /docker101tutorial
docker push /docker101tutorial

Docker 常用功能

使用 docker COMMAND --help 可以查看具体 COMMAND 命令的用法和作用。

镜像管理 image

Docker 中经常要做的是将镜像拉取到本地,和镜像相关的有一些常用的操作包括:

登录 Docker registry、拉取镜像、编译镜像、移除镜像 等。

管理镜像的命令都集合在 docker image COMMAND 命令下,包括 build history import inspect save/load ls prune pull/push rm tag

  1. docker login [OPTIONS] [SERVER] [flags]

登录 Docker registry 或者云后台。如果没有提供 registry 服务器,默认由守护进程定义。

参数 完整参数 作用
-p --password string 密码
-u --username string 用户名
  1. docker pull [OPTIONS] NAME[:TAG|@DIGEST]

从 registry 拉取一个镜像或者一个仓库

参数 完整参数 作用
-a --all-tags 下载仓库中所有已打标签的镜像
--disable-content-trust 跳过镜像认证 (默认为 true)
--platform string 如果服务器支持多平台,则设置平台
-q --quiet 不显示详细输出日志
  1. docker build [OPTIONS] PATH | URL | -

根据 Dockerfile 编译镜像

参数 完整参数 作用
-f --file string Dockerfile的名字(默认是 ‘PATH/Dockerfile’)
--network string 设置运行实例在运行时的网络模式(默认为 default)
--no-cache 编译镜像时不使用缓存
  1. docker rmi [OPTIONS] IMAGE [IMAGE...]

移除一个或多个镜像

参数 完整参数 作用
-f --force 强制移除镜像
  1. docker save [OPTIONS] IMAGE [IMAGE...]

保存一个或多个镜像到一个 tar 包中(默认流式输出到 STDOUT)

参数 完整参数 作用
-o --output string 写入到文件中,而不是STDOUT
  1. docker load [OPTIONS]

从一个 tar 包或者 STDIN 加载一个镜像

参数 完整参数 作用
-i --input string 从 tar 包读取,而不是 STDIN
-q --quiet 不显示详细输出日志
  1. docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]

从一个 tar 包导入容器创建一个文件系统镜像

参数 完整参数 作用
-c --change list 对创建的镜像应用 Dockerfile 指令
-m --message string 为导入的镜像设置提交消息
  1. docker inspect [OPTIONS] NAME|ID [NAME|ID...]

显示一个或多个 Docker 对象(包括镜像、容器、volume 等)的详细信息

参数 完整参数 作用
-f --format string 将输出字符串以给定的 Go 模板形式格式化
  1. docker history [OPTIONS] IMAGE

显示镜像的历史

参数 完整参数 作用
--format string 使用 Go 模板优化显示镜像
-H --human 以人类可读的格式打印大小和日期(默认为 true)
-q --quiet 仅仅显示镜像 ID
  1. docker image ls [OPTIONS] [REPOSITORY[:TAG]]

列出所有镜像

参数 完整参数 作用
-a --all 显示所有镜像(默认隐藏中间的镜像)
--digests 显示摘要
-f --filter filter 根据提供的条件过滤输出
--format string 使用 Go 模板优化显示镜像

容器管理 container

管理容器是 Docker 中最常见的操作,包括 查看容器列表、查看容器详情、创建容器、在容器中运行命令、启动容器、暂停容器、停止容器、移除容器 等。

管理容器的命令都集合在 docker container COMMAND 命令下,常用的命令包括:ps|ls|list create/rm exec run start/stop pause/unpause logsexportinspectkill 等。

  1. docker ps [OPTIONS]

查看所有容器

参数 完整参数 作用
-a --all 显示所有容器(默认只显示运行中的容器)
-f --filter filter 基于提供的条件过滤输出
-n --last int 显示最后创建的 n 个容器(包括所有状态)
-l --lastest 显示最后创建的容器(包括所有状态)
-s --size 显示总文件大小
  1. docker create [OPTIONS] IMAGE [COMMAND] [ARG...]

创建一个新的容器

参数 完整参数 作用
-c --cpu-shares int CPU占用(相对比重)
--cpus decimal CPU数量
-e --env list 设置环境变量
-h --hostname strin 容器主机名
-i --interactive 保持 STDIN 打开,即使未连接(以交互模式运行容器)
-m --memory bytes 内存限制
-p --publish list 发布一个容器的端口到主机
-t --tty 分配一个伪TTY
-u --user string 用户名或者UID(格式:<name/uid>[:group/gid]
-v --volume list 绑定挂载一个 volume
  1. docker exec [OPTIONS] CONTAINER COMMAND [ARG...]

在一个运行的容器中运行命令

参数 完整参数 作用
-d --detach 以后台模式执行命令
-e --env list 设置环境变量
-i --interactive 保持 STDIN 打开,即使未连接(以交互模式运行容器)
-t --tty 分配一个伪TTY
-u --user string 用户名或者UID(格式:<name/uid>[:group/gid]
-w --workdir string 在容器中的工作目录
  1. docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

在一个新容器中运行命令(根据镜像生成新的容器并运行命令,相当于 docker create + docker exec

参数 完整参数 作用
-c --cpu-shares int CPU占用(相对比重)
--cpus decimal CPU数量
-d --detach 以后台模式运行容器并打印容器 ID
-e --env list 设置环境变量
-h --hostname strin 容器主机名
-i --interactive 保持 STDIN 打开,即使未连接(以交互模式运行容器)
-m --memory bytes 内存限制
--name string 给容器起名
-p --publish list 发布一个容器的端口到主机
-t --tty 分配一个伪TTY
-u --user string 用户名或者UID(格式:<name/uid>[:group/gid]
-v --volume list 绑定挂载一个 volume
  1. docker start [OPTIONS] CONTAINER [CONTAINER...]

启动一个或多个容器

  1. docker stop [OPTIONS] CONTAINER [CONTAINER...]

停止一个或多个容器

参数 完整参数 作用
-t --time int 停止前等待的秒数(默认 10)
  1. docker pause CONTAINER [CONTAINER...]

暂停一个或多个容器的所有进程

  1. docker rm [OPTIONS] CONTAINER [CONTAINER...]

移除一个或多个容器

参数 完整参数 作用
-f --force 强制移除一个运行着的容器(使用 SIGKILL)
-l --link 移除特定的链接
-v --volumes 移除与容器相关的匿名 volumes
  1. docker logs [OPTIONS] CONTAINER

获取一个容器的日志

参数 完整参数 作用
--details 展示日志详情
-f --follow 跟踪日志输出
--since string 需要展示日志的开始时间(例如,2013-01-02T13:23:37Z)或相对开始时间(例如,42m 代表42分钟内)
-n --tail string 显示日志末尾的行数(默认 所有)
-t --timestamps 展示时间戳
--until string 需要展示日志的截止时间(例如,2013-01-02T13:23:37Z)或相对截止时间(例如,42m 代表42分钟内)

发布镜像 pull/push

  1. docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]

创建一个标签 TARGET_IMAGE 指向 SOURCE_IMAGE

  1. docker push [OPTIONS] NAME[:TAG]

将一个镜像或仓库 NAME 推送到 registry

参数 完整参数 作用
-a --all-tags 推送仓库中所有已打标签的镜像
--disable-content-trust 跳过镜像签名 (默认为 true)
-q --quiet 不显示详细输出日志

数据持久化 volume

默认的 Docker 引擎可以通过两种方式来将宿主的数据挂载到容器中 named volume、bind mounts。

docker_mount_volume_inner

named volume

named volume 可以视为一个简单的数据桶,Docker 维护磁盘上的物理位置,Docker 用户只需要记住 volume 的名称。当使用 volume 时,Docker 会确保提供正确的数据。

我们需要首先通过 docker volume create 创建一个 volume,然后在 docker create 或者 docker run 创建容器的时候通过 -v 参数指定要使用的 volume 名称和路径。

  1. docker volume create [OPTIONS] [VOLUME]

创建一个 volume

参数 完整参数 作用
-d -driver string 表明 volume 的驱动名(默认 local)
--label list 设置 volume 的元数据
-o --opt map 设置驱动特殊参数(默认 map[])
  1. docker volume inspect [OPTIONS] VOLUME [VOLUME...]

显示一个或多个 volume 的详细信息

参数 完整参数 作用
-f --format string 将输出字符串以给定的 Go 模板形式格式化

bind mounts

通过 bind mounts,我们可以控制主机上的确切的挂载点。bind mounts 可以用来持久化数据,但它通常用于向容器中提供额外的数据。

1
2
3
4
docker run -dp 3000:3000 \
-w /app -v "$(pwd):/app" \
node:12-alpine \
sh -c "yarn install && yarn run dev"

bind mounts 也是通过在 docker create 或者 docker run 创建容器的时候通过 -v 参数来指定绑定的路径,如上所示 "$(pwd):/app" 即为需要绑定挂载的主机路径。使用 bind mounts 在本地开发设置中非常常见。优点是开发机器不需要安装所有的构建工具和环境。只需一个 docker 运行命令,开发环境就可以启动了。

其他持久化方案

bind mounts 和 named volume 是 Docker 引擎附带的两种主要持久化方案。但是,还有其他 volume 驱动程序可用于支持其他用例(SFTP、Ceph、NetApp、S3等)。

网络管理 network

默认情况下,容器之间是互相隔离无法通信的,两个相关的容器只能通过 _网络_ 来进行通信。

与 _网络_ 相关的命令可以通过 docker network COMMAND 来调用,包括 connect/disconnect create inspect ls prune rm

  1. docker network create [OPTIONS] NETWORK

创建一个网络

参数 完整参数 作用
--attachable 允许手动附加容器
--aux-address map 网络驱动使用的辅助 IPv4 或 IPv6(默认 map[])
-d --driver string 管理网络的驱动(默认 bridge)
-o --opt map 设置驱动的特殊选项(默认 map[])
  1. docker network connect [OPTIONS] NETWORK CONTAINER

将一个容器连接到一个网络

参数 完整参数 作用
--ip IPv4地址
--ip6 IPv6地址
  1. docker network disconnect [OPTIONS] NETWORK CONTAINER

将一个容器从一个网络断开

参数 完整参数 作用
-f --force 强制将一个容器从一个网络断开
  1. docker network rm NETWORK [NETWORK...]

移除一个或多个网络

多镜像应用 compose

Docker Compose 是一个开发用于帮助定义和共享多容器应用程序的工具。使用 Compose,我们可以创建一个 YAML 文件来定义服务,并且使用一个命令,可以将所有容器同时启动或销毁。

Compose 需要使用 docker-compose COMMAND 或者 docker compose COMMAND 来调用,常用的命令有 build config create/rm up/down start/stop/restart images kill pause/unpause pull/push exec run ps logs port 等。

  1. docker compose up [OPTIONS] [SERVICE...]

创建并启动所有容器

参数 完整参数 作用
--build 在启动容器前先编译镜像
-d --detach 独立模式,在后台运行容器
--no-recreate 如果容器已经存在,不重建。与 --force-recrete冲突
-V --renew-anon-volumes 重新创建匿名volume而不是从之前的容器中检索数据
-t --timeout int 在连接容器或容器已经运行时,使用此超时时间(以秒为单位)关闭容器。(默认值为10)
  1. docker compose down [OPTIONS]

停止并移除所有容器、网络

参数 完整参数 作用
--remove-orphans 删除 Compose 文件中未定义的服务中的容器
rmi string 删除服务使用到的镜像,”local”仅仅删除没有指定tag的镜像(”local”/“all”)
-t --timeout int 停止前等待的秒数(默认 10)
-v --volumes volumes 删除在 Compose 文件的 volume 部分中声明的 named volumes 和附加到容器的匿名 volumes。

插件管理 plugin

Docker 可以通过 docker plugin COMMAND 来管理插件,包括以下命令:

命令 作用
create 从 rootfs 和配置文件创建插件。插件数据目录必须包含 config.json 和 rootfs 目录。
disable 禁用一个插件
enable 启用一个插件
inspect 显示一个或多个插件的详细信息
install 安装一个插件
ls 列出所有插件
push 发布一个插件到 registry
rm 移除一个或多个插件
set 变更一个插件的设置项
upgrade 更新一个已存在的插件

安全管理 secret/scan

Docker Scan 是一个安全扫描工具,可以扫描本地的镜像中的安全漏洞,Docker Scan 是与 Snyk 合作来提供漏洞扫描服务的,所以必须登录 Docker Hub 账号。

docker scan [OPTIONS] IMAGE

对镜像进行安全漏洞扫描

参数 完整参数 作用
-f --file string 与镜像相关的 Dockerfile,提供更多详细的结果
--json 以 JSON 格式输出结果

Docker Secret 用于在 Swarm mode 集群中安全的管理密码、密钥证书等敏感信息,并允许在多个 Docker 容器实例之间共享访问指定的秘密信息。docker secret 只能从 Docker Swarm 模式的 manager 节点调用,如果在本机进行试验,需要先执行 docker swarm init 命令。secret 创建之后可以在 docker service create 中通过 --secret 参数使用,或者在 Compose 文件中使用。

命令 作用
create 从文件或者 STDIN 创建一个 secret
inspect 显示一个或多个 secret 的详细信息
ls 列出所有的 secret
rm 移除一个或多个 secret

集群管理 swarm

Docker Swarm 是 Docker 公司推出的用来管理 docker 集群的平台,

docker_swarm_inner

Swarm 包括几个基本的概念

  • swarm :集群管理工具
  • node:节点,一个节点就是docker集群中的一个实例,我们可以在单台服务器上运行一个或多个节点
  • service:应用编排
  • task:应用实例

一个 Swarm 由一个或多个 Docker 节点组成。所有节点通过可靠的网络相连。节点会被配置为管理节点(Manager)或工作节点(Worker)。管理节点负责集群控制面(Control Plane),进行诸如监控集群状态、分发任务至工作节点等操作。工作节点接收来自管理节点的任务并执行。Swarm 的配置和状态信息保存在一套位于所有管理节点上的分布式 etcd 数据库中。该数据库运行于内存中,并保持数据的最新状态。关于该数据库最棒的是,它几乎不需要任何配置,作为 Swarm 的一部分被安装,无须管理。关于集群管理,最大的挑战在于保证其安全性。搭建 Swarm 集群时将不可避免地使用 TLS,因为它被 Swarm 紧密集成。关于应用编排,Swarm 中的最小调度单元是服务。它是随 Swarm 引入的,在 API 中是一个新的对象元素,它基于容器封装了一些高级特性,是一个更高层次的概念。当容器被封装在一个服务中时,我们称之为一个任务或一个副本,服务中增加了诸如扩缩容、滚动升级以及简单回滚等特性。

与 Swarm 相关的 docker 命令包括 docker swarm docker node docker service docker stack 等。

Docker Swarm 与 Kubernetes 对比

Docker Swarm 是 Docker 公司于 2015 年初发布的一款容器编排工具。Kubernetes 是 2014年中发布的一款容器编排工具,并得到了 Google 和 RedHat 的支持。

Docker Swarm 优势:

  1. 架构简单,部署运维成本较低
  2. 启动速度快

Docker Swarm 劣势:

  1. 无法提供更精细的管理
  2. 网络问题
  3. 容器可靠性

Kubernetes 优势:

  1. 管理更趋于完善稳定
  2. 健康机制完善
  3. 轻松应对复杂的网络问题

Kubernetes 劣势:

  1. 配置、搭建稍显复杂,学习成本高
  2. 启动速度稍逊

Dockerfile 参考

Dockerfile 的用法详见 Dockerfile reference
镜像是通过 Dockerfile 来描述的,Dockerfile 遵循以下格式:

1
2
3
# parser directive
# Comment
INSTRUCTION arguments

解析指令(parser directive)

解析器指令是可选的,并且会影响后续处理 Dockerfile 的方式。解析器指令不会将层添加到构建中,并且不会显示为构建步骤。解析器指令以# directive=value 的方式使用。 一个指令只能使用一次。

  • syntax:定义用于构建 Dockerfile 的 Dockerfile 语法的位置,仅在使用 BuildKit 后端时可用,在使用经典构建器后端时被忽略。
  • escape:设置用于转义 Dockerfile。 如果未指定,则默认转义字符为 \

指令(INSTRUCTION)

Dokcer 是分层存储的,修改的时候会创建一个新的层,所以这里的每一行(每个指令)都会创建一个新的层。

  • ENV: 用于声明环境变量
  • ARG:必须在 FROM 指令之前声明,指定镜像参数,相当于 docker build 命令中的 --build-arg <varname>=<value> 参数
  • ONBUILD:向镜像添加一个触发指令,当镜像用作另一个构建的基础时,该触发指令将在以后执行。触发器将在下游构建的上下文中执行,就像它是在下游 Dockerfil e中的 FROM 指令之后立即插入的一样。
  • FROM:初始化一个新的构建阶段并为后续指令设置 基本镜像,一个 Dockerfile 文件必须以 FROM 指令开始。
  • WORKDIR:为 RUN、CMD、ENTRYPOINT、COPY、ADD 指令指定当前工作目录
  • USER:设置用户名(或UID)和可选的用户组(或GID),以用作当前阶段剩余时间的默认用户和组。给定的用户将作为 RUN 、ENTRYPOINT、CMD 指令的用户
  • VOLUME:创建具有指定名称的装载点,并将其标记为保存来自本地主机或其他容器的外部装载 volume。
  • LABEL:为镜像添加元数据
  • ADD:复制新文件,目录或者远程文件 URL,将其从原地址添加到镜像中的地址
  • COPY:复制新文件,目录,将其从原地址添加到镜像中的地址
  • EXPOSE:声明当前容器要监听的网络端口
  • RUN:将在当前镜像之上的新层中执行任何命令并提交结果
  • CMD:容器启动的时候执行的命令,一个 Dockerfile 文件只有最后一个 CMD 指令会生效。
  • ENTRYPOINT:将一个容器配置为可执行的运行
  • STOPSIGNAL:设置退出时将会被发送到容器的系统调用信号
  • HEALTHCHECK:告知 Docker 如何检测一个容器是否运行
  • SHELL:可以覆盖默认使用的 SHELL 类型。Linux 默认的 shell 为 ["/bin/sh", "-c"] Windows 默认为 ["cmd", "/S", "/C"]

一个常见的 nodejs 应用的 Dockerfile 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# build stage
FROM node:10 AS build_image

WORKDIR /app

COPY . /app

EXPOSE 8080

RUN npm install && npm run build

# production stage
FROM node:10

WORKDIR /app

COPY --from=build_image /app/dist ./dist

RUN npm i -g http-server

CMD http-server ./dist

我们把两个镜像的生成过程写到了一个 Dockerfile 里,这是 Docker 支持的多阶段构建。

  1. FROM node:10 表示这个镜像是基于 node:10 来构建的,AS build_image,这是把第一个镜像命名为 build_image。
  2. WORKDIR /app 表示当前工作目录是 /app 。
  3. COPY . /app 表示将当前路径下的内容复制到 /app 路径下
  4. EXPOSE 8080 表示当前容器要访问的网络端口为 8080 端口
  5. RUN npm install && npm run build 表示在编译的时候(docker build)执行 npm install && npm run build
  6. COPY --from=build_image /app/dist ./dist 表示将来自 build_image 镜像的 /app/dist 路径下的内容复制到 ./dist 路径下
  7. RUN npm i -g http-server 表示在变异的时候运行 npm i -g http-server
  8. CMD http-server ./dist 表示在创建容器(docker create)的时候执行 http-server ./dist

一个常见的 前端 项目的 Dockerfile 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# build stage
FROM node:14.15.0 as build-stage

WORKDIR /app

COPY package.json ./

RUN npm install

COPY . .

RUN npm run build

# production stage
FROM nginx:stable-perl as production-stage

COPY --from=build-stage /app/dist /usr/share/nginx/html

COPY --from=build-stage /app/default.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

与 nodejs 项目类似也是编译阶段一个镜像,发布阶段一个镜像。 npm installnpm run build 分开的好处是可以在 .dockerignore 文件中添加 node_moudules 从而对依赖包进行缓存。

如果是公网开放的服务,可以在编译后将前端静态资源文件(js、css、ttf、png、jpg、jpeg、gif、svg文件等)上传到云存储服务器,并开启 CDN 服务,这样用户端下载静态资源文件会更快,体验会更好。

Dockerfile 最佳实践

  • 不要安装无效软件包
  • 应简化镜像中同时运行的进程数,理想状况下,每个镜像应该只有一个进程
  • 当无法避免同一镜像运行多进程时,应选择合理的初始化进程(init process)
  • 最小化层级
    • 最新的 docker 只有 RUN COPY ADD 创建新层,其他指令创建临时层,不会增加镜像大小
      • 比如 EXPOSE 指令就不会生成新层
    • 多条 RUN 指令可通过连接符成一条指令集以减少层数
    • 通过多端构建减少镜像层数
  • 把多行参数按字母排序,可减少可能出现的重复参数,并且提高可读性
  • 编写 Dockerfile 的时候,应该把变更频率低的编译指令优先构建以便放在镜像底层以有效利用 build cache
  • 复制文件时,每个文件应独立复制,这确保某个文件变更时,只影响该文件对应的缓存