-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add PVC syncing support #179
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,125 @@ | ||||||||||||||||||||||||||||
package controller | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
import ( | ||||||||||||||||||||||||||||
"context" | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
"github.com/rancher/k3k/k3k-kubelet/translate" | ||||||||||||||||||||||||||||
"github.com/rancher/k3k/pkg/apis/k3k.io/v1alpha1" | ||||||||||||||||||||||||||||
"github.com/rancher/k3k/pkg/log" | ||||||||||||||||||||||||||||
v1 "k8s.io/api/core/v1" | ||||||||||||||||||||||||||||
apierrors "k8s.io/apimachinery/pkg/api/errors" | ||||||||||||||||||||||||||||
"k8s.io/apimachinery/pkg/runtime" | ||||||||||||||||||||||||||||
"k8s.io/apimachinery/pkg/types" | ||||||||||||||||||||||||||||
ctrl "sigs.k8s.io/controller-runtime" | ||||||||||||||||||||||||||||
ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client" | ||||||||||||||||||||||||||||
"sigs.k8s.io/controller-runtime/pkg/controller" | ||||||||||||||||||||||||||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" | ||||||||||||||||||||||||||||
"sigs.k8s.io/controller-runtime/pkg/manager" | ||||||||||||||||||||||||||||
"sigs.k8s.io/controller-runtime/pkg/reconcile" | ||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
const ( | ||||||||||||||||||||||||||||
pvcController = "pvc-syncer-controller" | ||||||||||||||||||||||||||||
pvcFinalizerName = "pv.k3k.io/finalizer" | ||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
type PVCReconciler struct { | ||||||||||||||||||||||||||||
virtualClient ctrlruntimeclient.Client | ||||||||||||||||||||||||||||
hostClient ctrlruntimeclient.Client | ||||||||||||||||||||||||||||
clusterName string | ||||||||||||||||||||||||||||
clusterNamespace string | ||||||||||||||||||||||||||||
Scheme *runtime.Scheme | ||||||||||||||||||||||||||||
HostScheme *runtime.Scheme | ||||||||||||||||||||||||||||
logger *log.Logger | ||||||||||||||||||||||||||||
Translater translate.ToHostTranslater | ||||||||||||||||||||||||||||
//objs sets.Set[types.NamespacedName] | ||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we drop the comment?
Suggested change
|
||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
// AddPVCSyncer adds persistentvolumeclaims syncer controller to k3k-kubelet | ||||||||||||||||||||||||||||
func AddPVCSyncer(ctx context.Context, virtMgr, hostMgr manager.Manager, clusterName, clusterNamespace string, logger *log.Logger) error { | ||||||||||||||||||||||||||||
translater := translate.ToHostTranslater{ | ||||||||||||||||||||||||||||
ClusterName: clusterName, | ||||||||||||||||||||||||||||
ClusterNamespace: clusterNamespace, | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
// initialize a new Reconciler | ||||||||||||||||||||||||||||
reconciler := PVCReconciler{ | ||||||||||||||||||||||||||||
virtualClient: virtMgr.GetClient(), | ||||||||||||||||||||||||||||
hostClient: hostMgr.GetClient(), | ||||||||||||||||||||||||||||
Scheme: virtMgr.GetScheme(), | ||||||||||||||||||||||||||||
HostScheme: hostMgr.GetScheme(), | ||||||||||||||||||||||||||||
logger: logger.Named(pvcController), | ||||||||||||||||||||||||||||
Translater: translater, | ||||||||||||||||||||||||||||
clusterName: clusterName, | ||||||||||||||||||||||||||||
clusterNamespace: clusterNamespace, | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
return ctrl.NewControllerManagedBy(virtMgr). | ||||||||||||||||||||||||||||
For(&v1.PersistentVolumeClaim{}). | ||||||||||||||||||||||||||||
WithOptions(controller.Options{ | ||||||||||||||||||||||||||||
MaxConcurrentReconciles: maxConcurrentReconciles, | ||||||||||||||||||||||||||||
}). | ||||||||||||||||||||||||||||
Complete(&reconciler) | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
func (v *PVCReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { | ||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is probably a nit but I had some issues to understand what |
||||||||||||||||||||||||||||
log := v.logger.With("Cluster", v.clusterName, "PersistentVolumeClaim", req.NamespacedName) | ||||||||||||||||||||||||||||
var ( | ||||||||||||||||||||||||||||
virtPVC v1.PersistentVolumeClaim | ||||||||||||||||||||||||||||
hostPVC v1.PersistentVolumeClaim | ||||||||||||||||||||||||||||
cluster v1alpha1.Cluster | ||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||
if err := v.hostClient.Get(ctx, types.NamespacedName{Name: v.clusterName, Namespace: v.clusterNamespace}, &cluster); err != nil { | ||||||||||||||||||||||||||||
return reconcile.Result{}, err | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
// handling persistent volume sync | ||||||||||||||||||||||||||||
if err := v.virtualClient.Get(ctx, req.NamespacedName, &virtPVC); err != nil { | ||||||||||||||||||||||||||||
return reconcile.Result{}, ctrlruntimeclient.IgnoreNotFound(err) | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
syncedPVC := v.pvc(&virtPVC) | ||||||||||||||||||||||||||||
if err := controllerutil.SetControllerReference(&cluster, syncedPVC, v.HostScheme); err != nil { | ||||||||||||||||||||||||||||
return reconcile.Result{}, err | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
// handle deletion | ||||||||||||||||||||||||||||
if !virtPVC.DeletionTimestamp.IsZero() { | ||||||||||||||||||||||||||||
// deleting the synced service if exists | ||||||||||||||||||||||||||||
if err := v.hostClient.Delete(ctx, syncedPVC); err != nil { | ||||||||||||||||||||||||||||
return reconcile.Result{}, ctrlruntimeclient.IgnoreNotFound(err) | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
Comment on lines
+84
to
+87
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, I think this could lead to some orphaned resource. I.e. what if the syncedPVC does not exist? This will error, and it will not be requed because of the Maybe it's better to return only if the error is a different one. In case of ErrNotFound we should continue the deletion of the virtual PVC. |
||||||||||||||||||||||||||||
// remove the finalizer after cleaning up the synced service | ||||||||||||||||||||||||||||
if controllerutil.ContainsFinalizer(&virtPVC, pvcFinalizerName) { | ||||||||||||||||||||||||||||
controllerutil.RemoveFinalizer(&virtPVC, pvcFinalizerName) | ||||||||||||||||||||||||||||
if err := v.virtualClient.Update(ctx, &virtPVC); err != nil { | ||||||||||||||||||||||||||||
return reconcile.Result{}, err | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
Comment on lines
+89
to
+94
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be shortened using only the
Suggested change
Probably the Contains is useful when you need to check the existence and do some logic before actually remove it. |
||||||||||||||||||||||||||||
return reconcile.Result{}, nil | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
// getting the cluster for setting the controller reference | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
// Add finalizer if it does not exist | ||||||||||||||||||||||||||||
if !controllerutil.ContainsFinalizer(&virtPVC, pvcFinalizerName) { | ||||||||||||||||||||||||||||
controllerutil.AddFinalizer(&virtPVC, pvcFinalizerName) | ||||||||||||||||||||||||||||
if err := v.virtualClient.Update(ctx, &virtPVC); err != nil { | ||||||||||||||||||||||||||||
return reconcile.Result{}, err | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
Comment on lines
+100
to
+106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as before:
Suggested change
|
||||||||||||||||||||||||||||
// create or update the pv on host | ||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||
if err := v.hostClient.Get(ctx, types.NamespacedName{Name: syncedPVC.Name, Namespace: v.clusterNamespace}, &hostPVC); err != nil { | ||||||||||||||||||||||||||||
if apierrors.IsNotFound(err) { | ||||||||||||||||||||||||||||
log.Info("creating the persistent volume for the first time on the host cluster") | ||||||||||||||||||||||||||||
return reconcile.Result{}, v.hostClient.Create(ctx, syncedPVC) | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
return reconcile.Result{}, err | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
log.Info("updating pvc on the host cluster") | ||||||||||||||||||||||||||||
return reconcile.Result{}, v.hostClient.Update(ctx, syncedPVC) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
func (v *PVCReconciler) pvc(obj *v1.PersistentVolumeClaim) *v1.PersistentVolumeClaim { | ||||||||||||||||||||||||||||
hostPVC := obj.DeepCopy() | ||||||||||||||||||||||||||||
v.Translater.TranslateTo(hostPVC) | ||||||||||||||||||||||||||||
// don't sync finalizers to the host | ||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it mean we are still missing to remove the finalizers here? |
||||||||||||||||||||||||||||
return hostPVC | ||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package webhook | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
|
||
"github.com/rancher/k3k/pkg/controller/cluster/agent" | ||
"github.com/rancher/k3k/pkg/log" | ||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1" | ||
v1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/apimachinery/pkg/types" | ||
"k8s.io/utils/ptr" | ||
ctrl "sigs.k8s.io/controller-runtime" | ||
ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client" | ||
"sigs.k8s.io/controller-runtime/pkg/manager" | ||
) | ||
|
||
const ( | ||
webhookName = "nodename.podmutator.k3k.io" | ||
webhookTimeout = int32(10) | ||
webhookPort = "9443" | ||
webhookPath = "/mutate--v1-pod" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the |
||
) | ||
|
||
type webhookHandler struct { | ||
client ctrlruntimeclient.Client | ||
scheme *runtime.Scheme | ||
nodeName string | ||
clusterName string | ||
clusterNamespace string | ||
logger *log.Logger | ||
} | ||
|
||
// AddPodMutatorWebhook will add a mutator webhook to the virtual cluster to | ||
// modify the nodeName of the created pods with the name of the virtual kubelet node name | ||
func AddPodMutatorWebhook(ctx context.Context, mgr manager.Manager, hostClient ctrlruntimeclient.Client, clusterName, clusterNamespace, nodeName string, logger *log.Logger) error { | ||
handler := webhookHandler{ | ||
client: mgr.GetClient(), | ||
scheme: mgr.GetScheme(), | ||
logger: logger, | ||
clusterName: clusterName, | ||
clusterNamespace: clusterNamespace, | ||
nodeName: nodeName, | ||
} | ||
|
||
// create mutator webhook configuration to the cluster | ||
config, err := handler.configuration(ctx, hostClient) | ||
if err != nil { | ||
return err | ||
} | ||
if err := handler.client.Create(ctx, config); err != nil { | ||
return err | ||
} | ||
// register webhook with the manager | ||
return ctrl.NewWebhookManagedBy(mgr).For(&v1.Pod{}).WithDefaulter(&handler).Complete() | ||
} | ||
|
||
func (w *webhookHandler) Default(ctx context.Context, obj runtime.Object) error { | ||
pod, ok := obj.(*v1.Pod) | ||
if !ok { | ||
return fmt.Errorf("invalid request: object was type %t not cluster", obj) | ||
} | ||
w.logger.Infow("recieved request", "Pod", pod.Name, "Namespace", pod.Namespace) | ||
if pod.Spec.NodeName == "" { | ||
pod.Spec.NodeName = w.nodeName | ||
} | ||
return nil | ||
} | ||
|
||
func (w *webhookHandler) configuration(ctx context.Context, hostClient ctrlruntimeclient.Client) (*admissionregistrationv1.MutatingWebhookConfiguration, error) { | ||
w.logger.Infow("extracting webhook tls from host cluster") | ||
var ( | ||
webhookTLSSecret v1.Secret | ||
) | ||
if err := hostClient.Get(ctx, types.NamespacedName{Name: agent.WebhookSecretName(w.clusterName), Namespace: w.clusterNamespace}, &webhookTLSSecret); err != nil { | ||
return nil, err | ||
} | ||
caBundle, ok := webhookTLSSecret.Data["ca.crt"] | ||
if !ok { | ||
return nil, errors.New("webhook CABundle does not exist in secret") | ||
} | ||
webhookURL := "https://" + w.nodeName + ":" + webhookPort + webhookPath | ||
return &admissionregistrationv1.MutatingWebhookConfiguration{ | ||
TypeMeta: metav1.TypeMeta{ | ||
APIVersion: "admissionregistration.k8s.io/v1", | ||
Kind: "MutatingWebhookConfiguration", | ||
}, | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: webhookName + "-configuration", | ||
}, | ||
Webhooks: []admissionregistrationv1.MutatingWebhook{ | ||
{ | ||
Name: webhookName, | ||
AdmissionReviewVersions: []string{"v1"}, | ||
SideEffects: ptr.To(admissionregistrationv1.SideEffectClassNone), | ||
TimeoutSeconds: ptr.To(webhookTimeout), | ||
ClientConfig: admissionregistrationv1.WebhookClientConfig{ | ||
URL: ptr.To(webhookURL), | ||
CABundle: caBundle, | ||
}, | ||
Rules: []admissionregistrationv1.RuleWithOperations{ | ||
{ | ||
Operations: []admissionregistrationv1.OperationType{ | ||
"CREATE", | ||
}, | ||
Rule: admissionregistrationv1.Rule{ | ||
APIGroups: []string{""}, | ||
APIVersions: []string{"v1"}, | ||
Resources: []string{"pods"}, | ||
Scope: ptr.To(admissionregistrationv1.NamespacedScope), | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
package kubeconfig | ||
package certs | ||
|
||
import ( | ||
"crypto" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be
pvc.k3k.io/finalizer
?