Skip to content

Deployment管理 原创

1、功能

56659fd6b9ea3032018be40559afc28c MD5

2、类型转换

2.1、实现类型转换

internal\pkg\k8s\deployment\common.go 文件中新增 DeploymentCell,实现和 appv1.Deployment 进行类型转换,如下:

go
package deployment

import (
	"time"

	"github.com/joker-bai/hawkeye/internal/pkg/k8s/dataselect"
	appv1 "k8s.io/api/apps/v1"
)

type DeploymentCell appv1.Deployment

func (d DeploymentCell) GetName() string {
	return d.Name
}

func (d DeploymentCell) GetCreation() time.Time {
	return d.CreationTimestamp.Time
}

// 类型转换
func toCells(deploys []appv1.Deployment) []dataselect.DataCell {
	cells := make([]dataselect.DataCell, len(deploys))
	for i, d := range deploys {
		cells[i] = DeploymentCell(d)
	}
	return cells
}

func fromCells(cells []dataselect.DataCell) []appv1.Deployment {
	deploys := make([]appv1.Deployment, len(cells))
	for i := range cells {
		deploys[i] = appv1.Deployment(cells[i].(DeploymentCell))
	}
	return deploys
}

2.2、实现增删改查

(1)创建 internal/pkg/k8s/deployment/create.go 文件,输入以下内容,用于创建 deployment

go
package deployment

import (
	"context"
	"github.com/joker-bai/hawkeye/global"
	"github.com/joker-bai/hawkeye/internal/app/requests"
	appv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
	"strings"
)

const (
	DescriptionAnnotationKey = "description"
)

func CreateDeployment(deploy *requests.K8sDeploymentCreateRequest) error {
	annotations := map[string]string{}
	if deploy.Description != nil {
		annotations[DescriptionAnnotationKey] = *deploy.Description
	}

	labels := getLabelsMap(deploy.Labels)

	metadata := metav1.ObjectMeta{
		Annotations: annotations,
		Labels:      labels,
		Name:        deploy.Name,
	}

	containerSpec := corev1.Container{
		Name:  deploy.Name,
		Image: deploy.ContainerImage,
		SecurityContext: &corev1.SecurityContext{
			Privileged: &deploy.RunAsPrivileged,
		},
		Resources: corev1.ResourceRequirements{Requests: make(map[corev1.ResourceName]resource.Quantity)},
		Env:       convertEnvVarsSpec(deploy.Variables),
	}

	if deploy.ContainerCommand != nil {
		containerSpec.Command = []string{*deploy.ContainerCommand}
	}
	if deploy.ContainerCommandArgs != nil {
		containerSpec.Args = strings.Fields(*deploy.ContainerCommandArgs)
	}
	if deploy.MemoryRequirement != nil {
		containerSpec.Resources.Requests[corev1.ResourceMemory] = resource.MustParse(*deploy.MemoryRequirement)
	}
	if deploy.CpuRequirement != nil {
		containerSpec.Resources.Requests[corev1.ResourceCPU] = resource.MustParse(*deploy.CpuRequirement)
	}

	// 是否开启Readiness健康检测
	if deploy.IsReadinessEnable {
		probeHandler := getContainerProbe(deploy.ReadinessProbe)
		containerSpec.ReadinessProbe = &corev1.Probe{
			ProbeHandler:        probeHandler,
			InitialDelaySeconds: deploy.ReadinessProbe.InitialDelaySeconds,
			TimeoutSeconds:      deploy.ReadinessProbe.TimeoutSeconds,
			PeriodSeconds:       deploy.ReadinessProbe.PeriodSeconds,
			SuccessThreshold:    deploy.ReadinessProbe.SuccessThreshold,
			FailureThreshold:    deploy.ReadinessProbe.FailureThreshold,
		}
	}
	if deploy.IsLivenessEnable {
		probehandler := getContainerProbe(deploy.ReadinessProbe)

		containerSpec.LivenessProbe = &corev1.Probe{
			ProbeHandler:        probehandler,
			InitialDelaySeconds: deploy.LivenessProbe.InitialDelaySeconds,
			TimeoutSeconds:      deploy.LivenessProbe.TimeoutSeconds,
			PeriodSeconds:       deploy.LivenessProbe.PeriodSeconds,
			SuccessThreshold:    deploy.LivenessProbe.SuccessThreshold,
			FailureThreshold:    deploy.LivenessProbe.FailureThreshold,
		}
	}

	podSpec := corev1.PodSpec{Containers: []corev1.Container{containerSpec}}

	if deploy.ImagePullSecret != nil {
		podSpec.ImagePullSecrets = []corev1.LocalObjectReference{{Name: *deploy.ImagePullSecret}}
	}

	podTemplate := corev1.PodTemplateSpec{
		ObjectMeta: metadata,
		Spec:       podSpec,
	}

	deployment := &appv1.Deployment{
		ObjectMeta: metadata,
		Spec: appv1.DeploymentSpec{
			Replicas: &deploy.Replicas,
			Template: podTemplate,
			Selector: &metav1.LabelSelector{
				MatchLabels: labels,
			},
		},
	}

	if _, err := global.K8S.AppsV1().Deployments(deploy.Namespace).Create(context.TODO(), deployment, metav1.CreateOptions{}); err != nil {
		return err
	}

	// 如果需要创建Service,执行以下代码

	if deploy.IsCreateService {
		// TODO
	}

	return nil
}

