mirror of
https://github.com/yeasy/docker_practice.git
synced 2026-03-10 11:54:37 +00:00
295 lines
5.6 KiB
Go
295 lines
5.6 KiB
Go
## 7.4 CMD 容器启动命令
|
||
|
||
### 什么是 CMD
|
||
|
||
`CMD` 指令用于指定容器启动时默认执行的命令。它定义了容器的"主进程"。
|
||
|
||
> **核心概念**:容器的生命周期 = 主进程的生命周期。CMD 指定的命令就是这个主进程。
|
||
|
||
---
|
||
|
||
### 语法格式
|
||
|
||
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,信号无法正确传递给应用
|
||
|
||
---
|
||
|
||
### 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
|
||
|
||
...
|
||
```
|
||
|
||
---
|
||
|
||
### 运行时覆盖 CMD
|
||
|
||
`docker run` 后的命令会覆盖 Dockerfile 中的 CMD:
|
||
|
||
```bash
|
||
## ubuntu 默认 CMD 是 /bin/bash
|
||
|
||
$ docker run -it ubuntu # 进入 bash
|
||
$ docker run ubuntu cat /etc/os-release # 覆盖为 cat 命令
|
||
```
|
||
|
||
```
|
||
Dockerfile: docker run 命令:
|
||
CMD ["/bin/bash"] + cat /etc/os-release
|
||
│ │
|
||
└───────► 被覆盖 ◄───────┘
|
||
↓
|
||
执行: cat /etc/os-release
|
||
```
|
||
|
||
---
|
||
|
||
### 经典错误:容器立即退出
|
||
|
||
#### 错误示例
|
||
|
||
```docker
|
||
## ❌ 容器启动后立即退出
|
||
|
||
CMD service nginx start
|
||
```
|
||
|
||
#### 原因分析
|
||
|
||
```
|
||
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;"]
|
||
```
|
||
|
||
---
|
||
|
||
### 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) 章节。
|
||
|
||
---
|
||
|
||
### 最佳实践
|
||
|
||
#### 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
|
||
|
||
...
|
||
```
|
||
|
||
---
|
||
|
||
### 常见问题
|
||
|
||
#### Q: CMD 可以写多个吗?
|
||
|
||
不可以。多个 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"]
|
||
```
|
||
|
||
---
|
||
|
||
### 本章小结
|
||
|
||
| 要点 | 说明 |
|
||
|------|------|
|
||
| **作用** | 指定容器启动时的默认命令 |
|
||
| **推荐格式** | exec 格式 `CMD ["程序", "参数"]` |
|
||
| **覆盖方式** | `docker run image 新命令` |
|
||
| **与 ENTRYPOINT** | CMD 作为 ENTRYPOINT 的默认参数 |
|
||
| **核心原则** | 应用必须在前台运行 |
|
||
|
||
### 延伸阅读
|
||
|
||
- [ENTRYPOINT 入口点](7.5_entrypoint.md):固定的启动命令
|
||
- [后台运行](../05_container/5.2_daemon.md):容器前台/后台概念
|
||
- [最佳实践](../16_appendix/16.1_best_practices.md):Dockerfile 编写指南
|