天天看點

基于containerpilot的服務注冊與發現微服務服務的注冊與發現參考

所謂分久必合合久必分,分治可以解決all in one的問題,但是更多的問題因為隔離而産生,為了解決這些問題又會有相應的工具産生。作為已經不算火熱的微服務概念,落地解決方案也漸漸成熟和成型,為了說明containerpilot的适用場景,首先簡單說明白幾個基本概念。

不管是前端還是後端服務,項目開始的時候追求短平快,所有的代碼會放在一個代碼庫中,基于同一個架構和語言開發,頂多根據檔案或者檔案夾做一下子產品化。前端和後端服務做一下分離(或者也沒有做),放在一個SLB後面,作為無狀态的應用伺服器也能實作基本的水準擴容。然而,随着業務的快速發展,這樣的簡單結構會逐漸變成障礙。微服務的實踐其實應該由來已久,隻是最近幾年被炒的火熱,而微服務的核心思想還是設計模式中經典的思想:單一責任。基于這個思想,微服務的其他便利才有了落腳點。

基于containerpilot的服務注冊與發現微服務服務的注冊與發現參考

既然我們的目标是把一個服務打散,也就是從單程序要放到多程序中去,就需要涉及到程序間通信。經典的作業系統理論理論裡面提到的IPC方法有: 管道,有名管道(無父子關系限制),信号量(計數鎖),消息隊列,信号,共享記憶體,套接字。這些方法大多是基于單機器中程序通信的方式。我們利用微服務在部署的時候另外一個原則: 消除單點,是以肯定是需要能夠跨機器程序的方式通信了,那套接字就是重要的選擇,實際應用中因為開發更多在應用層實作,是以Restful和RPC的方式是常用的手段。這裡就存在一個問題,服務因為彼此隔離,彼此通信的時候怎麼知道彼此的存在,同時在需要調用依賴的服務時,如何能夠調用到合适的機器(這裡指的是機器的IP和端口号),這就是服務的注冊與發現問題了。同時服務的注冊和發現也存在兩種模式: 用戶端模式和服務端模式。

基于containerpilot的服務注冊與發現微服務服務的注冊與發現參考
基于containerpilot的服務注冊與發現微服務服務的注冊與發現參考

可以看到這裡的差别主要在于是否将注冊中心暴露出去,我們後面的讨論基于用戶端模式。

可能你直接看上面的架構,感覺不到這個注冊中心的好處,讓我們再放幾張圖,看看我們原來是怎麼做的

基于containerpilot的服務注冊與發現微服務服務的注冊與發現參考
基于containerpilot的服務注冊與發現微服務服務的注冊與發現參考
基于containerpilot的服務注冊與發現微服務服務的注冊與發現參考

當然實際場景有可能比這還要複雜,可能中間用的還有F5或者haproxy,這裡隻是個示意。剛開始簡單實作就檔個nginx,對upstream有個基本的healthcheck,通過反向代理就請求到後端服務就可以了。當通路量上升,單個nginx扛不住就再在前端檔個LVS,通過四層協定轉發IP包,第二層對nginx分組講負載進一步分流。但是這樣LVS變成了單點,而且配置變得更加複雜。最後又不得不把這個單點消除,由用戶端智能判斷合适的分組,請求後端的服務。這裡nginx在所有情況下既擔當了負載均衡的角色,又擔當了服務發現的角色(如果服務分組的時候根據通路量來分,會把通路量差不多的放在一個分組,通過nginx重寫路由映射到多個後端服務)。這裡很明顯配置都是需要人工幹預的,任何一台後端實際服務的機器IP變化都需要運維手動維護,繁瑣而且容易出錯。這樣大家就可以感受到上面注冊中心的好處了把,所有的服務清單可以集中在一個地方管理。

所有服務都集中存在一個地方管理,現在的主流方案主要是這三個

因為隻用過 <code>consul</code>,下面都是以它為依據展開,但是作為服務注冊中心的概念上是相似的.作為注冊中心基本的功能就是kv的存儲,服務的配置資訊還有應用的配置資訊都可以放在這裡,<code>consul</code> 提供了 <code>agent</code> 運作的模式,也就是 <code>client</code> 和 <code>server</code> 模式,看下圖

基于containerpilot的服務注冊與發現微服務服務的注冊與發現參考

對應的,因為是使用查詢注冊中心的方式來做服務發現(其實就是用戶端的服務發現 smart client),需要用戶端通過調用 <code>consul</code> 的 restful API 來查詢,雖然可以通過在每個服務執行個體上啟動一個在 <code>client</code> 模式下的 <code>agent</code> 緩解叢集的并發通路壓力,但是還是需要用戶端主動查詢的,有沒有更好的方式呢?

啟動 <code>consul</code> 的agent或者直接連接配接 <code>consul</code> 的cluster,将目前服務注冊進去,第一次生成依賴服務的位址 <code>services.json</code>

在 <code>containerpilot</code> 可以跟consul的agent或者cluster通信之後,擷取依賴服務的清單,通過 <code>consul-template</code> 将服務的資訊渲染到預定義好的模闆檔案中

