ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Docker Compose 部署中间件全指南:从环境搭建到K8s迁移

Docker Compose 部署中间件全指南:从环境搭建到K8s迁移 1. 项目概述与需求拆解1.1 为什么要用 Docker Compose 装中间件我最早接触中间件部署的时候干的还是最原始的活儿去官网下载安装包解压、改配置、加环境变量、写 systemd 服务脚本然后一台一台机器重复。如果只是装个 MySQL 还好说等后面 Redis、RabbitMQ、Nginx、Kafka 这些堆到一起每一套都有自己的依赖和配置方式光是记安装步骤就能让人头大。换一台机器整套流程又要重新来一遍。当时我就想要是能把这些乱七八糟的安装过程统一起来、一键搞定就好了。后来切到 Docker Compose这个问题算是彻底解决了。Docker Compose 做的事情简单说就是用一个 YAML 文件把多个容器的启动参数、网络、存储、依赖关系全部描述清楚然后一行docker compose up -d全部拉起来。对于中间件这种“部署方式高度相似、版本参数成体系”的组件它几乎是最合适的载体。你不需要再关心宿主机上是不是少了某个依赖库不需要担心卸载的时候留下一堆残留文件更不需要在团队里传一份几百字的“手工安装指南”。一份 compose 文件所有人拉下来直接跑。从实际效果看我后来接手过的项目凡是用 Compose 管理中间件的环境搭建速度普遍从“半天起步”缩到了“十分钟以内”出问题的概率也低得多。对于个人开发者、小团队、以及希望快速搭建开发/测试环境的场景来说Compose 这套方案基本是当下最优解。1.2 这套方案适合谁、能解决什么问题先给一个大概的受众画像你可以对照看看自己属于哪一类后端开发人员尤其是做 Spring Boot、微服务架构的。本地开发经常需要 MySQL、Redis、RabbitMQ 这些基础组件用 Compose 起一套写代码的时候不再被环境问题打断思路。运维 / 实施工程师需要在多台服务器包括内网环境快速部署一套业务系统。Compose 文件写好以后批量拷贝到各机器执行即可只需要改环境变量。测试工程师需要在干净环境中验证功能可能频繁重建数据库、清缓存。Compose 的重建成本极低几秒钟就能恢复一个干净环境。正在学习容器化、准备向 Kubernetes 迁移的人Compose 是理解容器编排的最佳起点。它把“网络、存储、依赖”讲清楚了后面看 K8s 的 Service、PVC、Deployment 会觉得顺很多。其实不只是中间件凡是那些“需要长期稳定运行、配置相对固定、依赖明确的组件”都适合用 Compose 管理。我见过有人把定时任务、日志采集器、监控告警系统全部用 Compose 编排效果都不错。它的核心价值在于把环境状态固化为代码让所有人共享同一套可复现的启动方式。2. 整体设计与方案选型思路2.1 Compose 文件的核心设计原则Compose 文件那套约定其实不难难的是怎么设计得合理、可维护。我刚开始写的时候完全是“能用就行”的风格所有服务塞在默认网络里数据目录乱挂密码直接写死在文件里端口能映射就映射。后来维护得多了才慢慢总结出几条比较重要的原则。第一每个中间件服务单独定义不搞“大杂烩”。有人喜欢把一个容器里同时装 MySQL 和 Redis或者把 Nginx 和 PHP-FPM 塞在一起。这在交付阶段可能省事但维护起来非常痛苦——只要其中一个组件出问题就得重启整个容器。中间件这种基础组件天然适合单容器单职责。Compose 里多写几个 service 没有任何成本反而排查问题的时候能精确定位。第二数据与容器分离。容器是“一次性”的MySQL 容器删了重建如果数据跟着消失那就真的悲剧了。所以数据目录、配置文件、日志目录都应该挂载到宿主机或者用 named volume。这一点在后面的章节我会具体演示。很多新手上来就会踩这个坑——容器一删库没了人傻了。第三敏感信息走环境变量或.env文件。密码、密钥这类东西不应该硬编码在 compose 文件里。Compose 原生支持.env文件替换变量这个机制一定要用起来。你的 compose 文件可以提交到 Git 仓库但.env文件必须加入.gitignore。第四版本固定不追新。镜像标签尽量用具体的版本号比如mysql:8.0、redis:7.0而不是裸的latest。原因很简单工具链的升级常常伴随配置格式变化你今天写好的 compose 文件三个月后碰到新版本镜像可能就跑不起来了。固定版本能保证可复现性。2.2 网络模式与依赖关系怎么规划Compose 默认会自动创建一个网络默认是 bridge 驱动所有在该 compose 文件里定义的服务都会加入这个网络。在这个网络里服务之间通过“服务名”进行 DNS 解析而不是 IP。比如你的 Spring Boot 服务连接 MySQLJDBC 地址写jdbc:mysql://mysql:3306/dbname即可。介个特性非常关键——它意味着你不需要关心容器 IP 的变化只要服务名不变网络内部就能稳定互通。有一个配置需要注意depends_on这个选项。它解决的问题是“启动顺序”——比如某个业务服务依赖数据库先启动。但我用下来的体会是depends_on只能保证“容器被创建和启动”的先后不能保证“服务真正可用”。MySQL 容器起来了但 MySQL 内部可能还在初始化此时业务服务去连接就会失败。如果要严谨一点需要配合healthcheck使用或者让业务服务的启动逻辑带有重试机制。再说一下端口映射的策略。凡是只需要内部通信的中间件尽量不要映射到宿主机。比如你的业务容器和消息队列容器都在同一个 Compose 网络里那 Kafka 的端口完全不需要暴露到宿主机。只有需要从宿主机外部访问的服务才需要做ports映射。很多人的安全漏洞其实就是把一堆不必要暴露的服务全部映射到了0.0.0.0:端口。如果你的中间件只是内部使用建议不要映射端口或者映射到127.0.0.1:端口这样只有本机能访问。2.3 选型对比Compose 与其他方式的区别我知道有人会问既然有 Docker Compose那 Dockerfile、docker run、Kubernetes 这些是什么关系简单说Dockerfile负责构建“镜像”本身解决的是“环境如何制作”的问题。比如你要做一个包含特定 SDK 的 Java 运行镜像。docker run是一次性启动一个容器的命令。适合临时调试不适合管理多个服务。Docker Compose站在docker run之上解决的是“多个容器如何协同启动”的问题可以用一个声明式的文件描述整个服务栈。它的优势是简单、直接、学习成本低特别适合开发环境和中小型部署场景。Kubernetes则解决的是更大规模下的“编排调度、自动扩缩容、自愈”等问题。功能强很多但复杂度也高不少。所以在中间件部署这个场景Compose 是性价比最高的方案。你不需要为“跑一个 MySQL”就去搭一套 K8s——那是拿大炮打蚊子。合理的路径是先用 Compose 理清服务之间的关系等业务增长到需要水平扩展、滚动更新的时候再迁移到 K8s。后面我会单独谈一下迁移时要注意的点。3. 核心配置解析与文件编写要点3.1 一个最基础但完整的示例我先给一个可运行的例子。假设我们要在本地搭建一套常用的开发环境MySQL 8.0、Redis 7.0、RabbitMQ 3.13。这是很多后端系统Spring Boot 项目的基础组合。# docker-compose.yml version: 3.8 services: mysql: image: mysql:8.0 container_name: dev-mysql restart: always environment: MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: demo_db MYSQL_USER: demo MYSQL_PASSWORD: demo123456 TZ: Asia/Shanghai ports: - 3306:3306 volumes: - ./data/mysql:/var/lib/mysql - ./conf/mysql/my.cnf:/etc/mysql/conf.d/my.cnf command: --character-set-serverutf8mb4 --collation-serverutf8mb4_unicode_ci healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -uroot, -proot123456] interval: 10s timeout: 5s retries: 5 redis: image: redis:7.0 container_name: dev-redis restart: always ports: - 6379:6379 volumes: - ./data/redis:/data - ./conf/redis/redis.conf:/etc/redis/redis.conf command: [redis-server, /etc/redis/redis.conf] healthcheck: test: [CMD, redis-cli, ping] interval: 10s timeout: 5s retries: 5 rabbitmq: image: rabbitmq:3.13-management container_name: dev-rabbitmq restart: always environment: RABBITMQ_DEFAULT_USER: admin RABBITMQ_DEFAULT_PASS: admin123 ports: - 5672:5672 - 15672:15672 volumes: - ./data/rabbitmq:/var/lib/rabbitmq healthcheck: test: [CMD, rabbitmq-diagnostics, -q, ping] interval: 15s timeout: 10s retries: 5对应的目录结构compose-env/ ├── docker-compose.yml ├── .env ├── conf/ │ ├── mysql/ │ │ └── my.cnf │ └── redis/ │ └── redis.conf └── data/ ├── mysql/ ├── redis/ └── rabbitmq/启动命令只有一行docker compose up -d查看状态docker compose ps看到healthy字样说明服务已通过健康检查可以正常使用。3.2 配置项逐个拆解环境变量、端口、数据卷很多人第一次看到这样的 compose 文件会觉得“这不就是一堆配置堆起来吗”。但实际上每一段背后的意义都值得展开。环境变量的优先级意识。注意看 MySQL 那个 service。我设置了MYSQL_ROOT_PASSWORD、MYSQL_DATABASE、MYSQL_USER、MYSQL_PASSWORD。这些是官方镜像约定好的环境变量——镜像里的启动脚本entrypoint会读取它们在首次初始化数据目录时创建对应的用户、库。有一个容易被忽视的点这些环境变量只在数据目录为空时生效。如果你之前已经跑过一次数据目录里已经有内容了那再改MYSQL_DATABASE是没有用的。改环境变量前要想清楚要么删数据卷重新初始化要么手动进去改库。端口映射的取舍。我把 3306、6379、5672、15672 全部映射到了宿主机。这在本地开发环境没问题因为你需要用 Navicat、Redis Desktop Manager 这类 GUI 工具连上去。但在生产环境你大概率不希望暴露 Redis 的 6379——Redis 本身没有足够强的访问控制裸奔在公网上极容易被攻击。生产环境建议物理网络隔离或者至少不要把这几个端口暴露到公网。数据卷使用 bind mount 还是 named volume。两种方式各有适用场景bind mount格式./data/mysql:/var/lib/mysql把宿主机的目录直接挂进去。优点是数据文件就在当前目录下你可以直接进去查看、备份、dump。缺点是受宿主机目录权限影响比较大。比如某些 Linux 发行版的 SELinux 策略会阻止容器写目录需要做额外处理。named volume格式mysql-data:/var/lib/mysql由 Docker 管理存储隔离性更好性能也比较稳定。缺点是你不太容易直接看到数据文件在哪个位置备份的时候要借助docker run --rm -v mysql-data:/data -v $(pwd):/backup alpine tar czf /backup/mysql.tar.gz /data之类的命令。我个人的习惯开发环境用 bind mount因为直观有状态的正式服务用 named volume因为省心安全。交给你自由选择。3.3 添加 Sentinel、MinIO、Nacos 等中间件的技巧现实环境里我们经常还要加 Sentinel流量防护、MinIO对象存储、Nacos配置中心和注册中心这类的中间件。它们的 compose 配置也不复杂但有几个小细节值得注意。以 Sentinel 为例它的控制台是一个 Spring Boot 应用默认端口是 8858。从某个版本之后官方提供的 Docker 镜像中如果要在控制台配置持久化需要挂载一个日志目录。一个最小配置sentinel: image: bladex/sentinel-dashboard:1.8.8 container_name: dev-sentinel restart: always ports: - 8858:8858 environment: JAVA_OPTS: -Dserver.port8858 -Dcsp.sentinel.dashboard.serverlocalhost:8858 -Dproject.namesentinel-dashboard volumes: - ./data/sentinel:/root/logs这里JAVA_OPTS是镜像约定的环境变量用来覆盖 JVM 参数。如果你要配置-Dcsp.sentinel.dashboard.server指向某个远程地址就改这里。Nacos 稍微特殊一点它有单体模式和集群模式。在本地开发用单体即可。注意 Nacos 2.x 默认需要 MySQL 存储配置数据需要先配置好数据库nacos: image: nacos/nacos-server:v2.3.2 container_name: dev-nacos restart: always environment: MODE: standalone PREFER_HOST_MODE: hostname MYSQL_SERVICE_HOST: mysql MYSQL_SERVICE_DB_NAME: nacos_config MYSQL_SERVICE_USER: demo MYSQL_SERVICE_PASSWORD: demo123456 NACOS_AUTH_ENABLE: false ports: - 8848:8848 - 9848:9848 depends_on: mysql: condition: service_healthy注意MYSQL_SERVICE_HOST这里直接填了mysql而不是 IP。这正是利用了 Compose 自带的 DNS 解析——容器之间通过服务名互通。这招在多个中间件互相依赖的时候特别有用。MinIO 的配置就简单很多minio: image: minio/minio:latest container_name: dev-minio restart: always environment: MINIO_ROOT_USER: minioadmin MINIO_ROOT_PASSWORD: minioadmin ports: - 9000:9000 - 9001:9001 volumes: - ./data/minio:/data command: server /data --console-address :90019000 是 API 端口9001 是 Web 控制台端口。MINIO_ROOT_USER和MINIO_ROOT_PASSWORD是初始化管理员账号的约定环境变量。3.4 配置文件外置把 my.cnf / redis.conf 挂进去上一节我们提到了conf目录的挂载。这里补充说明一下为什么这样做以及怎么写这些配置。MySQL 默认的字符集不是 utf8mb4旧版本是 latin1如果直接建表中文和 emoji 可能会出现乱码问题。所以我通常在宿主机conf/mysql/my.cnf里写[mysqld] character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci default-time-zone 08:00 max_connections 500 [client] default-character-setutf8mb4 [mysql] default-character-setutf8mb4然后挂载到容器的/etc/mysql/conf.d/my.cnf。这个路径是 MySQL 官方镜像约定好的“补充配置目录扩展点”镜像启动时会自动读取这个目录下的所有.cnf文件。你不用去改镜像内的主配置省心很多。Redis 同理。我要开启 AOF 持久化、设置密码、限制内存使用策略就会在宿主机写conf/redis/redis.confappendonly yes appendfsync everysec requirepass redis123456 maxmemory 256mb maxmemory-policy allkeys-lru然后挂载到容器的/etc/redis/redis.conf启动时让 redis-server 显式加载它command: [redis-server, /etc/redis/redis.conf]注意这里不能写成command: redis-server /etc/redis/redis.conf这样的 string 格式。Compose 的 command 字段如果用字符串形式它不会经过 shell 分词所以容易被解析成一个整体参数。用列表形式YAML array最稳。这里推荐一个经验配置文件永远放在宿主机里不要在容器内用 vi/vim 临时改。因为容器一旦重建任何在容器内做的修改都会丢失。放进宿主机并挂载你的“环境配置”也成为了可以被 Git 追踪的资产。4. 实操全流程从零到一搭建完整环境4.1 前置检查版本和依赖正式开始之前先确认一下宿主机环境。Compose 本身依赖 Docker Engine。这里给出我建议的最低版本组件最低版本推荐版本Docker Engine20.1024.0Docker Compose2.02.24检查命令docker --version docker compose version注意旧版的docker-compose带横线的是 Python 写的功能上已经停更。新版的docker compose带空格的是 Docker 官方用 Go 写的插件推荐使用。如果你的服务器还是老版本建议先升级# 以 Ubuntu / Debian 为例 sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin如果是离线环境下面我会单独讲。4.2 完整步骤目录初始化、编写文件、启动与验证第一步创建项目目录并进入。mkdir -p ~/compose-env/{conf/{mysql,redis},data/{mysql,redis,rabbitmq}} cd ~/compose-env第二步创建.env文件把敏感变量集中管理cat .env EOF MYSQL_ROOT_PASSWORDroot123456 MYSQL_DATABASEdemo_db MYSQL_USERdemo MYSQL_PASSWORDdemo123456 REDIS_PASSWORDredis123456 TZAsia/Shanghai EOF然后在 compose 文件里引用这些变量environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: ${MYSQL_DATABASE} MYSQL_USER: ${MYSQL_USER} MYSQL_PASSWORD: ${MYSQL_PASSWORD} TZ: ${TZ}第三步编写docker-compose.yml内容参考 3.1 节把环境变量替换为${...}形式。第四步启动并观察日志。docker compose up -d docker compose ps docker compose logs -f mysql # 观察 MySQL 初始化日志MySQL 首次初始化可能会需要十几秒期间日志里会有Initializing database的输出。等到ready for connections出现就说明数据库已经可用。这里最容易踩的坑是刚执行完docker compose up -d就去连数据库此时 MySQL 还没准备好连接直接报错。所以加了 healthcheck配合docker compose ps看状态是否为healthy会更稳。第五步验证连通性。比如从宿主机连 MySQLmysql -h127.0.0.1 -P3306 -udemo -pdemo123456 demo_db如果不想安装 MySQL 客户端可以直接进入容器内部操作docker exec -it dev-mysql mysql -uroot -proot123456Redis 验证docker exec -it dev-redis redis-cli如果已经在 redis.conf 里配置了requirepass需要先执行AUTH redis123456。4.3 镜像拉取太慢、离线安装怎么办这是国内环境绕不开的一个问题也是我看到热词里专门有“docker compose离线安装”的原因。先说正常情况。如果服务器能访问公网但拉取 Docker Hub 镜像速度很慢我的经验是用可靠的镜像加速地址。Docker Daemon 配置文件/etc/docker/daemon.json里可以配置多个 registry-mirrors{ registry-mirrors: [ https://docker.1ms.run ] }配置后重启 Dockersudo systemctl daemon-reload sudo systemctl restart docker注意镜像加速只对 Docker Hub 官方仓库有效。如果你用的是其他镜像仓库请参考对应文档。更麻烦的是“纯离线环境”。这种情况通常出现在政企内网、机房隔离网络等场景。我的做法分两步第一步在能联网的机器上拉取镜像并打包成 tar。# 假设要安装的是 mysql:8.0, redis:7.0, rabbitmq:3.13-management docker pull mysql:8.0 docker pull redis:7.0 docker pull rabbitmq:3.13-management # 分别保存 docker save -o mysql-8.0.tar mysql:8.0 docker save -o redis-7.0.tar redis:7.0 docker save -o rabbitmq-3.13-management.tar rabbitmq:3.13-management第二步把 tar 文件和 compose 项目目录一起拷贝到离线服务器上然后加载镜像。docker load -i mysql-8.0.tar docker load -i redis-7.0.tar docker load -i rabbitmq-3.13-management.tar镜像加载成功之后docker compose up -d就不会再去拉取镜像直接使用本地已有的镜像创建容器。需要提醒的是中间件的镜像体积通常不小mysql:8.0 约 600MBminio 约 300MB拷贝到离线环境时要规划好磁盘空间。4.4 数据备份与恢复的两种常用姿势用 Compose 管理中间件备份数据这件事也不能忽略。我推荐两种方式。方式一直接备份挂载目录适合 bind mount。如果你用的是./data/mysql这种目录挂载直接打包目录就行tar czf mysql-backup-$(date %F).tar.gz ./data/mysql恢复时解压到原路径即可。但这种方式要求备份期间数据一致性好——最好是先停掉 MySQL 写入或者执行FLUSH TABLES WITH READ LOCK后再打包。方式二使用容器内工具导出适合所有场景。MySQLdocker exec dev-mysql sh -c exec mysqldump -uroot -p$MYSQL_ROOT_PASSWORD --all-databases all-databases.sqlRedisdocker exec dev-redis redis-cli -a redis123456 BGSAVE # 稍等片刻RDB 文件生成到 /data/dump.rdb对应宿主机 ./data/redis/dump.rdbRabbitMQ它的配置和消息数据都存储在/var/lib/rabbitmq直接挂载目录备份即可。我用得最多的还是 mysqldump 定时任务crontab的方式。把 dump 脚本丢到 crontab 里每天凌晨执行保留最近 7 天备份。这套组合对于中小型项目完全够用。5. 常用中间件清单与调优速查5.1 一份可直接抄作业的常用中间件 Compose 参考这里整理一份“开箱即用”的清单涵盖后端开发中最高频的几个中间件。你不需要全部启动按需裁剪即可。services: # ---------- MySQL ---------- mysql: image: mysql:8.0 container_name: mid-mysql restart: always environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root123456} MYSQL_DATABASE: ${MYSQL_DATABASE:-app_db} TZ: Asia/Shanghai command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci - --max_connections500 ports: - 3306:3306 volumes: - ./data/mysql:/var/lib/mysql - ./conf/mysql:/etc/mysql/conf.d healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -uroot, -p${MYSQL_ROOT_PASSWORD:-root123456}] interval: 10s timeout: 5s retries: 5 # ---------- Redis ---------- redis: image: redis:7.0 container_name: mid-redis restart: always ports: - 6379:6379 volumes: - ./data/redis:/data - ./conf/redis/redis.conf:/etc/redis/redis.conf:ro command: [redis-server, /etc/redis/redis.conf] # ---------- RabbitMQ ---------- rabbitmq: image: rabbitmq:3.13-management container_name: mid-rabbitmq restart: always environment: RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER:-admin} RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASS:-admin123} ports: - 5672:5672 - 15672:15672 volumes: - ./data/rabbitmq:/var/lib/rabbitmq # ---------- Nginx ---------- nginx: image: nginx:1.25 container_name: mid-nginx restart: always ports: - 80:80 - 443:443 volumes: - ./conf/nginx/conf.d:/etc/nginx/conf.d:ro - ./www:/usr/share/nginx/html:ro - ./logs/nginx:/var/log/nginx # ---------- MinIO ---------- minio: image: minio/minio:latest container_name: mid-minio restart: always environment: MINIO_ROOT_USER: ${MINIO_USER:-minioadmin} MINIO_ROOT_PASSWORD: ${MINIO_PASSWORD:-minioadmin} ports: - 9000:9000 - 9001:9001 volumes: - ./data/minio:/data command: server /data --console-address :9001 # ---------- Nacos ---------- nacos: image: nacos/nacos-server:v2.3.2 container_name: mid-nacos restart: always environment: MODE: standalone PREFER_HOST_MODE: hostname MYSQL_SERVICE_HOST: mysql MYSQL_SERVICE_DB_NAME: nacos_config MYSQL_SERVICE_USER: root MYSQL_SERVICE_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root123456} ports: - 8848:8848 - 9848:9848 depends_on: mysql: condition: service_healthy # ---------- Sentinel ---------- sentinel: image: bladex/sentinel-dashboard:1.8.8 container_name: mid-sentinel restart: always environment: JAVA_OPTS: -Dserver.port8858 -Dcsp.sentinel.dashboard.serverlocalhost:8858 -Dproject.namesentinel-dashboard ports: - 8858:8858 # ---------- Elasticsearch ---------- elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:8.11.3 container_name: mid-elasticsearch restart: always environment: - discovery.typesingle-node - xpack.security.enabledfalse - ES_JAVA_OPTS-Xms512m -Xmx512m ports: - 9200:9200 volumes: - ./data/elasticsearch:/usr/share/elasticsearch/data这个清单可以直接复制到你的docker-compose.yml里按需注释/取消注释。5.2 各中间件的关键参数与调优建议上节给了文件这节讲一下常见的优化方向避免“照抄清单但不知道如何调整”。MySQLmax_connections决定最大连接数默认 151对小型应用够用如果你有连接池且核心线程较多建议调大。innodb_buffer_pool_size 默认 128M这个参数开大能明显提升查询性能。在 compose 里可以通过command追加--innodb-buffer-pool-size1G或者写在my.cnf里。Redismaxmemory必须设置因为默认情况下 Redis 会一直吃内存直到操作系统 OOM。maxmemory-policy建议设置为allkeys-lru这样在内存满的时候 Redis 自动淘汰最久未使用的 key而不至于写入失败。RabbitMQ关键调优项是连接心跳heartbeat和 TCP 连接 backlog。如果客户端大量掉线通常是 heartbeat 时间设置太短。在 compose 中可以通过command设置command: [rabbitmq-server]或者通过 extra 配置文件调整。多数情况下默认配置已经能满足需求不需要过度调优。Elasticsearch容器方式跑 ES 要特别注意两个系统配置vm.max_map_count至少需要 262144。如果启动报错执行sudo sysctl -w vm.max_map_count262144若要永久生效写入/etc/sysctl.conf。另外ES 非常吃内存ES_JAVA_OPTS建议 Xms 和 Xmx 设置成一样避免 JVM 动态调整引发性能抖动。5.3 为什么推荐同时启用 healthcheck在前面的示例里我多次写了healthcheck。这可能看起来像是“多此一举”但实际使用中它救过我很多次。场景一depends_on只关心容器启动不关心服务可用。有healthcheck之后你可以在depends_on里写明depends_on: mysql: condition: service_healthy这样业务容器就只会在 MySQL 健康通过后才启动。Compose 2.x 之后完全支持这种写法。场景二docker compose ps输出的状态非常直观。未配置 healthcheck 时容器的状态通常只有running或exited。配置之后你会看到(healthy)或(unhealthy)。这是一个非常方便的“健康仪表盘”排查问题时一眼就能定位到哪个组件不正常。写 healthcheck 的时候有一个经验探针命令要尽可能轻量。不要用那种会执行大量查询的命令否则频繁探测反而会拖累服务。MySQL 官方推荐mysqladmin pingRedis 用redis-cli pingRabbitMQ 用rabbitmq-diagnostics -q ping这些都是官方认可的轻量探针。6. 常见问题的排查与修复实录6.1cannot stop docker compose application类问题的处理热词里专门有一个 “cannot stop docker compose application. reason: compose [stop] exit status 1”这正是我自己实操里遇到过多次的坑。现象是执行docker compose stop的时候报错exit status 1部分容器停止失败。常见原因有以下几个原因一容器内的进程不响应 SIGTERM 信号。Docker 停容器时默认先发 SIGTERM等一段时间默认 10 秒之后如果进程还没退出就会发 SIGKILL。有些中间件尤其是老版本 Nacos、Elasticsearch在收到 SIGTERM 后需要很长时间才能优雅退出。如果等了 10 秒还没退出Docker 会强制杀掉容器此时docker compose stop就会报错。解决方法很简单给docker-compose.yml中对应服务配置更长的停止超时时间services: nacos: image: nacos/nacos-server:v2.3.2 stop_grace_period: 60s原因二容器内的 PID 1 进程不是真正的业务进程。比如有些镜像使用了一个简单脚本作为 entrypoint脚本启动子进程之后自己退出了或没有正确把信号转发给子进程。这种时候容器收到 SIGTERM 也无法优雅退出。排查方法docker inspect dev-nacos --format {{.State.Pid}}进入容器看 PID 1 是什么进程。如果发现是sh或bash而不是真正的 Java 进程优先考虑换新版镜像。原因三容器处于异常状态无法正常停止。可以用最粗暴但有效的方式docker compose kill docker compose downkill直接发 SIGKILLdown会删除容器和默认网络不删数据卷。等清理完成后重新up -d即可。6.2 中间件容器间网络连不通的排查方法另一个很常见的问题是两个中间件容器明明在同一个 compose 网络里但程序连不上。特别是连接 Nacos、连接数据库时经常出现Connection refused。排查思路按照这三步走第一步确认容器是否正常docker compose ps第二步确认容器是否真的在同一个网络docker inspect dev-mysql --format {{json .NetworkSettings.Networks}} docker inspect dev-nacos --format {{json .NetworkSettings.Networks}}看看两边是不是都有同一个网络名通常是项目名_default。如果其中一个是单独用docker run启动的它就不会在这个网络里也就没法用服务名互相访问。第三步从业务容器里手动测试连通性docker exec -it dev-nacos ping mysql docker exec -it dev-nacos telnet mysql 3306如果 ping 不通或端口连不上多数情况下是网络模式配置有问题或者目标服务的端口绑定被本地防火墙挡住了。还有一个极容易忽略的坑服务名中的下划线。Compose 服务名支持字母、数字、下划线但在某些场景下DNS 解析下划线可能出问题。为了避免不必要的麻烦服务名尽量用-代替_比如mysql、rabbitmq不要写mysql_db这种。6.3 docker compose 覆盖策略override 文件的使用技巧关于 Compose 有一个高阶用法值得单独提一下docker-compose.override.yml。它与docker-compose.yml放在同一目录时执行任何 compose 命令都会自动合并docker-compose.yml和docker-compose.override.yml的配置。这个特性在“同一个项目不同环境不同配置”的场景下非常有用。我的习惯是docker-compose.yml只放“所有环境都相同”的基础配置。docker-compose.override.yml放本机特有的配置比如开发环境要映射端口、要挂载源码目录。生产环境用docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d显式指定两个文件。举个具体例子# docker-compose.yml services: app: image: myapp:latest environment: SPRING_PROFILES_ACTIVE: dev# docker-compose.override.yml本地开发 services: app: ports: - 8080:8080 volumes: - ./src:/app/src这样在本地跑docker compose up -dapp服务会自动带上端口映射和源码挂载而在生产环境指定-f docker-compose.yml -f docker-compose.prod.yml时不加载 override 文件也就不会意外暴露端口。这个模式对团队协作非常友好。6.4 容器日志膨胀与服务资源限制容器跑久了日志文件会越来越大。默认情况下 Docker 的 json-file 日志驱动会无限增长如果不处理/var/lib/docker 目录可能被撑爆。在docker-compose.yml里加一个全局配置services: mysql: image: mysql:8.0 logging: driver: json-file options: max-size: 50m max-file: 3这段配置表示每个容器的日志文件最大 50MB最多保留 3 个文件。超过范围后自动滚动覆盖避免磁盘被日志占满。这个配置强烈建议每个服务都加上。资源限制也是一样。默认容器不限制 CPU 和内存如果某个中间件出现内存泄漏或突发高负载可能拖垮整个宿主机。给关键服务加上services: mysql: image: mysql:8.0 deploy: resources: limits: cpus: 2.0 memory: 2g reservations: cpus: 0.5 memory: 512m注意独立的docker compose非 swarm 模式也能识别部分deploy.resources配置。如果版本太低不支持也可以用mem_limit: 2g和cpus: 2.0这种旧式写法。7. 从 Compose 平滑迁移到 Kubernetes7.1 设计层的差异点热词里提到了 “docker compose升级到k8s”这是很多团队走到一定规模后都会碰到的问题。先说结论Compose 和 K8s 在“编排理念”上有一定关联但并不是简单地把 YAML 翻译成 Deployment 就能直接跑。需要关注这几个差异点Compose 的“服务”对应 K8s 的多个资源。在 Compose 里一个 service 包含了镜像、端口、卷、环境变量、健康检查、资源限制等全部信息。到了 K8s这些配置要拆开部署到多个资源上Deployment管理副本数、Service提供稳定的访问入口、ConfigMap保存配置、Secret保存密码、PVC持久化存储。粒度更细但同时也意味着你要管理更多资源。网络模式完全不同。Compose 默认让所有服务在同一个扁平网络里天然互通K8s 则是每个 Pod 有独立 IPPod 之间通过 Service 或 DNS 访问。如果你在 Compose 里依赖了固定端口映射、或者用 localhost 访问兄弟服务迁移时必须改代码或重新设计。存储方式也要换。Compose 里最常用的是宿主机路径挂载bind mount到了 K8s 里则应该使用 PV PVC或者直接用 StorageClass 动态供应。本地路径挂载在 K8s 里只在单节点集群比如 k3s中可用多节点集群下 Pod 漂移后数据就找不到了。7.2 迁移的常见路径和配套工具一个比较务实的迁移步骤如下先把 Compose 中的每个 service 理解独立化确定哪些服务有状态需要持久化哪些服务无状态可以随意重建。有状态服务优先考虑用 StatefulSet PVC比如 MySQL、Redis、RabbitMQ 这类中间件有固定的网络标识需求主从节点名。无状态服务直接用 Deployment。配置统一抽到 ConfigMap/Secret把环境变量、配置文件迁移过去避免在 Deployment 里硬编码。服务暴露方式的调整Compose 里靠ports映射宿主机端口K8s 里通常是用 Service 的 ClusterIP 供集群内访问对外用 Ingress 或 LoadBalancer。端口暴露方式要重新规划。如果项目规模相对小不想完全手工迁移可以考虑一些辅助工具。比如 Compose 官方有一个实验性的docker compose convert命令可以尝试输出 K8s 风格的 YAML 作为起点。但坦白说这类工具生成的资源往往需要手工修正尤其是存储和网络部分。我的建议是不要指望自动化转换一步到位把 Compose 文件当作需求说明书自己动手写 K8s 资源定义。你在 Compose 里写的每个环境变量、每个卷映射、每个 healthcheck都在指导你如何设计 K8s 的 ConfigMap、PVC 和探针。从这个角度看先学好 Compose 完全可以认为是学习 K8s 的前置课。8. 写在最后的经验回到开头那句话我把这套 Compose 文件发给团队后最大的变化不是“安装变快了”而是“环境不可复现”这件事彻底消失了。新同事入职第一天拉下代码、装个 Docker、执行一条命令本地环境和线上几乎一致。以前那种“在我机器上是好的呀”的对话基本绝迹。这中间我踩过的坑确实不少——数据卷忘挂载导致容器重建丢数据、healthcheck 没配导致服务启动顺序错乱、镜像用 latest 导致升级后配置失效、日志不限制导致磁盘爆掉……每一个都是血泪教训。这些内容都写在前面了希望你能一次避开。最后再分享一个小技巧永远保留一份“最小可启动版”的 compose 文件。当你往里面加新中间件的时候如果启动失败、配置有问题就先把新服务从文件中注释掉确保原有核心服务不受影响。这种“最小化变更 渐进式集成”的思路能让你在面对一堆中间件的时候保持清晰而不是一锅粥越搅越乱。Docker Compose 不是终点但它是理解现代应用部署方式的最佳起点。把这份基础打牢后面学 K8s、学 Service Mesh 都会轻松很多。
返回列表