背景
近年來,網際網路上安全事件頻發,企業資訊保安越來越受到重視,而IDC伺服器安全又是縱深防禦體系中的重要一環。保障IDC安全,常用的是基於主機型入侵檢測系統Host-based Intrusion Detection System,即HIDS。在HIDS面對幾十萬臺甚至上百萬臺規模的IDC環境時,系統架構該如何設計呢?複雜的伺服器環境,網路環境,巨大的資料量給我們帶來了哪些技術挑戰呢?
需求描述
對於HIDS產品,我們安全部門的產品經理提出了以下需求:
- 滿足50W-100W伺服器量級的IDC規模。
- 部署在高併發伺服器生產環境,要求Agent低效能低損耗。
- 廣泛的部署相容性。
- 偏向應用層和使用者態入侵檢測(可以和核心態檢測部分解耦)。
- 針對利用主機Agent排查漏洞的最急需場景提供基本的能力,可以實現海量環境下快速查詢系統漏洞。
- Agent跟Server的配置下發通道安全。
- 配置資訊讀取寫入需要鑑權。
- 配置變更歷史記錄。
- Agent外掛具備自更新功能。
分析需求
首先,伺服器業務程式優先順序高,HIDS Agent程式自己可以終止,但不能影響宿主機的主要業務,這是第一要點,那麼業務需要具備熔斷功能,並具備自我恢復能力。
其次,程式保活、維持心跳、實時獲取新指令能力,百萬臺Agent的全量控制時間一定要短。舉個極端的例子,當Agent出現緊急情況,需要全量停止時,那麼全量停止的命令下發,需要在1-2分鐘內完成,甚至30秒、20秒內完成。這些將會是很大的技術挑戰。
還有對配置動態更新,日誌級別控制,細分精確控制到每個Agent上的每個HIDS子程式,能自由地控制每個程式的啟停,每個Agent的引數,也能精確的感知每臺Agent的上線、下線情況。
同時,Agent本身是安全Agent,安全的因素也要考慮進去,包括通訊通道的安全性,配置管理的安全性等等。
最後,服務端也要有一致性保障、可用性保障,對於大量Agent的管理,必須能實現任務分攤,並行處理任務,且保證資料的一致性。考慮到公司規模不斷地擴大,業務不斷地增多,特別是美團和大眾點評合併後,面對的各種作業系統問題,產品還要具備良好的相容性、可維護性等。
總結下來,產品架構要符合以下特性:
- 叢集高可用。
- 分散式,去中心化。
- 配置一致性,配置多版本可追溯。
- 分治與彙總。
- 相容部署各種Linux 伺服器,只維護一個版本。
- 節省資源,佔用較少的CPU、記憶體。
- 精確的熔斷限流。
- 伺服器數量規模達到百萬級的叢集負載能力。
技術難點
在列出產品需要實現的功能點、技術點後,再來分析下遇到的技術挑戰,包括不限於以下幾點:
- 資源限制,較小的CPU、記憶體。
- 五十萬甚至一百萬臺伺服器的Agent處理控制問題。
- 量級大了後,叢集控制帶來的控制效率,響應延遲,資料一致性問題。
- 量級大了後,資料傳輸對整個伺服器內網帶來的流量衝擊問題。
- 量級大了後,執行環境更復雜,Agent異常表現的感知問題。
- 量級大了後,業務日誌、程式執行日誌的傳輸、儲存問題,被監控業務訪問量突增帶來監控資料聯動突增,對內網頻寬,儲存叢集的爆發壓力問題。
我們可以看到,技術難點幾乎都是伺服器到達一定量級帶來的,對於大量的服務,叢集分散式是業界常見的解決方案。
架構設計與技術選型
對於管理Agent的服務端來說,要實現高可用、容災設計,那麼一定要做多機房部署,就一定會遇到資料一致性問題。那麼資料的儲存,就要考慮分散式儲存元件。 分散式資料儲存中,存在一個定理叫CAP定理
:

