docker 是一个基于 Go 语言开发的开源容器化平台,旨在通过轻量级、可移植的容器技术加速应用程序的交付并提高资源利用率。它秉承“构建一次,运行任何地方”的理念,利用 Linux 内核特性如命名空间(Namespaces)和控制组(Control Groups, cgroups)实现高效的资源隔离与限制,确保每个容器拥有独立的运行环境。docker 提供了一系列工具来简化应用的打包、分发和部署过程。例如,Dockerfile 定义了应用及其依赖的构建指令,而 Docker Compose 则允许用户定义和管理多容器应用的服务。这种设计使得开发者可以轻松创建出轻量、便携且自包含的容器,极大提升了开发效率和应用的一致性。

应用容器(Application Container) 是 docker 的专业术语,指的是一种轻量级、自包含的软件包,它包含了应用程序及其所有依赖项,可以在任何环境中一致地运行。docker 容器的设计初衷就是为了让开发者能够“构建一次,运行任何地方”,即无论是在开发者的笔记本电脑、测试服务器还是生产环境,容器化应用的行为都是一致的。

总结来说,docker 通过其创新性的容器化技术,提供了一种比传统虚拟机更为轻量和高效的替代方案,特别适合于云原生应用和分布式系统的部署。它不仅简化了开发和运维工作,还提升了应用的可移植性和扩展性,成为现代软件开发不可或缺的一部分。

  • 镜像(Images):docker 镜像是用于创建 docker 容器的模板
  • 容器(Containers):docker 容器是从 docker 镜像创建的运行实例
  • 挂载(Mounting):挂载是一种将宿主机的文件或目录在容器内部可见和可访问的方式,这种方式对于数据持久化和容器间数据共享十分重要

什么是 docker 容器

docker 是一种流行的开源软件平台,可简化创建、管理、运行和分发应用程序的过程。它使用容器打包应用程序及其依赖项,我们也可以将容器视为 Docker 镜像的运行实例。

Docker 和虚拟机有何不同

docker 是轻量级的沙盒,在其中运行的只是应用,虚拟机里面还有额外的系统。

相较于传统的虚拟机,docker 容器启动更快、占用更少的系统资源,并且能够更高效地共享主机的操作系统内核。此外,docker 采用客户端-服务器(C/S)架构,借助远程 API 实现对容器生命周期的全面管理。其核心原则——构建(Build)、传输(Ship)、运行(Run)——贯穿于整个应用开发生命周期,支持从开发到生产的无缝过渡。

通过结合 Linux 内核的命名空间(Namespaces)和控制组(Cgroups),docker 实现了强大的资源隔离和限制,显著增强了容器的安全性和稳定性。这使得每个容器都能拥有独立的运行环境,同时共享主机的操作系统内核,避免了传统虚拟机带来的额外性能开销——后者通常会占用物理资源的 6-8%。

docker 为现代软件开发提供了一个灵活、高效且易于使用的解决方案,极大地推动了微服务架构和持续集成/持续部署(CI/CD)流程的发展。相较于需要完整操作系统实例的传统虚拟机,docker 容器启动速度更快、资源利用率更高,因为它们不包含操作系统层,而是依赖于宿主机的内核。

什么是 DockerFile

Dockerfile 是一个文本文件,其中包含了我们需要运行以构建 docker 镜像的所有命令,每一条指令构建一层,每一条指令的内容用于描述该层应当如何构建。docker 使用 Dockerfile 中的指令自动构建镜像。

示例 Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 使用官方的 Python 3.9 环境作为基础镜像
FROM python:3.9-slim

# 设置工作目录
WORKDIR /app

# 复制当前目录下的所有文件到容器的 /app 目录
COPY . .

# 安装应用所需的依赖
RUN pip install --no-cache-dir -r requirements.txt

# 暴露应用程序运行的端口
EXPOSE 5000

# 设置环境变量
ENV NAME World

