From ef2206b083b2104982712643ee13dee448c03524 Mon Sep 17 00:00:00 2001 From: Jason DeTiberus Date: Tue, 3 Dec 2019 16:32:52 -0500 Subject: [PATCH] Start adding KubeadmControlPlane controller --- api/v1alpha3/kubeadm_control_plane_types.go | 4 + config/rbac/role.yaml | 13 ++ .../kubeadm_control_plane_controller.go | 127 ++++++++++++++++++ .../20191017-kubeadm-based-control-plane.md | 15 ++- main.go | 35 +++-- 5 files changed, 177 insertions(+), 17 deletions(-) create mode 100644 controllers/kubeadm_control_plane_controller.go diff --git a/api/v1alpha3/kubeadm_control_plane_types.go b/api/v1alpha3/kubeadm_control_plane_types.go index d17d3965cd4f..b31c9afebeee 100644 --- a/api/v1alpha3/kubeadm_control_plane_types.go +++ b/api/v1alpha3/kubeadm_control_plane_types.go @@ -23,6 +23,10 @@ import ( "sigs.k8s.io/cluster-api/errors" ) +const ( + KubeadmControlPlaneFinalizer = "kubeadmcontrolplane.cluster.x-k8s.io" +) + // KubeadmControlPlaneSpec defines the desired state of KubeadmControlPlane. type KubeadmControlPlaneSpec struct { // Number of desired machines. Defaults to 1. When stacked etcd is used only diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 9ab5b020fec0..d7b2c46c5ebc 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -70,6 +70,19 @@ rules: - get - list - watch +- apiGroups: + - cluster.x-k8s.io + resources: + - kubeadmcontrolplanes + - kubeadmcontrolplanes/status + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - cluster.x-k8s.io resources: diff --git a/controllers/kubeadm_control_plane_controller.go b/controllers/kubeadm_control_plane_controller.go new file mode 100644 index 000000000000..1daf09b2b4eb --- /dev/null +++ b/controllers/kubeadm_control_plane_controller.go @@ -0,0 +1,127 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + + "github.com/go-logr/logr" + "github.com/pkg/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/client-go/tools/record" + clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3" + "sigs.k8s.io/cluster-api/util/patch" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +// +kubebuilder:rbac:groups=core,resources=events,verbs=get;list;watch;create;patch +// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;patch +// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=kubeadmcontrolplanes;kubeadmcontrolplanes/status,verbs=get;list;watch;create;update;patch;delete + +// KubeadmControlPlaneReconciler reconciles a KubeadmControlPlane object +type KubeadmControlPlaneReconciler struct { + Client client.Client + Log logr.Logger + + controller controller.Controller + recorder record.EventRecorder +} + +func (r *KubeadmControlPlaneReconciler) SetupWithManager(mgr ctrl.Manager, options controller.Options) error { + c, err := ctrl.NewControllerManagedBy(mgr). + For(&clusterv1.KubeadmControlPlane{}). + Owns(&clusterv1.Machine{}). + WithOptions(options). + Build(r) + + if err != nil { + return errors.Wrap(err, "failed setting up with a controller manager") + } + + r.controller = c + r.recorder = mgr.GetEventRecorderFor("kubeadm-control-plane-controller") + + return nil +} + +func (r *KubeadmControlPlaneReconciler) Reconcile(req ctrl.Request) (res ctrl.Result, _ error) { + logger := r.Log.WithValues("kubeadmControlPlane", req.Name, "namespace", req.Namespace) + ctx := context.Background() + + // Fetch the KubeadmControlPlane instance. + kubeadmControlPlane := &clusterv1.KubeadmControlPlane{} + if err := r.Client.Get(ctx, req.NamespacedName, kubeadmControlPlane); err != nil { + if apierrors.IsNotFound(err) { + // Object not found, return. Created objects are automatically garbage collected. + // For additional cleanup logic use finalizers. + return ctrl.Result{}, nil + } + + // Error reading the object - requeue the request. + logger.Error(err, "Failed to retrieve requested KubeadmControlPlane resource from the API Server") + return ctrl.Result{Requeue: true}, nil + } + + // Initialize the patch helper. + patchHelper, err := patch.NewHelper(kubeadmControlPlane, r.Client) + if err != nil { + logger.Error(err, "Failed to configure the patch helper") + return ctrl.Result{Requeue: true}, nil + } + + defer func() { + // Always attempt to Patch the KubeadmControlPlane object and status after each reconciliation. + if patchErr := patchHelper.Patch(ctx, kubeadmControlPlane); patchErr != nil { + logger.Error(err, "Failed to retrieve requested KubeadmControlPlane resource from the API Server") + res.Requeue = true + } + }() + + // Handle deletion reconciliation loop. + if !kubeadmControlPlane.ObjectMeta.DeletionTimestamp.IsZero() { + return r.reconcileDelete(ctx, kubeadmControlPlane, logger), nil + } + + // Handle normal reconciliation loop. + return r.reconcile(ctx, kubeadmControlPlane, logger), nil +} + +// reconcile handles KubeadmControlPlane reconciliation. +func (r *KubeadmControlPlaneReconciler) reconcile(_ context.Context, kubeadmControlPlane *clusterv1.KubeadmControlPlane, logger logr.Logger) ctrl.Result { + // If object doesn't have a finalizer, add one. + controllerutil.AddFinalizer(kubeadmControlPlane, clusterv1.KubeadmControlPlaneFinalizer) + + logger.Error(errors.New("Not Implemented"), "Not Implemented") + return ctrl.Result{Requeue: true} + +} + +// reconcileDelete handles KubeadmControlPlane deletion. +func (r *KubeadmControlPlaneReconciler) reconcileDelete(_ context.Context, kubeadmControlPlane *clusterv1.KubeadmControlPlane, logger logr.Logger) ctrl.Result { + err := errors.New("Not Implemented") + + if err != nil { + logger.Error(err, "Not Implemented") + return ctrl.Result{Requeue: true} + } + + controllerutil.RemoveFinalizer(kubeadmControlPlane, clusterv1.KubeadmControlPlaneFinalizer) + return ctrl.Result{} +} diff --git a/docs/proposals/20191017-kubeadm-based-control-plane.md b/docs/proposals/20191017-kubeadm-based-control-plane.md index 31316342abde..16be155296f9 100644 --- a/docs/proposals/20191017-kubeadm-based-control-plane.md +++ b/docs/proposals/20191017-kubeadm-based-control-plane.md @@ -18,7 +18,7 @@ reviewers: - "@hardikdr" - "@sbueringer" creation-date: 2019-10-17 -last-updated: 2019-11-13 +last-updated: 2019-12-04 status: implementable --- @@ -224,16 +224,16 @@ type KubeadmControlPlaneStatus struct { // +optional Ready bool `json:"ready,omitempty"` - // ErrorReason indicates that there is a problem reconciling the + // FailureReason indicates that there is a problem reconciling the // state, and will be set to a token value suitable for // programmatic interpretation. // +optional - ErrorReason KubeadmControlPlaneStatusError `json:"errorReason,omitempty"` + FailureReason KubeadmControlPlaneStatusError `json:"errorReason,omitempty"` - // ErrorMessage indicates that there is a problem reconciling the + // FailureMessage indicates that there is a problem reconciling the // state, and will be set to a descriptive error message. // +optional - ErrorMessage *string `json:"errorMessage,omitempty"` + FailureMessage *string `json:"errorMessage,omitempty"` // +kubebuilder:object:root=true // +kubebuilder:resource:path=controlplanes,shortName=cp,scope=Namespaced,categories=cluster-api @@ -348,7 +348,7 @@ And the following defaulting: ##### Create - After a KubeadmControlPlane object is created, it must bootstrap a control plane with a given number of replicas. -- If an error occurs, `KubeadmControlPlane.Status.ErrorStatus` and `KubeadmControlPlane.Status.ErrorMessage` are populated. +- If an error occurs, `KubeadmControlPlane.Status.FailureReason` and `KubeadmControlPlane.Status.FailureMessage` are populated. - `KubeadmControlPlane.Spec.Replicas` must be an odd number. - Can create an arbitrary number of control planes if etcd is external to the control plane, which will be determined by introspecting `KubeadmControlPlane.Spec.KubeadmConfigSpec`. - Creating a KubeadmControlPlane with > 1 replicas is equivalent to creating a KubeadmControlPlane with 1 replica followed by scaling the KubeadmControlPlane to the desired number of replicas @@ -577,3 +577,6 @@ For the purposes of designing upgrades, two existing lifecycle managers were exa ## Implementation History - [x] 10/17/2019: Initial Creation +- [x] 11/19/2019: Initial KubeadmControlPlane types added [#1765](https://github.com/kubernetes-sigs/cluster-api/pull/1765) +- [x] 12/04/2019: Updated References to ErrorMessage/ErrorReason to FailureMessage/FailureReason +- [] 12/04/2019: Initial stubbed KubeadmControlPlane controller added [#1826](https://github.com/kubernetes-sigs/cluster-api/pull/1826) \ No newline at end of file diff --git a/main.go b/main.go index 74ebebcfdbdf..fe303b5b53b8 100644 --- a/main.go +++ b/main.go @@ -55,17 +55,18 @@ func init() { func main() { var ( - metricsAddr string - enableLeaderElection bool - watchNamespace string - profilerAddress string - clusterConcurrency int - machineConcurrency int - machineSetConcurrency int - machineDeploymentConcurrency int - kubeadmConfigConcurrency int - syncPeriod time.Duration - webhookPort int + metricsAddr string + enableLeaderElection bool + watchNamespace string + profilerAddress string + clusterConcurrency int + machineConcurrency int + machineSetConcurrency int + machineDeploymentConcurrency int + kubeadmConfigConcurrency int + kubeadmControlPlaneConcurrency int + syncPeriod time.Duration + webhookPort int ) flag.StringVar(&metricsAddr, "metrics-addr", ":8080", @@ -95,6 +96,9 @@ func main() { flag.IntVar(&kubeadmConfigConcurrency, "kubeadmconfig-concurrency", 10, "Number of kubeadm configs to process simultaneously") + flag.IntVar(&kubeadmControlPlaneConcurrency, "kubeadmcontrolplane-concurrency", 1, + "Number of kubeadm control planes to process simultaneously") + flag.DurationVar(&syncPeriod, "sync-period", 10*time.Minute, "The minimum interval at which watched resources are reconciled (e.g. 15m)") @@ -168,6 +172,15 @@ func main() { os.Exit(1) } + // KubeadmControlPlane controllers. + if err = (&controllers.KubeadmControlPlaneReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("KubeadmControlPlane"), + }).SetupWithManager(mgr, concurrency(kubeadmControlPlaneConcurrency)); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "KubeadmControlPlane") + os.Exit(1) + } + if webhookPort != 0 { if err = (&clusterv1alpha2.Cluster{}).SetupWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Cluster")