ARTICLE DETAIL

资讯详情

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

Docker实战避坑指南:从安装失败到容器化部署

Docker实战避坑指南:从安装失败到容器化部署 “docker desktop failed to start because virtualisation support wasn‘t detected”这句话我在过去半年里至少听过二十遍。同事也好、网友也好十个人装 Docker Desktop有四个卡在这一步剩下三个卡在镜像拉不动真正把这只鲸鱼用起来的反而没几个。这篇我不打算再写那种“先讲容器化概念、再讲镜像和容器区别”的教科书式入门。既然你要装 Docker、你在搜索 docker 安装教程、docker desktop 安装失败、docker 镜像下载慢、docker 网络不通那我们就直接按真实操作的顺序来先让 Docker 跑起来再让它跑得动最后让它跑得稳。顺便把我这些年踩过的坑一个个扒给你看。为什么标题说“你可以永远相信这只鲸鱼”因为 Docker 的确能在你环境一团糟的时候给你一个干净、可复现的运行环境但这只鲸鱼脾气也不小摸清它的脾气之前摔几次跟头很正常。本文按 Windows 为主、Linux 为辅的场景来写如果你用的是 Mac大部分命令和思路同样适用。1. 先让鲸鱼醒过来Windows装Docker Desktop的虚拟化拦路虎1.1 安装前先别急着点Exe三项环境自检很多人拿到 Docker Desktop 安装包双击下一步然后重启接着就看到了那个经典的报错virtualization support not detected。这时候的第一反应是“重装”其实不用问题大概率出在系统虚拟化能力没打开。装之前我建议先做三件事全程五分钟。第一打开任务管理器切到“性能”选项卡点左侧“CPU”看右下角有没有“虚拟化已启用”。如果显示“已禁用”不用再往下看了直接进 BIOS/UEFI 开启 Intel VT-x 或 AMD-V。不同品牌主板进 BIOS 的快捷键不太一样华硕一般是 Del 或 F7联想笔记本常见是 F2戴尔是 F2 或 F12。到了 BIOS 之后找“CPU Configuration”或“Advanced”下的“Intel Virtualization Technology”改成 Enabled保存重启。这一步不做后面一切免谈。第二Windows 功能里把两个开关打开控制面板 - 程序 - 启用或关闭 Windows 功能找到“适用于 Linux 的 Windows 子系统”和“虚拟机平台”都勾上。如果你用的是 Windows 11通常还有“Hyper-V”也顺手勾上。新版 Docker Desktop 默认基于 WSL2 后端这套组合缺一不可。第三确认系统版本。Windows 10 2004 及以上、Windows 11 任意 64 位版本都可以Windows 10 家庭版也能装因为 WSL2 本身不要求必须 Pro 版。这一点卡住不少人以为家庭版装不了 Docker实际是可以的。这三项检查完了重启一次电脑再打开 PowerShell 跑一句wsl --status正常情况下你会看到默认版本是 2。如果没安装 WSL 或版本不对执行wsl --install wsl --set-default-version 2这里我想多提一句很多教程让你直接安装 Docker Desktop却没说它依赖 WSL2结果报错的时候一脸懵。先确认 WSL2 没问题再装 Docker顺序反了就会觉得 Docker 是个“大坑”。1.2 Virtualization Support Not Detected的完整排查链路如果你已经装好了 Docker Desktop启动时弹窗报“virtualisation support wasn‘t detected”别急着卸载重来按下面这条链路一步步查。先看任务管理器的虚拟化状态。如果是“已启用”但 Docker 依然报错那问题就不在 BIOS而在 Hyper-V 或虚拟机监控程序的启动状态。打开管理员权限的 PowerShell执行bcdedit /set hypervisorlaunchtype auto这一条命令的作用是把 Windows 的 Hypervisor 设置为开机自动启动。Docker Desktop 需要 Hyper-V 平台提供的虚拟化能力有时候这个启动类型没设对就会误报“虚拟化不支持”。执行完记得重启。其次检查 WSL 内核是否过老。Docker Desktop 4.x 对 WSL 内核版本有要求老旧内核会出现各种奇怪的启动失败。更新方式也很简单wsl --update还有一类情况值得注意如果你机器上装了 VMware 或 VirtualBox它们可能和 Hyper-V 抢虚拟化资源。新版 VMware 已经支持与 Hyper-V 共存但旧版会冲突。最省事的办法是在“启用或关闭 Windows 功能”里暂时关掉“虚拟机平台”用 VMware 时把 Docker 停掉或者反过来不用 VMware 的时候就别装它。我自己的经验是机器上同时留 Docker Desktop 和 VMware默认不开 VMware只有必须跑图形化虚机时才临时启动。1.3 装完之后先别高兴太早基础验证三连Docker Desktop 启动成功后图标上的鲸鱼会从“Starting”变成“Running”。但图形界面不报错不代表环境一定可用我建议打开 PowerShell 做三连验证。第一次运行docker version你会看到 Client 和 Server 两段信息Server 段存在且 Engine 正常说明守护进程活着。如果只有 Client 没有 Server说明 daemon 没起来多半是 WSL2 或 Hyper-V 的问题。第二次运行docker run hello-world这个镜像只有几 KB能成功运行就说明拉取、创建容器、运行容器整条链路都是通的。如果这里卡住才轮到镜像源的问题也就是下一节要讲的。第三次运行docker ps和docker compose version。前者看容器列表是否正常返回后者确认 Compose 插件可用。Docker Desktop 自带的 Compose 是 v2 版本命令是docker compose而不是老的docker-compose注意区分。到这里鲸鱼算是醒过来了。但醒过来之后很多人的下一步是拉镜像然后再次被卡住。2. 镜像拉不动别先怪Docker镜像源与下载提速的实操2.1 你看到的“永远拉不完”到底是哪一环慢docker pull nginx:latest跑起来之后进度条走得比蜗牛还慢甚至卡在某个 layer 半天不动这是最常见的第二个劝退点。先说原因Docker 默认从 Docker Hub 拉镜像而 Docker Hub 的服务器在境外网络链路一通绕速度就一言难尽。这不是你机器的问题也不是 Docker 本身的问题纯粹是默认仓库离你太远。镜像拉取的过程分几个阶段解析仓库地址、TLS 握手、获取 manifest 清单、下载各个 layer。你看到进度条走不动绝大多数情况是卡在最后一步 —— layer 下载。一个 nginx 镜像不到 200MB一个 MySQL 镜像动辄 500MB 以上在低带宽链路上拉一次大镜像够你喝一壶。2.2 配置Registry Mirror的两种路径解决思路很简单给 Docker 配置一个离你更近的镜像加速器。Docker 官方为此设计了 registry mirror 机制也就是让 daemon 先去加速器地址找镜像找不到再回源到 Docker Hub。Docker Desktop 用户的操作路径打开 Docker Desktop 界面点右上角设置图标切到 Docker Engine 选项卡这里其实就是daemon.json的可视化编辑界面。在 JSON 里加入registry-mirrors字段例如{ registry-mirrors: [https://docker.m.daocloud.io] }保存后 Docker 会自动重启 daemon生效后可以再执行docker info查看输出里会多出几行Registry Mirrors列表。Linux 服务器上则是直接编辑/etc/docker/daemon.json没有这个文件就新建一个格式一样然后执行sudo systemctl restart docker。这里有个比较重要的经验加速器地址不是一成不变的有些公共加速服务今天能用明天可能就不行了。我的做法是准备两到三个地址轮换列为候选清单哪个慢就换哪个。如果你是腾讯云、阿里云的用户控制台里也有自己账号专属的容器镜像加速地址优先用那个因为它是按你账号分配的稳定性比公共的好不少。配置好镜像源之后再拉 nginx速度通常能快上几倍到十几倍体感非常明显。2.3 实在拉不动时的离线兜底方案还有一种场景你的服务器在内网外网访问受限配置镜像源也没用。这时候不要硬刚直接换思路 —— 离线传输镜像。在一台能正常拉取镜像的机器上docker pull nginx:latest docker save -o nginx.tar nginx:latest把生成的 nginx.tar 拷贝到目标机器上然后docker load -i nginx.tardocker save和docker load是一对老搭档前者把镜像连同元数据一起打包成 tar 文件后者还原。有一点必须提醒镜像有架构之分Linux 服务器常见 amd64树莓派和部分国产 ARM 设备是 arm64。在一台 amd64 机器上 save 出来的镜像load 到 arm64 机器上并不能直接跑。用docker manifest inspect 镜像名可以查看该镜像支持哪些架构实在不行就换一个多架构镜像或者加--platform参数指定平台。这一节的核心就一句话拉不动镜像的时候先检查镜像源再考虑离线传输不要一遍遍重试默认仓库浪费时间。3. 让鲸鱼干活MySQL 8.0、Redis主从与Compose编排3.1 MySQL 8.0容器化数据卷是第一条命镜像能正常拉取了就该让 Docker 干点正经事了。我拿最常见的“docker 安装 mysql8.0 并使用”来演示。很多人第一次跑 MySQL 容器会这么写docker run -d --name mysql8 -p 3306:3306 -e MYSQL_ROOT_PASSWORD你的密码 mysql:8.0命令很短跑起来也很开心但有个致命隐患容器一旦被删除你的数据库数据全没了。因为默认情况下MySQL 的数据写在容器可写层里容器生命周期一结束数据就跟着没了。正确做法是挂数据卷也就是把容器内的数据目录映射到宿主机上docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDYourStrongPass \ -e TZAsia/Shanghai \ -v mysql8_data:/var/lib/mysql \ mysql:8.0这里的-v mysql8_data:/var/lib/mysql用到了一个具名数据卷。Docker 会在宿主机上维护这个数据卷就算容器删了卷里的数据还在。下次再跑同一条命令MySQL 会接着旧数据启动密码和数据都保留。具名卷相比-v /某个宿主机目录:/var/lib/mysql的好处是你不用操心目录权限问题Docker 替你管理路径跨机器迁移也更方便。跑起来之后连接测试时有个经典坑MySQL 8 默认认证插件是caching_sha2_password一些老版本的客户端工具比如旧版 Navicat 或某些语言库连不上报错Authentication plugin caching_sha2_password cannot be loaded。解决办法是在容器里改掉 root 用户的认证方式docker exec -it mysql8 mysql -uroot -p然后执行ALTER USER root% IDENTIFIED WITH mysql_native_password BY 你的密码;这种方式只推荐用于开发环境或内网可信环境生产环境的账号管理别这么随便。另一个常见失败场景是“docker 安装 mysql 失败”日志里报[ERROR] [MY-010262] ... Cant start server: Bind on TCP/IP port: Address already in use这基本就是宿主机 3306 端口被占用了——要么本机装了 MySQL要么另一个容器已经占了端口。解决方式很简单换个宿主端口映射比如-p 3307:3306容器内部始终是 3306只有宿主机的手指位置变了。3.2 Redis主从一条命令和三条命令的差距“docker 安装 redis 主从”也是高频搜索词。这里我不抄官方文档直接按最容易理解的方式讲。先给 Docker 创建一个自定义网络这一步非常关键docker network create redis-net然后启动主节点docker run -d \ --name redis-master \ --network redis-net \ -p 6379:6379 \ redis:7再启动从节点docker run -d \ --name redis-slave1 \ --network redis-net \ -p 6380:6379 \ redis:7 \ redis-server --replicaof redis-master 6379启动从节点的命令里多了两个东西redis-server --replicaof指定了主节点的服务名和端口。这里我要强调一下为什么要先建redis-net这个自定义网络因为容器在默认 bridge 网络下相互之间只能用 IP 访问而 IP 每次启动可能变化自定义 bridge 网络自带 DNS 解析你可以在从节点里直接写redis-master这个服务名Docker 会自动解析成主节点的 IP。进去验证一下docker exec -it redis-slave1 redis-cli info replication输出里看到master_link_status:up说明主从链路是通的。这条链路用 Docker 搭起来就三分钟比手动去服务器装两个 Redis 实例再改配置要清爽太多。3.3 docker compose把“跑起来”变成“长出来”当你需要同时启动 MySQL、Redis 和你的应用时再一条条敲docker run就太原始了。这就是docker compose登场的时机。一个典型的docker-compose.yml长这样services: mysql8: image: mysql:8.0 container_name: mysql8 environment: MYSQL_ROOT_PASSWORD: YourStrongPass TZ: Asia/Shanghai ports: - 3306:3306 volumes: - mysql8_data:/var/lib/mysql healthcheck: test: [CMD, mysqladmin, ping, -h, localhost] interval: 5s timeout: 3s retries: 10 redis: image: redis:7 container_name: redis ports: - 6379:6379 app: build: ./app container_name: myapp ports: - 8080:8080 depends_on: mysql8: condition: service_healthy redis: condition: service_started volumes: mysql8_data:然后在 docker-compose.yml 所在目录执行docker compose up -d这里想特别讲一下depends_on。很多网上教程只写一个裸的depends_on: - mysql8它的作用仅仅是把 mysql8 容器先启动但 MySQL 真正就绪需要几秒甚至十几秒你的应用容器这时候去连数据库照样会报“连接被拒绝”。所以我上面写的是condition: service_healthy让 Docker 等到 MySQL 通过mysqladmin ping健康检查之后再启动 app 容器。这个细节是 compose 排产最容易被忽视的一环。顺带说一句旧版本的docker-compose工具已经逐渐被 Compose v2 取代现在如果你安装的是 Docker Desktop 或新版 Docker Engine直接用docker compose命令就行中间没有横杠。4. 容器一多就“失联”网络不通的排查实录4.1 三种常见的“网络不通”表现用 Docker 部署的东西一多网络问题就开始冒头。我总结下来常见的“网络不通”基本是三种表现第一种宿主机上访问不到容器的映射端口。比如docker run -p 8080:80 nginx之后浏览器打开http://127.0.0.1:8080一直转圈。第二种容器内部访问不了宿主机上的服务。比如容器里的应用要连宿主机上的某个服务连不通。第三种容器 A 访问容器 B 的服务名解析失败。这三种问题的排查思路各不相同但有个共同的底层逻辑先搞清楚 Docker 的网络模型再检查你的命令和网络配置。4.2 从bridge网络看端口映射的本质默认情况下Docker 会创建一个名为bridge的虚拟网桥宿主机上对应的接口一般叫docker0IP 段通常是172.17.0.0/16。每当你用docker run -p 宿主机端口:容器端口时Docker 做的事情是在 docker0 上做一层端口转发宿主机上的 8080 端口收到流量转发到对应容器的 80 端口。这个转发规则由 dockerd 维护常见出问题的地方就是这些规则被干扰。比如我在一台同时部署了其他防火墙产品的主机上就踩过坑Docker 容器一切正常但宿主机外部就是访问不了映射端口。最后发现是第三方安全软件把 Docker 创建的转发规则给清了。这种时候第一步先看端口有没有在监听netstat -tlnp | grep 8080Windows 上对应的是netstat -ano | findstr 8080。如果端口根本没有监听说明转发没生效检查容器是不是没起来如果端口在监听但外部连不上那大概率是防火墙或安全策略的问题。4.3 从容器内往外ping的顺序排查容器之间互相访问不了我建议按这个顺序查。先看容器在哪个网络里docker inspect 容器名 -f {{json .NetworkSettings.Networks}}确认两个容器在同一个自定义网络下。如果一个是默认 bridge另一个是自定义网络那它们确实互相访问不了必须让它们加入同一个网络。再看服务名解析docker exec -it 容器A ping 容器B的服务名能 ping 通说明 DNS 解析没问题问题出在目标容器里的应用端口没监听或绑定了错误的地址。特别是 Redis 这种服务默认可能只绑定了本地回环地址容器外当然进不去。这也解释了为什么很多 Redis 容器教程会强调加--bind 0.0.0.0或--protected-mode no目的就是让服务监听所有网卡。最后还可以抓包看真实通信情况docker exec -it 容器A sh -c apk add tcpdump tcpdump -i eth0不过一般来说tcpdump 属于进阶手段大多数网络不通的问题在 DNS 解析阶段就能定位到。跨主机的容器通信又是另一个话题比如两台物理机上的容器互相访问引入 Docker Swarm 或 Kubernetes 才比较合适单机 Docker 默认是解决不了这个的。网上还有人问“docker部署微服务项目”怎么排微服务一旦跨多机部署我的建议是直接上容器编排平台别在单机 Docker 层面硬撑。5. 青龙面板的依赖难题容器依赖管理的正确姿势5.1 部署青龙面板本身并不难“docker 青龙 依赖管理”这个搜索词很典型。青龙面板是一个定时任务管理和脚本执行面板支持 Python、JavaScript、Shell 等脚本本身用 Docker 部署很简单一条命令基本能搞定。docker run -d \ --name qinglong \ -p 5700:5700 \ -v $PWD/ql/config:/ql/config \ -v $PWD/ql/log:/ql/log \ -v $PWD/ql/db:/ql/db \ -v $PWD/ql/scripts:/ql/scripts \ --restartalways \ 你的镜像地址/qinglong:latest部署面板可以用官网推荐的镜像这里我不展开具体镜像名因为版本更新太快直接看官方文档最靠谱。跑起来之后访问http://宿主机IP:5700初始化账号密码面板就能用了。5.2 依赖装不上的根因但面板跑起来只是第一步真正的坑在依赖管理里。很多人在面板的“依赖管理”里安装了某些 Python 包或 Node 包结果脚本一跑还是提示缺模块。为什么装了还缺关键点在于面板自带的依赖管理只是往容器内的 Python 或 Node 环境里安装包但容器里的网络环境和你本地不一样默认走的是官方源速度慢且可能超时最终表现为安装失败。另外不太抬头的原因是对应语言的 bin 目录没有在 PATH 环境变量里导致面板找不到刚装好的可执行文件。根因清楚了解决方案就有了。要么给容器的语言包管理器配置国内镜像源。以 pip 为例在容器内或启动时挂载的配置目录里设置pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simplenpm 同理npm config set registry https://registry.npmmirror.com注意这种配置写在容器里容器 rebuild 或重新创建后可能会丢所以最好把配置固化到挂载目录或者写进面板的“初始化脚本”里每次启动自动执行。5.3 正确管理依赖的经验我在实操中养成了一个习惯不依赖面板的“依赖管理”界面装依赖而是直接在创建容器时把宿主机的依赖目录挂载进去并且把装依赖的命令写进启动脚本。比如给容器加一段初始化逻辑启动时先执行 pip install 或 npm install再启动面板主进程。这样的好处是容器不管怎么重启依赖总是按声明式配置重新装好不会出现“这次能用下次就缺包”的玄学问题。还有一个值得提醒的地方青龙面板的容器需要挂载的数据目录比较多config、log、db、scripts 最好都单独挂载。一旦容器出问题需要重建数据不丢才是最要紧的。很多人只挂了一个 scripts觉得脚本在就行了结果 job 配置文件没挂载又是一场事故。6. 一台N100跑20个容器低配环境下的Docker资源分配6.1 N100的真实承载力“N100 跑 20 个 docker”这个搜索词很有趣。N100 是 Intel 的一款低功耗四核处理器跑在迷你主机上很多人拿它当家用服务器。我的实际体验是跑 20 个容器完全可行但前提是你得选对镜像、管住资源、控制日志。如果 20 个容器里有四个 MySQL、三个 PostgreSQL 再加一个 Elasticsearch那别说 N100普通 i5 都吃不消。容器的核心价值之一就是资源共享但共享的前提是有边界。不加任何资源限制地跑 20 个容器很容易出现一两个“疯容器”把 CPU 吃满整个宿主机的服务全部变慢。6.2 用--memory和--cpus把容器关进笼子给容器分配资源的命令非常简单创建时加参数即可docker run -d \ --name web1 \ --cpus0.5 \ --memory512m \ --memory-swap512m \ nginx:alpine--cpus0.5表示这个容器最多占用半个 CPU 核心--memory512m限制物理内存上限--memory-swap512m是为了把 swap 也一并限制住否则容器看到的内存上限可能和实际不符。对于已经创建的容器可以用docker update动态调整docker update --memory512m --memory-swap512m --cpus0.5 web1我给低配机器总结的资源分配参考是轻量 Nginx 容器给 0.2 到 0.3 核、128MB 到 256MB 内存Java 应用视堆内存大小而定通常至少 1GB 以上不适合在 N100 上跑多个Go、Node 等应用可以比较小512MB 内存足够数据库类容器建议单独评估别轻易塞进大杂烩里。6.3 轻量镜像与日志策略镜像本身的体积直接影响运行期的磁盘占用和启动速度。同样的 Nginx官方镜像有两百多 MBnginx:alpine只有几十 MB。在低配服务器上能选 slim 或 alpine 标签的坚决不选完整版。迁移到轻量镜像时注意一点部分系统工具可能缺失比如 alpine 里没有 bash、没有 glibc有些应用可能跑不起来遇到就换对应语言的 slim 版本而不是硬上 alpine。日志也是个隐形杀磁盘的东西。默认情况下Docker 把容器日志以 json 格式存在宿主机上日志量大且不轮转时一个容器几天就能写几个 GB。建议全局配置日志轮转在/etc/docker/daemon.json里加{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }也可以给单个容器指定docker run -d --log-opt max-size10m --log-opt max-file3 nginx:alpine这招在低配机器上特别管用。我见过太多 PVE 或 Docker 宿主机“磁盘莫名打满”的求助帖最后一查全是日志在作怪。监控方面docker stats是最快的查看方式能看到每个容器的 CPU、内存、网络占用。想要更好看的界面装个 ctop 小工具也行单文件安装不占资源。低配机不适合再塞一套完整的 Prometheus 监控跑了几个小工具反而轻快。关于“docker 部署 kodbox”、“docker 部署 metabase”、“docker 安装 gitlab”这类具体应用的部署思路都一样先看官方镜像页面确认环境要求再按“拉镜像 - 建网络 - 挂数据卷 - 映射端口”四步走。GitLab 这类重应用动辄要 4GB 内存N100 上跑会非常勉强建议量力而行。6.4 我踩过的“最后一个坑”权限问题最后说一个几乎每天都会有人在群里问的问题docker: permission denied。明明安装好了 Docker命令一执行就报权限错误。原因是当前用户不在 docker 用户组里。解决方式很简单sudo usermod -aG docker $USER然后重新登录一次或者运行newgrp docker让组权限立即生效。这个操作之后再执行docker ps就不会加 sudo 了。为什么明明 sudo 可以但直接 docker 不行因为 Docker 的 socket 文件默认只对 root 和 docker 组开放你的用户不在组里自然被拒绝。这个权限设定主要是安全考虑不要把习惯性chmod 777用在/var/run/docker.sock上那等于把一个 root 权限后门打开给所有人。回到最初的题目。“你可以永远相信这只鲸鱼”这句话我现在依然认同但更准确的说法是相信它不等于不设防。装好的第一件事检查虚拟化、配好镜像加速、挂好数据卷、设好资源限制、管好日志轮转把这五件事做完这只鲸鱼确实能给你省下大把时间。我第一次在 N100 上同时跑十几个容器的时候也担心过这么小一台机器会不会翻车但跑了大半年下来只要容器各自的资源边界清晰它比你想象中要稳得多。Docker 这东西说白了就是一套纪律你对容器负责容器才会对你负责。
返回列表