上个月把采集服务容器化,本地 docker-compose 起了三个容器,跑了两周一直很稳。搬到客户机房那天,日志里全是 read timeout,目标站的响应一条都没有。第一反应是手上这批短效IP被封了,重新提了一批,还是原样。折腾到下午才反应过来:可能压根没走代理。在次回头看,容器里挂代理的坑就那么几个,记下来给同样踩过的人。
先别急着换IP,先确认请求走没走代理
最快的办法是在容器里手工发一条:curl -x http://账号:密码@IP:端口 https://httpbin.org/ip。能返回代理的出口IP,说明容器到代理、代理到目标站两条链路都通;这时候程序还超时,就不要再怀疑IP了。
另一个信号是 curl -v 的输出:真正走HTTP代理时,会先看到一行 CONNECT 目标域名:443,没有这行就是直连。直连有时候也能拿到数据,只是出口IP不对,这种"不报错但IP不对"的情况比超时更难发现。我后来在请求日志里加了一行,打印脱敏后的实际出口IP,才发现大概一半请求根本没经过代理。
环境变量:大小写各认一半,还只在当前shell活着
这是第一个坑。宿主机上 export http_proxy=... 之后本地跑脚本没问题,但容器是独立环境,docker run 不挂 -e 就什么都没有。
更麻烦的是不同程序认的变量不一样:Python 的 requests 只认小写 http_proxy/https_proxy;curl 大小写都认;Java 完全不看环境变量,要加 -Dhttp.proxyHost 和 -Dhttp.proxyPort。我当时 supervisor 配置里只写了大写 HTTP_PROXY,Python 进程读不到,两个都写上才正常。
如果是 systemd 拉起的服务,也别指望它继承你登录 shell 里的变量,要在 unit 文件里写 Environment=,改完 systemctl daemon-reload。
docker run -d \
-e http_proxy=http://user:pass@1.2.3.4:8080 \
-e https_proxy=http://user:pass@1.2.3.4:8080 \
-e HTTP_PROXY=http://user:pass@1.2.3.4:8080 \
-e HTTPS_PROXY=http://user:pass@1.2.3.4:8080 \
-e NO_PROXY= \
--dns 223.5.5.5 \
your-image
NO_PROXY 里那条内网域名,把请求悄悄放行了
配置是从公司模板抄的,NO_PROXY 里带着 .corp.local 和 10.0.0.0/8。目标站在测试环境里刚好解析到 10 段地址,于是被当成内网流量直连出去了,程序表现就是"代理没生效"。
在容器里执行 env | grep -i proxy,把 NO_PROXY 打出来逐条对一遍,比猜快得多。生产容器我最后是把 NO_PROXY 直接置空,只留确实要走内网的那一两个域名。
代理地址写的是域名,容器里根本解析不到
供应商后台给的是 proxy.xxx.com:8080,宿主机能解析,容器里 DNS 只指到 127.0.0.11,解析失败的表现和连接超时一模一样。改成IP直连,或者 docker run 加 --dns 223.5.5.5,问题就没了。
顺带说一句,容器时间不对也会出怪问题:有的服务商按时间戳做签名,容器时区差八小时,鉴权直接失败,日志里看着像账号密码错。这种时候先去容器里 date 一下,能省不少时间。
单条能通、20并发全红,是句柄和连接池
单请求测通了,压到20并发又开始大面积超时。查下来两件事:一是 ulimit -n 默认1024,多进程一开很快打满;二是 requests 默认每次新建连接,短效IP本来就换得勤,连接复用没做,握手开销全砸在超时上。
改成 Session 加 HTTPAdapter(pool_maxsize=50),超时拆成连接5秒、读取15秒,报错信息一下就清楚了。还有个细节,短效IP别一次提200个放着,按并发数和寿命算着提,提多了也是等它过期,白花钱。
最后落下来的配置清单
- 环境变量双份:docker run 用
-e把大小写都传进去,别依赖宿主机。 - 代码里显式写 proxies:不要赌环境变量,requests 直接在调用处传
proxies=最稳。 - NO_PROXY 自查:置空或只留必须项,进容器
env | grep -i proxy看一眼。 - 代理用IP:别用域名,容器 DNS 单独配,时区一起校正。
- 连接复用:Session + pool_maxsize,按并发给,别用默认值。
- 超时分级:连接5秒、读取15秒,日志打印脱敏后的代理地址和出口IP。
- 淘汰别太急:连续两次失败再换IP,避免一次抖动把好IP扔掉。
我现在用的是星月代理,池子覆盖全国300多个城市,可用率标在90%以上,高匿,短效IP自动轮换,API实时提取、白名单、会话保持都支持,后台能看到每条订单的提取记录和用量。新用户可以先领免费试用IP,拿上面这套清单在容器里跑一遍再决定买哪个套餐——参数表上的数字,和你在容器里实际跑出来的,经常不是一回事。
代理在容器里不生效,先按环境变量、NO_PROXY、DNS、连接池这四层过一遍,八成都能定位到。实在找不到原因,就在容器里手工 curl 一次,把"链路问题"和"IP问题"分开,比反复换服务商有效得多。
容器里代理不生效,大多不是IP的问题:环境变量的传递方式、NO_PROXY误放行、代理域名解析失败、并发上来后连接池不够,这四层按顺序查一遍,比换一批IP快得多。