CAP的解釋
關於CAP定理
,分為以下三點:
- 一致性(Consistency):分散式資料庫的資料保持一致。
- 可用性(Availability):任何一個節點當機,其他節點可以繼續對外提供服務。
- 分割槽容錯性(網路分割槽)Partition Tolerance:一個資料庫所在的機器壞了,如硬碟壞了,資料丟失了,可以新增一臺機器,然後從其他正常的機器把備份的資料同步過來。
根據定理,分散式系統只能滿足三項中的兩項而不可能滿足全部三項。理解CAP定理
的最簡單方式是想象兩個節點分處分割槽兩側。允許至少一個節點更新狀態會導致資料不一致,即喪失了Consistency。如果為了保證資料一致性,將分割槽一側的節點設定為不可用,那麼又喪失了Availability。除非兩個節點可以互相通訊,才能既保證Consistency又保證Availability,這又會導致喪失Partition Tolerance。
參見:CAP Theorem
CAP的選擇
為了容災上設計,叢集節點的部署,會選擇的異地多機房,所以 「Partition tolerance」是不可能避免的。那麼可選的是 AP
與 CP
。
在HIDS叢集的場景裡,各個Agent對叢集持續可用性沒有非常強的要求,在短暫時間內,是可以出現異常,出現無法通訊的情況。但最終狀態必須要一致,不能存在叢集下發關停指令,而出現個別Agent不聽從叢集控制的情況出現。所以,我們需要一個滿足 CP
的產品。
滿足CP的產品選擇
在開源社群中,比較出名的幾款滿足CP的產品,比如etcd、ZooKeeper、Consul等。我們需要根據幾款產品的特點,根據我們需求來選擇符合我們需求的產品。
插一句,網上很多人說Consul是AP產品,這是個錯誤的描述。既然Consul支援分散式部署,那麼一定會出現「網路分割槽」的問題, 那麼一定要支援「Partition tolerance」。另外,在consul的官網上自己也提到了這點 Consul uses a CP architecture, favoring consistency over availability.
Consul is opinionated in its usage while Serf is a more flexible and general purpose tool. In CAP terms, Consul uses a CP architecture, favoring consistency over availability. Serf is an AP system and sacrifices consistency for availability. This means Consul cannot operate if the central servers cannot form a quorum while Serf will continue to function under almost all circumstances.
etcd、ZooKeeper、Consul對比
借用etcd官網上etcd與ZooKeeper和Consul的比較圖。

