这是个困扰笔者2天的问题,过程中也查阅大量 stackoverflow / google / baidu(大多数解决方案都是内存小了 / 升级 / 重装等,这边都 not work ),今天终于想通解决了,故在此记录,望给有相同经历的同学提供一种思路。
先来看下具体问题,集群完成后发现 kubectl version 报错:net/http: TLS handshake timeout,追加 --v 9 查看详细日志后发现 Client 端正常,服务端服务正常响应。
[root@***-24-69-3 ~]# kubectl version --v 9
I0511 09:49:55.099313 2329027 loader.go:372] Config loaded from file: /etc/kubernetes/admin.conf
I0511 09:49:55.099762 2329027 round_trippers.go:466] curl -v -XGET -H "Accept: application/json, */*" -H "User-Agent: kubectl/v1.23.6 (linux/amd64) kubernetes/ad33385" 'https://***.24.69.222:6443/version?timeout=32s'
I0511 09:49:55.100226 2329027 round_trippers.go:510] HTTP Trace: Dial to tcp:***.24.69.222:6443 succeed
I0511 09:50:05.100639 2329027 round_trippers.go:570] HTTP Statistics: DNSLookup 0 ms Dial 0 ms TLSHandshake 10000 ms Duration 10000 ms
I0511 09:50:05.100654 2329027 round_trippers.go:577] Response Headers:
Client Version: version.Info{Major:"1", Minor:"23", GitVersion:"v1.23.6", GitCommit:"ad3338546da947756e8a88aa6822e9c11e7eac22", GitTreeState:"clean", BuildDate:"2022-04-14T08:49:13Z", GoVersion:"go1.17.9", Compiler:"gc", Platform:"linux/amd64"}
I0511 09:50:05.100728 2329027 helpers.go:237] Connection error: Get https://***.24.69.222:6443/version?timeout=32s: net/http: TLS handshake timeout
F0511 09:50:05.100742 2329027 helpers.go:118] Unable to connect to the server: net/http: TLS handshake timeout
goroutine 1 [running]:
k8s.io/kubernetes/vendor/k8s.io/klog/v2.stacks(0x1)
# 省略部分异常栈
# 直接执行 curl 访问,问题一致
[root@***-24-69-3 ~]# curl -v -k -H "Accept: application/json, */*" -H "User-Agent: kubectl/v1.23.6 (linux/amd64) kubernetes/ad33385" 'https://***.24.69.222:6443/version?timeout=32s'
* About to connect() to ***.24.69.222 port 6443 (#0)
* Trying ***.24.69.222...
* Connected to ***.24.69.222 (***.24.69.222) port 6443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
之后笔者用了比较笨的办法恢复了故障的节点,并保留了一台作为故障排查。
正常节点上用命令观察其实能发现故障节点服务一切是正常的,那么这里可以基本上判断 CNI 网络插件 Flannel 是正常服务的,这里就开始回看 kubectl 的 https 协议问题了。
[root@***-24-69-2 ~]# kubectl get no
NAME STATUS ROLES AGE VERSION
***-24-69-2.*** Ready control-plane,master 4d19h v1.23.6
***-24-69-3.*** Ready control-plane,master 4d19h v1.23.6
***-24-69-30.*** Ready <none> 35m v1.23.6
***-24-69-31.*** Ready <none> 15m v1.23.6
***-24-69-32.*** Ready <none> 30s v1.23.6
***-24-69-4.*** Ready control-plane,master 4d19h v1.23.6
***-24-69-5.*** Ready <none> 23h v1.23.6
***-24-69-6.*** Ready <none> 22h v1.23.6
# 24-69-3 观察 flannel 正常
[root@***-24-69-3 ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
4: bond0: <BROADCAST,MULTICAST,MASTER,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 38:68:dd:4f:e7:58 brd ff:ff:ff:ff:ff:ff
6: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:9b:fa:a8:00 brd ff:ff:ff:ff:ff:ff
inet ***.17.0.1/16 brd ***.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
13: bond0.169@bond0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 38:68:dd:4f:e7:58 brd ff:ff:ff:ff:ff:ff
inet ***.24.69.3/24 brd ***.24.69.255 scope global bond0.169
valid_lft forever preferred_lft forever
14: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default
link/ether 6a:89:1a:8a:92:1f brd ff:ff:ff:ff:ff:ff
inet 10.244.1.0/32 scope global flannel.1
valid_lft forever preferred_lft forever
这里需要回顾下 HTTPS 的安全通信机制(这里借用了《图解HTTP》的相关内容)
1. 客户端发送 Client Hello 报文与服务器进行SSL通信,报文中指明了客户端支持的SSL的版本,加密组件(加密算法及密钥长度)
2. 服务器可以进行 SSL 通信时,以 Server Hello 报文作为回应,报文中也包含了SSL版本及加密组件
3. 紧接着服务器发送 Certificate 报文,将自己的公开密钥证书发给客户端
4. 服务器发送 Server Hello Done 报文通知客户端,握手部分结束

5. SSL第一次握手结束后,客户端发送Client Key Exchange报文回应,报文中包含使用服务器公开密钥加密的一种被称为Pre-master secret的随机密码串,这个密码串十分重要,它作为后面通信的共享密钥
6. 接着客户端继续发送Change Cipher Spec报文,该报文提示服务器,在此报文之后的通信会采用Pre-master secret密钥加密
7. 客户端继续发送Finished报文,此报文包含了之前所有报文的整体校验值,服务器能否正确的解密该报文决定了此次握手协商是否成功
8. 服务器同样发送Change Cipher Spec报文
9. 服务器同样发送Finished报文
10. 服务器和客户端的Finished报文交换完毕后,SSL连接建立完成,通信加密完成受到SSL的保护,之后进行应用层的通信,即发送HTTP请求

11. 应用层通信,发送HTTP响应
12. 最后由客户端断开连接,发送close_notify报文,之后进行TCP的四次挥手断开连接
到这里,可以明确在客户端发送 Client Hello 报文与服务器进行 SSL 通信时,并未得到服务端 Server Hello 报文的应答,这里比较奇怪的是 ***.24.69.222 服务测网卡 IP 可以正常 ping 通,这里在说明下,***.24.69.222 是由 Master 节点中的一台虚拟出来的 IP,那么其他正常节点也可以通过该虚拟 IP 完成 HTTPS通信,其实这里问题就比较明显了(特别是高可用集群部署时,参照了 keepalived + lvs 做 LB 的同学们)。
[root@***-24-69-3 bin]# ping ***.24.69.222
PING ***.24.69.222 (***.24.69.222) 56(84) bytes of data.
64 bytes from ***.24.69.222: icmp_seq=1 ttl=64 time=0.140 ms
64 bytes from ***.24.69.222: icmp_seq=2 ttl=64 time=0.151 ms
64 bytes from ***.24.69.222: icmp_seq=3 ttl=64 time=0.084 ms
^C
--- ***.24.69.222 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.084/0.125/0.151/0.029 ms
这里需要引入 IP 地址和硬件地址以及 ARP 的相关知识:
物理地址是数据链路层和物理层使用的地址,而 IP 地址是网络层和以上各层使用的地址,是一种逻辑地址(称 IP 地址是逻辑地址是因为 IP 地址是用软件实现的)。
在发送数据时,数据从高层下到低层,然后才到通信链路上传输。使用 IP 地址的 IP 数据包一旦交给了数据链路层,就被封装成 MAC 帧了。MAC 帧在传送时使用的源地址和目的地址都是硬件地址,这两个硬件地址都写在 MAC 帧的首部中。
接着引入 ARP 地址解析协议了。
首先当我们知道了一个机器(主机或路由器)的 IP 地址,需要找出其相应的硬件地址。地址解析协议 ARP 就是用来解决这样的问题的。
地址解析协议 ARP 会在主机 ARP 高速缓存中存放一个 从 IP 地址到硬件地址的映射表,并且这个映射表还经常动态更新(新增或超时删除)。
好了,我们再来观察下 ARP 的高速缓存对应的 MAC 物理地址,看看是否有问题。
# 故障节点查看 ARP 高速缓存
[root@***-24-69-3 bin]# arp -e
Address HWtype HWaddress Flags Mask Iface
***.24.69.222 ether b4:05:5d:7d:89:3a C bond0.169
......
# 222 节点查看对应网卡
# 这里发现了,两者的 MAC 地址竟然不一致
[root@***-24-69-2 ~]# ip a
......
5: bond0.169@bond0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 38:68:dd:4f:e7:e8 brd ff:ff:ff:ff:ff:ff
inet ***.24.69.2/24 brd ***.24.69.255 scope global bond0.169
valid_lft forever preferred_lft forever
inet ***.24.69.222/24 brd ***.24.69.255 scope global secondary bond0.169:1
valid_lft forever preferred_lft forever
问题定位到,我们接着操作(以下就是实际的解决方案了)
[root@***-24-69-3 ~]# arp -d ***.24.69.222
[root@***-24-69-3 ~]# ping ***.24.69.222
[root@***-24-69-3 ~]# arp -e
Address HWtype HWaddress Flags Mask Iface
***.24.69.222 ether 38:68:dd:4f:e7:e8 C bond0.169
[root@***-24-69-3 ~]# kubectl version
Client Version: version.Info{Major:"1", Minor:"23", GitVersion:"v1.23.6", GitCommit:"ad3338546da947756e8a88aa6822e9c11e7eac22", GitTreeState:"clean", BuildDate:"2022-04-14T08:49:13Z", GoVersion:"go1.17.9", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"23", GitVersion:"v1.23.6", GitCommit:"ad3338546da947756e8a88aa6822e9c11e7eac22", GitTreeState:"clean", BuildDate:"2022-04-14T08:43:11Z", GoVersion:"go1.17.9", Compiler:"gc", Platform:"linux/amd64"}
OK,顺利修复,又看到了 Server Version 正常响应了。
总结:
参考文献:
《计算机网络(第6版)》谢希仁;
《图解HTTP》上野宣。
是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou
我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur
1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里
Rails中有没有一种方法可以提取与路由关联的HTTP动词?例如,给定这样的路线:将“users”匹配到:“users#show”,通过:[:get,:post]我能实现这样的目标吗?users_path.respond_to?(:get)(显然#respond_to不是正确的方法)我最接近的是通过执行以下操作,但它似乎并不令人满意。Rails.application.routes.routes.named_routes["users"].constraints[:request_method]#=>/^GET$/对于上下文,我有一个设置cookie然后执行redirect_to:ba
我正在使用Heroku(heroku.com)来部署我的Rails应用程序,并且正在构建一个iPhone客户端来与之交互。我的目的是将手机的唯一设备标识符作为HTTPheader传递给应用程序以进行身份验证。当我在本地测试时,我的header通过得很好,但在Heroku上它似乎去掉了我的自定义header。我用ruby脚本验证:url=URI.parse('http://#{myapp}.heroku.com/')#url=URI.parse('http://localhost:3000/')req=Net::HTTP::Post.new(url.path)#boguspara
我试图在我的网站上实现使用Facebook登录功能,但在尝试从Facebook取回访问token时遇到障碍。这是我的代码:ifparams[:error_reason]=="user_denied"thenflash[:error]="TologinwithFacebook,youmustclick'Allow'toletthesiteaccessyourinformation"redirect_to:loginelsifparams[:code]thentoken_uri=URI.parse("https://graph.facebook.com/oauth/access_token
我是Ruby的新手。我试过查看在线文档,但没有找到任何有效的方法。我想在以下HTTP请求botget_response()和get()中包含一个用户代理。有人可以指出我正确的方向吗?#PreliminarycheckthatProggitisupcheck=Net::HTTP.get_response(URI.parse(proggit_url))ifcheck.code!="200"puts"ErrorcontactingProggit"returnend#Attempttogetthejsonresponse=Net::HTTP.get(URI.parse(proggit_url)
我正在尝试解析网页,但有时会收到404错误。这是我用来获取网页的代码:result=Net::HTTP::getURI.parse(URI.escape(url))如何测试result是否为404错误代码? 最佳答案 像这样重写你的代码:uri=URI.parse(url)result=Net::HTTP.start(uri.host,uri.port){|http|http.get(uri.path)}putsresult.codeputsresult.body这将打印状态码和正文。
我正在安装gitlabhq,并且在Gemfile中有对某些资源的“git://...”的引用。但是,我在公司防火墙后面,所以我必须使用http://。我可以手动编辑Gemfile,但我想知道是否有另一种方法告诉bundler使用http://作为git存储库? 最佳答案 您可以通过运行gitconfig--globalurl."https://".insteadOfgit://或通过将以下内容添加到~/.gitconfig:[url"https://"]insteadOf=git://
我希望访问我机器上的所有HTTP流量(我的Windows机器-不是服务器)。据我了解,拥有一个本地代理是所有流量路线的必经之路。我一直在谷歌搜索但未能找到任何资源(关于Ruby)来帮助我。非常感谢任何提示或链接。 最佳答案 WEBrick中有一个HTTP代理(Rubystdlib的一部分)和here's一个实现示例。如果你喜欢生活在边缘,还有em-proxy伊利亚·格里戈里克。这postIlya暗示它似乎确实需要一些调整来解决您的问题。 关于ruby-如何捕获所有HTTP流量(本地代理)