ARTICLE DETAIL

资讯详情

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

打通Docker核心脉络:镜像、容器、数据卷与编排实战

打通Docker核心脉络:镜像、容器、数据卷与编排实战 如果你最近在折腾本地开发环境、部署开源项目或者正在帮同事排查那个经典的“代码在我机器上是好的到你机器上就不行”的问题那么 Docker 这关你是迟早要过的。Docker 今天已经是容器化技术的代名词它解决的不是某一个具体的业务问题而是整个软件从开发、交付到运行全链路的一致性问题。这篇博文不打算堆砌概念而是用一条线把“镜像、容器、数据卷、网络、Dockerfile、编排”这些核心知识点串起来配合完整的实操案例和故障排查记录帮你把 Docker 的核心脉络真正打通。适合刚入门的开发者、正在学习容器化的运维同学、以及所有想在自己电脑上快速复现各种开源项目的折腾党。1. 先搞明白容器到底解决了什么问题1.1 从“环境地狱”说起做开发的人都有过这种经历本地用的是 Node 18测试环境是 16生产环境是 14版本差一两个小版本依赖装出来就是不对劲。数据库更是重灾区MySQL 5.7 和 8.0 的认证插件不同Redis 6 和 Redis 7 的默认配置也不同更不用说系统层面的 glibc、OpenSSL 之类的底包了。我见过太多团队花一整天时间就为了在一台新服务器上把这个项目的环境跑起来。这不是某个人的操作问题而是软件运行环境本质上是一个极度复杂的集合操作系统内核版本、运行时版本、依赖库、配置文件、环境变量任何一环不一致结果就可能不一样。容器化技术解决的就是这个问题把软件和它运行所需要的完整环境一起打包形成一个可以在任何安装了容器引擎的机器上原样运行的单元。打个比方以前你给朋友推荐一道菜给的是菜谱和一堆原材料对方家里的灶具、锅具、火候不一样做出来味道肯定飘。容器化相当于你把做好的成品连同专用的炉灶和锅具一起封装好搬过去开火就能吃味道一模一样。Docker 就是目前最主流的这套“封装搬运”方案。这套方案的价值在有多个服务需要协同的时候更加明显。一个 Web 应用往往同时依赖 MySQL、Redis、Nginx甚至还有队列中间件如果用传统方式得分别在不同机器上安装配置版本还得对齐。用 Docker 之后每个依赖都是一个镜像一条命令就能启动环境一致性问题被大幅压低。1.2 镜像与容器模板与实例的关系Docker 里有三个基础概念镜像Image、容器Container、仓库Repository。很多人一开始容易把镜像和容器搞混其实用面向对象来理解非常直观镜像相当于类是静态的、只读的模板容器相当于类的实例是镜像运行起来后形成的动态进程。镜像本身是分层构建的。每一条 Dockerfile 指令都会生成一个新的只读镜像层这些层可以被多个镜像共享。比如你用同一个 Ubuntu 基础镜像构建了三个不同的应用镜像这三个镜像共享那一份 Ubuntu 层不重复占用磁盘。容器运行的时候Docker 会在镜像层之上再挂一个可写的容器层所有文件修改都发生在这层底层镜像完全不受影响。这就是“写时复制”机制。也是因为这种设计基于同一个镜像启动几十个容器占用的磁盘空间并不会线性增长启动速度也很快。容器在本质上是宿主机上的普通进程只不过借助 Linux 内核的 Namespace 和 Cgroups 实现了隔离和资源限制。Namespace 让容器拥有独立的文件系统、网络栈、进程视图Cgroups 则限制容器的 CPU、内存等资源用量。理解这一点对后面排查问题很有帮助容器内的进程没有 PC 上那种“虚拟机里的独立系统”的味道它更接近一个被关在沙箱里的进程。1.3 从 Docker 命令到容器进程中间发生了什么很多人熟练使用 docker run 却不知道背后是谁在干活。实际上 Docker 不是单一体而是一组协同组件的组合了解这个过程对排查问题很有价值。你执行 docker 命令时真正敲的是客户端 CLI它会把命令解析成 REST API 请求发给 Docker 守护进程dockerd。dockerd 是核心管理组件负责镜像管理、容器生命周期调度、网络和卷的管理。守护进程再往下是 containerd它负责真正管理容器运行时的生命周期。再往下是 runc它才是真正与 Linux 内核打交道、创建容器的那个进程。整个链路可以类比餐厅docker CLI 是服务员你点菜dockerd 是店长统筹安排containerd 是厨师长负责控制整个上菜流程runc 是掌勺的师傅菜最终从他手里出锅。Docker 之所以能成为容器化技术事实标准还因为引入了 OCIOpen Container Initiative标准。OCI 定义了镜像格式和容器运行时规范只要符合这个标准无论底层是用 Docker、Podman 还是 containerd都能运行同一个镜像。这对用户来说是实实在在的好处你构建出来的镜像可以带着走不在任何一层被锁死。2. 安装 Docker从本机到服务器的完整落地2.1 Windows 平台Docker Desktop 与虚拟化前置条件Windows 上安装 Docker 的常规方式是安装 Docker Desktop但很多人第一步就卡住了因为 Docker Desktop 在 Windows 上本质是需要虚拟化支持的。如果你启动时报错 “Virtualization support not detected” 或者类似“Docker Desktop failed to start because virtualization support is not enabled”先别急着重装软件问题多半出在系统虚拟化没有打开或 Windows 功能没有启用。先在任务管理器的“性能”标签页里点 CPU看看右下角有没有显示“虚拟化: 已启用”。如果显示“已禁用”需要进 BIOS/UEFI 开启虚拟化技术Intel 平台对应 VT-xAMD 平台对应 AMD-V。笔记本和品牌机不同厂商的 BIOS 界面差异很大但一般在 Advanced 或 Security 菜单下能找到这个开关。虚拟化打开之后再检查 Windows 功能。按 WinR 输入 optionalfeatures在“启用或关闭 Windows 功能”里找到并勾选三样Hyper-V、虚拟机平台、适用于 Linux 的 Windows 子系统。装完重启之后再确认 WSL2 是当前默认版本在 PowerShell 里执行 wsl --set-default-version 2如果还没安装 Linux 内核组件按提示更新一下即可。Docker Desktop 安装包可以从 Docker 官网下载安装过程基本无脑下一步。装完启动后建议在设置里明确选择“Use WSL 2 based engine”这是 Windows 上性能最好的运行方式比旧的 Hyper-V 后端更轻量。打开终端执行 docker version能看到客户端和服务端版本信息就算安装成功。很多人装完 Docker Desktop 后在 WSL 的终端里执行 docker 命令却提示 “failed to connect to the docker api at npipe:////./pipe/dockerdesktoplinuxen”这种时候 99% 是 Docker Desktop 本身没启动或者刚启动引擎还没就绪稍微等几秒再试确认托盘区的鲸鱼图标是运行状态而不是停止状态就行。2.2 Linux 平台的安装思路稳定比新功能重要Linux 上安装 Docker 没有 Windows 那么绕核心就两步配源、安装。Ubuntu/Debian 系的常用做法是先 apt update然后安装 docker.io 包。这个包是发行版仓库里自带的老版本胜在稳定跟系统自带的管理机制结合得好普通个人开发机用完全没问题。但要注意这里的版本通常比较旧如果你需要新特性或者类似 CentOS 这种老系统上要升级到 Docker那还是建议用 Docker 官方源装的 docker-ce 版本。以 Ubuntu 为例安装 docker-ce 的操作是把官方 GPG key 添加进系统然后把官方 apt 仓库写入 source list再 apt update 之后安装 docker-ce docker-ce-cli containerd.io。这套流程在 Docker 官方文档里写得非常详细网上搜到的教程也基本都这么操作照着做不会出错。CentOS 7 的升级路径也类似先卸载掉旧版 docker、docker-client 等包再通过 yum 安装 yum-utils接下来用 yum-config-manager 添加官方仓库最后 yum install docker-ce。CentOS 7 上经常额外需要配置一下 systemd 加载但本质也是跟着官方文档走。装完之后要做的第一件事是执行 docker run hello-world 验证。如果报 “Cannot connect to the Docker daemon”一般是 Docker 服务没有启动管理系统服务的方式是 systemctl start docker想开机自启就 systemctl enable docker。还有一类非常常见的权限问题报错信息里带 “permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock”原因是当前用户不在 docker 用户组里。解决方法不是去改 socket 文件的权限而是执行 sudo usermod -aG docker $USER然后退出登录重新进来。一定不要手贱去 chmod 777 docker.sock这会留下严重的安全隐患docker 组本身已经等价于 root 权限了管理组成员比乱改 socket 权限更合理。2.3 镜像加速配置为什么 docker pull 那么慢对于身处网络环境特殊、连接 Docker Hub 不稳定场景下的用户拉镜像慢是每天都要面对的事情。Docker 默认从 Docker Hub 拉取镜像官方仓库在部分区域网络下的下载速度确实不理想经常一个几百 MB 的基础镜像能拉到怀疑人生。解决办法是配置镜像加速器。docker pull 遵循的是 daemon 配置中的 registry-mirrors 参数可以在 Docker 的配置文件 daemon.json 里设置。Windows 下 Docker Desktop 可以在设置页面右键找到 Docker Engine 选项直接在 JSON 配置里修改Linux 下编辑 /etc/docker/daemon.json没有这个文件就新建一个。配置格式如下{ registry-mirrors: [ https://your-mirror.example.com ] }里面的镜像地址建议去你所使用的云服务商控制台获取专属加速地址一般每个账号会分配一个独立域名这种地址比网上搜到的公共加速源更稳定。配置保存之后需要重启 Docker 服务才能生效Linux 下执行 systemctl daemon-reload 和 systemctl restart dockerWindows 下可以直接重启 Docker Desktop。验证是否生效可以执行 docker info在输出里看 Registry Mirrors 这一行确认自己配置的地址已经出现在里面。需要说清楚的是镜像加速只对公网 Registry 的拉取过程起作用如果你在使用私有仓库或者自建 Harbor那走的是另一套认证和访问机制加速器不会处理。3. 核心操作实战以 MySQL 8.0 容器为例3.1 拉取镜像与版本选择概念说再多不如跑一个真实服务。我拿 MySQL 8.0 来走一遍完整流程因为数据库容器是把端口映射、数据持久化、环境变量配置这几个核心要素全部串起来的最佳示例。第一步是拉镜像。你可能会想直接用 docker pull mysql 不好吗我见过很多人这么干结果某天生产环境一更新MySQL 从 8.0 升到 9.x配置出现不兼容整个服务起不来。所以我的习惯是明确指定主版本号比如 docker pull mysql:8.0。这个标签指向当前 8.0 系列的最新版既不会随意跳到新的大版本又能吃到小版本的更新。镜像标签的语义要弄清楚latest 是默认标签随时在变不适合作为固定依赖mysql:8.0 是主版本系列标签更新迭代到 8.0.x 最新版本mysql:8.0.36 才是精确对应的版本标签。拉取完成之后可以先 docker image inspect mysql:8.0 看一下镜像的元信息暴露了哪些端口、默认环境变量、镜像用了什么样的目录结构这些信息在后面的运行配置阶段都会用到。不过更深层的了解还是建议看官方镜像的 Dockerfile 和文档官方镜像的说明文档几乎是所有容器化应用的范本。3.2 启动容器端口映射、数据卷与初始化配置启动 MySQL 容器的典型命令长这样docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDYourPass123 \ -e MYSQL_DATABASEappdb \ -v mysql_data:/var/lib/mysql \ --restartalways \ mysql:8.0逐项拆开来看-d 表示后台运行这是服务类容器的标配。--name 给容器起个固定的名字方便后续管理和连接。 -p 3306:3306 是端口映射冒号左边是宿主机端口右边是容器内部端口。容器内部端口是由应用自身决定的MySQL 默认监听 3306你在宿主机上想用哪个端口访问就说左边的数字。为什么容器有独立端口还要映射因为容器有自己独立的网络命名空间宿主机上不能直接通过 localhost 访问容器里的服务必须把宿主机某个端口的数据转发到容器端口上。-v mysql_data:/var/lib/mysql 是命名卷挂载这是数据库容器的关键操作。容器删除后容器层中的数据会一起消失如果 MySQL 的数据文件只写在容器里rm -f 一执行所有数据库就没了。挂载命名卷意味着 MySQL 的数据目录 /var/lib/mysql 实际存放在宿主机 Docker 管理的卷存储中容器删掉重建指定同一个卷名数据原封不动。这个机制对任何有状态应用都至关重要Redis、PostgreSQL、GitLab 也都不例外。-e 参数是注入环境变量。MySQL 官方镜像内部有一套初始化脚本会根据环境变量完成数据库初始化。MYSQL_ROOT_PASSWORD 设置 root 用户的密码MYSQL_DATABASE 会在首次启动时自动创建一个数据库。注意这些环境变量只在首次初始化数据目录时生效如果数据卷已经存在旧数据改密码不会自动应用要改密码还是得进入容器执行 SQL。字符集问题在使用 MySQL 8.0 时值得留意尤其涉及中文场景。单纯用环境变量无法指定字符集需要通过命令行参数传入。可以在 docker run 命令里追加--character-set-serverutf8mb4 \ --collation-serverutf8mb4_unicode_ci这样容器内的 MySQL 就会使用 utf8mb4 字符集避免中文乱码问题。我建议把这个作为 MySQL 容器的默认参数不是只有需要中文业务才用而是彻底规避掉非 ASCII 字符的潜在坑。3.3 日志查看、进入容器与日常运维容器启动之后第一步一定是确认它没挂。执行 docker ps 看运行状态如果容器处于 Exited则执行 docker logs mysql8 查看日志。日志对容器排查的意义太大了MySQL 初始化失败、密码策略不符、卷目录权限不对这些信息都会明确写在日志里面。docker logs 不加参数会输出全部日志日志量大时建议配合 docker logs --tail 100 mysql8 只看最后 100 行。需要执行容器内部命令时用 docker exec。docker exec -it mysql8 bash 会进入容器得到一个 shell进去之后 mysql -uroot -p 就能连上数据库。数据库镜像一般比较精简没有 vi、没有 netstat、没有 ip 命令都是正常现象缺什么用什么方式解决不要指望容器里像完整操作系统一样齐全。容器里安装东西也不是不行但要注意容器是临时的重建后所有临时安装都会丢失建议把需要固化的东西写进 Dockerfile而不是手工进入容器去改。我一直强调一个观点容器是一个“活着的进程”不是一台可以登进去长期折腾的虚拟机。它的生命周期绑定在容器主进程上主进程结束容器就退出。这句话在排查“容器启动即退出”时尤其重要后面第 6 章还会展开。3.4 顺手拆一个 Redis 主从复制部署MySQL 单容器案例跑通之后我再演示一个更贴近“容器协作”的实战用两个 Redis 容器搭建主从复制。这个过程会牵扯到自定义网络也是一个极其实用的知识点。先创建一个自定义 bridge 网络然后分别启动主从两个容器。主容器暴露宿主机端口方便外部访问从容器不需要暴露端口因为我们只让它在网络内部访问主容器docker network create redis-net docker run -d \ --name redis-master \ --network redis-net \ -p 6379:6379 \ redis:7 redis-server --appendonly yes docker run -d \ --name redis-slave \ --network redis-net \ redis:7 redis-server --slaveof redis-master 6379这里有个关键设计slave 容器连接 master 时用的是容器名 redis-master 而不是 IP 地址。在自定义网络中Docker 内置了 DNS 解析容器之间可以通过名字直接互通。这比记 IP 可靠得多容器重建后 IP 会变化而 Docker 网络的 DNS 会自动更新。验证一下从复制是否生效docker exec -it redis-slave redis-cli info replication输出中如果显示 role:slave 且 master_link_status:up主从就建立成功了。这个例子同时验证了三个知识点自定义网络的用法、容器间通信的 DNS 机制、以及多个容器实例协同部署的方式。日常工作中我们很少真的手动一个个 docker run 来部署多服务应用而是把这套逻辑写进 Docker Compose一条命令完成全部启动这个主题我会在第 5 章详细展开。4. 深入 Dockerfile 与镜像构建4.1 核心指令逐行拆解如果说 docker run 是消费镜像那写 Dockerfile 就是生产镜像。镜像不是凭空来的任何自定义应用要容器化第一步都是写 Dockerfile。我以一个最典型的 Python Web 应用为例完整的 Dockerfile 长这样FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . EXPOSE 8000 CMD [python, app.py]FROM 是基础镜像一切构建都从某个基础镜像开始。为什么选 python:3.11-slim 而不是 python:3.11因为 slim 版本剔除了大量编译工具和文档镜像体积小得多而我们的应用如果不需要在容器里现场编译依赖就完全够用。WORKDIR 设置工作目录。执行到这一句后后续指令的当前路径都默认是这个目录如果目录不存在会自动创建。建议每个项目都显式声明 WORKDIR不要让容器里的文件散落在根目录或默认目录。COPY requirements.txt . 把依赖清单复制进容器。RUN pip install 在构建时执行把 Python 依赖安装到镜像里。这里有个很关键的层缓存规律Docker 构建时会检查每一层指令是否变更如果前面几层的输入没有变化就直接复用缓存。把依赖安装放在代码复制前面这样每次修改业务代码后重新构建只需要重跑 COPY . . 之后的指令pip install 这步被缓存构建速度飞快。反之如果把 COPY . . 放前面哪怕只改了一行代码整个依赖安装都要重来。EXPOSE 8000 只是声明容器内服务监听 8000 端口它本身并不做端口映射。端口映射是启动容器时用 -p 完成的EXPOSE 更像是一个文档性质的信息告诉使用者这个镜像默认会使用哪些端口。docker run 加不加 -p 不影响容器内部监听但外部要访问就必须映射。CMD 和 ENTRYPOINT 是新手最容易糊涂的地方。用一句话概括ENTRYPOINT 定义容器启动后必然运行的那个主进程CMD 提供这个进程的默认参数。docker run 后面跟的附加命令会覆盖 CMD 的内容但如果 ENTRYPOINT 存在它不会被覆盖。看起来复杂其实类比很简单ENTRYPOINT 是“这个人要做什么工作”CMD 是“这个工作的默认参数”。比如 redis 镜像把 redis-server 定义为 ENTRYPOINTCMD 提供默认配置文件路径你在 docker run 后面加上 redis-server --appendonly yes 这样的额外参数时实际上是在覆盖或追加 CMD 的内容。4.2 构建上下文与多阶段构建写好了 Dockerfile使用 docker build -t myapp:1.0 . 构建镜像最后的 . 表示构建上下文目录。这里存在一个普遍的误解构建上下文不是 Dockerfile 所在目录这么简单它是构建过程中能被 COPY 指令访问到的文件集合。docker build 会把整个上下文目录打包发送给 Docker 守护进程包括你不需要的任何大文件。所以必须建立 .dockerignore 文件把 node_modules、.git、日志、临时目录统统排除掉否则你只要在项目里放了一个 800MB 的日志文件每次构建都要把这个文件打包发送一遍慢得离谱。多阶段构建是另一个实战利器。它解决的核心问题是“编译环境大运行环境要小”的矛盾。以 Node 前端项目为例FROM node:18 AS build WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM nginx:alpine COPY --frombuild /app/dist /usr/share/nginx/html EXPOSE 80第一个阶段用完整的 Node 环境完成依赖安装和项目构建产出静态文件第二个阶段只拿 nginx 作为运行环境将第一阶段构建出的 dist 目录复制进去。最终镜像只包含 nginx 和静态文件体积可能只有一百多 MB而不是带着一整套 Node 开发环境的几百 MB。这种思路用在 Java 上同样有效第一阶段用 maven 镜像编译 JAR第二阶段用 jre 镜像运行效果立竿见影。4.3 开发工具中的镜像打包很多人在 IntelliJ IDEA 里点一下构建就能打包 Docker 镜像或者在 VS Code 里装个 Docker 插件就完成部署觉得很神奇。其实这些工具在底层就是帮你执行了 docker build只不过把过程封装成了可视化按钮。理解 Dockerfile 之后这些工具的使用就变得透明了不会再遇到“插件配置半天还是构建失败”的抓瞎状态。像 IDEA 的 Docker 插件、Maven 的 dockerfile-maven-plugin 插件、Spring Boot 2.3 自带的 Cloud Native Buildpacks本质上都是把你项目里的 Dockerfile 或构建参数提交给 Docker区别只在自动生成了多少配置。底层原理掌握牢固换任何工具都能快速上手。5. 数据卷、网络与多容器协作5.1 数据持久化的三种方案容器是轻量、可随意删除重建的但数据不能跟着容器一起丢掉。Docker 提供了三种数据持久化方案简单对比如下方案类型数据位置适用场景Named Volume命名卷Docker 管理的宿主机目录数据库数据、应用状态Bind Mount绑定挂载宿主机任意指定目录开发热更新、配置文件Container Layer容器层容器可写层临时测试不推荐生产命名卷用 -v mydata:/var/lib/mysql 这种形式卷的实际位置由 Docker 管理用户不直接感知。优点是完全托管docker volume ls 可以查看所有卷备份迁移也很方便直接对卷目录操作即可。绑定挂载用 -v /host/path:/container/path 这种形式宿主机路径直接暴露给容器修改宿主机文件容器立刻可见非常适合开发场景下免重建热更新代码。一个新手频繁踩的坑是创建了命名卷但某个版本镜像里的服务默认用户与宿主机目录权限不匹配。比如在容器里运行的是 uid 999 的用户而绑定挂载的宿主机目录归属 root容器就会报权限不足无法写入。遇到这种情况不要硬扛优先检查镜像说明文档里是否提供了指定 PUID/PGID 的环境变量很多成熟镜像已经解决了这个问题。5.2 网络模式与容器互通Docker 网络默认有四种模式理解它们能省掉大量排查问题的时间网络模式隔离性访问方式典型场景bridge中等-p 端口映射单机多容器常见选择host低直接用宿主机端口性能敏感场景none完全隔离无网络安全隔离测试container共享一个网络栈localhost 互通特殊调试场景bridge 是默认模式也是大多数场景下最合适的选择。容器通过虚拟网桥接入一个独立网段端口映射的实现机制是 iptables DNAT将宿主机端口的流量转到容器 IP 对应端口上。这里要注意一个细节在默认的 bridge 网络中容器之间不能用容器名直接互访只能通过 IP。而容器重建后 IP 通常会变所以生产环境需要容器间稳定通信时第一选择是创建自定义网络。自定义 bridge 网络内置 DNS 解析容器名即主机名互相访问毫无障碍。这就是上一章 Redis 主从部署时要先 docker network create redis-net 的原因。网络不通是另一个高频问题。容器里 ping 外网通不了要区分两种情况DNS 解析失败还是路由不通。可以在容器里执行 cat /etc/resolv.conf 看 DNS 配置再试 ping 8.8.8.8 区分能否到达 IP之后再试 ping www.baidu.com 区分 DNS 是否正常。很多时候容器网络能通是因为宿主机防火墙策略拦截或路由表配置异常优先排查宿主机 iptables 规则不要一上来就重建容器。5.3 docker-compose用 YAML 定义多容器应用手动敲 docker run 部署单个容器还行部署一套含 MySQL、Redis、Nginx、应用服务的小型项目时命令就会变得巨长且不可维护。docker-compose 用声明式的 YAML 文件把多个容器的定义集中管理一条 docker compose up -d 命令就把整套环境拉起来。一个典型示例version: 3.8 services: mysql: image: mysql:8.0 container_name: app-mysql environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: appdb volumes: - mysql_data:/var/lib/mysql ports: - 3306:3306 healthcheck: test: [CMD, mysqladmin, ping, -h, localhost] interval: 5s retries: 10 app: build: . ports: - 8000:8000 depends_on: mysql: condition: service_healthy volumes: mysql_data:compose 文件的基本结构分三块services 定义各个容器volumes 定义命名卷networks 定义自定义网络。上面的例子体现了一个高级用法应用服务通过 depends_on 声明依赖 MySQL并且指定了 condition: service_healthy只有 MySQL 的健康检查通过之后才启动应用。这样避免了一个常见的启动竞态问题——应用容器先启动连不上 MySQL 直接崩溃退出。入门用户可以先把 compose 当成“多条 docker run 命令的 YAML 化”每个 services 下的字段几乎都能对应到 docker run 的某个参数。这套语法掌握之后部署 GitLab、Metabase、OpenObserve 这类依赖多个组件的开源项目基本就是从官方文档复制一份 compose 文件再改改端口和密码的事。6. 高频故障排查实录6.1 镜像拉取失败与下载慢镜像拉取是 Docker 使用中频率最高的操作也是出问题最集中的环节。常见的报错有几类pull access denied 表示镜像不存在或者没有权限可能是指定的镜像名打错了也可能是在拉一个私有仓库的镜像但没有先 docker login。timeout 或 i/o timeout 是网络层面没通重点检查网络连通性和代理配置多次重试或更换网络环境后再拉。为了避免反复踩坑建议固化一个拉取策略基础镜像尽量选择体积更小的变体比如 alpine、slim 版本官方镜像优先从 Docker Hub 官方网站页面确认镜像名和可用标签如果网络环境持续不稳定按照第 2 章的方法配置镜像加速器。这些手段组合使用之后拉取失败的情况会明显减少。6.2 容器网络不通容器网络不通的表现五花八门宿主机访问不到容器端口、容器访问不了外网、容器之间互相 ping 不通。排查这些问题的核心逻辑是逐层确认先看宿主机的端口是否在监听再看 Docker 的端口映射是否正确生效最后再进容器内部分析。宿主机访问不到容器端口时第一反应执行 docker ps 确认容器在不在执行 docker port 容器名 确认映射关系。如果映射关系在但访问还是不通检查宿主机防火墙是否放行了对应端口。容器之间互访失败时重点看它们是否在同一个自定义网络中不在同一个网络的容器只能通过映射端口访问容器名互访当然失败。一个检查技巧是执行 docker inspect 容器名在 NetworkSettings 段可以看到容器所属网络、IP 地址、端口映射的完整信息比瞎猜高效得多。6.3 Docker 服务启动失败与权限错误Linux 下 Docker 服务启动失败时第一件事是 journalctl -u docker 查看服务日志。如果是 daemon.json 配置文件写错比如 JSON 格式错误、配置项拼写错误通常日志里会明确指出解析失败的位置。还有一种情况是 SELinux 或 AppArmor 限制导致 dockerd 无法正常工作这多见于启用强制模式的系统排查思路上可以临时调整相关安全策略但生产环境不建议简单地关闭 SELinux 一了百了。权限错误方面最有代表性的就是连接 docker.sock 被拒的问题我在第 2 章已经提到过。这里再补充一条如果你是 root 用户执行 docker 命令仍然报权限错误优先检查是不是因为通过 sudo 执行的命令导致环境变量丢失或者 Docker 服务根本没有运行。可以先执行 systemctl status docker 确认服务状态再执行 id 确认当前用户是否在 docker 组不要一上来就怀疑是权限系统的问题。6.4 容器启动即退出“容器一启动就退出”是新手最迷惑的问题。前面我强调过容器的生命周期等于主进程的生命周期。主进程一开始运行容器就是 Up 状态主进程正常退出或报错退出容器立即变成 Exited。所以当你的容器退出时第一反应应该是“容器里的主进程为什么结束了”而不是“容器为什么停了”。常见原因有几种。第一种是你启动命令指定了后台运行的程序比如 CMD [“bash”, “-c”, “nohup java -jar app.jar ”]主进程 bash 执行完这一行后立刻退出容器也就跟着退了。正确的做法是让服务以前台方式运行把进程交给容器的主进程管理。Java 服务直接作为 CMD 的进程Nginx 用 daemon off 启动都是为了让主进程保持存活。第二种是环境变量缺失导致进程启动失败比如启动脚本里依赖某个 -e 注入的变量变量没传进程启动一半就崩了。这时 docker logs 里会有明确的错误信息。第三种是权限问题或目录不存在比如绑定挂载的宿主机目录没创建容器内进程启动时报错。具体的排查手段有两个最管用docker logs 容器名 查看退出前的日志输出以及 docker inspect 容器名 --format {{.State.ExitCode}} 查看退出码。退出码 0 表示主进程主动正常退出大概率是容器设计问题非 0 退出码则要看业务日志定位具体错误。把这些信息对应起来容器启动即退出的问题基本都能定位出方向。写在最后的一些经验这套 Docker 核心知识体系我自己反复带过很多人最大的体会是学 Docker 不要追求一次性背完所有命令和参数抓住镜像、容器、数据卷、网络这四条主线再补充 Dockerfile 和 compose 两个工具手段日常 90% 的工作就覆盖了。遇到陌生镜像时先跑一遍官方文档的示例命令再用 docker inspect 看元信息基本能摸清这个镜像的脾气。最后再分享一个我自己的小习惯所有重要容器启动时都加上 --restartalways这样服务器重启之后服务能自动拉起来省去半夜被监控报警叫醒的麻烦每隔一段时间用 docker image prune -f 清理悬空镜像磁盘空间会宽裕很多。容器化这条路方向对了剩下的事都靠动手试出来的。
返回列表