CoreOS綜合案例之分布式服務的監控
編者按:InfoQ開設新欄目“品味書香”,精選技術書籍的精彩章節,以及分享看完書留下的思考和收獲,歡迎大家關注。本文節選自林帆著《CoreOS實踐之路》中的章節“激勵他人的第二大障礙”,討論了成功技術領導的一些經驗。
6.1.1 案例說明
6.1.1.1 案例背景
在一個運行著數十上百種應用服務的集群的運維和應用設計中常常會遇到這樣的需求:服務狀態的收集,如何在集群的任意節點上快速地獲取到整個集群中任意一個服務的狀態呢?
這是典型的大型分布式服務監控任務。傳統的解決方案一般都需要引入一個額外的監控系統,例如Nagios。這些監控工具通常會使用一個集中的數據收集和存儲節點,然后利用在每個節點上的客戶端收集定制數據,并發送回收集節點進行統一處理和展示,其余節點如果需要集群狀態,就要到這個收集節點上獲取。此外,對于容器化應用,還需要在制作容器時就將收集服務數據的客戶端打包到容器里面,從而對容器內容造成侵入性。另一些解決方案是讓監控的客戶端安裝在每個主機節點上,通過Docker服務獲取容器的運行狀態。這種方法比較適合采集基本的容器數據信息,例如CPU、內存、磁盤訪問等,而不便于對具體的服務進行定制信息收集。
在這個案例中,需要考慮如下幾個分布式服務監控的具體特性。
- 硬件故障的可能性,監控服務的系統不應該存在單點依賴的情況。
- 被監控服務運行的節點、數量均不確定,因此設計的系統應該具備足夠的靈活性。
- 當被監控服務由于故障而轉移到新的節點上運行時,監控應該能夠繼續正常進行。
6.1.1.2 方案分析
在CoreOS系統中,數據存儲和分發的任務已經有Etcd能夠解決,而定時采集監控的工具可以用Fleet配置一個服務腳本來完成,因此,用戶只需要設計好監控數據的收集方法就足夠了。
此外,在CoreOS系統中,被監控的分布式服務程序本身可以通過Fleet來管理。為了避免集中收集數據引入單點故障問題,數據的收集也應該采用分布式的方式,在被監控服務所在的節點上就近采集,然后直接保存到Etcd中。為此,可以通過Fleet中的服務依賴管理功能,為每個被監控的服務配備一個額外的“秘書服務”,這個服務就跟隨著被監控的應用服務在節點間同時啟動,同時遷移,同時記錄下被監控服務的實時狀態。這樣便省去了傳統的集中收集數據時判斷哪個節點當前運行哪些服務的麻煩。
收集數據的定制會包含很多針對具體業務場景相關的細節,不具有普遍共性。為了不偏頗特別的應用場景,在這個例子中,采用一個最簡單的服務作為監控的目標:一個Apache HTTP服務器的默認頁面地單純地檢查HTTP服務是否可用。監控這個簡單的服務,除了收集數據的方法以外,與監控一個復雜的服務并沒有任何差別。通過這個例子,我們將體會到Etcd與Fleet搭配的輕量級服務組合能夠為集群管理帶來的便利性。
6.1.2 方案實施
6.1.2.1 容器化被監控的服務
由于Docker和Rkt等容器具備了集群鏡像分發的特性,將服務容器化能夠使得普通的服務通過Fleet的協助獲得更好的跨節點調度能力。
特別是對于運行在CoreOS系統上的服務,由于系統的只讀分區不可修改,加上系統本身十分精簡,許多不常用的依賴庫或軟件都無法找到。若不通過容器運行,許多服務都難以運行起來。作為模擬被監控服務的目的,我們直接使用Docker官方倉庫里的Apache鏡像,并編寫相應的服務Unit模板文件來運行服務的實例。
將以下文件保存為apache@.service,放到/etc/systemd/system/目錄中。
[Unit] Description=運行在%i端口上的Apache HTTP Web服務 # 需要依賴的服務 Requires=etcd.service Requires=docker.service # 依賴的啟動順序 After=etcd.service After=docker.service [Service] # 第一次運行下載鏡像較耗時,為了防止誤判,關閉啟動的超時判定 TimeoutStartSec=0 # 關閉Systemd在結束服務時殺死同CGroup進程的操作 # 這個操作對Docker托管的服務不適用,會誤殺Docker后臺守護進程 KillMode=none # 讀取CoreOS的公共環境變量文件 EnvironmentFile=/etc/environment # 啟動的命令,使用-=符號表示忽略docker kill和docker rm運行時的錯誤 ExecStartPre=-/usr/bin/docker kill apache.%i ExecStartPre=-/usr/bin/docker rm apache.%i ExecStartPre=/usr/bin/docker pull httpd ExecStart=/usr/bin/docker run --name apache.%i -p ${COREOS_PUBLIC_IPV4}:%i:80 httpd # 結束的命令 ExecStop=/usr/bin/docker stop apache.%i [X-Fleet] # 不要在同一個服務器節點上運行多個Apache服務 Conflicts=apache@*.service
在第3章中我們已經介紹了Unit模板文件,在上面這個模板文件中,使用@后面的參數作為運行時監聽的端口號。在實際的應用中這是一種推薦的做法,因為它能夠使得管理服務時可以很方便地通過服務名稱找到服務監聽的端口號。
上面模板文件中的注釋已經比較詳細了。這個模板文件可以適用于大多數通過Docker托管的服務。下面再簡單介紹一下這個模板中的幾個部分,以便用戶修改這個模板以適應具體業務場景的需求。
首先,在Unit段中,Requires列出了這個服務需要依賴的其他用戶或系統服務的名字,然后用After和Before(這里沒有)等關鍵字指明這些服務的啟動順序。CoreOS會等待所有寫在After區域的服務啟動完成后再運行當前服務,同時在當前服務啟動完成后,喚起所有寫在Before區域的其他服務。
然后,在Service段中,設置了兩個特別的參數“TimeoutStartSec=0”和“KillMode=none”。前一個配置之前提到過,主要是防止Docker在第一次啟動時由于下載鏡像時間較長,被Systemd認為失去響應而誤殺;后一個配置是因為Docker的每個容器都托管于同一個守護進程下面,默認在服務停止后Systemd會嘗試殺死所有屬于同一CGroup下的進程,此時它會嘗試去清理Docker的后臺進程,結果是要么Docker的守護進程意外結束,要么Systemd始終依然認為進程沒有清理干凈,導致下一次啟動同名的容器時出現莫名的問題。
配置“EnvironmentFile=/etc/environment”是屬于CoreOS特有的用法,在CoreOS系統中,/etc/environment文件默認包含了服務器節點的公網IP地址和內網IP地址,這個文件中的變量是在每次系統啟動時寫入的。
$ cat /etc/environment COREOS_PRIVATE_IPV4=172.31.14.97 COREOS_PUBLIC_IPV4=##.##.##.## <- 演示目的,隱藏實際地址
CoreOS下的許多服務都會使用這個配置文件,這個模板后面用到的變量COREOS_ PUBLIC_IPV4就來自于這個文件。
最后,在X-Fleet段中,可以看到這里配置的“Conflicts=apache@*.service”讓與當前服務相同的服務進程不要調度到當前這個節點上。這樣做是為了實現服務的高可用性,在出現單節點故障的時候,確保其他相同的服務是運行在集群中不同的節點上的,整個集群仍然能夠繼續對外提供服務。在實際應用時,還需要加上反向代理作為負載均衡節點,從而屏蔽后端服務調度對用戶訪問造成的影響。
對于一般的應用服務而言,這個模板的配置基本是通用的。
6.1.2.2 使用Fleet和Etcd監控服務狀態
這次內容的主角,秘書服務(暫且想象她是一位貌美如花的女子)出場了。我們來一睹它的芳容。
[Unit] Description=監控運行在%i端口上的Apache服務運行狀態 # 需要依賴的服務 Requires=etcd.service Requires=apache@%i.service # 依賴的啟動順序 After=etcd.service After=apache@%i.service BindsTo=apache@%i.service [Service] # 讀取CoreOS的公共環境變量文件 EnvironmentFile=/etc/environment # 服務意外終止時自動重啟 Restart=on-failure RestartSec=0 # 啟動的命令 # 檢測被監控的服務是否運行正常,并將檢測的結果用etcdctl寫入Etcd記錄中 ExecStart=/bin/bash -c '\ while true; do \ curl -f ${COREOS_PUBLIC_IPV4}:%i; \ if [ $? -eq 0 ]; then \ etcdctl set /services/apache/${COREOS_PUBLIC_IPV4} \'{"host": "%H", "ipv4_addr": ${COREOS_PUBLIC_IPV4}, "port": %i}\' --ttl 30; \ else \ etcdctl rm /services/apache/${COREOS_PUBLIC_IPV4}; \ fi; \ sleep 20; \ done' # 結束的命令 ExecStop=/usr/bin/etcdctl rm /services/apache/${COREOS_PUBLIC_IPV4} [X-Fleet] # 服務運行在與它所監控的Apache服務相同的主機上 ConditionMachineOf=apache@%i.service
這個文件應該被命名為apache-secretary@%i.service并存放到/etc/systemd/system/目錄中。如果讀完這個文件依然覺得這個秘書長得有點抽象,下面就具體分析一下這個Unit文件內容。
首先,不難看出,它也是一個Unit模板(使用了 %i 占位符)。
然后,它的Unit段使用了一個特別的限定:BindsTo=apache@%i.service。這個關鍵字表明,這個秘書服務需要隨著它監控的Apache應用服務一起被停止和重啟。這一點很必要,否則這個秘書就無法正確地匯報所監控的服務狀態了。
接下來,Service段中的ExecStart和ExecStop的內容是需要重點說明的地方,待稍后慢慢道來。
最后,X-Fleet段指明這個服務要與對應的服務始終保持在同一個主機節點上,這使得秘書服務能夠直接就近獲得服務狀態,而不用去查找當前被監控服務運行的節點。
這樣看來,最麻煩的地方無非就是上面這段亂糟糟的ExecStart命令了。先大略地打量一下,這個地方和ExecStop里面都使用了etcdctl,它們應該是比較關鍵的內容,先提出來看看。
在ExecStart中的這個命令,是往 Etcd 數據服務的/services/apache/目錄下寫入了一個以當前Apache服務所在IP地址命名的鍵,而鍵的內容就是秘書所記錄的服務信息,包括服務所在的主機名、公網IP地址和監聽的公網網卡端口號。最后的 TTL 設置是其中的精彩之處,它確保了當整個服務失效或被遷移到其他節點上時,這條記錄會在30秒內被清除。
etcdctl set /services/apache/${COREOS_PUBLIC_IPV4} \'{"host": "%H", "ipv4_addr": ${COREOS_PUBLIC_IPV4}, "port": %i}\' --ttl 30;
下面這個命令在ExecStart和ExecStop中各出現了一次,它的參數比較清晰,就是移除/services/apache/下面剛才寫入的那個鍵。
etcdctl rm /services/apache/${COREOS_PUBLIC_IPV4};
在ExecStart命令的最外層,使用一個“while true”循環將整個執行的命令包裹起來,因此除非外部因素結束這個秘書服務,否則監控的任務就不會停止。在服務本身出現故障失聯時,它將會跟隨著被監控服務一起停止、一起被遷移,然后又一起在新節點上繼續提供服務。
再往下看,有一個“sleep 20”的行,也就是說,在被監控服務運行正常的情況下,每隔20秒秘書就會刷新一次服務狀態信息(雖然在這個例子里只有IP地址、端口這些比較固定的信息,但在實際情況中所監控的信息可能不止這些)。這也使得在正常情況下,Etcd中的數據永遠不會由于TTL的超時而被清除。
至此,這個ExecStart的意思也就大致清晰了。其中還沒有被提到的那行curl命令,以及“etcdctl set”中需要記錄的內容就是實際監控服務需要定制的部分。這個例子里的Apache其實是監控的最簡單情況。
簡單歸簡單,但稍加思考就不難發現一個問題,要求用戶自己管理啟動兩個有依賴順序的服務?顯然這里有些不合理的地方。
6.1.2.3 關聯秘書服務
剛剛在寫Apache應用服務時,由于還沒有秘書的存在,我們只考慮了應用服務自己的啟動依賴。然而,一個合理的需求是,當應用服務啟動時,相應的秘書服務也應該自動啟動。還記得上面apache-secretary@%i.service文件中的BindsTo那行嗎?事實上,單有這個配置只能夠實現這個服務隨著相應的Apache服務停止和重啟,當Apache啟動時,由于秘書服務的進程還不存在,這里的 BindsTo 是不會生效的。因此,還需要向Apache服務中加上秘書服務的依賴,以及指定啟動順序。
# Requirements Requires=etcd.service Requires=docker.service Requires=apache-secretary@%i.service # 增加這行,指明依賴服務名稱 # Dependency ordering After=etcd.service After=docker.service Before=apache-secretary@%i.service # 增加這行,指定依賴啟動順序
現在只要服務自身啟動了,Fleet就會在同一個節點上為它分配一個獨立的秘書服務。
6.1.2.4 啟動服務
在第4章中,已經介紹過在Fleet中啟動Unit模板的方法,只需要在Fleet命令中的模板名參數的@符號后面加上相應的標識字符串就可以運行服務的實例了。由于我們在模板中使用了這個標識字符串作為服務在容器外暴露的端口號(這是一種很常用的技巧),因此這個字符串應該使用一個小于65525的數字表示。例如:
$ fleetctl submit apache@.service apache-secretary@.service $ fleetctl load apache@8080.service apache-secretary@8080.service $ fleetctl start apache@8080.service
最后一步執行“fleetctl start”的時候只需要指定一個Apache服務就足夠了,它的監控秘書服務會在Apache服務之后自動被Fleet觸發。
6.1.2.5 服務狀態
啟動完成以后,可以用fleetctl檢查一下集群中運行的所有服務。
$ fleetctl list-units | grep apache UNIT MACHINE ACTIVE SUB apache-secretary@8080.service 14ffe4c3… /172.31.14.97 active running apache@8080.service 14ffe4c3… /172.31.14.97 active running
還可以再注冊一個Apache服務到集群中,它會自動運行到不同的節點上(由于apache@.service文件中的Conflicts配置)。
$ fleetctl load apache@8081.service apache-secretary@8081.service $ fleetctl start apache@8081.service $ fleetctl list-units | grep apache UNIT MACHINE ACTIVE SUB apache-secretary@8081.service 1af37f7c... /172.31.14.95 active running apache-secretary@8080.service 14ffe4c3... /172.31.14.97 active running apache@8081.service 1af37f7c... /172.31.14.95 active running apache@8080.service 14ffe4c3... /172.31.14.97 active running
現在,在集群中的任意一個節點上,通過Etcd都可以輕松地獲得集群中每一個Apache服務的信息。
$ etcdctl ls /services/apache/ /services/apache/172.31.14.97 /services/apache/172.31.14.95 $ etcdctl get /services/apache/172.31.14.95 { "host": "core01", "ipv4_addr": "172.31.14.95", "port": "8081" }
6.1.2.6 模擬故障情景
最后我們快速地模擬一下這種情況,即有一部分應用服務掛了。
將一個服務停止(或者直接殺死,不過那樣它會到別的節點上自動重啟,可能觀察不到效果),按照上面的設計,記錄在 Etcd 中的信息應該在30秒后由于TTL超時而被刪除。
$ fleetctl stop apache@8080.service $ fleetctl list-units UNIT MACHINE ACTIVE SUB apache-secretary@8080.service 14ffe4c3... /172.31.14.97 inactive dead apache-secretary@8081.service 1af37f7c... /172.31.14.95 active running apache@8080.service 14ffe4c3... /172.31.14.97 inactive dead apache@8081.service 1af37f7c... /172.31.14.95 active running
現在,再來查詢一次服務狀態,可以看到記錄的Apache服務只剩下172.31.14.95節點的8081端口的那個了。
$ etcdctl ls /services/apache/ /services/apache/172.31.14.95 $ etcdctl get /services/apache/172.31.14.97 Error: 100: Key not found (/services/apache/172.31.14.97)
6.1.3 案例延伸
6.1.3.1 為模板實例建立鏈接
在做這個案例的測試時,如果手工啟動每個服務和相應的秘書服務還是相對比較麻煩的。由于Fleet啟動使用了模板服務必須明確地指定標識字符串,因此總是需要在啟動命令參數里面明確地寫出每個服務的名稱和標識,并且管理起來并不十分方便。
相比之下,由于非模板的Unit文件每個都是獨立的實體,可以使用通配符(*)來一次啟動在同一個目錄下的多個Unit服務。因為每個服務對應了磁盤上一個真實的Unit文件,那么可不可以使用鏈接文件來給同一個模板創建多個帶標識字符串的別名來代替呢?
下面我們來做個測試,為每個服務模板創建三個以帶標識字符串的完整服務實例名稱命名的鏈接文件。
$ ln -s templates/apache@.service instances/apache@8082.service $ ln -s templates/apache@.service instances/apache@8083.service $ ln -s templates/apache@.service instances/apache@8084.service $ ln -s templates/apache-secretary@.service instances/apache-secretary@8082.service $ ln -s templates/apache-secretary@.service instances/apache-secretary@8083.service $ ln -s templates/apache-secretary@.service instances/apache-secretary@8084.service
然后用通配符啟動這個目錄下的所有鏈接文件。
$ fleetctl start instances/* Unit apache@8082.service launched on 14ffe4c3... /172.31.14.97 Unit apache@8083.service launched on 1af37f7c... /172.31.14.95 Unit apache@8084.service launched on 9e389e93... /172.31.14.93 Unit apache-secretary@8082.service launched on 14ffe4c3... /172.31.14.97 Unit apache-secretary@8083.service launched on 1af37f7c... /172.31.14.95 Unit apache-secretary@8084.service launched on 9e389e93... /172.31.14.93
可以看到,Fleet欣然接受了這些通過鏈接創建的替身文件,并正確地將鏈接文件的標識字符串用于配置相應的應用服務啟動。實際上,這種給每個要啟動的具體應用實例創建一個鏈接到真實模板的方式恰恰是CoreOS官方推薦的使用模板方式,它將原本僅僅體現在啟動參數里面的服務標識固化為一個個隨時可見、可管理的鏈接,為管理服務提供了很大的便利。
6.1.3.2 數據的處理與展示
在這個案例的最后,所有的監控數據都還是以鍵值對的形式保持在Etcd的配置存儲空間中的。這樣的數據雖然得以持久和方便地查詢,但卻顯得既不直觀也不友好。
在實際的應用中,一般會對這類有意義的數據進行進一步的處理和展示。例如計算實時的集群服務健康狀況,在特定條件下觸發報警,或者將數據以圖標的形式更清晰地提供給使用者。這些工作雖然沒有在這個案例中進行詳細討論,但卻是評判一套數據監控方案實用性的重要方面。CoreOS對數據的處理與展示并沒有提供什么特別的工具,所有的工作都需要由用戶通過Etcd API額外編碼實現。
我們在第5章中已經介紹了Etcd API的使用,基于這些RESTful的接口實現用戶自己的Web頁面的監控工具并不是什么難事,由于具體界面設計與實際應用的業務場景十分相關,通用性不高,這里不做詳述。
6.1.4 案例總結
在這個案例中,雖然我們僅僅設計了一個單純的HTTP服務的監控,然而隨著需要監控的服務數量的增加和集群中服務的流動(節點間遷移)增多,案例中監控策略的復雜度并不會顯著地增加,算得上是因為簡單所以可靠。分布在各個節點上的秘書服務能夠很好地適應被監控服務的動態變化,并且對被監控服務具有很低的入侵性(不會限制服務運行在哪里),因此當應用場景變得復雜化、分布化時,其優勢會比傳統的集中式管理策略體現得更加明顯。從本質上說,這是在用分布式的思想來解決分布式的問題,因此顯得得心應手,駕輕就熟。
書籍介紹
本書是一本介紹CoreOS操作系統使用和周邊技術的入門實踐類書籍。本書內容分為三個主要部分。第一部分 (第1章)主要介紹CoreOS的基本概念和系統的安裝,為后續各個組件的使用做好鋪墊工作;第二部分(第2~6章)主要介紹CoreOS中*核心的內置 組件,通過這些組件,使用者能夠完成大部分CoreOS的日常操作和開發任務;第三部分(第7~9章)主要針對CoreOS中一些比較進階的話題以及組件 進行更具體的講解,并介紹一些CoreOS使用技巧。
來自: http://www.infoq.com/cn/articles/coreos-distributed-service-monitoring