基於 Consul 的 Docker Swarm 服務發現


Docker 是一種新型的虛擬化技術,它的目標在於實現輕量級操作系統的虛擬化。相比傳統的虛擬化方案,Docker 虛擬化技術有一些很明顯的優勢:啟動容器的速度明顯快於傳統虛擬化技術,同時創建一台虛擬機占用的資源也要遠遠小於傳統的虛擬技術。Swarm 是 Docker 集群化的技術,而 Swarm 集群化 Docker 離不開服務發現。Consul 能夠解決 Swarm 的服務發現問題。本文將介紹 Swarm 如何使用 Consul 作為服務發現。

Docker Swarm 服務發現

Docker 集群化可以通過 Swarm 來實現。Swarm 對 Docker 集群中節點的動態加入和退出的感知叫做服務發現。Docker Swarm 支持多種服務發現方式,下面進行詳細介紹。

服務發現方式

Docker Swarm 支持的服務發現方式主要有以下幾種:

1. Token

Token 發現策略使用 ID 來唯一標識集群,每一個節點通過指定集群 ID 來標識加入的集群。所以首先使用 Swarm 產生一個集群 ID,然后每一個節點通過 join Cluster-ID 動態加入這個集群。

2. Nodes

Nodes 發現策略依賴於集群節點,我們在集群 manage 節點執行加入集群指令時,就需要指定要加入集群的其他節點。所以集群成員需要預先定義。

3. File

首先將加入集群的節點存儲在一個文件中,我們在集群 manage 節點執行加入集群指令時,指定存儲集群中所有節點的文件。所以集群成員也是需要預先定義。

4. 分布式 key/value 存儲

Swarm 使用第三方軟件的 key/value 形式存儲節點信息,分布式 key-value 存儲不僅能夠實現 Swarm 節點的動態加入,同時提供了很多其他的功能,直觀顯示集群中的每個節點以及每個節點提供的服務等。

Swarm 通過 key/value 存儲實現的服務發現有三種,接下來,分別進行介紹。

分布式 key/value 存儲

Swarm 通過分布式 key/value 存儲實現的服務發現方式有以下三種:

1. ZooKeeper

ZooKeeper 是比較成熟的分布式 key/value 存儲,但是使用復雜,入門門檻較高,需要借助第三方軟件來發送 key-value 數據給 ZooKeeper 完成數據存儲。

2. Etcd

Etcd 部署使用簡單,提供了可靠的數據存儲,但是需要借助第三方軟件,手動將 key/value 數據發送給 Etcd 來存儲。

3. Consul

Consul 提供了可靠的數據存儲,並且提供了服務發現框架,不需要借助第三方軟件。

本文主要介紹第三方服務發現 Consul 在 Docker Swarm 中的的使用,接下來對 Consul 進行介紹。

Consul 簡介

Consul 提供了分布式環境中服務的注冊和發現,支持分布式,高可用,多數據中心。

Consul 重要概念

Consul 中的重要概念:

1. Agent:Consul 集群中每個成員運行的守護進程,以 Client 或者 Server 的模式存在。

2. Client:發送 RPC 請求給服務器的 Agent。

3. Server:是一個 Agent,負責響應 RPC 查詢,維護集群狀態,參與 Raft 仲裁等。

Server 有三種存在狀態。接下來,分別介紹這三種狀態以及它們之間的關系。

Server 存在狀態

Server 的三種狀態形式:Leader,Follower,Candidate。

1. Candidate:Server 參與 Raft 仲裁,競選期間所有服務器的狀態。

2. Leader:Server 參與 Raft 仲裁,競選結束獲勝服務器的狀態。

3. Follower:Server 參與 Raft 仲裁,競選結束未獲勝服務器的狀態。

三種狀態之間關系如下:

Consul 集群 Server 節點中要有一個 Leader。Leader 負責維護集群中所有節點的狀態。當集群還沒有 Leader 或者 Leader 出現故障時候,所有的 Server 運行 Raft 算法,開始競選。競選期間所有 Server 的狀態稱為 Candidate。最終集群中的 Server 經過競選,只有一台 Server 獲勝,競選結束。這個時候集群中獲勝的 Server 稱為 Leader,其他的 Server 稱為 Follower。

Consul 架構

為了從整體上對 Consul 有一個了解,下面以一個數據中心的 Consul 部署結構圖來對 Consul 進行說明。一個數據中心的 Consul 集群由客戶端和服務器組成,服務器以 Leader 和 Follower 兩種角色存在。Client 會定時向 Server 發送 RPC 請求,匯報自己的運行狀態。Leader 收到 Client 的 RPC 請求,會給 client 發送 RPC 響應,同時存儲 Client 的狀態等信息。Follower 收到 Client 的 RPC 請求,會轉發給 Leader。Leader 存儲該 Client 的信息。

Consul 的架構如下圖 1 所示。

圖 1. Consul 架構圖

 

對 Swarm 的服務發現策略和 Consul 有了整體了解后,下面具體介紹在代碼實現中,Swarm 如何使用 Consul 作為服務發現。

