使用 TCP Wrappers 保護 MySQL 如何導致服務中斷

作者:Ananias Tsalouchidis

翻譯:孟維克

原文:https://www.percona.com/blog/2020/01/07/how-securing-mysql-with-tcp-wrappers-can-cause-an-outage/


案例

保護 MySQL 總是一個挑戰。有一些通用的最佳實踐可用於安裝加固,但是您的設置越複雜,就越有可能遇到一些難以排查的故障的問題。

我們最近在研究一個案例,當活躍線程很高,超過一個閾值(但並不總是相同)時,MySQL 開始變得不可用。


技術譯文 | 使用 TCP Wrappers 保護 MySQL 如何導致服務中斷

在此期間,有許多像下面這樣的日誌,mysqld 有幾秒鐘沒有響應。

<code>2019-11-27T10:26:03.476282Z 7736563 [Note] Got an error writing communication packets2019-11-27T10:26:03.476305Z 7736564 [Note] Got an error writing communication packets/<code>

"Got an error writing communication packets"是一個很常見的日誌消息,它可能由多種原因引起。(官方文檔請參考文末鏈接)


我們是如何處理此問題並查找根本原因的

首先要做的是遠程執行一個簡單的循環,以確定這是否是隨機發生的,是網絡問題還是與 mysqld 本身相關的問題。

<code>[RDBA] percona@monitoring1: ~ $ time for i in {1..100}; \\do mysql -h 10.0.2.14 -Bsse "show status like '%uptime';"; \\doneUptime 3540Uptime 3540Uptime 3540Uptime 3541Uptime 3541Uptime 3541Uptime 3541Uptime 3542Uptime 3542Uptime 3542Uptime 3543Uptime 3543Uptime 3543Uptime 3543Uptime 3543Uptime 3544^C/<code>

最初想做的是確認客戶報告的行為。因此,鑑於所有應用服務器都處於遠程位置(因此客戶端通過 TCP 鏈接),想確認是否有遠程連接被丟棄(這是由於網絡問題?還是處於任何原因導致 MySQL 無響應?)。還想驗證是否存在一個場景,即 X 中的一個連接被丟棄或一定時間後連接被丟棄。確認場景通常有助於確認根本原因是什麼。執行此遠程連接循環的另一個原因是驗證此問題是否僅在遠程連接時發生還是在本地連接時也出現(稍後將測試本地連接)。

在網絡層 troubleshooting,並沒有發現任何問題,因此決定使用另外一個循環在本地通過 TCP 鏈接到 mysqld。這個測試表明 MySQL 確實不可用的(或者至少不能隨機訪問它)。不幸的是,當時並沒有通過套接字測試本地連接。通過套接字連接完全繞過網絡層。如果嘗試使用套接字進行連接,會立即意識到這實際上不是 MySQL 問題,因為 MySQL 總是可用的(所以在網絡級別上有些東西阻塞了連接)。下面是更多的細節。

繼續進行 troubleshooting, netstat 顯示許多連接處於 TIME_WAIT 狀態。TIME_WAIT 表示源端已經關閉了連接。下面是一個在測試環境中使用 netstat 識別 TCP 連接的示例。

<code>[RDBA] percona@db4-atsaloux: ~ $ sudo netstat -a -tActive Internet connections (servers and established)Proto Recv-Q Send-Q Local Address           Foreign Address         Statetcp        0      0 0.0.0.0:sunrpc          0.0.0.0:*               LISTENtcp        0      0 db4-atsaloux:42000      0.0.0.0:*               LISTENtcp        0      0 localhost:domain        0.0.0.0:*               LISTENtcp        0      0 0.0.0.0:ssh             0.0.0.0:*               LISTENtcp        0      0 0.0.0.0:nrpe            0.0.0.0:*               LISTENtcp        0      0 db4-atsaloux:ssh        10.0.2.10:35230         ESTABLISHEDtcp        0     36 db4-atsaloux:ssh        10.0.2.10:39728         ESTABLISHEDtcp        0      0 db4-atsaloux:49154      10.0.2.11:mysql         ESTABLISHEDtcp6       0      0 [::]:mysql              [::]:*                  LISTENtcp6       0      0 [::]:sunrpc             [::]:*                  LISTENtcp6       0      0 [::]:ssh                [::]:*                  LISTENtcp6       0      0 [::]:nrpe               [::]:*                  LISTENtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:50950         TIME_WAITtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:50964         TIME_WAITtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:50938         TIME_WAITtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:50940         TIME_WAITtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:51010         TIME_WAITtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:50994         TIME_WAITtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:50986         TIME_WAITtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:44110         ESTABLISHEDtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:50984         TIME_WAITtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:50978         TIME_WAITtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:51030         TIME_WAITtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:50954         TIME_WAITtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:51032         TIME_WAITtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:51042         TIME_WAITtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:50996         TIME_WAITtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:51046         TIME_WAITtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:51000         TIME_WAITtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:50942         TIME_WAITtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:51004         TIME_WAITtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:44108         ESTABLISHEDtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:50992         TIME_WAITtcp6       0      0 db4-atsaloux:mysql      10.0.2.10:50988         TIME_WAIT/<code>

這讓我們認識到,可能已經耗盡了 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 解析。

<code>db4-atsaloux (none)> select @@skip_name_resolve;+---------------------+| @@skip_name_resolve |+---------------------+|                   1 |+---------------------+1 row in set (0.00 sec)/<code>

為了進一步調試 MySQL 實際做了什麼,我們對 mysqld 進程進行了 strace。


根本原因

我們注意到 mysqld 進程過於頻繁地訪問 /etc/hosts.allow/etc/hosts.deny

文件。

<code>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) = 464read(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) = 721read(51, "", 4096)                      = 0read(51, "# /etc/hosts.allow: list of host"..., 4096) = 464read(51, "", 4096)                      = 0read(51, "# /etc/hosts.deny: list of hosts"..., 4096) = 721read(51, "", 4096)                      = 0read(51, "# /etc/hosts.allow: list of host"..., 4096) = 464read(51, "", 4096)                      = 0read(51, "# /etc/hosts.deny: list of hosts"..., 4096) = 721read(51, "", 4096)                      = 0read(51, "# /etc/hosts.allow: list of host"..., 4096) = 464read(51, "", 4096)                      = 0read(51, "# /etc/hosts.deny: list of hosts"..., 4096) = 721read(51, "", 4096)                      = 0read(51, "# /etc/hosts.allow: list of host"..., 4096) = 464read(51, "", 4096)                      = 0read(51, "# /etc/hosts.deny: list of hosts"..., 4096) = 721read(51, "", 4096)                      = 0read(51, "# /etc/hosts.allow: list of host"..., 4096) = 464read(51, "", 4096)                      = 0read(51, "# /etc/hosts.deny: list of hosts"..., 4096) = 721read(51, "", 4096)                      = 0read(51, "# /etc/hosts.allow: list of host"..., 4096) = 464read(51, "", 4096)                      = 0read(51, "# /etc/hosts.deny: list of hosts"..., 4096) = 721read(51, "", 4096)                      = 0read(51, "# /etc/hosts.allow: list of host"..., 4096) = 464read(51, "", 4096)                      = 0/<code>

如我們所見,一些新的連接需要花費很長的時間來連接 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 是否執行正常。

<code>[RDBA] percona@db4-atsaloux: ~ $ ldd /usr/sbin/mysqld | grep libwraplibwrap.so.0 => /lib/x86_64-linux-gnu/libwrap.so.0 (0x00007fa80532c000)/<code>

相關文檔及博文鏈接:

《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/>)


分享到:


相關文章: