Skip to content

Latest commit

 

History

History
512 lines (401 loc) · 18.1 KB

File metadata and controls

512 lines (401 loc) · 18.1 KB

应用层常见排查命令

介绍一些应用层相关排查命令,对于有些 Linux 命令 windows 上没有的可以考虑 windows 上安装个 git bash。

应用层举例

再往上层来看,TCP/UDP 都是为了传输数据,也就是字节流,上层怎么解析这些流是应用层程序自己的行为(协议)。类比就像电话,只负责传递声音,而不在乎你是啥语言,TCP/UDP 职责把字节流传输过去。

dns

Linux 上 DNS 相关配置就是 /etc/resolv.conf 文件里的 nameserver ,而里面所有可配置项目见 man 手册页 ,常见的就是下面的选项:

此处讲解下 DNS 解析的一些常见命令说明:

nslookup

nslookup 分为交互和非交互模式,不带任何选项和参数执行就是交互模式,交互模式能力稍微强点:

$ nslookup www.baidu.com
Server:		127.0.0.53
Address:	127.0.0.53#53

Non-authoritative answer:
www.baidu.com	canonical name = www.a.shifen.com.
Name:	www.a.shifen.com
Address: 183.2.172.42
Name:	www.a.shifen.com
Address: 183.2.172.185

此处 DNS server IP 是 127.0.0.53 是因为有些 Linux 发行版会使用 systemd-resolved 在本机起一个 DNS server,通过命令来配置上游 DNS :

$ cat /etc/resolv.conf
nameserver 127.0.0.53
options edns0 trust-ad
# 使用 resolvectl 命令查看
$ resolvectl -h
$ resolvectl  --no-pager status
$ resolvectl  --no-pager statistics

在一些 initContainer 里有些人喜欢做一些前置依赖检查 service 域名解析可用性,而早期 K8S 官方文档里推荐使用 busybox:1.28 里的 nslookup,后续有不少人发现升级 busybox 后无法解析了:

# busybox:1.28
$ nslookup kubernetes.default
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes.default
Address 1: 10.96.0.1 kubernetes.default.svc.cluster.local

# busybox:1.31.1
$ nslookup kubernetes.default
Server:         10.96.0.10
Address:        10.96.0.10:53

** server can't find kubernetes.default: NXDOMAIN

*** Can't find kubernetes.default: No answer

command terminated with exit code 1

这个问题是见 issue docker-library/busybox#48,所以一般 DNS 排查过程中不推荐使用 nslookup 命令,但是只是简单解析的话,可以找个 glibc 镜像,使用以下黑科技:

# https://github.com/bitnami/containers/blob/main/bitnami/git/2/debian-12/prebuildfs/opt/bitnami/scripts/libnet.sh#L26
$ getent ahostsv4 kubernetes.default | awk '/STREAM/ {print $1;exit; }'
10.96.0.1
$ getent ahostsv4 kube-dns.kube-system | awk '/STREAM/ {print $1;exit; }'
10.96.0.10

dig

排查 DNS 问题建议使用更强大的 dig @server name type 命令,很多程序的 DNS 解析行为都是用的 glibc 提供的库,而避免因素干扰,dig 可以完全从命令行来指定 DNS 的解析逻辑:

# 使用默认的 DNS 解析
$ dig www.baidu.com

; <<>> DiG 9.16.48-Ubuntu <<>> www.baidu.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 21504
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;www.baidu.com.			IN	A

;; ANSWER SECTION:
www.baidu.com.		60	IN	CNAME	www.a.shifen.com.
www.a.shifen.com.	59	IN	A	183.2.172.42
www.a.shifen.com.	59	IN	A	183.2.172.185

;; Query time: 0 msec
;; SERVER: 127.0.0.53#53(127.0.0.53)
;; WHEN: Mon Jul 15 14:53:38 CST 2024
;; MSG SIZE  rcvd: 101

# 简洁输出,缺省是查询 A 记录
$ dig +short baidu.com
39.156.66.10
110.242.68.66

# tcp dns 解析
$ dig +short +tcp baidu.com
# 使用指定 DNS 解析
$ dig @223.5.5.5 +short baidu.com

# 使用指定端口解析
$ dig @223.5.5.5 -p 53 baidu.com

# Pod 容器内使用 resolv.conf 里的 search 查找,因为默认行为是 +nosearch 的
$ dig +short kubernetes.default +search

例如宿主机上有 dig 命令,测试 coredns 能否解析:

$ dig @10.96.0.10 +short kubernetes.default.svc.cluster.local
# 或者绕过 svc,使用指定 coredns POD_IP 还不通就是 CNI 或者跨节点的问题
$ dig @<POD_IP> +short kubernetes.default.svc.cluster.local

