• 微信
    咨询
    微信在线咨询 服务时间:9:00-18:00
    纵横数据官方微信 使用微信扫一扫
    马上在线沟通
  • 业务
    咨询

    QQ在线咨询 服务时间:9:00-18:00

    选择下列产品马上在线沟通

    纵横售前-老古
    QQ:519082853 售前电话:18950029581
    纵横售前-江夏
    QQ:576791973 售前电话:19906048602
    纵横售前-小李
    QQ:3494196421 售前电话:19906048601
    纵横售前-小智
    QQ:2732502176 售前电话:17750597339
    纵横售前-燕子
    QQ:609863413 售前电话:17750597993
    纵横值班售后
    QQ:407474592 售后电话:18950029502
    纵横财务
    QQ:568149701 售后电话:18965139141

    售前咨询热线:

    400-188-6560

    业务姚经理:18950029581

  • 关注

    关于纵横数据 更多优惠活动等您来拿!
    纵横数据官方微信 扫一扫关注官方微信
  • 关闭
  • 顶部
  • 您所在的位置 : 首页 > 新闻公告 > 云主机容器网络异常如何处理?

    云主机容器网络异常如何处理?

    如果说容器启动失败让人着急,那容器网络异常简直让人想砸电脑。为什么呢?因为启动失败报错信息明明白白,你至少知道从哪里下手。可网络出问题了,表现千奇百怪——有时候是容器里ping不通外网,有时候是外部访问不了容器里的服务,还有时候容器之间明明在同一台主机上却死活连不上。

    我在云主机上跑容器这几年,网络问题遇到的次数最多,解决的周期也最长。今天这篇文章,我想把那些真实的案例和排查思路写下来,希望对你有帮助。

    容器内部无法访问外网:从DNS到网关逐个查

    先讲一个最近遇到的案例。一个同事跟我说,他的容器能正常启动,但是容器里curl百度没有任何响应,一直卡着直到超时。他试了好几个镜像,都是同样的问题。他怀疑是云主机的网络配置出了问题,可云主机本身上网是正常的。

    我登录到那台云主机上,先执行了一个简单的测试。docker run --rm alpine ping 114.114.114.114。这个命令会启动一个临时的alpine容器,尝试ping一个公共DNS服务器。神奇的事情发生了,ping是通的,说明容器访问外网的IP层没有问题。那为什么curl百度不行呢?问题出在DNS解析上。

    进入容器内部,cat /etc/resolv.conf看了一下,发现DNS服务器被设置成8.8.8.8,这是谷歌的公共DNS。但这台云主机所在的网络环境,对国外DNS服务器的访问做了限制,导致DNS请求一直得不到响应。解决方案很简单,在启动容器的时候用--dns参数指定一个可用的DNS服务器,比如云厂商内网提供的DNS或者114.114.114.114。改完之后,curl百度就正常了。

    容器内部无法访问外网,排查思路其实很清晰。先用docker exec进容器,依次测试这几个东西。先ping一个IP地址,比如114.114.114.114,如果不通,说明容器连基本的网络路由都有问题。如果IP能通但域名不通,那八九不离十是DNS的问题。用cat /etc/resolv.conf看容器使用的DNS服务器,用nslookup或者dig测试域名解析是否正常。如果IP和域名都通,只有特定的端口不通,那可能是目标服务器的防火墙问题,也可能是容器里的应用本身配置有误。

    外部无法访问容器里的服务:端口映射和防火墙的博弈

    这个问题太常见了。你在云主机上跑了一个Web服务,docker run -p 8080:80,然后打开浏览器访问云主机的公网IP加8080端口,结果页面打不开。

    我遇到过这样一个案例。一个开发人员照着文档启动了Nginx容器,端口映射也做了,docker ps显示端口映射关系正常。他在云主机上用curl localhost:8080,能正常返回Nginx的欢迎页面。但是从自己的电脑上访问公网IP加8080端口,就是连不上。他怀疑是容器网络模式的问题,折腾了半天也没找到原因。

    我登录上去看了一下,第一反应是检查云主机的防火墙。执行iptables -L -n,发现INPUT链默认策略是DROP,而且没有任何规则放行8080端口。Docker在创建端口映射的时候,会自动在iptables里添加规则,把宿主机端口上的流量转发到容器里去。但这条规则是加在DOCKER链里的,流量要先通过INPUT链的允许才能到达DOCKER链。INPUT链默认丢弃所有流量,导致外部的请求根本到不了Docker的转发规则。

    解决方案是在宿主机的防火墙上放行对应的端口。对于iptables,添加一条规则允许8080端口的TCP连接。对于使用firewalld的系统,用firewall-cmd添加端口。改完之后,从外部访问就正常了。

    还有一个容易被忽视的问题是云厂商的安全组。很多人在云主机上配置了半天防火墙,结果忘了去云厂商的控制台里放行安全组规则。安全组是云主机外面的第一道防线,安全组没放行的端口,流量根本到不了云主机。排查的时候,先确认安全组里有没有放行对应的端口,再检查本机防火墙,顺序别搞反了。

    容器之间无法通信:同一主机和跨主机的区别

    容器之间的通信有两种情况,一种是同一台云主机上的两个容器互相访问,另一种是不同云主机上的容器互相访问。这两种情况的排查思路完全不一样。

    先讲一个同主机容器通信的案例。两个容器跑在同一台云主机上,容器A要访问容器B的服务。容器B启动的时候映射了端口,比如docker run -p 3306:3306。容器A里配置的连接地址是云主机的内网IP加3306端口,连接超时。后来改成了127.0.0.1加3306端口,还是不行。

    问题出在哪里呢?容器A访问127.0.0.1的时候,这个地址指向的是容器A自己的回环接口,而不是宿主机的。用云主机的内网IP也不行,因为Docker的端口映射默认只监听在0.0.0.0上,但从容器A发出的请求经过宿主机的网络栈时,源地址是容器的地址,可能会被防火墙或者内核参数拦截。

    更好的做法是让两个容器在同一个用户自定义的网络上。先docker network create mynet创建一个桥接网络,然后两个容器启动的时候都用--network mynet。这样容器A就可以直接用容器B的名字作为主机名来访问,比如curl http://容器B:80。这种方式不需要做端口映射,容器之间可以直接通信,效率高而且不容易出问题。

    跨主机的容器通信就复杂多了。最简单的方案是用Kubernetes,它内置了跨主机通信的方案。如果你坚持用Docker原生的方式来跨主机通信,可以选择overlay网络。但overlay网络需要配置键值存储,还要确保云主机之间的UDP端口能通,配置起来相当麻烦。我在云主机上试过几次,最后都放弃了,还是投向了Kubernetes的怀抱。

    容器网络时断时续:MTU和网络拥塞的坑

    容器网络有时候会出现一个诡异的现象,大部分时间是正常的,但偶尔会卡住几秒钟或者丢几个包。这种间歇性的问题最难排查。

    有一次,一个容器化的应用频繁报错,说是连接数据库的超时时间到了。检查了数据库本身的性能,一切正常。在容器里用ping测试到数据库服务器的网络,发现偶尔会有几秒钟的延迟飙升,从几毫秒跳到了几百毫秒。但是用云主机本身去ping同一个数据库服务器,延迟一直是稳定的。

    后来发现问题是MTU不匹配。云主机的物理网卡MTU是1500,但Docker默认创建的虚拟网桥MTU也是1500。然而云厂商的底层网络实际支持的MTU可能是1450或者更小。当容器发出的包大小在1450到1500之间的时候,这些包需要被分片,分片过程会消耗时间,有时候还会丢包。把容器的MTU改成1450之后,延迟飙升的问题就消失了。

    调整容器MTU的方法是在docker run的时候加上--mtu参数,或者在daemon.json里全局设置。对于已经存在的容器,需要重建才能生效。

    还有一个可能性是云主机的网络带宽被占满了。如果同一台云主机上跑了太多容器,或者某个容器在进行大流量传输,整个宿主机的网络出口就会拥堵,所有容器的网络都会受影响。用iftop或者nethogs这些工具可以看到实时的网络流量,找出哪个容器或者哪个进程在占用带宽,然后针对性地做限流或者迁移。

    容器网络延迟高:地理位置和路由的问题

    如果你的容器需要访问位于另一个城市甚至另一个国家的服务,延迟高是很正常的。但有时候,明明两个服务都在同一个云厂商的同一片区域里,延迟还是高得离谱,这就值得查一查了。

    我遇到过一个问题。一个容器需要调用同一个云平台上的另一个API服务,两个服务都在同一个可用区里。但是每次请求的响应时间都在几百毫秒,完全不像内网该有的速度。抓包看了一下,发现请求的包竟然被路由到了公网,绕了一大圈才回来。原因是在容器里解析API服务的域名时,得到了一个公网IP而不是内网IP。

    解决办法是在容器启动的时候修改DNS搜索域,或者把API服务的内网IP直接写到容器的hosts文件里。docker run的时候加上--add-host参数,把域名和内网IP的对应关系告诉容器。这样请求就会走内网,延迟从几百毫秒降到了几毫秒。

    另外一个导致延迟高的问题是容器里的DNS解析太慢。有些容器镜像的/etc/nsswitch.conf文件里,hosts项的配置顺序是先dns再files,导致每次解析域名都会先查DNS,即使这个域名在/etc/hosts里已经写好了。改成files dns,就会先查hosts文件,找不到再查DNS,解析速度快很多。

    端口被占用:容器和宿主机抢端口

    这个问题比较简单直白。启动容器的时候报错说端口已经被占用,或者容器启动成功但是服务不可用。基本上都是因为宿主机上的端口被其他进程占用了,或者被其他容器占用了。

    我见过一个案例,一个容器里跑了MySQL,映射了3306端口。过了一段时间,另一个容器也想用3306端口,启动的时候就报错了。解决方案有两个,要么换一个宿主机的端口映射给新容器,比如映射3307到容器的3306。要么停掉旧的容器或者修改它的端口映射。

    用netstat -tlnp或者lsof -i:3306可以快速看到是哪个进程占用了这个端口。如果是容器占用的,docker ps可以看到容器的端口映射情况。查到之后,根据实际需求决定是停掉旧的还是给新的换端口。

    容器网络性能差:频繁的系统调用和上下文切换

    如果你发现容器里的网络吞吐量明显低于云主机本身的网络性能,或者CPU使用率很高但数据传输速度上不去,那可能是网络性能配置的问题。

    一个真实的案例。一台配置不错的云主机,宿主机上跑了一个代理服务的容器。业务量上来之后,发现网络吞吐量始终上不去,CPU使用率已经接近百分之百。检查了容器里的应用代码,没有什么明显的性能问题。后来发现是Docker默认使用的端口映射方式导致的性能瓶颈。

    Docker默认的端口映射是通过iptables的NAT来实现的,每个数据包都需要经过宿主机内核的Netfilter框架,开销比较大。对于高吞吐量的场景,可以考虑使用host网络模式。docker run --network host启动容器,容器直接使用宿主机的网络栈,没有NAT的开销,性能会好很多。代价是容器和宿主机共享网络命名空间,端口不能冲突。

    还有一个优化点是关闭不必要的网络命名空间特性。比如,如果你的容器不需要IPv6,可以在daemon.json里禁用IPv6支持。不需要用户自定义的iptables规则,可以禁用iptables的介入。这些微小的优化叠加起来,对网络性能会有明显的改善。

    总结

    说到最后,我想总结一下云主机容器网络异常的排查思路。网络问题虽然千变万化,但归根结底逃不出这几个层面。

    先从最基础的开始看。容器本身的网络配置对不对,用docker inspect可以查看容器的网络模式、IP地址、网关这些信息。然后看宿主机的网络通不通,ping一下容器里的IP地址,看宿主机能不能跟容器通信。接着检查网络链路,从容器往外ping,看是第一步就不通,还是到了某一步才断掉。再看DNS和路由,域名解析是不是正常,路由表是不是正确。最后检查防火墙和安全组,这是最容易遗漏的地方,也是最容易修复的地方。

    每个层面都有对应的工具来验证。ip addr看网络接口,ip route看路由表,iptables看防火墙规则,tcpdump抓包看实际的数据包走向,ping和traceroute看网络连通性和路径,curl和telnet测试具体的服务端口是否可达。把这些工具组合起来用,大部分网络问题都能找到根因。

    说句实在话,容器网络本来就是个复杂的东西。Docker做了很多自动化的配置,让你觉得网络很简单,但其实底层有网桥、有VETH对、有iptables规则、有网络命名空间,还有各种内核参数在起作用。出了问题的时候,这些自动化配置反而成了黑盒,让你不知道从哪里下手。但只要你愿意一层层剥开来看,用工具去验证每一个环节,网络问题并没有想象中那么可怕。



    最新推荐


    微信公众帐号
    关注我们的微信