04-25 周四 FastBuild重构实践-TLS、全局捕获异常、一键配置

04-25 周四 FastBuild重构实践
时间版本修改人描述
04-25V0.1宋全恒新建文档
2024年5月6日14:33:16V1.0宋全恒完成文档撰写

简介

 由于 04-22 周日 阿里云-瑶光上部署FastBuild过程(配置TLS、自定义辅助命令)描述了重新部署一个FastBuild实例的过程,通过阅读这个,可以看到部署一个FastBuild的实例是非常复杂的,之前的两次部署,直接让我花费了将近10个小时,太痛苦了。因此优化就成了必须要进行的,因为我也是一个有完美主义倾向的程序猿。

问题分析-依赖

因为它有如下的依赖:

  • Docker 服务器配置(启用TLS的话,还需要对TLS服务进行启动,在某些部署时,不需要启动TLS,但由于代码写死,不具有灵活性,在不需要启动TLS的环境下,也需要启用了TLS的Docker服务)
  • FastBuild运行时,需要tools和source以及tls信息。而且这些信息无法再运行后配置,必须运行之前准备。但一旦外部环境变化,重新部署,需要重新更新配置文件。因此提前准备配置文件无法一劳永逸。因为镜像启动以及挂载这些路径由上层应用决定,因此在运行时未添加判断需要的目录是否存在的问题,导致问题定位非常不便。
  • 瑶光镜像判断问题,在代码中写死
  • 还有一个很痛苦的点,就是,每次部署一个新的环境,竟然就需要开辟一个新的分支,然后将配置写入配置文件,提交commit,这几乎是无法忍受的,更灵活的配置,应该就是一个分支足够了,在不同的环境下运行,在运行后配置一下即可。不然就有太多机械的,无意义的工作存在。
  • 还有一个痛苦的点,就是在镜像构建完成之后,回调外部提供的接口,由于之前的代码写死了header,导致在第二次部署时扯皮比较多,而且日志打印的也少。

注:所有的写死,都是自己坑自己。切记这个教训。

image-20240506145629006

将依赖可配置

 这是本次重构的核心思想,

将所有的外部依赖进行可配置

 即先保证FastBuild不需要任何的外部依赖,而可以运行,然后在运行时通过接口一次诸如全部依赖配置。沿着这个思想,我们就需要把所有的配置从配置文件移动到库中了。

 主要问题

  • 容器服务器ip问题
  • 挂载目录存在问题
  • Docker服务配置问题
  • TLS的问题[]自动切换
  • 回调接口问题
  • 日志打印问题
  • 瑶光镜像判断

构建镜像测试请求

{"webSSHSecret": "qkrhxQmWOe5kvpJpplvTuQ==", "jupyterLabSecret": "qkrhxQmWOe5kvpJpplvTuQ==", "task_data": {"task_name": "10.101.12.128-songquanheng@zhejianglab.com-1714116639", "target_image_name": "10.101.12.128/songquanheng-zhejianglab.com/ubuntu:sqh-18.04", "callback_url": "http://alkaidos.cn/api/app/dros-ic-platform/harbor/image/callback"}, "dockerfile_json": {"base_image": "harbor.alkaidos.cn/base/ubuntu:18.04", "maintainer": "1597398607723978754", "image_installer_config": {"python_env": {"present": "", "update": false, "target": "", "install_loc": "/usr/local/dros/python"}, "pip_installer_config": {"installer_name": "pip", "install": {"present": "", "update": false, "target": "", "install_loc": ""}, "source": {"installer_name": "", "type": "", "file_name": ""}, "software_list": [], "delimiter": "", "python_version": []}, "package_manager_installer_config": {"installer_name": "apt", "install": {"present": "apt 1.6.14 (amd64)", "update": false, "target": "", "install_loc": ""}, "source": {"installer_name": "", "type": "ali", "file_name": "ubuntu-18.04.list"}, "software_list": [], "delimiter": "", "python_version": []}, "conda_installer_config": {"installer_name": "conda", "install": {"present": "", "update": false, "target": "", "install_loc": ""}, "source": {"installer_name": "", "type": "", "file_name": ""}, "software_list": [], "delimiter": "", "python_version": []}, "webSSHSecret": "", "jupyterLabSecret": ""}}}

解决

TLS、Docker、Harbor、Host依赖

问题分析

容器IP问题

 之前是放在配置文件中的,现在通过接口传入

 分析关于IP问题

image-20240425110708890

 可以看到系统可以只依赖端口,可以将Host放置在数据库中

 新增数据库表fb_host_table,保存ip,端口等信息。

 增加python的数据库服务DBHostService,同时增加host_controller.py,host_controller.py,同时在main.py中引入这个router。

 其中host_controller中包含主机信息的查询和新建

tls信息使用
image-20240425152845738
fb_tls_config = Configuration.fb_tls_config()
remote_docker = Configuration.remote_docker()

 而在ImageUtils类中,这是普通成员变量