dig 只介绍这些基础用法。

ssh

ssh 卡住如何排查:

# ssh 提供 -v 参数,打印详细的信息,越多 v 信息越详细
$ ssh -vvvv xxx@xxxx

还有除去你添加过 pam 模块以外,可能是 sshd 配置问题,可以:

-r: Print elapsed time between system call starts (in the 2nd column in the output below)
-T: Print the elapsed times (durations) of syscalls (the rightmost fields like “<0.000364>”)
-f: Follow and trace child processes too
-p: Attach to PID 10635 and trace that
$ strace -r -T -f -p [sshd进程号]

http

前面说了,TCP 实际就是传输数据的字节流,假设我们自己设计一个应用协议:

  • 第一个字节(8bit)定义后面字节流的实际解码类型,例如 0 代表 raw 流例如文件,1 代表文字,2 代表图片...

然后开发一个聊天程序,底层就是 case 第一个字节,如果是 1,把后面的字节解码为字符到聊天对话里展示,2 就是图片。

 type := data[0]
 rec_data = data[1:-1]
 case uint8(type):
  0:
    file.WriteByte(rec_data)
  1:
    message.WriteConsole(rec_data)
  2:
    pic.Display(rec_data)

就此,你定义了一个应用层协议,然后发现这些不方便扩容,其实 http 协议和这个大致差不多,只不过它设计得没有这么粗制滥造,它的协议格式为下面三部分:

POST /submit-form HTTP/1.1 # 请求行执行的操作,如 GET、POST、PUT、DELETE。 URI 路径,HTTP 版本
Host: www.example.com # header,后端处理一般会忽略大小写
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Content-Type: application/x-www-form-urlencoded
Content-Length: 27

username=user&password=pass # body,和 header 里的 Content-Type 对应例如:text/html,image/jpeg。。。。

telnet 就是建立 TCP 后发送字符串,所以也可以模拟简单的 GET 请求:

$ telnet www.baidu.com 80
Trying 183.2.172.185...
Connected to www.a.shifen.com.
Escape character is '^]'.<---- 输入下面的后回车回车,因为 telnet \r\n 才是发送
GET / HTTP/1.1

.... <-- 百度的 http 回复

curl

当然,在面对更复杂的 http 接口排错时候,更多还是使用 curl 命令发送或者模拟 http 请求,curl 命令不单单是支持 http 协议,还支持其他协议,这里暂不讨论:

$ curl -V
curl 7.68.0 (x86_64-pc-linux-gnu) libcurl/7.68.0 OpenSSL/1.1.1f zlib/1.2.11 brotli/1.0.7 libidn2/2.2.0 libpsl/0.21.0 (+libidn2/2.2.0) libssh/0.9.3/openssl/zlib nghttp2/1.40.0 librtmp/2.3
Release-Date: 2020-01-08
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp
# ↑ 这里是协议支持
Features: AsynchDNS brotli GSS-API HTTP2 HTTPS-proxy IDN IPv6 Kerberos Largefile libz NTLM NTLM_WB PSL SPNEGO SSL TLS-SRP UnixSockets

介绍下常见的 http 基础用法:

只看返回的头部,也就是 HEAD 请求:

$ curl -I http://httpbin.org

-v 选项打印详细信息,包含域名解析到的 IP,https 证书,发起的头部信息,然后接口 http://httpbin.org/get 会返回客户端的 header 信息:

