ARTICLE DETAIL

资讯详情

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

Docker从入门到部署的常见坑:虚拟化、镜像源与网络排查

Docker从入门到部署的常见坑:虚拟化、镜像源与网络排查 我平时接到的Docker求助有一大半都发生在安装和第一天使用这个阶段。很多人把Docker笔记当作一堆命令的堆砌但实际上真正让新手放弃Docker的不是命令难记而是装完起不来、拉镜像拉不动、容器之间网络不通这三座大山。这篇笔记我整理的是自己从Windows本到Linux服务器上反复踩过的坑涵盖了virtualization support not detected这类启动报错、镜像下载慢、docker网络不通、权限错误以及MySQL、Redis、GitLab这类最常见应用的部署实录。适合刚装好Docker、正准备往里部署第一个服务的人也适合已经被各种报错折磨到想卸载的人——大部分问题其实都有固定套路可以解。1. 为什么你的Docker Desktop会卡在virtualization support not detected这个报错基本是Windows用户安装Docker Desktop的第一道坎。很多人一看到virtualization support not detected就以为是软件装坏了重装好几遍都是同样的结果其实这个报错的含义很明确Docker Desktop本身没问题是你的Windows系统没有把虚拟化能力开放给Docker使用。Docker Desktop在Windows上跑容器靠的是Windows的虚拟化底层——Hyper-V或者WSL 2后端两者都需要CPU虚拟化技术VT-x/AMD-V真正处于开启状态。1.1 这个报错的真实含义不是你没装对而是虚拟化没用上我先说结论遇到这个报错时99%的情况不是Docker Desktop安装包的问题而是下面四个条件里至少有一个没满足。CPU虚拟化VT-x/AMD-V在BIOS/UEFI里没有开启Windows的虚拟化相关功能没有启用Hyper-V或虚拟机平台WSL 2的内核组件未安装或版本过旧系统里存在和Hyper-V冲突的第三方虚拟化软件比如旧版VirtualBox、某些安卓模拟器判断CPU虚拟化是否开启最直接的办法是打开任务管理器切到性能页签在最下方看虚拟化这一项。如果显示已启用说明BIOS层面没问题如果显示已禁用就得重启进BIOS找到Intel Virtualization Technology或SVM ModeAMD平台之类的选项打开。不同主板厂商的BIOS菜单位置差异很大但关键词基本就是Virtualization、VT-x、SVM这几个搜自己主板型号加这些关键词就能找到具体位置。真正容易被忽略的是Windows功能这一层。有些人BIOS里的虚拟化明明打开了任务管理器也显示已启用但Docker Desktop依然报错。这时候需要在控制面板 - 程序 - 启用或关闭Windows功能里勾选Hyper-V和虚拟机平台两项同时把适用于Linux的Windows子系统也勾上然后重启。这里有个细节如果你用的是Windows 10家庭版控制面板里可能根本看不到Hyper-V选项这是系统版本限制不是你没有权限。家庭版想用Docker Desktop推荐直接走WSL 2后端先装好WSL再装Docker Desktop让Docker依赖WSL的虚拟化平台而不是完整的Hyper-V绕开版本限制。1.2 WSL 2后端激活Docker Desktop依赖的真正底层Docker Desktop现在的默认后端就是WSL 2。所谓WSL 2可以理解成一个轻量级的、和Windows深度集成的Linux虚拟机Docker Desktop把容器运行时丢进这个Linux环境里跑性能和原生Linux差距很小。激活WSL 2只需要在管理员权限的PowerShell或CMD里执行wsl --install这条命令会自动安装WSL 2的内核、默认的Linux发行版并把WSL版本设为2。装完后执行wsl --set-default-version 2确保后续创建的发行版都走WSL 2。然后重启再打开Docker Desktop设置里确认Use the WSL 2 based engine是勾选状态。我遇到过一种容易被忽略的情况执行wsl --install时报错说需要启用虚拟机平台功能但控制面板里虚拟机平台明明勾上了。这时候多半是Windows功能虽然勾了但系统还没完全生效或者需要手动执行dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestartdism命令会强制把虚拟机平台功能写进系统镜像配置里比在图形界面勾选更可靠。执行完后务必重启再检查wsl --status确认WSL版本。1.3 卡在启动界面和内核过期的隐藏原因还有一类报错是Docker Desktop能启动但一直转圈停在Starting the Docker Engine...最后弹一个小红框。这往往是WSL的内核版本太老或者某个Linux发行版在Docker启动时被占用导致的。排查方法是先关闭Docker Desktop然后在命令行执行wsl --shutdown这个命令会把所有WSL实例全部停掉清空资源占用。再启动Docker Desktop很多时候就直接起来了。如果还不行检查WSL内核更新在GitHub的微软官方WSL2内核发布页下载最新的msi包安装或者直接执行wsl --update让系统自己更新内核。更新完继续执行wsl --shutdown再启动Docker。这里想多说一句如果你之前装过VMware Workstation或者旧版VirtualBox它们默认会把宿主机的Hyper-V功能禁用导致Docker Desktop起不来。不是让你卸载虚拟机软件而是在虚拟机软件设置里关闭它自带的虚拟化引擎或者切换成兼容Hyper-V的模式。VMware从15.5版本开始支持与Hyper-V共存如果你的版本太老确实会和Docker冲突。2. 镜像拉不动、下载龟速给Docker配上合适的镜像源镜像下载慢是Docker入门的第二个大坑。默认情况下Docker会从Docker Hub拉取镜像国内访问这个仓库的延迟和丢包率都很难受一个几百MB的镜像卡几个小时是常态。很多人第一反应是去搜加速器然后随便复制一段配置进daemon.json结果发现要么过期了要么加了配置反而拉不动。2.1 镜像下载慢的本质Docker Hub在国内的连通性问题Docker Hub的官方仓库部署在海外国内直连时链路质量不稳定这是CDN和网络链路层面的问题和你家宽带多少兆没关系。Docker客户端拉镜像是分层的大镜像几十上百个layer任何一个layer下载超时都会导致整个拉取失败或反复重试。所以镜像加速的核心思路不是换网络而是让Docker客户端去访问一个或几个国内可达性好的镜像仓库这些仓库会缓存Docker Hub上的热门镜像下载时直接从缓存分发。2.2 daemon.json的正确写法和生效逻辑配置镜像源的位置在Docker守护进程的配置文件daemon.jsonLinux下通常在/etc/docker/daemon.jsonWindows下是C:\ProgramData\Docker\config\daemon.jsonDocker Desktop也可以在设置界面的Docker Engine页签里直接编辑。配置格式如下{ registry-mirrors: [ https://docker.1ms.run, https://docker.xuanyuan.me ] }修改完daemon.json后必须重启Docker才能生效。Linux执行systemctl restart dockerWindows在Docker Desktop的Troubleshoot图标里选Restart。验证是否生效可以执行docker info在输出信息里找Registry Mirrors字段能看到你配置的地址列表就是生效了如果这一栏为空说明daemon.json没被读到最常见的原因是JSON格式有错——比如多了一个逗号或者用了中文引号。我个人的建议是不要只配一个源至少写两个作为备选。因为现在可用的镜像加速站点变动很频繁某个站点可能前一天还能用后一天就停止了对Docker Hub的缓存服务。配置多个源的好处是Docker拉取镜像时会自动逐个尝试上一个失败不会阻塞整个拉取过程。至于具体填哪些地址这里不给固定清单因为时效性太强。你可以搜一下当前还在活跃的公共Docker镜像加速服务优先选择有明确服务说明、支持HTTPS的站点。2.3 镜像源配置的边界不是所有镜像都能加速有个容易混淆的点registry-mirrors只对Docker Hub上的镜像有效也就是你直接执行docker pull nginx、docker pull mysql:8.0这类不带仓库地址前缀的镜像。如果你从其他私有仓库或第三方仓库拉镜像比如docker pull gitlab/gitlab-ce这种虽然也在Hub上但如果你要从一个特定的云厂商镜像仓库拉就需要在镜像完整名称里带上仓库域名这个加速配置是不起作用的。另外临时拉取某个镜像也可以直接指定仓库地址比如docker pull docker.m.daocloud.io/library/mysql:8.0但以我个人经验daemon.json里配好加速源才是正路临时指定容易搞混镜像名而且每个镜像都要手动加前缀维护成本高。3. 容器网络不通从docker0到iptables的完整排查思路镜像能拉下来了容器也跑起来了然后进入第三个深坑网络不通。这里我梳理过的绝大多数问题就是三类形态容器能启动但宿主机访问不到容器里服务的端口容器里ping不通外网apt-get update全都超时同一台机器上多个容器之间互相访问不了或者容器访问宿主机服务失败3.1 先从端口映射说起容器IP和宿主机IP的根本区别理解Docker网络首先要分清容器IP和宿主机IP。容器不是一个独立机器它本质上是一个隔离的进程环境有自己独立的网络命名空间。默认情况下容器内部的IP是Docker网桥分配的比如172.17.0.2这个地址只有Docker宿主机自己以及同一网桥上的其他容器能访问局域网里的其他电脑根本不知道这个IP存在。所以想让外部访问容器里的服务必须做端口映射也就是把容器端口暴露到宿主机的某个端口上启动命令里的-p参数干的就是这件事docker run -d --name nginx -p 8080:80 nginx这里的8080:80表示把宿主机的8080端口映射到容器的80端口。启动后其他机器访问宿主机IP:8080就能到达容器里的Nginx。每次排查我明明启动成功了为什么访问不了的问题第一步永远是确认-p参数没有写反。-p 8080:80和-p 80:8080意义完全不同很多小白写的反了然后访问宿主机的8080端口自然什么都没有。3.2 容器内网和外网都不通的排查链路我先说说容器内网不通这件事。默认情况下容器使用bridge网络所有容器会挂在一个叫docker0的虚拟网桥上。同一宿主机上的容器之间可以通过docker inspect 容器名查到的容器IP互相访问。如果两个容器之间ping不通绝大多数情况是它们不在同一个bridge网络上。比如用docker run启动的容器默认挂在docker0而用docker compose启动的服务会默认创建一个独立项目网络其他容器自然访问不到。排查分三步。第一步进入容器看看网络配置docker exec -it 容器名 sh cat /etc/resolv.conf ip addr如果/etc/resolv.conf里DNS是空的或者指向了一个不可达的地址外网肯定不通。第二步在宿主机上查看iptables的转发规则是否正常iptables -L -n | grep DOCKER如果Docker的链不存在或者规则不对可能是因为你在装Docker之前或之后手动改过防火墙策略把Docker的流量拦截了。第三步检查宿主机内核的IP转发开关sysctl net.ipv4.ip_forward返回0说明包转发被禁用了容器即使拿到IP数据包也转发不出去访问外网必然失败。临时开启用sysctl -w net.ipv4.ip_forward1要永久生效需要在/etc/sysctl.conf里加一行net.ipv4.ip_forward 1。这里有个容易忽略的坑如果服务器本来装了firewalldDocker安装时会自动往iptables里插入规则但firewalld的重载操作可能会清掉Docker的规则导致容器网络突然全断。这时候重启Docker是最快的恢复手段systemctl restart docker但更根本的解法是别让firewalld和Docker抢iptables规则或者在firewalld里放行Docker使用的网段。3.3 容器访问宿主机服务的方式容器里需要连接宿主机上跑的服务比如宿主机装了MySQL容器里的应用要连这个MySQL这个场景很常见。我见过不少人硬编码宿主机的内网IP其实有一个更稳的做法在容器的网络里宿主机的地址就是网关地址。可以通过ip route查看容器内的默认网关通常就是172.x.x.1这个地址在容器里就代表宿主机。所以你的应用配置数据库地址时填这个网关IP就行不用关心宿主机到底有几个网卡、IP会不会变。如果是在docker compose里编排多个服务更推荐用服务名互相访问。compose会为每个服务创建一个DNS记录容器里直接用另一个服务的名字作为主机名就能解析到对应IP。这一点在部署微服务项目时尤其重要依赖服务名而不是脆弱的容器IP迁移和扩容时才不用修改一堆配置。4. 权限错误、服务启动失败Linux环境下的Docker自检清单从Windows切到Linux服务器上操作Docker最常见的问题不是网络而是权限。docker命令明明执行了报错却是一长串permission denied。这个问题的根源在于Docker守护进程以root身份运行而普通用户默认不在Docker的用户组里。4.1 权限错误的三种形态和解法第一种是执行任意docker命令都报没有权限解法是把当前用户加入docker组sudo usermod -aG docker $USER newgrp docker这里有个细节修改用户组之后那个终端会话还不会立刻生效必须重新登录或者执行newgrp docker切换会话的组身份不然你会以为自己没改成功。第二种是容器内访问挂载目录volume报Permission denied。比如你给MySQL容器挂了一个数据目录容器启动后MySQL无法写文件这通常是宿主机目录的权限和容器内进程的UID不匹配导致的。MySQL容器内的进程默认以mysql用户运行UID是999而你在宿主机创建的数据目录所有者和组都是root容器内自然没权限写。解法是调整宿主机目录的所有权让容器内进程的用户能写sudo chown -R 999:999 /your/mysql/data这里的999就是MySQL容器内mysql用户的UID。每个镜像的内部用户UID不同先用docker exec 容器名 id查看再改宿主机目录权限匹配上了就不会再报权限错误。第三种是挂载Docker Socket时提示权限问题。有些容器比如Portainer这类管理工具需要挂载/var/run/docker.sock才能与宿主机Docker通信。如果你用了rootless模式或者Docker Socket的组权限被改过容器里访问这个socket就会被拒绝。最简单的处理是确保socket文件的所有者组是docker并且当前用户在docker组里。4.2 服务启动失败别急着翻日志先看systemd状态Linux上Docker服务启动失败正确排查顺序很重要。第一步永远是用systemd检查服务状态systemctl status docker如果输出里显示Active: failed再执行journalctl -u docker.service --since today查看服务最近的日志。我遇到过的启动失败原因大概有几类dockerd无法绑定到iptables规则通常是之前Docker未完全关闭socket还在占用Docker的存储驱动和内核不对付报overlayfs相关错误多见于较旧的CentOS内核daemon.json里配置了无效的镜像源导致守护进程启动时校验配置不通过有个高频情况改了daemon.json后重启Docker结果启动直接失败。原因基本是JSON里某个字段写错了或者镜像源地址格式不对。这时候可以先备份原配置然后用一个最小的daemon.json排除问题{ log-driver: json-file }注意检查日志的末尾几行通常会有具体失败原因的英文描述再根据报错去处理。如果启动失败是因为上次的容器没正常退出执行rm -f /var/run/docker/containerd/containerd.pid清掉残留pid文件再启动。4.3 老版本CentOS升级Docker的常见坑CentOS 7自带或者旧版安装的Docker一般是1.13或18.x时代的老版本这类版本在新内核上经常出现存储驱动不兼容、cgroup配置错误的问题。升级思路不是覆盖安装而是先卸载干净再装新版。sudo yum remove -y docker docker-common docker-selinux docker-engine sudo rm -rf /var/lib/docker注意删掉/var/lib/docker这一步谨慎操作这里存着所有本地镜像和容器数据如果里面有需要的镜像升级前先docker save备份。删掉之后安装新版Docker用官方源或者阿里云的CentOS仓库都可以。安装完成后同样执行systemctl start docker验证。CentOS 7默认的内核版本是3.10跑新版本Docker基本没问题但如果你要用一些依赖新内核特性的功能比如io_uring或者某些文件系统快照功能还是建议升级内核。5. 典型应用部署实录MySQL、Redis主从、GitLab社区版Docker的学习曲线过了一半之后大多数人开始部署真实应用了。这里我选了三个最有代表性的MySQL 8.0、Redis主从、GitLab社区版。这三个分别代表了有状态数据服务的持久化、多节点协作、以及重资源应用在容器里的资源规划。5.1 MySQL 8.0容器化数据持久化和编码两个关键点MySQL装上能用不难难的是删了容器数据还在。务必挂载数据卷否则docker rm之后数据就没了。一份稳妥的启动命令如下docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDyourpassword \ -e TZAsia/Shanghai \ -v /opt/mysql8/data:/var/lib/mysql \ -v /opt/mysql8/conf:/etc/mysql/conf.d \ mysql:8.0这里我把两个目录挂了出来数据目录和配置目录。配置文件目录挂载的好处是改配置不用进容器直接在宿主机conf目录下新建或修改my.cnf然后重启容器即可。注意挂载配置文件时宿主机的文件必须真实存在而且容器内MySQL会对配置文件权限做校验如果权限过于开放所有人可写MySQL会拒绝使用这是纯MySQL的安全机制不是Docker报错。MySQL 8.0默认的字符集是utf8mb4但如果你的数据来源于一些旧系统可能需要在配置文件里显式设置字符集和排序规则[mysqld] character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci另外8.0版本默认的认证插件是caching_sha2_password如果你的应用连接组件比较老比如PHP 7.1之前版本连不上可以在创建用户时指定认证插件CREATE USER app% IDENTIFIED WITH mysql_native_password BY password;这个坑在部署老应用时很常见值得记录下来。5.2 Redis主从复制容器编排里的Link关系Redis做高可用一个master至少配一个slave。容器化部署时主从之间通过--link或者自定义网络连接。早期的docker run方式是docker run -d --name redis-master redis:7 redis-server --appendonly yes docker run -d --name redis-slave --link redis-master:master redis:7 \ redis-server --slaveof master 6379注意--slaveof master 6379里的master是--link设置的别名容器内Redis会把这个主机名解析到master容器的IP。现在的推荐做法是使用docker compose在同一个网络里用服务名通信配置更可读且不用考虑容器的启动顺序。不过Redis主从里有个常见问题主从都起来了但slave一直处于连接状态但无法同步。排查思路是先看slave的日志确认master的IP和端口是否可达然后检查master是否开启了protected-mode如果master在容器里运行而宿主机的Redis端口没映射到容器内slave访问的6379就是宿主机的Redis而不是容器的这个混淆造成了很多主从不工作的假故障。5.3 GitLab社区版资源规划才是第一要务GitLab是出了名的吃内存官方推荐至少4GB内存但实际上2GB内存跑起来非常勉强动不动就502。部署命令基本每个人都见过sudo docker run -d \ --hostname gitlab.example.com \ -p 443:443 -p 80:80 -p 22:22 \ -v /opt/gitlab/config:/etc/gitlab \ -v /opt/gitlab/logs:/var/log/gitlab \ -v /opt/gitlab/data:/var/opt/gitlab \ gitlab/gitlab-ce:latest我要强调的是如果服务器内存只有2GB部署前一定要在GitLab配置里做内存限制。在挂载的config目录里的gitlab.rb里设置unicorn[worker_processes] 2 sidekiq[max_concurrency] 5 postgresql[shared_buffers] 256MB prometheus_monitoring[enable] false然后docker exec -it gitlab gitlab-ctl reconfigure让GitLab重新加载配置。不关Prometheus的话光监控组件就能吃掉数百MB内存。GitLab首次启动会做初始化等容器状态变成healthy需要几分钟甚至十几分钟看到正在启动不要急着反复重启耐心等日志里出现gitlab Reconfigured!才算就绪。6. 容器运维里那些文档里不会写的实操心得走到这一步Docker基本算会用并且能运作了。最后这部分是我平时使用Docker过程中沉淀下来的一些小众但非常实用的操作每条都对应过真实的问题场景。6.1 进入容器和查看日志的正确姿势很多人习惯用docker attach进入容器但attach是连接到一个正在运行的容器的终端如果你的容器入口命令是一个交互式shellattach之后按CtrlC会直接把容器主进程给停掉。更安全的方式是docker exec -it 容器名 shexec是在容器里新起一个进程不会影响容器主进程的生命周期怎么退出都无所谓得养出这个习惯。看日志也有讲究。docker logs 容器名默认只展示最近几十分钟的输出如果想看完整日志docker logs --tail 200 -f 容器名--tail 200只显示最后200行-f持续跟踪输出。如果容器已经删了但日志还没删除可以去/var/lib/docker/containers/容器ID/目录下找*-json.log文件用普通文本工具直接查看。这常用于定位那种容器启动瞬间崩日志还没来得及打印的场景。6.2 镜像瘦身与IDEA打包Docker镜像打镜像最常用的Dockerfile编写原则一个是尽量把频繁变动的层放后面好利用构建缓存另一个是把多个RUN合并成一条减少镜像层数。比如一个Java应用镜像FROM openjdk:17-jdk-alpine WORKDIR /app COPY target/app.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]配合Maven或IDEA的Docker插件在IDEA里可以直接右键Dockerfile文件执行Build它在信任Docker Desktop的前提下会把构建上下文和Dockerfile一起发送给守护进程。这个流程不复杂但很多人卡在构建上下文过大导致构建慢、或者打包时把本地target目录的旧jar一起传进去。解决方法是确保.dockerignore文件存在把target/、.git/、*.iml这类不需要的文件过滤掉构建上下文小了构建速度立刻提上来。6.3 备份、迁移和日常清理容器数据备份的正确姿势核心思想是把容器挂载的数据卷打包。可以用一个临时容器来打包docker run --rm -v /opt/mysql8/data:/data -v /backup:/backup ubuntu tar czf /backup/mysql-data.tar.gz -C /data .--rm指定用完即删打包完成后临时容器自动清理。迁移到新机器时把tar包带去再用同样方式解压到新数据目录即可。整个备份过程中MySQL如果还在写入数据可能不一致稳妥做法是先在业务低峰期用mysqldump逻辑导出再做文件打包。日常清理方面docker system df可以查看镜像、容器、卷、构建缓存占用了多少空间。执行docker system prune -af会清理所有停止的容器、未使用的网络、悬空镜像和构建缓存。-a慎用它会把所有未被运行中容器引用的镜像一起删掉我建议平时只执行docker system prune -f --volumes保留镜像清理容器和卷。卷有时会被误删如果有重要的数据卷清理前先docker volume ls确认一下。还有个常用命令是docker stats实时查看所有运行中容器的CPU、内存、网络、磁盘IO占用。部署了GitLab这类资源大户之后我会保持这个命令开在旁边窗口一旦内存暴涨能立刻发现是哪个容器在吃资源再决定是扩容还是调整容器内存限制。毕竟容器虽然隔离了进程但不隔离宿主机资源一个容器写爆磁盘能拖垮整台机器上的所有服务这种事故我在生产环境见过不止一次。至于限制容器资源最简单的做法是启动时加参数docker run --memory512m --cpus0.5 -d nginx--memory限制内存总量--cpus限制CPU核数。对于已经在运行的容器可以用docker update动态调整docker update --memory1g --memory-swap1g 容器名--memory-swap最好和--memory保持一致否则容器在内存压力下会使用swap性能骤降还不好排查。说到底Docker没那么玄乎。它给你的是一套标准的打包运行环境的手段你真正要养成的是遇到问题先分层的排查习惯先看Docker引擎是否健康再看容器是否真正在运行再看网络通了没有最后才是进容器看应用日志。顺着这条链路捋一遍大部分问题都能定位到具体环节。这大概也是我从一次次被报错折磨到能淡定处理的心路收获吧。
返回列表