Files
docker_practice/07_dockerfile/7.4_cmd.md
T

284 lines
6.0 KiB
Go
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 7.4 CMD 容器启动命令
### 何时使用 CMD何时使用 ENTRYPOINT
在深入 CMD 的细节之前我们需要理解一个关键问题**CMD ENTRYPOINT 应该在什么时候使用**
这是 Dockerfile 使用中最常见的困惑之一简单的答案是
- **CMD**定义容器的默认命令”。如果用户在 `docker run` 时提供命令CMD 会被覆盖
- **ENTRYPOINT**定义容器的入口脚本”。通常用于启动应用的某个特定部分
**决策树**
1. **你的容器是否有不可变的启动逻辑** 比如需要先做一些初始化工作然后才运行应用 使用 ENTRYPOINT
2. **用户经常会在 `docker run` 时传入不同的命令吗** 使用 CMD让用户灵活覆盖
3. **大多数情况下你希望容器始终以相同的方式启动** 使用 ENTRYPOINT
更多细节见 [7.5 ENTRYPOINT 指令](7.5_entrypoint.md)
### 7.4.1 什么是 CMD
`CMD` 指令用于指定容器启动时默认执行的命令它定义了容器的 主进程”。
> **核心概念**容器的生命周期 = 主进程的生命周期CMD 指定的命令就是这个主进程
---
### 7.4.2 语法格式
CMD 有三种格式
| 格式 | 语法 | 推荐程度 |
|------|------|---------|
| **exec 格式**| `CMD [“可执行文件”, “参数1”, “参数2”]` | **推荐** |
| **shell 格式** | `CMD 命令 参数1 参数2` | 简单场景 |
| **参数格式** | `CMD [“参数1”, “参数2”]` | 配合 ENTRYPOINT |
#### exec 格式推荐
```docker
CMD ["nginx", "-g", "daemon off;"]
CMD ["python", "app.py"]
CMD ["node", "server.js"]
```
**优点**
- 直接执行指定程序是容器的 PID 1
- 正确接收信号 ( SIGTERM)
- 无需 shell 解析
#### shell 格式
```docker
CMD echo "Hello World"
CMD nginx -g "daemon off;"
```
**实际执行**会被包装为 `sh -c`
```docker
## 你写的
CMD echo $HOME
## 实际执行的
CMD ["sh", "-c", "echo $HOME"]
```
**优点**可以使用环境变量管道等 shell 特性 **缺点**主进程是 sh信号无法正确传递给应用
---
### 7.4.3 exec 格式 vs shell 格式
| 特性 | exec 格式 | shell 格式 |
|------|----------|-----------|
| 主进程 | 指定的程序 | `/bin/sh` |
| 信号传递 | 正确 | 无法传递 |
| 环境变量 | 需要 shell 包装 | 自动解析 |
| 推荐使用 | 大多数场景 | 需要 shell 特性时 |
#### 信号传递问题示例
```docker
## ❌ shell 格式:docker stop 会超时
CMD node server.js
## 实际是 sh -c "node server.js"
## SIGTERM 发给 sh,不会传递给 node
## ✅ exec 格式:docker stop 正常工作
CMD ["node", "server.js"]
## SIGTERM 直接发给 node
...
```
---
### 7.4.4 运行时覆盖 CMD
`docker run` 后的命令会覆盖 Dockerfile 中的 CMD
```bash
## ubuntu 默认 CMD 是 /bin/bash
$ docker run -it ubuntu # 进入 bash
$ docker run ubuntu cat /etc/os-release # 覆盖为 cat 命令
```
```bash
Dockerfile: docker run 命令:
CMD ["/bin/bash"] + cat /etc/os-release
│ │
└───────► 被覆盖 ◄───────┘
执行: cat /etc/os-release
```
---
### 7.4.5 经典错误容器立即退出
#### 错误示例
```docker
## ❌ 容器启动后立即退出
CMD service nginx start
```
#### 原因分析
```bash
1. CMD service nginx start
↓ 被转换为
2. CMD ["sh", "-c", "service nginx start"]
3. sh 启动,执行 service 命令
4. service 命令将 nginx 放到后台
5. service 命令结束,sh 退出
6. 容器主进程(sh)退出 → 容器停止
```
#### 正确做法
```docker
## ✅ 让 nginx 在前台运行
CMD ["nginx", "-g", "daemon off;"]
```
---
### 7.4.6 CMD vs ENTRYPOINT
| 指令 | 用途 | 运行时行为 |
|------|------|-----------|
| **CMD**| 默认命令 | `docker run` 参数会 **覆盖** |
| **ENTRYPOINT**| 入口点 | `docker run` 参数会 **追加** 到它后面 |
#### 单独使用 CMD
```docker
## Dockerfile
CMD ["curl", "-s", "http://example.com"]
```
```bash
$ docker run myimage # 执行默认命令
$ docker run myimage curl -v ... # 完全覆盖
```
#### 搭配 ENTRYPOINT
```docker
## Dockerfile
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(参数覆盖)
```
详见 [ENTRYPOINT 入口点](7.5_entrypoint.md)章节
---
### 7.4.7 最佳实践
#### 1. 优先使用 exec 格式
```docker
## ✅ 推荐
CMD ["python", "app.py"]
## ⚠️ 仅在需要 shell 特性时使用
CMD ["sh", "-c", "echo $PATH && python app.py"]
```
#### 2. 确保应用在前台运行
```docker
## ✅ 前台运行
CMD ["nginx", "-g", "daemon off;"]
CMD ["apache2ctl", "-D", "FOREGROUND"]
CMD ["java", "-jar", "app.jar"]
## ❌ 不要使用后台服务命令
CMD service nginx start
CMD systemctl start nginx
```
#### 3. 使用双引号
```docker
## ✅ 正确:双引号
CMD ["node", "server.js"]
## ❌ 错误:单引号(JSON 不支持)
CMD ['node', 'server.js']
```
#### 4. 配合 ENTRYPOINT 使用
```docker
## 用于可配置参数的场景
ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8080"]
## 运行时可以覆盖端口
$ docker run myapp --port 9000
...
```
---
### 7.4.8 常见问题
#### QCMD 可以写多个吗
不可以多个 CMD 只有最后一个生效
```docker
CMD ["echo", "first"]
CMD ["echo", "second"] # 只有这个生效
```
#### Q如何在 CMD 中使用环境变量
```docker
## 方法1:使用 shell 格式
CMD echo "Port is $PORT"
## 方法2:显式使用 sh -c
CMD ["sh", "-c", "echo Port is $PORT"]
```
#### Q为什么我的容器不响应 Ctrl+C
可能是使用了 shell 格式信号被 sh 吃掉了
```docker
## ❌ 信号无法传递
CMD python app.py
## ✅ 信号正确传递
CMD ["python", "app.py"]
```
---