ARTICLE DETAIL

资讯详情

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

pip安装报错NewConnectionError?DNS域名解析故障排查与修复完整指南

pip安装报错NewConnectionError?DNS域名解析故障排查与修复完整指南 前几天同事跑过来说新装的 Linux 服务器上执行pip install直接罢工报错信息看起来特别唬人一大段Retrying最后甩出一个NewConnectionError。我瞄了一眼就知道这十有八九是域名解析挂了也就是大家常说的 DNS 问题。说白了pip 在下载包之前要先通过网络把pypi.org解析成 IP 地址如果这一步失败后面连建立连接的机会都没有自然就会抛NewConnectionError。这个报错在 Python 开发和运维环境里非常常见尤其是刚搭好的机器、刚拉起来的容器或者是换了办公网络之后稍微不注意网络配置就会触发。我写这篇文章就是想把这条报错的完整排查思路和修复方法整理出来适合所有用 pip 安装 Python 包的朋友新手可以照着操作老手也可以看看有没有漏掉的细节。1. 看到 NewConnectionError 别慌先看懂它在说什么1.1 一个典型的报错现场先还原一下最常见的报错现场。假设你在一个刚初始化的 Python 环境里执行pip install requests结果终端输出的不是进度条而是一连串重试日志Collecting requests Retrying (Retry(total4, connectNone, readNone, redirectNone, statusNone)) after connection broken by NewConnectionError(pip._vendor.urllib3.util.retry.Retry object at 0x7f...: Failed to establish a new connection: [Errno -2] Name or service not known): /simple/requests/ Retrying (Retry(total3, connectNone, readNone, redirectNone, statusNone)) after connection broken by NewConnectionError(pip._vendor.urllib3.util.retry.Retry object at 0x7f...: Failed to establish a new connection: [Errno -2] Name or service not known): /simple/requests/ ...后面的内容基本就是不断重试最后报Could not fetch URL再往后就是WARNING: Retrying最终失败退出。很多朋友看到这一大片日志就慌了以为是什么深奥的网络问题其实关键信息就两处NewConnectionError和[Errno -2] Name or service not known。1.2 报错信息拆解Errno -2 到底代表什么要理解这个错误得先看 pip 下载包时走的完整链路。pip 底层用的是 requests 库requests 底层用 urllib3而 urllib3 在发起 HTTPS 请求之前需要先通过操作系统把pypi.org解析成 IP 地址然后才能建立 TCP 连接。这条链路上任何一环出问题都有可能以NewConnectionError的方式暴露出来。[Errno -2] Name or service not known里面的-2是来自系统调用getaddrinfo()的错误码在绝大多数 Linux 发行版上对应EAI_NONAME含义非常直接系统找不到这个主机名对应的地址。换句话说你的机器在问“pypi.org 的 IP 是多少”的时候DNS 服务器回答“我不知道有这个名字的机器”。这不是 pip 自身的问题而是操作系统层面的域名解析失败。这里有个容易混淆的变种也要提前说清楚如果你看到的是[Errno -3] Temporary failure in name resolution代码是EAI_AGAIN那意思是 DNS 服务器没响应而不是查不到记录。这两个错误经常被混为一谈但排查方向完全不同。前者要去看解析记录是否存在、系统用的 DNS 是否正常返回后者要去看网络到 DNS 服务器之间的链路。用生活里的例子类比DNS 就相当于你手机里的通讯录。你要给某个联系人打电话发 HTTP 请求必须先知道他的电话号码IP 地址。通讯录里没有这个人或者你的手机卡欠费了导致通讯录同步不下来电话自然拨不出去。Name or service not known就是“通讯录里查无此人”Temporary failure in name resolution则是“通讯录服务打不开”。2. 三步定位判断问题根源是否在 DNS2.1 第一组测试区分全局域名解析还是单一域名解析遇到这种报错不要急着改东西先做测试。因为“Name or service not known”可能只是某个域名解析不了也可能是整台机器所有域名都解析不了。两种情况的处理方式截然不同。我通常先跑这组命令# 测试一个常见的外网域名 ping -c 2 baidu.com # 测试 pip 实际访问的域名 ping -c 2 pypi.org如果两个都显示“Name or service not known”那说明是全局 DNS 配置出了问题优先检查/etc/resolv.conf和网络配置。如果baidu.com能解析但pypi.org失败那就说明解析服务是通的问题可能出在特定域名上比如内网 DNS 没有配置转发规则或者上游 DNS 对某些域名返回了异常结果。这一步虽然简单但很多人会忽略。我见过有同事在容器里装了dnsutils之后dig一通乱敲结果发现百度和 pypi 都无法解析最后查出容器的 DNS 地址指向了一个不存在的内网 IP。如果一开始就做这个对比测试节省的可不是一两分钟。2.2 第二组测试验证 pip 实际使用的系统解析链路ping测试能通过不代表 Python 进程就一定能解析成功。pip 运行时的解析行为和操作系统命令行解析工具不一定走同一个路径。最直接的办法是用 Python 自己测一下python -c import socket; print(socket.gethostbyname(pypi.org))如果这个命令报同样的[Errno -2] Name or service not known那基本可以确定 Python 进程拿到的系统解析配置就有问题。如果它成功输出了 IP 地址但pip install仍然报错那就说明 pip 请求的地址、端口或者代理环节出了问题需要往前多看一层。另外一个很值得做的测试是用nslookup明确指定 DNS 服务器来解析# 使用默认 DNS nslookup pypi.org # 指定一个公共 DNS 服务器 nslookup pypi.org 223.5.5.5这里我把223.5.5.5阿里 DNS作为替代测试地址你也可以用114.114.114.114或8.8.8.8。关键对比在于默认配置解析失败指定 DNS 后成功说明本机的 DNS 配置或当前网络环境有问题指定 DNS 后依然失败那就要考虑是不是网络隔离、防火墙或者上游 DNS 本身不健康。2.3 第三组测试用细粒度工具锁定故障位置前面两组测试基本能把问题缩小到“系统 DNS 配置”或“网络环境链路”。但有些时候还需要更细的观察我习惯再用下面这几个命令补一刀# 查看系统当前用的 DNS 服务器 cat /etc/resolv.conf # 查看网卡实际配置NetworkManager 接管时resolv.conf 可能不显示真实来源 nmcli dev show | grep DNS # 查看 systemd-resolved 的解析状态 resolvectl status这组测试背后的逻辑很关键现在很多 Linux 发行版默认用systemd-resolved、NetworkManager或者netplan管理网络手动改/etc/resolv.conf不一定能反映系统真正使用的 DNS。有时候你cat /etc/resolv.conf看到的是127.0.0.53这是 systemd-resolved 的本地回环地址真正的上游 DNS 在resolvectl status里才能看到。如果不区分这些就会出现“我改了 resolv.conf怎么还是没用”的尴尬情况。顺便提一个细节很多新人在 Linux 上看到/etc/resolv.conf里只有一个nameserver 127.0.0.53就以为系统没有配置 DNS。其实这个本地回环地址正是 systemd-resolved 的服务入口DNS 请求会由它转发给上游。要确认上游到底是谁一定要执行resolvectl status或systemd-resolve --status。下面这张表可以帮你快速对号入座测试结果可能原因下一步方向所有域名都无法解析/etc/resolv.conf为空或 DNS 地址不可达修复系统 DNS 配置部分域名无法解析内网 DNS 转发规则缺失或本地缓存异常更换 DNS 或检查转发规则Python 无法解析但 ping 正常Python 环境或进程受到代理、LD_LIBRARY 等影响检查环境变量和 Python 安装方式指定 DNS 后仍然解析失败网络隔离、防火墙、上游 DNS 异常检查出口网络和路由3. 动手修复从临时方案到一劳永逸3.1 快速应急临时修改 DNS 并立即生效如果问题定位到本机 DNS 配置先做临时修复保证当前任务能跑完。临时修复的意思就是改完立刻生效重启后可能丢失但这一步能帮你快速验证。在大部分 Debian/Ubuntu 系统上直接编辑/etc/resolv.conf是反馈最快的方式sudo vim /etc/resolv.conf把内容改成nameserver 223.5.5.5 nameserver 114.114.114.114保存退出后不需要重启网络服务立刻再跑一次nslookup pypi.org python -c import socket; print(socket.gethostbyname(pypi.org))看到正常输出 IP 地址后pip install大概率就能恢复。这里我用的是阿里 DNS 和 114DNS这两个在国内访问速度快对大多数开发者来说比较省心。如果你在海外服务器上可能更适合用8.8.8.8或者1.1.1.1不过那只是需要根据地域做个选择不影响排查思路。要注意很多现代系统会在一段时间后自动重写/etc/resolv.conf因为网络管理服务会接管这个文件。临时改完发现过一会又变回去了不算奇怪。这时候需要用下面的永久方案。3.2 稳妥落地把 DNS 配置写进系统的网络管理服务永久配置 DNS 的方法取决于你的系统用哪个网络管理组件。我给三个最常见的方案。NetworkManager 管理的桌面系统或服务器# 查看当前连接名称 nmcli connection show # 给指定连接设置静态 DNS nmcli connection modify Wired connection 1 ipv4.dns 223.5.5.5 114.114.114.114 nmcli connection modify Wired connection 1 ipv4.ignore-auto-dns yes # 重启连接让配置生效 nmcli connection down Wired connection 1 nmcli connection up Wired connection 1systemd-resolved 管理的系统常见于 Ubuntu 18.04sudo vim /etc/systemd/resolved.conf在[Resolve]段下取消注释并修改[Resolve] DNS223.5.5.5 114.114.114.114 FallbackDNS8.8.8.8然后重启服务sudo systemctl restart systemd-resolved执行resolvectl status可以看到新的上游 DNS 已经生效。Windows 用户用命令行配置# 查看网卡名称 netsh interface show interface # 设置静态 DNS netsh interface ip set dns WLAN static 223.5.5.5 netsh interface ip add dns WLAN 114.114.114.114 index2macOS 用户可以直接在“系统设置 → 网络 → Wi-Fi → 详细信息 → DNS”里添加223.5.5.5和114.114.114.114也可以用命令行networksetup -setdnsservers Wi-Fi 223.5.5.5 114.114.114.114三个平台配置完都需要清理一下系统 DNS 缓存再重新测试避免旧的失败记录继续干扰判断。顺便提一句如果你自己搭过 DNS 服务器或者在内网里有专属 DNS 服务重点检查它的上游转发规则和缓存策略。很多时候内网机器解析外网域名失败就是因为内网 DNS 服务器没有配置合理的外部转发。3.3 换一个更快的源把 pypi 仓库切换到国内镜像DNS 修复是第一步但如果你的网络访问官方pypi.org一直不稳光修 DNS 也只是勉强能用。我更推荐把 pip 的默认源切到国内镜像既降低域名解析出问题的概率也能明显提升下载速度。最简单的方式是全局配置pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple如果你需要临时指定不修改全局配置pip install requests -i https://mirrors.aliyun.com/pypi/simple/常用的国内 pip 镜像源地址我整理在这张表里镜像源地址备注清华https://pypi.tuna.tsinghua.edu.cn/simple更新及时用户多阿里云https://mirrors.aliyun.com/pypi/simple/速度稳定腾讯云https://mirrors.cloud.tencent.com/pypi/simple/适合腾讯云内网使用豆瓣https://pypi.douban.com/simple/老牌镜像偶尔不稳定切换之后再做一次pip install测试通常会很快。这里有一个容易踩的坑有些镜像源对 HTTPS 证书的要求和官方源不完全一致如果遇到SSL相关报错可以在pip install时临时加上--trusted-host参数但我不建议长期全局配置这个参数会降低安全性。更好的做法是选择证书更新及时的镜像源。3.4 容器与虚拟环境场景下的特殊处理如果你是在 Docker 容器里跑 Python 应用那情况会稍微复杂一点。容器内部的/etc/resolv.conf通常由 Docker 引擎注入手动改容器内文件很容易被重置或并没有真正改变容器使用的 DNS。最推荐的做法是在启动容器时指定 DNSdocker run --dns 223.5.5.5 --dns 114.114.114.114 python:3.11 pip install requests如果是 docker-compose 编排也可以统一配置services: app: image: python:3.11 dns: - 223.5.5.5 - 114.114.114.114如果你希望宿主机上所有容器都默认使用某个 DNS可以修改 Docker daemon 的/etc/docker/daemon.json{ dns: [223.5.5.5, 114.114.114.114] }然后重启 Dockersudo systemctl restart docker重启之后已经存在的容器需要重建才能继承这个配置这一点容易被忽略。另外Python 虚拟环境本身不涉及 DNS 配置虚拟环境只是隔离 Python 包底层解析依然走操作系统网络栈。所以不要在虚拟环境层面找 DNS 问题没意义。4. 实测现场那些常见的坑和排查笔记4.1 Docker 容器里改了 resolv.conf 却不生效实测中我碰到过最经典的一次容器里cat /etc/resolv.conf看到的确实是狗屁不通的 DNS 地址我直接vim改成223.5.5.5然后pip install居然还是Name or service not known。当时也挺困惑后来查了 Docker 文档才意识到容器里的/etc/resolv.conf是从宿主机继承并动态生成的Docker 引擎会在容器启动和网络重连时按自己的逻辑重新写入这个文件。你手动改了过一分钟可能就被覆盖回原来的值。正确做法就是我前面写的要么启动时带--dns参数要么在 daemon 配置里统一设置。还有一种情况是容器通过--network host启动完全使用宿主机网络栈那就要排查宿主机的 DNS而不是容器内部。这个思路稍不注意就会绕远路。4.2 IPv6 优先引起的间歇性故障另一个比较隐蔽的坑是 IPv6 配置对 pip 连接的影响。如果系统 DNS 返回了域名的 IPv6 地址而你的网络环境完全没有 IPv6 路由Python 在尝试连接时可能会卡住虽然没有直接报Name or service not known但同样会出现连接失败类错误最终表现形式也像是网络不健康。定位方式很简单先看pypi.org解析出来的地址是几类地址再用ping -4和ping -6分别测试。如果确认是 IPv6 问题可以通过配置让解析优先返回 IPv4。在 Python 层面可以设置socket.AF_INET强制 IPv4但在日常 pip 使用中更实用的做法是在系统层面打开 IPv4 优先。Linux 下可以修改/etc/gai.conf取消precedence ::ffff:0:0/96 100这一行的注释表示 IPv4-mapped 地址优先级更高。我见过有些朋友跑到 Python 问题群里提问自己被这个问题卡了两天最后发现是机房服务器开了 IPv6 但路由不通。这类问题最怕一开始就钻进报错文本里实际先把网络栈的基础属性摸清楚会更快。4.3 内网环境下 DNS 限制怎么处理公司内网或校园网的场景下DNS 问题往往更复杂。原因在于内网有大量内网域名需要解析不能简单地把 DNS 全换成公共 DNS否则内网域名可能解析不出来。这时候我一般建议分两步走。第一步判断内网 DNS 服务器是否能够解析外网域名。很多内网 DNS 设计上只负责内网记录外网解析需要向上游转发。如果转发规则缺失就会出现“内网域名正常、外网全部失败”的现象。这种问题终端用户改不动需要联系网管。第二步如果确认内网 DNS 能解析外网但 pip 还是偶尔报错那很可能是内网 DNS 对pypi.org这类域名的缓存太旧或者被策略性丢弃。这种情况下我通常不建议直接换 DNS 配置而是用“局部绕过”的思路比如给 pip 配置国内镜像源因为镜像源域名往往也在国内 CDN 上解析后的 IP 更稳定可控。如果你已经配置了系统级代理也要确认代理服务本身能正常访问外网域名不然 Python 的请求会被带到一条更曲折的路上。4.4 浏览器能打开 pypipip 却报错这个现象特别容易让人迷惑浏览器明明可以访问https://pypi.org终端里pip install就是解析失败。很多人第一反应是 pip 坏了或者自己的机器被“限制”了。实际上浏览器和 pip 的解析路径并不完全等价。浏览器可能有自己的 DNS 缓存可能走系统代理甚至用了 DoHDNS over HTTPS而 pip 进程默认用的是操作系统原生解析。这就导致浏览器“看起来正常”但系统的原生解析链路依然不健康。遇到这种情况我建议直接忽略浏览器表现回到命令行里用nslookup和python -c import socket...来测以终端的解析结果为准。还有一种可能系统里设置了HTTP_PROXY、HTTPS_PROXY环境变量pip 会优先走代理即使代理指向的是一个已经失效的地址报错也会被伪装成连接问题。排查时可以先临时清掉这些环境变量再测unset HTTP_PROXY HTTPS_PROXY ALL_PROXY pip install requests如果清了代理就正常那问题就在代理配置上而不是 DNS。这个步骤虽然简单但非常有效。4.5 修复后过了一天又复发修复完 DNS 发现第二天又报同样的错这种情况多半是系统网络管理服务做了事情。典型的是笔记本在无线网络和有线网络之间切换或者虚拟机休眠唤醒后网络重连/etc/resolv.conf会被重新生成。如果每次都要手动改那说明你治标没治本。我的做法是遇到经常切换网络环境的工作机器直接把/etc/systemd/resolved.conf里的 DNS 写死同时把 NetworkManager 的自动 DNS 关掉ipv4.ignore-auto-dns yes。这样即使网络切换只要网卡本身能拿到 IPDNS 也会稳定使用你指定的地址。对于云服务器场景如果/etc/resolv.conf经常被云平台初始化脚本改回默认值还需要检查一下是否有dhclient或cloud-init的额外配置在重置网络设置。5. 排查命令速查与习惯沉淀5.1 常用命令速查表把这些年反复用到的排查命令整理成了一个速查表建议收藏一份遇到问题直接按顺序跑。表格里标了不同平台的差异。操作目的Linux 命令Windows 命令macOS 命令查看当前 DNS 配置cat /etc/resolv.confipconfig /allscutil --dns测试域名解析nslookup pypi.orgnslookup pypi.orgnslookup pypi.org指定 DNS 测试nslookup pypi.org 223.5.5.5nslookup pypi.org 223.5.5.5nslookup pypi.org 223.5.5.5清理 DNS 缓存resolvectl flush-cachesipconfig /flushdnssudo killall -HUP mDNSResponderPython 进程解析测试python -c import socket; print(socket.gethostbyname(pypi.org))同左同左查看系统解析服务resolvectl status无scutil --dns每个命令跑完重点看两件事输出里有没有 IP 地址报错是 No answer 还是 Timeout。前者通常是记录问题或路由问题后者通常是链路问题。5.2 我沉淀下来的排查顺序遇到pip install相关网络报错我现在已经形成了固定的排查节奏基本不会乱。第一步看报错关键词区分Name or service not knownDNS 解析不到和Temporary failure in name resolutionDNS 服务不可达。第二步用nslookup对比测试默认 DNS 和公共 DNS锁定是配置问题还是链路问题。第三步检查系统网络管理服务的实际生效配置特别是 systemd-resolved 和 NetworkManager 接管的情况下不要只盯着/etc/resolv.conf。第四步临时替换 DNS 做验证确认修复后立刻把配置固化到系统层面。第五步顺手把 pip 源切到国内镜像降低后续下载的解析风险。这套流程跑下来目前还没有遇到解决不了的问题。就算偶尔遇到比较偏的坑比如 IPv6 优先、代理环境变量干扰、容器解析异常也都能在前面任何一个阶段被定位出来。归根结底DNS 问题的本质就是“名字到地址的翻译”链路断了只要能找到断点在哪修复方案只是顺手的事。我个人在实际排查中还保留一个小习惯每次搭建新的开发环境装完 Python 后会第一时间执行下面这条命令把所有基础网络能力一起验证掉。python -c import socket, urllib.request; print(socket.gethostbyname(pypi.org)); print(urllib.request.urlopen(https://pypi.org, timeout5).status)如果这条命令能顺利输出 IP 和 200那后续 pip 基本不会在网络上出幺蛾子。如果这一步就失败趁早解决 DNS 问题别等到真要装包的时候才手忙脚乱。
返回列表