刚接触容器技术时,Docker、Docker Compose 和 Kubernetes 经常一起出现,很容易让人误以为它们是三种差不多的工具。其实,它们解决的问题处在不同层次:
- Docker 负责把一个应用装进容器,并运行起来。
- Docker Compose 负责在一台机器上组织多个容器。
- Kubernetes 负责在多台机器上管理大量容器,并让系统持续稳定运行。
可以先记住一句简单的话:
Docker 管一个容器,Docker Compose 管一组容器,Kubernetes 管一个容器集群。
一、为什么需要 Docker?
假设你开发了一个 Java 网站,在自己的电脑上运行正常。交给同事或部署到服务器后,却出现了各种问题:
- 服务器没有安装 Java。
- Java 版本不一致。
- 缺少某些系统依赖。
- 配置文件路径不同。
- 同事不知道该执行哪些启动命令。
这就是开发中常见的"在我电脑上明明能运行"。Docker 的做法是:把应用程序、运行环境、依赖和启动命令一起打包成镜像。Docker 官方将镜像描述为运行容器所需文件、程序库和配置的标准化软件包。拿日常外卖举个例子:
- 应用代码是食物。
- Java、Node.js 等运行环境是餐具。
- 配置和依赖是调料。
- Docker 镜像是封装好的外卖套餐。
- 容器是这份套餐真正被打开并使用的状态。
同一个镜像可以启动多个容器,就像同一份套餐模板可以制作出很多份外卖。
1、镜像和容器有什么区别?
镜像是一份只读模板,容器是镜像运行后的实例。可以把它们理解为:
镜像 = 程序安装包
容器 = 已经启动的程序
例如,通过下面的命令启动一个 Nginx 容器:
bash
docker run -d -p 8080:80 --name my-nginx nginx
这条命令做了几件事:
- 获取
nginx镜像。 - 根据镜像创建一个名为
my-nginx的容器。 - 在后台运行容器。
- 把本机的 8080 端口映射到容器的 80 端口。
访问 http://localhost:8080,就能看到 Nginx 页面。
2、Dockerfile 是什么?
如果要把自己的应用制作成镜像,通常需要编写 Dockerfile。下面是一个简单的 Java 应用示例:
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY target/demo.jar app.jar
EXPOSE 8080
CMD "java", "-jar", "app.jar"
它表达的意思很直接:
- 使用带有 Java 17 的基础镜像。
- 把工作目录设为
/app。 - 将项目生成的 JAR 文件复制进去。
- 声明应用使用 8080 端口。
- 容器启动时执行
java -jar app.jar。
然后构建并运行:
bash
docker build -t demo-app:1.0 .
docker run -d -p 8080:8080 demo-app:1.0
这样,其他人不需要手动安装 Java,也不必研究项目的启动方式。只要能运行容器,就能按照同样的方式启动这个应用。
二、为什么还需要 Docker Compose?
只有一个容器时,使用 docker run 并不麻烦。实际项目通常不止一个服务。例如,一个普通网站可能包含:
前端 Nginx
↓
Java 后端
↓
MySQL 数据库
↓
Redis 缓存
如果全部使用 docker run,需要分别配置端口、网络、环境变量和数据卷。启动命令会越来越长,而且很容易漏掉参数。Docker Compose 就是用来处理这种情况的。它允许我们在一个 YAML 文件中定义多个服务、网络和数据卷,再通过一条命令统一启动。Docker 官方把 Compose 定义为"用于定义和运行多容器应用的工具"。
举一个简单示例:下面的 compose.yaml 定义了一个 Java 应用和一个 MySQL 数据库:
bash
services:
app:
build: .
ports:
- "8080:8080"
environment:
DB_HOST: mysql
DB_PORT: 3306
DB_NAME: demo
DB_USER: root
DB_PASSWORD: "123456"
depends_on:
- mysql
mysql:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: "123456"
MYSQL_DATABASE: demo
volumes:
- mysql-data:/var/lib/mysql
volumes:
mysql-data:
在这个文件所在目录执行:
bash
docker compose up -d
Compose 会完成这些工作:
- 构建并启动 Java 应用。
- 下载并启动 MySQL 镜像。
- 建立容器之间的网络。
- 创建数据库存储卷。
- 把后端服务的 8080 端口暴露给本机。
查看日志可以执行:
bash
docker compose logs -f
停止并删除这些容器:
bash
docker compose down
docker compose down -v //-v 会删除数据库使用的数据卷,已有数据可能随之消失。
注:早期版本使用带连字符的独立命令:docker-compose up -d,现在主流版本已经把 Compose 集成进 Docker CLI,通常使用:docker compose up -d,两者只是写法不同罢了。
三、为什么又出现了 Kubernetes?
Docker Compose 很适合开发、测试和单机部署。不过,当系统运行在多台服务器上时,事情会迅速变复杂。
假设一个电商网站原来只有一个后端容器。促销开始后,访问量突然增加,需要运行十个后端实例。此时会遇到一连串问题:
- 十个容器应该分配到哪些服务器?
- 某台服务器宕机后,容器怎么办?
- 某个容器崩溃后,谁负责重启?
- 用户请求怎样分发到不同容器?
- 发布新版本时,怎样避免整个网站停机?
- 访问量下降后,怎样减少实例,节省资源?
这些事情靠人工处理并不现实。Kubernetes,简称 K8s ,就是用来自动管理容器化应用的 。Kubernetes 是一个用于管理容器化工作负载和服务的平台,支持声明式配置和自动化。
四、理解 Kubernetes 的几个基本概念
Kubernetes 的概念很多,入门时先抓住 Node、Pod、Deployment 和 Service 就够了。
1、Node:运行容器的机器
Node 是 Kubernetes 集群中的工作机器,可以是物理服务器,也可以是云服务器或虚拟机。举个例子:
Kubernetes 集群
├── Node 1:192.168.1.11
├── Node 2:192.168.1.12
└── Node 3:192.168.1.13
Kubernetes 会把应用分配到这些 Node 上运行。一个标准集群由控制平面和一台或多台工作节点组成。
2、Pod:Kubernetes 中最小的部署单位
Kubernetes 通常不直接管理单个容器,而是管理 Pod。一个 Pod 可以包含一个或多个关系非常紧密的容器。大多数业务场景中,一个 Pod 里只有一个主要应用容器。
Pod
└── Java 应用容器
Pod├── Java 应用容器
└── ELK日志采集容器
同一个 Pod 内的容器共享网络环境,也可以共享存储。可以暂时把 Pod 理解为"给容器套了一层 Kubernetes 管理外壳"。
3、Deployment:管理 Pod 的数量和版本
如果我们希望运行三个 Java 应用实例,可以定义一个 Deployment:
bash
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
spec:
replicas: 3
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
spec:
containers:
- name: demo-app
image: example/demo-app:1.0
ports:
- containerPort: 8080
其中,replicas: 3表示期望运行三个 Pod。Kubernetes 会不断检查实际状态,如果其中一个 Pod 崩溃,Deployment 会重新创建一个。Kubernetes 关心的不是某个具体 Pod 必须永远活着,而是系统要始终保持三个可用副本。
4、Service:给 Pod 提供稳定入口
Pod 可能随时被销毁和重建,IP 地址也可能变化。客户端不能把请求直接绑定到某个 Pod 的 IP。Service 会在一组 Pod 前面提供一个稳定的访问入口:
用户请求
↓
Service
↙ ↓ ↘
Pod Pod Pod
即使后面的 Pod 被替换,Service 的访问方式仍然可以保持不变。它还能把请求分发给多个 Pod。Kubernetes 官方将 Service 描述为向一个或多个 Pod 暴露网络应用的方法。对应的参考配置:
bash
apiVersion: v1
kind: Service
metadata:
name: demo-app-service
spec:
selector:
app: demo-app
ports:
- port: 80
targetPort: 8080
type: ClusterIP
这里的选择器selector:app:demo-app会找到带有相同标签的 Pod。
五、三者之间到底是什么关系?
接下来我们用个餐厅做比喻:
1、Docker:负责标准化每一道菜。Docker 规定菜怎么做、需要哪些原料、做好后装进什么盒子。同一个镜像在不同机器上运行,环境差异会小很多。
2、Docker Compose:负责一桌套餐。一个网站不只有后端,还需要数据库和缓存。Compose 把这些服务写进同一份菜单:
一份套餐
├── 前端
├── 后端
├── MySQL
└── Redis
执行一条命令,整套服务一起启动。它通常管理的是一台主机上的多个容器。
3、Kubernetes:负责经营连锁餐厅。当应用分布在多台服务器上,Kubernetes 开始发挥作用:
- 哪台服务器有空闲资源,就把 Pod 调度过去。
- 某个实例坏了,就创建新的。
- 顾客变多时,可以增加应用副本。
- 发布新版本时,可以逐批替换旧版本。
- 对外提供统一入口,把请求分给不同实例。
因此,三者不是简单的替代关系,而是层次不同:
应用代码
↓
Dockerfile 构建镜像
↓
Docker 运行容器
↓
Docker Compose 组织单机上的多个容器
↓
Kubernetes 编排集群中的容器化应用
六、写在最后
三者的定位可以简化为下面这张表:
| 工具 | 主要用途 | 常见使用场景 |
|---|---|---|
| Docker | 构建镜像、运行容器 | 打包和运行单个应用 |
| Docker Compose | 定义并启动多个关联容器 | 本地开发、测试、单机部署 |
| Kubernetes | 在集群中调度和管理容器化应用 | 多机部署、扩容、故障恢复、滚动更新 |