前言:Nacos 可作为注册中心、配置中心,其组成集群对外提供一致的服务,类似于 Zookeeper;zk 的集群成员列表是通过其配置文件 zoo.cfg 维护的 ,其它类似的组件不再讨论,那么 nacos 的集群成员是通过什么机制发现的呢?
Nacos 集群成员发现
逻辑说明
Nacos 在启动时创建 ServerMemberManager(核心的集群成员操作类),它会调用 LookupFactory.createLookup() 通过 nacos.core.member.lookup.type 指定的类别初始化 MemberLookup 实现类;后续的成员发现和变更由 MemberLookup.afterLookup() 通知到 ServerMemberManager,此后"配置管理模块"、"注册中心模块"所感知的集群成员都由 ServerMemberManager 取得。
源码关注
-
ServerMemberManager 集群成员的所有操作都汇聚于此类,初始化、成员变更、成员列表、集群间汇报等。
-
LookupFactoty.createLookup()
-
AbstractMemberLookup
-
StandaloneMemberLookup only self
-
FileConfigMemberLookup watch conf/cluster.conf
-
AddressServerMemberLookup http get from address server
在 k8s 部署 Nacos 集群
nacos 官方提供 docker 镜像,并结合 nacos-k8s-yaml(附录:peer-finder-cluster-demo.yaml)部署于 Kubernetes,以部署 nacos 三个节点为例。 查看 yaml 可看到其包含了一个 initContainer: peer-finder-plugin-install,用来干嘛?其实 peer-finder 插件是用于不断地监听 nacos-headless 的服务列表变更,若列表有变更则写入 conf/cluster.conf,如下:
nacos-0.nacos-headless.default.svc.cluster.local:8848 nacos-1.nacos-headless.default.svc.cluster.local:8848 nacos-2.nacos-headless.default.svc.cluster.local:8848
所以该方案还是利用了上节所述 FileConfigMemberLookup 成员发现机制,conf/cluster.conf 的维护由一个脚本不断地更新,在集群扩缩节点时通过 nacos-headless 准实时获得变更成员,从而每个节点都能维持一致的成员列表。
上述方案有什么缺陷?
-
有状态:StatefulSet
-
无法跨 k8s 集群部署 nacos 集群:因为依赖 nacos-headless (k8s Service) ,k8s 集群内的 CoreDNS 域名。
那有什么方法解决呢?可以清楚地知道,上述的 nacos 集群有状态就是与 cluster.conf 文件的维护有关,那么在不引入额外依赖的情况下能想到的唯一解决方案就是使用“配置管理模块”依赖的数据库。(注:注册中心模块不依赖数据库)
在 k8s 部署无状态的 Nacos 集群 - Customize DatabaseMemberLookup
集群间成员的感知就是一个成员列表,各节点只要知道一致的成员列表就行;数据库是中心化的依赖,借鉴 AddressServerMemberLookup 实现类似的成员列表发现逻辑,实现基于数据库的集群成员发现:DatabaseMemberLookup。
-
节点启动
-
清理超时的过期成员节点
-
(不断心跳)将自己维护到数据库
-
从数据库获取所有成员列表(有变更则通知 ServerMemberManager)
-
节点下线
-
清理自己在数据库的记录
-
将自己下线的事件实时告知其他成员(实时性很重要)
以此,Nacos 仅依赖数据库,不断维护自身心跳且节点下线时主动通知其他成员,各个节点均能准实时地获取一致的(在线)成员列表。
附录
peer-finder-cluster-demo.yaml
### Nacos ###
#### https://hub.docker.com/r/nacos/nacos-server
#### https://github.com/nacos-group/nacos-k8s
#### https://github.com/nacos-group/nacos-docker
---
apiVersion: v1
kind: Service
metadata:
name: nacos-headless
labels:
app: nacos-headless
spec:
selector:
app: nacos
type: ClusterIP
clusterIP: None
ports:
- port: 8848
name: server
targetPort: 8848
- port: 7848
name: rpc
targetPort: 7848
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nacos
spec:
serviceName: nacos-headless
selector:
matchLabels:
app: nacos
replicas: 3
template:
metadata:
labels:
app: nacos
annotations:
pod.alpha.kubernetes.io/initialized: "true"
prometheus.io/path: /nacos/actuator/prometheus
prometheus.io/port: '8848'
prometheus.io/scrape: 'true'
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: "app"
operator: In
values:
- nacos
topologyKey: "kubernetes.io/hostname"
initContainers:
- name: peer-finder-plugin-install
image: nacos/nacos-peer-finder-plugin:1.1
imagePullPolicy: Always
volumeMounts:
- name: plugin-dir
mountPath: /home/nacos/plugins/peer-finder
containers:
- name: k8snacos
imagePullPolicy: Always
image: nacos/nacos-server:1.4.2
resources:
requests:
memory: "8Gi"
cpu: "4"
ports:
- containerPort: 8848
name: client
- containerPort: 7848
name: rpc
env:
- name: JVM_XMS
value: 6g
- name: JVM_XMX
value: 6g
- name: JVM_XMN
value: 3g
- name: JVM_MS
value: 128m
- name: JVM_MMS
value: 320m
- name: SERVICE_NAME
value: "nacos-headless"
- name: DOMAIN_NAME
value: "cluster.local"
- name: POD_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
- name: NACOS_REPLICAS
value: "3"
- name: MYSQL_SERVICE_HOST
valueFrom:
configMapKeyRef:
name: nacos-cm
key: mysql.host
- name: MYSQL_SERVICE_DB_NAME
valueFrom:
configMapKeyRef:
name: nacos-cm
key: mysql.db.name
- name: MYSQL_SERVICE_PORT
valueFrom:
configMapKeyRef:
name: nacos-cm
key: mysql.port
- name: MYSQL_SERVICE_USER
valueFrom:
configMapKeyRef:
name: nacos-cm
key: mysql.user
- name: MYSQL_SERVICE_PASSWORD
valueFrom:
configMapKeyRef:
name: nacos-cm
key: mysql.password
- name: MODE
value: "cluster"
- name: NACOS_SERVER_PORT
value: "8848"
- name: PREFER_HOST_MODE
value: "hostname"
- name: NACOS_SERVERS
value: "nacos-0.nacos-headless.default.svc.cluster.local:8848 nacos-1.nacos-headless.default.svc.cluster.local:8848 nacos-2.nacos-headless.default.svc.cluster.local:8848"
- name: NACOS_AUTH_ENABLE
value: "true"
- name: NACOS_AUTH_TOKEN
value: "SecretKey012345678901234567890123456789012345678901234567890123456789TODO"
- name: NACOS_AUTH_CACHE_ENABLE
value: "true"
- name: NACOS_AUTH_USER_AGENT_AUTH_WHITE_ENABLE
value: "false"
- name: NACOS_AUTH_IDENTITY_KEY
value: serverIdentityTODO
- name: NACOS_AUTH_IDENTITY_VALUE
value: securityTODO
volumeMounts:
- name: plugin-dir
mountPath: /home/nacos/plugins/peer-finder
volumes:
- name: plugin-dir
emptyDir: {}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nacos-cm
data:
mysql.host: "TODO"
mysql.db.name: "TODO"
mysql.port: "3306"
mysql.user: "TODO"
mysql.password: "TODO"
# ---
# ------------------- App Ingress ------------------- #
# apiVersion: extensions/v1beta1
# kind: Ingress
# metadata:
# name: nacos-headless
# # namespace: default
# spec:
# rules:
# - host: TODO
# http:
# paths:
# - path: /
# backend:
# serviceName: nacos-headless
# servicePort: server
参考文献