class ImageUtils:"""镜像工具包,用于对镜像进行检测,处理启动容器,执行语句,构建镜像"""# docker sdk中上层的api,需要使用远端的docker server执行镜像构建tls_config = TLSConfig(client_cert=(fb_tls_config.client_cert_path, fb_tls_config.client_key_path),ca_cert=fb_tls_config.ca_path,verify=True)docker_client = docker.DockerClient(base_url=remote_docker.get_base_url(), tls=tls_config)# api_client docker sdk进行原始的接口调用,主要用来进行inspect_image进行调用获取镜像元数据api_client = docker.APIClient(base_url=remote_docker.get_base_url(), tls=tls_config)

 从上述的代码看,docker_client和api_client这两个变量时关键,登录的方式

    docker_client = docker.DockerClient(base_url=remote_docker.get_base_url(), tls=tls_config)# api_client docker sdk进行原始的接口调用,主要用来进行inspect_image进行调用获取镜像元数据api_client = docker.APIClient(base_url=remote_docker.get_base_url(), tls=tls_config)docker_client.login(username=harbor_config.username, password=harbor_config.password,registry=harbor_config.registry)api_client.login(username=harbor_config.username, password=harbor_config.password, registry=harbor_config.registry)

 api_client执行路如下的工作:

image-20240425153642855

 而docker_client用于启动容器和获取镜像信息

image-20240425153531758

 而Harbor仓库主要有三个配置,其实有4个,就是harbor的域名。

[harbor]
username = robot$algorithm
password = 1rhkIx3ufBI37tvkQ6kwDx7mRYcZpFCB
registry = 10.101.12.128

 remote_docker一共六个配置项,tls是否启用也是一个配置项

[tls]
client_cert_path = /mnt/self-define/meizhewei/fastbuild/tls/cert-jenkins.pem
client_key_path = /mnt/self-define/meizhewei/fastbuild/tls/key-jenkins.pem
ca_path = /mnt/self-define/meizhewei/fastbuild/tls/ca-jenkins.pem[remote-docker]
# 记录远端docker server的host:port
host = 10.101.12.122
port = 2375

将Harbor信息拷贝到数据库中

Docker Server 信息拷贝到数据库中

注,主要是UploadFile,Form花费了较多的时间。

@router.post("/update-docker-server")
async def update_docker_server_config(host: str = Form(), port: int = Form(), tls_tar_file: UploadFile = File(None)):if not validate_host(host):return Response.error(f"请输入有效的ip或者域名,参数host: {host}")new_docker_server = DBDockerServer(host=host,port=port,tls_verify=False)if tls_tar_file:tls_folder_name = get_ip_address_folder(host)target_dir = "/mnt/nas_self-define/fastbuild/tls"tls_dir = save_and_extract_tar(tls_tar_file, target_dir, tls_folder_name)tls_files = ["ca-jenkins.pem", "cert-jenkins.pem", "key-jenkins.pem"]if not all(file in get_files_in_directory(tls_dir) for file in tls_files):return Response.error(f"请上传正确的tls文件,当前上传的文件为{tls_tar_file.filename},解压后不包含{' '.join(tls_files)}")new_docker_server.tls_verify = Truenew_docker_server.client_cert_path = os.path.join(tls_dir, "cert-jenkins.pem")new_docker_server.ca_path = os.path.join(tls_dir, "ca-jenkins.pem")new_docker_server.client_key_path = os.path.join(tls_dir, "key-jenkins.pem")print(f"插入一条新的Docker server配置, {new_docker_server}")DBDockerServerService.save(new_docker_server)return Response.success(msg=f"插入docker_server信息{new_docker_server}")

 采用的方式是一直插入,取最新的,这样比较简单。

防御代码-重构ImageUtils

 怎么防止在未进行配置时调用ImageUtils对象呢?

 这也是一个不太优雅的地方,就是在FastBuild的关键几处采用了相同的防御代码。

防御代码的目的是确保在使用FastBuild进行镜像构建的时候,已经完成了Docker、Harbor、host的配置

镜像构建
@router.post("/build-image")
async def build_image(image_request: ImageRequest):"""镜像构建接口,用于根据用户选择的需求配置,生成dockerfile,构建镜像,推送到harbor仓库。:param image_request 任务构建请求:return:"""host = DBHostService.query_latest_host()if host.not_set():return Response.error("要使用FastBuild服务构建镜像,请先配置FastBuild容器服务所在的宿主机")harbor = DBHarborService.query_latest_harbor()if not harbor:return Response.error("在使用FastBuild时,请先配置Harbor仓库")docker_server = DBDockerServerService.query_latest_docker_server()if not docker_server:return Response.error("在使用FastBuild时,请先配置Docker Server")...
拉取镜像
@router.post("/pull-image")
async def pull_image(image_name: str):harbor = DBHarborService.query_latest_harbor()if not harbor:return Response.error("在使用FastBuild时,请先配置Harbor仓库")docker_server = DBDockerServerService.query_latest_docker_server()if not docker_server:return Response.error("在使用FastBuild时,请先配置Docker Server")...
检查对象
@router.post("/check-image")
async def check_image(image_name: str):harbor = DBHarborService.query_latest_harbor()if not harbor:return Response.error("在使用FastBuild时,请先配置Harbor仓库")docker_server = DBDockerServerService.query_latest_docker_server()if not docker_server:return Response.error("在使用FastBuild时,请先配置Docker Server")...

开始的情况

 起初配置文件如下所示: 我们的目标是删除其中的[tls]、[remote-docker]、[harbor]

