ARTICLE DETAIL

资讯详情

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

Ubuntu安装Docker Engine全流程:从apt源配置到镜像加速与排错

Ubuntu安装Docker Engine全流程:从apt源配置到镜像加速与排错 很多人在 Ubuntu 上装 Docker第一反应是去搜“Ubuntu 安装 Docker 教程”然后照着某篇三年前的旧文章敲命令结果装到一半要么 GPG 密钥报错要么 daemon 起不来要么拉镜像卡到怀疑人生。我自己前前后后在各种 Ubuntu 版本上装过不下二十次 Docker从 16.04 一路用到 24.04踩过的坑比教程里的步骤都多所以想把自己验证过的一套完整流程整理出来让后来的人少走点弯路。这篇文章讲的内容比较实在既包括 Docker Engine 在 Ubuntu 上的完整安装步骤也包括装完之后绕不开的权限配置、镜像加速、常见故障排查还会顺带聊一下 Docker Desktop 在 Ubuntu 生态里到底处于什么位置。不管你是刚接触 Docker 的新手还是在配开发环境时被权限和网络问题折磨过的老手这篇都有参考价值。1. 先搞清楚你在 Ubuntu 上需要的是 Docker Engine 还是 Docker Desktop1.1 两种产品形态很多人一开始就搞混了一说到“安装 Docker”很多人的第一反应是去 Docker 官网下载 Docker Desktop。但 Docker 其实分为两个完全不同的产品线Docker Engine 和 Docker Desktop。这两者的定位差异非常大。Docker Engine 是真正在后台运行的容器运行时核心包括dockerd守护进程、containerd、runc等组件它跑在 Linux 上通过命令行docker与之交互。你平时执行docker run、docker build最终干活的就是这套引擎。Docker Desktop 则是一个带 GUI 的桌面应用程序它内部会启动一个轻量级虚拟机在 Windows 上基于 Hyper-V 或 WSL2在 macOS 上基于 Virtualization.framework然后在虚拟机里运行 Docker Engine。Desktop 的价值在于它把容器的运行环境从操作系统层面隔离出来让你在 macOS 或 Windows 上也能无缝使用 Linux 容器。这里有一个很关键的点如果你是 Ubuntu 物理机或者服务器Docker Engine 直接跑在本机 Linux 内核上性能和资源占用是最优的完全没必要再套一层虚拟机去跑 Docker Desktop。我在自己日常用的 Ubuntu 工作站上做过简单对比Docker Desktop 方式因为多了一层虚拟化容器启动速度和 IO 性能都有肉眼可见的损耗。1.2 不同使用场景下的选择建议根据我的实际经验可以这样选使用场景推荐方案理由Ubuntu 物理机 / 服务器Docker Engine原生支持、性能最好、无 GUI 依赖Ubuntu 虚拟机VMware/VirtualBoxDocker Engine嵌套虚拟化配置麻烦Engine 直接可用Windows 主机Docker Desktop with WSL2原生 Windows 容器体验最好macOS 主机Docker Desktop唯一成熟的方案我在 VMware 虚拟机里装 Ubuntu 也试过 Docker Desktop结果遇到virtualization support not detected这类嵌套虚拟化问题需要额外开启 CPU 的虚拟化透传折腾半天不如直接装 Docker Engine 来得干净。所以如果你确定要在 Ubuntu 上使用 Docker这套文章后续的内容全都围绕 Docker Engine 展开。这是生产环境、开发环境里最主流、最严谨的选型。2. 用官方 apt 仓库安装 Docker Engine完整步骤和每一条命令的意图2.1 清理历史残留apt-get remove那一步不是走过场如果你之前没用 apt 装过 Docker可能会觉得下面这步多余。但根据我的踩坑经历很多 Ubuntu 用户会在某个阶段用过sudo apt install docker.ioUbuntu 软件源自带的旧版 Docker或者装过docker-engine、docker.io等老包。残留的旧版本会造成什么问题最典型的是两个新旧版本工具链混乱docker --version显示的版本很老但 daemon 已经换成新版导致docker build时缓存行为异常。旧版的/var/lib/docker数据目录格式与新版本不兼容启动 daemon 时直接报错。所以安装之前先把可能存在的旧包清理干净sudo apt-get remove docker docker-engine docker.io containerd runc注意apt-get remove不会删除/var/lib/docker下的镜像、容器、卷等数据。如果你对旧数据没有需求可以考虑手动清理sudo rm -rf /var/lib/docker提示如果是生产服务器升级先备份/var/lib/docker或至少对重要容器做docker commit或数据卷备份再决定是否清理。2.2 添加 GPG 密钥与官方软件源让 apt 认识 Docker 官方仓库Ubuntu 20.04 及以上版本都预装了ca-certificates和curl但为了保证环境干净还是建议先把依赖装齐sudo apt-get update sudo apt-get install ca-certificates curl gnupg然后创建存放 GPG 密钥的目录并下载 Docker 官方密钥。这一步的核心意义在于apt 需要通过这个 GPG 密钥来校验软件包签名防止从官方仓库下载到的 deb 包被篡改。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 sudo chmod ar /etc/apt/keyrings/docker.gpg这里有个以前经常踩的坑老教程里会直接apt-key add -但 apt-key 在 Ubuntu 22.04 及更新版本里已经明确标记为 deprecated继续用会收到 warning而且密钥管理方式比较混乱。现在官方推荐的keyrings目录方式更干净更新密钥、删除密钥都一目了然。接下来把 Docker 官方 apt 源写入系统列表。命令看起来长实际上就是告诉 apt“我有一个新软件源地址是什么架构是什么密钥在哪个文件”。需要注意操作系统代号部分我写成通用写法Ubuntu 24.04 会自动替换为nobleecho \ 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我建议把这命令拆解一下理解而不是无脑复制$(dpkg --print-architecture)自动识别当前 CPU 架构x86_64 会输出amd64ARM 的 Ubuntu 会输出arm64保证源地址架构匹配。$(. /etc/os-release echo $VERSION_CODENAME)读取系统版本代号22.04 是jammy24.04 是noble自动适配不需要手动改。2.3 安装 docker-ce、docker-ce-cli、containerd.io软件源配置好之后先更新索引让 apt 识别到新仓库sudo apt-get update然后安装 Docker Engine 相关组件sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin这几个包各司其职docker-ceDocker Engine 本体即守护进程dockerd。docker-ce-clidocker命令行工具。注意它和 engine 是分开安装的避免因版本错位导致 CLI 和 daemon 通信协议不匹配。containerd.io容器运行时管理组件负责镜像传输、容器生命周期管理dockerd 底层依赖它。docker-buildx-plugin新版 build 工具支持多架构镜像构建。docker-compose-pluginCompose v2 插件用docker compose命令编排多容器。我以前只装前三个包后来发现需要docker buildx构建多平台镜像时还得手动补装插件不如一步到位。Compose 插件同理没有它写docker-compose.yml都用不了新版命令。2.4 启动、设置开机自启、验证版本安装完成后Docker 服务默认可能没有启动。依次执行sudo systemctl enable docker sudo systemctl start dockerenable是创建系统服务软链接让 Docker 在开机时自动运行start是立刻启动。这一步可以在服务器上尤其重要不然机器重启后 dockerd 不跑所有容器都起不来。验证安装是否成功先看版本信息docker version正常输出会分 Client 和 Server 两端。如果只看到 Client 而 Server 部分报Cannot connect to the Docker daemon说明引擎没起来往下翻第 6 节排查。再跑官方标配验证sudo docker run hello-world这条命令会自动拉取hello-world镜像然后运行一个输出问候语的临时容器。如果能看到 “Hello from Docker!” 的提示说明引擎已经正常工作。3. 摆脱 sudo把当前用户加入 docker 组但这把双刃剑要想清楚3.1 为什么每次都输 sudo 这么烦以及背后的 socket 权限逻辑默认情况下docker命令通过/var/run/docker.sock这个 Unix socket 与 dockerd 通信。这个 socket 文件归属于root:docker组普通用户没有访问权限所以你必须用sudo垫一步。每次执行都带sudo的痛点不用多说了难处在于sudo docker会把所有镜像、容器文件的所有者都变成 root后续手动管理文件权限时很别扭。更麻烦的是如果你在 CI 脚本或自动化工具里直接用sudo docker一旦涉及到 sudoers 配置不当排查权限问题会非常头疼。3.2 两步操作让普通用户直接跑 docker把当前用户加入docker组是 Linux 社区通用的解法sudo usermod -aG docker $USER-aG参数表示追加append到docker组而不是覆盖原有用户组这一点很关键。不加-a的话会把用户从其他组中移出可能导致权限丢失我见过有人在云服务器上因为少写-a把sudo组都搞丢了结果前面所有命令都得靠 root 登录救命。执行完usermod之后需要重新登录使组变更生效。很多新手卡在这一步明明已经加入docker组了可是docker ps还是报 permission denied。原因就是当前 shell 会话的用户组信息还没刷新。最简单的做法是退出终端重新登录或者执行newgrp docker这条命令会以新的组身份启动一个子 shell。但要注意它只对当前终端会话生效新开的终端窗口如果还没重新登录照样需要先newgrp docker。最可靠的还是彻底 logout 再 login。3.3 docker 组等于 root 权限这个风险得摆到台面上说把用户加入 docker 组省事是真省事危险也是真危险。原因在于docker 组用户能够通过 Docker 守护进程执行许多与 root 等价的操作。比如把宿主机的/etc目录挂载进容器并修改就可以轻松改掉系统的 SSH 配置再比如直接运行一个特权容器来读写宿主机文件系统。本质上能操作 docker.sock 的人就相当于拥有了 root 权限。所以我对生产服务器的建议是如果机器上有多人登录或者跑着重要的生产容器慎用这个方案如果只是自己开发机加入 docker 组带来的便利远大于风险。回到“怎么选”的问题上我自己的习惯是开发机加组生产服务器保持sudo docker或者用专门的运维账号管理。4. 拉镜像慢的问题配置 registry mirror 加速4.1 “Timeout” 报错背后的网络原因装好 Docker 的第一天你大概率会兴冲冲地docker pull mysql:8.0然后看着进度条在 0% 卡住过一会儿直接给你一个net/http: TLS handshake timeout或者i/o timeout。这个问题的本质是Docker Hub 的默认镜像仓库地址registry-1.docker.io部署在境外国内访问时网络链路不稳定尤其在高峰期丢包严重。报错看起来像是 Docker 配置问题其实是网络链路问题。解决思路是给 Docker 配置 registry mirror镜像加速器也就是让 containerd 从位于国内的镜像仓库拉取镜像。这个加速器会定期同步 Docker Hub 上的公共镜像你拉取时实际是从最近的加速节点下载速度提升非常明显。4.2 通过 daemon.json 配置镜像加速器Docker 的守护进程配置位于/etc/docker/daemon.json。如果文件不存在手动创建即可sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json -EOF { registry-mirrors: [https://docker.m.daocloud.io] } EOF这里我以 DaoCloud 提供的公共加速地址为例。目前国内可用的公共镜像加速服务变动比较频繁你如果在自己购买的云厂商环境里建议登录云厂商控制台查看容器镜像服务里分配的专属加速地址稳定性远比公共加速高。配置完成后重启 Docker 使配置生效sudo systemctl daemon-reload sudo systemctl restart dockerdaemon-reload告诉 systemd 重新读取 daemon 配置这一步不能省。有人只改了daemon.json就执行systemctl restart docker结果配置没被正确加载因为 systemd 里的环境变量或启动参数还没刷新。4.3 快速验证加速是否真的生效重启后可以用一条命令查看当前 Registry 配置docker info | grep -A 5 Registry Mirrors输出类似这样就说明加速器已生效Registry Mirrors: https://docker.m.daocloud.io/然后重新docker pull hello-world或者拉一个稍大的镜像观察速度差异。正常情况下原来几十 KB/s 的速度会提升到几 MB/s 甚至更快。注意registry mirror 只对 Docker Hub 上的公共镜像生效。如果你拉取的是第三方仓库镜像比如ghcr.io、quay.io、registry.cn-hangzhou.aliyuncs.com上的私有镜像加速器不会起作用需要单独配置仓库凭证或网络策略。5. 实战验证跑通 hello-world 之后立刻部署一个 MySQL 容器5.1 hello-world 到底验证了什么很多人跑完docker run hello-world觉得没啥了不起。实际上这个不到 2KB 的镜像背后验证了好几层东西daemon 能够正常拉取镜像网络链路通。containerd 能正常解压、创建容器运行时正常。dockerd 能正常与容器交互并收集输出控制链路正常。它相当于体检报告的第一页告诉你 Docker 核心链路没毛病。但到这里还不够我建议立刻用一个实际场景——部署 MySQL——来验证镜像加速、端口映射、数据卷、容器生命周期管理等全套能力。5.2 部署 MySQL 8.0端口映射、数据持久化、密码设置先创建一个 Docker 网络方便后续如果有其他容器要和 MySQL 通信docker network create app-network然后运行 MySQL 容器docker run -d \ --name mysql8 \ --network app-network \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDyour_password \ -e MYSQL_DATABASEtestdb \ -v mysql-data:/var/lib/mysql \ mysql:8.0逐个解释这些参数因为理解它们比命令本身更重要-d后台运行容器不会占据终端。--name mysql8给容器起名字之后docker stop mysql8/docker logs mysql8都直接用名字代替容器 ID。--network app-network把容器加到自定义网络中这样同一网络里的其他容器可以直接用服务名mysql8互相访问不需要通过 IP。-p 3306:3306端口映射宿主机 3306 端口转发到容器 3306 端口。左侧是宿主机端口右侧是容器端口。-e MYSQL_ROOT_PASSWORD通过环境变量告诉 MySQL 初始化脚本去设置 root 密码。注意这是最简单的测试方式生产环境建议用配置文件或密钥管理工具。-v mysql-data:/var/lib/mysql命名卷持久化MySQL 的数据文件写到命名的 Docker volume 中而不是容器可写层。这样即使容器被删除数据依然保留。等几秒钟让 MySQL 初始化完成然后用宿主机上的客户端测试连接mysql -h 127.0.0.1 -P 3306 -u root -p这里有个很容易迷惑的点容器内部 MySQL 监听的是 3306你从宿主机连的时候走的是127.0.0.1:3306。如果你在宿主机上同时装了另一个 MySQL 占用了 3306 端口就会端口冲突启动时报Bind for 0.0.0.0:3306 failed: port is already allocated。解决办法是换宿主机映射端口比如-p 3307:3306。5.3 容器管理的几个日常高频命令部署完 MySQL 之后下面这些命令是你接下来几乎每天都要用的值得花两分钟熟悉# 查看运行中的容器-a 查看所有容器 docker ps docker ps -a # 查看日志-f 实时跟踪 docker logs -f mysql8 # 进入容器内部交互式 shell docker exec -it mysql8 bash # 停止 / 启动 / 重启 docker stop mysql8 docker start mysql8 docker restart mysql8 # 删除容器-f 强制删除运行中的 docker rm -f mysql8 # 查看所有镜像 / 删除镜像 docker images docker rmi mysql:8.0 # 查看磁盘占用 docker system dfdocker exec -it mysql8 bash这个命令我几乎每天都要用。进入容器后可以快速查看配置文件、执行 SQL、排查问题。但有一个小坑容器里的系统是最小化安装很多命令vim、ping、netstat都不存在需要apt-get update apt-get install -y vim之类手动装。如果实在不习惯可以优先用docker logs查应用日志减少进容器的次数。6. 装完 Docker 后最常踩的五个坑附完整排查链路6.1 Cannot connect to the Docker daemon先去看服务状态这是出现频率最高的报错之一典型输出Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?很多人的第一反应是重装 Docker其实大多数情况下只是服务没起来。按照这条链路排查基本能定位问题# 1. 查看 docker 服务状态 systemctl status docker # 2. 如果显示 inactive (dead)启动它 sudo systemctl start docker # 3. 如果启动失败查看详细日志 journalctl -u docker --no-pager -n 50日志里比较常见的失败原因有overlayfs文件系统不可用、磁盘空间不足、iptables规则冲突、/var/lib/docker权限错误。整体思路是先确认服务起来了没有再看日志不要盲目卸载重装。有一次我在某台机器上发现 cron 任务把/etc/docker/daemon.json写坏了导致 daemon 启动时解析 JSON 失败这种问题重装十次也好不了看日志才能一眼定位。6.2 Got permission denied用户组刷新没生效如果你不是 root也还没加入 docker 组执行docker ps时会出现Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock这个报错实际上就是第 3 节讲到的组权限问题。快速确认当前用户是否已在 docker 组groups $USER如果输出里没有 docker说明usermod那步没执行成功或者用户不对。如果已经加入但依然报错多半是当前终端会话没有刷新组信息重新登录一次基本能解决。提示不建议用sudo chmod 666 /var/run/docker.sock给 socket 文件放开权限这样等于让所有用户都能控制 Docker安全性完全失控属于典型的下策。6.3 容器内无法解析域名DNS 配置的问题有时候容器能启动但容器内部访问外网时ping baidu.com报Temporary failure in name resolution。这个坑在 Ubuntu 20.04 及之后的版本上尤其常见原因是新版 Ubuntu 桌面版默认使用systemd-resolved会占用127.0.0.53:53作为本地 DNS 解析器。Docker 默认把宿主机/etc/resolv.conf里的 nameserver 复制进容器而127.0.0.53在容器网络命名空间里访问不到。定位方法先看宿主机/etc/resolv.conf内容确认 nameserver 指向再进容器查看 DNS 配置docker exec mysql8 cat /etc/resolv.conf如果容器内的 nameserver 是127.0.0.53手动指定 Docker daemon 使用的 DNS 服务器即可。修改/etc/docker/daemon.json加一行{ dns: [8.8.8.8, 223.5.5.5] }然后重启 Docker。这里要注意修改 daemon 的 DNS 配置后已经存在的容器不会自动更新/etc/resolv.conf需要重建容器才生效。我第一次调这个配置时改完 daemon 重启回头看旧容器还是解析失败一度以为是配置没生效折腾了半天才想起重建容器。6.4 磁盘被容器日志塞满log rotation 别再事后补救容器日志无限制增长是我在服务器上遇到的最隐蔽的坑。默认情况下Docker 会把容器 stdout/stderr 输出保存为 json-file 日志文件如果没有配置轮转策略长时间运行的容器比如数据库、Nginx会把磁盘空间一点点吃完直到df -h显示 100%。可以在 daemon.json 里全局配置日志轮转{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }含义是单个日志文件最大 10MB最多保留 3 个超过后自动滚动清理。配置好重启 Docker后续新建容器都会遵守这个限制。对已经存在的容器我一般用一条命令直接把日志文件清空不是删除容器只是清空日志sudo truncate -s 0 /var/lib/docker/containers/*/*-json.logtruncate命令保留文件句柄不会导致 daemon 写日志异常。千万别直接rm日志文件有些场景下 Docker 不会自动重新创建文件句柄可能导致该容器日志后续完全丢失。6.5 其他容易被忽略的时间同步、剩余磁盘空间最后这条经验来自我在虚拟机里跑 Docker 的踩坑经历挂起虚拟机、宿主机休眠恢复后容器内时间可能出现漂移。典型表现是容器内日志时间和宿主机时间差好几个小时业务日志里时间戳完全错乱。解决办法是在宿主机上配置好 NTP 时间同步sudo timedatectl set-ntp true另外Docker 运行过程中对磁盘空间很敏感。当/var/lib/docker所在分区使用率超过 85% 时容器的写入性能会明显下降拉镜像也可能失败。我建议随时关注df -h /var/lib/docker docker system dfdocker system df可以清楚地看到镜像、容器、数据卷分别占了多大空间。定期运行下面这条清理命令能回收大量磁盘docker system prune -af --volumes这条命令会删除所有停止的容器、未被容器使用的网络、无主镜像和未引用的数据卷。执行前必须确认这些资源都没有保留价值尤其是数据卷删了就真没了。所以我一般在开发环境随便跑生产环境会先docker ps -a人工过一遍再动。7. 关于 Docker Desktop我的个人看法7.1 原生 Ubuntu 环境直接用 Docker Engine 即可前面已经说过Ubuntu 环境下的首选是 Docker Engine。但我知道还是会有人执着于 Docker Desktop因为看上了它那个带图形界面的控制台、方便的镜像浏览、快捷的容器启停操作。这很正常写代码的人谁不希望有个可视化面板呢不过 Ubuntu 桌面上安装 Docker Desktop 的真实体验是需要额外安装 KVM 虚拟化支持、依赖 gnome-terminal、消耗 2-3GB 内存跑虚拟机。相比之下直接在浏览器打开 Portainer 或者其他 Web 管理面板使用体验更轻量而且能管理远程的 Docker 环境。对于想可视化操作的人我的建议是装 Docker Engine 之后再补一个 Portainerdocker volume create portainer_data docker run -d \ -p 8000:8000 -p 9443:9443 \ --name portainer \ --restartalways \ -v /var/run/docker.sock:/var/run/docker.sock \ -v portainer_data:/data \ portainer/portainer-ce:latest然后浏览器访问https://服务器IP:9443完成初始化就能在网页上看到所有容器、镜像、卷、网络的状态还能直接操作。这种方案对 Ubuntu 用户友好得多也不会引入嵌套虚拟化的问题。--restartalways保证 Portainer 异常退出后会自动重启搭配宿主机开机自启管理面基本免维护。7.2 Windows WSL2 场景下的 Docker Desktop 体验要点如果你是 Windows 主机想通过 WSL2 里的 Ubuntu 来写代码这时候 Docker Desktop 反而是最顺手的选择。它的 WSL2 backend 会让你在 WSL2 的 Ubuntu 终端里直接使用docker命令无需在 WSL 里再手动装一套 Engine性能和文件共享体验都比较顺畅。几个体验要点WSL2 的版本不要太老Windows 11 上的新版 WSL2 对 Docker Desktop 的支持远比初版稳定安装完 Docker Desktop 后在设置里打开 “Use the WSL 2 based engine” 选项然后在 “Resources - WSL Integration” 里勾选你需要使用 Docker 的那个发行版如果后面遇到 Docker Desktop 报virtualization support not detected多半是 BIOS 里没打开虚拟化或者 Windows 的 Hyper-V 组件没装好。我之前在一台 Windows 笔记本上用 WSL2 Ubuntu 写代码配好 Docker Desktop 之后体验已经非常接近 macOS 上的 Docker 了。但如果你问我在 Windows 上该不该装 Docker Desktop 而不用 WSL2我的回答是Windows 上跑 Linux 容器WSL2 是底线没有 WSL2 的话Docker 使用体验会大打折扣。在我这么多年的使用体验中Ubuntu 安装 Docker 这件事本身不复杂真正复杂的是装完之后对权限、网络、存储、日志这些细节的处理。把官方仓库安装流程背下来当然很容易但理解每一步之间为什么要先清理旧包、为什么要用keyrings目录、为什么要单独装 compose 插件这些才是你能在出问题时快速定位的底气。如果你现在正好要在 Ubuntu 上装 Docker我建议你照着这篇文章的流程走一遍装完别急着关终端花几分钟把docker version、docker info、docker ps这几个命令的输出都看一眼熟悉一下正常状态的样子。等哪天真遇到问题你会感谢自己当初多看了这一眼。
返回列表