在我們HIDS Agent的需求中,除了基本的服務發現
、配置同步
、配置多版本控制
、變更通知
等基本需求外,我們還有基於產品安全性上的考慮,比如傳輸通道加密
、使用者許可權控制
、角色管理
、基於Key的許可權設定
等,這點 etcd
比較符合我們要求。很多大型公司都在使用,比如Kubernetes
、AWS
、OpenStack
、Azure
、Google Cloud
、Huawei Cloud
等,並且etcd
的社群支援非常好。基於這幾點因素,我們選擇etcd
作為HIDS的分散式叢集管理。
選擇etcd
對於etcd在專案中的應用,我們分別使用不同的API介面實現對應的業務需求,按照業務劃分如下:
- Watch機制來實現配置變更下發,任務下發的實時獲取機制。
- 腦裂問題在etcd中不存在,etcd叢集的選舉,只有投票達到
N/2+1
以上,才會選做Leader,來保證資料一致性。另外一個網路分割槽的Member節點將無主。 - 語言親和性,也是Golang開發的,Client SDK庫穩定可用。
- Key儲存的資料結構支援範圍性的Key操作。
- User、Role許可權設定不同讀寫許可權,來控制Key操作,避免其他客戶端修改其他Key的資訊。
- TLS來保證通道資訊傳遞安全。
- Txn分散式事務API配合Compare API來確定主機上線的Key唯一性。
- Lease租約機制,過期Key釋放,更好的感知主機下線資訊。
- etcd底層Key的儲存為BTree結構,查詢時間複雜度為O(㏒n),百萬級甚至千萬級Key的查詢耗時區別不大。
etcd Key的設計
字首按角色設定:
- Server配置下發使用
/hids/server/config/{hostname
。
}/master - Agent註冊上線使用
/hids/agent/master/{hostname
。
} - Plugin配置獲取使用
/hids/agent/config/{hostname
。
}/plugin/ID/conf_name
Server Watch /hids/server/config/{hostname
,實現Agent主機上線的瞬間感知。Agent Watch
}/master/hids/server/config/{hostname
來獲取配置變更,任務下發。Agent註冊的Key帶有Lease Id,並啟用keepalive,下線後瞬間感知。 (異常下線,會有1/3的keepalive時間延遲)
}/
關於Key的許可權,根據不同字首,設定不同Role許可權。賦值給不同的User,來實現對Key的許可權控制。
etcd叢集管理
在etcd節點容災考慮,考慮DNS故障時,節點會選擇部署在多個城市,多個機房,以我們伺服器機房選擇來看,在大部分機房都有一個節點,綜合承載需求,我們選擇了N臺伺服器部署在個別重要機房,來滿足負載、容災需求。但對於etcd這種分散式一致性強的元件來說,每個寫操作都需要N/2-1
的節點確認變更,才會將寫請求寫入資料庫中,再同步到各個節點,那麼意味著節點越多,需要確認的網路請求越多,耗時越多,反而會影響叢集節點效能。這點,我們後續將提升單個伺服器效能,以及犧牲部分容災性來提升叢集處理速度。
客戶端填寫的IP列表,包含域名、IP。IP用來規避DNS故障,域名用來做Member節點更新。最好不要使用Discover方案,避免對內網DNS伺服器產生較大壓力。
同時,在配置etcd節點的地址時,也要考慮到內網DNS故障的場景,地址填寫會混合IP、域名兩種形式。
- IP的地址,便於規避內網DNS故障。
- 域名形式,便於做個別節點更替或擴容。
我們在設計產品架構時,為了安全性,開啟了TLS證書認證,當節點變更時,證書的生成也同樣要考慮到上面兩種方案的影響,證書裡需要包含固定IP,以及DNS域名範圍的兩種格式。
etcd Cluster節點擴容
節點擴容,官方手冊上也有完整的方案,etcd的Client裡實現了健康檢測與故障遷移,能自動的遷移到節點IP列表中的其他可用IP。也能定時更新etcd Node List,對於etcd Cluster的叢集節點變更來說,不存在問題。需要我們注意的是,TLS證書的相容。
分散式HIDS叢集架構圖

叢集核心元件高可用,所有Agent、Server都依賴叢集,都可以無縫擴充套件,且不影響整個叢集的穩定性。即使Server全部當機,也不影響所有Agent的繼續工作。
在以後Server版本升級時,Agent不會中斷,也不會帶來雪崩式的影響。etcd叢集可以做到單節點升級,一直到整個叢集升級,各個元件全都解耦。
程式語言選擇
考慮到公司伺服器量大,業務複雜,需求環境多變,作業系統可能包括各種Linux以及Windows等。為了保證系統的相容性,我們選擇了Golang作為開發語言,它具備以下特點:
- 可以靜態編譯,直接通過syscall來執行,不依賴libc,相容性高,可以在所有Linux上執行,部署便捷。
- 靜態編譯語言,能將簡單的錯誤在編譯前就發現。
- 具備良好的GC機制,佔用系統資源少,開發成本低。
- 容器化的很多產品都是Golang編寫,比如Kubernetes、Docker等。
- etcd專案也是Golang編寫,類庫、測試用例可以直接用,SDK支援快速。
- 良好的CSP併發模型支援,高效的協程排程機制。
產品架構大方向
HIDS產品研發完成後,部署的服務都執行著各種業務的伺服器,業務的重要性排在第一,我們產品的功能排在後面。為此,確定了幾個產品的大方向:
- 高可用,資料一致,可橫向擴充套件。
- 容災性好,能應對機房級的網路故障。
- 相容性好,只維護一個版本的Agent。
- 依賴低,不依賴任何動態連結庫。
- 侵入性低,不做Hook,不做系統類庫更改。
- 熔斷降級可靠,寧可自己掛掉,也不影響業務 。
產品實現
篇幅限制,僅討論框架設計
、熔斷限流
、監控告警
、自我恢復
以及產品實現上的主程式
與程式監控
。
框架設計