[fb]
# 系纾_湾P彉~@作¨潛®弾U, 佅¶中1级潛®弾U表示湾P潚~D类佞~K﻾L奾B轘¿轇~Lali﻾L 缾Q彘~S(163), 淾E位~N(qinghua)
source_dir = /mnt/nas_self-define/meizhewei/fastbuild/source
# 轕~\佃~O彞~D建任佊¡庠¹潛®弾U﻾L佅¶中任佊¡潛®弾U侾]嬾X乾FDockerfile以住~J轜~@襾A潚~D轕~\佃~O彞~D建彝~P彖~Y
task_dir = /mnt/nas_self-define/meizhewei/fastbuild/task
# 孾I袾E余¨彉~@作¨潛®弾U﻾L pip⽀~Aconda⽀~Apython佝~G伾M乾N佅¶中⽀~B佅¶中pip中住~H佈~F为pip2佒~Lpip3潛®弾U
tools_dir = /mnt/nas_self-define/meizhewei/fastbuild/tools
# FB彉~@作¨潚~D主彜º
host = 10.101.12.88
# FB彉~@位| 潔¨潚~D端住£
port = 48001[db]
file = sqlite:mnt/nas_self-define/meizhewei/fastbuild/database/fb-prod.db[tls]
client_cert_path = /mnt/nas_self-define/meizhewei/fastbuild/tls/cert-jenkins.pem
client_key_path = /mnt/nas_self-define/meizhewei/fastbuild/tls/key-jenkins.pem
ca_path = /mnt/nas_self-define/meizhewei/fastbuild/tls/ca-jenkins.pem[callback]
# 记弾U乾F轕~\佃~O彞~D建襾A䷾J彊¥潚~D主彜º端住£信彁¯﻾L轇~G潔¨HTTP位~O议
host = 10.101.12.120
port = 40096[remote-docker]
# 记弾U达\端docker server潚~Dhost:port
host = 10.101.12.122
port = 2375[aes]
# 潔¨乾NAES佊| 宾F潚~Dkey
key = c7e71f37dda040fd
# 潔¨乾NAES佊| 宾F潚~D佁~O移轇~O设置
iv = 0000000000000000[harbor]
username = robot$algorithm
password = 1rhkIx3ufBI37tvkQ6kwDx7mRYcZpFCB
registry = 10.101.12.128

实践-配置FastBuild并重构ImageUtils

建表

 基本的过程是设置了三个表:

image-20240506153627715

 如fb_harbor表定义如下:

#!/usr/bin/env python
# -*- coding:UTF-8 -*-"""
@author: songquanheng
@email: wannachan@outlook.com
@time: 2024年4月25日11:21:38
@desc: 用于存储Harbor相关的信息
"""
from datetime import datetimefrom sqlalchemy import Column, Integer, String, DateTimefrom db.db_element import Baseclass DBHarbor(Base):"""表示运行作业表,其对应slurm中正在运行的作业"""__tablename__ = 'fb_harbor_table'primary_id = Column(Integer, primary_key=True)"""记录创建时间"""create_time = Column(DateTime, nullable=False, default=datetime.now)"""更新时间"""update_time = Column(DateTime(timezone=True), default=datetime.now, onupdate=datetime.now, comment="修改时间")"""用户名"""username = Column(String)"""harbor密码"""password = Column(String)"""harbor的仓库地址"""registry = Column(String)registry_dns = Column(String)def __repr__(self):return f"<DBHarbor(primary_id={self.primary_id}, harbor username={self.username}, " \f"password={self.password}, registry={self.registry}, registry_dns={self.registry_dns})>"def get_harbor_config_dict(self):return {"username": self.username, "password": self.password, "registry": self.registry}

 可以看出,在上述的代码中,引入了registry_dins,这主要是为了瑶光镜像判断,harbor的域名也可以作为镜像名称的准备。

表操纵的接口

 依然以DBHarborService为例:

#!/usr/bin/env python
# -*- coding:UTF-8 -*-"""
@author: songquanheng
@email: wannachan@outlook.com
@time: 2024年4月25日11:22:09
@desc: 用于对Harbor仓库的信息进行更改
"""
from typing import Listfrom db.db_element import Session
from db.db_harbor import DBHarborclass DBHarborService:"""镜像构建任务存储服务"""@staticmethoddef query_all() -> List[DBHarbor]:with Session() as session:return session.query(DBHarbor) \.order_by(DBHarbor.update_time.desc()) \.all()@staticmethoddef save(harbor: DBHarbor) -> DBHarbor:"""新增或者更新一组集群信息"""with Session() as session:session.add(harbor)session.commit()return harbor@staticmethoddef query_latest_harbor() -> DBHarbor:with Session() as session:return session.query(DBHarbor) \.order_by(DBHarbor.create_time.desc()) \.first()

 可以看到上述,最主要的事query_latest_harbor,按照数据字段进行降序配列,并取第一个为当前有效的。并且,没有更新某个条目,而是不断地插入。

重构ImageUtils

 这部分的重构是很关键的,因为之前是通过读取文件生成了单例的配置对象,而现在要读取数据库中的配置。在实现时,是将Docker的配置以及Harbor的配置传入了ImageUtils类

image-20240506154529109

 这样修改之后,构造器方法变得复杂了

    def __init__(self, docker_server: DBDockerServer, harbor: DBHarbor) -> None:""":rtype: object"""self.docker_server = docker_serverself.harbor = harbor# docker sdk中上层的api,需要使用远端的docker server执行镜像构建if docker_server.tls_verify:tls_config = TLSConfig(client_cert=(docker_server.client_cert_path, docker_server.client_key_path),ca_cert=docker_server.ca_path,verify=True)self.docker_client = docker.DockerClient(base_url=docker_server.get_base_url(), tls=tls_config)self.api_client = docker.APIClient(base_url=docker_server.get_base_url(), tls=tls_config)else:self.docker_client = docker.DockerClient(base_url=docker_server.get_base_url())self.api_client = docker.APIClient(base_url=docker_server.get_base_url())self.docker_client.login(username=harbor.username, password=harbor.password, registry=harbor.registry)self.api_client.login(username=harbor.username, password=harbor.password, registry=harbor.registry)super().__init__()

 这样的重构,逻辑上是没有问题的, 但是楼主在运行时发现了,在Task类重构,即将一个实例注入到了Task类中,进行构建镜像的时候,如果仍然使用同一个image_utils对象的时候,会报错,具体原因还不知道

image-20240506154901200

 如上图所示,在构建镜像的时候,楼主重新实例化了一个局部的image_utils对象。这样没有了报错

[FIX]不清楚为什么要在构建的时候,使用新建的image_utils对象,而不能是self.image_utils,新建之后没有报AttributeError: ‘APIclient’ object has no attribute '_proxy_configs’错

一键配置config-fastbuild接口

 通过接口一键完成FastBuild的更新,并且在配置更新时,验证了提供的Docker和Harbor信息的有效性。

 关于一键配置接口,可以参见 04-28 周日 FastAPI Post请求同时传递文件和普通参数

@router.post("/config-fastbuild")
async def update_docker_server_config_in_object(fastbuild_host: str = Form(), fastbuild_port: int = Form(default=48001),harbor_username: str = Form(), harbor_password: str = Form(), harbor_registry: str = Form(),harbor_registry_dns: str = Form(default=''),docker_host: str = Form(), docker_port: int = Form(default=2375), docker_tls_tar_file: UploadFile = File(None)):print("fastbuild: ", fastbuild_host, fastbuild_port)print("harbor: ", harbor_username, harbor_password, harbor_registry, harbor_registry_dns)if not all(map(validate_host, [fastbuild_host, harbor_registry, harbor_registry_dns, docker_host])):return Response.error(data="fastbuild_host, harbor_registry_host, harbor_registry_dns, docker_host均应为有效的ip或者域名")db_host = DBHost(host_ip=fastbuild_host, host_port=fastbuild_port)db_harbor = DBHarbor(username=harbor_username, password=harbor_password, registry=harbor_registry,registry_dns=harbor_registry_dns)db_docker = DBDockerServer(host=docker_host, port=docker_port, tls_verify=False)if docker_tls_tar_file:tls_folder_name = get_ip_address_folder(db_docker.host)tls_dir = save_and_extract_tar(docker_tls_tar_file, system_config.get_tls_dir(), tls_folder_name)tls_files = ["ca-jenkins.pem", "cert-jenkins.pem", "key-jenkins.pem"]if not all(file in get_files_in_directory(tls_dir) for file in tls_files):return Response.error(f"请上传正确的tls文件,当前上传的文件为{docker_tls_tar_file.filename},解压后不包含{' '.join(tls_files)}")db_docker.tls_verify = Truedb_docker.client_cert_path = os.path.join(tls_dir, "cert-jenkins.pem")db_docker.ca_path = os.path.join(tls_dir, "ca-jenkins.pem")db_docker.client_key_path = os.path.join(tls_dir, "key-jenkins.pem")try:image_utils = ImageUtils(db_docker, db_harbor)except DockerException as exe:print(f"发生异常: {exe}")raise FBException(code=123, message=f"使用提供的docker和harbor信息,进行登录测试,测试失败,请检查,错误信息为{str(exe)}")DBHostService.save(db_host)DBHarborService.save(db_harbor)DBDockerServerService.save(db_docker)return Response.success(data="成功完成为FastBuild配置需要的宿主机信息,Docker信息以及Harbor信息")

 使用Controller接口config-fastbuild完成这些信息的更新

目录挂载问题

 在main.py中,直接判断必要的目录是否存在,不存在直接报错

if __name__ == '__main__':print("FastBuild 启动ing")necessary_paths = [system_config.get_source_dir(), system_config.get_tools_dir(), system_config.get_task_dir()]if not all(os.path.exists(path) for path in necessary_paths):print(f"在FastBuild运行前,请首先保证目录存在: source_dir: {system_config.get_source_dir()}用于保存安装器源文件, "f"tools_dir: {system_config.get_tools_dir()}用于保存安装器工具文件")print("由于目录验证失败,本次运行失败,FastBuild即将推出")sys.exit(1)db_host = DBHostService.query_latest_host()db_harbor = DBHarborService.query_latest_harbor()db_docker = DBDockerServerService.query_latest_docker_server()if all([db_host, db_harbor, db_docker]):print("FastBuild系统已经正确配置")print(f"FastBuild主机环境为: {db_host.host_ip} 端口: {db_host.host_port}")print(f"FastBuild镜像仓库环境为: {db_harbor}")print(f"FastBuild 镜像构建Docker环境为: {db_docker}")else:print("FastBuild系统尚未配置,请先调用/api/fast-build/config/config-fastbuild 配置FastBuild系统")add_default_host()print(f"查看激活配置文件: {get_config_file()}")uvicorn.run(app='main:app', host=host_config.host,port=int(host_config.port), reload=True, debug=True)

 进一步使用搬移函数,将代码重构成如下的内容:

if __name__ == '__main__':print("FastBuild 启动ing")if not system_config.necessary_dirs_exist():print(f"在FastBuild运行前,请首先保证目录存在: source_dir: {system_config.get_source_dir()}用于保存安装器源文件, "f"tools_dir: {system_config.get_tools_dir()}用于保存安装器工具文件")print("由于目录验证失败,本次运行失败,FastBuild即将推出")sys.exit(1)add_default_host()add_default_header()print_env_configuration()print(f"查看激活配置文件: {get_config_file()}")uvicorn.run(app='main:app', host=host_config.host,port=int(host_config.port), reload=True, debug=True)

 通过代码函数名提升可读性,也更加合理。至此完成了TLS的重构,而配置文件变成了如下的样子

[fb]
# 系统源所在目录, 其中1级目录表示源的类型,如阿里ali, 网易(163), 清华(qinghua)
source_dir = /mnt/nas_self-define/meizhewei/fastbuild/source
# 镜像构建任务根目录,其中任务目录保存了Dockerfile以及需要的镜像构建材料
task_dir = /mnt/nas_self-define/meizhewei/fastbuild/task
# 安装器所在目录, pip、conda、python均位于其中。其中pip中又分为pip2和pip3目录
tools_dir = /mnt/nas_self-define/meizhewei/fastbuild/tools
tls_dir = /mnt/nas_self-define/meizhewei/fastbuild/tls
# FB所在的主机
host = 0.0.0.0
# FB所占用的端口
port = 48001[callback]
headers = {"X-BizType": "DROS", "X-Login-UserId": "1", "Content-Type": "application/json"}[db]
file = sqlite:mnt/nas_self-define/meizhewei/fastbuild/database/fb-prod.db[aes]
# 用于AES加密的key
key = c7e71f37dda040fd
# 用于AES加密的偏移量设置
iv = 0000000000000000

太多的重复的路径,抽取basic_dir

回调测试信息

sqlite json数据存储

 这个是和回调测试这个需求一样的,就是在数据库中需要保存字典,因此同样的就是建表,

建表

#!/usr/bin/env python
# -*- coding:UTF-8 -*-"""
@author: songquanheng
@email: wannachan@outlook.com
@time: 2024年4月25日11:21:38
@desc: 保存FastBuild与上层服务的所有回调的信息,包括url,header,结果
"""
from datetime import datetimefrom sqlalchemy import Column, Integer, String, DateTime, JSONfrom db.db_element import Baseclass DBCallback(Base):"""表示每次回调的信息,包括回调的url,回调的header以及回调的结果"""__tablename__ = 'fb_callback_table'primary_id = Column(Integer, primary_key=True)"""记录创建时间"""create_time = Column(DateTime, nullable=False, default=datetime.now)"""更新时间"""update_time = Column(DateTime(timezone=True), default=datetime.now, onupdate=datetime.now, comment="修改时间")url = Column(String)headers = Column(JSON)state = Column(JSON)result = Column(String)def __repr__(self):return f"<DBCallback(url='{self.url}', headers='{self.headers}', result={self.result}"

 可以看到headers和state,均指定了JSON格式的数据库字段类型。

表操作API

 此处需要注意的query_all在获取所有的回调结果时,按照update_time排序降序。如果要更新headers,则可以调用update_latest_callback_headers,但这其实破坏了数据的完整性,暂时也没什么重要的,先这样实现的。未来则可以进一步的新建一个数据,主要是为了快速实现所以这样做的。

#!/usr/bin/env python
# -*- coding:UTF-8 -*-"""
@author: songquanheng
@email: wannachan@outlook.com
@time: 2024年4月29日14:12:03
@desc: 回调记录管理
"""
from typing import Listfrom db.db_callback import DBCallback
from db.db_element import Sessionclass DBCallbackService:@staticmethoddef query_all() -> List[DBCallback]:with Session() as session:return session.query(DBCallback) \.order_by(DBCallback.update_time.desc()) \.all()@staticmethoddef save(callback: DBCallback) -> DBCallback:"""或添加一条DockerServer数据"""with Session() as session:session.add(callback)session.commit()return callback@staticmethoddef query_latest_callback() -> DBCallback:with Session() as session:return session.query(DBCallback) \.order_by(DBCallback.update_time.desc()) \.first()@staticmethoddef update_latest_callback_headers(headers: dict):with Session() as session:callback: DBCallback = session.query(DBCallback) \.order_by(DBCallback.update_time.desc()) \.first()callback.headers = headerssession.commit()

保存和更新headers

 保存指的是在main.py运行时,将默认的headers插入到数据库中,或者在FastBuild运行后,更新headers

def add_default_header():"""添加默认请求头"""callback = DBCallbackService.query_latest_callback()if callback is not None:returnDBCallbackService.save(DBCallback(headers=callback_config.header))
@router.post("/update-headers")
async def update_harbor_config(headers: dict):DBCallbackService.update_latest_callback_headers(headers=headers)return Response.success(msg="完成回调请求时需要的的headers信息的更新")

 同时,提供了测试回调的接口,方便直接测试回调的结果进行打印。这主要是回调时和外部系统交互,防止扯皮而开发的接口,比较直观的定位问题。

@router.post("/test-callback")
async def test_callback():callback = DBCallbackService.query_latest_callback()if not callback.url:return Response.error(msg="当前还没有回调记录,因此无法获取真实有效的回调地址")header = callback.headersurl = callback.urlstate = callback.stateresult = CallBackService.state_upload(url=url, state=state, headers=header)return Response.success(msg="使用系统保留的最后一次回调记录测试回调请求", data=result)

Python全局捕获异常

捕获FBException

 在main.py中,配置了该代码,这样系统发生FBException会转移到这个地方

# 全局异常处理中间件
@app.exception_handler(FBException)
async def http_exception_handler(request: Request, exc: FBException):print(f"request_url: {request.url}")return JSONResponse(status_code=500,content={"message": f"{exc.message}", "code": f"{exc.code}"})

 在config-fastbuid时可以触发该异常:

    try:image_utils = ImageUtils(db_docker, db_harbor)except DockerException as exe:print(f"发生异常: {exe}")raise FBException(code=123, message=f"使用提供的docker和harbor信息,进行登录测试,测试失败,请检查,错误信息为{str(exe)}")

注:当前不确定是只有在Controller层中出现的异常可以被全局捕获异常捕获,还是所有的异常都可以被FastBuild捕获。

注:已经验证了,全局捕获异常好像只有在Controler中的异常可以被有效捕获并返回给客户端,系统中触发的异常,无法有效的捕获。

动态导入

 动态导入,是这次重构的一个意外的行为,这是因为自己重新添加了三个controller,结果呢,需要在main.py中将这三个controller依次写入,就非常尴尬,就想着是否可以自动注入。因此搜索之后,通过代码进行解决

image-20240506163151984

注,这要求将所有的controller放在同一个包中。

 在main.py中添加如下的代码:

# 定义一个动态导入控制器的函数
def include_all_routers():controllers_package = 'controller'# 获取 controller 包的路径package = importlib.import_module(controllers_package)path = package.__path__# 迭代 controller 包下的所有模块for _, module_name, _ in pkgutil.iter_modules(path):module = importlib.import_module(f"{controllers_package}.{module_name}")if hasattr(module, 'router'):app.include_router(getattr(module, 'router'))# 调用函数动态包含所有路由器
include_all_routers()

 这样之后的效果就是,我们在controller中新增加了controller接口,系统可以自动识别。

commit一览

[Fix]由于代码重构修改了返回值,而页面在调用构建列表时需要使用task_id,因此重新再构建任务的返回信息中添加了该任务id的信息 songquanheng 2024/4/29 17:17
[MOD]使用importlib自动导入controller下的各个router songquanheng 2024/4/29 16:28
[MOD]将上述数据库查询服务获取全部结果query_all均按照更新时间进行排序 songquanheng 2024/4/29 15:53
[Fix]由于之前的重构调整了state_upload两个参数的顺序,将外部调用的顺序也进行调整。 songquanheng 2024/4/29 15:47
[ADD]完成回调逻辑的添加 songquanheng 2024/4/29 15:40
[MOD]完成默认的headers存入数据库,并且在main.py运行时,从配置文件中读取默认的headers songquanheng 2024/4/29 15:29
[MOD]调整main.py中包含顺序router的顺序,在swagger上以字母顺序展示router songquanheng 2024/4/29 15:11
[MOD]重构函数,将必要的目录存在necessary_dirs_exist()移动到SystemConfig类中 songquanheng 2024/4/29 10:15
[MOD]暂时维持配置文件不变,不引入新的问题 songquanheng 2024/4/28 18:15
[MOD]完成对于FastBuild的配置,发现Bug,将基于harbor和docker的登录验证放入了if之后了。测试登录应该在外层代码中 songquanheng 2024/4/28 18:08
[ADD]在配置文件中添加tls_dir配置,这样在程序使用tls目录时,可以读取 songquanheng 2024/4/28 17:42
[ADD]完成接口config-fastbuild的添加,通过一个接口完成docker、harbor、fastbuild信息的配置 songquanheng 2024/4/28 17:08
[ADD]添加全局捕获异常,并且在反馈给页面的时候获取异常信息提示 songquanheng 2024/4/28 17:03
[FIX]由于引用了PyDantic的类,因此在响应构建任务时,直接返回,重复序列化会报错 songquanheng 2024/4/26 20:53
[FIX]不清楚为什么要在构建的时候,使用新建的image_utils对象,而不能是self.image_utils,新建之后没有报AttributeError: 'APIclient' object has no attribute '_proxy_configs'错 songquanheng 2024/4/26 20:37
[MOD]由于遇到问题,暂时将代码中tls_config的配置移动出来 songquanheng 2024/4/26 19:30
[MOD]修改,因为在DBHost类中的字段名为host_port songquanheng 2024/4/26 18:54
[MOD]更新,优化更新DockerServer时的配置接口响应信息 songquanheng 2024/4/26 17:20
[MOD]由于后增加了registry_dns,添加对于该字段的支持 songquanheng 2024/4/26 17:03
[Fix]将删除多了的db_config重新添加到文件中 songquanheng 2024/4/26 16:44
[DEL]移除不在需要的harbor_config对象,以及HostConfig中的get_static_resource_prefix函数,HarborConfig类中的get_harbor_config_dict songquanheng 2024/4/26 16:27
[MOD]重构代码,将静态资源修改为从数据库中获取,同时在开始创建惊险构建任务时,必须保证已经正确配置了FastBuild服务所在的宿主机IP信息 songquanheng 2024/4/26 16:23
[ADD]添加在FastBuild中想要使用服务,必须首先配置容器所在的宿主机IP地址 songquanheng 2024/4/26 16:06
[DEL]移除配置文件中的[harbor][remote-docker][tls]项 songquanheng 2024/4/26 15:23
[Refactor]由于Harbor的配置位于数据库中,因此读取Harbor配置,并且对于is_alkaid_image的判断逻辑进行修改,同时将docker_server和harbor作为ImageUtils的普通成员,并移除从普通文件中获取remote_docker和harbor的代码 songquanheng 2024/4/26 15:03
[ADD]为DBHarbor添加字段registry_dns,用来存储harbor的域名 songquanheng 2024/4/26 14:53
[ADD]添加DockerServer和Harbor配置进入数据库的功能,其他位置采用防御代码,系统必须首先正确配置DockerServer和Harbor,FastBuild才能正常的工作 songquanheng 2024/4/26 14:41
[MOD]修改代码格式,移除无用的注释代码 songquanheng 2024/4/26 13:47
[ADD]添加DockerServer相关的数据结构和控制接口,在传递文件的时候,表明启用TLS songquanheng 2024/4/26 11:22
[ADD]添加辅助函数,获取目录下的文件列表,另外判断IP是否有效,以及根据IP获取相应的目录名,以保存tls文件 songquanheng 2024/4/26 11:21
[ADD]将Harbor的配置信息移入数据库,不再配置文件中进行。 songquanheng 2024/4/25 16:59
[MOD]当初始运行时,如果发现host信息不为空,则避免重新插入默认的主机信息0.0.0.0 songquanheng 2024/4/25 16:49
[MOD]由于函数名为update_host,因此,不再每次都添加一条主机信息的记录 songquanheng 2024/4/25 15:19
[ADD]添加config_controller,用来统一对系统进行配置和查看 songquanheng 2024/4/25 14:58
[MOD]将配置文件中的[fb]下的host从ip修改为0.0.0.0,在程序运行之后修改主机配置信息,当前仅允许主机IP进行修改,并且更新会重新插入一条主机信息 songquanheng 2024/4/25 14:08
[DEL]由于回调地址由上层确定,因此配置文件中的callback项,不再需要,因此移除 songquanheng 2024/4/24 15:08
[MOD]在回调服务的时候,打印入参,出参,url方便问题的定位 songquanheng 2024/4/24 14:53
[MOD]添加日志打印,方便问题排查 songquanheng 2024/4/22 17:18
[MOD]修改FastBuild容器所在的宿主机主机ip为43 songquanheng 2024/4/22 15:33
[MOD]为阿里云-瑶光添加相应的配置 songquanheng 2024/4/22 15:06
[Fix]在镜像拉取和推送过程中,支持镜像名称和目标镜像名称两个英文: 即172.27.213.154:30003/base/ubuntu:v3的支持。同时在is_alkaid_image中添加云栖的ip前缀支持 songquanheng 2024/3/14 15:30
[MOD]为云栖工程院阿里云部署配置远程docker,本服务所在主机IP,Harbor仓库用户名密码 songquanheng 2024/3/8 14:23

总结

 这次代码重构,其实还是挺不错的体验,一共花费了四天的时间,这样就将FastBuild推到了一个新的状态,依赖较少,这样我觉得下次需要重新部署的时候,可以很方便的,因为FastBuild均可以通过接口进行配置了。

 经过重构之后的FastBuild取得了如下的效果:

  • Docker、Habor、Host的信息从配置文件中移动到数据库中,可以在运行后灵活的更新,并且支持TLS是否开启的灵活配置
  • 使用Jenkins打包时,仅仅需要同一个master分支即可,不需要重新新建分支,并更新各种配置。
  • 回调的测试,可以在接口中实现查询回调结果。并且将header移动到数据库中,在实践的时候使用了sqlite的JSON数据结构,比较方便进行更新。
  • 新增了controller了,可以自动导入,而不需要修改main.py
  • 增加了全局捕获异常,因此对于Controller中的异常,可以比较灵活的返回给用户错误的提示信息。
  • 瑶光镜像判断,也不用写死了,基于config-fastbuild的传入的Harbor的ip和域名进行判断,更加灵活。
  • 目录挂载有效性判断,防止在没有有效挂载目录的情况下,没有日志打印的问题。

注: 言而总之,还是希望每个程序猿可以更多的关注重构,关于重构可以参考如下的文章:

《重构 改善既有代码的设计》之重构,第一个案例详解

《重构 改善既有代码的设计》之重构原则

《重构 改善既有代码的设计》之代码的坏味道

《重构 改善既有代码的设计》之重构列表

Extract Method

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/834864.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

怎么设置付费视频课程_在线教育知识付费系统

在信息爆炸的时代&#xff0c;我们每天都被海量的信息包围。然而&#xff0c;真正有价值、能够让我们快速提升的知识&#xff0c;往往隐藏在这些信息的深海之中。今天&#xff0c;我要为大家介绍的&#xff0c;就是这样一份珍贵的宝藏——我们的付费视频课程。 工具/原料 微信…

手把手教你微调Stable Diffusion

温馨提示 关于本文&#xff1a; 本文你可以学习到完整的不使用webui借助lora和dreambooth微调Stable Diffusion的全过程。 手把手教你微调Stable Diffusion生成优弧&#xff0c;但是半失败版&#x1f602; 关于训练&#xff1a; 单卡32GV100进行的微调&#xff0c;因为一些…

【leetcode】数学位数题总结

涉及题型&#xff1a;两数相加问题、大数溢出等 相加问题 根据题意定义rs的数据结构判断是存储方式是正序还是逆序&#xff0c;如果是正序需要反转 比如 123 12 135是正序&#xff0c; 321 21 135是逆序反转的方式&#xff1a;对于可以从后往前遍历的&#xff08;如字符串…

高效工作之软件系统——数据结构登记表

数据结构模板 开发完软件系统后&#xff0c;往往需要进行一些登记——《软件系统数据结构登记表》 然后软件项目有60个表左右&#xff0c;难道需要手动录入&#xff0c;那肯定不可能 工欲善其事必先利其器&#xff01;go。。。同事给的模板是下图 效果图 于是想到 之前使用…

【mysql】mysql导入导出数据详解

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

初中都没念完的我,是怎么从IT这行坚持下去的...

大家好&#xff0c;我是一名二线&#xff08;伪三线&#xff0c;毕竟连续两年二线城市了&#xff09;的程序员。 现阶段状态在职&#xff0c;28岁&#xff0c;工作了10年左右&#xff0c;码农从事了5年左右&#xff0c;现薪资9k左右。如文章标题所说&#xff0c;初二辍学&…

AVL树的原理及其实现

文章目录 前言了解AVL树AVL树的特点AVL树的节点调整方案右单旋为什么要右单旋呢&#xff1f;右单旋代码 左单旋为什么要左单旋&#xff1f;左单旋代码 左右双旋左右双旋之后平衡因子的情况左右双旋代码实现 右左双旋右左双旋代码&#xff1a; 简单测试 前言 回顾我们对于二叉搜…

Altman确认:神秘Chatbot非GPT-4.5,OpenAI搜索引擎即将上线

&#x1f680; Altman确认&#xff1a;神秘Chatbot非GPT-4.5&#xff0c;OpenAI搜索引擎即将上线 摘要&#xff1a;近日&#xff0c;Sam Altman在哈佛大学的演讲中确认&#xff0c;引发广泛猜测的gpt2-chatbot并非OpenAI即将发布的下一代模型GPT-4.5。与此同时&#xff0c;关于…

亚信安慧AntDB:解锁数智化的新时代

亚信安慧AntDB的融合实时的特性使得它在数据库领域独树一帜。传统的数据库系统往往只能追求数据的准确性和一致性&#xff0c;但在实际的业务场景中&#xff0c;这些特性并不能满足企业的需求。AntDB的出现打破了传统束缚&#xff0c;为企业带来了全新的数据处理方式&#xff0…

低代码审计作业平台:引领企业实现审计高效革命

随着信息化时代的深入发展&#xff0c;审计工作面临着前所未有的挑战与机遇。传统的审计方式往往繁琐复杂&#xff0c;效率低下&#xff0c;已无法满足现代企业对高效、准确、智能的审计需求。在这样的背景下&#xff0c;审计作业低代码平台应运而生&#xff0c;以其独特的优势…

B/S模式的web通信(高并发服务器)

这里写目录标题 目标实现的目标 服务器代码&#xff08;采用epoll实现服务器&#xff09;整体框架main函数init_listen_fd函数&#xff08;负责对lfd初始化的那一系列操作&#xff09;epoll_run函数do_accept函数do_read函数内容补充&#xff1a;http中的getline函数 详解do_re…

【C++初阶】第十站:vector 中通用函数的模拟实现

目录 vector中的三个重要迭代器 默认成员函数 构造函数(无参构造) 构造函数(函数模板) 构造函数(带有默认参数) size_t int 拷贝构造函数 赋值重载 析构函数 迭代器相关函数 begin和end 容量和大小相关函数 size capacity resize 修改容器内容相关函数 reser…

不想让Win系统更新,那就让它暂停一万年

按照下图所示进行操作 winR 输入 regedit&#xff0c;进入注册表编辑器 随后依次点击 HKEY_LOCAL_MACHINE ⬇ SOFTWARE ⬇ Microsoft ⬇ WindowsUpdate ⬇ UX ⬇ Settings 最后在右侧空白处 文件类型 新建DWORD&#xff08;32位&#xff09;值&#xff08;D&#xff09; 命名…

PyQt5的布局管理

文章目录 1.垂直布局和水平布局垂直布局&#xff08;QVBoxLayout&#xff09;&#xff1a;水平布局&#xff08;QHBoxLayout&#xff09;&#xff1a; 2. 布局中的addStrech2.1 我们首先看只有一个Strech的情况&#xff0c;比较容易理解2.2 两个Strech2.3 多个Strech 3.栅格布局…

FPGA HDMI Sensor无线航模摄像头

FPGA方案&#xff0c;接收摄像头sensor 图像数据后&#xff0c;通过HDMI输出到后端 客户应用&#xff1a;无线航模摄像头 主要特性&#xff1a; 1.支持2K以下任意分辨率格式 2.支持多种型号sensor 3.支持自适应摄像头配置&#xff0c;并补齐输出时序 4.可定制功能&#xff…

OpenHarmony 实战开发(南向)-Docker编译环境搭建

Docker环境介绍 OpenHarmony为开发者提供了两种Docker环境&#xff0c;以帮助开发者快速完成复杂的开发环境准备工作。两种Docker环境及适用场景如下&#xff1a; 独立Docker环境&#xff1a;适用于直接基于Ubuntu、Windows操作系统平台进行版本编译的场景。 基于HPM的Docker…

【ArcGIS Pro微课1000例】0058:玩转NetCDF多维数据集

一、NetCDF介绍 NetCDF(network Common Data Form)网络通用数据格式是由美国大学大气研究协会(University Corporation for Atmospheric Research,UCAR)的Unidata项目科学家针对科学数据的特点开发的,是一种面向数组型并适于网络共享的数据的描述和编码标准。NetCDF广泛应…

【Java】Java中栈溢出的常见情况及解决方法

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

Linux实验二:文件IO操作

目录 一、实验目的二、实验内容三、实验环境四、参考代码五、实验步骤步骤1. 编辑程序源代码test2.c步骤2. 编译源代码test2.c步骤3. 编辑源文件alice.txt步骤4. 运行程序test2 六、实验结果七、实验总结 一、实验目的 1、掌握Linux中系统调用、文件描述符的基本概念&#xff…

docker-compose管理jenkins

1.安装docker和compose 1.docker 更新系统&#xff1a;yum update 安装依赖项&#xff1a;yum install -y yum-utils device-mapper-persistent-data lvm2 配置镜像源&#xff1a;yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce…