mirror of
https://github.com/kubesphere/kubekey.git
synced 2025-12-26 01:22:51 +00:00
* fix: change failed_when execute time Signed-off-by: joyceliu <joyceliu@yunify.com> * doc: move pkg/util to pkg/controllers/util Signed-off-by: joyceliu <joyceliu@yunify.com> --------- Signed-off-by: joyceliu <joyceliu@yunify.com>
284 lines
11 KiB
Go
284 lines
11 KiB
Go
package infrastructure
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/cockroachdb/errors"
|
|
capkkinfrav1beta1 "github.com/kubesphere/kubekey/api/capkk/infrastructure/v1beta1"
|
|
kkcorev1 "github.com/kubesphere/kubekey/api/core/v1"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/client-go/tools/record"
|
|
"k8s.io/klog/v2"
|
|
"k8s.io/utils/ptr"
|
|
clusterv1beta1 "sigs.k8s.io/cluster-api/api/v1beta1"
|
|
"sigs.k8s.io/cluster-api/util/conditions"
|
|
ctrl "sigs.k8s.io/controller-runtime"
|
|
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
|
|
ctrlcontroller "sigs.k8s.io/controller-runtime/pkg/controller"
|
|
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
|
"sigs.k8s.io/controller-runtime/pkg/handler"
|
|
"sigs.k8s.io/controller-runtime/pkg/manager"
|
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
|
|
|
"github.com/kubesphere/kubekey/v4/cmd/controller-manager/app/options"
|
|
_const "github.com/kubesphere/kubekey/v4/pkg/const"
|
|
"github.com/kubesphere/kubekey/v4/pkg/controllers/util"
|
|
"github.com/kubesphere/kubekey/v4/pkg/converter"
|
|
)
|
|
|
|
// KKClusterReconciler reconciles a KKCluster object
|
|
type KKClusterReconciler struct {
|
|
*runtime.Scheme
|
|
ctrlclient.Client
|
|
record.EventRecorder
|
|
}
|
|
|
|
var _ options.Controller = &KKClusterReconciler{}
|
|
var _ reconcile.Reconciler = &KKClusterReconciler{}
|
|
|
|
// Name implements controllers.controller.
|
|
// Subtle: this method shadows the method (*Scheme).Name of KKClusterReconciler.Scheme.
|
|
func (r *KKClusterReconciler) Name() string {
|
|
return "kkcluster-reconciler"
|
|
}
|
|
|
|
// SetupWithManager implements controllers.controller.
|
|
func (r *KKClusterReconciler) SetupWithManager(mgr manager.Manager, o options.ControllerManagerServerOptions) error {
|
|
r.Scheme = mgr.GetScheme()
|
|
r.Client = mgr.GetClient()
|
|
r.EventRecorder = mgr.GetEventRecorderFor(r.Name())
|
|
|
|
return ctrl.NewControllerManagedBy(mgr).
|
|
WithOptions(ctrlcontroller.Options{
|
|
MaxConcurrentReconciles: o.MaxConcurrentReconciles,
|
|
}).
|
|
For(&capkkinfrav1beta1.KKCluster{}).
|
|
// Watches inventory to sync kkmachine.
|
|
Watches(&kkcorev1.Inventory{}, handler.EnqueueRequestsFromMapFunc(r.ownerToKKClusterMapFunc)).
|
|
// Watches kkmachine to sync kkmachine.
|
|
Watches(&capkkinfrav1beta1.KKMachine{}, handler.EnqueueRequestsFromMapFunc(r.ownerToKKClusterMapFunc)).
|
|
Complete(r)
|
|
}
|
|
|
|
func (r *KKClusterReconciler) ownerToKKClusterMapFunc(ctx context.Context, obj ctrlclient.Object) []ctrl.Request {
|
|
kkcluster := &capkkinfrav1beta1.KKCluster{}
|
|
if err := util.GetOwnerFromObject(ctx, r.Client, obj, kkcluster); err == nil {
|
|
return []ctrl.Request{{NamespacedName: ctrlclient.ObjectKeyFromObject(kkcluster)}}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// +kubebuilder:rbac:groups=kubekey.kubesphere.io,resources=*,verbs=get;list;watch;create;update;patch;delete
|
|
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=*,verbs=get;list;watch;create;update;patch;delete
|
|
// +kubebuilder:rbac:groups=controlplane.cluster.x-k8s.io,resources=*,verbs=get;list;watch
|
|
// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=*,verbs=get;list;watch
|
|
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
|
|
|
|
func (r *KKClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, retErr error) {
|
|
// Get KKCluster.
|
|
kkcluster := &capkkinfrav1beta1.KKCluster{}
|
|
err := r.Client.Get(ctx, req.NamespacedName, kkcluster)
|
|
if err != nil {
|
|
if !apierrors.IsNotFound(err) {
|
|
return ctrl.Result{}, errors.Wrapf(err, "failed to get kkcluster %q", req.String())
|
|
}
|
|
|
|
return ctrl.Result{}, nil
|
|
}
|
|
clusterName := kkcluster.Labels[clusterv1beta1.ClusterNameLabel]
|
|
if clusterName == "" {
|
|
klog.V(5).InfoS("kkcluster is not belong cluster. skip", "inventory", req.String())
|
|
|
|
return ctrl.Result{}, nil
|
|
}
|
|
scope, err := newClusterScope(ctx, r.Client, reconcile.Request{NamespacedName: types.NamespacedName{
|
|
Namespace: req.Namespace,
|
|
Name: clusterName,
|
|
}})
|
|
if err != nil {
|
|
return ctrl.Result{}, err
|
|
}
|
|
if err := scope.newPatchHelper(scope.KKCluster, scope.Inventory); err != nil {
|
|
return ctrl.Result{}, err
|
|
}
|
|
defer func() {
|
|
if retErr != nil {
|
|
if scope.KKCluster.Status.FailureReason == "" {
|
|
scope.KKCluster.Status.FailureReason = capkkinfrav1beta1.KKClusterFailedUnknown
|
|
}
|
|
scope.KKCluster.Status.FailureMessage = retErr.Error()
|
|
}
|
|
if err := r.reconcileStatus(ctx, scope); err != nil {
|
|
retErr = errors.Join(retErr, err)
|
|
}
|
|
if err := scope.PatchHelper.Patch(ctx, scope.KKCluster, scope.Inventory); err != nil {
|
|
retErr = errors.Join(retErr, err)
|
|
}
|
|
}()
|
|
|
|
// skip if cluster is paused.
|
|
if scope.isPaused() {
|
|
klog.InfoS("cluster or kkcluster is marked as paused. Won't reconcile")
|
|
|
|
return reconcile.Result{}, nil
|
|
}
|
|
|
|
// Add finalizer first if not set to avoid the race condition between init and delete.
|
|
// Note: Finalizers in general can only be added when the deletionTimestamp is not set.
|
|
if scope.KKCluster.ObjectMeta.DeletionTimestamp.IsZero() && !controllerutil.ContainsFinalizer(scope.KKCluster, capkkinfrav1beta1.KKClusterFinalizer) {
|
|
controllerutil.AddFinalizer(scope.KKCluster, capkkinfrav1beta1.KKClusterFinalizer)
|
|
|
|
return ctrl.Result{}, nil
|
|
}
|
|
|
|
// Handle deleted clusters
|
|
if !scope.KKCluster.DeletionTimestamp.IsZero() {
|
|
return reconcile.Result{}, r.reconcileDelete(ctx, scope)
|
|
}
|
|
|
|
// Handle non-deleted clusters
|
|
return reconcile.Result{}, r.reconcileNormal(ctx, scope)
|
|
}
|
|
|
|
// reconcileDelete delete cluster
|
|
func (r *KKClusterReconciler) reconcileDelete(ctx context.Context, scope *clusterScope) error {
|
|
// waiting inventory deleted
|
|
inventoryList := &kkcorev1.InventoryList{}
|
|
if err := util.GetObjectListFromOwner(ctx, r.Client, scope.KKCluster, inventoryList); err != nil {
|
|
return err
|
|
}
|
|
for _, obj := range inventoryList.Items {
|
|
if err := r.Client.Delete(ctx, &obj); err != nil {
|
|
return errors.Wrapf(err, "failed to delete inventory %q", ctrlclient.ObjectKeyFromObject(&obj))
|
|
}
|
|
}
|
|
|
|
if len(inventoryList.Items) == 0 {
|
|
// Delete finalizer.
|
|
controllerutil.RemoveFinalizer(scope.KKCluster, capkkinfrav1beta1.KKClusterFinalizer)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// reconcileInventory reconcile kkcluster's hosts to inventory's inventoryHosts.
|
|
func (r *KKClusterReconciler) reconcileNormal(ctx context.Context, scope *clusterScope) error {
|
|
inventoryHosts, err := converter.ConvertKKClusterToInventoryHost(scope.KKCluster)
|
|
if err != nil { // cannot convert kkcluster to inventory. may be kkcluster is not valid.
|
|
scope.KKCluster.Status.FailureReason = capkkinfrav1beta1.KKClusterFailedInvalidHosts
|
|
|
|
return err
|
|
}
|
|
// if inventory is not exist. create it
|
|
if scope.Inventory.Name == "" {
|
|
scope.Inventory = &kkcorev1.Inventory{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: scope.Name,
|
|
Namespace: scope.Namespace,
|
|
Labels: map[string]string{
|
|
clusterv1beta1.ClusterNameLabel: scope.Name,
|
|
},
|
|
},
|
|
Spec: kkcorev1.InventorySpec{
|
|
Hosts: inventoryHosts,
|
|
},
|
|
}
|
|
if err := ctrl.SetControllerReference(scope.KKCluster, scope.Inventory, r.Scheme); err != nil {
|
|
return errors.Wrapf(err, "failed to set ownerReference from kkcluster %q to inventory", ctrlclient.ObjectKeyFromObject(scope.KKCluster))
|
|
}
|
|
|
|
return errors.Wrapf(r.Client.Create(ctx, scope.Inventory), "failed to create inventory for kkcluster %q", ctrlclient.ObjectKeyFromObject(scope.KKCluster))
|
|
}
|
|
|
|
// if inventory's host is match kkcluster.inventoryHosts. skip
|
|
if reflect.DeepEqual(scope.Inventory.Spec.Hosts, inventoryHosts) {
|
|
return nil
|
|
}
|
|
|
|
// set inventory
|
|
scope.Inventory.Spec.Hosts = inventoryHosts
|
|
// if host contains in group but not contains in hosts. remove it.
|
|
for _, g := range scope.Inventory.Spec.Groups {
|
|
newHosts := make([]string, 0)
|
|
for _, h := range g.Hosts {
|
|
if h == _const.VariableLocalHost {
|
|
newHosts = append(newHosts, h)
|
|
|
|
continue
|
|
}
|
|
for hn := range inventoryHosts {
|
|
if h == hn {
|
|
newHosts = append(newHosts, h)
|
|
}
|
|
}
|
|
}
|
|
g.Hosts = newHosts
|
|
}
|
|
if scope.Inventory.Annotations == nil {
|
|
scope.Inventory.Annotations = make(map[string]string)
|
|
}
|
|
scope.Inventory.Annotations[kkcorev1.HostCheckPlaybookAnnotation] = ""
|
|
scope.Inventory.Status.Phase = kkcorev1.InventoryPhasePending
|
|
scope.Inventory.Status.Ready = false
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *KKClusterReconciler) reconcileStatus(ctx context.Context, scope *clusterScope) error {
|
|
// sync KKClusterNodeReachedCondition.
|
|
switch scope.Inventory.Status.Phase {
|
|
case kkcorev1.InventoryPhasePending:
|
|
conditions.MarkUnknown(scope.KKCluster, capkkinfrav1beta1.KKClusterNodeReachedCondition, capkkinfrav1beta1.KKClusterNodeReachedConditionReasonWaiting, "waiting for inventory host check playbook.")
|
|
case kkcorev1.InventoryPhaseSucceeded:
|
|
conditions.MarkTrue(scope.KKCluster, capkkinfrav1beta1.KKClusterNodeReachedCondition)
|
|
case kkcorev1.InventoryPhaseFailed:
|
|
conditions.MarkFalse(scope.KKCluster, capkkinfrav1beta1.KKClusterNodeReachedCondition, capkkinfrav1beta1.KKClusterNodeReachedConditionReasonUnreached, clusterv1beta1.ConditionSeverityError,
|
|
"inventory host check playbook %q run failed", scope.Inventory.Annotations[kkcorev1.HostCheckPlaybookAnnotation])
|
|
}
|
|
|
|
// after inventory is ready. continue create cluster
|
|
// todo: when cluster node changed, Is it should be ready?
|
|
scope.KKCluster.Status.Ready = scope.KKCluster.Status.Ready || scope.Inventory.Status.Ready
|
|
|
|
// sync KKClusterKKMachineConditionReady.
|
|
kkmachineList := &capkkinfrav1beta1.KKMachineList{}
|
|
if err := r.Client.List(ctx, kkmachineList, ctrlclient.MatchingLabels{
|
|
clusterv1beta1.ClusterNameLabel: scope.Name,
|
|
}); err != nil {
|
|
return errors.Wrapf(err, "failed to get kkMachineList with label %s=%s", clusterv1beta1.ClusterNameLabel, scope.Name)
|
|
}
|
|
|
|
// sync kkmachine status to kkcluster
|
|
failedKKMachine := make([]string, 0)
|
|
for _, kkmachine := range kkmachineList.Items {
|
|
if kkmachine.Status.FailureReason != "" {
|
|
failedKKMachine = append(failedKKMachine, kkmachine.Name)
|
|
}
|
|
}
|
|
if len(failedKKMachine) != 0 {
|
|
conditions.MarkFalse(scope.KKCluster, capkkinfrav1beta1.KKClusterKKMachineConditionReady, capkkinfrav1beta1.KKMachineKKMachineConditionReasonFailed, clusterv1beta1.ConditionSeverityError,
|
|
"failed kkmachine %s", strings.Join(failedKKMachine, ","))
|
|
scope.KKCluster.Status.FailureReason = capkkinfrav1beta1.KKMachineKKMachineConditionReasonFailed
|
|
scope.KKCluster.Status.FailureMessage = "[" + strings.Join(failedKKMachine, ",") + "]"
|
|
}
|
|
|
|
cpn, _, err := unstructured.NestedInt64(scope.ControlPlane.Object, "spec", "replicas")
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to get replicas from machineControlPlane %s", ctrlclient.ObjectKeyFromObject(scope.ControlPlane))
|
|
}
|
|
mdn := int(ptr.Deref(scope.MachineDeployment.Spec.Replicas, 0))
|
|
if scope.KKCluster.Status.Ready && scope.KKCluster.Status.FailureReason == "" &&
|
|
len(kkmachineList.Items) == int(cpn)+mdn {
|
|
conditions.MarkTrue(scope.KKCluster, capkkinfrav1beta1.KKClusterKKMachineConditionReady)
|
|
}
|
|
|
|
return nil
|
|
}
|