在 VPS 上折腾各种软件环境,最怕的就是依赖地狱、版本冲突和难以卸载。Docker 将应用及其全部依赖打包成隔离的容器,实现了"一次配置,到处运行"。本指南从基础概念到完整 LEMP 栈实战,带您掌握现代服务器运维最核心的容器化技能。
🐳 什么是 Docker?核心概念
Docker 是一种轻量级容器化技术,将应用程序及其所有依赖项(库、环境变量、配置文件)打包成标准化的容器(Container),确保它在任何 Linux 环境下都能以完全相同的方式运行。
环境隔离
MySQL、Redis、Nginx 各自运行在独立容器中,互不干扰。不同项目可以使用不同版本的同一软件(如 PHP 7.4 + PHP 8.3 并存)
秒级启动
容器共享宿主机内核,没有传统虚拟机的系统引导过程。启动一个 Nginx 容器只需约 0.3 秒
一键迁移
换服务器只需复制 docker-compose.yml 和数据目录,执行一条命令即可完整恢复所有服务
📖 三个核心概念
只读模板,相当于安装光盘。从 Docker Hub 拉取(如 nginx:alpine)或通过 Dockerfile 构建。镜像本身不运行,用来创建容器。
镜像运行后的实例,相当于已安装好的虚拟机。容器是可读写的,但内部变更在容器删除后消失(数据持久化靠挂载卷)。
存储和分发镜像的服务。Docker Hub 是最大的公共仓库(hub.docker.com),也可以搭建私有仓库(Harbor)。
⚔️ Docker vs 传统虚拟机
很多新手会混淆 VPS(虚拟机)和 Docker 容器。理解它们的区别,是掌握容器化思维的关键。
| 对比维度 | 传统虚拟机(KVM/VMware) | Docker 容器 ⭐ |
|---|---|---|
| 隔离层级 | 硬件级(含完整 Guest OS) | 进程级(共享宿主机内核) |
| 资源占用 | 极大(数 GB 内存/磁盘) | 极小(数十 MB 起) |
| 启动速度 | 分钟级 | 秒级/毫秒级 |
| 镜像大小 | 数 GB(含完整 OS) | 数十 MB 到数百 MB |
| 可移植性 | 受限于 Hypervisor 类型 | 任意支持 Docker 的 Linux 环境 |
| 安全隔离 | 强(内核完全隔离) | 较弱(共享内核,但 namespace 隔离) |
| 适用场景 | 强隔离需求、运行不同操作系统 | 微服务、快速部署、开发环境统一 |
⚙️ 一键安装 Docker 环境
# ── 官方一键安装脚本(适用于 Ubuntu / Debian / CentOS)────────────────────
# 自动检测发行版并安装最新稳定版 Docker Engine + Docker Compose 插件
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# 启动并设置开机自启
sudo systemctl enable --now docker
# 验证安装
docker --version # 应输出:Docker version 27.x.x
docker compose version # 应输出:Docker Compose version v2.x.x
# ── 可选:允许普通用户运行 Docker(无需每次 sudo)──────────────────────────
# 将当前用户加入 docker 组,重新登录后生效
sudo usermod -aG docker $USER
newgrp docker # 临时生效,或重新 SSH 登录
💡 新版本变化: Docker 已将 docker-compose 作为插件集成到 CLI 中。现在推荐使用 docker compose(中间是空格)而非旧版的 docker-compose(连字符)。两者功能相同,但新版性能更好。
⌨️ Docker 基础命令速查
| 命令 | 功能描述 |
|---|---|
| docker ps | 查看正在运行的容器 |
| docker ps -a | 查看所有容器(含已停止) |
| docker images | 查看本地已下载的镜像列表 |
| docker pull nginx:latest | 从 Docker Hub 拉取指定镜像 |
| docker run -d --name app -p 80:80 nginx | 后台运行容器并映射端口 |
| docker stop app | 优雅停止容器(发送 SIGTERM) |
| docker start app | 重新启动已停止的容器 |
| docker restart app | 重启容器(等同于 stop + start) |
| docker rm app | 删除已停止的容器 |
| docker rm -f app | 强制删除运行中的容器 |
| docker rmi nginx:latest | 删除本地镜像 |
| docker logs -f --tail 100 app | 实时查看最近 100 行日志 |
| docker exec -it app /bin/sh | 进入容器内部交互式终端 |
| docker inspect app | 查看容器详细配置(JSON 格式) |
| docker system prune -a --volumes | 清理所有未使用资源(镜像/容器/卷)⚠️ 谨慎执行 |
💾 必学:数据持久化与挂载
⚠️ 新手最常踩的坑: 容器是"用完即抛"的无状态单元。执行 docker rm 容器名 后,容器内部的数据(数据库文件、上传的图片、配置变更)全部永久消失!必须通过挂载将数据存储在宿主机上。
# ════════════════════════════════════════════════════════════
# 数据持久化:两种挂载方式对比
# ════════════════════════════════════════════════════════════
# ── ❌ 错误做法(容器删除 = 数据消失)────────────────────────────────────────
docker run -d --name mysql_bad -e MYSQL_ROOT_PASSWORD=secret mysql:8.0
# 执行 docker rm mysql_bad 后,数据库数据完全丢失!
# ── ✅ 方式一:Bind Mount(绑定宿主机目录)推荐用于开发和生产 ────────────────
# 格式:-v 宿主机绝对路径:容器内路径
docker run -d --name mysql_good -v /opt/mysql/data:/var/lib/mysql # 数据目录映射
-v /opt/mysql/conf:/etc/mysql/conf.d # 配置目录映射
-e MYSQL_ROOT_PASSWORD=secret -p 127.0.0.1:3306:3306 # 只绑定本地,不对外暴露
--restart unless-stopped # 意外崩溃自动重启
mysql:8.0
# ── ✅ 方式二:Named Volume(命名卷)推荐用于数据库等需要高 I/O 的场景 ─────
# Docker 管理存储位置(在 /var/lib/docker/volumes/ 下),性能略优于 Bind Mount
docker volume create mysql_data
docker run -d --name mysql_vol -v mysql_data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=secret mysql:8.0
# 查看所有命名卷
docker volume ls
# 查看卷详情(包含宿主机实际路径)
docker volume inspect mysql_data 📁Bind Mount(绑定挂载)
- 路径直观,宿主机直接访问修改
- 适合配置文件、网站源码
- 迁移时直接打包目录
路径强耦合,可移植性略差
🗄️Named Volume(命名卷)
- Docker 统一管理,性能略优
- 适合数据库等高 I/O 场景
- 支持 volume driver 对接云存储
默认路径在 /var/lib/docker/,不直观
🌐 进阶:Docker 网络模式详解
理解 Docker 网络是实现容器间安全通信的关键。不同网络模式适用于不同场景:
bridge(默认) 容器获得独立虚拟 IP,通过 docker0 网桥与宿主机通信。推荐使用自定义 bridge 网络——容器间可直接用服务名通信(内置 DNS)。
host 容器直接使用宿主机网络,无独立 IP,性能最好(无 NAT 开销),但失去网络隔离,需注意端口冲突。适合对延迟极敏感的场景。
none 完全禁用网络,容器没有网络接口。适合数据处理、批量计算等完全不需要网络的任务,安全隔离最彻底。
# ════════════════════════════════════════════════════════════
# Docker 网络模式详解
# ════════════════════════════════════════════════════════════
# ── 默认:bridge 模式(最常用)──────────────────────────────────────────────
# 每个容器分配独立虚拟 IP(172.17.0.x),通过 docker0 网桥与宿主机通信
# 容器间通信需要通过 IP 或 --link(已废弃,推荐自定义网络)
docker run -d --name app1 --network bridge nginx
# ── 自定义 bridge 网络(推荐!容器间可直接用服务名通信)────────────────────
docker network create myapp_net
docker run -d --name web --network myapp_net nginx
docker run -d --name db --network myapp_net mysql:8.0
# 在 web 容器内可直接 ping db(用容器名作为 DNS)
# docker exec web ping db → 可以 ping 通
# ── host 模式(高性能,容器直接使用宿主机网络)───────────────────────────────
# 容器不再有独立 IP,直接监听宿主机端口
# 性能最好,但失去网络隔离,端口冲突风险高
docker run -d --name fast_app --network host nginx
# 注意:host 模式下不需要 -p 端口映射,容器直接使用宿主机 80 端口
# ── none 模式(完全隔离网络,适合数据处理任务)───────────────────────────────
docker run -d --name isolated --network none alpine sleep 3600
# 网络管理常用命令
docker network ls # 列出所有网络
docker network inspect myapp_net # 查看网络详情
docker network connect myapp_net app1 # 将已有容器加入网络 🎼 Docker Compose:多容器编排
当应用需要多个容器协同工作时(数据库 + Web 服务器 + 缓存),用命令行逐个 docker run 既繁琐又难以维护。Docker Compose 用一个 yaml 文件声明所有服务的配置关系,一条命令启动全部。
基础 docker-compose.yml 示例
# 文件路径:/opt/myapp/docker-compose.yml
# 最基础的 Nginx + PHP-FPM 组合示例
services:
web:
image: nginx:alpine # 使用轻量级 alpine 版本
container_name: myapp_nginx
restart: unless-stopped # 除非手动停止,否则崩溃或开机自动重启
ports:
- "80:80" # 宿主机端口:容器端口
- "443:443"
volumes:
- ./nginx/conf:/etc/nginx/conf.d # Nginx 配置目录
- ./html:/var/www/html # 网站文件目录
- ./logs/nginx:/var/log/nginx # 日志目录(方便在宿主机查看)
networks:
- app_net
depends_on:
- php # 确保 php 服务先启动
php:
image: php:8.3-fpm-alpine
container_name: myapp_php
restart: unless-stopped
volumes:
- ./html:/var/www/html # 与 Nginx 挂载相同目录(共享网站文件)
networks:
- app_net
# 自定义网络(容器间用服务名通信,如 nginx 配置中 fastcgi_pass php:9000)
networks:
app_net:
driver: bridge Docker Compose 核心命令速查
docker compose up -d 后台启动所有服务(自动拉取镜像)
docker compose down 停止并删除容器(保留挂载数据)
docker compose down -v 停止并删除容器及命名卷⚠️ 数据会删除
docker compose ps 查看所有服务的运行状态
docker compose logs -f 实时查看所有服务的合并日志
docker compose logs -f web 只看指定服务的日志
docker compose pull 拉取最新镜像(之后需 up -d 重建)
docker compose restart web 重启指定服务
docker compose exec web sh 进入指定服务容器内部
docker compose config 验证并查看最终合并的配置
🏗️ 实战:LEMP 栈完整部署
LEMP(Linux + Nginx + MySQL + PHP)是部署 WordPress 等 PHP 应用的经典组合。以下是一套完整的生产级 Compose 配置,包含 Redis 缓存和安全最佳实践。
docker-compose.yml(完整 LEMP + Redis)
# 文件路径:/opt/lemp/docker-compose.yml
# 完整 LEMP 栈:Linux + Nginx + MySQL + PHP-FPM + Redis
services:
# ── Nginx 反向代理 ─────────────────────────────────────────────────────────
nginx:
image: nginx:alpine
container_name: lemp_nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./www:/var/www/html
- ./nginx/ssl:/etc/nginx/ssl
networks:
- lemp_net
depends_on:
- php
- mysql
# ── PHP-FPM ────────────────────────────────────────────────────────────────
php:
image: php:8.3-fpm-alpine
container_name: lemp_php
restart: unless-stopped
volumes:
- ./www:/var/www/html # 与 Nginx 共享网站目录
environment:
- PHP_MEMORY_LIMIT=256M
networks:
- lemp_net
# ── MySQL 8 ────────────────────────────────────────────────────────────────
mysql:
image: mysql:8.0
container_name: lemp_mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} # 从 .env 文件读取密码
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql # 使用命名卷保存数据
networks:
- lemp_net
# 不对外暴露端口,只在内部网络访问(安全!)
# ── Redis 缓存 ──────────────────────────────────────────────────────────────
redis:
image: redis:7-alpine
container_name: lemp_redis
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD} # 启用密码认证
volumes:
- redis_data:/data
networks:
- lemp_net
# ── 命名卷(Docker 管理,高 I/O 性能)──────────────────────────────────────
volumes:
mysql_data:
redis_data:
# ── 自定义内部网络 ────────────────────────────────────────────────────────────
networks:
lemp_net:
driver: bridge .env 配置文件(敏感信息隔离)
# 文件路径:/opt/lemp/.env
# 敏感配置统一放在 .env 文件,不提交到 Git(在 .gitignore 中添加 .env)
MYSQL_ROOT_PASSWORD=强密码_至少16位
MYSQL_DATABASE=myapp_db
MYSQL_USER=myapp_user
MYSQL_PASSWORD=另一个强密码
REDIS_PASSWORD=Redis访问密码
# 启动命令:
# cd /opt/lemp && docker compose up -d
# 查看所有服务状态:
# docker compose ps .env 文件包含数据库密码等敏感信息,必须在 .gitignore 中添加 .env 防止提交到版本控制系统。可以提交一个 .env.example 模板(清空密码值)供团队参考。
📝 进阶:编写自定义 Dockerfile
当 Docker Hub 上的现成镜像不满足需求时,可以编写 Dockerfile 构建自定义镜像。多阶段构建是生产环境的最佳实践——在构建阶段安装编译工具,最终镜像只保留运行时必需的文件,显著减小镜像体积。
多阶段构建示例(Node.js 生产镜像)
# 文件路径:/opt/myapp/Dockerfile
# 示例:为 Node.js 应用构建生产镜像(多阶段构建,减小最终镜像体积)
# ════ 阶段一:构建(Builder Stage)════════════════════════════════════════════
FROM node:20-alpine AS builder
WORKDIR /app
# 先复制 package.json,利用 Docker 层缓存(依赖不变时跳过 npm install)
COPY package*.json ./
RUN npm ci --only=production
# 再复制源码并构建
COPY . .
RUN npm run build
# ════ 阶段二:生产镜像(只包含运行时必需文件)══════════════════════════════════
FROM node:20-alpine AS production
WORKDIR /app
# 创建非 root 用户运行应用(安全最佳实践)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# 只从 builder 阶段复制构建产物
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json .
# 设置文件所有权
RUN chown -R appuser:appgroup /app
USER appuser
# 暴露端口(文档说明,不实际发布)
EXPOSE 3000
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --retries=3 CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "dist/server.js"] # 在 Dockerfile 所在目录执行构建
docker build -t myapp:v1.0 .
# 打标签(为推送到 Docker Hub 或私有仓库做准备)
docker tag myapp:v1.0 yourusername/myapp:v1.0
docker tag myapp:v1.0 yourusername/myapp:latest
# 推送到 Docker Hub(需要先 docker login)
docker push yourusername/myapp:v1.0
# 查看镜像层信息(了解每层的大小,优化 Dockerfile)
docker history myapp:v1.0 🧹 进阶:限制容器日志大小
Docker 默认将容器的标准输出无限期保存在 /var/lib/docker/containers/ 下。高流量服务的日志可能在几周内积累到数十 GB,直接把 VPS 磁盘占满导致服务崩溃。
# ── 方案一(推荐):全局配置,对所有新容器生效 ─────────────────────────────
# 编辑或创建 /etc/docker/daemon.json
# 配置内容(JSON 格式):
# {
# "log-driver": "json-file",
# "log-opts": {
# "max-size": "20m",
# "max-file": "3"
# }
# }
# 含义:每个容器日志文件最大 20MB,最多保留 3 个滚动文件
# 即最多占用 60MB 磁盘空间(20MB × 3)
# 保存后重启 Docker 守护进程(仅对新创建的容器生效)
sudo systemctl restart docker
# ── 方案二:在 docker-compose.yml 中单独配置特定服务 ─────────────────────────
# services:
# web:
# image: nginx
# logging:
# driver: json-file
# options:
# max-size: "50m" # 高流量服务可以设大一些
# max-file: "5"
# ── 查看当前容器日志占用空间 ─────────────────────────────────────────────────
# 查看所有容器日志总大小
du -sh /var/lib/docker/containers/*/*-json.log 2>/dev/null | sort -rh | head -10
# 手动清空某个容器的日志(不停服务)
truncate -s 0 /var/lib/docker/containers/容器ID/容器ID-json.log 📋 /etc/docker/daemon.json 完整配置示例
{
"log-driver": "json-file",
"log-opts": {
"max-size": "20m",
"max-file": "3"
},
"dns": ["1.1.1.1", "8.8.8.8"]
} 保存后执行 sudo systemctl restart docker 生效。注意:只对之后新创建的容器有效,存量容器需重建。
🗼 进阶:Watchtower 容器自动更新
每次应用发布新版本都要手动 pull 和 up -d 很麻烦?Watchtower 会监控您的容器,发现镜像有新版本时自动拉取、重建、清理旧镜像,实现完全无人值守的容器更新。
# ── Watchtower:自动监控并更新所有 Docker 容器 ────────────────────────────
docker run -d --name watchtower --restart always -v /var/run/docker.sock:/var/run/docker.sock # 需要 socket 权限来管理其他容器
containrrr/watchtower --cleanup # 更新后自动删除旧镜像,释放磁盘
--schedule "0 3 * * *" # Cron 格式:每天凌晨 3 点检查更新
# ── 进阶:只更新特定容器(打标签)──────────────────────────────────────────
# 在需要自动更新的容器上加 label:
# docker run -d --label=com.centurylinklabs.watchtower.enable=true --name app nginx
# 然后以 --label-enable 模式运行 Watchtower(只更新打了标签的容器)
# docker run -d --name watchtower # -v /var/run/docker.sock:/var/run/docker.sock # containrrr/watchtower --label-enable --cleanup
# ── 查看 Watchtower 的更新日志 ────────────────────────────────────────────
docker logs -f watchtower --label-enable 模式,只对无状态服务(Nginx、API 服务等)开启自动更新。
❓ 常见问题解答
docker compose up -d 运行后容器立刻退出,怎么排查?
排查步骤:① docker compose ps 查看哪个服务的状态是 "Exited";② docker compose logs 服务名 查看退出前的日志输出——通常会有明确的报错信息(如配置文件格式错误、端口冲突、缺少环境变量);③ 常见原因:.env 文件不存在导致必需的环境变量为空(MySQL 的 MYSQL_ROOT_PASSWORD 未设置会立即退出)、挂载的宿主机目录不存在、端口已被其他进程占用(ss -tlnp | grep 端口号)。
容器内的应用如何访问宿主机上的服务(如宿主机的 MySQL)?
在 bridge 网络模式下,容器内使用特殊地址 host.docker.internal 访问宿主机(Linux 需要在 docker run 时加 --add-host host.docker.internal:host-gateway)。或者,直接使用宿主机的内网 IP(通常是 172.17.0.1,即 docker0 网桥地址,可用 ip addr show docker0 查看)。更推荐的做法:将宿主机的 MySQL 也迁移到 Docker,用自定义 bridge 网络实现容器间通信,彻底容器化管理。
docker system prune -a 和 docker system prune 有什么区别?执行前要注意什么?
docker system prune:清理已停止的容器、悬空镜像(dangling images,即无 tag 的中间层镜像)、未使用的网络。docker system prune -a:在此基础上额外删除所有未被任何容器使用的镜像(包括有 tag 的镜像)。--volumes 参数还会删除未使用的命名卷——这个参数极其危险,会删除数据库数据,千万不要在生产环境随意执行。建议只用 docker system prune(不加 -a),或精确指定要删除的资源(docker image prune / docker container prune)。
Docker 容器占用了多少内存?怎么限制单个容器的资源使用?
查看各容器实时资源占用:docker stats(类似 top,实时刷新)。在 docker-compose.yml 中限制资源:
services: web: deploy: resources: limits: memory: 512m cpus: '0.5'
这样该容器最多使用 512MB 内存和 50% 单核 CPU。典型参考值:Nginx 约 20-50MB,PHP-FPM(含业务)约 100-300MB/进程,MySQL 约 300-500MB(取决于 buffer_pool 配置),Redis 约 20-200MB(取决于数据量)。1GB 内存的 VPS 跑 LEMP 全栈会比较吃力,建议 2GB 起步。
Dockerfile 中 RUN、CMD 和 ENTRYPOINT 有什么区别?
RUN:构建镜像时执行(安装软件、创建目录等),结果被固化到镜像层中。每条 RUN 命令创建一个新镜像层,建议用 && 合并多条命令减少层数。CMD:容器启动时的默认命令,可以被 docker run 命令行参数覆盖。一个 Dockerfile 只有最后一条 CMD 生效。ENTRYPOINT:容器的入口点,不会被命令行参数覆盖(除非 --entrypoint)。常用组合:ENTRYPOINT 定义固定执行程序,CMD 提供默认参数——docker run image 新参数 时 CMD 被替换而 ENTRYPOINT 保持不变。
如何将整个 Docker 应用迁移到新服务器?
标准迁移流程:① 在新服务器安装 Docker(官方脚本一键安装);② 将配置目录(docker-compose.yml、.env、Nginx 配置等)和数据目录(MySQL 数据、网站文件等)打包传输:rsync -avz /opt/myapp/ root@新服务器IP:/opt/myapp/;③ 如果使用 Named Volume,需要先导出:docker run --rm -v mysql_data:/data -v /tmp:/backup alpine tar czf /backup/mysql_data.tar.gz /data,传输后在新机器解压;④ 在新服务器执行 docker compose up -d,镜像会自动从 Docker Hub 重新拉取;⑤ 验证服务正常后切换 DNS,保持旧服务器再运行 24-48 小时作为备用。
Docker 容器如何配置时区?容器内时间和宿主机不一致怎么办?
容器默认使用 UTC 时区,日志时间戳会与宿主机不一致(如宿主机是 UTC+8,日志会差 8 小时)。解决方案:在 docker-compose.yml 的服务配置中添加:environment: - TZ=Asia/Shanghai,并挂载宿主机时区文件:volumes: - /etc/localtime:/etc/localtime:ro。两种方案选一即可,推荐 TZ 环境变量(更简洁)。对于已运行的容器需重建才能生效(docker compose up -d --force-recreate)。
我已经用了 1Panel 面板管理 Docker,还需要学命令行操作吗?
强烈建议学。理由:① 面板异常时的应急手段:1Panel 本身也是一个 Docker 容器,如果面板出问题,只能用命令行操作其他容器;② 面板的应用商店不能覆盖所有场景:定制化的 docker-compose.yml、多阶段 Dockerfile 构建、网络配置调试都需要命令行;③ CI/CD 流水线中没有 GUI:自动化部署脚本必须用命令行;④ 面板是加速器而非替代品:理解了命令行原理,才能看懂面板操作背后发生了什么,出问题时不慌乱。第 13 篇的 1Panel 和本篇是互补关系,两者都学效果最好。
学完 Docker 后,下一步应该按什么顺序继续?
按本站 30 篇路径,第 15 篇(本篇)→ 第 16 篇(database-setup)→ 第 17 篇(personal-cloud-storage)是最顺畅的进阶链条。关联逻辑:第 16 篇的 MySQL/PostgreSQL/Redis 部署会大量用到本篇的 Compose 和数据持久化知识——事实上本篇的 LEMP 栈实战已经包含了 MySQL + Redis 的 Compose 配置,第 16 篇会深入讲解这些数据库的内部配置和安全加固;第 17 篇的 Nextcloud/AList 私有云盘则是 Docker 最典型的应用场景之一,一条 docker compose up -d 即可部署。
Docker 和 Kubernetes(K8s)是什么关系?什么时候需要 K8s?
Docker 是单机容器运行时,管理一台服务器上的容器。Kubernetes(K8s)是容器编排平台,管理跨多台服务器的容器集群,提供自动扩缩容、故障自愈、滚动更新、服务发现等能力。什么时候需要 K8s:① 需要横向扩展到 3 台以上服务器;② 需要零停机滚动发布;③ 有复杂的微服务架构(20+ 个服务);④ 企业级可靠性要求。个人 VPS / 小型项目完全不需要 K8s——Docker Compose 足够应对 95% 的个人和中小企业场景,学习成本远低于 K8s,且不需要额外的 Master 节点(K8s 的控制面本身就需要消耗约 4GB 内存)。