效能監控之 JMX 監控 Docker 容器中的 Java 應用
「這是我參與11月更文挑戰的第20天,活動詳情檢視:2021最後一次更文挑戰」
一、前言
今天在配置 docker 和 JMX 監控的時候,看到有一個細節和非容器環境中的 JMX 配置不太一樣。所以在這裡寫一下,以備其他人查閱。
二、遇到的問題
1、問題現象
一般情況下,我們配置 JMX 只要寫上下面這些引數就可以了。
以下是無密碼監控時的 JMX 配置引數(有密碼監控的配置和常規監控無異)
bash
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9998
-Djava.rmi.server.hostname=<serverip>
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false
但是在 docker 容器中這樣配置的時候,會出現這個錯誤。
2、問題分析
這裡就要說明一下邏輯了。為什麼會這樣呢?
先看 docker 環境的網路結構。
容器使用預設的網路模型,就是 bridge 模式。在這種模式下是 docker run 時做的 DNAT 規則,實現資料轉發的能力。所以我們看到的網路資訊是以下這樣的:
docker 中的網絡卡資訊:
bash
[root@f627e4cb0dbc /]# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.18.0.3 netmask 255.255.0.0 broadcast 0.0.0.0
inet6 fe80::42:acff:fe12:3 prefixlen 64 scopeid 0x20<link>
ether 02:42:ac:12:00:03 txqueuelen 0 (Ethernet)
RX packets 366 bytes 350743 (342.5 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 358 bytes 32370 (31.6 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
docker 中的路由資訊:
bash
[root@a2a7679f8642 /]# netstat -r
Kernel IP routing table
Destination Gateway Genmask Flags MSS Window irtt Iface
default gateway 0.0.0.0 UG 0 0 0 eth0
172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0
[root@a2a7679f8642 /]#
宿主機上的對應網絡卡資訊:
bash
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.18.0.1 netmask 255.255.0.0 broadcast 0.0.0.0
ether 02:42:44:5a:12:8f txqueuelen 0 (Ethernet)
RX packets 6691477 bytes 498130
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 6751310 bytes 3508684363 (3.2 GiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
宿主機上的路由資訊:
bash
[root@7dgroup ~]# netstat -r
Kernel IP routing table
Destination Gateway Genmask Flags MSS Window irtt Iface
default gateway 0.0.0.0 UG 0 0 0 eth0
link-local 0.0.0.0 255.255.0.0 U 0 0 0 eth0
172.17.208.0 0.0.0.0 255.255.240.0 U 0 0 0 eth0
172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
192.168.16.0 0.0.0.0 255.255.240.0 U 0 0 0 br-676bae33ff92
所以宿主機和容器是可以直接通訊的,即便埠沒有映射出來。如下所示:
bash
[root@7dgroup ~]# telnet 172.18.0.3 8080
Trying 172.18.0.3...
Connected to 172.18.0.3.
Escape character is '^]'.
另外,因為是橋接的,宿主機上還有類似 veth0b5a080 的虛擬網絡卡裝置資訊,如:
bash
eth0b5a080: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
ether 42:c3:45:be:88:1a txqueuelen 0 (Ethernet)
RX packets 2715512 bytes 2462280742 (2.2 GiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 2380143 bytes 2437360499 (2.2 GiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
這就是虛擬網絡卡對 veth pair,docker 容器裡一個,宿主機一個。 在這種模式下,有幾個容器,主機上就會有幾個 veth 開頭的虛擬網絡卡裝置。
但是如果不是宿主機訪問的話,肯定是不通的。如下圖所示:
當我們用監控機 訪問的時候,會是這樣的結果:
bash
Zees-Air-2:~ Zee$ telnet <serverip> 8080
Trying <serverip>...
telnet: connect to address <serverip>: Connection refused
telnet: Unable to connect to remote host
Zees-Air-2:~ Zee$
因為 8080 是容器開的埠,並不是宿主機開的埠,其他機器是訪問不了的。 這就是為什麼要把埠映射出來給遠端訪問的原因,對映之後的埠,就會有 NAT 規則來保證資料包可達。
檢視下 NAT 規則,就知道。如下:
```bash [root@7dgroup ~]# iptables -t nat -vnL Chain PREROUTING (policy ACCEPT 171 packets, 9832 bytes) pkts bytes target prot opt in out source destination 553K 33M DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT 171 packets, 9832 bytes) pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 2586 packets, 156K bytes) pkts bytes target prot opt in out source destination 205K 12M DOCKER all -- * * 0.0.0.0/0 !60.205.104.0/22 ADDRTYPE match dst-type LOCAL 0 0 DOCKER all -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT 2602 packets, 157K bytes) pkts bytes target prot opt in out source destination 265K 16M MASQUERADE all -- * !docker0 172.18.0.0/16 0.0.0.0/0 0 0 MASQUERADE all -- * !br-676bae33ff92 192.168.16.0/20 0.0.0.0/0 0 0 MASQUERADE tcp -- * * 192.168.0.4 192.168.0.4 tcp dpt:7001 0 0 MASQUERADE tcp -- * * 192.168.0.4 192.168.0.4 tcp dpt:4001 0 0 MASQUERADE tcp -- * * 192.168.0.5 192.168.0.5 tcp dpt:2375 0 0 MASQUERADE tcp -- * * 192.168.0.8 192.168.0.8 tcp dpt:8080 0 0 MASQUERADE tcp -- * * 172.18.0.4 172.18.0.4 tcp dpt:3306 0 0 MASQUERADE tcp -- * * 172.18.0.5 172.18.0.5 tcp dpt:6379 0 0 MASQUERADE tcp -- * * 172.18.0.2 172.18.0.2 tcp dpt:80 0 0 MASQUERADE tcp -- * * 172.18.0.6 172.18.0.6 tcp dpt:9997 0 0 MASQUERADE tcp -- * * 172.18.0.6 172.18.0.6 tcp dpt:9996 0 0 MASQUERADE tcp -- * * 172.18.0.6 172.18.0.6 tcp dpt:8080 0 0 MASQUERADE tcp -- * * 172.18.0.3 172.18.0.3 tcp dpt:9995 0 0 MASQUERADE tcp -- * * 172.18.0.3 172.18.0.3 tcp dpt:8080
Chain DOCKER (3 references) pkts bytes target prot opt in out source destination 159K 9544K RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0 0 0 RETURN all -- br-676bae33ff92 * 0.0.0.0/0 0.0.0.0/0 1 40 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3307 to:172.18.0.4:3306 28 1486 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:6379 to:172.18.0.5:6379 228 137K DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:91 to:172.18.0.2:80 3 192 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9997 to:172.18.0.6:9997 0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9996 to:172.18.0.6:9996 0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9002 to:172.18.0.6:8080 12 768 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9995 to:172.18.0.3:9995 4 256 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9004 to:172.18.0.3:8080
[root@7dgroup ~]# ```
我們看到了宿主機的 91 埠的資料會傳給 172.18.0.2
的 80 埠。宿主機的 3307 埠會傳給 172.18.0.4
的3306 埠。
囉囉嗦嗦說到這裡,那和 JMX 有啥關係。苦就苦在,JMX 是這樣的:
在註冊時使用的是引數 jmxremote.port
,然後返回一個新的埠 jmxremote.rmi.port
。
在呼叫服務時使用是引數 jmxremote.rmi.port
。 前面提到了,因為 docker 在 bridge 模式下埠是要用 -p 顯式指定的,不然沒 NAT 規則,資料包不可達。所以在這種情況下,只能把 jmxremote.rmi.port
也暴露出去。所以必須顯式指定。因為不指定的話,這個埠會隨機開。隨機開的埠又沒 NAT 規則,所以是不通的了。
三、解決方案
所以,這種以上情況只能指定 jmxremote.rmi.port
為固定值,並暴露出去。 配置如下:
bash
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9995
-Djava.rmi.server.hostname=<serverip>
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.rmi.port=9995
像上面的設定就是兩個都是 9995,這樣是允許的,這種情況下注冊和呼叫的埠就合併了。
再啟動 docker 容器的時候,就需要這樣了。
bash
docker run -d -p 9003:8080 -p 9995:9995 --name 7dgroup-tomcat5
-e CATALINA_OPTS="-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=9995 \
-Djava.rmi.server.hostname=<serverip> \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.rmi.port=9995" c375edce8dfd
然後就可以連線上 JMX 的工具了。
在有防火牆和其他的裝置的網路環境中,也有可能出同樣的問題。明白了JMX 的註冊呼叫邏輯之後,就可以解決各種類似的問題了。
網路鏈路是做效能分析的人必須想明白的技術點,所以前面說了那麼多內容。
四、總結
這裡對於 JMX 工具的選擇囉嗦兩句。有人喜歡花哨的,有人喜歡簡單的,有人喜歡黑視窗的。我覺得工具選擇的時候,要看適用情況,在效能分析的時候,一定要選擇合適的工具,而不是選擇體現技術高超的工具。
最後留個作業:
-
如果
docker run
中如果指定-p 19995:9995
,也就是換個埠暴露出去,其他配置都不變。JMX 工具還能連得上嗎? -
如果
jmxremote.rmi.port
和jmxremote.port
不合並,並且同時把兩個埠都暴露出去,其他配置都不變。JMX 工具還能連得上嗎?
有興趣的可以自己嘗試下哦。
- 效能工具之 Jmeter 通過 SpringBoot 工程啟動
- 持續交付之解決Jenkins整合編譯獲取程式碼提交記錄及釘釘通知
- 電商專案 Jmeter 指令碼實戰開發
- 效能監控之 blackbox_exporter Prometheus Grafana 實現網路探測
- 效能監控之初識 Prometheus
- 效能監控之Telegraf InfluxDB Grafana實現結構化日誌實時監控
- 效能監控之 JMX 監控 Docker 容器中的 Java 應用
- 效能監控之常見JDK命令列工具整理
- 效能工具之JMeter InfluxDB Grafana打造壓測視覺化實時監控
- 效能分析之一個簡單 Java 執行緒 dump 分析示例
- SpringCloud 日誌在壓測中的二三事
- 效能分析之如何高效解決 SQL 產生的記憶體溢位
- 效能分析之單條SQL查詢案例分析(mysql)
- 效能分析之JMeter 指令碼執行失敗導致的問題
- 效能工具之Java分析工具BTrace入門
- Filebeat Kafka Logstash Elasticsearch Kibana 構建日誌分析系統
- 混沌工程之 ChaosToolkit K8S 使用之刪除 POD 實驗
- Linux 網路故障模擬工具TC
- 效能工具之 JMeter 快速入門