支持从nacos(已实现),eureka(已实现)等注册中心同步到apisix(已实现)和kong(已实现)等网关, 后续将支持自定义插件,支持用户自己用 python 实现支持类似携程阿波罗注册中心,etcd注册中心,consul注册中心等插件,以及spring gateway等网关插件的高扩展性
docker run anjia0532/discovery-syncer-python:v2.7.1
特别的,-c
支持配置远端http[s]的地址,比如读取静态资源的,比如读取nacos的
-c http://xxxxx/nacos/v1/cs/configs?tenant=public&group=DEFAULT_GROUP&dataId=discovery-syncer.yaml
,便于管理
破坏性更新
v2.7.0 版本增加了redis的依赖,原方案会导致内存缓慢增长,部分情况下甚至出现任务阻塞卡死的情况,如果不想引入redis,可以退回v2.6.13版本
或者修改 app.tasks.task_syncer.py
和 app.scheduler.scheduler.py
将 funboost_background_scheduler_redis_store
改为 funboost_aps_scheduler
将 https://github.com/anjia0532/discovery-syncer-python/blob/master/app/tasks/common.py 改为
class FunboostCommonConfig(BoosterParams):
# 中间件选型见3.1章节 https://funboost.readthedocs.io/zh/latest/articles/c3.html
broker_kind: BrokerEnum = BrokerEnum.SQLITE_QUEUE
# 是否使用分布式控频
is_using_distributed_frequency_control: bool = True
# 是否将发布者的心跳发送到redis,有些功能的实现需要统计活跃消费者。因为有的中间件不是真mq。这个功能,需要安装redis.
is_send_consumer_hearbeat_to_redis: bool = True
参考 https://github.com/anjia0532/discovery-syncer-python/commit/eb4f0299ae330d259b2331ecef75f336a2c5c37a
运行的时候,需要配置环境变量
REDIS_HOST=127.0.0.1
REDIS_USERNAME=
REDIS_PASSWORD=
REDIS_PORT=6379
REDIS_DB=7
REDIS_DB_FILTER_AND_RPC_RESULT=8
REDIS_URL=f'redis://{REDIS_USERNAME}:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}'
注意:
请勿将此服务暴露到公网,否则对于引发的一切安全事故概不负责。安全起见,所有接口统一都增加 SYNCER-API-KEY
header
头校验,需要在配置文件 common.syncer-api-key
修改默认值,
长度最低为32位,需要同时包含大小写字母,数字,和特殊字符
路径 | 返回值 | 用途 |
---|---|---|
GET / |
OK |
服务是否启动 |
GET /redoc/ |
redocly ui | Redocly 接口文档 |
GET /docs/ |
swagger ui | Swagger 接口文档 |
GET /-/reload |
OK |
重新加载配置文件,加载成功返回OK,主要是cicd场景或者k8s的configmap reload 场景使用 |
GET /show-memory |
text/plain | 显示当前内存使用情况,主要是排查内存泄露等问题用 |
GET /health |
JSON | 判断服务是否健康,可以配合k8s等容器服务的健康检查使用 |
PUT /discovery/{discovery-name}?alive_num=1 |
OK |
主动下线上线注册中心的服务,配合CI/CD发版业务用 |
GET /gateway-api-to-file/{gateway-name} |
text/plain | 读取网关admin api转换成文件用于备份或者db-less模式(目前仅支持apisix,kong建议用deck) |
POST /migrate/{gateway-name}/to/{gateway-name} |
OK |
将网关数据迁移(目前仅支持apisix,kong建议用deck) |
PUT /restore/{gateway-name} |
OK |
将 db-less 文件还原到网关(目前仅支持apisix,kong建议用deck) |
num 是显示的条数,默认20条
加载成功返回OK
主要是 cicd 场景或者 k8s 的 configmap reload 场景使用
返回值
{
// 一共有几个enabled的同步任务(targets)
"total": 2,
// 正常在跑的有几个
"running": 2,
// 有几个超过 配置文件定义的maximum-interval-sec的检测时间没有运行的,失联的。
"lost": 0,
// 都在跑,状态是OK(http状态码是200),有在跑的,有失联的,状态是WARN(http状态码是200),全部失联,状态是DOWN(http状态码500)
"status": "OK",
// 哪些成功,哪些失败
"details": [
"syncer:a_task,is ok",
"syncer:b-api,is ok"
],
// 运行时长
"uptime": "1m6s"
}
discovery-name
是注册中心的名字,如果不存在,则返回 Not Found
http status code 是404
alive_num
是执行上下线操作后,断言存活实例数量,小于等于0则不判断,不传默认1,不满足则报错,http status code 是500, content
类似 ('最少存活实例数1不满足,总实例数(含之前已下线数量)2,要下线实例数2,剩余在线实例数0',)
body入参
{
// 检索哪个服务下的实例
"serviceName": "",
// 基于注册中心元数据还是基于实例ip来查找
"type": "METADATA/IP",
// 匹配的查询条件,支持正则
"regexpStr": "",
// 匹配的元数据key,如果是ip则不用填
"metadataKey": "",
// 匹配到的将状态改成上线还是下线
"status": "UP/DOWN",
// 其他没匹配的,状态是上线还是下线,ORIGIN保持不变
"otherStatus": "UP/DOWN/ORIGIN",
// 扩展参数
"extData": {
}
}
gateway-name 是网关的名字,如果不存在,则返回 Not Found
,http status code是404
如果服务报错,resp body 会返回空字符串,header 中的 syncer-err-msg
会返回具体原因,http status code 是500,参数 file
是可选的,如果不传,则默认/tmp/文件名
比如 /tmp/apisix.yaml
,并返回文件路径
如果正常,resp body 会返回转换后的文本内容,syncer-file-location
会返回syncer服务端的路径(
一般是系统临时目录+文件名,例如/tmp/apisix.yaml
), http status code是200
注意
精力有限,目前仅实现了 apisix 的 admin api 转 yaml,kong 的未实现,有需要的,欢迎提PR贡献代码或者提issues 来反馈
origin_gateway_name 是源网关的名字,target_gateway_name 是目标网关的名字,如果不存在,则返回 Not Found
http status code
是404
注意
精力有限,目前仅实现了 apisix 实例间数据迁移 (支持 apisix 2x->3x , apisix 3x->2x,apisix 2x->2x,apisix 3x->3x),kong 的未实现,有需要的,欢迎提PR贡献代码或者提issues 来反馈
gateway-name 是网关的名字,如果不存在,则返回 Not Found
http status code 是404
body入参(以apisix为例)
# Auto generate by https://github.com/anjia0532/discovery-syncer-python, Don't Modify
# apisix 2.x modify conf/config.yaml https://apisix.apache.org/docs/apisix/2.15/stand-alone/
# apisix:
# enable_admin: false
# config_center: yaml
# apisix 3.x modify conf/config.yaml https://apisix.apache.org/docs/apisix/3.2/deployment-modes/#standalone
# deployment:
# role: data_plane
# role_data_plane:
# config_provider: yaml
# save as conf/apisix.yaml
routes: [ ]
services: [ ]
upstreams: [ ]
plugins: [ ]
#END
注意
仅限同版本还原,不支持跨版本还原,如apisix 2.x 还原到 apisix 3.x,apisix 3.x 还原到 apisix 2.x。有需要跨版本的 精力有限,目前仅实现了 apisix 的 yaml 还原 apisix ,kong 的未实现,有需要的,欢迎提PR贡献代码或者提issues 来反馈
环境变量 | 值 |
---|---|
SYNC_JOB_JSON | 同步作业json文件路径 |
GIT_REPO | git库地址,如果不需要push到库则不设置即可 |
REMOTE | remote名字,不传为origin |
BRANCH | git分支名字,不传为main |
DEFAULT_USER_EMAIL | git提交时用户名,不传为discovery-syncer-python@syncer.org |
DEFAULT_BASE_DIR | git 库clone路径,默认为 syncer |
job.json
格式类似
discovery-syncer-python/tools/backup.py
Lines 117 to 124 in 216e36b
如果用 docker 的话
echo '{}' > ./job.json
docker run -e SYNC_JOB_JSON=job.json \
-e BRANCH=main \
-e REMOTE=origin \
-e GIT_REPO='git@github.com:anjia0532/discovery-syncer-python-demo.git' \
-e DEFAULT_USER_EMAIL='discovery-syncer-python@syncer.org' \
-e DEFAULT_BASE_DIR='syncer' \
-v $(pwd)/job.json:/opt/discovery-syncer/job.json \
anjia0532/discovery-syncer-python-backup:v2.4.5
如果用命令行的话
wget https://raw.githubusercontent.com/anjia0532/discovery-syncer-python/master/tools/backup.py
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --no-cache-dir --upgrade GitPython httpx
# 安装 git 和 python3.11
echo '{}' > ./job.json
export SYNC_JOB_JSON=job.json
python3 backup.py
vi restore.py
python3 restore.py
修改 app.model.config.DiscoveryType 新增类型,比如
class DiscoveryType(Enum):
NACOS = "nacos"
EUREKA = "eureka"
# 新增 redis
REDIS = "redis"
创建 app/service/discovery/redis.py
文件
from typing import List
from app.model.syncer_model import Service, Instance, Registration
from app.service.discovery.discovery import Discovery
from core.lib.logger import for_service
logger = for_service(__name__)
class Redis(Discovery):
def __init__(self, config):
super().__init__(config)
def get_all_service(self, config: dict, enabled_only: bool = True) -> List[Service]:
pass
def get_service_all_instances(self, service_name: str, ext_data: dict, enabled_only: bool = True) -> tuple[
List[Instance], int]:
pass
def modify_registration(self, registration: Registration, instances: List[Instance]):
pass
修改 config.yaml
discovery-servers:
redis1:
type: redis
weight: 100
prefix: 2
host: "redis://localhost:6379"
config:
demo: demo
gateway-servers:
apisix1:
type: apisix
admin-url: http://apisix-server:9080
prefix: /apisix/admin/
config:
X-API-KEY: xxxxxxxx-xxxx-xxxxxxxxxxxx
version: v3
targets:
- discovery: redis
gateway: apisix1
enabled: false
# .. 忽略其他部分
修改 app.model.config.GatewayType 新增类型,比如
class GatewayType(Enum):
KONG = "kong"
APISIX = "apisix"
SPRING_GATEWAY = "spring_gateway"
创建 app/service/gateway/spring_gateway.py
文件
from typing import Tuple, List
from app.model.syncer_model import Instance
from app.service.gateway.gateway import Gateway
class SpringGateway(Gateway):
def __init__(self, config):
super().__init__(config)
def get_service_all_instances(self, target: dict, upstream_name: str = None) -> List[Instance]:
pass
def sync_instances(self, target: dict, upstream_name: str, diff_ins: list, instances: list):
pass
def fetch_admin_api_to_file(self, file_name: str) -> Tuple[str, str]:
pass
async def migrate_to(self, target_gateway: 'Gateway'):
pass
修改 config.yaml
discovery-servers:
nacos1:
type: nacos
weight: 100
prefix: /nacos/v1/
host: "http://nacos-server:8858"
gateway-servers:
spring_gateway1:
type: spring_gateway
admin-url: http://gateway-server:9080
prefix: /
config:
demo: xxxxxxxx-xxxx-xxxxxxxxxxxx
targets:
- discovery: nacos1
gateway: spring_gateway1
enabled: false
# .. 忽略其他部分
-
已解决
目前的同步任务是串行的,如果待同步的量比较大,或者同步时间窗口设置的特别小的情况下,会导致挤压 -
已解决
不支持自定义同步插件,不利于自行扩展 -
同步机制目前是基于定时轮询,效率比较低,有待优化,比如增加缓存开关,上游注册中心与缓存比对没有差异的情况下,不去拉取/变更下游网关的upstream信息,或者看看注册中心支不支持变动主动通知机制等。
- funboost -- python万能分布式函数调度框架
- apisix -- 云原生Api网关
- kong -- 云原生Api网关
- nacos -- 一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台
- eureka -- 中间层服务器的发现、负载平衡和故障转移
- discovery-syncer -- 我之前用golang写的多端注册中心网关同步工具
This module is licensed under the BSD license.
Copyright (C) 2017-, by AnJia anjia0532@gmail.com.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.