ARTICLE DETAIL

资讯详情

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

Docker实战:从容器化部署到环境一致性,彻底告别“本地能跑线上崩”

Docker实战:从容器化部署到环境一致性,彻底告别“本地能跑线上崩” 咱们搞开发的人谁没被“我本地能跑线上崩了”这句话坑过说多了都是泪。我当年带项目的时候光这句话就听了不下百遍每次一听就头皮发麻。环境变量不一样、系统依赖缺失、版本对不上、权限配置五花八门随便一个都能让线上直接红屏。后来我把团队整个切到 Docker 上这句话才彻底从我们这儿绝迹了。Docker 说白了就是一套“集装箱式”的软件打包方案。你把你应用的代码、运行环境、依赖、配置全部塞进一个容器镜像里不管到哪儿拉下来跑就是一模一样的东西。它能干的事儿太多了本地开发、CI/CD 打包、微服务部署、数据库搭建、甚至跑各种中间件基本覆盖了日常开发的 80% 场景。这篇我不讲什么高深理论就用做项目的视角把 Docker 怎么用、为什么能解决“本地能跑、线上崩”以及实操中那些坑一次给你捋清楚。1. 先搞清楚Docker 到底解决了什么痛点1.1 “我本地能跑”背后的隐性成本很多刚接触 Docker 的人会觉得这玩意儿不就是个虚拟机吗还真不是。咱们先不扯底层原理就说一个最扎心的事实你本地能跑是因为你本地那套环境是你自己亲手调出来的。比如你用 Windows 开发代码里用了 Linux 的路径或者调了个系统级命令线上服务器是 Linux跑不起来很正常再比如你本地 Node 是 16线上是 12某个 API 用法两个版本行为不一样线上直接挂还有更隐蔽的你本地装了一堆全局包代码里写漏了依赖线下碰巧能跑到线上全新环境一装依赖就跑不起来。这些问题本质上不是代码逻辑的问题而是环境一致性的问题。传统解决方式是什么写部署文档。但部署文档这种东西更新频率永远追不上代码变更而且每个开发者的理解能力还不一样。有人按文档装好了有人装到一半就放弃了。就算文档写得再细操作系统版本差异、防火墙规则、网络政策这些都会让文档变成废纸。1.2 容器化是怎么把环境“封印”起来的Docker 的核心思路特别朴素把你应用需要的一切包括代码、运行时、系统库、配置文件全部打包成一个镜像。这个镜像就是一个只读模板你在上面创建容器来运行。容器跑起来之后你访问的是它里面的那一套独立环境跟你宿主机上有啥半点关系没有。我打个比方你就懂了。以前交付软件就好比把一堆零件和图纸给客户让客户自己找工具、自己找场地、自己组装出问题了你得远程指导。用 Docker 之后相当于你直接交付一个封装好的整体模块里面所有的零件、工具、燃料都备齐了客户只需要插上电就能跑。这个“整体模块”就是镜像跑起来就是容器而 Docker 就是那台负责制造和调度模块的机器。1.3 环境一致性的价值远超部署本身很多人以为 Docker 的好处仅仅是部署省心其实环境一致性带来的连锁反应多得多。比如新同事入职以前要花一整天配环境现在拉个镜像五分钟进开发环境比如出了线上 bug以前要抓现场数据、猜测复现步骤现在直接用线上同一个镜像在本地容器里跑稳定复现比如多环境隔离以前一台机器上要装多个版本的 JDK、MySQL、Redis互相污染全凭运气现在每个容器独立运行版本随便切换。我团队里现在有个铁律线上跑的不是某个服务器上的进程而是某个镜像的一个实例。一切以镜像为准任何人提出的“帮我看看服务器上环境”这种需求一律按不会操作处理。这么坚持半年你会发现“本地能跑”这句话基本就消失了因为大家跑的本来就是同一个东西。2. Docker 三件套镜像、容器、仓库怎么配合2.1 镜像只读模板的制作逻辑镜像里边的内容是分层存储的这是 Docker 一个特别关键的设计。每一层都是只读的Dockerfile 里每一条指令对应一层。比如你写FROM mysql:8.0这就是基础层ENV MYSQL_ROOT_PASSWORDxxx这又是一层COPY拷贝配置又是一个新层。因为分层复用同一个基础镜像可以被很多上层镜像共享磁盘占用和构建时间都大幅减少。构建镜像用 Dockerfile我给你们一个最简单的示例构建一个基于 Nginx 的静态网站FROM nginx:1.27-alpine COPY ./dist /usr/share/nginx/html COPY ./nginx.conf /etc/nginx/nginx.conf EXPOSE 80这里我特意选了 alpine 变体比默认的 nginx 镜像小很多因为 alpine 是精简版 Linux体积可能只有默认镜像的一半左右。对于线上部署镜像越小推拉速度越快被攻击面也越小。后面我会专门讲怎么给镜像瘦身。2.2 容器镜像运行时的隔离空间容器是镜像运行时的实例。同一个镜像可以同时创建多个容器它们之间互不影响就像同一个软件开多个窗口每个窗口是独立会话。容器可以启动、停止、删除但改动不会写进镜像里。如果你在容器里改了文件然后删掉这个容器改动就没了除非你先把改动提交成新镜像或者挂载宿主机目录。这也是很多人新手期踩坑的地方容器删了就一切归零。所以数据库这类有状态服务必须把数据目录挂到宿主机上否则容器一重启数据就没了。比如跑 MySQL 的时候挂载/var/lib/mysql目录跑 Redis 的时候挂载 dump 文件目录这是保命操作。2.3 仓库分发镜像的中央枢纽镜像不是只能存在本地机器上仓库就是集中存放和分发镜像的地方。Docker Hub 是官方公共仓库里面有海量现成镜像从操作系统到数据库到各种中间件都有。公司内部一般会搭私有仓库比如 Harbor用来存自己构建的镜像控制访问权限也避免从公共仓库拉取的安全风险。平时用命令的时候从公共仓库拉镜像实际上就是docker pull 名称:标签。标签版本号特别重要比如mysql:8.0和mysql:latest后者看起来很省事但latest是滚动更新的今天和明天拉的可能不是一个版本。生产环境我强烈不建议用latest最好把版本号固定死这样出了任何问题都能精准追溯到到底是哪个版本的镜像。3. 环境准备与安装Windows、Linux 全流程3.1 Windows 上安装 Docker Desktop 的步骤Windows 平台最省心的方案就是 Docker Desktop它自带图形界面还能直接映射端口和查看容器日志。装之前先确认两件事一是 CPU 虚拟化要在 BIOS 里开启也就是 Intel VT-x 或 AMD-V二是 Windows 功能里要启用 Hyper-V 或者使用 WSL 2 后端。现在 Docker Desktop 默认推荐 WSL 2性能和兼容性都更好。安装步骤不复杂去官网下 Docker Desktop 安装包一路下一步就行。装完打开它能自动配置好 Docker Engine。你在命令行里敲docker version能同时看到客户端和服务端版本号就说明装好了。注意一个特别常见的坑很多人的报错是“Virtualization support not detected”或者“Docker Desktop failed to start because virtualization support wasnt detected”这基本就是虚拟化没开启去 BIOS 里把 VT-x/AMD-V 打开即可或者确认 Windows 的“虚拟机平台”功能是否启用。如果装的是旧版本还可能出现 WSL 内核版本过旧的问题。解决办法是在 PowerShell 里运行wsl --update更新 WSL然后重启 Docker Desktop大部分都能解决。3.2 Linux 上安装 Docker Engine 的正确姿势Linux 服务器一般不需要 Docker Desktop直接装 Docker Engine。以 Ubuntu 为例官方推荐先卸载旧的包再设置官方仓库然后安装。其实还可以用官方的一键脚本但我不太推荐生产环境用。因为一键脚本会拉取最新版且不好追踪安装源出现问题难以排查。这里我给你们梳理一遍手动安装的完整流程# 1. 更新 apt 并安装依赖 sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg # 2. 添加 Docker 官方 GPG 密钥 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 sudo chmod ar /etc/apt/keyrings/docker.gpg # 3. 添加 Docker 仓库 echo \ deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release echo $VERSION_CODENAME) stable | \ sudo tee /etc/apt/sources.list.d/docker.list /dev/null # 4. 安装 Docker Engine sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin装完之后记得把当前用户加入 docker 组不然每次执行 docker 命令都要 sudo烦死你。sudo usermod -aG docker $USER newgrp docker注意做完这步要重新登录终端才生效。如果加了组还是不生效直接重启系统别在那干瞪眼。3.3 镜像源配置解决拉取慢的千古难题在国内拉 Docker Hub 镜像慢是常态有些镜像甚至拉不下来。这个问题的核心是网络链路问题解决办法就是配置镜像加速器。Docker Desktop 和 Linux Docker Engine 的配置位置略有不同但逻辑一样。在 Docker Desktop 里通过设置界面里的 Docker Engine 选项直接编辑 JSON 配置加上 registry-mirrors 字段。在 Linux 上编辑/etc/docker/daemon.json同样加这个字段{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com ] }配好之后重启 Docker 服务sudo systemctl restart docker在 Docker Desktop 里直接重启 Docker Desktop 即可。之后拉镜像就会自动从加速器走速度通常能提升不少。这里要提醒一句镜像加速器属于公共基础设施稳定性受外界因素影响不同时间段效果可能不一样。如果某个加速器失效了换一个试试就行。4. 实战演练用 Docker 部署 MySQL 和 Redis4.1 部署 MySQL 8.0从拉镜像到配置调优我先拿 MySQL 8.0 举例因为这是最典型的有状态服务。直接跑容器会踩到数据持久化和字符集两个大坑我一步一步来演示。第一步拉取镜像。这里我明确指定版本号8.0.36不带 latestdocker pull mysql:8.0.36注意MySQL 官方镜像有个特性容器首次启动时会自动执行/docker-entrypoint-initdb.d目录下的 .sql 脚本。这个设计很巧妙你可以在宿主机上准备好初始化脚本挂载进这个目录容器跑起来就会自动初始化表结构和测试数据。比如我建一个/data/init.sqlCREATE DATABASE IF NOT EXISTS app_db DEFAULT CHARACTER SET utf8mb4; CREATE USER app_user% IDENTIFIED BY AppPassw0rd; GRANT ALL PRIVILEGES ON app_db.* TO app_user%; FLUSH PRIVILEGES;然后跑容器MySQL 的关键参数一次说清docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDRootPassw0rd \ -e TZAsia/Shanghai \ -v /data/mysql:/var/lib/mysql \ -v /data/init.sql:/docker-entrypoint-initdb.d/init.sql:ro \ -v /etc/mysql/conf.d:/etc/mysql/conf.d \ --restartalways \ mysql:8.0.36这里每个参数都有讲究。-d是后台运行--name给容器起名字-p 3306:3306把宿主机的 3306 映射到容器的 3306前面是宿主机端口后面是容器端口。-e是环境变量MYSQL_ROOT_PASSWORD 指定 root 密码TZ 指定时区。-v是卷挂载第一个挂载把数据落到宿主机 /data/mysql第二个挂载把初始化脚本塞进去第三个挂载挂配置目录。--restartalways让容器挂了自动重启线上必备。但是只这样跑还不行mysql 官方镜像默认字符集可能不是 utf8mb4而 utf8mb4 才是能完整支持中文和表情字符的字符集。我在第三个挂载目录里放一个配置文件my.cnf[mysqld] character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci default-time-zone08:00 max_connections500容器启动后会读取这个配置比手动进去改省事得多。跑起来之后用docker exec -it mysql8 mysql -uroot -p进入容器内的 MySQL 客户端执行SHOW VARIABLES LIKE character%;验证字符集。整个过程完全不需要你在宿主机上装 MySQL这也就是容器化最大的舒服之处。4.2 部署 Redis单机到主从一次打通Redis 部署比 MySQL 简单因为它是内存数据库持久化方式不同。单机部署很直接docker run -d \ --name redis7 \ -p 6379:6379 \ -v /data/redis/data:/data \ -v /data/redis/redis.conf:/etc/redis/redis.conf \ --restartalways \ redis:7.2.4 redis-server /etc/redis/redis.conf这里我直接把宿主机上的 redis.conf 挂载进去让容器用自定义配置启动。注意最后面的参数它会把镜像默认的启动命令替换成redis-server /etc/redis/redis.conf这样容器启动时会加载我们自定义的配置。Redis 主从同步是经典场景比如一主一从。原理特别简单从节点连上主节点之后发送 SYNC 命令主节点把数据全量传给从节点之后增量同步。我给你们一个 docker compose 文件一次性把主从都拉起来services: redis-master: image: redis:7.2.4 container_name: redis-master command: redis-server --requirepass MasterPassw0rd ports: - 6379:6379 volumes: - /data/redis-master:/data restart: always redis-slave: image: redis:7.2.4 container_name: redis-slave command: redis-server --slaveof redis-master 6379 --masterauth MasterPassw0rd ports: - 6380:6379 depends_on: - redis-master restart: always保存为 docker-compose.yml在文件目录下执行docker compose up -d两个容器就一起跑起来了。注意从节点的端口映射我改成了宿主机 6380这样方便区分。从节点通过--slaveof redis-master 6379找到主节点这里redis-master是 compose 服务名compose 内部会自动做 DNS 解析指向主节点容器。启动完后用docker exec -it redis-master redis-cli -a MasterPassw0rd info replication看一下主节点的角色应该显示 master再进从节点执行同样的命令确认 role 是 slavemaster_link_status 是 up。主从就通了。你可以在主节点 set 一个 key然后去从节点 get 一下能拿到就说明同步正常。4.3 容器管理常用命令速查部署只是第一步日常运维还要用不少命令。我把最常用的整理成一张表方便你随时翻操作命令说明查看运行中容器docker ps加-a显示所有包括已停止的进入容器内部docker exec -it 容器名 /bin/bash类似 SSH 进到容器里查看容器日志docker logs -f 容器名实时滚动输出日志排查问题首选停止/启动/重启docker stop/start/restart 容器名管理容器生命周期删除容器docker rm -f 容器名加 -f 强删慎用构建镜像docker build -t 镜像名:标签 .在 Dockerfile 目录下执行查看镜像列表docker images看本地有哪些镜像删除镜像docker rmi 镜像名:标签删镜像前先删依赖容器查看资源占用docker stats实时看 CPU、内存、网络这些命令你多用几遍就熟了不用背。重点记住docker logs和docker exec排障基本靠这两个。5. Docker Compose多服务编排的利器5.1 为什么需要 Compose单个服务可以用 docker run 跑但是一个项目动辄需要 MySQL、Redis、Nginx、后端应用好几个服务全用 docker run 命令去敲又长又容易错。Docker Compose 就是解决多容器编排问题的。它用 YAML 文件描述服务、网络、卷的关系一条命令就能把整个项目拉起来。我特别建议哪怕是单机部署也用 Compose 来管理。因为 YAML 文件本身就是“基础设施即代码”整个环境配置都能版本化管理换一台机器复制文件跑一下就行。不夸张地说docker compose 是生产环境部署的底线工具。5.2 一个完整的 Web 应用编排示例我演示一个最典型的 Web 项目React 前端打包成静态文件用 Nginx 托管后端是 Java Spring Boot 服务数据库用 MySQL再加一个 Redis 做缓存。四个服务全用 Compose 编排services: mysql: image: mysql:8.0.36 container_name: app-mysql environment: MYSQL_ROOT_PASSWORD: RootPassw0rd MYSQL_DATABASE: app_db volumes: - mysql_data:/var/lib/mysql networks: - app_net restart: always redis: image: redis:7.2.4 container_name: app-redis command: redis-server --appendonly yes volumes: - redis_data:/data networks: - app_net restart: always backend: build: ./backend container_name: app-backend environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/app_db?useUnicodetruecharacterEncodingutf8 SPRING_DATA_REDIS_HOST: redis depends_on: - mysql - redis ports: - 8080:8080 networks: - app_net restart: always frontend: build: ./frontend container_name: app-frontend ports: - 80:80 depends_on: - backend networks: - app_net restart: always volumes: mysql_data: redis_data: networks: app_net: driver: bridge这里有个特别重要的细节后端配置里数据库地址写的是mysql:3306Redis 地址写的是redis不是 localhost也不是宿主机 IP。在 Compose 网络里服务名就是主机名容器之间通过服务名访问。这就是容器网络和宿主机网络的最大区别。执行docker compose up -d启动执行docker compose logs -f查看全部日志执行docker compose down停止并删除容器卷不会删数据还在。这套流程你可以反复跑不会把环境搞脏。如果要更新后端代码先重新构建镜像再重启服务docker compose build backend docker compose up -d backend5.3 Compose 文件的几个关键配置我先说几个高频配置项。depends_on控制启动顺序但只是等待容器启动不等服务完全就绪比如 MySQL 容器启动了一瞬间但还没接受连接后端就启动了可能连不上。这时候需要在后端启动脚本里做重试逻辑或者用健康检查。restart: always是保命配置服务崩溃会自动拉起但如果是基础镜像不存在的错误它会疯狂重启日志刷屏。volumes和networks在文件底部声明这样在不同的 compose 文件中可以复用。生产环境建议再给关键服务加上健康检查。以 MySQL 为例services: mysql: image: mysql:8.0.36 healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -uroot, -pRootPassw0rd] interval: 10s timeout: 5s retries: 5加上健康检查后后端可以配置depends_on的 condition 为 service_healthy这样就能真正等 MySQL 完全就绪再启动后端解决连不上的问题。6. 搞懂 Dockerfile 和镜像瘦身6.1 从 Dockerfile 构建自定义镜像很多场景下公共镜像没法直接用需要自己构建。比如你写了一个 Java 应用需要打包成镜像或者你写了一个 Python Flask 服务需要装一堆 pip 包。Dockerfile 就是描述构建过程的脚本。我给你看一个 Java Spring Boot 项目的 Dockerfile 优化版# 多阶段构建 # 第一阶段编译环境 FROM maven:3.9.6-eclipse-temurin-17 AS builder WORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests # 第二阶段运行环境 FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY --frombuilder /build/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]多阶段构建是镜像瘦身的核心手段。第一阶段用 Maven 镜像编译第二阶段直接用精简的 JRE 镜像运行。最终镜像里只有编译好的 jar 包和 JRE不会留下 Maven 和源码体积能小一大半。Python 项目也有类似的套路FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD [python, app.py]我这里特意用了slim版本比完整版少了编译工具链但能省很多体积。如果你的依赖里有需要编译的包比如 pandas、numpy那就可能需要在 build 阶段装编译工具还是建议用多阶段构建。6.2 镜像瘦身的三个务实技巧第一个技巧选择合适的基础镜像。能用 alpine 不用 Debian能用 slim 不用完整版。比如nginx:alpine、python:slim、eclipse-temurin:jre-alpine同样功能的镜像体积可能差三四倍。第二个技巧清理构建缓存。apt 安装包会留下缓存文件pip 安装会留下缓存目录这些都要清理。在 Dockerfile 里应该这样写RUN apt-get update \ apt-get install -y --no-install-recommends some-package \ rm -rf /var/lib/apt/lists/*--no-install-recommends不装推荐包rm -rf清理缓存这两步能有效减小层体积。pip 也一样安装时加--no-cache-dir。第三个技巧合并 RUN 指令。Dockerfile 里每条 RUN 指令都会产生一层镜像层数太多不但体积大构建也慢。能合并的指令尽量合并所谓“一个 RUN 干完所有事”。但有例外如果你有频繁变动的文件尽量放后面 COPY这样可以利用缓存加速构建。比如上面 Spring Boot 项目的写法先 COPY pom.xml执行依赖下载再 COPY 源码这样只要 pom.xml 没变重新构建时依赖下载那层命中缓存省去重新下载依赖的时间。6.3 构建镜像时的缓存机制Docker 构建镜像时会逐条执行 Dockerfile 指令每一条指令检查是否能用缓存。如果指令内容没变而且基础层没变就直接用缓存不重新执行。所以 Dockerfile 里文件拷贝顺序非常影响构建速度。正确的策略是把不经常变化的文件放前面比如 pom.xml、package.json、requirements.txt把经常变化的代码放后面。这样日常开发重新构建时前面依赖层命中缓存只需要重新构建后面的代码层十几秒就能完成。反过来如果你把整个目录全 COPY 进去代码一改动后面所有层全部重建构建会慢很多。还有一个缓存失效的坑COPY 文件时即使内容一样的文件如果文件权限不同也会导致缓存失效。所以 COPY 的时候建议用--chown显式指定权限减少无谓的缓存失效。7. 生产环境部署与运维避坑7.1 数据持久化和日志管理容器天生是“无状态”的任何写入容器层的改动在容器删除后就没了。生产环境最重要的一件事就是把有状态的数据全部挂载到宿主机或者外部存储上。数据库数据目录、Redis 的持久化文件、Elasticsearch 的索引目录这些必须用 volume 挂载。我建议直接用命名卷而不是 bind mount因为命名卷由 Docker 管理跨主机迁移更方便。比如 Compose 文件里声明mysql_data:/var/lib/mysqlDocker 会在宿主机某个目录下维护这个卷你不需要知道具体路径需要备份的时候用docker volume inspect查具体位置或者直接打 tar 包。日志这块docker logs能看容器内标准输出但如果日志量太大建议直接把日志挂载到宿主机目录配合 logrotate 做轮转。比如给应用容器加一行services: backend: volumes: - /data/logs/app:/app/logs这样日志文件直接落在宿主机 /data/logs 下不影响宿主机文件系统清理也方便。7.2 端口映射和网络策略容器网络是 Docker 比较抽象的部分但生产环境避不开。默认的 bridge 网络模式下容器通过 NAT 访问外部网络宿主机通过端口映射访问容器。-p 8080:8080就是把宿主机 8080 端口流量转发到容器 8080。需要注意端口冲突和暴露范围。生产环境尽量只暴露必要的端口比如数据库容器就不要把 3306 暴露到公网只让内网访问。可行方式有两个一是端口映射只绑定内网 IP比如-p 127.0.0.1:3306:3306二是不映射端口只在 Docker 网络内供其他容器访问宿主机想连就用docker exec进入容器后用 localhost 连。有跨主机通信需求的话就要考虑 overlay 网络或者直接上 Kubernetes 了。Docker 自带的 bridge 网络只在单机内有效多机容器之间互相访问还是得靠原生网络方案。7.3 容器退出和重启策略容器跑着跑着退出这事太常见了。先别慌用docker ps -a看退出码然后docker logs看日志。退出码 0 是正常退出比如一个人为 stop退出码非 0 才是程序崩溃。137 是被 SIGKILL 杀掉的通常是内存超限或者被手动 kill139 是段错误一般是代码问题。重启策略方面我建议区分场景。开发环境不用配 restart方便调试生产环境配restart: unless-stopped或者restart: always。区别是unless-stopped在你手动 stop 后不会自动重启always无论什么原因退出都会重启。这个细微差别可能导致诡异行为你发现某个容器删不掉手动停了又自己起来。那就是always在捣鬼。所以生产我更推荐unless-stopped。配合 Docker Compose 的docker compose up -d --scale还能做到多实例伸缩但单机多实例意义有限因为负载均衡还得另配。到了那个阶段还是考虑 Kubernetes 吧。8. 网络与结合开发流程的玩法8.1 容器之间互联自定义网络的重要性不用自定义网络容器之间通信也能用 IP 地址但容器重新创建后 IP 会变写死了就废了。正确做法是创建自定义网络让容器用服务名互访。创建网络docker network create app_net启动容器时指定网络docker run -d --name backend --network app_net backend:1.0.0 docker run -d --name frontend --network app_net -p 80:80 frontend:1.0.0这样 backend 容器内部可以直接 pingfrontend:80而且 IP 怎么变都不用管。Compose 项目默认会创建网络服务名自动解析这点前面演示过了。自定义网络还有一个隔离作用。不同业务的服务放到不同网络里互相之间默认不通安全隔离比防火墙还省事。8.2 开发环境下的容器化玩法开发环境用 Docker最爽的就是“一键起环境”。比如新项目标配 MySQL、Redis、RabbitMQ、MongoDB只要一个 docker-compose-dev.ymldocker compose -f docker-compose-dev.yml up -d全部起来比每个人各自装强一万倍。开发场景还需要热更新。前端项目可以直接把源码目录挂载进 Nginx 容器改完代码刷新就能看到后端 Java 项目可以挂载 target 目录配合 devtools 自动重启Python 项目可以挂载源码目录配合 --reload 参数。用 Docker 只是统一了环境并没有牺牲开发效率。我常用的方案是开发环境用 compose 启动基础设施容器应用本身在宿主机跑通过映射端口连容器里的数据库。这样既能调试应用代码又能保证数据库环境的统一。到了联调阶段再让应用也容器化整体跑一遍。这种做法比“所有东西都在容器里裸跑”更容易定位问题。8.3 微服务项目打包部署的常规路径有微服务项目的时候每个服务一个镜像镜像多了就要统一管理版本。团队内可以约定镜像标签规则比如服务名:分支名-构建序号或者服务名:日期-commit短号配合 CI 系统在每次代码合并后自动构建。构建流程一般是代码推送到代码仓库触发 CI 流水线拉代码、跑测试、打包镜像、推送到私有仓库然后通知测试环境拉取最新镜像。这套流程里 Docker 是基础但重要的反而是 CI 的编排能力。我见过很多团队在 CI 阶段才踩到镜像推送权限、构建缓存、仓库清理这些细节问题。提前把私有仓库和 CI 流程打通后面会省很多事。9. 常见问题与排查技巧实录9.1 关键报错速查表我把日常运维最高频的报错和解决办法整理成一张表你遇到问题先查表大多数都能直接抄答案报错/现象可能原因解决办法Docker Desktop failed to start because virtualization support wasnt detectedBIOS 没开虚拟化或 Hyper-V 没启用进 BIOS 开启 VT-x/AMD-V开启 Windows 虚拟机平台wsl --updateCannot connect to the Docker daemonDocker 服务没启动或当前用户不在 docker 组sudo systemctl start dockersudo usermod -aG docker $USER后重新登录Get https://registry-1.docker.io/v2/: net/http: request canceled网络问题镜像下载超时配置镜像加速器测试宿主机能否访问外网port is already allocated端口被占用docker ps看占用端口netstat -tunlp查进程改映射端口no space left on device磁盘满了或 Docker 存储目录过大docker system prune -a清理悬空镜像和停止容器查看/var/lib/docker大小容器启动后立即退出启动命令有问题或依赖服务没就绪docker logs 容器名看日志检查环境变量、配置文件路径exec: bash: executable file not found容器里没有 bash比如 alpine 镜像改用docker exec -it 容器名 /bin/sh这张表我贴在公司内部文档里新人救急特别好用。但你要明白报错只是表象真正的根因往往在日志里。养成拿到报错先看日志、再看配置的习惯。9.2 权限问题docker 命令提示 permission denied这个问题几乎每个 Linux 用户都会遇到。错误提示是“Got permission denied while trying to connect to the Docker daemon socket”。原因很明确Docker 客户端需要访问/var/run/docker.sock这个 socket 归属 root:docker 组当前用户不在 docker 组里。解决办法就是把自己加入 docker 组sudo usermod -aG docker $USER然后重新登录。我见过很多人加完组不重新登录直接再执行 docker 命令还是报错然后到处查资料。实际上 newgrp docker 或者重新打开终端就行。还有权限问题容易跟 Docker Desktop 的 WSL 2 搅在一起。如果你在 WSL 里使用 docker需要确认终端打开的发行版和 Docker Desktop 用的是同一个 WSL 发行版否则命令行里根本找不到 docker 命令或出现连接错误。9.3 docker compose 启动失败服务间连接被拒compose 启动多个服务时最常见的失败场景是 A 服务连接不上 B 服务。比如后端连接 MySQL 报Connection refused。原因通常是后端启动太快MySQL 还没就绪。虽然加健康检查能解决一部分但有更简单的思路在后端代码里加启动重试连接不上时等几秒再连连续重试若干次。这比单纯靠编排层确保顺序更健壮因为应用永远不应该假设下游已经就绪。另外一个坑是compose 文件里环境变量引用问题。比如我写SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/app_db这里的mysql是服务名不是容器名也不是宿主机 IP。如果把服务名改成别的比如db那 URL 里也要跟着改成db。这类问题排查起来不复杂看提示的是 DNS 解析失败还是连接拒绝就知道了。9.4 镜像构建失败定位到具体行Docker build 失败时输出日志会提示到具体 RUN 指令和层。比如 pip install 拉包超时或者 apt 源访问不了基本都是网络问题。解决办法是给构建过程设置代理或者配置国内镜像源。如果构建失败发生在 COPY 阶段源文件路径不存在常见于 Dockerfile 的上下文目录没找对。构建上下文默认是执行 docker build 时的当前目录Dockerfile 里 COPY 的相对路径都是基于这个上下文的别搞混了。构建时还能用--progressplain参数看完整日志不然默认会折叠成功步骤报错信息容易被淹没。多行日志不好定位时可以在 Dockerfile 里临时分段执行。比如报错在第 5 步我就把前面第 1 到 4 步先构建出来然后以这个镜像为基础单独跑一遍第 5 步的命令看具体是哪个命令出了问题。这个方法屡试不爽。10. 为什么我还是离不开 Docker都说 Docker 是基础工具但用着用着你会发现它改变的不只是部署方式而是你写代码的思路。以前你只会关注代码逻辑现在你会关心这个服务依赖什么环境、数据放哪里、配置怎么注入、日志怎么收集。这些思考方式才是 Docker 带给一个开发者最值钱的东西。我还记得去年线上出现过一次故障数据库磁盘被打爆我坐下来排查的过程就靠着三个东西docker inspect看配置、docker logs看日志、docker stats看资源。每一步都有迹可循不像以前在裸机上排查连“这台机器上到底装了哪些东西”都得靠猜。现在团队里新同学入职我给他们的第一课就是不用急着背命令先学会用 Docker 把一个项目跑起来然后尝试改造 Dockerfile再尝试拆分成多个容器。一遍下来他们对容器化的理解比看几十篇文档都深刻。最后分享一个实操小经验我习惯给每个 Dockerfile 都加上LABEL maintainer团队昵称镜像建出来之后一眼能看出谁构建的排查问题需要问人的时候省去了翻聊天记录的时间。这种细节不起眼但时间久了你会发现规范化的价值恰恰体现在这些没人注意的地方。Docker 这条路一旦走进去就回不了头了。它不是终点但绝对是你逃离“我本地能跑”这个魔咒最快的捷径。
返回列表