zroger's blog

Docker 笔记

ZRoger ZRoger #docker

Docker 与 Docker Compose 的使用笔记:容器基础、Compose 快速上手,以及项目名称、生命周期钩子、profiles 等进阶用法。

这个系列记录我在项目里使用 Docker 与 Docker Compose 时整理下来的笔记。

  1. 第一篇是 Docker Desktop 的上手流程——镜像加速、端口映射、容器生命周期,以及容器隔离背后的 namespaces 与 cgroups;
  2. 第二篇跟随官方 Quick Start 走一遍 Dockerfile 加 compose.yaml 的完整示例,并覆盖 Compose Watch 与多文件拆分;
  3. 第三篇则是一些进阶用法:项目名称的优先级规则、生命周期钩子、用 profiles 切换环境,以及服务启动顺序的控制。

Docker 快速上手

ZRoger ZRoger #docker

Docker Desktop 入门:镜像加速、端口映射与容器生命周期管理,以及容器隔离依赖的 namespaces 与 cgroups。

开始

环境 win10,已开启 Hyper-V后,成功安装 Docker Desktop。打开后可以看到一个命令:

Terminal window
docker run -d -p 80:80 docker/getting-started

在执行这个命令之前,先修改下 docker 中的镜像

地址:Settings/Docker Engine
{
"registry-mirrors": [
"https://registry.docker-cn.com"
],
....
}

或者使用阿里的docker镜像

之后再执行命令:

docker run

成功后,访问 localhost/tutorial,在 docker desktop 中也可以看到刚刚获取的东西

docker desktop

对于刚刚运行的命令

docker run -d -p 80:80 docker/getting-started
  • -d :在后台中以隔离模式运行容器
  • -p 80:80 :将主机localhost中 80 端口映射到容器的 80 端口
  • docker/getting-started:使用到的镜像

以上的命令也可以缩写成:

docker run -dp 80:80 docker/getting-started

Docker Dashboard

在继续的深入之前,想重点介绍 docker dashboard,它可以让你快速看到目前机器上运行了的容器。可以快速访问容器日志,容器中有命令解析器(即可以在容器中输入命令),更方便管控容器的生命周期(停止服务,删除等)

getting-started 在一个随机命名的容器内。当然你也可以随意取名

先查看系统中目前的容器

Terminal window
docker ps -a

找到想要修改的容器,找到它的 container id,修改名称:

Terminal window
docker rename container_id newname

第一个参数可以是 container id 和 name。如果一个容器的name和另一个容器的id是一样的,会执行两次。

docker ps
CONTAINER ID NAMES
41033a3d043a hello
41033a3d043b 41033a3d043a

docker rename第一个参数<my_container>,是先匹配NAME,再匹配ID的。也就是说下列语句会被正确执行两次,第一次会把name为41033a3d043a的容器改名,第二次会把id为41033a3d043a的容器改名。

什么是容器(container)?

现在你已经运行了一个容器,什么是容器呢?简单来说,容器是你机器上的一个进程,可以把它想象成是一个箱子,与主机上的其他进程隔离。这种隔离的方式利用 内核(kernel namespaces)内核名称空间与 cgroups,这个功能已经在 Linux 存在了很长一段时间。Docker 致力与使这些功能更易于使用。

如果想知道如何从头开始创建容器,可以看下来自 Aqua Security 的 Liz Rice 的演讲,展示了使用 Go 从头开始创建一个容器。虽然这是一个简单容器,没有涉及到网络,文件系统映像等等。但是它可以让你深入了解这些是如何运作的。

Docker Compose - Quick Start

ZRoger ZRoger #docker

Docker Compose 快速上手:从 Flask + Redis 示例串起 Dockerfile、compose.yaml、Compose Watch,以及多文件拆分服务。

Docker Compose Quick Start

对 官网 Docker Compose Quick Start 的一些简单记录

用 Docker Compose 来解决什么问题 ?

比如:使用 docker 来构建 redis 哨兵模式来学习,构建 ELK ,或者提供一个当前系统的最小可用版本提供其他组测试对接用

例子

创建测试用的项目

Terminal window
mkdir composetest
cd composetest

新建 app.py 到文件夹中

文件位置:composetest/app.py
import time
import redis
from flask import Flask
app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)
def get_hit_count():
retries = 5
while True:
try:
return cache.incr('hits')
except redis.exceptions.ConnectionError as exc:
if retries == 0:
raise exc
retries -= 1
time.sleep(0.5)
@app.route('/')
def hello():
count = get_hit_count()
return f'Hello World! I have been seen {count} times.\n'

连接同一个 network 环境下名为 redis 的redis容器服务,git_hit_count() 是对 redis 服务的重试连接

新建另外一个文件 requirements.txt

文件位置:composetest/requirements.txt
flask
redis

新建 Dockerfile 文件

文件位置:composetest/Dockerfile
# syntax=docker/dockerfile:1
FROM python:3.10-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run", "--debug"]

新建 compose.yaml

文件位置:composetest/compose.yaml
services:
web:
build: .
ports:
- "8000:5000"
redis:
image: "redis:alpine"

