mirror of
https://github.com/yeasy/docker_practice.git
synced 2026-08-10 08:27:25 +00:00
1. FROM「必须是第一条指令」(4.5、7.16、summary 三处)与本书 7.7.4「FROM 之前的 ARG」整节直接冲突。官方 Dockerfile 参考写的是 "A Dockerfile must begin with a FROM instruction. This may be after parser directives, comments, and globally scoped ARGs",且 "ARG is the only instruction that may precede FROM"。三处 一并改成「第一条构建指令」并点明例外。 2. 12 章小结把 USER Namespace 列进「默认启用」的一档,并写「容器 root ≠ 宿主机 root」;而同章 12.2.2 明写 USER Namespace 默认**不**启用、需 userns-remap 显式 开启,18.1 更直说「在默认情况下,容器内的 root 用户(UID=0)就是宿主机上的 root 用户」。小结按正文口径改回——这条读反了会直接误判容器逃逸的风险面。 3. 11.5「目前支持三种日志驱动类型」是 Compose 早期文本的残留。官方支持 json-file、 local、syslog、journald、gelf、fluentd、awslogs、splunk、etwlogs、gcplogs、 none 共十余种,本书 19 章小结自己就列了六种。改为给出常见取值并链到官方清单。 4. 7.8 与 7.5 的示例注释建议 postgres:latest / redis:latest,而 4.1、7.10、 7.16、7 章小结、4.5 全都要求避免 latest(7.8 同一文件第 177 行也写「避免 latest」)。按全书口径改掉这两处。 另:ENV 的空格分隔旧写法(7.6 的「格式一」、附录四的 PG_MAJOR/PATH 示例)改为等号 形式并加注。BuildKit 的 LegacyKeyValueFormat 检查会报 "ENV key=value" should be used instead of legacy "ENV key value" format, 而本书 10.2 与 07 章 README 正是在推荐 docker buildx build --check。
7.6 KiB
7.6 KiB
7.5 ENTRYPOINT 入口点
何时使用 ENTRYPOINT:从“容器”到“命令”
如果说 CMD 是“容器中的默认程序”,那么 ENTRYPOINT 就是“把容器变成一个命令”。这个思维转变决定了你何时使用 ENTRYPOINT。
使用 ENTRYPOINT 的典型场景:
-
命令行工具:你想让镜像像
curl或wget一样使用ENTRYPOINT ["curl"] # docker run myimage http://example.com → curl http://example.com -
应用启动脚本:你有一个初始化脚本,需要接收命令行参数
ENTRYPOINT ["/app/entrypoint.sh"] # docker run myimage --debug → /app/entrypoint.sh --debug -
与 CMD 结合:ENTRYPOINT 定义入口,CMD 定义默认参数
ENTRYPOINT ["python", "app.py"] CMD ["--port", "8000"] # docker run myimage → python app.py --port 8000 # docker run myimage --port 9000 → python app.py --port 9000
对比 CMD:如果没有这些“把容器当命令用”的需求,通常使用 CMD 就足够了。
7.5.1 什么是 ENTRYPOINT
ENTRYPOINT 指定容器启动时运行的入口程序。与 CMD 不同,ENTRYPOINT 定义的命令不会被 docker run 的参数覆盖,而是 接收这些参数。
核心作用:让镜像像一个可执行程序一样使用,
docker run的参数作为这个程序的参数。
7.5.2 语法格式
| 格式 | 语法 | 推荐程度 |
|---|---|---|
| exec 格式 | ENTRYPOINT ["可执行文件", "参数1"] |
✅推荐 |
| shell 格式 | ENTRYPOINT 命令 参数 |
⚠️ 不推荐 |
## exec 格式(推荐)
ENTRYPOINT ["nginx", "-g", "daemon off;"]
## shell 格式(不推荐)
ENTRYPOINT nginx -g "daemon off;"
7.5.3 ENTRYPOINT vs CMD
核心区别
| 特性 | ENTRYPOINT | CMD |
|---|---|---|
| 定位 | 固定的入口程序 | 默认参数 |
| docker run 参数 | 追加为参数 | 完全覆盖 |
| 覆盖方式 | --entrypoint |
直接指定命令 |
| 适用场景 | 把镜像当命令用 | 提供默认行为 |
行为对比
## 只用 CMD
CMD ["curl", "-s", "http://example.com"]
$ docker run myimage # curl -s http://example.com
$ docker run myimage -v # 执行 -v(错误!)
$ docker run myimage curl -v ... # curl -v ...(完全替换)
## 只用 ENTRYPOINT
ENTRYPOINT ["curl", "-s"]
$ docker run myimage # curl -s(缺参数)
$ docker run myimage http://example.com # curl -s http://example.com ✓
## ENTRYPOINT + CMD 组合(推荐)
ENTRYPOINT ["curl", "-s"]
CMD ["http://example.com"]
$ docker run myimage # curl -s http://example.com(默认)
$ docker run myimage http://other.com # curl -s http://other.com ✓
$ docker run myimage -v http://other.com # curl -s -v http://other.com ✓
7.5.4 场景一:让镜像像命令一样使用
需求:启动前准备
创建一个查询公网 IP 的 “命令” 镜像。
使用 CMD 的问题
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
CMD ["curl", "-s", "http://myip.ipip.net"]
$ docker run myip # ✓ 正常工作
当前 IP:61.148.226.66
$ docker run myip -i # ✗ 错误!
exec: "-i": executable file not found
## -i 替换了整个 CMD,被当作可执行文件
...
使用 ENTRYPOINT 解决
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
ENTRYPOINT ["curl", "-s", "http://myip.ipip.net"]
$ docker run myip # ✓ 正常工作
当前 IP:61.148.226.66
$ docker run myip -i # ✓ 添加 -i 参数
HTTP/1.1 200 OK
...
当前 IP:61.148.226.66
交互图示
ENTRYPOINT ["curl", "-s", "http://myip.ipip.net"]
│
docker run myip -i
│
▼
curl -s http://myip.ipip.net -i
└─────────────────────────────┘
ENTRYPOINT + docker run 参数
7.5.5 场景二:启动前的准备工作
需求
在启动主服务前执行初始化脚本 (如数据库迁移、权限设置)。
实现方式
# 建议使用 redis:7 等具体版本标签,避免 latest,具体版本号根据生产需求选择
FROM redis:7-alpine
COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["redis-server"]
docker-entrypoint.sh:
#!/bin/sh
set -e
## 准备工作
echo "Initializing..."
## 如果第一个参数是 redis-server,以 redis 用户运行
if [ "$1" = 'redis-server' ]; then
chown -R redis:redis /data
exec gosu redis "$@"
fi
## 其他命令直接执行
exec "$@"
工作流程
docker run redis docker run redis bash
│ │
▼ ▼
docker-entrypoint.sh redis-server docker-entrypoint.sh bash
│ │
├─ 初始化 ├─ 初始化
├─ chown -R redis:redis /data │
└─ exec gosu redis redis-server └─ exec bash
(以 redis 用户运行) (以 root 用户运行)
关键点
- exec “$@”:用传入的参数替换当前进程,确保信号正确传递
- 条件判断:根据 CMD 不同执行不同逻辑
- 用户切换:使用
gosu切换用户 (比su更适合容器)
7.5.6 场景三:带参数的应用
# 建议使用 python:3.12 或 python:3,具体版本号根据应用兼容性需求选择
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
ENTRYPOINT ["python", "app.py"]
CMD ["--host", "0.0.0.0", "--port", "8080"]
## 使用默认参数
$ docker run myapp
## 执行: python app.py --host 0.0.0.0 --port 8080
## 覆盖参数
$ docker run myapp --host 0.0.0.0 --port 9000
## 执行: python app.py --host 0.0.0.0 --port 9000
## 完全不同的参数
$ docker run myapp --help
## 执行: python app.py --help
...
7.5.7 覆盖 ENTRYPOINT
使用 --entrypoint 参数覆盖:
## 正常运行
$ docker run myimage
## 覆盖 ENTRYPOINT 进入 shell 调试
$ docker run --entrypoint /bin/sh myimage
## 覆盖 ENTRYPOINT 并传入参数
$ docker run --entrypoint /bin/cat myimage /etc/os-release
7.5.8 ENTRYPOINT 与 CMD 组合表
| ENTRYPOINT | CMD | 最终执行命令 |
|---|---|---|
| 无 | 无 | 无 (容器无法启动) |
| 无 | ["cmd", "p1"] |
cmd p1 |
["ep", "p1"] |
无 | ep p1 |
["ep", "p1"] |
["cmd", "p2"] |
ep p1 cmd p2 |
ep p1 (shell) |
["cmd", "p2"] |
/bin/sh -c "ep p1" (CMD 被忽略) |
⚠️ 注意:shell 格式的 ENTRYPOINT 会忽略 CMD!
7.5.9 最佳实践
1. 使用 exec 格式
## ✅ 推荐
ENTRYPOINT ["python", "app.py"]
## ❌ 避免 shell 格式
ENTRYPOINT python app.py
2. 提供有意义的默认参数
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
3. 入口脚本使用 exec
#!/bin/sh
## 准备工作...
## 使用 exec 替换当前进程
exec "$@"
4. 处理信号
确保 ENTRYPOINT 脚本能正确传递信号:
#!/bin/bash
trap 'kill -TERM $PID' TERM INT
## 启动应用
app "$@" &
PID=$!
## 等待应用退出
wait $PID