標籤:

基於Docker、Registrator、Zookeeper實現的服務自動註冊

摘要:本文屬於jasonGeng88原創,歡迎轉載,轉載請保留出處:jasonGeng88/blog

本文所有服務均採用docker容器化方式部署

當前環境

  1. 系統:Mac OS
  2. docker 1.12.1
  3. docker-compose 1.8.0場景

在微服務架構中,傳統的大型單一應用會根據業務場景被拆分成眾多功能單一且獨立自治的微型服務。每個服務模塊不僅能獨立的對外暴露服務,而且根據業務需求,可以動態的進行擴容或縮減,充分利用了伺服器資源。

但此架構引入了很多新的問題。其中一個是,隨著服務的增多,以及服務的動態擴容,服務地址(IP、Port)硬編碼的方式已經顯得不太合適。我們需要尋求一種機制,讓每個服務能動態的創建地址,同時調用方要能獲取到這些信息、且感知地址的動態變化

註冊中心

為解決上述問題,業界給出的一種方案是使用註冊中心,通過服務的發布-訂閱模式,來解決上述場景,即所謂的 「服務的註冊&發現」。

舉例來講,大家過去是否有翻查電話簿打電話的經歷。有一天,你想給小明打個電話,可是不知道他的電話號碼是多少。於是去翻查電話簿上對方的電話信息,這裡電話簿就是所謂的註冊中心,而翻查電話簿的動作就是屬於服務的發現過程,那麼小明給出自己的電話號碼,由自己(或他人)記錄在電話簿上的動作就屬於服務的註冊過程。

理解了小明的例子,再來看服務註冊發現的流程,是不是一樣呢?

  1. 服務註冊:服務提供者將自身的服務信息註冊進註冊中心;
  2. 服務訂閱:服務消費者從註冊中心獲取服務信息,並對其進行監聽;
  3. 緩存服務信息:將獲取到的服務信息緩存到本地,減少與註冊中心的網路通信;
  4. 服務調用:查找本地緩存,找到對應的服務地址,發送服務請求;
  5. 變更通知:當服務節點有變動時(服務新增、刪除等),註冊中心將通知監聽節點變化的消費者,使其更新服務信息。

服務註冊

回到小明的例子,小明有兩種方式將自己的電話寫入電話簿,一種是自己親自登記,一種是找他人代為登記。即所謂的服務 「自註冊」 與 「第三方註冊」。

  • 自註冊: 服務內部啟動客戶端,連接註冊中心,寫入服務信息。

    • 問題:
      • 服務代碼對註冊中心進行了硬編碼,若更換了註冊中心,服務代碼也必須跟著調整;
      • 註冊中心必須與每個服務都保持通信,來做心跳檢測。如果服務很多時,對註冊中心也是一種額外的開銷;
  • 第三方註冊(本文採用方式): 採用協同進程的方式,監聽服務進程的變化,將服務信息寫入註冊中心。

    • 好處:做到了服務與註冊中心的解耦,對服務而言,完成了服務的自動化註冊;
    • 問題:協同進程本身也要考慮高可用,否則將成為單點故障的風險點;

考慮篇幅原因,服務消費者相關內容將在下篇進行講述

技術方案

服務註冊中心: 作為整個架構中的核心,註冊中心要做到的是支持分散式、支持持久化存儲。可以把它想像成一個集中化管理的中心伺服器。同時負責將服務註冊信息的變動實時通知給服務消費者。

這裡,技術上我們選用的 ZK (ZooKeeper)。大家可以把 ZK 想像成文件伺服器,註冊中心在 ZK 中的展現就是節點路徑圖,每個節點下存放者相應的服務信息,ZK路徑圖如下:

服務提供者: 服務以 docker 容器化方式部署(實現服務埠的動態生成),並以 docker-compose 的方式來管理,這裡包含各種語言實現的服務(如JAVA、PHP等),通過 Registrator 完成服務的自動註冊。

技術說明

Docker: 是一個開源工具,能將一個WEB應用封裝在一個輕量級,便攜且獨立的容器里,然後可以運行在幾乎任何服務環境下。 Docker的一般使用在以下幾點: 自動化打包和部署應用。

Docker-compose: 是一個用來定義、啟動和管理服務的工具,通過compose配置文件,將一個或多個 Docker 容器組合成一個服務。並通過簡單的命令對服務進行管理,如啟動、銷毀、水平擴展等等。

Registrator: 一個由Go語言編寫的,針對docker使用的,通過檢查容器在線或者停止運行狀態自動註冊和去註冊服務的工具。

ZK: 作為一個分散式服務框架,是 Apache Hadoop 的一個子項目,它主要是用來解決分散式應用中經常遇到的一些數據管理問題,如:統一命名服務、狀態同步服務、集群管理、分散式應用配置項的管理等等。

示例

代碼地址: jasonGeng88/service_registry_discovery

示例主要從3個方面演示:

  1. 框架搭建
  2. 服務準備
  3. 場景演示

框架搭建:

ZK 部署

