閱讀53 返回首頁    go 阿裏雲 go 技術社區[雲棲]


基於containerpilot的服務注冊與發現

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

微服務

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

image

更多內容可以參考這裏: 微服務架構的優勢與不足

服務的注冊與發現

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

image

image

可以看到這裏的差別主要在於是否將注冊中心暴露出去,我們後麵的討論基於客戶端模式

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

image

image

image

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

服務注冊中心

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

  • consul: 提供了一站式的解決方案,健康檢查,DNS解析一應俱全,甚至提供了個簡單的UI工具
  • etcd: 僅提供基本的kv分布式存儲
  • zookeeper: 一些老的係統還在使用,配置管理都相對麻煩

因為隻用過 consul,下麵都是以它為依據展開,但是作為服務注冊中心的概念上是相似的.作為注冊中心基本的功能就是kv的存儲,服務的配置信息還有應用的配置信息都可以放在這裏,consul 提供了 agent 運行的模式,也就是 clientserver 模式,看下圖

image

server 模式的 agent 互相通信構成一個集群,主要負責參與選出集群的主節點和實現信息同步, client 模式的 agent 隻是同步集群的數據,或者在自己接受到數據變更請求的時候將變化廣播出去.server 模式的 agent 組成的集群可以放在多個數據中心來保證高可用,通過 gossip 協議實現分布式的數據一致性.這裏從簡化概念考慮,你可以把 consul 的整個集群當成一個"哈希表",服務都是注冊到這個"哈希表"中,然後其他的服務通過查找這個"哈希表"來獲取實際調用的下遊服務.實際情況並不是這麼簡單,因為不是本文的重點,這裏略過了,有興趣的同學可以自己看下官方文檔

Tip: 基於應用的配置信息提供了consul-template實現動態的配置更新,結合服務注冊在一起使用可以實現自動的依賴服務配置信息更新.本來這樣是很酸爽,隻是這樣有個缺點,也是在實踐中發現的,就是配置的變更管理如果用consul來管就沒有辦法track變更的曆史記錄了,在加上SRE限製了 consul UI 的訪問權限,很多時候上線因為開發不能直接通過UI修改,這個便利性就大打折扣了.經常會出現有新的配置沒有即使同步到生產環境的情況,人工管理的成本很高,所以在係統中嚐試使用後就退化成普通的文件配置(也可以track變更,因為有git的提交記錄).

手工注冊和查詢的痛苦

因為上麵提到的 consul 基本的功能是提供了服務信息的存儲,通過它的resulful API可以很容易的將一個服務注冊到 consul 裏麵,同時 consul 也會自動對他執行健康檢查,在服務掛掉的時候從可用服務列表中可以過濾掉(一般一個服務會對應一個 SLB 和後麵一堆的實際應用服務器)有問題的機器,保證服務的高可用.同時我們也需要在對服務進程接受到特殊信號量,比如 SIGKILL 的時候將服務從 consul 中移除,這就也需要調用API實現.雖然可以通過將注冊和解注冊的行為通過公共庫的方式在一個地方維護,但畢竟不是那麼優雅.

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

獨立出注冊和查詢為獨立服務

終於主角登場了,containerpilot是大名鼎鼎的 joyent 公司(最早開發nodejs的公司)開源出來的一款針對容器內服務做自動注冊和發現的好工具.之前我們提到,原始的服務注冊和發現依賴於程序通過restful API的調用實現,這樣就需要在代碼中實現調用的邏輯.現在用了 containerpilot 再加上一些封裝之後,就可以實現程序對於服務注冊中心的無感知.具體情況是這樣的,所有 containerpilot 相關的配置放在一個固定的目錄下麵,例如 /etc/containerpilot,在容器啟動的時候會執行腳本從依賴關係文件(例如 serviceDependencies.json)中讀取出下遊服務列表,刪除socker文件(例如 /var/run/containerpilot.socket),執行 containerpilot 的可執行文件.containerpilot 讀取配置文件 config.json5,通過模板語法,在不同的條件下根據環境變量激活不同的job.這裏麵最重要的部分是這幾個:

  • 啟動 consul 的agent或者直接連接 consul 的cluster,將當前服務注冊進去,第一次生成依賴服務的地址 services.json
  • containerpilot 可以跟consul的agent或者cluster通信之後,獲取依賴服務的列表,通過 consul-template 將服務的信息渲染到預定義好的模板文件中
  • watch在consul中依賴的服務,當檢測到更新的時候給服務發送 SIGHUP 的信號提示服務重新讀取依賴服務的最新地址 services.json
  • 通過調用 ping 命令對服務進行健康檢查,在服務不健康甚至是容器異常退出的時候會將 consul 中服務的狀態標記為不可用(不會解注冊)