以上定义了2个服务:

  1. web 服务来自由 Dockerfile 构建镜像。
  2. redis 服务

build and run compose

在当前目录下执行

Terminal window
docker compose up
Creating network "composetest_default" with the default driver
Creating composetest_web_1 ...
Creating composetest_redis_1 ...
Creating composetest_web_1
Creating composetest_redis_1 ... done
Attaching to composetest_web_1, composetest_redis_1
web_1 | * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
redis_1 | 1:C 17 Aug 22:11:10.480 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
redis_1 | 1:C 17 Aug 22:11:10.480 # Redis version=4.0.1, bits=64, commit=00000000, modified=0, pid=1, just started
redis_1 | 1:C 17 Aug 22:11:10.480 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
web_1 | * Restarting with stat
redis_1 | 1:M 17 Aug 22:11:10.483 * Running mode=standalone, port=6379.
redis_1 | 1:M 17 Aug 22:11:10.483 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
web_1 | * Debugger is active!
redis_1 | 1:M 17 Aug 22:11:10.483 # Server initialized
redis_1 | 1:M 17 Aug 22:11:10.483 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
web_1 | * Debugger PIN: 330-787-903
redis_1 | 1:M 17 Aug 22:11:10.483 * Ready to accept connections

检验服务情况,访问 localhost

Hello World! I have been seen 1 times.

停止应用程序,方法是在第二个终端中的项目目录中运行 docker compose down,或者在启动应用程序的原始终端中按 CTRL+C 停止应用程序。

使用 Compose Watch

修改 compose.yaml 文件,添加 watch 用来预览正在运行的 Compose 服务,在编辑和保存代码时会自动更新:

文件位置:composetest/compose.yaml
services:
web:
build: .
ports:
- "8000:5000"
develop:
watch:
- action: sync
path: .
target: /code
redis:
image: "redis:alpine"

每当文件发生更改时,Compose 都会将文件同步到容器内的相应位置。复制完成后,捆绑器会更新正在运行的应用程序,而无需重新启动。

输入 docker compose watch 或 docker compose up --watch 来构建和启动应用程序并启动文件监视模式

修改 app.py 并重新访问 localhost

@app.route('/')
def hello():
count = get_hit_count()
return f'Hello World! I have been seen {count} times.\n'
return f'Hello from Docker! I have been seen {count} times.\n'

拆分服务

将 redis 服务定义从 compose.yaml 中拆分出来

新建 infra.yaml

文件位置:composetest/infra.yaml
services:
redis:
image: "redis:alpine"

在 compose.yaml 中引入

include:
- infra.yaml
services:
web:
build: .
ports:
- "8000:5000"
develop:
watch:
- action: sync
path: .
target: /code
redis:
image: "redis:alpine"

运行 docker compose up 命令,使用更新后的 Compose 文件构建应用程序并运行它。你应该会在浏览器中看到 Hello world 消息

其他命令

运行 docker compose up 命令,使用更新后的 Compose 文件构建应用程序并运行它。你应该会在浏览器中看到 Hello world 消息

Terminal window
docker compose up -d
Starting composetest_redis_1...
Starting composetest_web_1...
docker compose ps
Name Command State Ports
-------------------------------------------------------------------------------------
composetest_redis_1 docker-entrypoint.sh redis ... Up 6379/tcp
composetest_web_1 flask run Up 0.0.0.0:8000->5000/tcp

使用 docker compose up -d 启动了 Compose,请在使用完后停止服务

Terminal window
docker compose stop

可以使用 docker compose down 命令关闭所有内容,彻底删除容器

Docker How-tos

ZRoger ZRoger #docker

Docker Compose 进阶用法:项目名称的优先级、生命周期钩子、用 profiles 切换环境,以及服务启动与关闭顺序的控制。

自定义项目名称

默认会使用当前 compose.yaml 所处文件夹的名称作为 project name 。使用 project name 来隔离各个环境。

  1. 在开发主机上:创建单个环境的多个副本,这对于为项目的每个功能分支运行稳定副本很有用
    • saas 化的场景
    • 私有定制化的场景
  2. 在 CI 服务器上:通过将项目名称设置为唯一的构建编号来防止构建之间的干扰
  3. 在共享或开发主机上:避免可能共享相同服务名称的不同项目之间的干扰

项目名称只能包含 小写字母、十进制数字、破折号 和 下划线,并且必须以 小写字母 或 十进制数字 开头

优先级如下 - 从高到低:

  1. -p 命令行标志

    Terminal window
    docker compose -p demo_project up
  2. 设置 COMPOSE_PROJECT_NAME 环境变量

    Terminal window
    export COMPOSE_PROJECT_NAME=demo_project
    docker compose up
  3. 在 Compose 文件中使用顶级的 name 属性

    name: my_custom_project
    services:
    web:
    image: nginx
  4. 使用多个 Compose 文件时的最后一个 name 属性,当通过 -f 标志指定多个 Compose 文件时,最后一个文件中定义的 name 属性生效

    • 文件 docker-compose.yml:

      name: project_a
      services:
      web:
      image: nginx
    • 文件 docker-compose.override.yml:

      name: project_b
      services:
      web:
      ports:
      - "8080:80"
    • 运行命令:

      Terminal window
      docker compose -f docker-compose.yml -f docker-compose.override.yml up

      由于 docker-compose.override.yml 是最后一个指定的文件,其 name: project_b 生效,项目名称为 project_b。由于 docker-compose.override.yml 是最后一个指定的文件,其 name: project_b 生效,项目名称为 project_b。

