ARTICLE DETAIL

资讯详情

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

Docker核心知识盘点:镜像、容器、网络与数据卷实战

Docker核心知识盘点:镜像、容器、网络与数据卷实战 最近我把手头几个跑在Docker里的服务重新梳理了一遍顺便把踩过的坑和常用的命令都归了个档。在做这件事之前我一直觉得Docker就是个“装软件更方便的虚拟机”直到真正上手拆解镜像、网络和存储之后才发现它的核心价值在于把环境本身也变成了可以交付、可以版本化的代码。这篇东西不是教科书式的Docker文档翻译更像是我自己从“能跑”到“跑得明白”这个过程里沉淀下来的核心知识清单。不管你是刚装好Docker Desktop、准备部署MySQL和Redis的小白还是已经用docker run跑了不少容器、想搞明白网络和存储原理的后端同学应该都能在里面找到对自己有用的部分。1. 先别急着敲命令镜像、容器、仓库的底层关系1.1 镜像就是安装包容器就是运行中的进程镜像Image是一个只读模板容器是镜像启动后的运行实例。这句话很多人看到过但理解不够深导致后续大规模踩坑以为容器就是个小虚拟机进去改这改那最后容器一删全没了才开始喊数据丢失。镜像是由一层层只读文件系统叠加出来的容器在镜像之上多了一层可写层你在容器里写的任何文件、装的任何包都在这一层里容器删除后这一层跟着销毁镜像本身毫发无损。把这套模型真正想明白之后很多操作逻辑就通了。比如为什么官方文档反复说“容器是不可变基础设施要重新构建而不是进去修补”因为补丁随时会丢而镜像构建文件可以复现、可以版本管理。再比如为什么同一个镜像可以同时跑几十个容器因为每个容器只共享只读层各自的可写层互不干扰。还有为什么要学Dockerfile而不是依赖docker commit因为分层文件系统会让一个简单改动生成一大堆没用的中间层提交出来的镜像又大又乱。1.2 仓库、标签和分层存储的三个常识仓库Registry是集中存放镜像的地方Docker Hub是最大的公共仓库公司内部也常用Harbor或自建私有仓库。拉镜像时写的名称其实分几段仓库地址、镜像名、标签比如registry.example.com/nginx:1.26这种形式没写地址就默认Docker Hub。标签Tag不是哈希同一个镜像可以有多个标签latest不一定是最新版很多项目甚至用latest指代最新的稳定版所以严谨的生产环境应该锁死具体版本号比如nginx:1.26.2。分层机制是另一个关键点。镜像的每一层都是只读的构建时每个RUN指令、每个COPY操作都可能产生新层删除文件只是在上一层打一个“删除标记”底层数据其实还在所以镜像体积不会因为删除变小。理解这一点对写Dockerfile帮助很大多个RUN尽量合并、用多阶段构建丢弃编译中间层、把不常变化的依赖层放在前面以利用构建缓存这些都是建立在分层原理之上的优化手段。2. 安装部署从Linux服务器到Windows桌面2.1 Ubuntu/CentOS装Docker的四步流程安装Docker其实并不复杂但很多人照着网上的教程装完就遇到各种启动失败多半是装错了源或者少了系统依赖。以Ubuntu 22.04为例官方推荐的安装路径是添加Docker的apt源然后安装docker-ce、docker-ce-cli、containerd.io这几个包不建议直接apt install docker.io那个版本通常比较旧后续维护也麻烦。# 更新索引并安装依赖 sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg # 添加Docker官方GPG密钥与apt源 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 $(. /etc/os-release echo $VERSION_CODENAME) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null # 安装最新Docker引擎与插件 sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin如果你在CentOS 7上旧内核与老版本docker-ce可能冲突建议先确认内核不低于3.10并用yum repolist确认官方仓库是否配置正确。CentOS 7升级Docker并不复杂关键是卸载旧版本时不要顺手把依赖也删掉否则会连带卸掉一堆基础包。装完之后别急着跑容器先做三件事。第一把当前用户加进docker组否则每次命令都要sudo很烦sudo usermod -aG docker $USER加完重新登录一次。第二确认开机自启sudo systemctl enable docker sudo systemctl start docker。第三验证安装docker version看到Client和Server两段都输出正常才算真正装完。如果Server段出现Cannot connect to the Docker daemon说明守护进程没起来这时候去看dockerd日志而不是怀疑命令敲错。2.2 Windows装Docker Desktop最常卡住的两个报错Windows上装Docker Desktop本身没什么难度双击安装包下一步就行真正的坑在启动阶段。最常见的两个报错几乎覆盖了大半求助帖一个是初始化时提示Virtualization support not detected另一个是启动后提示Failed to connect to the Docker API at npipe:////./pipe/dockerDesktopLinuxEngine。第一个报错的核心是虚拟化没开。Docker Desktop在Windows上依赖WSL2或Hyper-V先以管理员身份打开PowerShell执行bcdedit /set hypervisorlaunchtype auto再去“启用或关闭Windows功能”里勾选“适用于Linux的Windows子系统”和“虚拟机平台”重启电脑后重新打开Docker Desktop。如果重启后还是同样报错问题多半在BIOS进主板设置把Intel VT-x或AMD SVM打开。这一步没有任何命令行能绕过虚拟化开关是硬件层面的。第二个报错的意思是Docker引擎没有成功启动常见原因有三种WSL2内核版本太老、Docker Desktop里的WSL集成没勾选、或者是上次异常退出导致引擎状态卡死。我的处理顺序是先完全退出Docker Desktop再打开PowerShell执行wsl --update然后重启电脑或至少wsl --shutdown让WSL彻底重启再启动Docker Desktop。如果前面都无效才考虑在Docker Desktop设置里重置WSL数据注意这个操作会清掉你已经拉取的所有镜像和容器数据操作前一定要想清楚。我自己有次为了省事直接点重置结果本地几个镜像全没了重新拉一遍的痛苦至今记得。2.3 镜像下载慢的根源与加速配置镜像下载慢在国内几乎是绕不开的话题原因是默认的registry-1.docker.io和国内网络之间的连通性不太稳定。解决办法是给Docker引擎配置镜像加速器也就是告诉Docker从国内镜像源拉取。修改/etc/docker/daemon.json文件Windows桌面版是Settings-Docker Engine里改JSON把registry-mirrors数组加进去网上常见的是云厂商提供的加速地址稳定性时效性变化很快没有永久有效的源。改完必须重启Docker引擎sudo systemctl daemon-reload sudo systemctl restart docker。注意直接重启容器不会生效因为加速配置属于daemon级别。如果你在公司内网或者有私有仓库把私有仓库地址放在registry-mirrors列表第一位能进一步提高拉取速度和稳定性。另外一个容易忽略的点是不要同时配一堆加速源Docker会逐个尝试源太多反而拖慢首次拉取速度。3. 核心操作命令背后的逻辑与连接原理3.1 容器生命周期从run到exec的几个关键细节docker run 是大家学会的第一个命令但真正用好容器生命周期管理得先弄清楚几个容易被忽略的区别。docker create 只是创建容器docker start 才会启动docker run 是 create start 的组合-d表示后台运行-it表示交互式终端。--restart策略要主动设置比如always或unless-stopped这样服务器重启后容器才能自动恢复这是个经常被遗漏的生产环境配置。容器里 PID 1 的进程是容器生命周期的根本。很多容器退出不是因为程序崩溃而是因为PID 1进程结束了。比如用docker run nginx /bin/bashbash变成PID 1一旦退出容器就停了。所以正确的启动命令要么直接使用镜像默认的CMD/ENTRYPOINT要么用-it配合 bash 并保持前台运行切忌让PID 1进程在后台抱着shell不动。排查容器为什么停第一件事看docker logs 容器名第二件事看docker inspect里的退出码区分正常退出0和异常退出非0。3.2 数据卷容器文件不会丢的唯一正确姿势容器可写层会随容器删除而消失所以凡是需要持久化的数据必须交给卷Volume。Volume有两种常见用法bind mount是把宿主机目录直接挂进容器适合开发调试比如-v /home/me/project:/appDocker管理的volume则是Docker自己维护的存储目录适合数据库这类需要稳定存储的中间件比如-v mysql-data:/var/lib/mysql数据会存放在/var/lib/docker/volumes/mysql-data下容器删了数据还在。怎么确认挂载有没有生效docker inspect里的Mounts字段能看到宿主机路径、容器路径和读写权限。挂载目录权限问题也很常见容器内进程以root运行宿主目录可能被塞了一堆root属主文件所以可以加:ro或通过chown调整属主。另一个容易踩的坑是目录挂载顺序-v /a:/b里如果宿主机目录不存在Docker会自动创建空目录启动后容器里对应路径被空目录覆盖数据目录里什么都没有数据库直接初始化失败。所以推荐把重要的持久化目录统一放到固定路径下并通过docker volume命令管理少用绝对路径的bind mount。3.3 网络模式容器为什么互相ping不通Docker默认的bridge网络对应宿主机上的docker0网桥每个容器通过虚拟网卡接入同一个内部网络默认可以通过IP互访。但因为容器重建后IP会变跨容器访问时不要依赖写死的IP。自定义bridge网络提供了DNS解析能力同一个自定义网络里的容器可以通过服务名互相访问这是Docker Compose能实现服务编排的基础。命令示例docker network create mynet docker run --name web --net mynet -d nginx docker run --name app --net mynet -d your_app # 在app容器里直接访问 http://web:80 即可host网络模式直接把容器进程塞进宿主机网络栈端口不需要映射性能损耗最小但失去了网络隔离none模式只有loopback适合做离网任务。跨主机通信和overlay网络属于进阶内容生产环境通常配合Kubernetes或者Swarm来管理这里暂时不展开。“docker网络不通”是排障区里出现频率极高的问题。我的排查顺序通常是这样的先确认两个容器在不在同一个网络上用docker network inspect mynet看connected容器列表再确认宿主机防火墙有没有放行相关端口尤其是ufw和云厂商安全组最后进容器里用docker exec执行ping、curl看DNS解析是否正常。还有一个非常容易被忽略的点容器内的服务listen地址是不是0.0.0.0如果服务只监听127.0.0.1就算端口映射正确宿主机和外部同样无法访问。4. 场景实战从MySQL、Redis到Compose和微服务打包4.1 部署MySQL 8.0数据卷、端口和认证方式MySQL应该是Docker里被部署最多的数据库。直接跑docker run -d --name mysql8 -p 3306:3306 -e MYSQL_ROOT_PASSWORDyourpassword -v mysql-data:/var/lib/mysql mysql:8.0看似一条命令完事但核心其实是数据卷。如果不挂载目录容器一删数据库数据全没哪怕有备份也要花时间恢复。挂载之后即使容器重建、升级镜像版本数据文件依然保留。部署完连接不上的问题按照我这边的踩坑经验主要有三种。第一root用户默认绑定localhost或者没有开放远程需要进入容器执行SQL把root的host改成%并刷新授权。第二宿主机防火墙或者云安全组没有放行3306。第三MySQL 8.0默认使用 caching_sha2_password 认证插件旧版本的客户端驱动握手失败需要执行ALTER USER root% IDENTIFIED WITH mysql_native_password BY 新密码;或者直接升级客户端驱动。如果你遇到“docker安装mysql失败”这种描述先别慌看日志。docker logs mysql8才是排查入口绝大多数初始化失败都会在日志里写清楚原因比如端口被占用、MYSQL_ROOT_PASSWORD未设置、或者内存不足启动不了。启动MySQL建议同时配置-e TZAsia/Shanghai时区和--restart unless-stopped自动重启否则容器里存的默认时区会和服务器差八个小时查日志和看业务数据时非常别扭。4.2 Redis主从复制网络与配置文件的搭配Redis用Docker部署比MySQL更轻量但主从复制里最容易翻车的是网络和配置。先创建一个自定义网络docker network create redis-net然后启动主节点docker run -d --name redis-master --net redis-net -p 6379:6379 redis:7.0。启动从节点docker run -d --name redis-slave --net redis-net -p 6380:6379 \ redis:7.0 redis-server --replicaof redis-master 6379注意这里从节点没有映射6379而是6380防止和主节点端口冲突。--replicaof redis-master 6379使用的是服务名前提是两个容器在同一个自定义网络里这在默认bridge网络下是不行的——默认网络不支持容器名DNS解析这也是很多人从节点一直报错找不到主机的原因。另一个经典问题是Redis容器启动后不加载自己的配置。很多人把redis.conf通过-v ./redis.conf:/etc/redis/redis.conf挂进去然后直接docker run redis结果检查配置发现啥都没生效。原因很简单镜像默认启动命令是redis-server压根没读你挂进去的配置文件。正确写法是在命令行最后显式指定配置文件路径比如redis-server /etc/redis/redis.conf或者在docker run后面直接加配置参数。做持久化同样的道理如果不挂载数据目录或者不开启appendonly容器删了缓存数据也就没了。4.3 Docker Compose一个文件拉起整套服务手写多条docker run命令管理多个服务会越来越痛苦尤其是微服务和复杂中间件场景一条条命令复制粘贴非常容易漏参数。Docker Compose的价值就是把这些参数固化到一个YAML文件里一次docker compose up -d全部拉起。需要注意新版Docker官方推荐使用插件方式运行docker compose老版本的docker-compose命令虽然还在但已不是主流。一个典型的Compose文件长这样services: mysql: image: mysql:8.0 container_name: myapp-mysql environment: MYSQL_ROOT_PASSWORD: root123 volumes: - mysql-data:/var/lib/mysql ports: - 3306:3306 healthcheck: test: [CMD, mysqladmin, ping, -h, localhost] interval: 10s retries: 5 redis: image: redis:7.0 container_name: myapp-redis ports: - 6379:6379 app: build: . depends_on: mysql: condition: service_healthy redis: condition: service_started volumes: mysql-data:用Compose有几个细节值得留意。depends_on只保证启动顺序不保证依赖服务已经可用所以依赖了MySQL的应用最好配合healthcheck否则应用启动时数据库还没初始化完成容易报连接失败。同一个Compose项目里的服务默认在一个自定义网络里服务名即域名比如app容器里可以直接用mysql:3306连接数据库不必关心IP。还有个日常操作上的坑改完yml文件要重新docker compose up -d而不是docker compose restart因为restart不会应用新增的服务和网络配置变化。4.4 微服务打包与GitLab部署的实践经验把微服务项目打包成镜像核心是写一个合格的Dockerfile。以Java后端为例最好用多阶段构建先编译再打包最终镜像里只保留运行环境体积能小很多FROM maven:3.9-eclipse-temurin-17 AS builder WORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline -B COPY src ./src RUN mvn package -DskipTests -B FROM eclipse-temurin:17-jre WORKDIR /app COPY --frombuilder /build/target/app.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]第一次COPY只复制pom.xml并预拉依赖就是把容易缓存的层放在前面后面代码变动时不会每次都重新下依赖构建会快很多。配合.dockerignore文件把 target、.git、*.log 排除掉避免把一堆无关文件塞进构建上下文影响构建速度和镜像体积。IDE方面IDEA装好Docker插件后可以直接可视化构建镜像、管理容器配置好远程或本地Docker连接就能一键跑起来对开发调试很友好。如果你用的是PHP或Node这类项目思路一样构建阶段装依赖运行阶段只放产物和运行时尽量避免在最终镜像里保留编译工具链。GitLab的Docker部署同样适合用Compose。社区版镜像gitlab/gitlab-ce启动时需要挂载config、logs、data三个目录并设置external_url比如-e GITLAB_OMNIBUS_CONFIGexternal_url http://gitlab.example.com。端口上要注意容器内HTTP和SSH默认是80和22宿主机如果已经占用要映射成别的端口比如-p 8080:80 -p 2222:22用SSH拉代码时也要对应改成ssh://githost:2222/...。GitLab容器启动很慢首次初始化可能要几分钟到十几分钟这段等待期里不要反复重启容器否则容易把配置写坏。像青龙这类通过容器运行的面板应用依赖装不上或者运行报错的根源大多是容器内缺少编译工具链和对应语言运行时需要在Dockerfile或者Compose环境变量里补上node/python的构建依赖不要在宿主机装完就以为是容器里也有了。容器是隔离环境宿主机的一切都和它无关。5. 常见问题排查与避坑实录5.1 Docker守护进程起不来先怀疑daemon.json“docker服务启动失败”是装完Docker后最打击信心的问题。我见过最多的原因是daemon.json写错某个JSON键值漏了逗号或者配了不存在的镜像加速地址导致dockerd解析配置失败。排查方法很简单sudo journalctl -u docker.service --since today或直接dockerd --debug看日志里报错的最后几行。JSON格式化有个小技巧配置前先用python或jq校验一下格式。cat /etc/docker/daemon.json | jq .如果确认是配置问题改完记得systemctl daemon-reload再systemctl restart docker。还有一种较少见的情况磁盘空间不足/var/lib/docker所在分区满了容器和镜像操作全部失败。用df -h检查完确认后清理/var/lib/docker/tmp、删除无用镜像或迁移数据卷目录都可以解决。5.2 Docker权限错误一个组就能解决的事刚装完Docker执行任何命令都报permission denied ... /var/run/docker.sock几乎可以肯定是当前用户不在docker组里。原因很简单Docker的CLI需要连接守护进程的Unix Socket而这个Socket默认只有root和docker组成员可访问。解决办法sudo groupadd docker # 如果组不存在 sudo usermod -aG docker $USER修改完必须重新登录一次组权限才会在会话里生效或者执行newgrp docker临时切换。需要说明的是放进docker组意味着这个用户拥有root级别的容器控制权因为通过docker可以挂载宿主机任意目录到容器里。所以不要把docker组随便加给不信任的人也不要轻易拿Docker socket去启动不受信任的容器这是安全底线。5.3 网络和端口访问排查从容器内外两侧打通网络不通是排查工作量最大的问题我总结了一套稳定的排查路线。第一步先测试容器之间能否通信进入A容器docker exec -it a bashping B容器的服务名或IP第二步看端口监听netstat -tunlp确认服务监听的地址是不是0.0.0.0第三步查宿主机iptables和云安全组防火墙放行端口第四步在宿主机直接curl容器映射端口curl 127.0.0.1:宿主机端口能通说明容器内部和网络映射正常剩下的问题就在外部防火墙。还有一个容易被人忽略的点很多基础镜像为了控制体积连ping、curl、bash都没有。遇到这种情况不要慌用docker run --rm --net host --pid host -it alpine sh启动一个带工具的调试容器临时接入宿主机网络命名空间就能从外部视角排查网络问题用完自动销毁干净。5.4 资源限制与运维习惯上生产前最后一道手续容器默认没有CPU和内存上限一个出故障的容器可能把宿主机的资源吃光。上生产前建议给所有重要容器加上资源限制docker run -d --name mysql-production \ --memory2g --cpus2 \ -v mysql-data:/var/lib/mysql \ mysql:8.0Compose文件里对应字段是mem_limit和cpus。另外建议开启--restart unless-stopped但不要滥用always除非你确定这个容器需要随Docker引擎自动拉起。日常运维时多看看docker stats实时掌握每个容器的CPU、内存和网络IO随着镜像数量变多还可以部署Portainer等可视化管理工具操作起来更直观。最后再提醒一个从搭建第一天就该养成的好习惯容器不存任何重要数据所有数据卷和配置都做成可迁移的镜像用标签锁定版本Compose文件放在版本控制里。这样不管是升级镜像、迁移服务器还是恢复故障都能用一套文本文件把整个环境恢复出来。我自己从一开始照着教程敲 docker run到现在能独立设计一套微服务的容器化部署方案中间最大的一个认知转变是Docker真正厉害的地方不在“装软件方便”而在于把环境、依赖、配置都变成了可以版本化、可以审查、可以重建的代码。只要镜像和Compose文件在换台机器也能把整套环境原样拉起来。所以遇到部署问题我现在的第一反应永远是先看日志数据卷挂载永远是数据库类服务的第一优先级。这两个习惯能帮你少走很多弯路。
返回列表