func getLabelsMap(labels []requests.Label) map[string]string {
	res := map[string]string{}
	for _, label := range labels {
		res[label.Key] = label.Value
	}
	return res
}

func convertEnvVarsSpec(envs []requests.EnvironmentVariable) []corev1.EnvVar {
	var res []corev1.EnvVar
	for _, env := range envs {
		res = append(res, corev1.EnvVar{Name: env.Name, Value: env.Value})
	}
	return res
}

func getContainerProbe(probe requests.HealthCheckDetail) corev1.ProbeHandler {
	var probehandler corev1.ProbeHandler
	switch probe.Type {
	case "HTTP":
		var httpHeader []corev1.HTTPHeader
		if len(probe.HttpHeader) > 0 {
			for _, header := range probe.HttpHeader {
				h := corev1.HTTPHeader{
					Name:  header.Name,
					Value: header.Value,
				}
				httpHeader = append(httpHeader, h)
			}
		}
		probehandler.HTTPGet = &corev1.HTTPGetAction{
			Path: probe.Path,
			Port: intstr.IntOrString{
				Type:   0,
				IntVal: probe.Port,
			},
			Scheme:      corev1.URIScheme(probe.Protocol),
			HTTPHeaders: httpHeader,
		}
	case "TCP":
		probehandler.TCPSocket = &corev1.TCPSocketAction{
			Port: intstr.IntOrString{
				Type:   0,
				IntVal: probe.Port,
			},
		}
	case "EXEC":
		probehandler.Exec = &corev1.ExecAction{
			Command: []string{probe.Command},
		}
	}
	return probehandler
}

(2)在 internal/pkg/k8s/deployment/delete.go 中创建以下内容,用户删除 deployment

go
package deployment

import (
	"context"
	"github.com/joker-bai/hawkeye/global"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func DeleteDeployment(name, namespace string) error {
	if err := global.K8S.AppsV1().Deployments(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}); err != nil {
		return err
	}
	return nil
}

(3)在 internal/pkg/k8s/deployment/list.go 中创建以下内容,用于列出 deployment 列表

go
package deployment

import (
	"context"

	"github.com/joker-bai/hawkeye/global"
	"github.com/joker-bai/hawkeye/internal/pkg/k8s/dataselect"
	appv1 "k8s.io/api/apps/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func GetDeploymentList(name, namespace string, page, limit int) ([]appv1.Deployment, error) {
	deploys, err := global.K8S.AppsV1().Deployments(namespace).List(context.TODO(), metav1.ListOptions{})
	if err != nil {
		return nil, err
	}

	// 做排序
	selector := dataselect.DataSelector{
		GenericDataList: toCells(deploys.Items),
		DataSelectQuery: &dataselect.DataSelectQuery{
			Filter: &dataselect.FilterQuery{
				Name: name,
			},
			Paginate: &dataselect.PaginateQuery{
				Limit: limit,
				Page:  page,
			},
		},
	}

	filted := selector.Filter()
	data := filted.Sort().Paginate()
	return fromCells(data.GenericDataList), nil
}

(4)在 internal/pkg/k8s/deployment/update.go 中创建以下内容,用于更新 deployment

go
package deployment

import (
	"context"
	"encoding/json"
	"github.com/joker-bai/hawkeye/global"
	appv1 "k8s.io/api/apps/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func UpdateDeployment(namespace, content string) error {
	var deploy appv1.Deployment
	if err := json.Unmarshal([]byte(content), &deploy); err != nil {
		return err
	}
	if _, err := global.K8S.AppsV1().Deployments(namespace).Update(context.TODO(), &deploy, metav1.UpdateOptions{}); err != nil {
		return err
	}
	return nil
}

(5)在 internal/pkg/k8s/deployment/detail.go 中创建以下内容,用于获取 deployment 详情

go
package deployment

import (
	"context"

	"github.com/joker-bai/hawkeye/global"
	appv1 "k8s.io/api/apps/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func GetDeploymentDetail(name, namespace string) (*appv1.Deployment, error) {
	deploy, err := global.K8S.AppsV1().Deployments(namespace).Get(context.TODO(), name, metav1.GetOptions{})
	if err != nil {
		return nil, err
	}
	return deploy, nil
}

(6)在 internal/pkg/k8s/deployment/restart.go 中创建以下内容,用于重启 Deployment

go
package deployment

