## 7.5 ENTRYPOINT 入口点 ### 什么是 ENTRYPOINT `ENTRYPOINT` 指定容器启动时运行的入口程序。与 CMD 不同,ENTRYPOINT 定义的命令不会被 `docker run` 的参数覆盖,而是**接收这些参数**。 > **核心作用**:让镜像像一个可执行程序一样使用,`docker run` 的参数作为这个程序的参数。 --- ### 语法格式 | 格式 | 语法 | 推荐程度 | |------|------|---------| | **exec 格式**| `ENTRYPOINT ["可执行文件", "参数1"]` | ✅**推荐** | | **shell 格式** | `ENTRYPOINT 命令 参数` | ⚠️ 不推荐 | ```docker ## exec 格式(推荐) ENTRYPOINT ["nginx", "-g", "daemon off;"] ## shell 格式(不推荐) ENTRYPOINT nginx -g "daemon off;" ``` --- ### ENTRYPOINT vs CMD #### 核心区别 | 特性 | ENTRYPOINT | CMD | |------|------------|-----| | **定位** | 固定的入口程序 | 默认参数 | | **docker run 参数** | 追加为参数 | 完全覆盖 | | **覆盖方式** | `--entrypoint` | 直接指定命令 | | **适用场景** | 把镜像当命令用 | 提供默认行为 | #### 行为对比 ```docker ## 只用 CMD CMD ["curl", "-s", "http://example.com"] ``` ```bash $ docker run myimage # curl -s http://example.com $ docker run myimage -v # 执行 -v(错误!) $ docker run myimage curl -v ... # curl -v ...(完全替换) ``` ```docker ## 只用 ENTRYPOINT ENTRYPOINT ["curl", "-s"] ``` ```bash $ docker run myimage # curl -s(缺参数) $ docker run myimage http://example.com # curl -s http://example.com ✓ ``` ```docker ## ENTRYPOINT + CMD 组合(推荐) ENTRYPOINT ["curl", "-s"] CMD ["http://example.com"] ``` ```bash $ 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 ✓ ``` --- ### 场景一:让镜像像命令一样使用 #### 需求(启动前准备) 创建一个查询公网 IP 的"命令"镜像。 #### 使用 CMD 的问题 ```docker 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"] ``` ```bash $ docker run myip # ✓ 正常工作 当前 IP:61.148.226.66 $ docker run myip -i # ✗ 错误! exec: "-i": executable file not found ## -i 替换了整个 CMD,被当作可执行文件 ... ``` #### 使用 ENTRYPOINT 解决 ```docker 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"] ``` ```bash $ 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 参数 ``` --- ### 场景二:启动前的准备工作 #### 需求 在启动主服务前执行初始化脚本(如数据库迁移、权限设置)。 #### 实现方式 ```docker FROM redis:7-alpine COPY docker-entrypoint.sh /usr/local/bin/ ENTRYPOINT ["docker-entrypoint.sh"] CMD ["redis-server"] ``` **docker-entrypoint.sh**: ```bash #!/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 用户运行) ``` #### 关键点 1. **exec "$@"**:用传入的参数替换当前进程,确保信号正确传递 2. **条件判断**:根据 CMD 不同执行不同逻辑 3. **用户切换**:使用 `gosu` 切换用户(比 `su` 更适合容器) --- ### 场景三:带参数的应用 ```docker 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"] ``` ```bash ## 使用默认参数 $ 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 ... ``` --- ### 覆盖 ENTRYPOINT 使用 `--entrypoint` 参数覆盖: ```bash ## 正常运行 $ docker run myimage ## 覆盖 ENTRYPOINT 进入 shell 调试 $ docker run --entrypoint /bin/sh myimage ## 覆盖 ENTRYPOINT 并传入参数 $ docker run --entrypoint /bin/cat myimage /etc/os-release ``` --- ### 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! --- ### 最佳实践 #### 1. 使用 exec 格式 ```docker ## ✅ 推荐 ENTRYPOINT ["python", "app.py"] ## ❌ 避免 shell 格式 ENTRYPOINT python app.py ``` #### 2. 提供有意义的默认参数 ```docker ENTRYPOINT ["nginx"] CMD ["-g", "daemon off;"] ``` #### 3. 入口脚本使用 exec 运行以下命令: ```bash #!/bin/sh ## 准备工作... ## 使用 exec 替换当前进程 exec "$@" ``` #### 4. 处理信号 确保 ENTRYPOINT 脚本能正确传递信号: ```bash #!/bin/bash trap 'kill -TERM $PID' TERM INT ## 启动应用 app "$@" & PID=$! ## 等待应用退出 wait $PID ``` --- ### 本章小结 | ENTRYPOINT | CMD | 适用场景 | |------------|-----|---------| | ✓ | ✗ | 镜像作为固定命令使用 | | ✗ | ✓ | 简单的默认命令 | | ✓ | ✓ | **推荐**:固定命令 + 可配置参数 | ### 延伸阅读 - [CMD 容器启动命令](7.4_cmd.md):默认命令 - [最佳实践](../16_appendix/16.1_best_practices.md):启动命令设计 - [后台运行](../05_container/5.2_daemon.md):前台/后台概念