ARTICLE DETAIL

资讯详情

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

从二进制到Docker容器化部署:迁移实战与踩坑指南

从二进制到Docker容器化部署:迁移实战与踩坑指南 从二进制脚本一把梭到 Docker 容器化部署我前后折腾了大半个月。这篇文章把整套思路、迁移步骤和踩坑过程都记下来了内容包括 Docker 部署与传统二进制部署的核心差异、镜像和网络怎么选、MySQL 和 Java 应用怎么从二进制安装平滑迁到容器里以及迁移过程中最容易遇到的网络不通、端口连不上、Windows 下 Docker Desktop 启动失败等问题。如果你正打算把手上一堆“裸奔”的服务迁到 Docker或者刚刚开始接触 Docker 部署这篇应该能帮你省下不少时间。1. 为什么我从二进制部署转向了Docker部署1.1 二进制部署的那些痛点先说背景。我之前维护的服务器上有 zabbix、MySQL、Redis、Nginx还有几个内部 Java 应用基本都是二进制安装包加手动脚本部署的。机器少的时候还好机器一多问题就全冒出来了。第一个问题是环境不一致。同样一个 Java 应用开发机器上用的是 JDK 8 某个小版本测试服务器上是另一个小版本到了生产环境又变成系统自带的 OpenJDK结果就是本地跑得好好的一上服务器就报各种奇奇怪怪的错误。还有 glibc 版本、时区、字符集、系统库依赖任何一个环节对不上服务就是起不来或者起来之后行为不对。第二个问题是升级和回滚非常痛苦。二进制部署的升级本质上就是“下载新包、停服务、覆盖文件、重启”如果新版本有问题想回滚就只能靠之前的备份而备份常常是不完整的。我记得有一次升级 Nginx忘了备份旧版本的配置文件回滚的时候只能靠印象重新写级别非常难受。第三个问题是依赖冲突。同一个目录下装了两个版本的东西、两个服务抢同一个端口、系统 Python 被某个脚本改掉了、某个动态库版本被更新导致其他服务崩溃……这些在二进制部署下特别常见。到了后期为了不动系统环境我都是把不同服务放到不同用户目录下靠环境变量和脚本隔离本质上是人为制造了“伪隔离”脆弱且难维护。第四个问题是新机器复制环境成本高。入职新同事或者新采购一台服务器要把整套服务装起来得对着自己写的部署文档一步步来。文档更新不及时装出来的环境就和线上有差异排错又得花半天时间。1.2 Docker到底解决了什么Docker 对于这些问题的解决思路核心就是“把环境和应用打包成一个镜像用容器运行”。镜像可以理解成一个“带环境的快照模板”。你负责把操作系统依赖、运行时、配置文件、应用代码都固化在镜像里然后通过 Docker 创建出来的容器就是这个模板的一个运行实例。换句话说开发、测试、生产三套环境跑的是同一个镜像环境差异被彻底抹掉了。这里要补一下 Docker 隔离的原理。容器本质上还是跑在宿主机内核上的一个普通进程它之所以能从外部表现得像一台独立的小机器是因为用到了 Linux 内核的 Namespace 和 Cgroups 两个机制。Namespace 负责“看起来像独立”比如 PID Namespace 让容器里第一个进程看到的进程 PID 是 1Network Namespace 让容器有自己独立的 IP、端口、网络栈Mount Namespace 让容器有自己独立的文件系统视图。Cgroups 负责“用起来受限”它可以限制容器最多能用多少 CPU、多少内存、多少磁盘 IO。再加上镜像底层的 OverlayFS 分层存储机制多个容器可以共享同一个镜像的底层只读层只有被修改的部分才会写入自己的可写层这就实现了“模板共享、实例隔离”。用生活里的例子类比镜像就像一张菜的标准化配方和半成品包厨房里每个厨师都按这个配方做做出来的菜口味一致容器就是做出来的一份菜。不同厨师之间互不干扰配方升级了所有菜的口味一起升级这就是 Docker 部署相比二进制部署的底层优势。所以当我说“把服务迁到 Docker”本质上是在改变服务的交付方式从“搬运一堆文件和一个启动脚本”变成“搬运一个镜像、用一条命令启动”。2. Docker部署方案怎么选镜像、数据卷与网络模式2.1 镜像选型官方镜像、轻量镜像与版本锁定很多人刚开始用 Docker 的时候有个习惯直接docker pull mysql或者docker pull nginx这种不带 tag 的写法拉下来的是 latest也就是当前最新版本。用 latest 在初期很方便但在生产环境里是大忌。原因有两个一是 latest 会滚动更新下次 pull 到的内容可能和上次不一样环境就不可复现了二是最新版本往往意味着行为变更比如 MySQL 从 5.7 升到 8.0 之后认证插件变了很多老的客户端直接连不上。所以我的建议是生产环境的镜像一定要锁定具体版本号比如mysql:8.0.36、redis:7.2.4、nginx:1.25.4。镜像体积也要关注。以 Nginx 为例官方镜像基于 Debian体积在 100MB 以上而nginx:alpine只有 30MB 左右。体积小不仅节省磁盘更重要的是拉取快、启动快、被攻击面小。但 alpine 镜像用的是 musl 库而不是 glibc如果你的服务里有依赖 glibc 特性的二进制程序比如很多 Java 的 native 库、部分编译好的 Python 扩展在 alpine 上跑会出问题。稳妥的做法是官方镜像优先不带特殊依赖的用 alpine 版有复杂依赖的应用先用 Debian/Ubuntu 基础镜像。另外还要注意Java 应用不要随便挑镜像。Spring Boot 应用我建议直接用eclipse-temurin或amazoncorretto这种专门的 JDK/JRE 镜像带标准的 glibc 和时区数据。早期我试过自己从 Ubuntu 18.04 开始安装 JDK 做镜像结果是镜像又大又难维护后来彻底换成现成的运行时镜像。2.2 数据要持久化吗volumes与bind mount的选择这是把二进制部署迁到 Docker 时最容易搞错的一点容器是有生命的删除容器不等于删除容器里写的文件吗不对Docker 容器的可写层是临时性的容器删除后可写层数据不可恢复。所以对于 MySQL、Redis、Nginx 日志、应用上传的文件必须做数据持久化。Docker 提供了两种方式bind mount 和 volume。bind mount 是直接把宿主机的一个目录挂到容器里的一个路径比如-v /data/mysql:/var/lib/mysql宿主机/data/mysql里有什么容器/var/lib/mysql里就有什么。它的优点是直观数据文件直接用宿主机工具备份和查看迁移时直接拷贝目录即可。缺点是宿主机目录的权限和容器内进程的权限可能不一致这也是后面我会说的“权限坑”的来源。volume 是 Docker 自己管理的存储区域通过docker volume create mydata创建然后用-v mydata:/var/lib/mysql挂载。它的好处是 Docker 帮你管理权限和生命周期性能也更好一些但数据对宿主机来说是隐藏的备份和查看都要通过临时容器或docker run -v data_volume:/volume alpine ls /volume这样的方式。我个人的取舍标准很简单数据库数据、Redis 持久化数据、Elasticsearch 数据用 volume因为它们是“纯数据”不需要也不想让宿主机直接操作配置文件、日志文件、上传目录用 bind mount因为运维需要经常查看和修改。两种方式可以混用比如 MySQL 就完全可以把数据放 volume、把配置放 bind mount。注意不管用哪种方式都要在第一次启动容器之前就规划好挂载路径否则已经在容器里写了一遍数据再挂载目录会出现“旧数据看不到、新数据写不到”的混乱局面。2.3 网络模式bridge、host与容器间通信Docker 的默认网络模式是 bridge也就是桥接模式。每个容器会有一个独立的网络命名空间、一块虚拟网卡、一个由 Docker 分配的 IP 地址通过 NAT 访问外网。宿主机要访问容器里的服务需要做端口映射比如-p 3306:3306把宿主机的 3306 端口转发到容器的 3306 端口。bridge 模式的问题是容器 IP 不固定重启容器后 IP 可能变化所以容器之间不应该互相用 IP 通信而应该通过 Docker 的 DNS 解析服务名。具体做法是创建一个自定义 bridge 网络docker network create app-net然后启动容器时都加上--network app-net这样容器之间就可以直接用服务名也就是容器名互相访问。比如一个 Java 应用容器里连接 MySQLJDBC 的地址可以直接写成jdbc:mysql://mysql-container:3306/dbnameDocker 内置的 DNS 会自动把这个名字解析成对应容器的当前 IP。还有两种模式需要留意。host 模式下容器不隔离网络直接使用宿主机的网络栈启动容器时不需要也不能做端口映射对外访问效率高适合性能敏感或者对端口数量要求多的场景比如一个大端口列表的服务。但 host 模式牺牲了网络隔离端口冲突的风险回到了宿主机层面我这里只有特殊的监控采集组件才用它。容器与容器之间的“localhost”是一个经典坑。如果在容器 A 里访问容器 B 上的 MySQL不要写localhost:3306因为 localhost 在这个语境下指的是容器 A 自己而不是宿主机或容器 B。正确做法是写容器 B 的 IP不推荐IP会变或者自定义 bridge 网络下的服务名推荐。而如果只是从宿主机访问容器里面的 MySQL用127.0.0.1:3306配合端口映射就行。3. 二进制部署转Docker部署的完整实操3.1 迁移前盘点服务清单与依赖梳理迁移的第一步不是敲命令而是做盘点。我习惯先建一张表把要迁移的服务一项项列清楚信息至少包括这些服务名版本对外端口配置文件路径数据目录启动方式依赖关系是否有状态MySQL8.0.363306/etc/mysql/my.cnf/var/lib/mysqlsystemd无有状态Redis7.2.46379/etc/redis.conf/data/redissystemd无有状态app-serviceJava 88080/opt/app/config//opt/app/logs启动脚本MySQL、Redis无状态Nginx1.25.480/443/etc/nginx/网站静态文件systemd前端资源弱状态这张表的价值在于帮你想清楚两件事。第一服务的“状态”在哪里。数据库的持久化数据、Redis 的持久化文件、Nginx 的静态资源是状态迁移时要重点保证它们不丢一个 Java 应用如果只是处理请求、写日志没有本地持久化数据那它就是无状态的迁移时可以随时重建。第二服务的依赖关系必须是明确的否则后面编排 Docker Compose 的时候启动顺序乱了服务之间互相连不上排查起来很费劲。盘点完成以后我建议按优先级分批迁移先迁无状态的 Nginx 和 Java 应用练手和搭建 CI 流程再迁 Redis 这类缓存最后迁 MySQL 这种有核心数据的数据库。数据库迁移对停机时间有要求放到最后稳妥一点。3.2 MySQL迁移从二进制安装到Docker容器把 MySQL 从二进制安装迁到 Docker最核心的原则是数据不能丢。我的完整流程如下。第一步备份旧数据。如果是 5.7 往 8.0 迁直接用mysqldump导 SQL 比较保险因为它能把逻辑结构重放一遍避免二进制格式不兼容。如果版本一致比如都是 8.0可以直接拷贝数据文件速度很快但要求 MySQL 能干净停机systemctl stop mysql cp -a /var/lib/mysql /data/mysql-backup-20240120这个cp -a会保留文件属主和权限很重要。如果直接cp -r数据文件属主变了后续启动可能直接失败。第二步规划宿主机目录。我习惯把配置和数据分开mkdir -p /data/mysql/conf mkdir -p /data/mysql/data第三步准备配置文件。二进制部署下 MySQL 的my.cnf一般包含比较多的配置段落迁到 Docker 时不需要整个搬只要把差异化的部分做成my.cnf.d下的额外文件。比如我把字符集、时区、binlog 保留时间等单独写成一个文件[mysqld] character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci default-time-zone08:00 log-bin/var/lib/mysql/mysql-bin binlog-expire-logs-seconds604800放到/data/mysql/conf/zz-custom.cnf然后挂载到容器里的/etc/mysql/conf.d/目录。这样镜像自带的默认配置还在我的自定义配置只做增量覆盖。第四步启动容器。MySQL 官方镜像是数据文件在/var/lib/mysql配置文件在/etc/mysql/。启动命令如下docker run -d \ --name mysql8 \ --network app-net \ -p 3306:3306 \ -e TZAsia/Shanghai \ -e MYSQL_ROOT_PASSWORDYourPassword \ -v /data/mysql/conf:/etc/mysql/conf.d \ -v mysql-data:/var/lib/mysql \ --restart always \ mysql:8.0.36这里解释几个关键参数。-e MYSQL_ROOT_PASSWORD是首次初始化时设置 root 密码用的如果/var/lib/mysql已经有数据了这个参数会被忽略直接用原来的密码。--restart always保证了宿主机重启后 MySQL 容器自动启动弥补了 systemd 的自动重启能力。-v mysql-data:/var/lib/mysql用了一个名为mysql-data的 volume 来存放数据。第五步如果迁移前直接拷贝了数据文件需要修权限。MySQL 官方的 Docker 镜像里mysqld 进程是以 uid 999 的用户运行的。旧的数据目录如果属主是系统里的mysql用户通常 uid 不是 999容器里就写不进去。解决办法是chown -R 999:999 /data/mysql/data这个步骤用语言很难想起来一旦忘了容器会反复启动失败日志里全是权限报错。我自己第一次迁移就卡在这里半小时。第六步把原有业务账号从 SQL 迁移过来。用mysqldump导出时mysql库里的用户表也需要导出或者直接在新容器里重新创建账号。注意 8.0 默认的认证插件是caching_sha2_password如果业务端是老的 MySQL 客户端连接可能会报Authentication plugin caching_sha2_password cannot be loaded这种时候需要把老账号的认证方式改回mysql_native_passwordALTER USER app_user% IDENTIFIED WITH mysql_native_password BY password;整体迁移完成后跑一遍旧环境上的核心 SQL对比结果集是否一致然后才能切流量。3.3 业务应用迁移用Dockerfile把Java应用容器化Java 应用Spring Boot是二进制转 Docker 的最典型场景因为它“装环境”最烦JDK 版本、时区、编码、JVM 参数、日志路径每个都是坑。但它的逻辑其实很统一打成一个可执行 jar然后跑java -jar。我现在的做法是 multi-stage 构建也就是一个 Dockerfile 里分阶段第一阶段负责编译打包第二阶段只复制 jar 出来放到良好的运行环境里。好处是最终镜像里没有 Maven、没有源码、没有依赖缓存体积能小很多。先写一个Dockerfile# 第一阶段编译 FROM maven:3.9-eclipse-temurin-8 AS builder WORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn package -DskipTests # 第二阶段运行 FROM eclipse-temurin:8-jre ENV TZAsia/Shanghai RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime echo Asia/Shanghai /etc/timezone WORKDIR /app COPY --frombuilder /build/target/app.jar ./app.jar EXPOSE 8080 ENTRYPOINT [java, -server, -Xms512m, -Xmx1024m, -Djava.security.egdfile:/dev/./urandom, -jar, app.jar]这里面有几个经验点。一是 JVM 内存参数-Xms512m -Xmx1024m要结合容器内存限制来定不要无脑调大。如果你用docker run --memory2g那 JVM 最好限制在 1.5g 以内给系统和容器留点余量否则 OOMKilled 是跑不掉的。二是-Djava.security.egdfile:/dev/./urandom这是很多生产环境都踩过的问题。JVM 默认的熵源如果不够Java 应用启动时在生成安全随机数阶段会卡住很久表现为“启动很慢”。指定这个参数之后启动速度显著变快。三是日志问题。二进制部署下 Java 应用一般写文件日志迁到 Docker 后我建议改成输出到 stdout然后用 Docker 的日志驱动统一收集。原因很简单文件日志需要再挂载目录、再处理日志轮转而输出到 stdout 的话docker logs直接能看配合日志系统收集也更方便。具体做法是在logback.xml/log4j2.xml里写一个 Console appenderFile appender 可以不要了。构建镜像docker build -t app-service:20240120 .运行容器docker run -d \ --name app-service \ --network app-net \ -p 8080:8080 \ -e SPRING_PROFILES_ACTIVEprod \ --memory2g \ --restart always \ app-service:20240120注意这里我用环境变量SPRING_PROFILES_ACTIVE来切换环境配置值不要写死在镜像里。Spring Boot 应用特别适合这么干因为它的配置文件天然支持环境变量替换。数据库连接地址直接写jdbc:mysql://mysql8:3306/dbname这里的mysql8就是刚才 MySQL 容器的名字。3.4 多服务编排用Docker Compose替代手工启动服务一多一条条docker run就管理不过来了。我现在基本都用 Docker Compose它是 Docker 官方提供的多容器编排工具用一个 YAML 文件描述一组服务一条命令整体启动。写一个docker-compose.yml示例把 MySQL、Redis、Java 应用和 Nginx 组合起来services: mysql: image: mysql:8.0.36 container_name: mysql8 restart: always environment: - TZAsia/Shanghai - MYSQL_ROOT_PASSWORDYourPassword volumes: - /data/mysql/conf:/etc/mysql/conf.d - mysql-data:/var/lib/mysql ports: - 3306:3306 healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -pYourPassword] interval: 10s timeout: 5s retries: 5 redis: image: redis:7.2.4 container_name: redis restart: always command: [redis-server, /usr/local/etc/redis/redis.conf] volumes: - /data/redis/redis.conf:/usr/local/etc/redis/redis.conf - redis-data:/data ports: - 6379:6379 app: build: . image: app-service:latest container_name: app-service restart: always environment: - SPRING_PROFILES_ACTIVEprod ports: - 8080:8080 depends_on: mysql: condition: service_healthy redis: condition: service_started nginx: image: nginx:1.25.4 container_name: nginx restart: always volumes: - /data/nginx/conf.d:/etc/nginx/conf.d - /data/nginx/html:/usr/share/nginx/html - /data/nginx/certs:/etc/nginx/certs ports: - 80:80 - 443:443 depends_on: - app volumes: mysql-data: redis-data:这里重点说depends_on和healthcheck的配合。早期我用 Compose 的时候depends_on只是控制启动顺序不保证 MySQL 已经启动完成。Java 应用在 MySQL 还没就绪时就尝试连接结果连接池初始化失败整个应用起不来。解决方式就是给 MySQL 加healthcheck然后在depends_on里写condition: service_healthy这样 Compose 会等 MySQL 通过健康检查后再启动应用。Compose 常用命令就这几个docker compose up -d # 启动所有服务 docker compose ps # 查看状态 docker compose logs -f app # 查看某个服务的实时日志 docker compose exec app sh # 进入容器 docker compose down # 停止并删除容器数据卷默认保留有一点要提醒docker compose down默认不会删除 volume数据还在。但如果你手抖加了-v数据卷会被一并删除。生产环境我建议在 Compose 文件里把重要的数据卷都声明为external: true这样 Compose 不会自动管理它们的生命周期误删风险低很多。4. 迁移过程中我踩过的坑与解决办法4.1 docker网络不通容器连不上外网这是新手最常碰到的问题容器创建成功了但容器里ping www.baidu.com不通apt update也失败。原因一般出在以下几个方面。第一DNS 配置不对。容器默认用的 DNS 是宿主机/etc/resolv.conf里的配置如果宿主机的 DNS 设置本身有问题或者有防火墙拦截了 UDP 53 端口容器解析域名就会失败。解决办法是可以给容器显式指定 DNSdocker run --dns 223.5.5.5 --dns 8.8.8.8 ...或者修改 Docker daemon 的配置在/etc/docker/daemon.json里加{ dns: [223.5.5.5, 8.8.8.8] }然后重启 Docker。第二宿主机 iptables 规则冲突。Docker 依赖 iptables 做 NAT 转发如果系统里有其他防火墙软件比如 firewalld 或 ufw覆盖了 Docker 的 iptables 规则容器就出不了网。我踩过一次是在 Ubuntu 上同时装了 ufw 和 Docker默认情况 ufw 会 drop 掉 FORWARD 链的流量。解决思路不是永久关闭防火墙而是理清规则顺序把 Docker 自己维护的链和防火墙规则融合到一起。最简单的排查方法iptables -t nat -L -n | grep 172.17如果看到MASQUERADE规则缺失大概率就是 Docker 的 NAT 规则被清了。生产环境建议查 Docker 官方文档中关于 iptables 和防火墙的章节不同系统的兼容处理方式略有差异。第三容器内网络栈异常。查看容器状态docker exec -it 容器名 sh ip addr # 看容器里有没有IP ip route # 看默认路由如果容器里连 IP 都没有说明容器网络创建失败可以docker network prune清理掉不用的网络然后重启容器。注意容器里ping不通不代表业务不通。很多精简镜像比如 alpine 官方版甚至没有安装 ping 命令所以排查网络应该先用curl或wget实测端口连通性再查底层网络。4.2 容器里的MySQL/Redis连不上“从宿主机访问容器里面的 MySQL 连不上”是我看到频率最高的问题它至少有几种不同的原因。一是端口映射没做或者做错了。容器里的 MySQL 监听的是 3306 端口如果启动命令里没有-p 3306:3306那宿主机是访问不到容器 MySQL 的。到这一步要检查docker port mysql8输出应该像这样3306/tcp - 0.0.0.0:3306如果没有输出说明没映射成功。二是 MySQL 自己监听的地址不对。默认情况下 MySQL 监听的是*所有网卡但有些配置文件里写了bind-address 127.0.0.1这就导致即使宿主机端口映射成功容器内部的 MySQL 也只接受来自容器自己的连接。这种问题在二进制部署时代也常见迁移到容器后因为网络视角变了更隐蔽。排查方法docker exec -it mysql8 mysql -uroot -p -e show variables like bind_address;如果值是127.0.0.1改成0.0.0.0再重启。三是宿主机防火墙把端口拦了。即使 Docker 端口映射成功如果宿主机 iptables/firewalld 拦截了对 3306 端口的访问外部依然连不上。用telnet 127.0.0.1 3306测试本机连容器是通的再从另一台机器测试就不通那大概率就是防火墙问题。四是客户端版本和认证插件不兼容。MySQL 8.0 默认caching_sha2_password认证老版本客户端比如 mysql 5.7 的客户端、老版本的 JDBC 驱动会报认证错误。解决办法前面已经写过把用户认证方式改回mysql_native_password或者升级客户端驱动。Redis 连不上的情况简单一些因为 Redis 默认只监听127.0.0.1容器端口映射后宿主机能连容器了但容器里的 Redis 只接受容器自己所以必须显式开启redis-server --bind 0.0.0.0同时注意protected-mode yes会在有密码或网络绑定时才放行迁移到容器后建议设置requirepass。4.3 Windows上Docker Desktop启动失败Windows 上跑 Docker 依赖虚拟化技术常见报错是Docker Desktop failed to start because virtualization support wasnt detected.这个报错信息非常误导人我一开始以为电脑 CPU 不支持虚拟化查了一通才发现多半是以下三种情况。第一种BIOS 里没开虚拟化。重启进 BIOS找 Intel VT-x 或 AMD-V 相关选项改成 Enabled。品牌机不同选项名称不太一样但基本都是 “Virtualization Technology” 或 “SVM Mode”。第二种没启用 Windows 的虚拟化平台功能。需要在“启用或关闭 Windows 功能”里勾选 “Hyper-V” 和 “适用于 Linux 的 Windows 子系统”。Docker Desktop 现在推荐用 WSL 2 后端所以要确保 WSL 2 可用wsl --set-default-version 2 wsl --update第三种虚拟机监控程序被其他产品占用。比如之前装过 VirtualBox如果 VirtualBox 的 Hyper-V 兼容模式没开会和 Docker Desktop 冲突。反过来某些安全软件、沙箱工具也会禁用 Hyper-V。处理方式是卸载无关虚拟机软件或者关掉 Windows 功能里的旧版 Hyper-V 再重新启用。Windows 桌面场景下我个人的建议是如果纯做开发用 Docker Desktop 最省事如果是在 Windows 服务器上折腾生产服务别用 Hyper-V 后端直接用 Docker Engine 的 Windows 容器模式或者干脆上 Linux 服务器。4.4 容器数据丢失与权限问题“容器删掉之后数据全没了”这个故事每天都在发生。我自己也经历过第一次用 Docker 跑 MySQL没挂载数据卷后来为了更新配置docker rm删掉容器整个库数据直接蒸发。那一刻的感受很难形容但确实让我把“先规划卷、再启动容器”刻进了骨头里。从二进制迁移到 Docker 时还有另一个容易忽略的点旧数据目录的权限。前面提到的chown -R 999:999就是一个典型。不同镜像内部的用户 uid 不同一定要通过镜像文档弄清楚。比如 MySQL 的 uid 是 999Redis 的 uid 是 999但 PostgreSQL 的 uid 是 70Nginx 是 101。挂载目录的属主如果和容器内进程的用户不匹配容器启动就会失败。还有一个细节是文件属主权限的umask问题。如果你用 bind mount 挂载应用日志目录容器内进程写的文件属主可能和宿主机当前用户不同导致宿主机上一些脚本无法清理旧日志。解决方法是把挂载目录的属主统一改成容器用户或者镜像的 entrypoint 里加chown操作。5. 迁移完成后的运维习惯变化二进制部署转 Docker 部署最后给我带来的最大变化其实不在技术栈本身而是运维习惯。原来我维护一个服务脑子里记的是“配置文件在哪、启动命令是什么、日志在哪个目录”。现在容器化之后我的关注点变成了“镜像是什么版本、数据卷挂在哪、环境变量怎么注入”。这个变化看似简单实际影响很深。版本管理上镜像有了明确 tag升级就是改 tag 再docker compose up -d回滚就是改回旧 tag 再启动真正做到秒级回滚比原来先备份再换文件的那套流程快太多了。配置管理上现在所有配置都能过环境变量或者挂载文件不会再出现“生产环境的配置只有某个人电脑上有”这种历史遗留问题。日志管理上容器 stdout 日志统一接管配合日志采集工具直接检索比登服务器tail -f强出不止一个量级。我个人现在给团队定的一个原则是所有新服务必须走 Docker 部署除非有明确理由比如涉及内核模块、无法容器化的裸金属设备驱动否则一律镜像化。旧服务分批迁移每次迁完都留下迁移文档和回滚预案。按这个节奏半年内把团队维护的二十多个服务全部容器化了再没有出现过“新环境装两天、线上环境多个包”这种破事。最后分享一个小经验迁移过程中一定要先做一次完整的“新环境从零部署演练”。找一台干净的机器拉镜像、跑 Compose、导入备份数据把整个流程走一遍确认每个步骤都有记录。很多人迁移失败不是因为 Docker 不行而是对旧系统的理解不够演练一遍能暴露出所有隐藏依赖。经历过这一轮你才会真正理解 Docker 部署带来的收益它把“环境”变成了可携带的资产而不是绑定在某台服务器上的诅咒。
返回列表