一個服務跟 containerpilot 集成的結構是這樣的

untitled diagram 4

使用 containerpilot

概要

首先明確兩個概念:

  • 被托管服務,即運行在本容器內的主要進程,對外提供某種功能。
  • 依賴服務,即被托管服務所依賴的服務,通常運行在其它容器內。

containerpilot 基於 consul 實現了如下兩個功能:

  • 將被托管服務注冊到 consul 內,並維持健康檢查心跳,在容器停止時自動解注冊該服務。
  • 獲取依賴服務的實例地址。

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

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

- runit
    - containerpilot
        - your service

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

- runit
    - containerpilot
    - your service

環境變量

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

  • CONSUL_ADDR :容器內可使用該值調用 consul API ,默認值為 127.0.0.1:8500 。使用默認值時,會在容器內部起一個 consul client ,並根據 CONSUL_JOIN_ADDRCONSUL_ADVERTISE 加入已有集群。
  • CONSUL_JOIN_ADDR :已有 consul 集群中任意一個節點的 IP 地址,consul client 加入集群時使用。
  • CONSUL_ADVERTISE :consul client 加入集群時使用哪個 IP 通信,需用 k8s pod IP 或 container host IP。
  • SERVICE_NAME :注冊到 consul 中被托管服務名稱,ID 會自動設置為 ${SERVICE_NAME}-${CONTAINER_HOSTNAME} 。可以從外部傳入或內部寫死。此外,如果存在環境變量 CONSUL_PREFIX ,其值將會被拚到服務名稱和 ID 前麵。默認值為 main
  • SERVICE_PORT :注冊到 consul 中被托管服務端口。不傳則不注冊服務到 consul 。
  • SERVICE_COMMAND :啟動被托管服務的命令和參數,不傳遞則不啟動。
  • SERVICE_INTERFACE :注冊到 consul 中被托管服務地址,可選網卡名如 eth0 或靜態地址 static:192.168.1.100 ,默認值為 eth0
  • SERVICE_SIGNAL_CHANGED :當依賴服務實例發生變化時,是否給被托管服務發送 SIGHUP ,傳值代表發送。

獲取依賴服務的實例地址

被托管服務如果依賴於其他服務,需在項目中添加 serviceDependencies.json 文件,構建 docker 鏡像時把這個文件複製到 WORKDIR 下,示例如下:

{
  "service1": ["service2", "service3"],
  "service2": ["service4"]
}

表示 service1 依賴於 service2service3 ,而 service2 依賴於 service4 ,即 key 為被托管的服務名,value 為該服務的依賴列表。

對於不使用服務注冊,隻需要服務發現的情況,可以使用 SERVICE_NAME 的默認值 main 作為 key,示例如下:

{
  "main": ["service2", "service3"],
}

containerpilot 會根據 SERVICE_NAME 選取需要監控的依賴服務,比如設置 SERVICE_NAMEservice1 ,則將監控 service2service3,當依賴服務實例發生變化時,會更新 /etc/containerpilot/services.json ,生成的內容示例如下:

{
  "service2": [
    "1.1.1.1:80",
    "2.2.2.2:80"
  ],
  "service3": [
    "3.3.3.3:80",
    "4.4.4.4:80"
  ]
}

其中 key 為依賴的服務名,value 為該服務的實例地址數組。

被托管服務要獲取該文件的更新可通過如下方式:

  • golang 、java 這種 long running 的,需要托管於 containerpilot ,然後設置環境變量 SERVICES_SIGNAL_CHANGED ,然後代碼中處理 SIGHUP 信號,收到信號時重新讀取該文件。
  • php 這種不好處理信號的,可以每次處理請求時都重新讀取該文件。

啟動 containerpilot

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

#!/bin/bash

# 假設外部已傳入 CONSUL_JOIN_ADDR 環境變量
# 假設運行在 k8s 上,則 CONSUL_ADVERTISE 使用默認值即可
export SERVICE_NAME="test"
export SERVICE_PORT=80
export SERVICE_COMMAND="myservice args"

# containerpilot 默認使用健康檢查命令 ./ping ,即需要把 ping 文件放在  WORKDIR 下,並在啟動 containerpilot 前切換到 WORKDIR
# WORKDIR 默認為 /
cd /

# 啟動 containerpilot
exec /etc/containerpilot/containerpilot.sh

參考

最後更新:2017-11-04 15:06:07

  上一篇:go  php獲取今日頭條視頻地址並插入織夢cms數據庫
  下一篇:go  2017雲棲大會參會感想