如上圖,在框架的設計上,封裝常用類庫,抽象化定義Interface
,剝離etcd Client
,全域性化Logger
,抽象化App的啟動、退出方法。使得各模組
(以下簡稱App
)只需要實現自己的業務即可,可以方便快捷的進行邏輯編寫,無需關心底層實現、配置來源、重試次數、熔斷方案等等。
沙箱隔離
考慮到子程式不能無限的增長下去,那麼必然有一個程式包含多個模組的功能,各App
之間既能使用公用底層元件(Logger
、etcd Client
等),又能讓彼此之間互不影響,這裡進行了沙箱化
處理,各個屬性物件僅在各App
的sandbox
裡生效。同樣能實現了App
程式的效能熔斷
,停止所有的業務邏輯功能,但又能具有基本的自我恢復
功能。
IConfig
對各App的配置抽象化處理,實現IConfig的共有方法介面,用於對配置的函式呼叫,比如Check
的檢測方法,檢測配置合法性,檢測配置的最大值、最小值範圍,規避使用人員配置不在合理範圍內的情況,從而避免帶來的風險。
框架底層用Reflect
來處理JSON配置,解析讀取填寫的配置項,跟Config物件對比,填充到對應Struct
的屬性上,允許JSON配置裡只填寫變化的配置,沒填寫的配置項,則使用Config
對應Struct
的預設配置。便於靈活處理配置資訊。
type IConfig interface {
Check() error //檢測配置合法性
}func ConfigLoad(confByte []byte, config IConfig) (IConfig, error) {...//反射生成臨時的IConfig var confTmp IConfig confTmp = reflect.New(reflect.ValueOf(config).Elem().Type()).Interface().(IConfig)... //反射 confTmp 的屬性 confTmpReflect := reflect.TypeOf(confTmp).Elem() confTmpReflectV := reflect.ValueOf(confTmp).Elem() //反射config IConfig configReflect := reflect.TypeOf(config).Elem() configReflectV := reflect.ValueOf(config).Elem()... for i = 0;
i <
num;
i++ {
//遍歷處理每個Field envStructTmp := configReflect.Field(i) //根據配置中的項,來覆蓋預設值 if envStructTmp.Type == confStructTmp.Type {
configReflectV.FieldByName(envStructTmp.Name).Set(confTmpReflectV.Field(i))複製程式碼
Timer、Clock排程
在業務資料產生時,很多地方需要記錄時間,時間的獲取也會產生很多系統呼叫。尤其是在每秒鐘產生成千上萬個事件,這些事件都需要呼叫獲取時間
介面,進行clock_gettime
等系統呼叫,會大大增加系統CPU負載。 而很多事件產生時間的準確性要求不高,精確到秒,或者幾百個毫秒即可,那麼框架裡實現了一個顆粒度符合需求的(比如100ms、200ms、或者1s等)間隔時間更新的時鐘,即滿足事件對時間的需求,又減少了系統呼叫。
同樣,在有些Ticker
場景中,Ticker
的間隔顆粒要求不高時,也可以合併成一個Ticker
,減少對CPU時鐘的呼叫。
Catcher
在多協程場景下,會用到很多協程來處理程式,對於個別協程的panic錯誤,上層執行緒要有一個良好的捕獲機制,能將協程錯誤丟擲去,並能恢復執行,不要讓程式崩潰退出,提高程式的穩定性。
抽象介面
框架底層抽象化封裝Sandbox的Init、Run、Shutdown介面,規範各App的對外介面,讓App的初始化、執行、停止等操作都標準化。App的模組業務邏輯,不需要關注PID檔案管理,不關注與叢集通訊,不關心與父程式通訊等通用操作,只需要實現自己的業務邏輯即可。App與框架的統一控制,採用Context包以及Sync.Cond等條件鎖作為同步控制條件,來同步App與框架的生命週期,同步多協程之間同步,並實現App的安全退出,保證資料不丟失。
限流
網路IO
- 限制資料上報速度。
- 佇列儲存資料任務列表。
- 大於佇列長度資料丟棄。
- 丟棄資料總數計數。
- 計數資訊作為心跳狀態資料上報到日誌中心,用於資料對賬。
磁碟IO
程式執行日誌,對日誌級別劃分,參考 /usr/include/sys/syslog.h
:
- LOG_EMERG
- LOG_ALERT
- LOG_CRIT
- LOG_ERR
- LOG_WARNING
- LOG_NOTICE
- LOG_INFO
- LOG_DEBUG
在程式碼編寫時,根據需求選用級別。級別越低日誌量越大,重要程度越低,越不需要傳送至日誌中心,寫入本地磁碟。那麼在異常情況排查時,方便參考。
日誌檔案大小控制,分2個檔案,每個檔案不超過固定大小,比如20M
、50M
等。並且,對兩個檔案進行來回寫,避免日誌寫滿磁碟的情況。
IRetry
為了加強Agent的魯棒性,不能因為某些RPC動作失敗後導致整體功能不可用,一般會有重試功能。Agent跟etcd Cluster也是TCP長連線(HTTP2),當節點重啟更換或網路卡頓等異常時,Agent會重連,那麼重連的頻率控制,不能是死迴圈般的重試。假設伺服器內網交換機因內網流量較大產生抖動,觸發了Agent重連機制,不斷的重連又加重了交換機的負擔,造成雪崩效應,這種設計必須要避免。 在每次重試後,需要做一定的回退機制,常見的指數級回退
,比如如下設計,在規避雪崩場景下,又能保障Agent的魯棒性,設定最大重試間隔,也避免了Agent失控的問題。
//網路庫重試Interfacetype INetRetry interface {
//開始連線函式 Connect() error String() string //獲取最大重試次數 GetMaxRetry() uint ...
}// 底層實現func (this *Context) Retry(netRetry INetRetry) error {... maxRetries = netRetry.GetMaxRetry() //最大重試次數 hashMod = netRetry.GetHashMod()for {
if c.shutting {
return errors.New("c.shutting is true...")
} if maxRetries >
0 &
&
retries >
= maxRetries {
c.logger.Debug("Abandoning %s after %d retries.", netRetry.String(), retries) return errors.New("超過最大重試次數")
}... if e := netRetry.Connect();
e != nil {
delay = 1 <
<
retries if delay == 0 {
delay = 1
} delay = delay * hashInterval... c.logger.Emerg("Trying %s after %d seconds , retries:%d,error:%v", netRetry.String(), delay, retries, e) time.Sleep(time.Second * time.Duration(delay))
}...
}複製程式碼
事件拆分
百萬臺IDC規模的Agent部署,在任務執行、叢集通訊或對宿主機產生資源影響時,務必要錯峰進行,根據每臺主機的唯一特徵取模,拆分執行,避免造成雪崩效應。
監控告警
古時候,行軍打仗時,提倡「兵馬未動,糧草先行」,無疑是冷兵器時代決定勝負走向的重要因素。做產品也是,尤其是大型產品,要對自己執行狀況有詳細的掌控,做好監控告警,才能確保產品的成功。
對於etcd叢集的監控,元件本身提供了Metrics
資料輸出介面,官方推薦了Prometheus來採集資料,使用Grafana來做聚合計算、圖示繪製,我們做了Alert
的介面開發,對接了公司的告警系統,實現IM、簡訊、電話告警。
Agent數量感知,依賴Watch數字,實時準確感知。
如下圖,來自產品剛開始灰度時的某一時刻截圖,Active Streams(即etcd Watch的Key數量)即為對應Agent數量,每次灰度的產品數量。因為該操作,是Agent直接與叢集通訊,並且每個Agent只Watch一個Key。且叢集資料具備唯一性、一致性,遠比心跳日誌的處理要準確的多。

