308 lines
9.9 KiB
Go
308 lines
9.9 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"errors"
|
||
"github.com/wonderivan/logger"
|
||
appsv1 "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"
|
||
"strconv"
|
||
"time"
|
||
)
|
||
|
||
var Deployment deployment
|
||
|
||
type deployment struct{}
|
||
|
||
//定义列表的返回内容,Items是deployment元素列表,Total为deployment元素数量
|
||
type DeploymentsResp struct {
|
||
Items []appsv1.Deployment `json:"items"`
|
||
Total int `json:"total"`
|
||
}
|
||
|
||
//定义DeployCreate结构体,用于创建deployment需要的参数属性的定义
|
||
type DeployCreate struct {
|
||
Name string `json:"name"`
|
||
Namespace string `json:"namespace"`
|
||
Replicas int32 `json:"replicas"`
|
||
Image string `json:"image"`
|
||
Label map[string]string `json:"label"`
|
||
Cpu string `json:"cpu"`
|
||
Memory string `json:"memory"`
|
||
ContainerPort int32 `json:"container_port"`
|
||
HealthCheck bool `json:"health_check"`
|
||
HealthPath string `json:"health_path"`
|
||
}
|
||
|
||
//定义DeploysNp类型,用于返回namespace中deployment的数量
|
||
type DeploysNp struct {
|
||
Namespace string `json:"namespace"`
|
||
DeployNum int `json:"deployment_num"`
|
||
}
|
||
|
||
//获取deployment列表,支持过滤、排序、分页
|
||
func (d *deployment) GetDeployments(filterName, namespace string, limit, page int) (deploymentsResp *DeploymentsResp, err error) {
|
||
//获取deploymentList类型的deployment列表
|
||
deploymentList, err := K8s.ClientSet.AppsV1().Deployments(namespace).List(context.TODO(), metav1.ListOptions{})
|
||
if err != nil {
|
||
logger.Error(errors.New("获取Deployment列表失败, " + err.Error()))
|
||
return nil, errors.New("获取Deployment列表失败, " + err.Error())
|
||
}
|
||
//将deploymentList中的deployment列表(Items),放进dataselector对象中,进行排序
|
||
selectableData := &dataSelector{
|
||
GenericDataList: d.toCells(deploymentList.Items),
|
||
DataSelect: &DataSelectQuery{
|
||
Filter: &FilterQuery{Name: filterName},
|
||
Paginate: &PaginateQuery{
|
||
Limit: limit,
|
||
Page: page,
|
||
},
|
||
},
|
||
}
|
||
|
||
filtered := selectableData.Filter()
|
||
total := len(filtered.GenericDataList)
|
||
data := filtered.Sort().Paginate()
|
||
|
||
//将[]DataCell类型的deployment列表转为appsv1.deployment列表
|
||
deployments := d.fromCells(data.GenericDataList)
|
||
|
||
return &DeploymentsResp{
|
||
Items: deployments,
|
||
Total: total,
|
||
}, nil
|
||
}
|
||
|
||
//获取deployment详情
|
||
func (d *deployment) GetDeploymentDetail(deploymentName, namespace string) (deployment *appsv1.Deployment, err error) {
|
||
deployment, err = K8s.ClientSet.AppsV1().Deployments(namespace).Get(context.TODO(), deploymentName, metav1.GetOptions{})
|
||
if err != nil {
|
||
logger.Error(errors.New("获取Deployment详情失败, " + err.Error()))
|
||
return nil, errors.New("获取Deployment详情失败, " + err.Error())
|
||
}
|
||
|
||
return deployment, nil
|
||
}
|
||
|
||
//设置deployment副本数
|
||
func (d *deployment) ScaleDeployment(deploymentName, namespace string, scaleNum int) (replica int32, err error) {
|
||
//获取autoscalingv1.Scale类型的对象,能点出当前的副本数
|
||
scale, err := K8s.ClientSet.AppsV1().Deployments(namespace).GetScale(context.TODO(), deploymentName, metav1.GetOptions{})
|
||
if err != nil {
|
||
logger.Error(errors.New("获取Deployment副本数信息失败, " + err.Error()))
|
||
return 0, errors.New("获取Deployment副本数信息失败, " + err.Error())
|
||
}
|
||
//修改副本数
|
||
scale.Spec.Replicas = int32(scaleNum)
|
||
//更新副本数,传入scale对象
|
||
newScale, err := K8s.ClientSet.AppsV1().Deployments(namespace).UpdateScale(context.TODO(), deploymentName, scale, metav1.UpdateOptions{})
|
||
if err != nil {
|
||
logger.Error(errors.New("更新Deployment副本数信息失败, " + err.Error()))
|
||
return 0, errors.New("更新Deployment副本数信息失败, " + err.Error())
|
||
}
|
||
|
||
return newScale.Spec.Replicas, nil
|
||
}
|
||
|
||
//创建deployment,接收DeployCreate对象
|
||
func (d *deployment) CreateDeployment(data *DeployCreate) (err error) {
|
||
//初始化appsv1.Deployment类型的对象,并将入参的data数据放进去
|
||
deployment := &appsv1.Deployment{
|
||
ObjectMeta: metav1.ObjectMeta{
|
||
Name: data.Name,
|
||
Namespace: data.Namespace,
|
||
Labels: data.Label,
|
||
},
|
||
Spec: appsv1.DeploymentSpec{
|
||
Replicas: &data.Replicas,
|
||
Selector: &metav1.LabelSelector{
|
||
MatchLabels: data.Label,
|
||
},
|
||
Template: corev1.PodTemplateSpec{
|
||
ObjectMeta: metav1.ObjectMeta{
|
||
Name: data.Name,
|
||
Labels: data.Label,
|
||
},
|
||
Spec: corev1.PodSpec{
|
||
Containers: []corev1.Container{
|
||
{
|
||
Name: data.Name,
|
||
Image: data.Image,
|
||
Ports: []corev1.ContainerPort{
|
||
{
|
||
Name: "http",
|
||
Protocol: corev1.ProtocolTCP,
|
||
ContainerPort: data.ContainerPort,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
Status: appsv1.DeploymentStatus{},
|
||
}
|
||
//判断健康检查功能是否打开,若打开,则增加健康检查功能
|
||
if data.HealthCheck {
|
||
deployment.Spec.Template.Spec.Containers[0].ReadinessProbe = &corev1.Probe{
|
||
ProbeHandler: corev1.ProbeHandler{
|
||
HTTPGet: &corev1.HTTPGetAction{
|
||
Path: data.HealthPath,
|
||
Port: intstr.IntOrString{
|
||
Type: 0,
|
||
IntVal: data.ContainerPort,
|
||
},
|
||
},
|
||
},
|
||
InitialDelaySeconds: 5,
|
||
TimeoutSeconds: 5,
|
||
PeriodSeconds: 5,
|
||
}
|
||
deployment.Spec.Template.Spec.Containers[0].LivenessProbe = &corev1.Probe{
|
||
ProbeHandler: corev1.ProbeHandler{
|
||
HTTPGet: &corev1.HTTPGetAction{
|
||
Path: data.HealthPath,
|
||
Port: intstr.IntOrString{
|
||
Type: 0,
|
||
IntVal: data.ContainerPort,
|
||
},
|
||
},
|
||
},
|
||
InitialDelaySeconds: 15,
|
||
TimeoutSeconds: 5,
|
||
PeriodSeconds: 5,
|
||
}
|
||
}
|
||
//定义容器的limit和request资源
|
||
deployment.Spec.Template.Spec.Containers[0].Resources.Limits = map[corev1.ResourceName]resource.Quantity{
|
||
corev1.ResourceCPU: resource.MustParse(data.Cpu),
|
||
corev1.ResourceMemory: resource.MustParse(data.Memory),
|
||
}
|
||
deployment.Spec.Template.Spec.Containers[0].Resources.Requests = map[corev1.ResourceName]resource.Quantity{
|
||
corev1.ResourceCPU: resource.MustParse(data.Cpu),
|
||
corev1.ResourceMemory: resource.MustParse(data.Memory),
|
||
}
|
||
|
||
//调用sdk创建deployment
|
||
_, err = K8s.ClientSet.AppsV1().Deployments(data.Namespace).
|
||
Create(context.TODO(), deployment, metav1.CreateOptions{})
|
||
if err != nil {
|
||
logger.Error(errors.New("创建Deployment失败, " + err.Error()))
|
||
return errors.New("创建Deployment失败, " + err.Error())
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
//删除deployment
|
||
func (d *deployment) DeleteDeployment(deploymentName, namespace string) (err error) {
|
||
err = K8s.ClientSet.AppsV1().Deployments(namespace).Delete(context.TODO(), deploymentName, metav1.DeleteOptions{})
|
||
if err != nil {
|
||
logger.Error(errors.New("删除Deployment失败, " + err.Error()))
|
||
return errors.New("删除Deployment失败, " + err.Error())
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
//重启deployment
|
||
func (d *deployment) RestartDeployment(deploymentName, namespace string) (err error) {
|
||
//此功能等同于一下kubectl命令
|
||
//kubectl deployment ${service} -p \
|
||
//'{"spec":{"template":{"spec":{"containers":[{"name":"'"${service}"'","env":[{"name":"RESTART_","value":"'$(date +%s)'"}]}]}}}}'
|
||
|
||
//使用patchData Map组装数据
|
||
patchData := map[string]interface{}{
|
||
"spec": map[string]interface{}{
|
||
"template": map[string]interface{}{
|
||
"spec": map[string]interface{}{
|
||
"containers": []map[string]interface{}{
|
||
{"name": deploymentName,
|
||
"env": []map[string]string{{
|
||
"name": "RESTART_",
|
||
"value": strconv.FormatInt(time.Now().Unix(), 10),
|
||
}},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
}
|
||
//序列化为字节,因为patch方法只接收字节类型参数
|
||
patchByte, err := json.Marshal(patchData)
|
||
if err != nil {
|
||
logger.Error(errors.New("json序列化失败, " + err.Error()))
|
||
return errors.New("json序列化失败, " + err.Error())
|
||
}
|
||
//调用patch方法更新deployment
|
||
_, err = K8s.ClientSet.AppsV1().Deployments(namespace).Patch(context.TODO(), deploymentName, "application/strategic-merge-patch+json", patchByte, metav1.PatchOptions{})
|
||
if err != nil {
|
||
logger.Error(errors.New("重启Deployment失败, " + err.Error()))
|
||
return errors.New("重启Deployment失败, " + err.Error())
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
//更新deployment
|
||
func (d *deployment) UpdateDeployment(namespace, content string) (err error) {
|
||
var deploy = &appsv1.Deployment{}
|
||
|
||
err = json.Unmarshal([]byte(content), deploy)
|
||
if err != nil {
|
||
logger.Error(errors.New("反序列化失败, " + err.Error()))
|
||
return errors.New("反序列化失败, " + err.Error())
|
||
}
|
||
|
||
_, err = K8s.ClientSet.AppsV1().Deployments(namespace).Update(context.TODO(), deploy, metav1.UpdateOptions{})
|
||
if err != nil {
|
||
logger.Error(errors.New("更新Deployment失败, " + err.Error()))
|
||
return errors.New("更新Deployment失败, " + err.Error())
|
||
}
|
||
return nil
|
||
}
|
||
|
||
//获取每个namespace的deployment数量
|
||
func (d *deployment) GetDeployNumPerNp() (deploysNps []*DeploysNp, err error) {
|
||
namespaceList, err := K8s.ClientSet.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
for _, namespace := range namespaceList.Items {
|
||
deploymentList, err := K8s.ClientSet.AppsV1().Deployments(namespace.Name).List(context.TODO(), metav1.ListOptions{})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
deploysNp := &DeploysNp{
|
||
Namespace: namespace.Name,
|
||
DeployNum: len(deploymentList.Items),
|
||
}
|
||
|
||
deploysNps = append(deploysNps, deploysNp)
|
||
}
|
||
return deploysNps, nil
|
||
}
|
||
|
||
//类型转换
|
||
func (d *deployment) toCells(deployments []appsv1.Deployment) []DataCell {
|
||
cells := make([]DataCell, len(deployments))
|
||
for i := range deployments {
|
||
cells[i] = deploymentCell(deployments[i])
|
||
}
|
||
return cells
|
||
}
|
||
|
||
func (d *deployment) fromCells(cells []DataCell) []appsv1.Deployment {
|
||
deployments := make([]appsv1.Deployment, len(cells))
|
||
for i := range cells {
|
||
//cells[i].(podCell)是将DataCell类型转成podCell
|
||
deployments[i] = appsv1.Deployment(cells[i].(deploymentCell))
|
||
}
|
||
return deployments
|
||
}
|