ARTICLE DETAIL

资讯详情

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

Docker实战指南:从安装到容器网络与数据卷的避坑手册

Docker实战指南:从安装到容器网络与数据卷的避坑手册 最近频繁有朋友问我Docker的问题新电脑装了Docker Desktop起不来Linux上装好了却总报权限错误MySQL容器跑了几天数据丢了项目里要用Compose搭Redis主从却一直连不上……这些问题我基本都踩过一遍。今天干脆把手里的经验整理一下从安装到部署、再到网络和数据卷的常见坑用一套能直接照做的流程串起来。文章面向刚接触Docker的开发者也适合那些已经会用docker run、但遇到问题不知道怎么排查的运维同学。1. 先搞清楚Docker到底帮你解决了什么问题1.1 镜像和容器你只需要记住两个词很多人学了几天Docker还是分不清镜像和容器其实用做饭来类比就特别直白镜像就是“预制菜包”里面包含运行某个程序需要的全部东西——代码、运行时、系统库、配置文件全都打包到一个只读文件里。而容器就是你把这份预制菜下锅炒出来的那盘菜是一个可以读写、可以运行的实例。同一个镜像可以同时炒出很多盘一模一样的菜每个容器互相独立互不干扰。再往深处说一层Docker利用的是Linux内核的命名空间和cgroups机制这也解释了为什么它比虚拟机轻那么多。虚拟机需要在宿主机里再模拟一套完整操作系统跑起来动辄几个GB的内存占用而Docker容器直接共享宿主机的内核启动一个普通容器往往只需要一两秒。你把它理解成给进程做了一层“隔离舱”而不是给整台机器做“仿真”就完全不会走偏。1.2 为什么现在几乎所有团队都在用Docker早期开发最大的痛点就是“在我电脑上能跑”。Java项目JDK版本不一样可能编译报错Python项目依赖冲突能折腾一天更别提Redis、MySQL、Nginx这些中间件的版本兼容问题。Docker的出现把这层混乱直接统一了Dockerfile把环境固化成了代码任何人拉下来同一份镜像跑出来的行为完全一致。除了环境一致性Docker在前后端联调、CI/CD流水线、弹性扩容上的价值也很明显。比如你写微服务本地起一堆服务依赖机器风扇转得飞起用Docker Compose直接一条命令全部拉起资源占用可控关掉也特别干净。对于运维一个镜像可以同时跑在不同规格的宿主机上不再需要为每台机器单独配置环境。换句话说Docker已经不只是“部署工具”而是现代软件交付的基本单元。2. 环境准备不同系统安装Docker的完整流程2.1 Linux装DockerUbuntu和CentOS两条命令路线Linux下安装Docker最常见的是两条路径使用发行版自带的软件源或者用官方提供的安装脚本。我这里更推荐前一种可控性更强也方便定制版本。Ubuntu/Debian系列先更新索引再安装sudo apt update sudo apt install docker.io -y sudo systemctl enable --now dockerCentOS/RHEL系列先配置Docker官方仓库sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install -y docker-ce docker-ce-cli containerd.io sudo systemctl enable --now docker装完之后用docker version验证一下能看到Client和Server两段信息就说明服务正常。如果执行docker version只显示Client、没有Server大概率是docker服务没起来用systemctl status docker查一下原因。在较老的CentOS 7内核上偶尔会遇到overlayfs兼容性问题升级内核或者把存储驱动切到vfs能暂时解决但我的建议是生产环境尽量用较新的内核版本。2.2 Docker Desktop在Windows/macOS上的安装与常见失败Windows用户装Docker Desktop之前建议先把WSL2准备好。Docker Desktop在Windows下默认使用WSL2做后端没有启用WSL2的话安装过程即便顺利启动时也会遇到各式各样的问题。遇到最常见的错误提示Docker Desktop failed to start because virtualisation support wasnt detected基本能定位到以下几个原因BIOS里没开启虚拟化。开机进BIOS找到“Intel VT-x”或“AMD SVM”这一项把它打开。Hyper-V没有启用。在Windows功能里勾选“Hyper-V”和“虚拟机平台”重启后再试。WSL2没有真正安装。以管理员身份运行wsl --update然后wsl --set-default-version 2。电脑上装了第三方安全软件拦截了Docker的管理员服务。这类情况可以暂时把安全软件退出来再启动。注意即使能启动Docker Desktop如果某个镜像运行异常也要先检查Windows的虚拟化是否被占用比如同机器在跑其他Android模拟器或者VMware这类软件通常会抢占虚拟化能力导致Docker Desktop起不来。2.3 拉不动镜像给Docker配一个可用的镜像加速源国内云服务器或家庭宽带拉取Docker官方镜像经常超时镜像加速基本是唯一实用的解法。Linux上修改/etc/docker/daemon.json{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.net ] }然后重启Dockersudo systemctl restart docker。Windows的Docker Desktop在Settings-Docker Engine里直接改JSON保存后会自动重启引擎。换加速源之后可以用docker pull mysql:8.0测试速度。如果还是慢建议直接搜索那些当前可用的公共加速器或者自行搭建镜像缓存服务这个我后面讲私有仓库时还会提到。3. 日常用得最多的核心操作镜像、容器、数据卷、日志3.1 镜像拉取、容器生命周期和常用参数解析平时操作Docker最绕不开的就是docker run。我用一个MySQL容器来拆解常用参数示例命令docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -v mysql_data:/var/lib/mysql \ mysql:8.0-d表示后台运行不加这个参数容器会直接在前台占用终端。--name给容器起名字后续管理、查看都会方便很多一般都要带上。-p 3306:3306把宿主机的3306端口映射到容器的3306端口。宿主机端口冲突时可以把左边改成一个不冲突的端口比如-p 3307:3306。-e用来传入环境变量镜像内程序会读取这些变量做初始化比如MySQL会用它设置root密码。-v mysql_data:/var/lib/mysql是数据卷挂载把容器内的数据目录映射到一个由Docker管理的卷中防止容器删除后数据丢失。容器生命周期管理我用这套命令就够了docker ps -a # 查看所有容器 docker start mysql8 # 启动已存在的容器 docker stop mysql8 # 停止容器 docker rm mysql8 # 删除容器 docker images # 查看本机镜像列表 docker rmi mysql:8.0 # 删除镜像删除容器前想保留数据的话只要不删数据卷即可如果连着数据卷一起删容器里的数据就真没了。养成习惯生产环境不要用docker rm -v去清理一个不确定是否还有用的容器。容器生命周期里还有一个隐藏考点ENTRYPOINT和CMD的区别。简单说ENTRYPOINT是镜像的“主程序入口”CMD只是给这个入口传的默认参数。比如docker run ubuntu echo hello会执行echo hello而如果镜像里定义了ENTRYPOINT [nginx, -g]你追加的命令行参数会拼到nginx后面。想强制覆盖ENTRYPOINT时可以在命令行加--entrypoint参数。3.2 进入容器、执行命令、查看日志的基本用法调试时最常见的需求是进到容器内部看看进程、配置文件或者网络状态。命令很简单docker exec -it mysql8 bash-it的意思是把当前终端绑定到容器里的bash进程这样就能像在一台瘦身Linux里一样敲命令。如果容器里连bash都没有很多精简镜像只有sh就把bash换成sh。查看日志是排查问题的第一抓手docker logs -f mysql8-f是持续跟踪输出跟tail -f一样。日志默认只显示最近一段历史想从头看可以加--tail 100或者直接在日志文件路径找。容器与宿主机之间拷贝文件的命令是docker cp 宿主机文件 容器ID:/容器内路径 docker cp 容器ID:/容器内文件 宿主机路径我之前调试一个Nginx镜像时就是靠这个命令把宿主机里的配置直接拷进容器省掉了重新构建镜像的过程。3.3 权限错误docker: permission denied的两步修复刚装完Docker的Linux用户执行docker ps大概率会看到permission denied。原因很简单docker socket文件的权限默认只给root用户和docker用户组普通用户没权限访问。解决办法分两步sudo usermod -aG docker $USER newgrp docker执行完newgrp docker可以让当前终端立即加入用户组不用退出重新登录。如果已经远程ssh登录也可以直接断开重连。注意这只适用于可信的本机用户不建议在共享服务器上给所有人都加docker组因为加入docker组基本等同拥有root权限可以通过挂载宿主机目录来提权。4. 高频实战MySQL、Redis主从、GitLab再到微服务部署4.1 用Docker安装MySQL 8.0并配置持久化与字符集MySQL是Docker使用频率最高的软件之一。直接docker run跑一个MySQL其实很容易但很多人后续遇到两个坑一是容器删了数据全没了二是有中文写入报错。这里给出一个生产可用的启动方案docker run -d \ --name mysql8 \ --restartalways \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDMyStrongPwd \ -e TZAsia/Shanghai \ -v /data/mysql/conf:/etc/mysql/conf.d \ -v /data/mysql/data:/var/lib/mysql \ mysql:8.0 \ --character-set-serverutf8mb4 \ --collation-serverutf8mb4_unicode_ci有几个细节值得说清楚。--restartalways让Docker守护进程在容器异常退出时自动拉起它服务器重启后MySQL也会跟着起省心很多。字符集方面MySQL 8.0默认字符集已经不是latin1但显式指定utf8mb4能保证表情符号和生僻字不出乱码。宿主机上的/data/mysql/conf会自动创建想要自定义my.cnf直接往这个目录丢一个配置文件再重启容器即可。如果启动失败常见原因有三个3306端口被宿主机程序占用用ss -lntp检查一下容器内MySQL初始化日志没看完就误判失败用docker logs mysql8看真正的报错服务器内存低于2GBMySQL 8.0初始化时会比较吃力这种情况可以把--restartalways暂时去掉先查日志解决资源问题。4.2 Docker Compose一键拉起Redis主从架构实际项目中一个服务往往依赖多个中间件。每次都手动写一长串docker run太痛苦用Docker Compose把架构描述成YAML文件才是长期方案。以Redis主从为例我常用的编排如下docker-compose.ymlversion: 3.8 services: redis-master: image: redis:7 container_name: redis-master command: [redis-server, --appendonly, yes] ports: - 6379:6379 volumes: - master-data:/data redis-slave: image: redis:7 container_name: redis-slave command: [redis-server, --slaveof, redis-master, 6379] depends_on: - redis-master ports: - 6380:6379 volumes: - slave-data:/data volumes: master-data: slave-data:在文件所在目录执行docker compose up -d两个容器就会一起生成并启动。这里重点讲一个很多人忽略的问题容器间通信。主从配置里我写的是--slaveof redis-master 6379而不是--slaveof 127.0.0.1 6379。原因是在Compose默认的bridge网络里每个服务名本身就是一个可解析的DNS名redis-master会被解析成该容器在Docker内部网络里的IP。如果你在容器内部用127.0.0.1那指向的是这个slave容器自己自然无法连到主节点。验证主从状态的办法是进入容器执行Redis命令docker exec -it redis-slave redis-cli info replication看到role:slave和master_host:redis-master基本就正常了。从节点要连不上优先检查主节点的bind配置是否限制了内网访问以及Compose里是否遗漏了自定义网络。4.3 GitLab社区版容器部署与资源控制团队内部搭Git仓库很多人图省事直接GitLab官方包安装结果依赖环境一堆问题。用Docker部署GitLab社区版其实更清爽这里给一个最小可用配置docker run -d \ --name gitlab \ --restartalways \ -p 80:80 \ -p 443:443 \ -p 2222:22 \ -v /data/gitlab/etc:/etc/gitlab \ -v /data/gitlab/log:/var/log/gitlab \ -v /data/gitlab/data:/var/opt/gitlab \ gitlab/gitlab-ce:latest启动后GitLab初始化要等上一阵子可以用docker logs -f gitlab观察看到 “GitLab is ready” 提示再去浏览器访问。注意-p 2222:22是把宿主机的2222映射到容器的22端口因为宿主机22常常被SSH占用GitLab里克隆仓库时就要写ssh://git宿主机IP:2222/用户/项目.git。如果你不想处理这个端口映射可以直接-p 22:22前提是宿主机22没被占用。GitLab镜像比较大运行也吃内存官方建议至少4GB可用内存。如果服务器内存有限可以在环境变量里禁用Prometheus和Grafana来省资源-e prometheus_enabledfalse -e grafana_enabledfalse我第一次在2GB内存的小机器上跑GitLab结果系统卡死之后加了这个参数才勉强流畅运行。4.4 IDEA一键打包Docker镜像并部署微服务微服务开发中用IDEA的Docker插件来打包镜像非常顺手。项目根目录写一个DockerfileFROM openjdk:17-jdk-slim LABEL maintaineryourname WORKDIR /app COPY target/xxx.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]然后在IDEA的Run Configuration里添加Dockerfile运行方式指定Dockerfile路径和镜像名称比如registry.example.com/demo/xxx-service:1.0.0点击运行就会自动完成docker build。注意首次使用需要让IDEA能连接Docker在Settings-Build Tools-Docker里配置连接本机Docker Desktop或者在远程服务器上暴露的tcp端口。构建完成之后推送镜像docker push registry.example.com/demo/xxx-service:1.0.0生产环境拉取该镜像并运行docker run -d --name xxx-service -p 8080:8080 \ -e SPRING_PROFILES_ACTIVEprod \ --network your_app_network \ registry.example.com/demo/xxx-service:1.0.0如果微服务之间有服务发现需求记得把它们放到同一个自定义bridge网络里否则服务名无法互相解析。还有一点Docker构建镜像时COPY target/xxx.jar这一步要求先执行过Maven打包否则会直接报文件不存在。我经常犯这个错于是习惯于在构建之前先跑一次mvn clean package或让IDEA执行前置构建步骤。5. 网络与数据存储最容易翻车的两个环节5.1 Docker网络模型与网络不通排查思路Docker最常见的网络不解问题基本绕不开几种模型。默认bridge网络是容器与宿主机之间有NAT容器有独立IP宿主机端口映射才能从外部访问。host网络模式下容器直接使用宿主机的网络栈端口不需要映射性能好但隔离性差。none模式适合完全不依赖网络的容器一般不常用。自定义bridge网络则支持容器间通过服务名通信。排查网络不通时我有一套固定顺序先看两个容器是否在同一个自定义网络里docker network inspect 网络名。从容器A里ping容器B的服务名确认DNS解析是否正常。检查目标容器的端口是否真的在监听docker exec 容器B ss -lntp。检查服务是否绑定到了非loopback地址。很多中间件默认只监听127.0.0.1在容器里这样绑定的话外部容器就无法访问需要修改配置让它监听0.0.0.0。另外宿主机外部要访问容器服务必须做端口映射这一步忘了是新手最容易犯的错误-p 宿主机端口:容器端口。5.2 数据卷三种挂载方式的选型与避坑持久化是容器化应用的高频问题。Docker里数据持久化主要有三种方式bind mount把宿主机某个目录挂给容器。如-v /data/mysql:/var/lib/mysql好处是数据文件直接可见备份方便适合数据库、配置等场景。命名卷-v myvolume:/var/lib/mysql数据存放在/var/lib/docker/volumes/myvolume下Docker管理适合日志、临时文件等。tmpfs数据只存在内存中适合跑临时任务容器停止后数据就没了。我自己的选型原则是数据库和有状态服务用bind mount日志和缓存用命名卷临时计算任务直接用tmpfs。bind mount时要特别注意权限问题容器内外uid不一致可能导致容器没权限读写挂载目录这时要么改宿主机目录的属主要么在run命令里加-u指定用户。5.3 私有镜像仓库一条命令搭建自己的Registry镜像下载慢、团队内部分发镜像不方便一个轻量级私有仓库能解决很多问题。Docker官方提供了registry镜像部署方式极简docker run -d --name registry \ -p 5000:5000 \ -v /data/registry:/var/lib/registry \ --restartalways \ registry:2推送镜像时先把镜像打上仓库地址标签docker tag myapp:latest 宿主机IP:5000/myapp:latest docker push 宿主机IP:5000/myapp:latest拉取时也要带上仓库地址docker pull 宿主机IP:5000/myapp:latest注意默认的registry是明文HTTP传输。如果远程服务器要访问私有仓库需要在每台客户端的 daemon.json 里把该地址加进insecure-registries否则Docker会拒绝推送和拉取。想上HTTPS就得挂证书和Nginx做反向代理这个按需配置即可。6. 进阶场景青龙依赖管理、GPU容器与其他热门用法6.1 容器化部署青龙面板时的依赖安装方法青龙面板这类定时任务管理工具容器化部署后经常出现“脚本依赖安装失败”的反馈。依赖问题是容器环境与宿主机环境隔离导致的不能指望宿主机的Node.js和Python装上之后容器内也有。标准做法是进入容器后安装docker exec -it qinglong bash cd /ql/scripts npm install pnpm -g pip install requests或者在宿主机上用docker exec直接调容器内的Shelldocker exec -it qinglong bash -c cd /ql/scripts npm install更推荐的方式是把它固化进自定义镜像以青龙官方镜像为基础写Dockerfile里提前安装好项目需要的Node模块和Python包这样每次创建容器都自带依赖不需要反复手工安装。说到青龙切记不要把它用于违反服务条款的自动化脚本尤其是涉及灰产的场景。我这里只讲技术层面的依赖管理思路。6.2 用GPU跑容器NVIDIA Container Toolkit配置与验证容器里跑大模型、深度学习推理已经非常普遍。默认情况下Docker容器无法读取宿主机GPU需要NVIDIA Container Toolkit来打通这层。Ubuntu上的安装步骤sudo apt install nvidia-container-toolkit sudo systemctl restart docker运行容器时增加GPU参数docker run --rm --gpus all \ -v /models:/models \ vllm/vllm-openai:v0.27.1 \ --model /models/qwen3-embedding-0.6b如果--gpus all报权限或找不到设备用docker run --rm -it --gpus all nvidia/cuda:12.2.0-base-ubuntu22.04 nvidia-smi验证一下容器内能否看到GPU信息。看到显卡型号和显存就说明NVIDIA软链是通的。在宿主机单独执行nvidia-smi正常不代表容器内正常这个区别很多人没搞清楚。6.3 从DVWA靶场到HadoopDocker在学习和实验环境中的价值Docker轻量、可快速销毁的特性天然适合搭学习实验环境。比如做安全测试学习时用Docker拉起DVWA靶场docker run -d -p 8080:80 vulnerables/web-dvwa访问宿主机8080端口就能开始练习测完直接docker rm销毁不留残留。同样地Hadoop这类复杂的分布式系统官方或社区维护的镜像能让你在单机上快速起一个多容器集群学习核心原理的时候非常方便。ROS2等机器人方向的环境配置一直比较繁琐但容器里跑ROS2 humble和micro-ROS agent正在成为新趋势让环境复现和设备验证变得简洁很多。这类场景让我愿意向大家推荐Docker为“学习利器”因为它能让你放得开手去试错试错了删除容器重来成本几乎为零。7. 常见问题排查速查表把这几类问得最多的问题整理成一张速查表遇到类似情况直接对照问题现象最可能的根因快速解决办法Docker Desktop启动失败提示virtualisation support not detectedBIOS虚拟化没开或Hyper-V/WSL2未启用开BIOS的VT-x/SVM启用Windows可选功能更新WSL2docker ps提示permission denied当前用户不在docker用户组sudo usermod -aG docker $USER后重新登录拉取镜像超时或极慢未配置镜像加速源修改daemon.json配置registry-mirrors并重启Docker容器删除后MySQL数据丢失未挂载数据卷启动时加-v 宿主机目录:/var/lib/mysqlRedis主从连不上从节点配置了127.0.0.1使用主节点容器名作为可解析地址容器外部访问不到服务未映射宿主机端口加-p 宿主机端口:容器端口容器之间ping不通不在同一自定义网络docker network create并在run时指定--networkgitlab容器内存占用过高默认监控组件过多添加环境变量关闭prometheus和grafana容器内读不了挂载目录uid权限不一致修改宿主机目录权限或容器启动时指定-u排查问题的通用心法就三条先看日志、再验网络、最后查权限。道理很简单但照着这个顺序执行能省下大量盲目试错的成本。一位前辈跟我说过一句话我至今记得Docker学得好不好不看你记得多少命令而是看你能不能准确地描述“哪个进程在哪个隔离环境里监听了哪个端口数据写到了哪里”。把这条主线想透绝大部分Docker疑问都能自己找到答案。我自己操作中也确实如此——最初的坑都是因为没搞清网络命名空间和数据卷的边界在哪里。多拿几个容器练手拆掉重建几次顺手之后你也会觉得它就是一套逻辑严谨的“装鸡蛋的盒子”只是一旦真正理解了它的边界你往里面装什么都心里有数。
返回列表