diff --git a/api/v1/const.go b/api/v1/const.go index 973aecb..f46368c 100644 --- a/api/v1/const.go +++ b/api/v1/const.go @@ -4,3 +4,32 @@ const ( ModeIngress = "ingress" ModeNodePort = "nodeport" ) + +const ( + ConditionTypeDeployment = "Deployment" + ConditionTypeService = "Service" + ConditionTypeIngress = "Ingress" + + ConditionMessageDeploymentOKFmt = "Deployment %s is ready" + ConditionMessageDeploymentNotFmt = "Deployment %s is not ready" + ConditionMessageServiceOKFmt = "Service %s is ready" + ConditionMessageServiceNotFmt = "Service %s is not ready" + ConditionMessageIngressOKFmt = "Ingress %s is ready" + ConditionMessageIngressNotFmt = "Ingress %s is not ready" + + ConditionReasonDeploymentReady = "DeploymentReady" + ConditionReasonDeploymentNotReady = "DeploymentNotReady" + ConditionReasonServiceReady = "ServiceReady" + ConditionReasonServiceNotReady = "ServiceNotReady" + ConditionReasonIngressReady = "IngressReady" + ConditionReasonIngressNotReady = "IngressNotReady" + + ConditonStatusTrue = "True" + ConditonStatusFalse = "False" +) + +const ( + StatusReasonSuccess = "Success" + StatusMessageSuccess = "Success" + StatusPhaseComplete = "Complete" +) diff --git a/controllers/msbdeployment_controller.go b/controllers/msbdeployment_controller.go index e8b163f..c279834 100644 --- a/controllers/msbdeployment_controller.go +++ b/controllers/msbdeployment_controller.go @@ -18,13 +18,16 @@ package controllers import ( "context" + "fmt" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" networkv1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "reflect" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "strings" + "time" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" @@ -34,6 +37,8 @@ import ( myAppsv1 "mashibing.com/pkg/mashibing-deployment/api/v1" ) +var WaitRequeue = 10 * time.Second + // MsbDeploymentReconciler reconciles a MsbDeployment object type MsbDeploymentReconciler struct { client.Client @@ -54,6 +59,14 @@ type MsbDeploymentReconciler struct { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.0/pkg/reconcile func (r *MsbDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + // 状态更新策略 + // 创建的时候 + // 更新为创建 + // 更新的时候 + // 根据获取的状态来判断时候更新status + // 删除的时候 + // 只有在操作 ingress 的时候,并且 mode 为 nodeport 的时候 + logger := log.FromContext(ctx, "MsbDployment", req.NamespacedName) logger.Info("Reconcile is started.") @@ -74,10 +87,26 @@ func (r *MsbDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Reques if errors.IsNotFound(err) { // 2.1 不存在对象 // 2.1.1 创建 deployment - if err := r.createDeployment(ctx, mdCopy); err != nil { - return ctrl.Result{}, err + if errCreate := r.createDeployment(ctx, mdCopy); err != nil { + return ctrl.Result{}, errCreate + } + if _, errStatus := r.updateStatus(ctx, + mdCopy, + myAppsv1.ConditionTypeDeployment, + fmt.Sprintf(myAppsv1.ConditionMessageDeploymentNotFmt, req.Name), + myAppsv1.ConditonStatusFalse, + myAppsv1.ConditionReasonDeploymentNotReady); errStatus != nil { + return ctrl.Result{}, errStatus } } else { + if _, errStatus := r.updateStatus(ctx, + mdCopy, + myAppsv1.ConditionTypeDeployment, + fmt.Sprintf("Deployment %s, err: %s", req.Name, err.Error()), + myAppsv1.ConditonStatusFalse, + myAppsv1.ConditionReasonDeploymentNotReady); errStatus != nil { + return ctrl.Result{}, errStatus + } return ctrl.Result{}, err } } else { @@ -86,6 +115,25 @@ func (r *MsbDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Reques if err := r.updateDeployment(ctx, mdCopy, deploy); err != nil { return ctrl.Result{}, err } + if deploy.Status.AvailableReplicas == mdCopy.Spec.Replicas { + if _, errStatus := r.updateStatus(ctx, + mdCopy, + myAppsv1.ConditionTypeDeployment, + fmt.Sprintf(myAppsv1.ConditionMessageDeploymentOKFmt, req.Name), + myAppsv1.ConditonStatusTrue, + myAppsv1.ConditionReasonDeploymentReady); errStatus != nil { + return ctrl.Result{}, errStatus + } + } else { + if _, errStatus := r.updateStatus(ctx, + mdCopy, + myAppsv1.ConditionTypeDeployment, + fmt.Sprintf(myAppsv1.ConditionMessageDeploymentNotFmt, req.Name), + myAppsv1.ConditonStatusFalse, + myAppsv1.ConditionReasonDeploymentNotReady); errStatus != nil { + return ctrl.Result{}, errStatus + } + } } // ======= 处理 service ========= @@ -109,7 +157,23 @@ func (r *MsbDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Reques } else { return ctrl.Result{}, myAppsv1.ErrorNotSupportMode } + if _, errStatus := r.updateStatus(ctx, + mdCopy, + myAppsv1.ConditionTypeService, + fmt.Sprintf(myAppsv1.ConditionMessageServiceNotFmt, req.Name), + myAppsv1.ConditonStatusFalse, + myAppsv1.ConditionReasonServiceNotReady); errStatus != nil { + return ctrl.Result{}, errStatus + } } else { + if _, errStatus := r.updateStatus(ctx, + mdCopy, + myAppsv1.ConditionTypeService, + fmt.Sprintf("Service %s, err: %s", req.Name, err.Error()), + myAppsv1.ConditonStatusFalse, + myAppsv1.ConditionReasonServiceNotReady); errStatus != nil { + return ctrl.Result{}, errStatus + } return ctrl.Result{}, err } } else { @@ -129,6 +193,14 @@ func (r *MsbDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Reques } else { return ctrl.Result{}, myAppsv1.ErrorNotSupportMode } + if _, errStatus := r.updateStatus(ctx, + mdCopy, + myAppsv1.ConditionTypeService, + fmt.Sprintf(myAppsv1.ConditionMessageServiceOKFmt, req.Name), + myAppsv1.ConditonStatusTrue, + myAppsv1.ConditionReasonServiceReady); errStatus != nil { + return ctrl.Result{}, errStatus + } } // ======= 处理 ingress ========= @@ -143,12 +215,28 @@ func (r *MsbDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Reques if err := r.createIngress(ctx, mdCopy); err != nil { return ctrl.Result{}, err } + if _, errStatus := r.updateStatus(ctx, + mdCopy, + myAppsv1.ConditionTypeIngress, + fmt.Sprintf(myAppsv1.ConditionMessageIngressNotFmt, req.Name), + myAppsv1.ConditonStatusFalse, + myAppsv1.ConditionReasonIngressNotReady); errStatus != nil { + return ctrl.Result{}, errStatus + } } else if strings.ToLower(mdCopy.Spec.Expose.Mode) == myAppsv1.ModeNodePort { // 4.1.2 mode 为 nodeport // 4.1.2.1 退出 return ctrl.Result{}, nil } } else { + if _, errStatus := r.updateStatus(ctx, + mdCopy, + myAppsv1.ConditionTypeIngress, + fmt.Sprintf("Ingress %s, err: %s", req.Name, err.Error()), + myAppsv1.ConditonStatusFalse, + myAppsv1.ConditionReasonIngressNotReady); errStatus != nil { + return ctrl.Result{}, errStatus + } return ctrl.Result{}, err } } else { @@ -159,15 +247,37 @@ func (r *MsbDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Reques if err := r.updateIngress(ctx, mdCopy, ig); err != nil { return ctrl.Result{}, err } + if _, errStatus := r.updateStatus(ctx, + mdCopy, + myAppsv1.ConditionTypeIngress, + fmt.Sprintf(myAppsv1.ConditionMessageIngressOKFmt, req.Name), + myAppsv1.ConditonStatusTrue, + myAppsv1.ConditionReasonIngressReady); errStatus != nil { + return ctrl.Result{}, errStatus + } } else if strings.ToLower(mdCopy.Spec.Expose.Mode) == myAppsv1.ModeNodePort { // 4.2.2 mode 为 nodeport // 4.2.2.1 删除 ingress if err := r.deleteIngress(ctx, mdCopy); err != nil { return ctrl.Result{}, err } + r.deleteStatus(mdCopy, myAppsv1.ConditionTypeIngress) } } + // 最后检查状态时候最终完成 + if sus, errStatus := r.updateStatus(ctx, + mdCopy, + "", + "", + "", + ""); errStatus != nil { + return ctrl.Result{}, errStatus + } else if !sus { + logger.Info("Reconcile is ended.") + return ctrl.Result{RequeueAfter: WaitRequeue}, nil + } + logger.Info("Reconcile is ended.") return ctrl.Result{}, nil } @@ -341,3 +451,133 @@ func (r *MsbDeploymentReconciler) deleteIngress(ctx context.Context, md *myAppsv } return r.Client.Delete(ctx, ig) } + +// 处理status +// return: +// +// bool: 资源是否完成,时候需要等待。如果是 true,表示资源已经完成没不需要再次reconcile +// 如果是 false,表示资源还未完成,需要重新入队 +// error:执行 update 的状态 +func (r *MsbDeploymentReconciler) updateStatus(ctx context.Context, md *myAppsv1.MsbDeployment, conditionType, message, status, reason string) (bool, error) { + if conditionType != "" { + // 1. 获取 status + //status := md.Status + // 2. 获取 conditions 字段 + //conditions := status.Conditions + // 3. 根据当前的需求,获取指定的 condition + var condition *myAppsv1.Condition + for i := range md.Status.Conditions { + // 4. 是否获取到 + if md.Status.Conditions[i].Type == conditionType { + // 4.1 获取到了 + condition = &md.Status.Conditions[i] + } + } + if condition != nil { + // 4.1.1 获取当前线上的 conditon 状态,与存储的condition进行比较,如果相同,跳过。不同,替换 + if condition.Status != status || + condition.Message != message || + condition.Reason != reason { + condition.Status = status + condition.Message = message + condition.Reason = reason + } + } else { + // 4.2 没获取到,创建这个conditon,更新到conditons中 + md.Status.Conditions = append(md.Status.Conditions, + createCondition(conditionType, message, status, reason)) + } + } + + // 5. 继续处理其他的conditions + m, re, p, sus := isSuccess(md.Status.Conditions) + if sus { + // 6.1 如果所有conditions的状态都为成功,则更新总的 status 为成功。 + md.Status.Message = myAppsv1.StatusMessageSuccess + md.Status.Reason = myAppsv1.StatusReasonSuccess + md.Status.Phase = myAppsv1.StatusPhaseComplete + } else { + // 6.2 遍历所有的conditons 状态,如果有任意一个condition不是完成的状态,则将这个状态更新到总的 status 中。更待一定时间再次入队。 + md.Status.Message = m + md.Status.Reason = re + md.Status.Phase = p + } + // 7. 执行更新 + return sus, r.Client.Status().Update(ctx, md) +} + +func isSuccess(conditions []myAppsv1.Condition) (message, reason, phase string, sus bool) { + if len(conditions) == 0 { + return "", "", "", false + } + for i := range conditions { + if conditions[i].Status == myAppsv1.ConditonStatusFalse { + return conditions[i].Message, conditions[i].Reason, conditions[i].Type, false + } + } + return "", "", "", true +} + +func createCondition(conditionType, message, status, reason string) myAppsv1.Condition { + return myAppsv1.Condition{ + Type: conditionType, + Message: message, + Status: status, + Reason: reason, + LastTransitionTime: metav1.NewTime(time.Now()), + } +} + +// 需要是幂等的,可以多次执行,不管是否存在。如果存在就删除,不存在就什么也不做 +// 只是删除对应的Condition不做更多的操作 +func (r *MsbDeploymentReconciler) deleteStatus(md *myAppsv1.MsbDeployment, conditionType string) { + // 1. 遍历conditions + for i := range md.Status.Conditions { + // 2. 找到要删除的对象 + if md.Status.Conditions[i].Type == conditionType { + // 3. 执行删除 + md.Status.Conditions = deleteCondition(md.Status.Conditions, i) + } + } +} + +// a := struct { +// len int +// cap int +// [cap]int [1,2,3,4] +// } +// +// b = a +// +// b +// +// a +// a = append(a, "b") +// conditions = deleteCondition(conditions, 2) +// a = [1,2,3,4,5] +// f(a) +// +// f(b []int) { +// b = n[0:4] +// } +func deleteCondition(conditions []myAppsv1.Condition, i int) []myAppsv1.Condition { + // 前提:切片中的元素顺序不敏感 + // 1. 要删除的元素的索引值不能大于切片长度 + if i >= len(conditions) { + return []myAppsv1.Condition{} + } + + // 2. 如果切片长度为1,且索引值为0,直接清空 + if len(conditions) == 1 && i == 0 { + return conditions[:0] + } + + // 3. 如果长度-1等于索引值,删除最后一个元素 + if len(conditions)-1 == i { + return conditions[:len(conditions)-1] + } + + // 4. 交换索引位置的元素和最后一个元素,删除最后一个元素 + conditions[i], conditions[len(conditions)-1] = conditions[len(conditions)-1], conditions[i] + return conditions[:len(conditions)-1] +}