ARTICLE DETAIL

资讯详情

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

Spring Cloud微服务Docker化部署全攻略:从镜像到Compose编排

Spring Cloud微服务Docker化部署全攻略:从镜像到Compose编排 项目上线前一周测试环境一切正常到了生产服务器上接口超时、数据库连接被拒、日志打印乱码三个服务互相指责对方的版本不对。这种场景在很多团队里都上演过。后来我们决定把Docker真正用起来从开发到部署的整条链路统一标准化才把这类问题逐步根治。这篇文章就拿我们Spring Cloud微服务项目从构建到部署的完整过程来说把Docker从安装、镜像、网络到Compose编排的核心要点拆开讲一遍。无论你是在Windows上第一次双击Docker Desktop还是已经在Linux服务器上维护着十几个容器这篇内容应该都有可以拿走直接用的部分。1. 为什么我反复跟团队强调“Docker不是虚拟机”刚接触Docker的人最容易犯的错误就是把它理解为“一个很轻的虚拟机”。如果抱着这个认知去做微服务部署后面每一个设计决策都会跑偏。我见过不少同事往容器里塞systemd、塞完整的Ubuntu桌面、甚至试图用容器跑KVM最后全都撞得头破血流。1.1 容器与虚拟机的本质区别虚拟机是通过Hypervisor虚拟出一套完整的硬件然后在上面运行一个完整的操作系统。每个虚拟机都有自己的内核、文件系统、网络栈启动要几十秒甚至几分钟占用几个GB的内存都很正常。而Docker的容器共享宿主机内核它只通过Linux的namespace和cgroup做隔离。容器里面看起来有个文件系统有进程、有网络接口但实际上所有容器都在同一个内核之上。打个比方虚拟机是每个人租了一整套独立房子水电气全部分开容器是很多人住在同一栋楼里墙壁和门是隔开的但水管、电线、地基是共享的。这个区别带来两个直接结论容器启动只需要创建进程和分配隔离空间所以秒级启动、内存占用低一台4GB的服务器跑十几个容器完全可能。容器内运行的程序必须能在宿主机内核上跑。这意味着你没法在一个Linux宿主机上跑Windows容器也没法在一个老旧的Linux内核上跑需要新内核特性的服务。1.2 从一次“在我机器上是好的”说起我们当时有个订单服务本地连的是MySQL 8.0测试环境连的是MySQL 5.7生产环境更“经典”用的是别人维护的MariaDB。结果某个SQL用了MySQL 8.0才支持的窗口函数测试阶段没人发现一到生产直接报语法错误。负责的同事第一反应是“线上环境有问题”后来一查三套环境的数据库版本都不一样。这就是微服务时代的部署困境服务数量一多每个服务的依赖版本组合呈指数级增长。三套环境、五个服务、每个服务四五个依赖谁也没法保证所有环境完全一致。Docker解决这个问题的方式很朴素把运行环境连同依赖一起打包进镜像。开发者在镜像里用MySQL 8.0QA拉同一个镜像也是MySQL 8.0生产环境直接run这个镜像跑的还是MySQL 8.0。镜像就是部署的“合同”它把运行环境从物理机和操作系统里解耦出来。1.3 微服务部署为什么离不开Docker微服务拆分之后每个服务都是一个独立进程有自己的语言运行时、依赖库、配置文件。如果用传统方式部署得在一台服务器上装好JDK、Nginx、Redis、数据库再手工拷贝各种Jar包、脚本、配置。服务器一多人肉操作必然出错。Docker给微服务带来的东西我总结成三件事标准化的交付物一个服务交付的不是“源码部署文档”而是一个镜像镜像能跑环境就到位。进程级别的隔离不同服务之间的依赖冲突被隔离A服务的Netty版本不会干扰B服务的Netty版本。快速编排与横向扩展要扩容商品服务docker compose up -d --scale product3三份实例直接拉起配合负载均衡就能分担流量。2. 环境准备Docker安装时最容易翻车的三个环节很多新手第一步就被卡住了尤其是Windows用户安装完Docker Desktop双击图标结果一直停在“Docker Desktop is starting...”转圈然后弹出一句莫名其妙的话。2.1 Virtualization support报错的完整排查链路那句经典报错是virtualization support not detected, docker desktop failed to start because virtualiztion support is disabled。这里我要先纠正一个拼写误区报错里经常出现virtualiztion这是Docker Desktop自身文案的拼写问题很多人误以为自己下错了版本其实不是。这个报错的本质是Docker Desktop需要硬件虚拟化支持但当前系统没有开启或者被其他软件占用了。排查链路按顺序来打开任务管理器切到“性能”标签看右下角“虚拟化”是否显示“已启用”。如果显示“已禁用”需要进BIOS开启Intel VT-x或AMD-V。这一步的坑在于不同主板BIOS入口完全不同有的在Advanced - CPU Configuration有的在Security - Virtualization最笨也最有效的办法是把BIOS里所有带Virtualization、VT-x、SVM字样的选项全部设为Enabled。如果BIOS里已经开启任务管理器也显示“已启用”但Docker还是报这个错检查是否装了Hyper-V相关的其他虚拟化软件比如旧版VirtualBox、VMware Workstation或者安卓模拟器。它们和Windows的Hypervisor可能冲突最典型的就是开启Hyper-V之后旧版VirtualBox没法用。Windows家庭版用户要注意Docker Desktop从某个版本开始依赖WSL2如果你的系统是旧版家庭版需要先确认Windows版本号。winver查看如果是1903以前的老版本先更新Windows再装否则直接装Docker Desktop会各种莫名其妙。当时我们团队有个同事的笔记本就是第三种情况BIOS里都开了任务管理器也显示启用但Docker Desktop怎么都起不来。最后发现他装了某款模拟器模拟器先占了Hyper-V的虚拟化层把模拟器卸载重装Docker就正常了。2.2 WSL2和Hyper-V到底选哪个Docker Desktop在Windows上有两种后端WSL2和Hyper-V。新版本默认用WSL2我建议不要改。原因很简单WSL2启动快、内存占用比Hyper-V轻而且你可以把WSL2的虚拟硬盘文件放在自定义目录避免塞满C盘。WSL2和你的日常Linux开发环境是同一个内核你在WSL里装的东西和容器共享网络栈更自然。Hyper-V后端隔离性更强适合企业强管控场景但性能开销大启动慢日常开发没必要。如果你要改后端在Docker Desktop的Settings - General里切换。切换之后Docker会重启原有容器不会丢但需要注意镜像存储位置可能变化生产环境别乱切。2.3 Linux服务器安装与镜像加速配置Linux上安装Docker很简单curl -fsSL https://get.docker.com | bash可以一键装但生产服务器我不建议这么干。更好的做法是使用发行版官方源装以Ubuntu为例sudo apt update sudo apt install ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg echo deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin这里有个细节官方源用download.docker.com在某些网络环境下访问比较慢但装的过程多等一会儿一般能完成。安装完成后执行docker info验证。如果你看到permission denied说明当前用户不在docker组里sudo usermod -aG docker $USER然后退出重新登录。这个操作很多人忘了之后每次都要sudo非常难受。镜像加速的问题也提一下。Docker Hub的访问速度在不同网络环境下差异很大docker pull经常卡住。Docker Desktop用户可以在Settings - Docker Engine里配置registry-mirrorsLinux用户则修改/etc/docker/daemon.json{ registry-mirrors: [https://你的镜像加速地址] }改完重启Docker。这一步属于Docker常规配置能显著提升拉取镜像的体验。3. 镜像与容器从MySQL 8.0部署理解Docker的核心逻辑安装好Docker之后你手上的“玩具”就是镜像和容器。我当时让团队所有人做的第一个练习不是Hello World而是用Docker部署一个MySQL 8.0并让本地项目连上它。这个练习覆盖了镜像拉取、容器运行、端口映射、数据卷、环境变量五个核心概念。3.1 镜像分层是什么为什么Dockerfile那么快Docker镜像不是一个大文件而是由很多只读层叠加而成。每一层记录文件系统的一个变更比如装了一个软件包、复制了某个配置文件。当你基于某个镜像构建新镜像时公共层是可以重复使用的不需要重新下载。这就是为什么基于同一个基础镜像构建多个服务磁盘占用不会成倍增长。也是为什么你改一行代码重新构建时只需要重新下载/构建变更的层其余层直接走缓存。Dockerfile里每一行指令都可能生成一个新的层所以指令顺序讲究“变化少的往前放变化多的往后放”。举个最简单的例子FROM openjdk:17-jdk WORKDIR /app COPY target/app.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]这里COPY target/app.jar后面不能有任何执行步骤依赖先前的缓存层否则改一次Jar包后面所有层全部失效。如果你有大量依赖安装的步骤建议把依赖声明文件先COPY进去跑完安装命令再COPY源码这样依赖层不会因为源码变化而反复重建。3.2 部署MySQL 8.0的完整命令拆解先说明这里假设你只是想快速跑一个MySQL实例给开发用生产环境还是要考虑配置、备份和高可用。命令是docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDroot123 \ -e MYSQL_DATABASEcommerce \ -v mysql_data:/var/lib/mysql \ -v /etc/localtime:/etc/localtime:ro \ mysql:8.0逐项拆解-d后台运行不加这个的话容器会挂在当前终端前台。--name mysql8给容器起一个名字后续docker logs mysql8、docker exec -it mysql8 bash都要用这个名字。-p 3306:3306把宿主机的3306端口映射到容器的3306端口。宿主机端口在冒号左边容器端口在右边。宿主机端口冲突是最常见的启动失败原因换个宿主机端口比如-p 3307:3306就能解决。-e注入环境变量。MySQL镜像用MYSQL_ROOT_PASSWORD设置root密码MYSQL_DATABASE会创建一个额外数据库。-v mysql_data:/var/lib/mysql这是把容器里的MySQL数据目录挂载到一个名为mysql_data的卷里。没有这一行容器删了数据就会丢失。启动后可以这样验证docker ps docker exec -it mysql8 mysql -uroot -proot123如果连接不上优先看日志docker logs mysql8我遇到过最典型的启动失败是docker run时报Bind for 0.0.0.0:3306: port is already allocated这就是宿主机3306端口被占用了要么停掉占用进程要么换端口。3.3 数据卷为什么容器被删数据不能跟着没了容器本身是临时性的。你可以随时docker rm -f mysql8把它删掉但删掉不等于删掉数据——前提是你挂载了数据卷。Docker的数据卷有两种主要形式命名卷-v mysql_data:/var/lib/mysqlDocker管理卷的存储位置你不用关心它具体在哪备份用docker run --rm -v mysql_data:/data alpine tar -czf /tmp/mysql_data.tar.gz -C /data .。绑定挂载-v /home/user/mysql-data:/var/lib/mysql直接映射宿主机目录好处是你可以在宿主机上直接看到数据文件适合自己管理备份策略。我在生产环境更推荐绑定挂载因为备份思路更直白把宿主机目录纳入定期备份计划即可。但要注意权限容器里的MySQL进程是以mysql用户运行的如果宿主机目录所属用户不对容器启动时会报权限错误。解决方法是先让目录对应用户有权限或者用--user参数指定UID。4. 网络不通是新手第一大敌Docker网络模型实战在所有Docker相关的报错里“网络不通”应该是最让人头疼的一类。明明是同一个宿主机上的两个容器互相ping不通明明端口映射了外部访问还是拒绝连接服务A自称启动成功服务B却说连不上数据库。这些问题的根源大多是对Docker网络模型理解不到位。4.1 bridge、host、none三种模式到底怎么选先记住一句话Docker容器默认的网络模式是bridge所有容器都在一个虚拟的NAT网络里容器之间默认是不能通过IP直接通信的需要通过端口映射或者自定义网络。bridge默认模式容器有自己的虚拟网卡和IP宿主机通过端口映射把流量转发到容器。适合单机多个服务的场景。host容器直接共享宿主机的网络命名空间没有独立IP端口也不用映射直接用宿主机端口。性能好但隔离性差两个host模式的容器不能监听同一个端口。none没有网络只能本地回环适合对网络隔离要求极高的场景日常用不到。我们当时在服务器上部署微服务网关注册到Nacos的地址是内网IP服务之间调用走的是网关转发一直强调用bridge模式配合自定义网络而不是host模式。原因很简单host模式会让每个服务直接占用宿主机端口服务数量一多端口管理就变成灾难。4.2 端口映射失败的完整排查链路有一次测试反馈说商品服务的外部地址死活访问不了页面一直转圈。这个问题的排查链路非常有代表性我建议按顺序走第一步确认容器确实在运行。docker ps -a如果容器状态是Exited先看日志docker logs product-service第二步确认容器内部端口确实正常。进入容器测试docker exec -it product-service bash curl localhost:8080/actuator/health如果这里通了说明应用本身没问题问题出在映射或外部链路。第三步确认宿主机端口映射。docker ps里看PORTS列。如果是0.0.0.0:8080-8080/tcp说明映射存在。这时用ss -tlnp | grep 8080查看端口监听情况。如果看到docker-proxy在监听说明Docker的端口转发正常。第四步检查宿主机防火墙。这一步经常被忽略。如果宿主机开了ufw或firewalld外部访问会被拦截。当时我们的问题就出在云服务器安全组只放行了80端口没有放行8080。Docker的端口映射做得再好流量在宿主机外面就被安全组拦掉了。这个排查链路的顺序很重要从内到外先确认容器内、再确认映射、最后看外部防火墙。别一上来就问防火墙容易漏掉前面更基础的环节。4.3 自定义bridge网络让服务用名字互调默认的bridge网络里容器之间虽然能互相访问但IP是动态分配的重启一次就变了。微服务之间调用如果靠IP分分钟断连。解决办法是创建自定义bridge网络docker network create commerce-net启动容器时指定网络docker run -d --name mysql8 --network commerce-net -p 3306:3306 mysql:8.0 docker run -d --name product-service --network commerce-net -p 8080:8080 product:latest在自定义网络里Docker自带DNS服务容器可以直接用名字解析到对方的IP。也就是说product-service里配置的数据库地址可以直接写jdbc:mysql://mysql8:3306/commerce不需要知道MySQL容器的IP到底是多少。这里有个反直觉的地方docker exec -it进入容器后ping mysql8是通的但如果你用docker run临时起一个容器在默认网络里它没法通过mysql8这个名字访问自定义网络的容器。每个网络是隔离的命名空间容器可以同时加入多个网络但启用时用--network指定哪个网络里进行服务发现。5. Docker Compose微服务编排的“总配电箱”当你的微服务数量超过三个一条条手敲docker run就不现实了。记不住参数、容易写错端口、环境不一致而且每多一个容器启动顺序就得靠人脑维护。这时候Docker Compose就该上场。5.1 从shell脚本失控说起我给不少团队做过咨询见过很多“看似自动化”的部署一个几百行的shell脚本里面全是docker命令、sleep 10、再docker run下一个。这种脚本一开始能跑等服务和环境多了之后改一个端口就要在一堆命令里找而且sleep等待完全是赌运气数据库还没准备好业务服务已经启动然后疯狂重试。Compose的思路非常直接把整个应用的容器配置写在一个docker-compose.yml文件里启动用docker compose up -d停止用docker compose down日志聚合用docker compose logs -f。文件即配置、配置即代码可以进版本库维护。5.2 用compose搭建MySQL与Redis主从的完整示例先看一个比较典型的开发环境Compose文件包含MySQL 8.0和Redis主从两个组件services: mysql: image: mysql:8.0 container_name: commerce-mysql restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: commerce ports: - 3306:3306 volumes: - mysql_data:/var/lib/mysql healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -proot123] interval: 5s timeout: 3s retries: 10 redis-master: image: redis:7 container_name: redis-master restart: unless-stopped ports: - 6379:6379 command: [redis-server, --requirepass, redis123] redis-slave: image: redis:7 container_name: redis-slave restart: unless-stopped depends_on: - redis-master ports: - 6380:6379 command: [redis-server, --slaveof, redis-master, 6379, --requirepass, redis123, --masterauth, redis123] volumes: mysql_data:几点经验restart: unless-stopped容器异常退出时会自动重启这个配置对线上服务非常友好。除非手动stop否则它一直保持运行。healthcheck这是Compose里容易被低估的配置。MySQL的mysqladmin ping能真实反映数据库是否可接受连接比单纯看进程活着靠谱得多。Redis从库通过--slaveof redis-master 6379指定主库这里的redis-master直接用了服务名Docker内置DNS会自动解析这就是Compose网络模型的便利之处。5.3 depends_on和healthcheck微服务最典型的“假装连不上”很多人写Compose时喜欢用depends_on控制启动顺序但如果没有配合healthcheckdepends_on的语义只是“等容器启动”而不是“等容器健康”。MySQL容器刚起来那几秒端口可能已经监听但内部InnoDB还没准备好接受连接业务服务立刻去连就会报Communications link failure。正确做法是数据库容器配好healthcheck业务服务在depends_on里加condition: service_healthyservices: product-service: image: product:latest depends_on: mysql: condition: service_healthy这样Compose会等待MySQL的healthcheck通过再启动product-service。这个改动看似小实际上解决了启动阶段大量“数据库连接被拒”的误报。5.4 环境变量与多环境配置同一个Compose文件开发环境和生产环境的数据库密码、端口可能完全不同。我的做法是把所有可变配置抽成环境变量新建.env文件MYSQL_PASSWORDprod_over_9000 EXTERNAL_PORT18080Compose文件里用${MYSQL_PASSWORD}引用。不同环境只需要准备不同的.env文件然后执行docker compose --env-file .env.prod up -d这里有个安全细节.env文件绝不能提交到代码仓库里面是生产密码。我们的做法是在仓库里放.env.example里面只有占位符实际文件在部署服务器上手工创建。6. 微服务部署实战Spring Cloud项目从Jar包到整套环境前面的基础概念都通了这一节来说最实际的一个Spring Cloud微服务项目怎么用Docker把它完整部署起来。6.1 服务拆分之后我们需要多少容器角色以我们当时一个中型电商项目为例拆成了这些角色注册中心/配置中心用的Nacos一个容器就能同时承担服务注册和配置管理。网关Spring Cloud Gateway所有外部请求的入口。业务服务商品、订单、用户三个服务各自独立镜像。中间件MySQL、Redis、RabbitMQ分别容器化。前端Nginx容器托管打包后的静态资源并反向代理到网关。一共七八个容器用脚本管理已经开始吃力所以直接上Compose统一定义整条链路。6.2 多阶段构建Dockerfile写法和Java服务优化Java微服务的镜像常见问题是打出来的镜像太大甚至包含完整的Maven构建环境。我们采用多阶段构建把编译和运行分开# 构建阶段 FROM maven:3.9-eclipse-temurin-17 AS builder WORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn package -DskipTests # 运行阶段 FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY --frombuilder /build/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -Xms256m, -Xmx512m, -jar, app.jar]经验点mvn dependency:go-offline先把依赖下载好这样后续只要源码没变构建阶段会命中缓存打包速度能快很多。运行阶段用eclipse-temurin:17-jre-alpine而不是完整的openjdk:17镜像体积能减掉一半以上。Alpine基础镜像比较小但要注意某些需要native库的组件可能在Alpine上缺少libc兼容遇到类似问题再换回标准的slim版本也行。-Xms和-Xmx限制JVM堆内存这是防止容器内存爆掉的关键一步。JVM默认按物理机内存比例设置堆大小容器里如果不显式限制很容易把整个宿主机内存吃光。6.3 用Compose拉起注册中心、网关与业务服务下面是一个简化的Compose片段重点看服务注册与发现services: nacos: image: nacos/nacos-server:v2.3.2 container_name: commerce-nacos environment: MODE: standalone NACOS_AUTH_ENABLE: false ports: - 8848:8848 - 9848:9848 gateway: image: gateway:latest container_name: commerce-gateway environment: SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR: nacos:8848 SPRING_CLOUD_NACOS_CONFIG_SERVER_ADDR: nacos:8848 depends_on: - nacos ports: - 8080:8080 product-service: image: product:latest container_name: commerce-product environment: SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR: nacos:8848 SPRING_CLOUD_NACOS_CONFIG_SERVER_ADDR: nacos:8848 SPRING_DATASOURCE_URL: jdbc:mysql://mysql8:3306/commerce depends_on: - nacos - mysql这里最核心的改动是把Nacos地址配成nacos:8848而不是localhost:8848或某个具体的IP。因为网关和业务服务都在同一个Docker网络里容器名就是主机名互相访问靠名字就可以。6.4 服务间调用为什么用容器名而不是IPSpring Cloud微服务里服务A调用服务B通常不直接写IP而是通过注册中心拿实例地址。Nacos里注册的IP是服务启动时自己上报的。容器启动时如果没配置它会默认用容器的内网IP上报。这在同一个Compose网络内没问题因为服务消费者只需要通过这个内网IP去访问而同一个自定义网络内的容器互相都能通。但要注意一个经典坑如果容器使用host网络模式启动或者Nacos和业务服务不在同一个网络里业务服务注册给Nacos的IP可能是127.0.0.1或者一个不可达的IP。这时候消费者拿到这个地址去调用必然失败。解决方法是显式指定注册IP在Spring Cloud的应用配置里设置spring: cloud: nacos: discovery: ip: 192.168.1.20在容器环境下更推荐的做法是保证所有需要互相调用的服务都在同一个自定义网络里让Docker的DNS把容器名解析成内网IP。这样既不用关心容器实际拿到了什么IP也不用在配置里显式写死宿主机IP避免换服务器就配置失效。7. 生产环境里我吃过的教训前几节讲的都是“怎么把服务跑起来”但真正的麻烦往往发生在跑了三个月之后。这一节把我在生产环境里踩过的坑集中说一遍能救一个是一个。7.1 容器不是虚拟机处理后台进程和定时任务的姿势第一次把服务容器化引入生产时我们有点太激进了把一个包含多个功能的“全家桶”服务整个塞进一个容器里面既有业务接口Java进程又有定时任务同一个Jar包的另一个启动方式还有日志采集agent。结果就是容器内多进程管理混乱Java进程挂了容器不退出定时任务日志和业务日志混在一起排查问题罪受。Docker容器的设计哲学是单进程优先一个容器最好只跑一个主进程。但实际微服务项目里定时任务和业务进程往往是同一个Jar包的不同profile。这种情况下更推荐的做法是把定时任务抽成独立服务或者用控制脚本作为容器入口你这个脚本负责启动Jar包并监控进程状态。如果真要在容器里跑多个进程借助supervisord这类进程管理器来控制启动顺序和进程守护不要裸奔。7.2 资源限制一定要给否则宿主机先挂Docker默认不限制容器资源也就是说一个容器可以无限吃宿主机内存和CPU。我们当时线上有个服务出现内存泄漏Java进程把容器内所有内存吃完然后开始消耗宿主机的内存和swap最后整台服务器OOM其他服务的容器被系统杀掉一大片。那次事故之后我们把所有容器都加上了--memory和--cpus限制docker run -d --memory1g --cpus1 product:latestCompose里对应的写法services: product-service: image: product:latest deploy: resources: limits: memory: 1g cpus: 1.0设置资源限制的好处不只是防止某容器拖垮宿主机也是给每个服务划定明确的资源预算部署之前就能估算服务器能承载多少服务。7.3 日志、镜像清理、数据备份更需要的运维习惯容器越跑越多日志和镜像占用的磁盘也会悄悄膨胀。Java服务默认往stdout打日志Docker会捕获这些输出写入json文件。如果不加轮转长期运行可能把磁盘写满。建议在daemon.json里全局配置日志轮转{ log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 } }镜像也是一个容易忽略的点。每次构建新版本docker build都会留下悬空镜像dangling image久而久之占用大量磁盘。我建议部署脚本里带上清理指令docker image prune -f docker container prune -fprune -f只清理停止的容器和无用的悬空镜像不影响正在运行的容器可以放心加进常规部署流程。数据库容器的备份比物理机裸装更需要注意容器删了重建数据不会丢前提是你挂载了卷但卷的损坏和误删同样会让数据消失。我们的备份策略是宿主机上写一个cron脚本每天用docker exec进入数据库容器执行mysqldump把备份文件导到宿主机的备份目录再同步到异地存储。容器化不改变数据备份的基本原则只是执行方式从本机命令变成了docker exec。7.4 微服务部署这条路往前走还有哪些值得关注Docker解决的是单个应用和单台机器的标准化部署问题但微服务规模大了之后你还会面对跨机器的调度、服务发现、滚动更新、自动扩缩容。我们现在的做法是Docker Compose负责单机编排加上Nginx做反向代理和负载均衡日常扩容还够用。等服务和机器数量继续涨下一步就得认真考虑Kubernetes这类容器编排平台。那时候你会发现现在学会的Docker镜像、网络、数据卷、资源限制这些基本功全都能无缝迁移过去没有白学。如果有人问我学Docker最应该记住的一句话我会说把镜像当作不可变的交付物把容器当作可丢弃的进程剩下的问题都是配置问题。配置问题用Compose管起来环境问题用镜像锁起来这才是微服务部署的核心思路。最后分享一个我们项目里的实际小技巧每次发布新版本不要直接在老的容器上docker exec改文件。正确流程是构建新镜像然后重新docker compose up -d替换容器。镜像ID变了、容器重建了但数据卷和数据不受影响。这套流程走了快一年发布时出岔子的概率比之前人肉部署低了一个数量级。
返回列表