當前位置: 項目根目錄

  • zookeeper/docker-compose.yml (為演示方便,這裡在單台機器上運行):

version: 2 #docker-compose版本nservices:n zoo1:n image: zookeepern restart: alwaysn ports:n - 2181:2181n environment:n ZOO_MY_ID: 1n ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888nn zoo2:n image: zookeepern restart: alwaysn ports:n - 2182:2181n environment:n ZOO_MY_ID: 2n ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888nn zoo3:n image: zookeepern restart: alwaysn ports:n - 2183:2181n environment:n ZOO_MY_ID: 3n ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888n

  • 啟動命令(執行上述文件

cd zookeeper && docker-compose up -dn

  • 查看結果

Registrator 部署

當前位置: 項目根目錄

  • registrator/docker-compose.yml

version: 2nservices:n registrator1:n image: gliderlabs/registratorn restart: alwaysn network_mode: "host"n volumes:n - /var/run/docker.sock:/tmp/docker.sockn # -ip 設置服務寫入註冊中心的IP地址n # zookeeper:// 設置連接的 ZK 協議、地址、註冊的根節點n command: "-ip 127.0.0.1 zookeeper://127.0.0.1:2181/services"nn registrator2:n image: gliderlabs/registratorn restart: alwaysn network_mode: "host"n volumes:n - /var/run/docker.sock:/tmp/docker.sockn command: "-ip 127.0.0.1 zookeeper://127.0.0.1:2182/services"nn registrator3:n image: gliderlabs/registratorn restart: alwaysn network_mode: "host"n volumes:n - /var/run/docker.sock:/tmp/docker.sockn command: "-ip 127.0.0.1 zookeeper://127.0.0.1:2183/services"n

  • 啟動命令

cd registrator/ && docker-compose up -dn

  • 查看結果

目前框架已搭建完畢,我們來連接一台ZK,來觀察節點情況:

# 進入 ZK 容器ndocker exec -it zookeeper_zoo1_1 /bin/bashn# 連接 ZKnzkCli.sh -server 127.0.0.1:2181n

服務準備:

項目中為演示準備了2個服務,分別是用JAVA、PHP實現的。

java_service_1(由springboot實現):

當前位置: services/java_service_1

  • 啟動文件

package com.example;nnimport org.springframework.boot.SpringApplication;nimport org.springframework.boot.autoconfigure.SpringBootApplication;nn@RestControllern@SpringBootApplicationnpublic class DemoApplication {nnt@RequestMapping("/")n String home() {n return "This is Service 1.";n }nntpublic static void main(String[] args) {nttSpringApplication.run(DemoApplication.class, args);nt}n}n

  • 生成 jar 文件

這裡使用 Spring Boot CLI 進行打包

spring jar ROOT.jar src/main/java/com/example/DemoApplication.javan

  • 構建鏡像

# java_service_1 為你要構建的鏡像名,tag默認為latestndocker build -t java_service_1 .n

php_service_2:

當前位置: services/php_service_2

  • index.php

<?phpnnecho This is Service 2.;n

  • 構建鏡像

# php_service_2 為你要構建的鏡像名,tag默認為latestndocker build -t php_service_2 .n

現兩個服務鏡像以構建完畢,展示如下:

場景演示:

當前位置: services

  • 場景 1: 啟動 service_1(JAVA)服務

docker-compose up -d service_1n

查看 ZK 節點情況

  • 場景 2: 啟動service_2(PHP)服務

docker-compose up -d service_2n

查看 ZK 節點情況

  • 場景 3: 擴展service_2(PHP)服務,個數為2

docker-compose up -d service_2n

查看 ZK 節點情況

  • 場景 4: 註銷service_1(JAVA)服務

docker-compose up -d service_2n

查看 ZK 節點情況

優化點

  • 在生產環境中,zk安全連接、節點訪問控制都是需要注意的。簡單做法,可以把連接地址改成內網IP,添加防火牆策略來限制連接客戶端。

  • Registrator這裡採用的是其多個進程分別連接不同的節點,來防止Registrator的單點故障。由於Registrator所用開銷較小,在服務數量與ZK節點數量不大的情況下,不會產生問題。 較好的方式是:Registrator提供失效自動地址切換功能(目前官方文檔好像沒有提供此方案,有了解的同學可以留言告訴我)。

總結

本文從介紹何為 「服務註冊&發現」 切入,以通俗易懂的語言介紹了其內在的本質,最後講到了實現的所用的具體技術方案,以及以 Demo 的方式,演示了 Registrator 是如何做到服務的自動化註冊的。

當然,這只是實現的一種形式。註冊中心用 etcd、consul 也都是可行的,而且 Registrator 官方最好的支持是 consul。我們這裡就不細究它們的差別了,找到適合自己、滿足業務的就是最好的??。

後續

服務發現機制與實現


推薦閱讀:

【技術總結】一起聊聊Kubernetes
如何快速建立生物信息分析環境
Docker 容器與鏡像的儲存
【DockerCon2017最新技術解讀】Docker最新特性介紹

TAG:Docker |