|
### 从手动到编排
你现在的流程是「单容器手工流」:
1. 导入镜像:`docker load -i py1.tar`
2. 启动容器:`docker run -itd --name kl ...`
3. 进容器装软件:`docker exec -it kl bash`
4. 打包成镜像:`docker commit` / `docker save`
这对**一个容器**没问题。但真实业务通常由多个容器协作:
- 一个 Web 容器 + 一个数据库容器 + 一个缓存容器
- 它们之间有启动先后、网络互通、数据共享的需求
- 你要手动 `docker run` 三遍、手动建网络、手动记端口映射……很快就会乱
**Docker 编排(Orchestration)** 就是用一个「声明式配置文件」描述:要哪些容器、怎么连、按什么顺序起。然后**一条命令**把整套环境拉起来 / 停掉 / 销毁。核心解决的痛点:
| 手工方式 | 编排方式 |
|---------|---------|
| 记住一长串 `docker run` 参数 | 写进配置文件,版本可追踪 |
| 手动建网络、连容器 | 网络在文件里声明,自动互通 |
| 一个挂了没人管 | 自动重启(self-healing) |
| 想加机器只能重做 | 集群内调度、可扩缩容 |
常见的三种编排工具:**docker-compose**(单机多容器)、**Docker Swarm**(集群)、**Kubernetes**(行业标准)。下面分别介绍。
|
|
### docker-compose:单机多容器
- **定位**:在一台机器上,用**一个 YAML 文件**管理多个容器。
- **谁在用**:本地开发、单机部署、CI 环境,最常用、最轻量。
- **形态**:现代 Docker 已把它内置为 `docker compose` 子命令(v2 插件,注意没有横杠);老版本是独立的 `docker-compose`(v1,有横杠)。
核心思想就一句话:**把 `docker run` 后面那一长串参数,写成 `docker-compose.yml` 里的几行配置**。
```yaml
# docker-compose.yml 示意
services:
web:
image: nginx:latest
ports:
- "8080:80"
db:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: "123456"
```
写好后:
- `docker compose up -d` 一键启动 `web` 和 `db` 两个容器
- 它俩自动进入**同一个网络**,`web` 容器里直接用服务名 `db` 就能连到数据库
- `docker compose down` 一键全部停止并删除
> 本节后面(Compose 部署示例)会用空容器做一个完整可跑的例子。
适用场景:**单机**。如果你的服务要跨多台服务器,就该上 Swarm 或 K8s 了。
|
|
### Docker Swarm:Docker 自带的集群
- **定位**:Docker 原生内置的**多机集群**方案,直接集成在 Docker 引擎里,不需要额外装大套组件。
- **谁在用**:中小规模集群、不想上 K8s 的团队。注意:2023 年起官方把它转为**维护模式**,不再加新特性,但仍可用。
最小玩法(两台以上装好 Docker 的机器):
```bash
# 在管理节点上初始化集群
docker swarm init --advertise-addr 192.168.1.10
# 它会输出一条 join 命令,拿到工作节点上执行即可加入集群
docker swarm join --token SWMTKN-xxx 192.168.1.10:2377
# 部署一个服务(自动在集群里调度到不同节点)
docker service create --name myweb -p 8080:80 --replicas 3 nginx:latest
```
特点:
- 命令几乎和单机 `docker run` 一样(`docker service` / `docker stack`),学习成本低。
- 支持服务发现、滚动更新、用 `--replicas` 做副本数伸缩。
- 比 K8s 功能少(没有强大的 Operator、调度策略较简单)。
一句话:**多机、轻量、和 docker 命令几乎无缝**,但生态和扩展性不如 K8s。
|
|
### Kubernetes(K8s):行业标准
- **定位**:跨多机的大规模容器编排**事实标准**,最初来自 Google 内部 Borg 系统的开源。
- **谁在用**:中大型生产环境、云原生生态,几乎所有公有云都提供托管 K8s(AKS / EKS / GKE)。
核心概念:
| 概念 | 类比 | 作用 |
|------|------|------|
| Pod | 一组共生死容器 | K8s 调度的最小单位,一个 Pod 里可放多个共享网络的容器 |
| Deployment | 副本控制器 | 声明「我要 3 个副本」,掉了一个自动补上 |
| Service | 稳定访问入口 | 给一组 Pod 一个固定名字和负载均衡,Pod 重建 IP 变了也不影响调用 |
同样部署 nginx(声明式 YAML,用 `kubectl` 操作):
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myweb
spec:
replicas: 3
selector:
matchLabels:
app: myweb
template:
spec:
containers:
- name: nginx
image: nginx:latest
```
特点:
- 自动扩缩容、滚动发布、自愈、配置 / 密钥管理、丰富的调度策略。
- **学习曲线陡**:概念多(Pod / Deployment / Service / Ingress / ConfigMap ...)、组件多(API Server / etcd / kubelet / kube-proxy)。
- 生态最丰富(Helm、Istio、Prometheus 等都围绕它)。
一句话:**大规模、生产级、生态最强**,但门槛也最高。
|
|
### 三者怎么选
| 维度 | docker-compose | Docker Swarm | Kubernetes |
|------|----------------|--------------|------------|
| 规模 | 单机 | 多机(中小集群) | 多机(大规模集群) |
| 安装 | Docker 自带 | Docker 引擎内置 | 需独立搭建 / 托管 |
| 学习成本 | 最低 | 低 | 高 |
| 配置文件 | `docker-compose.yml` | 同 compose / `stack` | 各种 YAML + CRD |
| 主要命令 | `docker compose` | `docker service/stack` | `kubectl` |
| 高可用 / 自愈 | 弱 | 基础 | 强 |
| 生态 | 一般 | 维护模式 | 极丰富 |
| 典型场景 | 本地开发、单机服务 | 内部小集群 | 生产级云原生 |
选型建议:
1. **个人学习 / 本地开发 / 一台服务器跑几个服务** → 用 **docker-compose**(本节示例就是它)。
2. **几台机器要做高可用、又不想搞太复杂** → **Docker Swarm**,平滑、和现有 docker 命令几乎一致。
3. **生产环境、要弹性伸缩、团队要长期发展** → 上 **Kubernetes**,投入大但天花板高。
常见渐进路线:`compose`(跑通业务)→ `swarm`(先上多机)→ `k8s`(再要规模和生态)。从 compose 起步成本最低,也是本页后续示例的工具。
|
|
### 目标与环境
我们用 docker-compose 部署一个**最小的多容器应用**:3 个几乎「什么都不做」的空容器,放在同一个网络里,能互相 ping 通。重点是把**整套流程**跑通。
> 为什么用「空容器」学?因为它剥离了业务,让你专注于编排本身:怎么写文件、怎么起、怎么停。空容器一旦能跑通,把里面的镜像换成 nginx / mysql / 你的 cent7,就是真实应用。
先确认 compose 可用:
```bash
# 现代版本(v2 插件,推荐),注意 compose 没有横杠
docker compose version
# 老版本(v1 独立脚本,带横杠)
docker-compose -v
```
- 输出类似 `Docker Compose version v2.x` 就 OK。
- Docker Desktop 和较新的 Docker Engine 默认自带 `docker compose`(v2),不用单独装。
- 如果两个都没有:Linux 上可装 `docker-compose-plugin`,或 `pip install docker-compose`。
然后建一个干净的项目目录:
```bash
mkdir -p ~/dkdemo && cd ~/dkdemo
```
接下来在这个目录里建一个 `docker-compose.yml`。
|
|
### 第一步:编写 docker-compose.yml
在 `~/dkdemo` 目录下新建 `docker-compose.yml`(新版 compose 已不需要写 `version` 字段):
```yaml
# ~/dkdemo/docker-compose.yml
services:
app1:
image: busybox:latest
container_name: myapp1
command: ["sleep", "infinity"] # 让空容器一直挂着不退出
networks:
- mynet
app2:
image: busybox:latest
container_name: myapp2
command: ["sleep", "infinity"]
networks:
- mynet
app3:
image: busybox:latest
container_name: myapp3
command: ["sleep", "infinity"]
networks:
- mynet
networks:
mynet:
driver: bridge
```
逐段解释:
- **`services`**:声明有哪些容器(这里 3 个:app1 / app2 / app3)。
- **`image`**:用 `busybox`(极小的镜像,约 1~2MB,适合做「空容器」)。你也可以换成自己导入的 `cent7`,或 `alpine`、`nginx`。
- **`container_name`**:给容器起固定名字,方便后续 `docker ps` / `exec`。
- **`command`**:**关键点**——容器只要「主进程退出」就会自动结束。busybox 默认起一个 shell 立刻退出,所以整个容器一启动就停了。用 `sleep infinity`(或 `tail -f /dev/null`)让它一直挂着,容器才「常驻」。
- **`networks`**:三个容器都加入同一个自定义网络 `mynet`。
- **`networks.mynet`**:声明这个网络用 bridge 驱动(默认的单机网络)。
一句话:同一 `mynet` 里的容器,可以**用服务名互相访问**(比如在 app1 里 `ping app2` 能通),不用记 IP。
|
|
### 第二步:启动、查看、停止
确保当前目录有 `docker-compose.yml`,然后:
```bash
# 一键后台启动全部服务(-d = detach 后台运行)
docker compose up -d
# 看看当前这套服务跑了哪些容器
docker compose ps
# 想看实时日志(-f 跟随)
docker compose logs -f
```
`up -d` 之后,`docker compose ps` 应能看到 3 个容器都是 `running`。这时它们已经「部署起来了」,虽然啥都不做。
**停止 / 重启 / 销毁**——这是四个容易混的命令:
| 命令 | 作用 | 容器还在吗 | 网络还在吗 |
|------|------|-----------|-----------|
| `docker compose stop` | 停止容器(发 SIGTERM) | 在(可重启) | 在 |
| `docker compose start` | 把刚才 stop 的容器重新启动 | — | 在 |
| `docker compose down` | 停止并**删除**容器和网络 | 删除 | 删除 |
| `docker compose restart` | 重启容器(配置不变) | 在 | 在 |
记忆口诀:
- **`stop` / `start`** 是「关机 / 开机」,容器本体保留,下次 `start` 还在。
- **`down`** 是「连锅端」,容器、网络都没了,只留 `docker-compose.yml` 文件,下次 `up -d` 重新创建。
- **`up -d`** 是幂等的:配置没改就啥也不做,改了就只重建变动的部分。
> 平时开发:改完配置 `docker compose up -d` 重新应用;完全不要了 `docker compose down`。
|
|
### 第三步:进容器看看,验证互通
容器起来了,怎么进去看?怎么验证它们确实在同一个网络、能互相访问?
```bash
# 进入 app1 容器执行 sh(开一个交互式终端)
docker compose exec app1 sh
# 进入后,在 app1 里 ping app2 —— 直接用「服务名」做主机名
/ # ping -c 3 app2
PING app2 (192.168.x.x): 56 data bytes # 能通说明同网可达
/ # exit
# 不进容器,直接执行一条命令也行
docker compose exec app3 ping -c 2 app1
# 看看 compose 这套服务里各容器的进程
docker compose top
```
几个常用排查命令:
```bash
# 看某个服务(比如 app2)的日志
docker compose logs app2
# 容器没起来 / 一直退出,先看日志和状态
docker compose ps -a
docker compose logs app1
# 列出 compose 自动建的网络
docker network ls | grep mynet
```
关键点:**同一网络里的容器用「服务名」互相访问**,这是 compose 相比手工 `docker run` 最大省心的地方——不用记 IP、不用每次 `--link`。等你把 busybox 换成 nginx + mysql,`web` 容器里直接 `mysql -h db` 就能连上数据库,靠的就是这个机制。
|
|
### 进阶:启动顺序与服务依赖
真实应用里,数据库没起来,Web 容器先起也没意义。compose 用 `depends_on` 表达**启动顺序**:
```yaml
services:
web:
image: nginx:latest
depends_on:
- db # compose 会先启动 db,再启动 web
ports:
- "8080:80"
db:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: "123456"
```
注意 `depends_on` 只保证**启动顺序**,不保证 db「已经能接受连接」。要等 db 真正就绪,加 `healthcheck` + `condition`:
```yaml
services:
web:
image: nginx:latest
depends_on:
db:
condition: service_healthy # 等 db 健康检查通过才起 web
db:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: "123456"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 3s
retries: 10
```
其它常用字段速查:
- **`volumes`**:挂载目录 / 数据卷,做持久化(容器删了数据还在)。例如 `volumes: ["./data:/var/lib/mysql"]`。
- **`ports`**:端口映射,格式 `"宿主机端口:容器端口"`,如 `"8080:80"`。
- **`environment`**:环境变量,注入配置(密码、开关)。
- **`restart`**:重启策略,`restart: always` 让容器挂了自动拉起。
- **`build`**:不想用现成镜像时,指向一个 Dockerfile 目录,compose 会自动构建。
到此你已经掌握 compose 的主干:**写 yml → `up -d` 起 → `down` 销毁**,其余字段都是围绕这套骨架加细节。
|