Compose 生命周期钩子

启动后

在容器启动后运行,但具体执行时间没有固定。在容器入口点执行期间,钩子的执行时间无法确定。

例子:

services:
app:
image: backend
user: 1001
volumes:
- data:/data
post_start:
- command: chown -R /data 1001:1001
user: root
volumes:
data: {} # a Docker volume is created with root ownership
  • 该钩子用于将卷的所有权更改为非 root 用户(因为卷默认以 root 所有权创建)。

  • 容器启动后, chown 命令会将 /data 目录的所有权更改为用户 1001。

结束前

在容器被特定命令(例如 docker compose down 或使用 Ctrl+C 手动停止)停止之前运行的命令。如果容器自行停止或被突然终止,这些钩子将不会运行

services:
app:
image: backend
pre_stop:
- command: ./data_flush.sh

在容器停止之前,将运行 ./data_flush.sh 脚本来执行任何必要的清理

在 Compose 中使用 profiles

在 compose.yaml 中配置 profiles 可以用来方便切换到不同的环境或者用例中。服务可以被标注一个或多个 profiles。未标注的服务默认为 default 。

给服务标注环境 profiles

services:
frontend:
image: frontend
profiles: [frontend]
phpmyadmin:
image: phpmyadmin
depends_on: [db]
profiles: [debug]
backend:
image: backend
db:
image: mysql
  • frontend 服务被标注为 frontend 环境
  • phpmyadmin 服务标注为 debug 环境

启动对应的配置,使用提供 –profile 命令行选项或使用 COMPOSE_PROFILES 环境变量

Terminal window
# --profile
docker compose --profile debug up
# -- env
COMPOSE_PROFILES=debug docker compose up

会启动 phpmyadmin , backend , db 这3个服务

多个 profiles

通过传递多个 –profile 标志或 COMPOSE_PROFILES 环境变量的逗号分隔列表来指定多个配置文件

Terminal window
docker compose --profile frontend --profile debug up
COMPOSE_PROFILES=frontend,debug docker compose up
# 启用所有配置文件,可以运行
docker compose --profile "*"

自动启动的 profiles 和 依赖关系解析

services:
backend:
image: backend
db:
image: mysql
db-migrations:
image: backend
command: myapp migrate
depends_on:
- db
profiles:
- tools

可以用于一次性服务和调试工具

命令:

Terminal window
# 只会启动 backend 和 db
docker compose up -d
# 运行 db-migrations (并且,如有必要,启动 db)
# 通过隐式启用配置文件 profiles `tools`
docker compose run db-migrations

db 服务 profiles 需要为空或者和 db-migrations 是同一个 profiles

关闭一些配置

与启动特定配置文件一样,您可以使用 –profile 命令行选项或使用 COMPOSE_PROFILES 环境变量:

Terminal window
docker compose --profile debug down
COMPOSE_PROFILES=debug docker compose down

在该配置中

services:
frontend:
image: frontend
profiles: [frontend]
phpmyadmin:
image: phpmyadmin
depends_on: [db]
profiles: [debug]
backend:
image: backend
db:
image: mysql

会停止 db、backend 和 phpmyadmin

停止并移除带有 debug 配置文件的服务以及不带该配置文件的服务,如果只想停止 phpmyadmin

Terminal window
docker compose down phpmyadmin
docker compose stop phpmyadmin

在 Compose 中控制启动和关闭顺序

通过 depends_one 来控制服务启动和关闭的顺序。compose 按照依赖顺序启动和停止容器服务,其中依赖关系由 depends_on,links,volumes_from 和 network_mode:“service:…” 决定。

比如在应用服务启动前,启动数据库,缓存等

在启动时,Compose 不会等到容器“准备好”,而只会等到它运行

检测服务就绪状态的解决方案是使用带有以下选项之一的条件属性

  • service_started
  • service_headlthy
  • service_completed_successfully

例子

services:
web:
build: .
depends_on:
db:
condition: service_healthy
restart: true
redis:
condition: service_started
redis:
image: redis
db:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
retries: 5
start_period: 30s
timeout: 10s

Compose 按照依赖顺序创建服务。数据库(db)和 Redis 在 web 之前创建。

Compose 等待标记为 service_healthy 的依赖项的健康检查通过。数据库(db)需在健康检查显示“healthy”后,web 才会创建。

restart: true 确保如果数据库(db)因显式的 Compose 操作(例如 docker compose restart)被更新或重启,web 服务也会自动重启,以确保正确重新建立连接或依赖关系。

数据库(db)服务的健康检查使用 pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB} 命令检查 PostgreSQL 数据库是否准备就绪。服务每 10 秒重试一次,最多重试 5 次。

Compose 也按照依赖顺序删除服务。web 在数据库(db)和 Redis 之前被删除。