基于consul实现微服务的服务发现和负载均衡
一. 背景
隨著2018年年初國務院辦公廳聯合多個部委共同發布了《國務院辦公廳關于促進“互聯網+醫療健康”發展的意見(國辦發〔2018〕26號)》,國內醫療IT領域又迎來了一波互聯網醫院建設的高潮。不過互聯網醫院多基于實體醫院建設,雖說掛了一個“互聯網”的名號,但互聯網醫院系統也多與傳統的院內系統,比如:HIS、LIS、PACS、EMR等共享院內的IT基礎設施。
如果你略微了解過國內醫院院內IT系統的現狀,你就知道目前的多數醫院的IT系統相比于互聯網行業、電信等行業來說是相對“落伍”的,這種落伍不僅體現在IT基礎設施的專業性和數量上,更體現在對新概念、新技術、新設計理念等應用上。雖然國內醫院IT系統在技術層面呈現出“多樣性”的特征,但整體上偏陳舊和保守 – - 你可以在全國范圍內找到10-15年前的各種主流語言(VB、delphi、c#等實現的IT系統,并且系統架構多為兩層C/S結構的。
近幾年“互聯網+醫療”的興起的確在一些方面提升了醫院的服務效率和水平,但這些互聯網醫療系統多部署于院外,并主要集中在“做入口”。它們并不算是醫院的核心系統:即沒有這些互聯網系統,醫院的業務也是照常進行的(患者可以在傳統的窗口辦理所有院內業務,就是效率低罷了)。因此,雖然這些互聯網醫療系統采用了先進的互聯網系統設計理念和技術,但并沒有真正提升院內系統的技術水平,它們也只能與院內那些“陳舊”的、難于擴展的系統做對接。
不過互聯網醫院與這些系統有所不同,雖然它依然“可有可無”,但它卻是部署在院內IT基礎設施上的系統,同時也受到了院內IT基礎設施條件的限制。在我們即將上線的一個針對醫院集團的互聯網醫院版本中,我們就遇到了“被限制”的問題。我們本想上線的Kubernetes集群因為院方提供的硬件“不足”而無法實施,只能“降級”為手工打造的基于consul的微服務服務發現和負載均衡平臺,初步滿足我們的系統需要。而從k8s到consul的實踐過程,總是讓我有一種從工業時代回到的農業時代或是“消費降級”的趕腳^_^。
本文就來說說基于當前較新版本的consul實現微服務的服務發現和負載均衡的過程。
?
二. 實驗環境
這里有三臺阿里云的ECS,即用作部署consul集群,也用來承載工作負載的節點(這點與真實生產環境還是蠻像的,醫院也僅能提供類似的這點兒可憐的設備):
- consul-1: 192.168.0.129
- consul-2: 192.168.0.130
- consul-3: 192.168.0.131
操作系統:Ubuntu?server 16.04.4 LTS
內核版本:4.4.0-117-generic
實驗環境安裝有:
- Docker 17.03.3-ce
- consul v1.1.0
- consul-template 0.19.5
- nginx 1.10.3
- registrator master版本
- Go 1.11版本
實驗所用的樣例程序鏡像:
- httpfrontservice
- httpbackendservice
- tcpfrontservice
?
三. 目標及方案原理
本次實驗的最基礎、最樸素的兩個目標:
- 所有業務應用均基于容器運行
- 某業務服務容器啟動后,會被自動注冊服務,同時其他服務可以自動發現該服務并調用,并且到達這個服務的請求會負載均衡到服務的多個實例。
這里選擇了與編程語言技術棧無關的、可搭建微服務的服務發現和負載均衡的Hashicorp的consul。關于consul是什么以及其基本原理和應用,可以參見我多年前寫的這篇有關consul的文章。
但是光有consul還不夠,我們還需要結合consul-template、gliderlab的registrator以及nginx共同來實現上述目標,原理示意圖如下:
原理說明:
- 對于每個biz node上啟動的容器,位于每個node上的Registrator實例會監聽到該節點上容器的創建和停止的event,并將容器的信息以consul service的形式寫入consul或從consul刪除。
- 位于每個nginx node上的consul-template實例會watch consul集群,監聽到consul service的相關event,并將需要expose到external的service信息獲取,按照事先定義好的nginx conf template重新生成nginx.conf并reload本節點的nginx,使得nginx的新配置生效。
- 對于內部服務來說(不通過nginx暴露到外部),在被registrator寫入consul的同時,也完成了在consul DNS的注冊,其他服務可以通過特定域名的方式獲取該內部服務的IP列表(A地址)和其他信息,比如端口(SRV),并進而實現與這些內部服務的通信。
參考該原理,落地到我們實驗環境的部署示意圖如下:
四. 步驟
下面說說詳細的實驗步驟。
1. 安裝consul集群
首先我們先來安裝consul集群。consul既支持二進制程序直接部署,也支持Docker容器化部署。如果consul集群單獨部署在幾個專用節點上,那么consul可以使用二種方式的任何一種。但是如果consul所在節點還承載工作負載,考慮consul作為整個分布式平臺的核心,降低它與docker engine引擎的耦合(docker engine可能會因各種情況經常restart),還是建議以二進制程序形式直接部署在物理機或vm上。這里的實驗環境資源有限,我們采用的是以二進制程序形式直接部署的方式。
consul最新版本是1.2.2(截至發稿時),consul 1.2.x版本與consul 1.1.x版本最大的不同在于consul 1.2.x支持service mesh了,這對于consul來說可是革新性的變化,因此這里擔心其初期的穩定性,因此我們選擇consul 1.1.0版本。
我們下載consul 1.1.0安裝包后,將其解壓到/usr/local/bin下。
在$HOME下建立consul-install目錄,并在其下面存放consul集群的運行目錄consul-data。在consul-install目錄下,執行命令啟動節點consul-1上的consul:
consul-1 node:# nohup consul agent -server -ui -dns-port=53 -bootstrap-expect=3 -data-dir=/root/consul-install/consul-data -node=consul-1 -client=0.0.0.0 -bind=192.168.0.129 -datacenter=dc1 > consul-1.log & 2>&1# tail -100f consul-1.log bootstrap_expect > 0: expecting 3 servers ==> Starting Consul agent... ==> Consul agent running!Version: 'v1.1.0'Node ID: 'd23b9495-4caa-9ef2-a1d5-7f20aa39fd15'Node name: 'consul-1'Datacenter: 'dc1' (Segment: '<all>')Server: true (Bootstrap: false)Client Addr: [0.0.0.0] (HTTP: 8500, HTTPS: -1, DNS: 53)Cluster Addr: 192.168.0.129 (LAN: 8301, WAN: 8302)Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false==> Log data will now stream in as it occurs:2018/09/10 10:21:09 [INFO] raft: Initial configuration (index=0): []2018/09/10 10:21:09 [INFO] raft: Node at 192.168.0.129:8300 [Follower] entering Follower state (Leader: "")2018/09/10 10:21:09 [INFO] serf: EventMemberJoin: consul-1.dc1 192.168.0.1292018/09/10 10:21:09 [INFO] serf: EventMemberJoin: consul-1 192.168.0.1292018/09/10 10:21:09 [INFO] consul: Adding LAN server consul-1 (Addr: tcp/192.168.0.129:8300) (DC: dc1)2018/09/10 10:21:09 [INFO] consul: Handled member-join event for server "consul-1.dc1" in area "wan"2018/09/10 10:21:09 [INFO] agent: Started DNS server 0.0.0.0:53 (tcp)2018/09/10 10:21:09 [INFO] agent: Started DNS server 0.0.0.0:53 (udp)2018/09/10 10:21:09 [INFO] agent: Started HTTP server on [::]:8500 (tcp)2018/09/10 10:21:09 [INFO] agent: started state syncer ==> Newer Consul version available: 1.2.2 (currently running: 1.1.0)2018/09/10 10:21:15 [WARN] raft: no known peers, aborting election2018/09/10 10:21:17 [ERR] agent: failed to sync remote state: No cluster leader我們的三個節點的consul都以server角色啟動(consul agent -server),consul集群初始有三個node( -bootstrap-expect=3),均位于dc1 datacenter(-datacenter=dc1),服務bind地址為192.168.0.129(-bind=192.168.0.129 ),允許任意client連接( -client=0.0.0.0)。我們啟動了consul ui(-ui),便于以圖形化的方式查看consul集群的狀態。我們設置了consul DNS服務的端口號為53(-dns-port=53),這個后續會起到重要作用,這里先埋下小伏筆。
這里我們使用nohup+&符號的方式將consul運行于后臺。生產環境建議使用systemd這樣的init系統對consul的啟停和配置更新進行管理。
從consul-1的輸出日志來看,單節點并沒有選出leader。我們需要繼續在consul-2和consul-3兩個節點上也重復consul-1上的操作,啟動consul:
consul-2 node:#nohup consul agent -server -ui -dns-port=53 -bootstrap-expect=3 -data-dir=/root/consul-install/consul-data -node=consul-2 -client=0.0.0.0 -bind=192.168.0.130 -datacenter=dc1 -join 192.168.0.129 > consul-2.log & 2>&1consul-3 node:# nohup consul agent -server -ui -dns-port=53 -bootstrap-expect=3 -data-dir=/root/consul-install/consul-data -node=consul-3 -client=0.0.0.0 -bind=192.168.0.131 -datacenter=dc1 -join 192.168.0.129 > consul-3.log & 2>&1啟動后,我們查看到consul-3.log中的日志:
2018/09/10 10:24:01 [INFO] consul: New leader elected: consul-32018/09/10 10:24:01 [WARN] raft: AppendEntries to {Voter a215865f-dba7-5caa-cfb3-6850316199a3 192.168.0.130:8300} rejected, sending older logs (next: 1)2018/09/10 10:24:01 [INFO] raft: pipelining replication to peer {Voter a215865f-dba7-5caa-cfb3-6850316199a3 192.168.0.130:8300}2018/09/10 10:24:01 [WARN] raft: AppendEntries to {Voter d23b9495-4caa-9ef2-a1d5-7f20aa39fd15 192.168.0.129:8300} rejected, sending older logs (next: 1)2018/09/10 10:24:01 [INFO] raft: pipelining replication to peer {Voter d23b9495-4caa-9ef2-a1d5-7f20aa39fd15 192.168.0.129:8300}2018/09/10 10:24:01 [INFO] consul: member 'consul-1' joined, marking health alive2018/09/10 10:24:01 [INFO] consul: member 'consul-2' joined, marking health alive2018/09/10 10:24:01 [INFO] consul: member 'consul-3' joined, marking health alive2018/09/10 10:24:01 [INFO] agent: Synced node info ==> Newer Consul version available: 1.2.2 (currently running: 1.1.0)consul-3 node上的consul被選為初始leader了。我們可以通過consul提供的子命令查看集群狀態:
# consul operator raft list-peers Node ID Address State Voter RaftProtocol consul-3 0020b7aa-486a-5b44-b5fd-be000a380a89 192.168.0.131:8300 leader true 3 consul-1 d23b9495-4caa-9ef2-a1d5-7f20aa39fd15 192.168.0.129:8300 follower true 3 consul-2 a215865f-dba7-5caa-cfb3-6850316199a3 192.168.0.130:8300 follower true 3我們還可以通過consul ui以圖形化方式查看集群狀態和集群內存儲的各種配置信息:
至此,consul集群就搭建ok了。
2. 安裝Nginx、consul-template和Registrator
根據前面的“部署示意圖”,我們在consul-1和consul-2上安裝nginx、consul-template和Registrator,在consul-3上安裝Registrator。
a) Nginx的安裝
我們使用ubuntu 16.04.4默認源中的nginx版本:1.10.3,通過apt-get install nginx安裝nginx,這個無須贅述了。
b) consul-template的安裝
consul-template是一個將consul集群中存儲的信息轉換為文件形式的工具。常用的場景是監聽consul集群中數據的變化,并結合模板將數據持久化到某個文件中,再執行某一關聯的action。比如我們這里通過consul-template監聽consul集群中service信息的變化,并將service信息數據與nginx的配置模板結合,生成nginx可用的nginx.conf配置文件,并驅動nginx重新reload配置文件,使得nginx的配置更新生效。因此一般來說,哪里部署有nginx,我們就應該有一個配對的consul-template部署。
在我們的實驗環境中consul-1和consul-2兩個節點部署了nginx,因此我們需要在consul-1和consul-2兩個節點上部署consul-template。我們直接安裝comsul-template的二進制程序(我們使用0.19.5版本),下載安裝包并解壓后,將consul-template放入/usr/local/bin目錄下:
# wget -c https://releases.hashicorp.com/consul-template/0.19.5/consul-template_0.19.5_linux_amd64.zip# unzip consul-template_0.19.5_linux_amd64.zip # mv consul-tempate /usr/local/bin # consul-template -v consul-template v0.19.5 (57b6c71)這里先不啟動consul-template,后續在注冊不同服務的場景中,我們再啟動consul-template。
c) Registrator的安裝
Registrator是另外一種工具,它監聽Docker引擎上發生的容器創建和停止事件,并將啟動的容器信息以consul service的形式存儲在consul集群中。因此,Registrator和node上的docker engine對應,有docker engine部署的節點上都應該安裝有對應的Registator。因此我們要在實驗環境的三個節點上都部署Registrator。
Registrator官方推薦的就是以Docker容器方式運行,但這里我并不使用lastest版本,而是用master版本,因為只有最新的master版本才支持service meta數據的寫入,而當前的latest版本是v7版本,年頭較長,并不支持service meta數據寫入。
在所有實驗環境節點上執行:
# docker run --restart=always -d \--name=registrator \--net=host \--volume=/var/run/docker.sock:/tmp/docker.sock \gliderlabs/registrator:master\consul://localhost:8500我們看到registrator將node節點上的/var/run/docker.sock映射到容器內部的/tmp/docker.sock上,通過這種方式registrator可以監聽到node上docker引擎上的事件變化。registrator的另外一個參數:consul://localhost:8500則是Registrator要寫入信息的consul地址(當然Registrator不僅僅支持consul,還支持etcd、zookeeper等),這里傳入的是本node上consul server的地址和服務端口。
Registrator的啟動日志如下:
# docker logs -f registrator 2018/09/10 05:56:39 Starting registrator v7 ... 2018/09/10 05:56:39 Using consul adapter: consul://localhost:8500 2018/09/10 05:56:39 Connecting to backend (0/0) 2018/09/10 05:56:39 consul: current leader 192.168.0.130:8300 2018/09/10 05:56:39 Listening for Docker events ... 2018/09/10 05:56:39 Syncing services on 1 containers 2018/09/10 05:56:39 ignored: 6ef6ae966ee5 no published ports在所有節點都啟動完Registrator后,我們來先查看一下當前consul集群中service的catelog以及每個catelog下的service的詳細信息:
// consul-1:# curl http://localhost:8500/v1/catalog/services {"consul":[]}目前只有consul自己內置的consul service catelog,我們查看一下consul這個catelog service的詳細信息:
// consul-1:# curl localhost:8500/v1/catalog/service/consul|jq% Total % Received % Xferd Average Speed Time Time Time CurrentDload Upload Total Spent Left Speed 100 1189 100 1189 0 0 180k 0 --:--:-- --:--:-- --:--:-- 193k [{"ID": "d23b9495-4caa-9ef2-a1d5-7f20aa39fd15","Node": "consul-1","Address": "192.168.0.129","Datacenter": "dc1","TaggedAddresses": {"lan": "192.168.0.129","wan": "192.168.0.129"},"NodeMeta": {"consul-network-segment": ""},"ServiceID": "consul","ServiceName": "consul","ServiceTags": [],"ServiceAddress": "","ServiceMeta": {},"ServicePort": 8300,"ServiceEnableTagOverride": false,"CreateIndex": 5,"ModifyIndex": 5},{"ID": "a215865f-dba7-5caa-cfb3-6850316199a3","Node": "consul-2","Address": "192.168.0.130","Datacenter": "dc1","TaggedAddresses": {"lan": "192.168.0.130","wan": "192.168.0.130"},"NodeMeta": {"consul-network-segment": ""},"ServiceID": "consul","ServiceName": "consul","ServiceTags": [],"ServiceAddress": "","ServiceMeta": {},"ServicePort": 8300,"ServiceEnableTagOverride": false,"CreateIndex": 6,"ModifyIndex": 6},{"ID": "0020b7aa-486a-5b44-b5fd-be000a380a89","Node": "consul-3","Address": "192.168.0.131","Datacenter": "dc1","TaggedAddresses": {"lan": "192.168.0.131","wan": "192.168.0.131"},"NodeMeta": {"consul-network-segment": ""},"ServiceID": "consul","ServiceName": "consul","ServiceTags": [],"ServiceAddress": "","ServiceMeta": {},"ServicePort": 8300,"ServiceEnableTagOverride": false,"CreateIndex": 7,"ModifyIndex": 7} ]3. 內部http服務的注冊和發現
對于微服務而言,有暴露到外面的,也有僅運行在內部,被內部服務調用的。我們先來看看內部服務,這里以一個http服務為例。
對于暴露到外部的微服務而言,可以通過域名、路徑、端口等來發現。但是對于內部服務,我們怎么發現呢?k8s中我們可以通過k8s集群的DNS插件進行自動域名解析實現,每個pod中container的DNS server指向的就是k8s dns server。這樣service之間可以通過使用固定規則的域名(比如:your_svc.default.svc.cluster.local)來訪問到另外一個service(僅需配置一個service name),再通過service實現該服務請求負載均衡到service關聯的后端endpoint(pod container)上。consul集群也可以做到這點,并使用consul提供的DNS服務來實現內部服務的發現。
我們需要對三個節點的DNS配置進行update,將consul DNS server加入到主機DNS resolver(這也是之前在啟動consul時將consul DNS的默認監聽端口從8600改為53的原因),步驟如下:
- 編輯/etc/resolvconf/resolv.conf.d/base,加入一行:
- 重啟resolveconf服務
再查看/etc/resolve.conf文件:
# cat /etc/resolv.conf # Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) # DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN nameserver 100.100.2.136 nameserver 100.100.2.138 nameserver 127.0.0.1 options timeout:2 attempts:3 rotate single-request-reopen我們發現127.0.0.1這個DNS server地址已經被加入到/etc/resolv.conf中了(切記:不要直接手工修改/etc/resolve.conf)。
好了!有了consul DNS,我們就可以發現consul中的服務了。consul給其集群內部的service一個默認的域名:your_svc.service.{data-center}.consul. 之前我們查看了cluster中只有一個consul catelog service,我們就來訪問一下該consul service:
# ping -c 3 consul.service.dc1.consul PING consul.service.dc1.consul (192.168.0.129) 56(84) bytes of data. 64 bytes from iZbp15tvx7it019hvy750tZ (192.168.0.129): icmp_seq=1 ttl=64 time=0.029 ms 64 bytes from iZbp15tvx7it019hvy750tZ (192.168.0.129): icmp_seq=2 ttl=64 time=0.025 ms 64 bytes from iZbp15tvx7it019hvy750tZ (192.168.0.129): icmp_seq=3 ttl=64 time=0.031 ms# ping -c 3 consul.service.dc1.consul PING consul.service.dc1.consul (192.168.0.130) 56(84) bytes of data. 64 bytes from 192.168.0.130: icmp_seq=1 ttl=64 time=0.186 ms 64 bytes from 192.168.0.130: icmp_seq=2 ttl=64 time=0.136 ms 64 bytes from 192.168.0.130: icmp_seq=3 ttl=64 time=0.195 ms# ping -c 3 consul.service.dc1.consul PING consul.service.dc1.consul (192.168.0.131) 56(84) bytes of data. 64 bytes from 192.168.0.131: icmp_seq=1 ttl=64 time=0.149 ms 64 bytes from 192.168.0.131: icmp_seq=2 ttl=64 time=0.184 ms 64 bytes from 192.168.0.131: icmp_seq=3 ttl=64 time=0.179 ms我們看到consul服務有三個實例,因此DNS輪詢在不同ping命令執行時返回了不同的地址。
現在在主機層面上,我們可以發現consul中的service了。如果我們的服務調用者跑在docker container中,我們還能找到consul服務么?
# docker run busybox ping consul.service.dc1.consul ping: bad address 'consul.service.dc1.consul'事實告訴我們:不行!
那么我們如何讓運行于docker container中的服務調用者也能發現consul中的service呢?我們需要給docker引擎指定DNS:
在/etc/docker/daemon.json中添加下面配置:
{"dns": ["node_ip", "8.8.8.8"] //node_ip: consul_1為192.168.0.129、consul_2為192.168.0.130、consul_3為192.168.0.131 }重啟docker引擎后,再嘗試在容器內發現consul服務:
# docker run busybox ping consul.service.dc1.consul PING consul.service.dc1.consul (192.168.0.131): 56 data bytes 64 bytes from 192.168.0.131: seq=0 ttl=63 time=0.268 ms 64 bytes from 192.168.0.131: seq=1 ttl=63 time=0.245 ms 64 bytes from 192.168.0.131: seq=2 ttl=63 time=0.235 ms這次就ok了!
接下來我們在三個節點上以容器方式啟動我們的一個內部http服務demo httpbackend:
# docker run --restart=always -d -l "SERVICE_NAME=httpbackend" -p 8081:8081 bigwhite/httpbackendservice:v1.0.0我們查看一下consul集群內的httpbackend service信息:
# curl localhost:8500/v1/catalog/service/httpbackend|jq% Total % Received % Xferd Average Speed Time Time Time CurrentDload Upload Total Spent Left Speed 100 1374 100 1374 0 0 519k 0 --:--:-- --:--:-- --:--:-- 670k [{"ID": "d23b9495-4caa-9ef2-a1d5-7f20aa39fd15","Node": "consul-1","Address": "192.168.0.129",...},{"ID": "a215865f-dba7-5caa-cfb3-6850316199a3","Node": "consul-2","Address": "192.168.0.130",...},{"ID": "0020b7aa-486a-5b44-b5fd-be000a380a89","Node": "consul-3","Address": "192.168.0.131",...} ]再訪問一下該服務:
# curl httpbackend.service.dc1.consul:8081 this is httpbackendservice, version: v1.0.0內部服務發現成功!
4. 暴露外部http服務
說完了內部服務,我們再來說說那些要暴露到外部的服務,這個環節就輪到consul-template登場了!在我們的實驗中,consul-template讀取consul中service信息,并結合模板生成nginx配置文件。我們基于默認安裝的/etc/nginx/nginx.conf文件內容來編寫我們的模板。我們先實驗暴露http服務到外面。下面是模板樣例:
//nginx.conf.template.... ...http {... ...### Virtual Host Configs##include /etc/nginx/conf.d/*.conf;include /etc/nginx/sites-enabled/*;## http server config#{{range services -}}{{$name := .Name}}{{$service := service .Name}}{{- if in .Tags "http" -}}upstream {{$name}} {zone upstream-{{$name}} 64k;{{range $service}}server {{.Address}}:{{.Port}} max_fails=3 fail_timeout=60 weight=1;{{end}}}{{end}}{{end}}{{- range services -}} {{$name := .Name}}{{- if in .Tags "http" -}}server {listen 80;server_name {{$name}}.tonybai.com;location / {proxy_pass http://{{$name}};}}{{end}}{{end}}}consul-template使用的模板采用的是go template的語法。我們看到在http block中,我們要為consul中的每個要expose到外部的catelog service定義一個server block(對應的域名為your_svc.tonybai.com)和一個upstream block。
對上面的模板做簡單的解析,弄明白三點,模板基本就全明白了:
- {{- range services -}}: 標準的{{ range pipeline }}模板語法,services這個pipeline的調用相當于: curl localhost:8500/v1/catalog/services,即獲取catelog services列表。這個列表中的每項僅有Name和Tags兩個字段可用。
- {{- if in .Tags “http” -}}:判斷語句,即如果Tags字段中有http這個tag,那么則暴露該catelog service。
- {{range $service}}: 也是標準的{{ range pipeline }}模板語法,$service這個pipeline調用相當于curl localhost:8500/v1/catalog/service/xxxx,即獲取某個service xxx的詳細信息,包括Address、Port、Tag、Meta等。
接下來,我們在consul-1和consul-2上啟動consul-template:
consul-1: # nohup consul-template -template "/root/consul-install/templates/nginx.conf.template:/etc/nginx/nginx.conf:nginx -s reload" > consul-template.log & 2>&1consul-2: # nohup consul-template -template "/root/consul-install/templates/nginx.conf.template:/etc/nginx/nginx.conf:nginx -s reload" > consul-template.log & 2>&1查看/etc/nginx/nginx.conf,你會發現http server config下面并沒有生成任何配置,因為consul集群中還沒有滿足Tag條件的service(包含tag “http”)。現在我們就來在三個node上創建httpfront services。
# docker run --restart=always -d -l "SERVICE_NAME=httpfront" -l "SERVICE_TAGS=http" -P bigwhite/httpfrontservice:v1.0.0查看生成的nginx.conf:
upstream httpfront {zone upstream-httpfront 64k;server 192.168.0.129:32769 max_fails=3 fail_timeout=60 weight=1;server 192.168.0.130:32768 max_fails=3 fail_timeout=60 weight=1;server 192.168.0.131:32768 max_fails=3 fail_timeout=60 weight=1;}server {listen 80;server_name httpfront.tonybai.com;location / {proxy_pass http://httpfront;}}測試一下httpfront.tonybai.com(可通過修改/etc/hosts),httpfront service會調用內部服務httpbackend(通過httpbackend.service.dc1.consul:8081訪問):
# curl httpfront.tonybai.com this is httpfrontservice, version: v1.0.0, calling backendservice ok, its resp: [this is httpbackendservice, version: v1.0.0 ]可以在各個節點上查看httpfront的日志:(通過docker logs),你會發現到httpfront.tonybai.com的請求被均衡到了各個節點上的httpfront service上了:
{GET / HTTP/1.0 1 0 map[Connection:[close] User-Agent:[curl/7.47.0] Accept:[*/*]] {} <nil> 0 [] true httpfront map[] map[] <nil> map[] 192.168.0.129:35184 / <nil> <nil> <nil> 0xc0000524c0} calling backendservice... {200 OK 200 HTTP/1.1 1 1 map[Date:[Mon, 10 Sep 2018 08:23:33 GMT] Content-Length:[44] Content-Type:[text/plain; charset=utf-8]] 0xc0000808c0 44 [] false false map[] 0xc000132600 <nil>} this is httpbackendservice, version: v1.0.05. 暴露外部tcp服務
我們的微服務可不僅僅有http服務的,還有直接暴露tcp socket服務的。nginx對tcp的支持是通過stream block支持的。在stream block中,我們來為每個要暴露在外面的tcp service生成server block和upstream block,這部分模板內容如下:
stream {{{- range services -}}{{$name := .Name}}{{$service := service .Name}}{{- if in .Tags "tcp" -}}upstream {{$name}} {least_conn;{{- range $service}}server {{.Address}}:{{.Port}} max_fails=3 fail_timeout=30s weight=5;{{ end }}}{{end}}{{end}}{{- range services -}}{{$name := .Name}}{{$nameAndPort := $name | split "-"}}{{- if in .Tags "tcp" -}}server {listen {{ index $nameAndPort 1 }};proxy_pass {{$name}};}{{end}}{{end}} }和之前的http服務模板相比,這里的Tag過濾詞換為了“tcp”,并且由于端口具有排他性,這里用”名字-端口”串來作為service的name以及upstream block的標識。用一個例子來演示會更加清晰。由于修改了nginx模板,在演示demo前,需要重啟一下各個consul-template。
然后我們在各個節點上啟動tcpfront service(注意服務名為tcpfront-9999,9999是tcpfrontservice expose到外部的端口):
# docker run -d --restart=always -l "SERVICE_TAGS=tcp" -l "SERVICE_NAME=tcpfront-9999" -P bigwhite/tcpfrontservice:v1.0.0啟動后,我們查看一下生成的nginx.conf:
stream {upstream tcpfront-9999 {least_conn;server 192.168.0.129:32770 max_fails=3 fail_timeout=30s weight=5;server 192.168.0.130:32769 max_fails=3 fail_timeout=30s weight=5;server 192.168.0.131:32769 max_fails=3 fail_timeout=30s weight=5;}server {listen 9999;proxy_pass tcpfront-9999;}}nginx對外的9999端口對應到集群內的tcpfront服務!這個tcpfront是一個echo服務,我們來測試一下:
# telnet localhost 9999 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. hello [v1.0.0]2018-09-10 08:56:15.791728641 +0000 UTC m=+531.620462772 [hello ] tonybai [v1.0.0]2018-09-10 08:56:17.658482957 +0000 UTC m=+533.487217127 [tonybai ]基于暴露tcp服務,我們還可以實現將全透傳的https服務暴露到外部。所謂全透傳的https服務,即ssl證書配置在服務自身,而不是nginx上面。其實現方式與暴露tcp服務相似,這里就不舉例了。
?
五. 小結
以上基于consul+consul-template+registrator+nginx實現了一個基本的微服務服務發現和負載均衡框架,但要應用到生產環境還需一些進一步的考量。
關于服務治理的一些功能,consul 1.2.x版本已經加入了service mesh的support,后續在成熟后可以考慮upgrade consul cluster。
consul-template在v0.19.5中還不支持servicemeta的,但在master版本中已經支持,后續利用新版本的consul-template可以實現功能更為豐富的模板,比如實現灰度發布等。
總結
以上是生活随笔為你收集整理的基于consul实现微服务的服务发现和负载均衡的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 高效遍历Java容器
- 下一篇: 浅谈高性能数据库集群 —— 读写分离