import (
	"context"
	"encoding/json"
	"github.com/joker-bai/hawkeye/global"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"strconv"
	"time"
)

func RestartDeployment(name, namespace string) error {
	// 组装数据
	patchData := map[string]interface{}{
		"spec": map[string]interface{}{
			"template": map[string]interface{}{
				"containers": []map[string]interface{}{
					{
						"name": name,
						"env": []map[string]string{
							{
								"name":  "RESTART_",
								"value": strconv.FormatInt(time.Now().Unix(), 10),
							},
						},
					},
				},
			},
		},
	}

	patchBytes, err := json.Marshal(patchData)
	if err != nil {
		return err
	}

	// 调用patch更新Deployment
	_, err = global.K8S.AppsV1().Deployments(namespace).Patch(context.TODO(), name, types.MergePatchType, patchBytes, metav1.PatchOptions{})
	if err != nil {
		return err
	}
	return nil
}

(7)在 internal/pkg/k8s/deployment/scale.go 中增加以下内容,用于伸缩 Deployment

go
package deployment

import (
	"context"
	"github.com/joker-bai/hawkeye/global"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func ScaleDeployment(name, namespace string, scaleNum int32) error {
	// 获取当前副本数
	scale, err := global.K8S.AppsV1().Deployments(namespace).GetScale(context.TODO(), name, metav1.GetOptions{})
	if err != nil {
		return err
	}
	// 修改副本数
	scale.Spec.Replicas = scaleNum
	// 更新副本数
	if _, err := global.K8S.AppsV1().Deployments(namespace).UpdateScale(context.TODO(), name, scale, metav1.UpdateOptions{}); err != nil {
		return err
	}
	return nil
}

(8)在 internal/pkg/k8s/deployment/rollback.go 中新增以下内容,用于回滚 Deployment

go
package deployment

import (
	"context"
	"github.com/joker-bai/hawkeye/global"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// 回滚

func RollbackDeployment(name, namespace, replicaSet string) error {
	d, err := global.K8S.AppsV1().Deployments(namespace).Get(context.TODO(), name, metav1.GetOptions{})
	if err != nil {
		return err
	}

	// 获取replicaSet
	replicaSets, err := GetDeploymentReplicaSet(name, namespace)
	if err != nil {
		return err
	}

	for _, rs := range replicaSets {
		if rs.ObjectMeta.Name == replicaSet {
			d.Spec.Template = rs.Spec.Template
			_, err = global.K8S.AppsV1().Deployments(namespace).Update(context.TODO(), d, metav1.UpdateOptions{})
			if err != nil {
				return nil
			}
		}
	}

	return nil
}

(9)在 internal/pkg/k8s/deployment/replicaset.go 中新增以下内容,用户获取 ReplicaSet

go
package deployment

import (
	"context"
	"github.com/joker-bai/hawkeye/global"
	appv1 "k8s.io/api/apps/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// 获取ReplicaSet

func GetDeploymentReplicaSet(name, namespace string) ([]appv1.ReplicaSet, error) {
	d, err := global.K8S.AppsV1().Deployments(namespace).Get(context.TODO(), name, metav1.GetOptions{})
	if err != nil {
		return nil, err
	}
	// 先获取labelSelector
	label := d.Spec.Selector.String()

	rsl, err := global.K8S.AppsV1().ReplicaSets(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: label})
	if err != nil {
		return nil, err
	}

	return rsl.Items, nil
}

(10)在 internal/pkg/k8s/deployment/pod.go 中新增以下内容,用户获取 Deployment 的 Pod

go
package deployment

import (
	"context"
	"github.com/joker-bai/hawkeye/global"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func GetDeploymentPod(name, namespace string) ([]corev1.Pod, error) {
	d, err := global.K8S.AppsV1().Deployments(namespace).Get(context.TODO(), name, metav1.GetOptions{})
	if err != nil {
		return nil, err
	}
	// 先获取labelSelector
	label := d.Spec.Selector.String()

	var pods *corev1.PodList
	// 根据label获取Pod
	pods, err = global.K8S.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: label})
	if err != nil {
		return nil, err
	}
	return pods.Items, nil
}

3、实现 services 方法

3.1、请求参数校验

internal/app/requests 目录中新建 k8s_deployment.go 文件,写入以下内容以完成请求参数校验:

go
package requests

import (
	"github.com/gin-gonic/gin"
	"github.com/joker-bai/hawkeye/pkg/app"
	"github.com/thedevsaddam/govalidator"
)

type K8sDeploymentCreateRequest struct {
	Name                 string                `json:"name" valid:"name"`                                     // Deployment的名字
	ContainerImage       string                `json:"container_image" valid:"container_image"`               // 容器镜像名
	ImagePullSecret      *string               `json:"image_pull_secret" valid:"image_pull_secret"`           // 拉取镜像的密钥
	ContainerCommand     *string               `json:"container_command" valid:"container_command"`           // 容器启动时执行的命令
	ContainerCommandArgs *string               `json:"container_command_args" valid:"container_command_args"` // 容器启动时执行命令的参数
	Replicas             int32                 `json:"replicas" valid:"replicas"`                             // 副本数
	PortMappings         []PortMapping         `json:"port_mappings" valid:"port_mappings"`                   // 容器端口
	Variables            []EnvironmentVariable `json:"variables" valid:"variables"`                           // 环境变量
	IsCreateService      bool                  `json:"is_create_service" valid:"is_create_service"`           // 是否创建Service
	Description          *string               `json:"description" valid:"description"`                       // 描述
	Namespace            string                `json:"namespace" valid:"namespace"`                           // 名称空间
	MemoryRequirement    *string               `json:"memory_requirement" valid:"memory_requirement"`         // 内存
	CpuRequirement       *string               `json:"cpu_requirement" valid:"cpu_requirement"`               // CPU
	Labels               []Label               `json:"labels" valid:"labels"`                                 // 标签
	RunAsPrivileged      bool                  `json:"run_as_privileged" valid:"run_as_privileged"`           // 是否特权用户运行容器
	IsReadinessEnable    bool                  `json:"is_readiness_enable" valid:"is_readiness_enable"`       // 是否开启ReadinessProbe健康检查
	ReadinessProbe       HealthCheckDetail     `json:"readiness_probe" valid:"readiness_probe"`               // ReadinessProbe配置
	IsLivenessEnable     bool                  `json:"is_liveness_enable" valid:"is_liveness_enable"`         // 是否开启LivenessProbe健康检查
	LivenessProbe        HealthCheckDetail     `json:"liveness_probe" valid:"liveness_probe"`                 // LivenessProbe 主要配置
}

type HealthCheckDetail struct {
	Type                string       `json:"type" valid:"type"`                                   // 检测方式:HTTP,TCP,Commond
	Protocol            string       `json:"protocol" valid:"protocol"`                           // 协议
	Path                string       `json:"path" valid:"path"`                                   // 路径
	Port                int32        `json:"port" valid:"port"`                                   // 端口
	HttpHeader          []HttpHeader `json:"http_header" valid:"http_header"`                     // HTTP头
	InitialDelaySeconds int32        `json:"initial_delay_seconds" valid:"initial_delay_seconds"` // 延迟探测时间
	PeriodSeconds       int32        `json:"period_seconds" valid:"period_seconds"`               // 执行探测频率
	TimeoutSeconds      int32        `json:"timeout_seconds" valid:"timeout_seconds"`             // 超时时间
	SuccessThreshold    int32        `json:"success_threshold" valid:"success_threshold"`         // 健康阈值
	FailureThreshold    int32        `json:"failure_threshold" valid:"success_threshold"`         // 不健康阈值
	Command             string       `json:"command" valid:"command"`                             // 命令行:当检测方式为Commond时用
}

type HttpHeader struct {
	Name  string `json:"name" valid:"name"`
	Value string `json:"value" valid:"value"`
}

type PortMapping struct {
	Port       int32  `json:"port" valid:"port" `              // 暴露端口
	TargetPort int32  `json:"target_port" valid:"target_port"` // 目标端口
	Protocol   string `json:"protocol" valid:"protocol"`       // 协议
}

type EnvironmentVariable struct {
	Name  string `json:"name" valid:"value"`  // Key
	Value string `json:"value" valid:"value"` // Value
}

type Label struct {
	Key   string `json:"key" valid:"key"`     // Key
	Value string `json:"value" valid:"value"` // Value
}

func ValidK8sDeploymentCreateRequest(data interface{}, ctx *gin.Context) map[string][]string {
	rules := govalidator.MapData{
		"name":            []string{"required"},
		"namespace":       []string{"required"},
		"container_image": []string{"required"},
		"replicas":        []string{"required"},
	}

	messages := govalidator.MapData{
		"namespace": []string{
			"required: namespace不能为空",
		},
		"name": []string{
			"required: name不能为空",
		},
		"container_image": []string{
			"required: image不能为空",
		},
		"replicas": []string{
			"required: replicas不能为空",
		},
	}

	return app.ValidateOptions(data, rules, messages)
}

type K8sDeploymentUpdateRequest struct {
	Namespace string `json:"namespace" valid:"namespace"`
	Content   string `json:"content" valid:"content"`
}

func ValidK8sDeploymentUpdateRequest(data interface{}, ctx *gin.Context) map[string][]string {
	rules := govalidator.MapData{
		"namespace": []string{"required"},
		"content":   []string{"required"},
	}
	messages := govalidator.MapData{
		"namespace": []string{
			"required: namespace 不能为空",
		},
		"content": []string{
			"required: content 不能为空",
		},
	}

	// 校验入参

	return app.ValidateOptions(data, rules, messages)
}

type K8sDeploymentListRequest struct {
	K8sCommonRequest
	Page  int `json:"page" valid:"page" valid:"page"`    // 页数
	Limit int `json:"limit" valid:"limit" valid:"limit"` // 每页条数
}

func ValidK8sDeploymentListRequest(data interface{}, ctx *gin.Context) map[string][]string {
	rules := govalidator.MapData{
		"namespace": []string{"required"},
		"page":      []string{"required"},
		"limit":     []string{"required"},
	}
	messages := govalidator.MapData{
		"namespace": []string{
			"required: namespace不能为空",
		},
		"page": []string{
			"required: page不能为空",
		},
		"limit": []string{
			"required: limit不能为空",
		},
	}

	// 校验入参

	return app.ValidateOptions(data, rules, messages)
}

type K8sDeploymentScaleRequest struct {
	K8sCommonRequest
	ScaleNum int32 `json:"scale_num" valid:"scale_num"`
}

func ValidK8sDeploymentScaleRequest(data interface{}, ctx *gin.Context) map[string][]string {
	rules := govalidator.MapData{
		"name":      []string{"required"},
		"namespace": []string{"required"},
		"scale_num": []string{"required"},
	}
	messages := govalidator.MapData{
		"name": []string{
			"required: name不能为空",
		},
		"namespace": []string{
			"required: namespace不能为空",
		},
		"scale_num": []string{
			"required: scale_num不能为空",
		},
	}

	// 校验入参

	return app.ValidateOptions(data, rules, messages)
}

type K8sDeploymentRollbackRequest struct {
	K8sCommonRequest
	ReplicaSet string `json:"image,omitempty" valid:"replica_set"`
}

func ValidK8sDeploymentRollbackRequest(data interface{}, ctx *gin.Context) map[string][]string {
	rules := govalidator.MapData{
		"namespace":   []string{"required"},
		"name":        []string{"required"},
		"replica_set": []string{"required"},
	}
	messages := govalidator.MapData{
		"namespace": []string{
			"required: namespace 不能为空",
		},
		"name": []string{
			"required: name 不能为空",
		},
		"replica_set": []string{
			"required: replica_set 不能为空",
		},
	}

	// 校验入参

	return app.ValidateOptions(data, rules, messages)
}

3.2、实现 services 方法

internal/app/services/k8s_deployment.go 文件中新增 Deployment 操作的 services 方法,如下:

go
package services

import (
	"github.com/joker-bai/hawkeye/internal/app/requests"
	"github.com/joker-bai/hawkeye/internal/pkg/k8s/deployment"
	appv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
)

// Deployment Service

func (s *Services) K8sDeploymentList(param *requests.K8sDeploymentListRequest) ([]appv1.Deployment, error) {
	return deployment.GetDeploymentList(param.Name, param.Namespace, param.Page, param.Limit)
}

func (s *Services) K8sDeploymentDelete(param *requests.K8sCommonRequest) error {
	return deployment.DeleteDeployment(param.Name, param.Namespace)
}

func (s *Services) K8sDeploymentUpdate(param *requests.K8sDeploymentUpdateRequest) error {
	return deployment.UpdateDeployment(param.Namespace, param.Content)
}

func (s *Services) K8sDeploymentCreate(param *requests.K8sDeploymentCreateRequest) error {
	return deployment.CreateDeployment(param)
}

func (s *Services) K8sDeploymentScale(param *requests.K8sDeploymentScaleRequest) error {
	return deployment.ScaleDeployment(param.Name, param.Namespace, param.ScaleNum)
}

func (s *Services) K8sDeploymentRollback(param *requests.K8sDeploymentRollbackRequest) error {
	return deployment.RollbackDeployment(param.Name, param.Namespace, param.ReplicaSet)
}

func (s *Services) K8sDeploymentRestart(param *requests.K8sCommonRequest) error {
	return deployment.RestartDeployment(param.Name, param.Namespace)
}

func (s *Services) K8sDeploymentGetReplicaSet(param *requests.K8sCommonRequest) ([]appv1.ReplicaSet, error) {
	return deployment.GetDeploymentReplicaSet(param.Name, param.Namespace)
}

func (s *Services) K8sDeploymentGetPod(param *requests.K8sCommonRequest) ([]corev1.Pod, error) {
	return deployment.GetPod(param.Name, param.Namespace)
}

func (s *Services) K8sDeploymentDetail(param *requests.K8sCommonRequest) (*appv1.Deployment, error) {
	return deployment.GetDeploymentDetail(param.Name, param.Namespace)
}

4、新增 controllers 方法

在 internal/app/controllers/api/v1 目录中新增 k8s_deployment.go 文件,实现如下方法:

go
package k8s

import (
	"github.com/gin-gonic/gin"
	"github.com/joker-bai/hawkeye/global"
	"github.com/joker-bai/hawkeye/internal/app/requests"
	"github.com/joker-bai/hawkeye/internal/app/services"
	"github.com/joker-bai/hawkeye/pkg/app"
	"github.com/joker-bai/hawkeye/pkg/errorcode"
	"go.uber.org/zap"
)

type DeploymentController struct{}

// List godoc
// @Summary 列出K8s Deployment
// @Description 列出K8s Deployment
// @Tags K8s Deployment管理
// @Produce json
// @Param name query string false "Deployment名" maxlength(100)
// @Param namespace query string false "命名空间" maxlength(100)
// @Param page query int true "页码"
// @Param limit query int true "每页数量"
// @Success 200 {object} string "成功"
// @Failure 400 {object} errorcode.Error "请求错误"
// @Failure 500 {object} errorcode.Error "内部错误"
// @Router /api/v1/k8s/deployment/list [get]
func (k *DeploymentController) List(ctx *gin.Context) {
	param := requests.K8sDeploymentListRequest{}
	response := app.NewResponse(ctx)

	if ok := app.Validate(ctx, &param, requests.ValidK8sDeploymentListRequest); !ok {
		return
	}

	svc := services.New(ctx)
	deployments, err := svc.K8sDeploymentList(&param)
	if err != nil {
		global.Log.Error("获取Deployment列表失败", zap.String("error", err.Error()))
		response.ToErrorResponse(errorcode.ErrorK8sDeploymentListFail)
		return
	}

	response.ToResponseList(deployments, len(deployments))
}

// Update godoc
// @Summary 更新Deployment
// @Description 更新Deployment
// @Tags K8s Deployment管理
// @Produce json
// @Param body body requests.K8sDeploymentUpdateRequest true "body"
// @Success 200 {object} string "成功"
// @Failure 400 {object} errorcode.Error "请求错误"
// @Failure 500 {object} errorcode.Error "内部错误"
// @Router /api/v1/k8s/deployment/update [post]
func (k *DeploymentController) Update(ctx *gin.Context) {
	param := requests.K8sDeploymentUpdateRequest{}
	response := app.NewResponse(ctx)

	if ok := app.Validate(ctx, &param, requests.ValidK8sDeploymentUpdateRequest); !ok {
		return
	}

	svc := services.New(ctx)
	err := svc.K8sDeploymentUpdate(&param)
	if err != nil {
		global.Log.Error("更新Deployment失败", zap.String("error", err.Error()))
		response.ToErrorResponse(errorcode.ErrorK8sDeploymentUpdateFail)
		return
	}

	response.ToResponse(gin.H{
		"msg": "Deployment更新成功",
	})
}

// Delete godoc
// @Summary 删除Deployment
// @Description 删除Deployment
// @Tags K8s Deployment管理
// @Produce json
// @Param body body requests.K8sCommonRequest true "body"
// @Success 200 {object} string "成功"
// @Failure 400 {object} errorcode.Error "请求错误"
// @Failure 500 {object} errorcode.Error "内部错误"
// @Router /api/v1/k8s/deployment/delete [post]
func (k *DeploymentController) Delete(ctx *gin.Context) {
	param := requests.K8sCommonRequest{}
	response := app.NewResponse(ctx)

	if ok := app.Validate(ctx, &param, requests.ValidK8sCommonRequest); !ok {
		return
	}

	svc := services.New(ctx)
	err := svc.K8sDeploymentDelete(&param)
	if err != nil {
		global.Log.Error("删除Deployments失败", zap.String("error", err.Error()))
		response.ToErrorResponse(errorcode.ErrorK8sDeploymentDeleteFail)
		return
	}

	response.ToResponse(gin.H{
		"msg": "Deployment删除成功",
	})
}

// Create godoc
// @Summary 创建Deployment
// @Description 创建Deployment
// @Tags K8s Deployment管理
// @Produce json
// @Param body body requests.K8sDeploymentCreateRequest true "body"
// @Success 200 {object} string "成功"
// @Failure 400 {object} errorcode.Error "请求错误"
// @Failure 500 {object} errorcode.Error "内部错误"
// @Router /api/v1/k8s/deployment/create [post]
func (k *DeploymentController) Create(ctx *gin.Context) {
	param := requests.K8sDeploymentCreateRequest{}
	response := app.NewResponse(ctx)

	if ok := app.Validate(ctx, &param, requests.ValidK8sDeploymentCreateRequest); !ok {
		return
	}

	svc := services.New(ctx)
	err := svc.K8sDeploymentCreate(&param)
	if err != nil {
		global.Log.Error("创建Deployment失败", zap.String("error", err.Error()))
		response.ToErrorResponse(errorcode.ErrorK8sDeploymentCreateFail)
		return
	}

	response.ToResponse(gin.H{
		"msg": "Deployment创建成果",
	})
}

// Restart godoc
// @Summary 重启Deployment
// @Description 重启Deployment
// @Tags K8s Deployment管理
// @Produce json
// @Param body body requests.K8sCommonRequest true "body"
// @Success 200 {object} string "成功"
// @Failure 400 {object} errorcode.Error "请求错误"
// @Failure 500 {object} errorcode.Error "内部错误"
// @Router /api/v1/k8s/deployment/restart [post]
func (k *DeploymentController) Restart(ctx *gin.Context) {
	param := requests.K8sCommonRequest{}
	response := app.NewResponse(ctx)

	if ok := app.Validate(ctx, &param, requests.ValidK8sCommonRequest); !ok {
		return
	}

	svc := services.New(ctx)
	err := svc.K8sDeploymentRestart(&param)
	if err != nil {
		global.Log.Error("重启Deployment失败", zap.String("error", err.Error()))
		response.ToErrorResponse(errorcode.ErrorK8sDeploymentRestartFail)
		return
	}

	response.ToResponse(gin.H{
		"msg": "重启Deployment成功",
	})
}

// Rollback godoc
// @Summary 回滚Deployment
// @Description 回滚Deployment
// @Tags K8s Deployment管理
// @Produce json
// @Param body body requests.K8sDeploymentRollbackRequest true "body"
// @Success 200 {object} string "成功"
// @Failure 400 {object} errorcode.Error "请求错误"
// @Failure 500 {object} errorcode.Error "内部错误"
// @Router /api/v1/k8s/deployment/rollback [post]
func (k *DeploymentController) Rollback(ctx *gin.Context) {
	param := requests.K8sDeploymentRollbackRequest{}
	response := app.NewResponse(ctx)

	if ok := app.Validate(ctx, &param, requests.ValidK8sDeploymentRollbackRequest); !ok {
		return
	}

	svc := services.New(ctx)
	err := svc.K8sDeploymentRollback(&param)
	if err != nil {
		global.Log.Error("回滚Deployment失败", zap.String("error", err.Error()))
		response.ToErrorResponse(errorcode.ErrorK8sDeploymentRollbackFail)
		return
	}

	response.ToResponse(gin.H{
		"msg": "回滚Deployment成功",
	})
}

// Scale godoc
// @Summary 伸缩Deployment
// @Description 伸缩Deployment
// @Tags K8s Deployment管理
// @Produce json
// @Param body body requests.K8sDeploymentScaleRequest true "body"
// @Success 200 {object} string "成功"
// @Failure 400 {object} errorcode.Error "请求错误"
// @Failure 500 {object} errorcode.Error "内部错误"
// @Router /api/v1/k8s/deployment/scale [get]
func (k *DeploymentController) Scale(ctx *gin.Context) {
	param := requests.K8sDeploymentScaleRequest{}
	response := app.NewResponse(ctx)

	if ok := app.Validate(ctx, &param, requests.ValidK8sCommonRequest); !ok {
		return
	}

	svc := services.New(ctx)
	err := svc.K8sDeploymentScale(&param)
	if err != nil {
		global.Log.Error("伸缩Deployment失败", zap.String("error", err.Error()))
		response.ToErrorResponse(errorcode.ErrorK8sDeploymentScaleFail)
		return
	}

	response.ToResponse(gin.H{
		"msg": "伸缩Deployment成功",
	})
}

// GetReplicaSet godoc
// @Summary 获取Deployment的ReplicaSet
// @Description 获取Deployment的ReplicaSet
// @Tags K8s Deployment管理
// @Produce json
// @Param name query string false "Deployment名" maxlength(100)
// @Param namespace query string false "命名空间" maxlength(100)
// @Param container query string false "容器" maxlength(100)
// @Success 200 {object} string "成功"
// @Failure 400 {object} errorcode.Error "请求错误"
// @Failure 500 {object} errorcode.Error "内部错误"
// @Router /api/v1/k8s/deployment/replicaset [get]
func (k *DeploymentController) GetReplicaSet(ctx *gin.Context) {
	param := requests.K8sCommonRequest{}
	response := app.NewResponse(ctx)

	if ok := app.Validate(ctx, &param, requests.ValidCommonIdRequest); !ok {
		return
	}

	svc := services.New(ctx)
	replicas, err := svc.K8sDeploymentGetReplicaSet(&param)
	if err != nil {
		global.Log.Error("获取Deployment的ReplicaSet失败", zap.String("error", err.Error()))
		response.ToErrorResponse(errorcode.ErrorK8sDeploymentGetReplicaSetFail)
		return
	}

	response.ToResponse(gin.H{
		"data": replicas,
		"msg":  "获取Deployment的ReplicaSet成功",
	})
}

// GetPods godoc
// @Summary 获取Deployment的Pod
// @Description 获取Deployment的Pod
// @Tags K8s Deployment管理
// @Produce json
// @Param name query string false "Deployment名" maxlength(100)
// @Param namespace query string false "命名空间" maxlength(100)
// @Param container query string false "容器" maxlength(100)
// @Success 200 {object} string "成功"
// @Failure 400 {object} errorcode.Error "请求错误"
// @Failure 500 {object} errorcode.Error "内部错误"
// @Router /api/v1/k8s/deployment/pods [get]
func (k *DeploymentController) GetPods(ctx *gin.Context) {
	param := requests.K8sCommonRequest{}
	response := app.NewResponse(ctx)

	if ok := app.Validate(ctx, &param, requests.ValidCommonIdRequest); !ok {
		return
	}

	svc := services.New(ctx)
	pods, err := svc.K8sDeploymentGetPod(&param)
	if err != nil {
		global.Log.Error("获取获取Deployment的Pod失败", zap.String("error", err.Error()))
		response.ToErrorResponse(errorcode.ErrorK8sDeploymentGetPodFail)
		return
	}

	response.ToResponse(gin.H{
		"data": pods,
		"msg":  "获取Deployment的Pod成功",
	})
}

// Detail godoc
// @Summary 获取Deployment的详情
// @Description 获取Deployment的详情
// @Tags K8s Deployment管理
// @Produce json
// @Param name query string false "Job名" maxlength(100)
// @Param namespace query string false "命名空间" maxlength(100)
// @Success 200 {object} string "成功"
// @Failure 400 {object} errorcode.Error "请求错误"
// @Failure 500 {object} errorcode.Error "内部错误"
// @Router /api/v1/k8s/job/detail [get]
func (k *DeploymentController) Detail(ctx *gin.Context) {
	param := requests.K8sCommonRequest{}
	response := app.NewResponse(ctx)

	if ok := app.Validate(ctx, &param, requests.ValidK8sCommonRequest); !ok {
		return
	}

	svc := services.New(ctx)
	deploy, err := svc.K8sDeploymentDetail(&param)
	if err != nil {
		global.Log.Error("获取获取Deployment的详情失败", zap.String("error", err.Error()))
		response.ToErrorResponse(errorcode.ErrorK8sDeploymentDetailFail)
		return
	}

	response.ToResponse(gin.H{
		"data": deploy,
		"msg":  "获取Deployment的详情成功",
	})
}

再到 pkg/errorcode/k8s.go 文件中新增如下错误代码:

go
package errorcode

var (
	......
	// K8s Deployment 错误码

	ErrorK8sDeploymentUpdateFail        = NewError(500011, "更新K8s Deployment失败")
	ErrorK8sDeploymentDeleteFail        = NewError(500012, "删除K8s Deployment失败")
	ErrorK8sDeploymentListFail          = NewError(500013, "获取K8s Deployment列表失败")
	ErrorK8sDeploymentDetailFail        = NewError(500014, "获取K8s Deployment详情失败")
	ErrorK8sDeploymentRestartFail       = NewError(500015, "重启K8s Deployment失败")
	ErrorK8sDeploymentGetReplicaSetFail = NewError(500016, "获取K8s Deployment的ReplicaSet失败")
	ErrorK8sDeploymentGetPodFail        = NewError(500017, "获取K8s Deployment的Pod失败")
	ErrorK8sDeploymentRollbackFail      = NewError(500018, "回滚K8s Deployment失败")
	ErrorK8sDeploymentScaleFail         = NewError(500019, "伸缩K8s Deployment失败")
	ErrorK8sDeploymentCreateFail        = NewError(500020, "创建K8s Deployment失败")
)

5、新增路由

internal/app/routers/k8s.go 文件中新增 Deployment 操作的路由,如下:

go
package routers

import (
	"github.com/gin-gonic/gin"
	v1 "github.com/joker-bai/kubemana/internal/app/controllers/api/v1"
)

type K8sRouter struct{}

func (r *K8sRouter) Inject(router *gin.RouterGroup) {

	k8s := router.Group("/k8s")
	{
		......

		// Deployment管理
		dc := new(k8s.DeploymentController)
		ks.GET("/deployment/list", dc.List)
		ks.POST("/deployment/create", dc.Create)
		ks.POST("/deployment/update", dc.Update)
		ks.POST("/deployment/delete", dc.Delete)
		ks.POST("/deployment/scale", dc.Scale)
        ks.GET("/deployment/detail", dc.Detail)
		ks.POST("/deployment/rollback", dc.Rollback)
		ks.POST("/deployment/restart", dc.Restart)
		ks.GET("/deployment/pods", dc.GetPods)
		ks.GET("/deployment/replicaset", dc.GetReplicaSet)
	}
}

6、测试一下

PS:测试之前都需要先初始化集群,在 4.3.1 Pod 章节有介绍。

这里简单测试列出 Deployment 接口,如下:

c674a907bfac1ea703734fdacff3a21b MD5

其他接口自行下去测试。

7、代码版本

本节开发完成后,记得生成 swag 和标记代码版本,如下:

go
$ swag init
$ git add .
$ git commit -m "新增k8s集群deployment操作"
最近更新