Swarm Consul 服務發現源碼解析

在 GitHub 官網可以下載Swarm 源碼。本文以 Swarm 1.2.4 為例,通過介紹 swarm join 的流程,來說明 Swarm 使用 Consul 的服務發現過程。

首先,我們簡單說明 Swarm 源碼文件夾結構以及每個文件的功能。

Swarm 源碼文件夾結構

Swarm 源碼的文件夾結構如下表 1 所示。

表 1. Swarm 源碼的文件夾結構

Swarm join 是 Swarm 節點加入 Swarm 集群的命令,並且節點在執行 swarm join 命令時,可以指定 Swarm 的服務發現方式。在對 Swarm 源碼的文件結構有了整體了解后,接下來我們通過分析 swarm join 的源碼,理解 Swarm 使用 Consul 作為服務發現的過程。

Swarm join 源碼解析

Swarm 支持一定的命令行操作。當客戶端輸入 swarm join 命令時,Swarm 會首先在自己支持的命令集合中找到該命令,同時找到該命令對應的處理函數。然后執行 swarm join 的處理函數。在該處理函數中,swarm 會根據命令行輸入的服務發現策略 Consul,初始化一個 Consul 客戶端,而后向 Consul 服務器發送注冊消息,完成客戶端加入集群的操作。

Swarm 所有操作的入口函數是 main 函數,如清單 1 所示。

清單 1. Swarm 入口函數
1
2
3
func main() {
   cli.Run()
}

main 函數的功能是啟動命令行應用程序。命令行應用程序在啟動的時候主要做三件事:1、創建命令行應用程序。2、初始化 Swarm 支持的命令。3、運行命令行應用程序。其中初始化 Swarm 支持的命令,定義了每個版本的 Swarm 支持的命令行集合,以及每個命令對應的處理函數。清單 2 是命令行程序啟動時的主要代碼。

清單 2. 創建命令行應用程序
1
2
3
4
5
6
7
8
9
10
func Run() {
     //1、創建 go 的命令行應用 app
     app := cli.NewApp()
     //2、初始化 app 的命令行參數
     app.Commands = commands
     //3、運行 app
     if err := app.Run(os.Args); err != nil {
         log.Fatal(err)
     }
}

以上代碼清單位於文件 cli/cli.go。下面對以上代碼的主要功能進行詳細說明。

  1. 創建命令行應用程序

    該功能是利用第三方庫 github.com/codegangsta/cli 來創建 Go 的命令行應用程序。在 Swarm 的第三方依賴文件夾 vendor/github.com/codegangsta/cli 可以找到創建 Go 應用程序的依賴文件。

  2. 初始化 Swarm 支持的命令

    這個部分是很關鍵的一部分,該版本的 Swarm 支持的所有命令以及每個命令對應的處理函數等信息都在此處完成初始化。Swarm 使用數組 commands 完成所有命令的初始化。commands 的定義位於文件 cli\commands.go,關鍵代碼如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var (
     commands = []cli.Command{
         {
             Name:      "create",
             ShortName: "c",
             Usage:     "Create a cluster",
             Action:    create,
         },
         {
             Name:      "list",
             ShortName: "l",
             Usage:     "List nodes in a cluster",
            Flags:     []cli.Flag{flTimeout, flDiscoveryOpt},
             Action:    list,
         },
         {
             Name:      "manage",
             ShortName: "m",
             Usage:     "Manage a docker cluster",
             //Flags 定義...
             Action: manage,
         },
         {
             Name:      "join",
             ShortName: "j",
             Usage:     "Join a docker cluster",
             //Flags 定義...
             Action:    join,
             },
         }
)

在上面的 commands 的定義中,Name 字段指定了 Swarm 支持的每個命令,Action 字段指定了每個命令的處理函數。下面我們把 swarm join 命令和上面的定義結合起來,具體說明下當在節點的終端輸入 join 命令時,swarm 代碼是如何執行的。

join 命令在終端的使用如下:

1
2
docker run -d -p 2376:2375 --name swarm-slave-01 swarm join
-addr 192.168.1.28:2376 consul://192.168.1.28:8500

當我們在終端輸入以上命令,Swarm 會首先解析命令,解析出用戶要執行的命令是 join 命令。然后在上面 commands 的定義中找到 join 命令的處理函數是 join 函數。接下來就調用 join 函數完成 join 命令的執行。

清單 3 展示了 Swarm 解析執行終端命令的過程。

清單 3. 命令行解析函數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func (a *App) Run(arguments []string) (err error){
     args := context.Args()
     if args.Present() {
        //...
         c := a.Command(name)
         if c != nil {
            //調用 Command 的 Run 函數來執行操作
             return c.Run(context)
         }
     }
     // Run default Action
     a.Action(context)
}
//Command 的 Run 函數
func (c Command) Run(ctx *Context) error {
     context.Command = c
//調用 Command 的 Action 函數,如果我們輸入的是 join 函數,接下來就會執行 join 函數
     c.Action(context)
}

以上代碼清單位於 github.com/codegangsta/cli/app.go 中的 Run 函數。

