Consul詳解
近期在微服務業務中用的註冊中心,在此簡單記錄下以備後用。
一 概述
1.1 概念
Consul
是 HashiCorp 公司推出的開源工具,用於實現分散式系統的服務發現與配置。Consul 是分散式的、高可用的、 可橫向擴充套件的。
1.2 特徵
- 服務發現: Consul 提供了通過 DNS 或者 HTTP 介面的方式來註冊服務和發現服務。一些外部的服務通過 Consul 很容易的找到它所依賴的服務。
- 健康檢測: Consul 的 Client 提供了健康檢查的機制,可以通過用來避免流量被轉發到有故障的服務上。
Key/Value
儲存: 應用程式可以根據自己的需要使用 Consul 提供的 Key/Value 儲存。 Consul 提供了簡單易用的 HTTP 介面,結合其他工具可以實現動態配置、功能標記、領袖選舉等等功能。- 多資料中心: Consul 支援開箱即用的多資料中心. 這意味著使用者不需要擔心需要建立額外的抽象層讓業務擴充套件到多個區域。
1.3 架構圖及解析
1.3.1 內部架構及原理
1.3.1.1架構圖
1.2.1.2 圖解
首先Consul支援多資料中心,在上圖中有兩個DataCenter,他們通過Internet互聯,同時請注意為了提高通訊效率,只有Server節點才加入跨資料中心的通訊。
在單個資料中心中,Consul分為Client和Server兩種節點(所有的節點也被稱為Agent),Server節點儲存資料,Client負責健康檢查及轉發資料請求到Server;Server節點有一個Leader和多個Follower,Leader節點會將資料同步到Follower,Server的數量推薦是3個或者5個,在Leader掛掉的時候會啟動選舉機制產生一個新的Leader。
叢集內的Consul節點通過gossip協議(流言協議)維護成員關係,也就是說某個節點了解叢集內現在還有哪些節點,這些節點是Client還是Server。單個資料中心的流言協議同時使用TCP和UDP通訊,並且都使用8301埠。跨資料中心的流言協議也同時使用TCP和UDP通訊,埠使用8302。
叢集內資料的讀寫請求既可以直接發到Server,也可以通過Client使用RPC轉發到Server,請求最終會到達Leader節點,在允許資料輕微陳舊的情況下,讀請求也可以在普通的Server節點完成,叢集內資料的讀寫和複製都是通過TCP的8300埠完成。
Consul 叢集間使用了 Gossip
協議通訊和 raft 一致性演算法
- Gossip —— Gossip protocol 也叫 Epidemic Protocol (流行病協議),實際上它還有很多別名,比如:“流言演算法”、“疫情傳播演算法”等。 這個協議的作用就像其名字表示的意思一樣,非常容易理解,它的方式其實在我們日常生活中也很常見,比如電腦病毒的傳播,森林大火,細胞擴散等等。
- Client —— 一個 Client 是一個轉發所有 RPC 到 server 的代理。這個 client 是相對無狀態的。client 唯一執行的後臺活動是加入 LAN gossip 池。這有一個最低的資源開銷並且僅消耗少量的網路頻寬。
- Server —— 一個 server 是一個有一組擴充套件功能的代理,這些功能包括參與 Raft 選舉,維護叢集狀態,響應 RPC 查詢,與其他資料中心互動 WAN gossip 和轉發查詢給 leader 或者遠端資料中心。
- DataCenter —— 雖然資料中心的定義是顯而易見的,但是有一些細微的細節必須考慮。例如,在 EC2 中,多個可用區域被認為組成一個資料中心。我們定義資料中心為一個私有的,低延遲和高頻寬的一個網路環境。這不包括訪問公共網路,但是對於我們而言,同一個 EC2 中的多個可用區域可以被認為是一個資料中心的一部分。
- Consensus —— 一致性,使用 Consensus 來表明就 leader 選舉和事務的順序達成一致。為了以容錯方式達成一致,一般有超過半數一致則可以認為整體一致。Consul 使用 Raft 實現一致性,進行 leader 選舉,在 consul 中的使用 bootstrap 時,可以進行自選,其他 server 加入進來後 bootstrap 就可以取消。
- LAN Gossip —— 它包含所有位於同一個區域網或者資料中心的所有節點。
- WAN Gossip —— 它只包含 Server。這些 server 主要分佈在不同的資料中心並且通常通過因特網或者廣域網通訊。
- RPC——遠端過程呼叫。這是一個允許 client 請求 server 的請求/響應機制。
1.3.2 Consul服務發現原理
1.3.2.1 原理圖
1.3.2.2 圖解
首先需要有一個正常的Consul叢集,有Server,有Leader。這裡在伺服器Server1、Server2、Server3上分別部署了Consul Server,假設他們選舉了Server2上的Consul Server節點為Leader。這些伺服器上最好只部署Consul程式,以儘量維護Consul Server的穩定。
然後在伺服器Server4和Server5上通過Consul Client分別註冊Service A、B、C,這裡每個Service分別部署在了兩個伺服器上,這樣可以避免Service的單點問題。服務註冊到Consul可以通過HTTP API(8500埠)的方式,也可以通過Consul配置檔案的方式。Consul Client可以認為是無狀態的,它將註冊資訊通過RPC轉發到Consul Server,服務資訊儲存在Server的各個節點中,並且通過Raft實現了強一致性。
最後在伺服器Server6中Program D需要訪問Service B,這時候Program D首先訪問本機Consul Client提供的HTTP API,本機Client會將請求轉發到Consul Server,Consul Server查詢到Service B當前的資訊返回,最終Program D拿到了Service B的所有部署的IP和埠,然後就可以選擇Service B的其中一個部署並向其發起請求了。如果服務發現採用的是DNS方式,則Program D中直接使用Service B的服務發現域名,域名解析請求首先到達本機DNS代理,然後轉發到本機Consul Client,本機Client會將請求轉發到Consul Server,Consul Server查詢到Service B當前的資訊返回,最終Program D拿到了Service B的某個部署的IP和埠。
圖中描述的部署架構筆者認為是最普適最簡單的方案,從某些預設配置或設計上看也是官方希望使用者採用的方案,比如8500埠預設監聽127.0.0.1,當然有些同學不贊同,後邊會提到其他方案。
1.4 為什麼使用服務發現
防止硬編碼、容災、水平擴縮容、提高運維效率等等,只要你想使用服務發現總能找到合適的理由。
一般的說法是因為使用微服務架構。傳統的單體架構不夠靈活不能很好的適應變化,從而向微服務架構進行轉換,而伴隨著大量服務的出現,管理運維十分不便,於是開始搞一些自動化的策略,服務發現應運而生。所以如果需要使用服務發現,你應該有一些對服務治理的痛點。
但是引入服務發現就可能引入一些技術棧,增加系統總體的複雜度,如果你只有很少的幾個服務,比如10個以下,並且業務不怎麼變化,吞吐量預計也很穩定,可能就沒有必要使用服務發現。
二 consul與其他框架差異
名稱 | 優點 | 缺點 | 介面 | 一致性演算法 |
---|---|---|---|---|
zookeeper | 1.功能強大,不僅僅只是服務發現 2.提供 watcher 機制能實時獲取服務提供者的狀態 3.dubbo 等框架支援 | 1.沒有健康檢查 2.需在服務中整合 sdk,複雜度高 3.不支援多資料中心 | sdk | Paxos |
consul | 1.簡單易用,不需要整合 sdk 2.自帶健康檢查 3.支援多資料中心 4.提供 web 管理介面 | 1.不能實時獲取服務資訊的變化通知 | http/dns | Raft |
etcd | 1.簡單易用,不需要整合 sdk 2.可配置性強 | 1.沒有健康檢查 2.需配合第三方工具一起完成服務發現 3.不支援多資料中心 | http | Raft |
三 安裝部署
3.1 物理伺服器安裝部署
3.1.1 linux安裝
wget https://releases.hashicorp.com/consul/1.5.1/consul_1.5.1_linux_amd64.zip
unzip consul_0.8.1_linux_amd64.zip
mv consul /usr/local/bin/
複製程式碼
3.1.2 superviosr啟動
- 配置
mkdir -p /data/consul/{data,logs}
cat < /etc/supervisord.d/consul.conf >EOF
[program:consul]
command=consul agent -server -bootstrap-expect 3 -data-dir /data/consul/data -bind=172.16.100.2 -ui -client 0.0.0.0 -advertise=172.16.100.2 -node=go2cloud-platform-test -rejoin
user=root
stdout_logfile=/data/consul/logs/consul.log
autostart=true
autorestart=true
startsecs=60
stopasgroup=true
ikillasgroup=true
startretries=1
redirect_stderr=true
EOF
複製程式碼
-
⚠️:啟動後需要手動在其他兩個節點手動加入
consul join 172.16.100.2
-
檢視
[root@go2cloud_platform_pord conf.d]# supervisorctl status consul
consul RUNNING pid 11838, uptime 0:13:28
複製程式碼
- 其他命令
# 檢視叢集成員
consul members
# 檢視叢集狀態
consul info
# 幫助
consul agent -h
複製程式碼
3.2 Docker部署
# 拉取映象
docker pull consul
複製程式碼
然後就可以啟動叢集了,這裡啟動4個Consul Agent,3個Server(會選舉出一個leader),1個Client。
#啟動第1個Server節點,叢集要求要有3個Server,將容器8500埠對映到主機8900埠,同時開啟管理介面
docker run -d --name=consul1 -p 8500:8500 -e CONSUL_BIND_INTERFACE=eth0 consul agent --server=true --bootstrap-expect=3 --client=0.0.0.0 -ui
#啟動第2個Server節點,並加入叢集
docker run -d --name=consul2 -e CONSUL_BIND_INTERFACE=eth0 consul agent --server=true --client=0.0.0.0 --join 172.17.0.2
#啟動第3個Server節點,並加入叢集
docker run -d --name=consul3 -e CONSUL_BIND_INTERFACE=eth0 consul agent --server=true --client=0.0.0.0 --join 172.17.0.2
#啟動第4個Client節點,並加入叢集
docker run -d --name=consul4 -e CONSUL_BIND_INTERFACE=eth0 consul agent --server=false --client=0.0.0.0 --join 172.17.0.2
複製程式碼
第1個啟動容器的IP一般是172.17.0.2,後邊啟動的幾個容器IP會排著來:172.17.0.3、172.17.0.4、172.17.0.5。
這些Consul節點在Docker的容器內是互通的,他們通過橋接的模式通訊。但是如果主機要訪問容器內的網路,需要做埠對映。在啟動第一個容器時,將Consul的8500埠對映到了主機的8900埠,這樣就可以方便的通過主機的瀏覽器檢視叢集資訊。
- 配置檔案方式註冊服務
# 編寫service.json
{
"services": [
{
"id": "hello1",
"name": "hello",
"tags": [
"primary"
],
"address": "172.17.0.5",
"port": 5000,
"checks": [
{
"http": "http://localhost:5000/",
"tls_skip_verify": false,
"method": "Get",
"interval": "10s",
"timeout": "1s"
}
]
}
]
}
複製程式碼
# 將json檔案拷貝進容器內
docker cp myservice.json consul1:/consul/config
# 過載配置檔案
docker exec consul1 consul reload
複製程式碼
此時已經有了服務,只是服務不可用,consul傳送給服務的請求不可達
3.3 k8s中部署
可以自己去寫yaml資源清單檔案或者利用官方提供好的helm的charts來安裝
- 利用helm進行安裝
# 查詢helm
[root@master opt]# helm search consul
NAME CHART VERSION APP VERSION DESCRIPTION
apphub/consul 5.3.3 1.6.0 Highly available and distributed service discovery and ke...
apphub/prometheus-consul-exporter 0.1.4 0.4.0 A Helm chart for the Prometheus Consul Exporter
bitnami/consul 5.3.3 1.6.0 Highly available and distributed service discovery and ke...
incubator/ack-consul 0.5.0 0.5.0 Install and configure Consul on Kubernetes.
stable/consul 3.8.1 1.5.3 Highly available and distributed service discovery and ke...
stable/prometheus-consul-exporter 0.1.4 0.4.0 A Helm chart for the Prometheus Consul Exporter
# 安裝consul
[root@master opt]# helm install stable/consul -n consul
NAME: consul
LAST DEPLOYED: Fri Nov 22 09:52:52 2019
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/ConfigMap
NAME DATA AGE
consul-tests 1 3m4s
==> v1/Pod(related)
NAME READY STATUS RESTARTS AGE
consul-0 0/1 ContainerCreating 0 3m4s
==> v1/Secret
NAME TYPE DATA AGE
consul-gossip-key Opaque 1 3m4s
==> v1/Service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
consul ClusterIP None <none> 8500/TCP,8400/TCP,8301/TCP,8301/UDP,8302/TCP,8302/UDP,8300/TCP,8600/TCP,8600/UDP 3m4s
consul-ui NodePort 10.107.180.193 <none> 8500:30082/TCP 3m4s
==> v1beta1/PodDisruptionBudget
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
consul-pdb N/A 1 0 3m4s
==> v1beta1/StatefulSet
NAME READY AGE
consul 0/3 3m4s
NOTES:
1. Watch all cluster members come up.
$ kubectl get pods --namespace=default -w
2. Test cluster health using Helm test.
$ helm test consul
3. (Optional) Manually confirm consul cluster is healthy.
$ CONSUL_POD=$(kubectl get pods -l='release=consul' --output=jsonpath={.items[0].metadata.name})
$ kubectl exec $CONSUL_POD consul members --namespace=default | grep server
複製程式碼
- 檢視執行狀態
我們可以看到helm安裝consul使用的是statefulset,服務使用的是NodePort方式
# 檢視執行狀態
[root@master opt]# kubectl get pods --show-labels -l release=consul
NAME READY STATUS RESTARTS AGE LABELS
consul-0 1/1 Running 0 3m41s chart=consul-3.8.1,component=consul-consul,controller-revision-hash=consul-6969c79b5c,heritage=Tiller,release=consul,statefulset.kubernetes.io/pod-name=consul-0
consul-1 1/1 Running 0 2m56s chart=consul-3.8.1,component=consul-consul,controller-revision-hash=consul-6969c79b5c,heritage=Tiller,release=consul,statefulset.kubernetes.io/pod-name=consul-1
consul-2 1/1 Running 0 2m17s chart=consul-3.8.1,component=consul-consul,controller-revision-hash=consul-6969c79b5c,heritage=Tiller,release=consul,statefulset.kubernetes.io/pod-name=consul-2
# 檢視svc
[root@master opt]# kubectl get svc -l release=consul
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
consul ClusterIP None <none> 8500/TCP,8400/TCP,8301/TCP,8301/UDP,8302/TCP,8302/UDP,8300/TCP,8600/TCP,8600/UDP 5m23s
consul-ui NodePort 10.107.180.193 <none> 8500:30082/TCP 5m23s
# 檢視server
[root@master opt]# CONSUL_POD=$(kubectl get pods -l='release=consul' --output=jsonpath={.items[0].metadata.name})
[root@master opt]# kubectl exec $CONSUL_POD consul members --namespace=default | grep server
consul-0 10.244.1.67:8301 alive server 1.5.3 2 dc1 <all>
consul-1 10.244.2.193:8301 alive server 1.5.3 2 dc1 <all>
consul-2 10.244.1.68:8301 alive server 1.5.3 2 dc1 <all>
複製程式碼
- 網頁進行檢視
四 使用
在此演示利用python來使用consul的伺服器發現與註冊,已經consul的簡單配置中心
4.1 服務發現與註冊
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import json
import requests
from consul import Consul, Check
from random import randint
# consul 操作類
class ConsulClient():
def __init__(self, host=None, port=None, token=None): # 初始化,指定consul主機,埠,和token
self.host = host # consul 主機
self.port = port # consul 埠
self.token = token
self.consul = Consul(host=host, port=port)
def register(self, name, service_id, local_ip, local_port, consul_health_url, tags=None, interval=None): # 註冊服務 註冊服務的服務名 埠 以及 健康監測埠
# 心跳檢測url
health_check_url = ''.join(["http://", local_ip, ":", str(local_port), consul_health_url])
# 健康檢查的ip,埠,檢查時間
http_check = Check.http(health_check_url, "10s")
return self.consul.agent.service.register(name, service_id=service_id, address=local_ip, port=int(local_port),
check=http_check, tags=tags, interval=interval)
def deregister(self, service_id):
# 此處有坑,原始碼用的get方法是不對的,改成put,兩個方法都得改
de_result = self.consul.agent.service.deregister(service_id)
check_result = self.consul.agent.check.deregister(service_id)
return de_result, check_result
def getService(self, name): # 負債均衡獲取服務例項
self.port = str(self.port)
url = 'http://' + self.host + ':' + self.port + '/v1/catalog/service/' + name # 獲取 相應服務下的DataCenter
dataCenterResp = requests.get(url)
if dataCenterResp.status_code != 200:
raise Exception('can not connect to consul ')
listData = json.loads(dataCenterResp.text)
dcset = set() # DataCenter 集合 初始化
for service in listData:
dcset.add(service.get('Datacenter'))
serviceList = [] # 服務列表 初始化
for dc in dcset:
if self.token:
url = 'http://' + self.host + ':' + self.port + '/v1/health/service/' + name + '?dc=' + dc + '&token=' + self.token
else:
url = 'http://' + self.host + ':' + self.port + '/v1/health/service/' + name + '?dc=' + dc + '&token='
resp = requests.get(url)
if resp.status_code != 200:
raise Exception('can not connect to consul ')
text = resp.text
serviceListData = json.loads(text)
for serv in serviceListData:
status = serv.get('Checks')[1].get('Status')
if status == 'passing': # 選取成功的節點
address = serv.get('Service').get('Address')
port = serv.get('Service').get('Port')
serviceList.append({'port': port, 'address': address})
if len(serviceList) == 0:
raise Exception('no serveice can be used')
else:
service = serviceList[randint(0, len(serviceList) - 1)] # 隨機獲取一個可用的服務例項
return service['address'], int(service['port'])
def getServices(self):
return self.consul.agent.services()
if __name__ == '__main__':
host = '10.234.2.204'
port = '30082'
server_name = 'myapp'
server_id = server_name + '-8500'
c = ConsulClient(host, port)
# print(c.deregister(server_id))
# print(c.register(server_name, server_id, 'x.x.x.x', 8012, '/ops-audit/health'))
print(c.consul.agent.services())
print(c.getService(server_name))
from apps.jumpserver.conf import get_consul_server
print(get_consul_server('cmp', 'SMARTOPS_API_URL'))
# server_name2 = 'myconsulapp'
# local_port = '8000'
# server_id2 = server_name2 + '-' + local_port
# print(c.register(server_name2, server_id2, '10.234.2.186', '8000', '/ops-audit/health'))
複製程式碼
注意,需要寫在服務啟動的時候去註冊,在利用到服務發現的地方查詢可用的後端伺服器來獲取服務的ip及埠使用。
在服務停止的時候去登出服務。
4.2 配置中心
- 在consul頁面Key/Value配置
def get_config(CONSUL_HOST, CONSUL_PORT, KEY_NAME):
c = consul.Consul(CONSUL_HOST, CONSUL_PORT)
index = None
index, data = c.kv.get(KEY_NAME, index=index)
return yaml.safe_load(data.get("Value").decode(encoding='utf-8'))
複製程式碼
一般情況下需要去啟一個程式一直watch consul的配置來實時更新應用中的配置,或者每次利用配置的時候去單獨呼叫獲取配置。