容器混搭搞出線上 Redis 事故,這坑我先踩為敬
事情是這樣的, 現在有一個 redis 3.0 集群節點都是裸 redis 或 host 網絡模式部署的容器 redis (基本上跟裸 redis 差不多), 需要把它們替換成 macvlan 網絡模式的 redis 容器, 以顯得我們的 dockerized redis cluster 很上檔次。
這事情幾個月前也搞過一次毫無壓力。然而這一次又搞, 就出岔子了。(這劇本不對啊摔)
于是開始 加了兩個 macvlan 的容器到上述 redis 集群作為從節點,打算稍候 failover 替換掉主, 過了十分鐘左右群里炸毛, 說數據都取不到,或者格式不對。上線一查, 發現正在加從節點的這個集群跟另一個集群的節點混到一起去了。這里吐槽一下 redis 集群的協議,兩個正常服務的集群可以直接通過一個 cluster meet 合并成一個集群, 然后槽位分布亂了...
首先當然是緊急恢復線上業務,先拉一個新集群出來再說 (所幸這個集群的數據不需要持久化)。結果,新集群剛弄出來。又被合進了上面那個集群。(這時我滿腦子都是某個科教片里兩個星系合并的一段視頻, 滿天都在炸!
然 后 cluster nodes 看了一下,發現集群里有幾個節點地址變成了 172.17.x.x,這應該是 docker 的內部網段地址,所以反應過來,可能是 docker 網絡配置問題,將握手流量發給了錯誤的節點,然后那些節點被并了進來。這時候創建一個新網段有點來不及了 (還打了個電話給已經請假回家的 @小六哇啦啦 老師...) 換了個思路,把新 redis 換個端口部署,再組個集群,觀察了一會兒,這方法起作用了 -.-!!
恢復了被炸 得雞飛狗跳的線上業務之后,就開始排查問題了。線索還是之前 cluster nodes 看到的那個 172.17.x.x 網段, 測試確認了一下,從 docker 容器內連宿主機,宿主機 accept 得到的會是 172.17.x.x 這個地址。而容器內路由表是這樣的:
![]() |
確 實如果宿主機的 IP 是 10.100.1.100 那么流量走的是 eth0 也就是 172.17.x.x 網卡。(10.222.0.0/16 是容器 macvlan 地址)之后就明白了,172.17.x.x 這樣的網卡地址在不同物理機上是可能相同的。也就是說,遭遇的問題可能是如下過程所致
![]() |
* 四個 redis #a #b #c #d
* #a #b 是兩個 host 網絡的 redis,在同一個集群中,#d 是 macvlan 部署的 redis,在另一個集群中
* #c 是一個空閑的 redis,它與 #d 恰好有相同的 eth0 地址
1> #c 通過 eth0 向 #a 發送了一個 handshake
2> #a 確認, 這時, 它認為 #c 的地址是 172.17.0.55
3> #a 將新節點地址廣播給 #b
4> #b 向 172.17.0.55 發送一個握手請求,然而,此地址在它所在機器上對應的是 #d,之后兩個集群就混一起去了
這也解釋了為啥幾個月之前這么搞的時候沒出問題,應該是那時候運氣好沒有相同地址的容器;同時也解釋了為啥不是每個純 macvlan 模式的 redis 集群都中槍。后來在測試機房找了兩個恰好相同網卡的容器,按上述思路搭了集群試了試,果然重現了。
解決方案
* 因噎廢食 : 以后別這么混搭玩了
* 繞過 : 端口號不一樣法
* 改默認路由 : 默認就走 vlan 網卡,不過這樣的話不能訪問外網, 對 redis 而言沒問題, 但其他業務可能就不行了
* 加路由 : 其實可以通過在容器內加一條路由 10.100.0.0/16 走 vlan 這樣宿主機 accept 到的地址就會是機房唯一的 vlan 網卡地址了,這個方案 @CMGS 正在評估中。
EOF
Via:豆瓣