服務發現與 Docker

注:該文由 adetante 編寫,原文地址爲 Service discovery with Docker前端

這篇博客的第一篇文章,我將寫一篇基於 Docker 容器構建一個不可變架構的解決方案的文章。node

這個主題將經過系列文章來描述,從最簡單的案例到更復雜的架構。git

整體的思想是設計一個「基於概念驗證」的解決方案,它容許:github

  • 啓動和中止一個新容器,若是系統須要擴展或是下線
  • 當一個新版本的應用準備推送到生產,這時使用新容器替換老的容器
  • 使用服務註冊和發現來自動把新的容器推送到生產架構

概述

這個邏輯架構是很是簡單的,一個無狀態的應用經過負載均衡器訪問。web

此處輸入圖片的描述

每一個應用的實例運行在它本身的 docker 容器中。docker

爲了動態配置管理,當咱們啓動和中止一個新容器的時候,咱們想後端能自動註冊進負載均衡器。這是基本需求,叫作**服務發現***:咱們想負載均衡器能自動發現提供服務的容器。shell

在這篇文章中,全部的節點將運行在相同的 docker 主機上。這是很是簡單的,可是這是實現基礎概念的第一個方法。而後咱們將經過容許在不一樣主機上透明的部署來是架構複雜化。數據庫

工具集

第一個示例將使用如下工具實現:json

  • Docker。一個運行應用容器的開源平臺
  • Synapse。一個 Airbnb 團隊開發的簡單的服務發現的工具
  • Haproxy。一個負載一個後端節點列表的 TCP 流量代理,它打開一個本地的端口,而後把流量傳遞進這個後端節點的端口。

服務發現

目標是減小或消除組件之間的「手動」的鏈接。當你把你的應用程序推送進生產的時候,全部的這些事情均可以配置:數據庫服務器的主機和端口,REST 服務的 URL 等等,在一個高可擴展的架構中,這些鏈接能夠動態改變。一個新的後端能夠被添加,一個數據庫節點能夠被中止。你的應用須要適應這種動態環境。ubuntu

這裏有一些工具能夠管理這些需求(Apache Zookeeper, etcd, ...)。這些工具的廣泛原則是:當啓動的時候,一個服務的實例必須註冊進配置服務器。當中止的時候(完美中止或是 Crash 了),節點必須從配置服務中移除掉。註冊後,其餘服務能夠在配置服務器中搜索到提供製度服務的實例列表(主機和端口)。

Synapse

Synapse 是一個簡單的服務發現的工具。Synapse 與如下倆個組件一塊兒使用:

  • Watcher:它們常常檢查一組服務器提供的服務。這能夠經過鏈接 Zookeeper,etcd 或是經過使用 Docker API 來檢查 Docker 容器來實現。
  • Haproxy:Synapse 根據 watcher 的結果來自動改變 HAproxy 的配置。這個意味着當一個新的實例被 watcher 發現,一個後端會被添加進 HAproxy 而且能夠經過代理的本地端口訪問的。一樣地,當實例中止的時候,Synapse 移除後端節點。

第一個解決方案

第一個解決方案將使用 Synapse 和 檢查 Docker 容器實現。

此處輸入圖片的描述

Synapse 管理一個運行在安裝了 Docker 的相同的主機上的以 8080 端口運行着的 HAproxy 實例。

Synapse 檢查 Docker 來發現容器是否運行着一個指定鏡像而且暴露一個指定端口。爲每個匹配的容器,Synapse 把其添加進 HAproxy 的配置。

對於這個示例,咱們從一個乾淨的 *Ubuntu 14.04 amd64 安裝開始。

安裝 Docker

安裝步驟已經在 Docker 的文檔中描述了:

$ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
$ sudo sh -c "echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list"
$ sudo apt-get update
$ sudo apt-get install lxc-docker

把如下行添加進 /etc/default/docker,使得 Docker API 在 tcp 上可用:

DOCKER_OPTS="-H 127.0.0.1:4243"

重起 docker:

$ sudo service docker restart

最後,定義如下環境變量來以便 docker 客戶端使用 tcp API:

$ export DOCKER_HOST=tcp://127.0.0.1:4243

爲 web(nodejs)應用程序建立鏡像

從 Docker 倉庫獲取最新的 Ubuntu 鏡像

$ sudo -E docker pull ubuntu:latest

啓動一個新的容器

$ sudo -E docker run -ti ubuntu bash

在這個容器中,安裝 nodejs

$ apt-get update && apt-get install -y nodejs