$ curl -v http://httpbin.org/get
*   Trying 35.173.225.247:80...
* TCP_NODELAY set
* Connected to httpbin.org (35.173.225.247) port 80 (#0)
> GET /get HTTP/1.1
> Host: httpbin.org
> User-Agent: curl/7.68.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Mon, 15 Jul 2024 08:05:33 GMT
< Content-Type: application/json
< Content-Length: 254
< Connection: keep-alive
< Server: gunicorn/19.9.0
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Credentials: true
< 
{
  "args": {}, 
  "headers": {
    "Accept": "*/*", 
    "Host": "httpbin.org", 
    "User-Agent": "curl/7.68.0", 
    "X-Amzn-Trace-Id": "Root=1-6694d84d-29cfb5b87bcaeb267f3b918f"
  }, 
  "origin": "178.xx.xx.xx", 
  "url": "http://httpbin.org/get"
}

-H key: value 设置 header ,例如:

$ curl  http://httpbin.org/get -H 'test: 11111111111111'
{
  "args": {}, 
  "headers": {
    "Accept": "*/*", 
    "Host": "httpbin.org", 
    "Test": "11111111111111", 
    "User-Agent": "curl/7.68.0", 
    "X-Amzn-Trace-Id": "Root=1-6694d921-0449f11d77d437e71b7113fc"
  }, 
  "origin": "178.236.xx.xx", 
  "url": "http://httpbin.org/get"
}

-X method ,例如发送 post 请求:

$ curl -XPOST  http://httpbin.org/post?a=123 --data-raw '{"key":"value"}'
{
  "args": {
    "a": "123"
  }, 
  "data": "", 
  "files": {}, 
  "form": {
    "{\"key\":\"value\"}": ""
  }, 
  "headers": {
    "Accept": "*/*", 
    "Content-Length": "15", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "curl/7.68.0", 
    "X-Amzn-Trace-Id": "Root=1-6694d982-180db3df277a1ce879676d16"
  }, 
  "json": null, 
  "origin": "178.236.xx.xx", 
  "url": "http://httpbin.org/post?a=123"
}

一些其他常见选项:

  • -o file 把 body 内容存到文件,下载文件时候使用
  • -k, --insecure 允许非安全连接,通常 https 由非权威 ca 签署证书,例如 kubernetes 的接口,curl -k https://10.96.0.1
  • -L --location 跟随重定向

而对于浏览器触发的请求,我们也可以 F12 network 里拷贝成 curl 命令格式,如下图:

browser-cap-to-curl

然后更改一些参数 curl 发请求,调试后端逻辑之类的。需要注意的是,有些后端逻辑会有防重放防御,也就是一个 http 请求发送后,再发送就无效了。

--resolve 选项方便你来不修改 hosts 来指定域名的 IP :

# curl --resolve <host:port:address> <URL>
$ curl --resolve example.com:80:127.0.0.1 http://example.com

一个要注意点的是,有时候后端逻辑会根据 User-Agent 做判断,浏览器看到的是 html 页面,curl 或者一些编程语言的 http 库请求后是 json 接口信息,当然不单单是 UA 字段。

还有有些网站避免爬虫,会利用浏览器的一些功能做人机校验,curl 是做不到那些的。

故障排查案例

一个故障排查案例,群友在 K8S 里部署了 jenkins,设置的插件源为 https 清华源,安装插件报错:

unable to find valid certifacation path to reuqested target

这种一般是证书相关,非权威机构签署的假证书,当时比较忙,没时间帮他看,聊天和一些后续对话总结出来他环境的一些奇怪信息:

  • 设置的源是清华的 https 源,不应该是非权威 https 证书
  • 机器上和网关以及宿主机都没代理和透明代理
  • jenkins 镜像也是使用的官方的
  • 后面还反馈说在 pod 日志里,发现其他域名也出现类似问题,例如 baidu.com
  • K8S 宿主机是虚拟机,是下载的 qcow2 文件导入创建的虚机,后面从 ISO 安装的系统上搭建 K8S 就好了

发现这个问题很奇怪,就让他复现一套环境给我远程上去看了下,在 ingress nginx 的容器里:

$ curl -v https://www.baidu.com
*   Trying 173.255.194.134:443...
* Connected to www.baidu.com (173.255.194.134) port 443 (#0)
* ALPN, offering h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*   CAfile: /etc/ssl/certs/ca-certificates.crt
*   CApath: /etc/ssl/certs
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TlS alert, certificate expired (557)
* SSL certificate problem: certificate has expired
* Closing connection 0
* curl: (60) SSL certificate problem: certificate has expired
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could notestablish a secure connection to it, To learn more about this situation andhow to fix it, please visit the web page mentioned above.

根据 curl 信息可以看到报错 https 证书过期,这个容器里的 curl -v 不够详细,于是 K8S 节点上 curl -v 发现又是正常的:

$ curl -v https://www.baidu.com
* About to connect() to www.baidu.com port 443 (#0)
*  Trying 180.101.50.242...
* Connected to www.baidu.com (180.101.50.242) port 443 (#0)
...

但是我细心的发现了这个解析 IP 和容器内不一样,不对劲,然后 K8S 节点上尝试:

$ curl -v --resolve www.baidu.com:443:173.255.194.134 https://www.baidu.com
* Added www.baidu.com:443:173.255.194.134 to DNS cache
* About to conenect() to www.baidu.com port 443 (#0)
*  Trying 173.255.194.134...
* Connected to www.baidu.com (173.255.194.134) port 443 (#0)
....
* Server certificate:
*       subject: CN=*.mytrafficmanagement.com
*       start date: Aug 10 23:39:33 2023 GMT
*       expire date: Nov 08 23:39:32 2023 GMT
...

也就是 DNS 解析问题,然后发现是 search 导致的:

$ cat /etc/resolv.conf
nameserver 192.168.122.1
nameserver 119.29.29.29
search com <-------- 这里
$ dig @192.168.122.1 +short www.baidu.com
www.a.shifen.com.
180.101.50.242
180.101.50.188
$ dig @119.29.29.29 +short www.baidu.com.com <----加上 search 域名
45.79.19.196
173.255.194.134 <---- 找到解析 IP 了
....

然后让去掉 K8S 节点上的 seach 后,重建下容器就好了,pod 拉起来的时候,如果是默认的 DNSPolicy 策略,会把宿主机的 search 和 options 追加到 POD 的容器内。

反向代理和负载均衡

实际的 http server 不是单点的,往往是好几台,前面有高性能反向代理服务器:

                                            +-------+
                                            |       |
                               -+---------->+  rs   |
                              /             +-------+
                             /    
                            /     
                           /                +-------+
+--------+                /                 |       |
| client +----------> proxy/VIP/LB -------->+  rs   |
+--------+                \                 +-------+
                           \    
                            \     
                             \              +-------+
                              ------------->+       |
                                            |  rs   |
                                            +-------+

软硬件实现手段为:

  • 云上 LB 之类的
  • 硬件 LB
  • 软件负载均衡,nginx、haproxy、keepalived + haproxy、Envoy....
  • 内核能力 lvs 之类的
  • 以及网络设备配置 ospf 和服务器 lvs 结合 ...

例如混合四层+应用层使用:

L1/L2 是 nginx、Envoy 之类 7 层代理
                           http proxy            +-------+
                                                 |       |
                           +---------+---------->+  rs   |
                           | +-----+ |           +-------+
                           | |     | |
                           | |  L1 | |
                           | +-----+ |           +-------+
+--------+   VIP or SLB    |         |           |       |
| client +---------------->+         +---------->+  rs   |
+--------+                 |         |           +-------+
                           | +-----+ |
                           | |     | |
                           | | L2  | |           +-------+
                           | +-----+ +---------->+       |
                           +---------+           |  rs   |
                                                 +-------+

抓包

抓包工具提供了一种详细深入的网络通信分析方式,能够帮助识别和解决各种网络和通信问题,同时也有助于理解和优化网络性能和安全性。

tcpdump

命令格式 tcpdump -nn -i <any|网卡名> [-w file.pcap] filter

  • -n 关闭反向 DNS 解析,-nn 顺带把端口按照数字显示
  • -i 指定网卡,any 表示所有网卡
  • -w 写入文件,可以导入 wireshark 查看 .pcap 格式文件

输出常见选项:

  • -e 输出数据链路层的头部信息,例如源目 MAC 地址
  • -v-vv-vvv 详细输出
  • tcpdump -D 列出可用于抓包的接口。将会列出接口的数值编号和接口名,它们都可以用于 -i 后。
  • tcpdump -r xx.pcap 读取抓包的文件。

表达式单元的逻辑符号 and、&&、or、 || 、not、!,使用括号 () 可以改变表达式的优先级,但需要注意的是括号会被 shell 解释,所以应该使用反斜线转义为 \(\),在需要的时候,还需要包围在引号中。

一些示例:

# 抓 icmp 的 ping 包
$ tcpdump -nn -i any icmp
# 限定 host
$ tcpdump -nn -i any icmp and host 223.5.5.5
# 抓取 flannel.1 的 vxlan 网络内的 172.27.1.4:9153 的包
$ tcpdump -nn -i flannel.1 host 172.27.1.4 and port 9153 -vv

# 抓包并保存
$ tcpdump -nn -i ant icmp and host 223.5.5.5 -w icmp.pcap
# 查看抓包文件
$ tcpdump -nn -r icmp.pcap

注意 tcpdump 4.99 开始默认抓包过程中就可以看到网卡名字了,但是包管理的版本都比较低,可以 源码编译安装 或者使用一些容器里的高版本。

ptcpdump

mozillazg/ptcpdump 是一个使用 eBPF 技术开发的、类 tcpdump 的网络抓包工具。它除了兼容 tcpdump 的常用命令行参数以及包过滤语法外, 还额外提供了以下特性:

  • 在输出中记录和显示发送网络流量的进程、容器、Pod 信息。
  • 支持对指定进程、容器以及 Pod 进行抓包
  • 当在 Wireshark 中打开保存的 pcapng 文件时,将能够看到每个数据包对应的进程、容器、Pod 信息。

缺点是要求 内核 >= 5.2

链接