# 定义容器启动时执行的命令
CMD ["python", "app.py"]

一些常用的指令:

参数 解释
FROM 建立基础镜像。在所有有效的 Dockerfile 中,FROM 是第一条指令
ENV 设置环境变量。这些变量可以在后续的构建步骤中使用,也可以在运行时由容器继承
RUN 执行命令行操作。通常会结合包管理工具(如 apt-getyum)来安装必要的依赖
COPY 本地文件或目录复制到镜像中的指定位置
EXPOSE 声明容器运行时要监听的端口。虽然这不会自动打开端口,但它为运行容器的人提供了有关哪些端口应该被映射的信息
CMD 容器默认启动命令。如果用户在运行容器时没有提供其它命令,那么这个命令将会被执行
WORKDIR 设置工作目录。所有后续的 RUNCMDENTRYPOINTCOPYADD 指令都将在这个目录下执行。
LABEL 为镜像添加元数据,例如作者信息、版本号等

构建镜像

要根据 Dockerfile 构建镜像,我们可以使用 docker build 用来创建按顺序执行多个命令行指令的自动创建。

1
docker build -t my-python-app .

这里的 -t 参数用于给构建的镜像打上标签, . 表示 Dockerfile 所在的当前目录。

Docker Compose 如何保证容器 A 先于容器 B 运行

Docker Compose 是一个用来定义和运行复杂应用的 docker 工具。一个应用通常由多个容器组成,使用 Docker Compose 将不再需要使用脚本来启动容器。Compose 通过一个配置文件来管理多个 Docker 容器。简单理解:Docker Compose 是 docker 的管理工具。

Docker Compose 在继续下一个容器之前不会等待容器准备就绪。为了控制我们的执行顺序,我们可以使用 depends_on 条件,使用 docker-compose up 命令将按照我们指定的依赖顺序启动和运行服务。

depends_on 指令用于定义服务之间的启动顺序。当使用 docker-compose up 命令时,Compose 会按照 depends_on 定义的依赖关系来启动服务。然而,depends_on 只确保指定的服务在逻辑上先被启动,并不等待这些服务完全初始化或变得可用

示例

1
2
3
4
5
6
7
8
version: '3.8'
services:
db:
image: postgres
web:
image: my-web-app
depends_on:
- db

在这个例子中,web 服务会在 db 服务启动后启动,但这并不意味着 web 会等到 db 完全准备好了才开始启动。web 服务可能会在 db 服务还没有完全初始化的情况下尝试连接数据库,这可能导致连接失败。

更严格的依赖控制

为了确保一个服务在另一个服务完全准备好后再启动,您可以采取以下几种方法

  • 健康检查(Health Checks)

使用 docker 的 healthcheck 指令可以为服务定义健康检查的条件。只有当健康检查通过时,服务才会被视为“健康”。这样可以在后续服务启动时确保依赖的服务已准备好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: '3.8'
services:
db:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
web:
image: my-web-app
depends_on:
db:
condition: service_healthy

在这个例子中,web 服务将不会启动,直到 db 服务通过了健康检查。

  • 自定义脚本

编写一个简单的脚本,在启动之前检查依赖服务的状态。例如,可以使用 wait-for-it.shdockerize 工具来等待特定端口或服务变得可用

1
2
3
4
5
6
7
version: '3.8'
services:
db:
image: postgres
web:
image: my-web-app
entrypoint: ["./wait-for-it.sh", "db:5432", "--", "python", "app.py"]

在这个例子中,web 服务会先运行 wait-for-it.sh 脚本来等待 db 服务的 5432 端口变得可用,然后再启动应用程序。

  • 重试机制

在应用程序代码中实现重试机制,以处理依赖服务暂时不可用的情况。例如,如果应用程序尝试连接数据库失败,它可以等待几秒钟后再次尝试连接。

  • 第三方工具