在這個容器中,使用如下內容建立一個簡單的 nodejs 腳本 /server.js

var http = require('http');
var os = require('os');

var server = http.createServer(function (request, response) {
    response.writeHead(200, {"Content-Type": "text/plain"});
    response.end("Hello from " + os.hostname() + "\n");
});

server.listen(8000);

console.log("Server running at http://127.0.0.1:8000/");

在這個容器中,使用如下內容編寫啓動腳本 /run.sh

#! /bin/sh
/usr/bin/nodejs /server.js

$ chmod a+x /run.sh

中止容器而且建立一個新的鏡像:

$ exit
# Get the ID of the container
$ sudo -E docker ps -a
# Change 3796ab3f5b76 in the following command with the ID listed above
$ sudo -E docker commit 3796ab3f5b76 local/nodeapp
# Remove the old container
$ sudo -E docker rm 3796ab3f5b76

在主機上安裝 synapse

$ sudo apt-get install build-essential ruby ruby-dev haproxy
$ sudo gem install synapse

編輯 /etc/default/haproxy 把 ENABLED 設置成 1

啓動一個後端實例

啓動一個 webapp 容器的實例:

$ sudo -E docker run -d -p 8000 local/nodeapp /run.sh

經過直接在這個容器中調用 nodejs 來測試。咱們必須首先獲取暴露的公共端口。

# Get the public port (mapped to 8000 in the container, here 49153)
$ sudo docker ps
$ curl http://127.0.0.1:49153
# Responds with "Hello from {container_id}"

使用 Synapse 自動配置 HAproxy

使用如下內容建立一個 /etc/synapse.json.conf 配置文件:

{
  "services": {
    "nodesrv": {
      "discovery": {
        "method": "docker",
        "servers": [
          {
            "name": "localhost",
            "host": "localhost"
          }
        ],
        "container_port": 8000,
        "image_name": "local/nodeapp"
      },
      "haproxy": {
        "port": 8080,
        "listen": [
          "mode http",
          "option httpchk /",
          "http-check expect string Hello"
        ]
      }
    }
  },
  "haproxy": {
    "reload_command": "service haproxy reload",
    "config_file_path": "/etc/haproxy/haproxy.cfg",
    "do_writes": true,
    "do_reloads": true,
    "global": [
      "chroot /var/lib/haproxy",
      "user haproxy",
      "group haproxy",
      "daemon"
    ],
    "defaults": [
      "contimeout 5000",
      "clitimeout 50000",
      "srvtimeout 50000"
    ]
  }
}

咱們能夠在這個文件中看到:

  • services.nodesrv.discovery: 配置的觀察者。這裏咱們使用 Docker API 來發現容器運行的名爲 local/nodeapp 的鏡像以及它暴露的 8080 端口
  • services.nodesrv.haproxy:配置與 nodesrv service 有關的相關的 HAproxy 端口
  • haproxy:被 Synapse 管理的全局配置實例

啓動 HAproxy 和 Synapse

$ sudo service haproxy start
$ sudo synapse -c /etc/synapse.json.conf

經過直接調用 HAproxy(監聽 8080 端口)來測試

$ curl http://localhost:8080
# Responds Hello from {container_id}

用 nodeapp 啓動第二個容器:

$ sudo -E docker run -d -p 8000 local/nodeapp /run.sh

經過 HAproxy 測試一些請求。幾秒後,每一個節點都將響應。
在一個新的 shell 中,運行一下循環,每兩秒調用一次 HAproxy:

while :
do
    curl http://localhost:8080
    sleep 2
done

HAproxy 不是經過 container1 就是經過 container2 響應。

中止其中一個容器:

$ sudo -E docker stop {container_id}

幾秒後,僅僅剩下的容器響應。

可是在以前咱們能夠看到一些 503 Service Unavailable 錯誤。這是因爲 Synapse 發現中止的容器而且從代理移除它的時候。

總結

在第一篇文章中,我配置 HAprxoy 從 Docker 容器發現後端節點。Synapse 對使這個進程自動化給予了不少幫助。儘管如此,這個解決方案還有一些缺點:

  • 由於 Synapse 使用 Docker API 發現後端服務,全部的被組織在一個 HAprxoxy 前端的服務必須是在同一個 Docker 主機上。
  • 正如咱們所看到的,當中止容器的時候,會有一些中斷。這是因爲 Synapse 按期的調用 Docker API 來發現新的或是已經移除的容器。

在下一篇文章中,這個解決方案將被擴展成容許在多主機透明部署。

相關文章
相關標籤/搜索