watch在consul中依賴的服務,當檢測到更新的時候給服務發送 <code>SIGHUP</code> 的信号提示服務重新讀取依賴服務的最新位址 <code>services.json</code>

通過調用 <code>ping</code> 指令對服務進行健康檢查,在服務不健康甚至是容器異常退出的時候會将 <code>consul</code> 中服務的狀态标記為不可用(不會解注冊)

一個服務跟 <code>containerpilot</code> 內建的結構是這樣的

基于containerpilot的服務注冊與發現微服務服務的注冊與發現參考

首先明确兩個概念:

被托管服務,即運作在本容器内的主要程序,對外提供某種功能。

依賴服務,即被托管服務所依賴的服務,通常運作在其它容器内。

containerpilot 基于 consul 實作了如下兩個功能:

将被托管服務注冊到 consul 内,并維持健康檢查心跳,在容器停止時自動解注冊該服務。

擷取依賴服務的執行個體位址。

這兩個功能即服務的注冊和發現,可同時使用,也可隻使用其中任意一個。

使用服務注冊功能時,或者使用服務發現功能且需要接收依賴服務執行個體更新信号時,被托管服務需由 containerpilot 管理,即最終服務管理結構如下:

隻使用服務發現功能且不需要接收依賴服務執行個體更新信号時,被托管服務可直接由 runit 管理,即最終服務管理結構如下:

使用 containerpilot 請按需設定以下環境變量:

<code>CONSUL_ADDR</code> :容器内可使用該值調用 consul API ,預設值為 <code>127.0.0.1:8500</code> 。使用預設值時,會在容器内部起一個 consul client ,并根據 <code>CONSUL_JOIN_ADDR</code> 和 <code>CONSUL_ADVERTISE</code> 加入已有叢集。

<code>CONSUL_JOIN_ADDR</code> :已有 consul 叢集中任意一個節點的 IP 位址,consul client 加入叢集時使用。

<code>CONSUL_ADVERTISE</code> :consul client 加入叢集時使用哪個 IP 通信,需用 k8s pod IP 或 container host IP。

<code>SERVICE_NAME</code> :注冊到 consul 中被托管服務名稱,ID 會自動設定為 <code>${SERVICE_NAME}-${CONTAINER_HOSTNAME}</code> 。可以從外部傳入或内部寫死。此外,如果存在環境變量 <code>CONSUL_PREFIX</code> ,其值将會被拼到服務名稱和 ID 前面。預設值為 <code>main</code> 。

<code>SERVICE_PORT</code> :注冊到 consul 中被托管服務端口。不傳則不注冊服務到 consul 。

<code>SERVICE_COMMAND</code> :啟動被托管服務的指令和參數,不傳遞則不啟動。

<code>SERVICE_INTERFACE</code> :注冊到 consul 中被托管服務位址,可選網卡名如 <code>eth0</code> 或靜态位址 <code>static:192.168.1.100</code> ,預設值為 <code>eth0</code> 。

<code>SERVICE_SIGNAL_CHANGED</code> :當依賴服務執行個體發生變化時,是否給被托管服務發送 <code>SIGHUP</code> ,傳值代表發送。

被托管服務如果依賴于其他服務,需在項目中添加 <code>serviceDependencies.json</code> 檔案,建構 docker 鏡像時把這個檔案複制到 WORKDIR 下,示例如下:

表示 <code>service1</code> 依賴于 <code>service2</code> 和 <code>service3</code> ,而 <code>service2</code> 依賴于 <code>service4</code> ,即 key 為被托管的服務名,value 為該服務的依賴清單。

對于不使用服務注冊,隻需要服務發現的情況,可以使用 <code>SERVICE_NAME</code> 的預設值 <code>main</code> 作為 key,示例如下:

containerpilot 會根據 <code>SERVICE_NAME</code> 選取需要監控的依賴服務,比如設定 <code>SERVICE_NAME</code> 為 <code>service1</code> ,則将監控 <code>service2</code> 和 <code>service3</code>,當依賴服務執行個體發生變化時,會更新 <code>/etc/containerpilot/services.json</code> ,生成的内容示例如下:

其中 key 為依賴的服務名,value 為該服務的執行個體位址數組。

被托管服務要擷取該檔案的更新可通過如下方式:

golang 、java 這種 long running 的,需要托管于 containerpilot ,然後設定環境變量 <code>SERVICES_SIGNAL_CHANGED</code> ,然後代碼中處理 <code>SIGHUP</code> 信号,收到信号時重新讀取該檔案。

php 這種不好處理信号的,可以每次處理請求時都重新讀取該檔案。

containerpilot 應作為 runit service 啟動,示例 <code>run</code> 腳本如下:

<a href="https://github.com/joyent/containerpilot">https://github.com/joyent/containerpilot</a>

<a href="https://www.joyent.com/blog/applications-on-autopilot">https://www.joyent.com/blog/applications-on-autopilot</a>

<a href="http://www.jianshu.com/p/c144a577f3d1">http://www.jianshu.com/p/c144a577f3d1</a>

繼續閱讀