公司成立之初,业务量较小,一个程序包揽了所有的业务逻辑,此时服务器数量少,上线简单,基本开发-测试-上线都是由开发人员完成。
随着业务量逐渐上升,功能增多,代码量增大,而单一功能上线需要重新编译整个程序,编译时间由原来的几秒到几分甚至几十分钟,一方面效率降低,另一方面横向扩容带来的处理性能提升效果逐渐减弱。所以由大一统拆分出各个子模块,将大而全的程序“微服务”化。
微服务的好处自然是不言而喻,但是许多个服务部署、变更也确实让人头疼。
如何解决这个问题呢?百度一下大把的服务治理、服务标准化、流程化,各种名词层出不穷,但就是没有一个具体的实现,对我这种新手真的很不友好。
所以结合具体公司业务,满足线上需求,实现了一套自动发布系统。
背景
我司开发人员完成新功能,测试通过后,再通过脚本上传程序包、启动进程以及一些其他的操作。
对于不是经常变更的业务来说,这么干确实没什么问题,但是我司已经拆出几百个微服务,部署在几百个主机上,而且这样的操作每天都要进行几十甚至上百次的时候,弊端就显现出来了:
- 编译打包本身耗时很长,再上传到服务器又是漫长的等待
- 发布时面对一堆零散的脚本,老司机虽然游刃有余,但是这些重复性的工作着实让人厌倦
- 突发状况需要回退时,又是一阵操作猛如虎
怎么办呢。。。
打包上传时间长?提前把程序包放在仓库,要用的时候直接拿行不行
一堆离散的脚本?老夫来接管脚本,提供一个统一的调用接口ok不
难回退?版本管理想用哪个版本就用哪个版本
为了实现版本管理和发布,我们需要记录服务的版本信息,服务发布实际上就是将对应的主机和服务的某个版本关联。
现有的主机管理是由前一位大佬搭建,技术栈是 Django 1.10.1 ,很老的版本了,为了快速实现功能就在此基础上开发吧。
说干就干,老夫写代码就是一把梭,ctrl+c、 ctrl+v 已是炉火纯青。
服务版本管理
一个服务会有很多个版本,如果用一张表来记录所有的信息,势必会有很多冗余的字段,所以拆分为服务、服务版本两张表,做外键关联。对应到 django 的 model 如下:
我司的代码由自建的gitlab管理,构建也是直接用了gitlab-ci,所以服务表里记录gitlab对应project的地址
from django.conf import settings
from django.db import models
class MicroService(models.Model):
LANGUAGE_TYPE = (
('cpp', 'cpp'),
('go', 'go'),
('python', 'python')
('other', 'other')
)
name = models.CharField(u'服务名称', max_length=64)
language = models.CharField(u'语言类型', max_length=16, choices=LANGUAGE_TYPE)
build_orig = models.CharField(u'构建来源', max_length=16, default='git')
build_url = models.CharField(u'构建地址', max_length=128) # gitlab or jenkins 的构建地址,用于触发构建任务
description = models.CharField(u'描述', max_length=256, null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='created_by', on_delete=models.DO_NOTHING)
updated = models.DateTimeField(auto_now=True)
updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='updated_by', on_delete=models.DO_NOTHING)
class Meta:
db_table = 'micro_service'
unique_together = (('name', ), )
ordering = ['-created']
通过gitlab触发一个构建任务时,服务的某个版本的构建状态有预备、运行中、成功、失败、超时、重复构建,可以使用枚举来记录状态的变化;版本创建完成后不能修改,所以只有创建人和创建时间
from enum import Enum, unique
@unique
class BuildStatus(Enum):
pending = 0
building = 1
success = 2
timeout = 3
failed = 4
duplicate = 5
class MicroServiceVersion(models.Model):
BUILD_STATUS = tuple([(v.value, k) for k, v in BuildStatus.__members__.items()])
microservice = models.ForeignKey(MicroService, on_delete=models.DO_NOTHING)
version = models.CharField(u'版本', max_length=48) # 版本号格式: 时间戳-提交分支-提交id
description = models.CharField(u'描述', max_length=128, null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING)
status = models.PositiveSmallIntegerField(choices=BUILD_STATUS,default=1)
file_path = models.FilePathField(u'程序包存放路径', max_length=128, null=True, blank=True)
ref = models.CharField(u'构建分支', max_length=128, default='master')
class Meta:
db_table = 'micro_service_version'
unique_together=(('microservice', 'version'),)
ordering = ['-created', 'version']
服务实例
服务发布实际上就是将主机和服务的某个版本关联:
- 部署,在实例表里创建一条主机、服务版本的关联记录
- 升级、回退,修改对应实例的版本
- 删除,删除相关数据
状态
当我们进行部署、升级、回退、删除等操作时,可能由于一些原因导致失败,所以用一个字段来记录实例的状态,方便进行后续的重试等操作。
锁定
假如 A用户 正在对 service_a 的实例进行升级操作,那么其他用户不能操作 service_a 的实例,可以将这些实例全部锁定,A用户的操作完成后再解除锁定
对应的 model :
from asset.models import Asset
@unique
class InstanceStatus(Enum):
running = 0 # 运行中 大部分时候处于此状态
installing = 1
upgrading = 2
reverting = 3
deleting = 4
install_failed = 11
upgrade_failed = 12
revert_failed = 13
delete_failed = 14
class MicroServiceInstance(models.Model):
STATUS = tuple([(v.value, k) for k, v in InstanceStatus.__members__.items()])
microservice = models.ForeignKey(MicroService, on_delete=models.DO_NOTHING)
version = models.ForeignKey(MicroServiceVersion, on_delete=models.DO_NOTHING)
host = models.ForeignKey(Asset, on_delete=models.DO_NOTHING)
port = models.IntegerField(u'端口号', null=True, blank=True, )
tag = models.CharField(u'标签', max_length=64, blank=True, null=True)
weight = models.PositiveSmallIntegerField(u'权重', default=100)
description = models.CharField(u'描述', max_length=128, blank=True, null=True)
created = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING)
status = models.PositiveSmallIntegerField(u'实例状态', choices=STATUS, default=0)
locked = models.BooleanField(u'操作锁定', default=False)
is_maintain = models.BooleanField(u'是否维护中', default=False) # 保留字段
class Meta:
db_table = 'micro_service_instance'
ordering = ['-created']
理清了服务、版本、实例的关系,建好了数据模型,接下来就是视图了。