Swarm 在初始化的時候,會初始化它支持的第三方服務發現。當我們在終端指定第三方服務發現的時候,Swarm 會調用對應的第三方服務發現接口,創建並初始化第三方服務發現客戶端,然后向第三方服務發現服務器發送注冊消息,完成注冊。

下一步,我們將要考慮 join 函數的具體實現,Swarm 創建 Consul 客戶端以及向 Consul 服務器的注冊過程。join 函數如清單 4 所示。

清單 4. join 函數
1
2
3
4
5
6
7
8
func join(c *cli.Context) {
     dflag := getDiscovery(c)
     1、創建服務發現
     d, err := discovery.New(dflag, hb, ttl, getDiscoveryOpt(c))
     for {
         2、注冊服務發現
         if err := d.Register(addr); err != nil {//....}
     }//.

以上代碼位於 cli/join.go。

join 函數主要功能是:創建服服務發現客戶端和向服務發現服務器注冊。

1. 創建服務發現

創建第三方服務發現客戶端使用的是簡單工廠模式。Swarm 會首先將支持的第三方服務發現初始化在一個 map 中,然后根據我們在命令行指定的第三方服務發現,去調用對應的初始化方法。所以如果我們自己寫了一個第三方服務發現軟件,可以很方便地嵌入到 Swarm 中。

創建服務發現的數據流程圖如下圖 2 所示。

圖 2. 創建服務發現流程圖

 

Swarm 首先調用 discovery 包下面的 New 函數,在該函數中調用 Backen 接口的 Initialize 方法,在 Initialize 方法中,調用 libkv 包下的 NewStore 方法。在 NewStore 方法中,首先判斷 Swarm 是否支持終端輸入的服務發現,如果支持,就去調用該服務發現的初始化方法完成第三方服務發現的初始化。

Consul 初始化函數 New 函數位於 github/com/docker/libkv/store/consul/consul.go,如清單 5。

清單 5. Consul 初始化函數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func New(endpoints []string, options *store.Config) (store.Store, error) {
     s := &Consul{}
     config := api.DefaultConfig()
     s.config = config
     config.HttpClient = http.DefaultClient
     config.Address = endpoints[0]
     config.Scheme = "http"
     ....
     // Creates a new client
     client, err := api.NewClient(config)
     ...
     s.client = client
     return s, nil
}

在 Consul 的初始化函數中,新創建一個 Consul 客戶端,並完成 Consul 客戶端一些參數的初始化。

2. 注冊服務發現

consul 完成服務發現的初始化,就會發送注冊信息給 consul 服務器完成注冊。key/value 的注冊代碼位於 github.com\docker\docker\pkg\discovery\kv\kv.go ,如清單 6:

清單 6. 服務注冊
1
2
3
4
5
// Register is exported
func (s *Discovery) Register(addr string) error {
     opts := &store.WriteOptions{TTL: s.ttl}
     return s.store.Put(path.Join(s.path, addr), []byte(addr), opts)
}

Consul 發送注冊地址到 Consul 服務器,完成 Consul 的注冊。

Consul 實現 Docker Swarm 服務注冊例子

結合Swarm join 源碼解析,下面介紹如何使用 Consul 實現 Docker Swarm 服務發現。

在 Swarm 的 Client 節點(IP 地址為 192.168.1.28)執行加入集群的指令,並指定 Consul 作為服務發現。命令如圖 3 所示。

圖 3. Swarm Join 指令

 

使用 Consul 的 Web UI 查看命令執行結果。在瀏覽器地址欄輸入 http://192.168.1.28:8500,然后選擇 key/value 選項卡,在該選項卡下選擇 docker/swarm/nodes,可以看到 192.168.1.28 這個節點已經注冊到 Docker Swarm 集群。

Consul 服務發現的結果如下圖 4 所示。

圖 4. Consul 服務發現結果

 

總結與展望

本文概述了 Docker Swarm 服務發現的四種策略並進行了簡單的比較,簡單介紹了 Consul,詳述了 Swarm 使用 Consul 作為服務發現的代碼流程,最后用一個例子說明了 Docker Swarm 使用 Consul 作為服務發現的過程,希望能夠讓大家對 Swarm 服務發現的過程有所了解。

通過對 Swarm 服務發現源碼的解析,可以看到 Swarm 源碼中使用第三方 key/value 作為服務發現的實現采用了簡單工廠模式,我們也可以很容易地將其他的第三方 key/value 插件,以及自己設計的服務發現插件嵌入到 Swarm 的服務發現中。

參考資料

學習

參考Consul、Etcd 和 ZooKeeper 的區別,了解第三方服務發現的區別

參考Consul 架構圖,了解更多 Consul 的基本架構

參考熱備份,查看熱備份概念

參考Raft,查看 Raft 算法實現原理

參考Swarm 代碼框架,了解 Swarm 代碼的整體設計

參考Swarm 源碼文件夾結構,了解 Swarm 源碼的文件夾結構

參考Swarm manage 和 Store 流程解析,了解更多 Swarm 第三方服務發現執行流程


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM