技術譯文 | 使用 TCP Wrappers 保護 MySQL 如何導致服務中斷
- 2020 年 3 月 13 日
- 筆記
作者:Ananias Tsalouchidis 翻譯:孟維克 原文:https://www.percona.com/blog/2020/01/07/how-securing-mysql-with-tcp-wrappers-can-cause-an-outage/
案例
保護 MySQL 總是一個挑戰。有一些通用的最佳實踐可用於安裝加固,但是您的設置越複雜,就越有可能遇到一些難以排查的故障的問題。
我們最近在研究一個案例,當活躍執行緒很高,超過一個閾值(但並不總是相同)時,MySQL 開始變得不可用。

在此期間,有許多像下面這樣的日誌,mysqld 有幾秒鐘沒有響應。
2019-11-27T10:26:03.476282Z 7736563 [Note] Got an error writing communication packets 2019-11-27T10:26:03.476305Z 7736564 [Note] Got an error writing communication packets
"Got an error writing communication packets"是一個很常見的日誌消息,它可能由多種原因引起。(官方文檔請參考文末鏈接)
我們是如何處理此問題並查找根本原因的
首先要做的是遠程執行一個簡單的循環,以確定這是否是隨機發生的,是網路問題還是與 mysqld 本身相關的問題。
[RDBA] percona@monitoring1: ~ $ time for i in {1..100}; do mysql -h 10.0.2.14 -Bsse "show status like '%uptime';"; done Uptime 3540 Uptime 3540 Uptime 3540 Uptime 3541 Uptime 3541 Uptime 3541 Uptime 3541 Uptime 3542 Uptime 3542 Uptime 3542 Uptime 3543 Uptime 3543 Uptime 3543 Uptime 3543 Uptime 3543 Uptime 3544 ^C最初想做的是確認客戶報告的行為。因此,鑒於所有應用伺服器都處於遠程位置(因此客戶端通過 TCP 鏈接),想確認是否有遠程連接被丟棄(這是由於網路問題?還是處於任何原因導致 MySQL 無響應?)。還想驗證是否存在一個場景,即 X 中的一個連接被丟棄或一定時間後連接被丟棄。確認場景通常有助於確認根本原因是什麼。執行此遠程連接循環的另一個原因是驗證此問題是否僅在遠程連接時發生還是在本地連接時也出現(稍後將測試本地連接)。
在網路層 troubleshooting,並沒有發現任何問題,因此決定使用另外一個循環在本地通過 TCP 鏈接到 mysqld。這個測試表明 MySQL 確實不可用的(或者至少不能隨機訪問它)。不幸的是,當時並沒有通過套接字測試本地連接。通過套接字連接完全繞過網路層。如果嘗試使用套接字進行連接,會立即意識到這實際上不是 MySQL 問題,因為 MySQL 總是可用的(所以在網路級別上有些東西阻塞了連接)。下面是更多的細節。
繼續進行 troubleshooting, netstat 顯示許多連接處於 TIME_WAIT 狀態。TIME_WAIT 表示源端已經關閉了連接。下面是一個在測試環境中使用 netstat 識別 TCP 連接的示例。
[RDBA] percona@db4-atsaloux: ~ $ sudo netstat -a -t Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:sunrpc 0.0.0.0:* LISTEN tcp 0 0 db4-atsaloux:42000 0.0.0.0:* LISTEN tcp 0 0 localhost:domain 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:ssh 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:nrpe 0.0.0.0:* LISTEN tcp 0 0 db4-atsaloux:ssh 10.0.2.10:35230 ESTABLISHED tcp 0 36 db4-atsaloux:ssh 10.0.2.10:39728 ESTABLISHED tcp 0 0 db4-atsaloux:49154 10.0.2.11:mysql ESTABLISHED tcp6 0 0 [::]:mysql [::]:* LISTEN tcp6 0 0 [::]:sunrpc [::]:* LISTEN tcp6 0 0 [::]:ssh [::]:* LISTEN tcp6 0 0 [::]:nrpe [::]:* LISTEN tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:50950 TIME_WAIT tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:50964 TIME_WAIT tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:50938 TIME_WAIT tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:50940 TIME_WAIT tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:51010 TIME_WAIT tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:50994 TIME_WAIT tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:50986 TIME_WAIT tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:44110 ESTABLISHED tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:50984 TIME_WAIT tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:50978 TIME_WAIT tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:51030 TIME_WAIT tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:50954 TIME_WAIT tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:51032 TIME_WAIT tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:51042 TIME_WAIT tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:50996 TIME_WAIT tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:51046 TIME_WAIT tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:51000 TIME_WAIT tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:50942 TIME_WAIT tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:51004 TIME_WAIT tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:44108 ESTABLISHED tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:50992 TIME_WAIT tcp6 0 0 db4-atsaloux:mysql 10.0.2.10:50988 TIME_WAIT
這讓我們認識到,可能已經耗盡了 TCP 層上的 TCP 連接,因為 TCP 會話數量增加了,這些會話一直保持打開狀態,直到出現 time_wait 超時。我們在之前寫了一篇相關 blog(請參考文末鏈接 )。這樣可以讓您很好地了解什麼是 "TIMEWAIT" 問題,以及可以採取哪些措施進行補救。
我們最初嘗試對埠範圍 ip_local_port_range 進行微調,並調整一些與內核相關的選項,如 tcp_tw_reuse,但是不幸的是,我們沒有成功,仍然有相同的問題。
檢查網路流量時發現,主機向 /etc/resolv.conf 中定義的 DNS 伺服器發出了大量的請求。說到網路流量檢測,由於網路基礎設施不是由我們管理的,我們無法核實網路層的一些事情。我們從客戶的 IT 部門確認,在網路層沒有發現任何錯誤。我們能做的就是對進出 MySQL 的流量進行數據包檢測,tcpdump 幫助識別出大量的 DNS 請求及緩慢的響應。在 DB 節點上最初用於包檢測的命令是 tcpdump dst port3306orsrc port3306,然後使用更具體的過濾規則來排除和過濾掉無用的資訊,如主伺服器和從伺服器之間的流量。
當時,想到的另一件事驗證 mysqld 是否處於任何原因正在嘗試進行 DNS 解析。這可以解釋最初遇到的一個問題。檢查變數 skip_name_resolve,我們發現它設置為 ON,所以 mysqld 不應該執行任何類型的 DNS 解析。
db4-atsaloux (none)> select @@skip_name_resolve; +---------------------+ | @@skip_name_resolve | +---------------------+ | 1 | +---------------------+ 1 row in set (0.00 sec)
為了進一步調試 MySQL 實際做了什麼,我們對 mysqld 進程進行了 strace。
根本原因
我們注意到 mysqld 進程過於頻繁地訪問 /etc/hosts.allow 和 /etc/hosts.deny 文件。
root@db4-atsaloux:~# strace -e open,read -p$(pidof mysqld) strace: Process 693 attached # /etc/hosts.deny: list of hosts that are _not_ allowed to access the system. read(51, "# /etc/hosts.allow: list of host"..., 4096) = 464 read(51, "", 4096) = 0 # /etc/hosts.allow: list of hosts that are allowed to access the system. read(51, "# /etc/hosts.deny: list of hosts"..., 4096) = 721 read(51, "", 4096) = 0 read(51, "# /etc/hosts.allow: list of host"..., 4096) = 464 read(51, "", 4096) = 0 read(51, "# /etc/hosts.deny: list of hosts"..., 4096) = 721 read(51, "", 4096) = 0 read(51, "# /etc/hosts.allow: list of host"..., 4096) = 464 read(51, "", 4096) = 0 read(51, "# /etc/hosts.deny: list of hosts"..., 4096) = 721 read(51, "", 4096) = 0 read(51, "# /etc/hosts.allow: list of host"..., 4096) = 464 read(51, "", 4096) = 0 read(51, "# /etc/hosts.deny: list of hosts"..., 4096) = 721 read(51, "", 4096) = 0 read(51, "# /etc/hosts.allow: list of host"..., 4096) = 464 read(51, "", 4096) = 0 read(51, "# /etc/hosts.deny: list of hosts"..., 4096) = 721 read(51, "", 4096) = 0 read(51, "# /etc/hosts.allow: list of host"..., 4096) = 464 read(51, "", 4096) = 0 read(51, "# /etc/hosts.deny: list of hosts"..., 4096) = 721 read(51, "", 4096) = 0 read(51, "# /etc/hosts.allow: list of host"..., 4096) = 464 read(51, "", 4096) = 0 read(51, "# /etc/hosts.deny: list of hosts"..., 4096) = 721 read(51, "", 4096) = 0 read(51, "# /etc/hosts.allow: list of host"..., 4096) = 464 read(51, "", 4096) = 0
如我們所見,一些新的連接需要花費很長的時間來連接 MySQL。mysqld pid 上的 strace 顯示頻繁地訪問 /etc/hosts.allow 和 /etc/hosts.deny。這些文件與 tcp wrappers 直接相關!許多系統管理員認為 TCP wrappers 是過時軟體(軟體開發已經停止,但是有很多替代方案),但是他們仍然被廣泛使用。使用 TCP wrappers 時,必須根據 ACL 檢查每個新的連接,並根據此 ACL 決定是否允許遠程主機連接到服務。
在 troubleshooting 時發現 DNS 解析與 MySQL 的 skip_name_resolve 功能無關,也與 mysqld 本身無關,我們知道這實際上不是一個 mysqld 問題。MySQL 已經啟動並能處理請求。就我個人而言,我認為這是一個「設計好的」軟體的致命弱點。
繼續檢查我們發現有一個錯誤的 DNS 配置 /etc/resolv.conf,所以當 DNS 反應緩慢或者 DNS 沒有響應,TCP wrappers 使得連接到 mysql 的連接停滯或者在等待 DNS 響應時被廢棄。
總結
1. 如果出於任何原因您需要使用TCP wrappers,請始終注意任何 DNS 問題可能會導致停頓或者中斷。
2. 使用 TCP wrappers,即使啟用了 skip_name_resolve,也會發生解析。
- 即使您沒有人為安裝 TCP wrappers,也有一些作業系統,比如某些版本的 CentOS 或者 Ubuntu,默認情況下是可用的。
- 如果您要為 MySQL 設置安全規則,一定要小心。請注意並不是每個二進位文件都可以使用它們。二進位文件應該連接到 TCP wrappers 的庫。
3. 如果 MySQL 服務啟用了 TCP wrappers,並且您真的需要它們,您應該確保 DNS 響應快速並且安全,即使這不是很容易管理。
- 為連接到 mysqld 服務的主機添加到
/etc/hosts文件,這樣不會為每次連接做真實的 DNS 解析。 - 在配置 DNS 解析時,也有一些最佳時間。在我看來,您至少應該在
/etc/resolv.conf中配置多個 DNS 伺服器,並且應該使用一些本地伺服器或最接近 Linux 伺服器的伺服器,並可能啟用快取。
4. 如果您遇到類似的問題,可以檢查 mysqld 是否針對 TCP wrappers 構建,並在 mysqld 二進位文件上執行 ldd,然後檢查它是否連接到了 TCP wrappers 庫。如果是,並且遇到同一個問題,那麼請檢查系統的 DNS 配置,以及 DNS 是否執行正常。
[RDBA] percona@db4-atsaloux: ~ $ ldd /usr/sbin/mysqld | grep libwrap libwrap.so.0 => /lib/x86_64-linux-gnu/libwrap.so.0 (0x00007fa80532c000)
相關文檔及博文鏈接: 《Communication Errors and Aborted Connections》 https://dev.mysql.com/doc/refman/5.7/en/communication-errors.html 《Application Cannot Open Another Connection to MySQL》https://www.percona.com/blog/2014/12/08/what-happens-when-your-application-cannot-open-yet-another-connection-to-mysql/>)
