从0到1开发自动化运维平台Kubernetes集群和应用管理Day3
今天还是一些CRUD操作,继续分享k8s集群和应用的管理。Kubernetes和应用模型class KubernetesCluster(TimeAbstract): """ K8s集群配置 """ name = models.CharField(max_length=100, unique=True, verbose_name="集群名称") version = models.JSONField(default=dict, verbose_name="版本", help_text="{"core": "1.14", "apiversion": "apps/v1"} core: 集群版本 apiversion: API版本") desc = models.TextField(null=True, blank=True, verbose_name="集群描述") config = models.JSONField(default=dict, verbose_name="集群配置") environment = models.ManyToManyField(Environment, related_name="env_k8s", blank=True, verbose_name="环境") product = models.ManyToManyField(Product, related_name="product_k8s", blank=True, verbose_name="产品") idc = models.ForeignKey(Idc, blank=True, null=True, on_delete=models.PROTECT, verbose_name="IDC") def __str__(self): return self.name class ExtMeta: related = True dashboard = True icon = "k8s" class Meta: default_permissions = () ordering = ["-id"] verbose_name = "K8s集群" verbose_name_plural = verbose_name + "管理" def get_default_value(): return { "key": "default", "value": "default" } # 应用部署方式 G_DEPLOY_TYPE = ( ("nonk8s", "非Kubernetes部署"), ("docker", "Docker部署"), ("k8s", "Kubernetes部署") ) G_ONLINE_CHOICE = ( (0, "未上线"), (1, "已上线"), (2, "部署中"), (3, "部署异常"), (9, "已申请上线") ) class MicroApp(TimeAbstract): # product.project.microapp appid = models.CharField(max_length=250, db_index=True, unique=True, verbose_name="应用ID", help_text="应用唯一标识,无需填写") name = models.CharField(max_length=128, verbose_name="应用") alias = models.CharField(max_length=128, blank=True, verbose_name="别名") project = models.ForeignKey(Project, on_delete=models.PROTECT, null=True, blank=True, verbose_name="项目") creator = models.ForeignKey(User, on_delete=models.PROTECT, null=True, blank=True, verbose_name="创建者", help_text="前端不需要传递") repo = models.JSONField(default=dict, verbose_name="仓库地址", help_text="{"name": name, "description": "", "path_with_namespace": "", "http_url_to_repo": url}") target = models.JSONField(default=get_default_value, verbose_name="JAR包配置", help_text="默认:default, {"default": "default", "custom": "xxx/a.war"}") extra_members = models.JSONField(default=get_default_extra_members, verbose_name="额外成员组", help_text="{"name": "自定义成员组1", members: [1,2,3]}") category = models.CharField(max_length=128, blank=True, null=True, verbose_name="应用分类") template = models.JSONField(default=dict, verbose_name="K8sDeployment模板", help_text="Kubernetes Deployment部署模板配置") language = models.CharField(max_length=32, default="java", verbose_name="开发语言") multiple_app = models.BooleanField(default=False, blank=True, verbose_name="多应用标志") multiple_ids = models.JSONField(default=list, verbose_name="多应用关联ID列表") dockerfile = models.JSONField(default=get_default_value, verbose_name="Dockerfile配置", help_text="默认:{default: null}, 可选: {"default|默认": null, "project|使用项目Dockerfile": "project", "custom|自定义Dockerfile": ""}") online = models.BooleanField(default=True, blank=True, verbose_name="上线下线", help_text="应用上线/下线状态标记, 下线状态的应用禁止发布.") desc = models.TextField(verbose_name="描述", null=True, blank=True) notify = models.JSONField(default=dict, verbose_name="消息通知") can_edit = models.JSONField(default=list, verbose_name="管理人员", help_text="有权限编辑该应用的人员ID 格式为数组, 如[1,2]") is_k8s = models.CharField(max_length=8, default="k8s", choices=G_DEPLOY_TYPE, verbose_name="部署方式", help_text=f"默认k8s, 可选: {dict(G_DEPLOY_TYPE)}") modules = models.JSONField(default=list, verbose_name="工程模块") def __str__(self): return "[%s]%s" % (self.name, self.alias) class ExtMeta: related = True dashboard = True icon = "component" class Meta: default_permissions = () ordering = ["-created_time"] verbose_name = "应用" verbose_name_plural = verbose_name + "管理" class AppInfo(TimeAbstract): # uniq_tag: product.project.microapp.env uniq_tag = models.CharField(max_length=128, unique=True, verbose_name="唯一标识", help_text="前端留空,无需传值") app = models.ForeignKey(MicroApp, blank=True, null=True, on_delete=models.PROTECT, verbose_name="应用") environment = models.ForeignKey(Environment, on_delete=models.PROTECT, null=True, verbose_name="环境") branch = models.CharField(max_length=64, blank=True, null=True, verbose_name="默认构建分支") allow_ci_branch = models.JSONField(default=list, verbose_name="允许构建的分支", help_text="存储数组格式,具体的分支名; 默认["*"], 表示允许所有分支.") allow_cd_branch = models.JSONField(default=list, verbose_name="允许发布的分支", help_text="存储数组格式,具体的分支名; 默认["*"], 表示允许所有分支.") build_command = models.CharField(max_length=250, blank=True, null=True, verbose_name="构建命令", help_text="根据应用开发语言, 从getKey("LANGUAGE")获取数据, 取出extra字段的build值") kubernetes = models.ManyToManyField(KubernetesCluster, related_name="k8s_app", through="KubernetesDeploy", verbose_name="K8s集群") hosts = models.JSONField(default=list, verbose_name="部署主机", help_text="部署主机, 格式: []") template = models.JSONField(default=dict, verbose_name="K8sDeployment模板", help_text="继承自当前应用的template字段,数据格式为对象 字段说明: type: 0|1, 0表示继承应用模板,template为空字典;1表示自定义模板 示例: {"type": 0, "template": {}}") # {0: 禁用, 1: 启用} is_enable = models.SmallIntegerField(default=1, verbose_name="启用", help_text="状态 {0: 禁用, 1: 启用},默认值为1") desc = models.TextField(verbose_name="描述", null=True, blank=True) can_edit = models.JSONField(default=list, verbose_name="管理人员", help_text="有权限编辑该应用的人员ID 格式为数组, 如[1,2]") online = models.SmallIntegerField(default=0, choices=G_ONLINE_CHOICE, verbose_name="是否上线", help_text=f"默认为0,即未上线 可选项: {G_ONLINE_CHOICE}") def __str__(self): return self.uniq_tag @property def namespace(self): return f"{self.environment.name.replace("_", "-")}-{self.app.project.name.replace("_", "-")}".lower() @property def jenkins_jobname(self): try: job_name = f"{self.environment.name}-{self.app.category.split(".")[-1]}-{self.app.project.name}-{self.app.name.split(".")[-1]}".lower() except AppInfo.DoesNotExist: job_name = "" return job_name class ExtMeta: related = True dashboard = True class Meta: default_permissions = () ordering = ["-update_time", "-id"] verbose_name = "应用模块" verbose_name_plural = verbose_name + "管理" class KubernetesDeploy(TimeAbstract): appinfo = models.ForeignKey(AppInfo, related_name="app_info", null=True, on_delete=models.CASCADE) kubernetes = models.ForeignKey(KubernetesCluster, related_name="app_k8s", null=True, on_delete=models.CASCADE) online = models.SmallIntegerField(default=0, choices=G_ONLINE_CHOICE, verbose_name="是否上线", help_text=f"默认为0,即未上线 可选项: {G_ONLINE_CHOICE}") version = models.CharField(max_length=250, blank=True, null=True, verbose_name="当前版本") def __str__(self): return "%s-%s" % (self.appinfo.app.appid, self.kubernetes.name) class Meta: default_permissions = () class DataDict(CommonParent): key = models.CharField(max_length=80, unique=True, verbose_name="键") value = models.CharField(max_length=80, verbose_name="值") extra = models.TextField(null=True, blank=True, default="", verbose_name="额外参数") desc = models.CharField(max_length=255, blank=True, null=True, verbose_name="备注") def __str__(self): return self.value class Meta: default_permissions = () verbose_name = "字典" verbose_name_plural = verbose_name + "管理" 编写序列化器import json from typing import List from rest_framework import serializers from django.db import transaction from django.contrib.auth.models import User from cmdb.models import Product, Project, Environment, KubernetesCluster, MicroApp, AppInfo, KubernetesDeploy class KubernetesClusterListSerializers(serializers.ModelSerializer): config = serializers.SerializerMethodField() def get_config(self, instance): return json.loads(instance.config) class Meta: model = KubernetesCluster fields = "__all__" class KubernetesClusterSerializers(serializers.ModelSerializer): class Meta: model = KubernetesCluster fields = "__all__" class MicroAppListSerializers(serializers.ModelSerializer): project_info = serializers.SerializerMethodField() appinfo = serializers.SerializerMethodField() creator_info = serializers.SerializerMethodField() extra_team_info = serializers.SerializerMethodField() def get_project_info(self, instance): project = instance.project return {"project": {"id": project.id, "alias": project.alias}, "product": {"id": project.product.id, "alias": project.product.alias}} def get_appinfo(self, instance): return [ {"id": i.id, "env_alias": i.environment.alias, "env": {"name": i.environment.name, "id": i.environment.id}, "online": i.online} for i in instance.appinfo_set.all()] def get_creator_info(self, instance): try: return {"id": instance.creator.id, "first_name": instance.creator.first_name, "username": instance.creator.username} except BaseException as e: return {"id": "", "first_name": "", "username": ""} def get_extra_team_info(self, instance): data = {} for k, v in instance.extra_members.items(): data[k] = [ {"id": i.id, "name": i.name, "first_name": i.first_name, "username": i.username} for i in User.objects.filter(id__in=v) ] return data class Meta: model = MicroApp fields = "__all__" class MicroAppSerializers(serializers.ModelSerializer): class Meta: model = MicroApp fields = "__all__" read_only_fields = ("appid",) @staticmethod def perform_extend_save(validated_data): def default_value(fields: List): for field in fields: if validated_data.get(field): if validated_data[field].get("key") != "custom": validated_data[field]["value"] = validated_data[field]["key"] return validated_data validated_data = default_value(["dockerfile", "target"]) validated_data[ "appid"] = f"{validated_data["project"].product.name}.{validated_data["project"].name}.{validated_data["name"]}" return validated_data def create(self, validated_data): instance = MicroApp.objects.create(can_edit=[validated_data["creator"].id], **self.perform_extend_save(validated_data)) return instance def update(self, instance, validated_data): return super().update(instance, self.perform_extend_save(validated_data)) class AppInfoListSerializers(serializers.ModelSerializer): app = MicroAppSerializers() kubernetes_info = serializers.SerializerMethodField() def get_kubernetes_info(self, instance): serializer = KubernetesDeploySerializers(data=KubernetesDeploy.objects.filter(appinfo=instance.id), many=True) serializer.is_valid() return serializer.data class Meta: model = AppInfo fields = "__all__" class AppInfoSerializers(serializers.ModelSerializer): class Meta: model = AppInfo fields = "__all__" def perform_extend_save(self, validated_data, *args, **kwargs): if validated_data.get("app", None) and validated_data.get("environment", None): validated_data[ "uniq_tag"] = f"{validated_data["app"].appid}.{validated_data["environment"].name.split("_")[-1].lower()}" if kwargs.get("instance", None): kubernetes = self.initial_data.get("kubernetes") _bulk = [] for kid in kubernetes: _ks = KubernetesCluster.objects.get(id=kid) _bulk.append(KubernetesDeploy(appinfo=kwargs["instance"], kubernetes=_ks)) KubernetesDeploy.objects.bulk_create(_bulk, ignore_conflicts=True) return validated_data @transaction.atomic def create(self, validated_data): instance = AppInfo.objects.create(**self.perform_extend_save(validated_data)) if "kubernetes" in self.initial_data: self.perform_extend_save(validated_data, **{"instance": instance}) return instance @transaction.atomic def update(self, instance, validated_data): KubernetesDeploy.objects.filter(appinfo=instance).delete() instance.__dict__.update(**self.perform_extend_save(validated_data, **{"instance": instance})) instance.save() return instance 编写视图from rest_framework.response import Response from rest_framework.decorators import action from django.db import transaction from cmdb.models import Product, Project, Environment, KubernetesCluster, MicroApp, AppInfo from cmdb.serializers import AppInfoListSerializers, AppInfoSerializers, KubernetesClusterListSerializers, KubernetesClusterSerializers, MicroAppListSerializers, MicroAppSerializers from common.extends.decorators import cmdb_app_unique_check class KubernetesClusterViewSet(viewsets.ModelViewSet): """ Kubernetes集群视图 ### Kubernetes集群权限 {"*": ("k8scluster_all", "k8s集群管理")}, {"get": ("k8scluster_list", "查看k8s集群")}, {"post": ("k8scluster_create", "创建k8s集群")}, {"put": ("k8scluster_edit", "编辑k8s集群")}, {"patch": ("k8scluster_edit", "编辑k8s集群")}, {"delete": ("k8scluster_delete", "删除k8s集群")} """ perms_map = ( {"*": ("admin", "管理员")}, {"*": ("k8scluster_all", "k8s集群管理")}, {"get": ("k8scluster_list", "查看k8s集群")}, {"post": ("k8scluster_create", "创建k8s集群")}, {"put": ("k8scluster_edit", "编辑k8s集群")}, {"patch": ("k8scluster_edit", "编辑k8s集群")}, {"delete": ("k8scluster_delete", "删除k8s集群")} ) queryset = KubernetesCluster.objects.all() serializer_class = KubernetesClusterSerializers def get_serializer_class(self): if self.action in ["list", "retrieve"]: return KubernetesClusterListSerializers return KubernetesClusterSerializers class MicroAppViewSet(viewsets.ModelViewSet): """ 项目应用视图 ### 项目应用权限 {"*": ("microapp_all", "应用管理")}, {"get": ("microapp_list", "查看应用")}, {"post": ("microapp_create", "创建应用")}, {"put": ("microapp_edit", "编辑应用")}, {"patch": ("microapp_edit", "编辑应用")}, {"delete": ("microapp_delete", "删除应用")} """ perms_map = ( {"*": ("admin", "管理员")}, {"*": ("microapp_all", "应用管理")}, {"get": ("microapp_list", "查看应用")}, {"post": ("microapp_create", "创建应用")}, {"put": ("microapp_edit", "编辑应用")}, {"patch": ("microapp_edit", "编辑应用")}, {"delete": ("microapp_delete", "删除应用")} ) queryset = MicroApp.objects.all() serializer_class = MicroAppSerializers def get_serializer_class(self): if self.action in ["list", "retrieve"]: return MicroAppListSerializers return MicroAppSerializers @cmdb_app_unique_check() def create(self, request, *args, **kwargs): """ 创建应用 提交参数 创建:{} """ try: request.data["name"] = request.data["name"].strip(" ").replace(" ", "-") except BaseException as e: logger.error("exception ", str(e)) serializer = self.get_serializer(data=request.data) if not serializer.is_valid(): return Response({"code": 40000, "status": "failed", "message": serializer.errors}) try: self.perform_create(serializer) except BaseException as e: logger.error(e) return Response({"code": 50000, "status": "failed", "message": str(e)}) data = {"data": serializer.data, "status": "success", "code": 20000} return Response(data) @transaction.atomic def perform_create(self, serializer): serializer.save(creator=self.request.user) @action(methods=["POST"], url_path="related", detail=False) def app_related(self, request): """ 应用关联 ### 传递参数: ids: 待关联应用id数组 target: 目标应用id """ try: target = request.data.get("target", None) ids = request.data.get("ids", None) if target: instance = self.queryset.get(id=target) ids.extend(instance.multiple_ids) self.queryset.filter(id__in=list(set(ids))).update(multiple_app=True, multiple_ids=list(set(ids))) return Response({"code": 20000, "data": "应用关联成功."}) except BaseException as e: logger.error("err", e) return Response({"code": 50000, "data": "关联应用异常,请联系管理员!"}) @action(methods=["POST"], url_path="unrelated", detail=False) def app_unrelated(self, request): """ 取消应用关联 ### 传递参数: id: 应用id """ try: instance = self.queryset.filter(id=request.data.get("id")) # 获取关联应用ID列表 ids = instance[0].multiple_ids ids.remove(instance[0].id) if len(ids) == 1: # 如果关联应用只剩下一个,则一起取消关联 self.queryset.filter(id__in=instance[0].multiple_ids).update(multiple_app=False, multiple_ids=[]) else: # 更新其它应用的关联应用ID self.queryset.filter(id__in=ids).update(multiple_ids=ids) # 取消当前实例应用关联 instance.update(multiple_app=False, multiple_ids=[]) return Response({"code": 20000, "data": "应用取消关联成功."}) except BaseException as e: return Response({"code": 50000, "data": "关联应用异常,请联系管理员!"}) class AppInfoViewSet(viewsets.ModelViewSet): """ 项目应用服务 * 服务对应着应用的不同环境,即应用每个环境创建一个对应的服务 ### 项目应用服务权限 {"*": ("microapp_all", "应用管理")}, {"get": ("microapp_list", "查看应用")}, {"post": ("microapp_create", "创建应用")}, {"put": ("microapp_edit", "编辑应用")}, {"patch": ("microapp_edit", "编辑应用")}, {"delete": ("microapp_delete", "删除应用")} """ perms_map = ( {"*": ("admin", "管理员")}, {"*": ("microapp_all", "应用管理")}, {"get": ("microapp_list", "查看应用")}, {"post": ("microapp_create", "创建应用")}, {"put": ("microapp_edit", "编辑应用")}, {"patch": ("microapp_edit", "编辑应用")}, {"delete": ("microapp_delete", "删除应用")} ) queryset = AppInfo.objects.all() serializer_class = AppInfoSerializers def get_serializer_class(self): if self.action in ["list", "retrieve"]: return AppInfoListSerializers return AppInfoSerializers def create(self, request, *args, **kwargs): request.data["uniq_tag"] = "default" serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) self.perform_create(serializer) data = serializer.data data = {"code": 20000, "status": 200, "data": data} return Response(data) 添加装饰器
细心的朋友应该可以发现,在视图里文件里导入一个自己编写的装饰器
from common.extends.decorators import cmdb_app_unique_check
我们先在项目根下创建common目录(venv) ➜ ydevops-backend mkdir -p common/extends (venv) ➜ ydevops-backend touch common/extends/decorators.py
然后编写装饰器cmdb_app_unique_checkfrom functools import wraps from rest_framework.response import Response from cmdb.models import MicroApp def cmdb_app_unique_check(): """ 应用唯一性检查 appid: {product.name}.{app.name} """ def check_app(product, name): try: if MicroApp.objects.filter(project__product__id=product, name=name).exists(): # 存在应用 return True except BaseException as e: pass return False def decorator(func): @wraps(func) def wrapper(self, request, *args, **kwargs): if check_app(request.data["product"], request.data["name"]): return Response({"code": 50000, "message": f"该产品下已存在[{request.data["name"]}]同名应用."}) return func(self, request, *args, **kwargs) return wrapper return decorator 添加路由from django.contrib import admin from django.urls import path, include from cmdb.view.view_cmdb import AppInfoViewSet, KubernetesClusterViewSet, MicroAppViewSet from rest_framework.routers import DefaultRouter from cmdb.views import RegionViewSet, IdcViewSet, ProductViewSet, ProjectViewSet, EnvironmentViewSet router = DefaultRouter() router.register("region", RegionViewSet) router.register("asset/idc", IdcViewSet) router.register("product", ProductViewSet) router.register("project", ProjectViewSet) router.register("environment", EnvironmentViewSet) router.register("app/service", AppInfoViewSet) router.register("app", MicroAppViewSet) router.register("kubernetes", KubernetesClusterViewSet) urlpatterns = [ path("admin/", admin.site.urls), path("api/", include(router.urls)), ]迁移数据表(venv) ➜ ydevops-backend python manage.py makemigrations Migrations for "cmdb": apps/cmdb/migrations/0003_appinfo_kubernetescluster_microapp_kubernetesdeploy_and_more.py - Create model AppInfo - Create model KubernetesCluster - Create model MicroApp - Create model KubernetesDeploy - Add field app to appinfo - Add field environment to appinfo - Add field kubernetes to appinfo (venv) ➜ ydevops-backend python manage.py makemigrations Migrations for "cmdb": apps/cmdb/migrations/0004_datadict.py - Create model DataDict (venv) ➜ ydevops-backend python manage.py migrate Operations to perform: Apply all migrations: admin, auth, cmdb, contenttypes, sessions Running migrations: Applying cmdb.0003_appinfo_kubernetescluster_microapp_kubernetesdeploy_and_more... OK Applying cmdb.0004_datadict... OK运行项目(venv) ➜ ydevops-backend python manage.py runserver 0.0.0.0:9000 Watching for file changes with StatReloader Performing system checks... System check identified no issues (0 silenced). March 23, 2023 - 22:13:47 Django version 4.1.7, using settings "devops_backend.settings" Starting development server at http://0.0.0.0:9000/ Quit the server with CONTROL-C.
打开浏览器访问http://localhost:9000/api/就可以看到目前已完成的接口了