etcd叢集Members之間健康狀況監控

用於監控管理etcd叢集的狀況,包括Member
節點之間資料同步,Leader選舉次數,投票發起次數,各節點的記憶體申請狀況,GC情況等,對叢集的健康狀況做全面掌控。
程式執行狀態監控告警


全量監控Aagent的資源佔用情況,統計每天使用最大CPU\記憶體的主機Agent,確定問題的影響範圍,及時做策略調整,避免影響到業務服務的執行。並在後續版本上逐步做調整優化。
百萬臺伺服器,日誌告警量非常大,這個級別的告警資訊的篩選、聚合是必不可少的。減少無用告警,讓研發運維人員疲於奔命,也避免無用告警導致研發人員放鬆了警惕,前期忽略個例告警,先解決主要矛盾。
- 告警資訊分級,告警資訊細分ID。
- 根據告警級別過濾,根據告警ID聚合告警,來發現同型別錯誤。
- 根據告警資訊的所在機房、專案組、產品線等維度來聚合告警,來發現同型別錯誤。
資料採集告警
- 單機資料資料大小、總量的歷史資料對比告警。
- 按機房、專案組、產品線等維度的大小、總量等維度的歷史資料對比告警。
- 資料採集大小、總量的對賬功能,判斷經過一系列處理流程的日誌是否丟失的監控告警。
熔斷
- 針對單機Agent使用資源大小的閾值熔斷,CPU使用率,連續N次觸發大於等於5%,則進行保護性熔斷,退出所有業務邏輯,以保護主機的業務程式優先。
- Master程式進入空閒狀態,等待第二次時間
Ticker
到來,決定是否恢復執行。 - 各個App基於業務層面的監控熔斷策略。
灰度管理
在前面的配置管理
中的etcd Key
設計裡,已經細分到每個主機(即每個Agent)一個Key。那麼,服務端的管理,只要區分該主機所屬機房、環境、群組、產品線即可,那麼,我們的管理Agent的顆粒度可以精確到每個主機,也就是支援任意緯度的灰度釋出管理與命令下發。
資料上報通道
元件名為 log_agent
,是公司內部統一日誌上報元件,會部署在每一臺VM、Docker上。主機上所有業務均可將日誌傳送至該元件。 log_agent
會將日誌上報到Kafka叢集中,經過處理後,落入Hive叢集中。(細節不在本篇討論範圍)
主程式
主程式實現跟etcd叢集通訊,管理整個Agent的配置下發與命令下發;管理各個子模組的啟動與停止;管理各個子模組的CPU、記憶體佔用情況,對資源超標進行進行熔斷處理,讓出資源,保證業務程式的執行。
外掛化管理其他模組,多程式模式,便於提高產品靈活性,可更簡便的更新啟動子模組,不會因為個別模組外掛的功能、BUG導致整個Agent崩潰。
程式監控
方案選擇
我們在研發這產品時,做了很多關於linux程式建立監控
的調研,不限於安全產品
,大約有下面三種技術方案:
方案 | Docker相容性 | 開發難度 | 資料準確性 | 系統侵入性 |
---|---|---|---|---|
cn_proc | 不支援Docker | 一般 | 存在核心拿到的PID,在/proc/ 下丟失的情況 |
無 |
Audit | 不支援Docker | 一般 | 同cn_proc | 弱,但依賴Auditd |
Hook | 定製 | 高 | 精確 | 強 |
對於公司的所有伺服器來說,幾十萬臺都是已經在執行的伺服器,新上的任何產品,都儘量避免對伺服器有影響,更何況是所有伺服器都要部署的Agent。 意味著我們在選擇系統侵入性
來說,優先選擇最小侵入性
的方案。
對於Netlink
的方案原理,可以參考這張圖(來自:kernel-proc-connector-and-containers)

系統侵入性比較
cn_proc
跟Autid
在「系統侵入性」和「資料準確性」來說,cn_proc
方案更好,而且使用CPU、記憶體等資源情況,更可控。Hook
的方案,對系統侵入性太高了,尤其是這種最底層做HOOK syscall的做法,萬一測試不充分,在特定環境下,有一定的概率會出現Bug,而在百萬IDC的規模下,這將成為大面積事件,可能會造成重大事故。
相容性上比較
cn_proc
不相容Docker,這個可以在宿主機上部署來解決。Hook
的方案,需要針對每種Linux的發行版做定製,維護成本較高,且不符合長遠目標(收購外部公司時遇到各式各樣作業系統問題)
資料準確性比較
在大量PID建立的場景,比如Docker的宿主機上,核心返回PID時,因為PID返回非常多非常快,很多程式啟動後,立刻消失了,另外一個執行緒都還沒去讀取/proc/
,程式都丟失了,場景常出現在Bash執行某些命令。
最終,我們選擇Linux Kernel Netlink介面的cn_proc指令
作為我們程式監控方案,藉助對Bash命令的收集,作為該方案的補充。當然,仍然存在丟資料的情況,但我們為了系統穩定性,產品侵入性低等業務需求,犧牲了一些安全性上的保障。
對於Docker的場景,採用宿主機執行,捕獲資料,關聯到Docker容器,上報到日誌中心的做法來實現。
遇到的問題
核心Netlink傳送資料卡住
核心返回資料太快,使用者態ParseNetlinkMessage
解析讀取太慢,導致使用者態網路Buff佔滿,核心不再傳送資料給使用者態,程式空閒。對於這個問題,我們在使用者態做了佇列控制,確保解析時間的問題不會影響到核心傳送資料。對於佇列的長度,我們做了定值限制,生產速度大於消費速度的話,可以丟棄一些資料,來保證業務正常執行,並且來控制程式的記憶體增長問題。
疑似“記憶體洩露”問題
在一臺Docker的宿主機上,執行了50個Docker例項,每個Docker都執行了複雜的業務場景,頻繁的建立程式,在最初的產品實現上,啟動時大約10M記憶體佔用,一天後達到200M的情況。
經過我們Debug分析發現,在ParseNetlinkMessage
處理核心發出的訊息時,PID頻繁建立帶來記憶體頻繁申請,物件頻繁例項化,佔用大量記憶體。同時,在Golang GC時,掃描、清理動作帶來大量CPU消耗。在程式碼中,發現對於linux/connector.h裡的struct cb_msg
、linux/cn_proc.h裡的struct proc_event
結構體頻繁建立,帶來記憶體申請等問題,以及Golang的GC特性,記憶體申請後,不會在GC時立刻歸還作業系統,而是在後臺任務裡,逐漸的歸還到作業系統,見:debug.FreeOSMemory
FreeOSMemory forces a garbage collection followed by anattempt to return as much memory to the operating systemas possible. (Even if this is not called, the runtime graduallyreturns memory to the operating system in a background task.)
但在這個業務場景裡,大量頻繁的建立PID,頻繁的申請記憶體,建立物件,那麼申請速度遠遠大於釋放速度,自然記憶體就一直堆積。
從文件中可以看出,FreeOSMemory
的方法可以將記憶體歸還給作業系統,但我們並沒有採用這種方案,因為它治標不治本,沒法解決記憶體頻繁申請頻繁建立的問題,也不能降低CPU使用率。
為了解決這個問題,我們採用了sync.Pool
的內建物件池方式,來複用回收物件,避免物件頻繁建立,減少記憶體佔用情況,在針對幾個頻繁建立的物件做物件池化後,同樣的測試環境,記憶體穩定控制在15M左右。
大量物件的複用,也減少了物件的數量,同樣的,在Golang GC執行時,也減少了物件的掃描數量、回收數量,降低了CPU使用率。
專案進展
在產品的研發過程中,也遇到了一些問題,比如:
- etcd Client Lease Keepalive的Bug。
- Agent程式資源限制的Cgroup觸發幾次核心Bug。
- Docker宿主機上瞬時大量程式建立的效能問題。
- 網路監控模組在處理Nginx反向代理時,動輒幾十萬TCP連結的網路資料獲取壓力。
- 個別程式開啟了10W以上的fd。
方法一定比困難多,但方法不是拍腦袋想出來的,一定要深入探索問題的根本原因,找到系統性的修復方法,具備高可用、高效能、監控告警、熔斷限流等功能後,對於出現的問題,能夠提前發現,將故障影響最小化,提前做處理。在應對產品運營過程中遇到的各種問題時,逢山開路,遇水搭橋,都可以從容的應對。
經過我們一年的努力,已經部署了除了個別特殊業務線之外的其他所有伺服器,數量達幾十萬臺,產品穩定執行。在資料完整性、準確性上,還有待提高,在精細化運營上,需要多做改進。
本篇更多的是研發角度上軟體架構上的設計,關於安全事件分析、資料建模、運營策略等方面的經驗和技巧,未來將會由其他同學進行分享,敬請期待。
總結
我們在研發這款產品過程中,也看到了網上開源了幾款同類產品,也瞭解了他們的設計思路,發現很多產品都是把主要方向放在了單個模組的實現上,而忽略了產品架構上的重要性。
比如,有的產品使用了syscall hook
這種侵入性高的方案來保障資料完整性,使得對系統侵入性非常高,Hook程式碼的穩定性,也嚴重影響了作業系統核心的穩定。同時,Hook程式碼也缺少了監控熔斷的措施,在幾十萬伺服器規模的場景下部署,潛在的風險可能讓安全部門無法接受,甚至是致命的。
這種設計,可能在伺服器量級小時,對於出現的問題多花點時間也能逐個進行維護,但應對幾十萬甚至上百萬臺伺服器時,對維護成本、穩定性、監控熔斷等都是很大的技術挑戰。同時,在研發上,也很難實現產品的快速迭代,而這種方式帶來的影響,幾乎都會導致核心當機之類致命問題。這種事故,使用伺服器的業務方很難進行接受,勢必會影響產品的研發速度、推進速度;影響同事(SRE運維等)對產品的信心,進而對後續產品的推進帶來很大的阻力。
以上是筆者站在研發角度,從可用性、可靠性、可控性、監控熔斷等角度做的架構設計與框架設計,分享的產品研發思路。
筆者認為大規模的伺服器安全防護產品,首先需要考慮的是架構的穩定性、監控告警的實時性、熔斷限流的準確性等因素,其次再考慮安全資料的完整性、檢測方案的可靠性、檢測模型的精確性等因素。
九層之臺,起於累土。只有打好基礎,才能運籌帷幄,決勝千里之外。
參考資料
- en.wikipedia.org/wiki/CAP_th…
- www.consul.io/intro/vs/se…
- golang.org/src/runtime…
- www.ibm.com/developerwo…
- www.kernel.org/doc/
- coreos.com/etcd/docs/l…
作者簡介
陳馳,美團點評技術專家,2017年加入美團,十年以上網際網路產品研發經驗,專注於分散式系統架構設計,目前主要從事安全防禦產品研發工作。
關於美團安全
美團安全部的大多數核心開發人員,擁有多年網際網路以及安全領域實踐經驗,很多同學參與過大型網際網路公司的安全體系建設,其中也不乏全球化安全運營人才,具備百萬級IDC規模攻防對抗的經驗。安全部也不乏CVE“挖掘聖手”,有受邀在Black Hat等國際頂級會議發言的講者,當然還有很多漂亮的運營妹子。
目前,美團安全部涉及的技術包括滲透測試、Web防護、二進位制安全、核心安全、分散式開發、大資料分析、安全演算法等等,同時還有全球合規與隱私保護等策略制定。我們正在建設一套百萬級IDC規模、數十萬終端接入的移動辦公網路自適應安全體系,這套體系構建於零信任架構之上,橫跨多種雲基礎設施,包括網路層、虛擬化/容器層、Server 軟體層(核心態/使用者態)、語言虛擬機器層(JVM/JS V8)、Web應用層、資料訪問層等,並能夠基於“大資料+機器學習”技術構建全自動的安全事件感知系統,努力打造成業界最前沿的內建式安全架構和縱深防禦體系。
隨著美團的高速發展,業務複雜度不斷提升,安全部門面臨更多的機遇和挑戰。我們希望將更多代表業界最佳實踐的安全專案落地,同時為更多的安全從業者提供一個廣闊的發展平臺,並提供更多在安全新興領域不斷探索的機會。
招聘資訊
美團安全部正在招募Web&
二進位制攻防、後臺&
系統開發、機器學習&
演算法等各路小夥伴。如果你想加入我們,歡迎簡歷請發至郵箱zhaoyan17@meituan.com
具體職位資訊可參考這裡:mp.weixin.qq.com/s/ynEq5LqQ2…
美團安全應急響應中心MTSRC主頁:security.meituan.com