还有一些第三方工具可以帮助管理复杂的依赖关系,如 docker-waitwait-for。这些工具提供了更多的灵活性和功能来确保服务按正确的顺序启动并准备好

docker 主要组成部分

  1. Docker Client(客户端)
    • 定义:docker 客户端是用户与 docker 守护进程(Docker Daemon)交互的主要接口。它允许用户通过命令行工具 dockerREST API 发送请求给守护进程。
    • 功能
      • 执行各种 docker 命令,如创建、启动、停止、删除容器等
      • 管理镜像、网络、卷等资源
      • 支持多种输出格式,包括 JSON、表格等,方便自动化脚本使用
  2. Docker Daemon(守护进程)
    • 定义:docker 守护进程(dockerd)是后台运行的服务,负责管理 docker 对象(如镜像、容器、网络和卷)。它是 docker 架构的核心组件,处理来自客户端的请求,并管理容器的生命周期。
    • 功能
      • 接收来自客户端的指令并执行相应的操作
      • 管理本地存储的镜像和容器
      • 与注册表(Registry)通信以拉取或推送镜像
      • 提供插件机制,支持扩展功能(如网络、存储驱动等)
      • 监控容器的状态变化,并根据需要调整资源分配
  3. Docker Image(镜像)
    • 定义:docker 镜像是一个只读模板,包含了操作系统环境、应用代码及其依赖项。它是构建容器的基础,可以看作一个静态的应用程序包。
    • 功能
      • 由一系列分层文件系统组成,每一层代表一个特定的更改或添加
      • 可以基于现有的镜像进行定制,通过 Dockefile 定义构建过程
      • 支持版本控制,每个镜像都有唯一的标签(tag),便于管理和追踪不同版本
      • 可以从公共或私有的 Docker Registry 中获取,也可以本地构建
  4. Docker Container(容器)
    • 定义
    • 功能
  5. Docker Registry(注册表)
    • 定义
    • 功能
  6. Docker Network(网络)
    • 定义
    • 功能
  7. Docker Volume(卷)
    • 定义
    • 功能
  8. Docker Compose
    • 定义
    • 功能
  9. Docker Swarm 和 Kubernetes
    • 定义
    • 功能

Docker 常用命令

  1. 查看本地主机的所用镜像:docker images
  2. 搜索镜像:docker search mysql
  3. 下载镜像:docker pull mysql,默认下载 latest 镜像
  4. 下载指定版本的镜像:docker pull mysql:5.7
  5. 删除镜像:docker rmi -f 镜像id 镜像id 镜像id

描述 Docker 容器生命周期

Docker 容器经历以下阶段:

  • 创建容器
  • 运行容器
  • 暂停容器(可选)
  • 取消暂停容器(可选)
  • 启动容器
  • 停止容器
  • 重启容器
  • 杀死容器
  • 销毁容器

Docker 容器间如何隔离

NameSpace 和 Cgroups

Linux 中的 PID(进程 id)、IPC(进程间通信)、网络等资源是全局的,而 Linux 的 NameSpace 机制是一种资源隔离方案,在该机制下这些资源将不再是全局的,而是属于某个特定的 NameSpace,各个 NameSpace 下的资源互不干扰。

NameSpace 实际上修改了应用进程看待整个计算机“视图”,即它的“视线”被操作系统做了限制,只能“看到”某些指定的内容。对于宿主机来说,这些被“隔离”了的进程跟其它进程并没有区别。

虽然 NameSpace 技术可以实现资源隔离,但是进程还是可以不受控的访问系统资源,比如 CPU、内存、磁盘、网络等,为了控制容器进程对资源的访问,Docker 采用 control groups 技术(也就是 cgrous),有了 cgroups 就可以控制容器中进程对资源的访问,比如限制某个容器使用内存的上限、可以在哪些 CPU 上运行等等。

有了这两项技术,容器看起来就真的像是独立的操作系统了。