AmitKumarDas / fun-with-programming

ABC - Always Be Coding
2 stars 2 forks source link

[k8s] controller runtime snippets #35

Closed AmitKumarDas closed 2 years ago

AmitKumarDas commented 3 years ago
// refer
// - https://github.com/loft-sh/kiosk/
// - https://book.kubebuilder.io/cronjob-tutorial/controller-implementation.html
// typical imports
  "context"
  tenancyv1alpha1 "github.com/loft-sh/kiosk/pkg/apis/tenancy/v1alpha1"
  "github.com/loft-sh/kiosk/pkg/util"
  "github.com/loft-sh/kiosk/pkg/util/clienthelper"
  "github.com/loft-sh/kiosk/pkg/util/clusterrole"
  "github.com/loft-sh/kiosk/pkg/util/loghelper"
  metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  "k8s.io/apimachinery/pkg/types"
  "k8s.io/client-go/util/workqueue"
  "sigs.k8s.io/controller-runtime/pkg/event"
  "sigs.k8s.io/controller-runtime/pkg/reconcile"
  "github.com/go-logr/logr"
  configv1alpha1 "github.com/loft-sh/kiosk/pkg/apis/config/v1alpha1"
  "github.com/loft-sh/kiosk/pkg/constants"
  corev1 "k8s.io/api/core/v1"
  "k8s.io/apimachinery/pkg/api/errors"
  "k8s.io/apimachinery/pkg/runtime"
  ctrl "sigs.k8s.io/controller-runtime"
  "sigs.k8s.io/controller-runtime/pkg/client"
  "sigs.k8s.io/controller-runtime/pkg/source"
  rbacv1 "k8s.io/api/rbac/v1"
  apiequality "k8s.io/apimachinery/pkg/api/equality"
AmitKumarDas commented 3 years ago

Internals

// https://github.com/kubernetes-sigs/controller-runtime/
// til
//
// config is used to talk to apiserver
// scheme maps go structs to GVKs
// mapper maps GVKs to Resources
// codecs is used to create a REST client for a GVK
// pkg/client/config/config.go
// 
// -- get config to talk to K8s API server
// -- sane QPS & burst configurations
// pkg/client/apiutil/apimachinery.go
//
// -- fetch latest discovery info
// -- get GVK for object only for single versioned GVK
// -- get the rest client for any GVK
// -- protobuf scheme only for native k8s resources
// pkg/client/apiutil/dynamicrestmapper.go
//
// dynamically discovers resource types at runtime
// pkg/client/client_cache.go
//
// -- fetch resources (old as well as new)
// -- a resource is composed of its client, gvk and RESTMapping
// pkg/client/client.go
//
// -- composes typed & unstruct clients
// -- creates the cache of common stuff like
//   -- config, scheme, mapper, codecs, & 
//   -- resources keyed by GVK
AmitKumarDas commented 3 years ago

Usage

// https://github.com/kubernetes-sigs/controller-runtime/
//
// pkg/client/example_test.go
c, err := client.New(config.GetConfigOrDie(), client.Options{})
pl := &corev1.PodList{}
err = c.List(context.Background(), pl, client.InNamespace("default"))
u := &unstructured.UnstructuredList{}
u.SetGroupVersionKind(schema.GroupVersionKind{
  Group:   "apps",
  Kind:    "DeploymentList",
  Version: "v1",
})
_ = c.List(context.Background(), u)
pod := &corev1.Pod{}
_ = c.Get(context.Background(), client.ObjectKey{
  Namespace: "namespace",
  Name:      "name",
}, pod)
pod.SetFinalizers(append(pod.GetFinalizers(), "new-finalizer"))
_ = c.Update(context.Background(), pod)
u := &unstructured.Unstructured{}
u.SetGroupVersionKind(schema.GroupVersionKind{
  Group:   "apps",
  Kind:    "Deployment",
  Version: "v1",
})
_ = c.Get(context.Background(), client.ObjectKey{
  Namespace: "namespace",
  Name:      "name",
}, u)
u.SetFinalizers(append(u.GetFinalizers(), "new-finalizer"))
_ = c.Update(context.Background(), u)
// Update status if diff
// Do a semantic deep equal since we compare resource quantities as well
// See https://github.com/kubernetes/apimachinery/issues/75
if apiequality.Semantic.DeepEqual(&oldStatus, &account.Status) == false {
   err = r.Status().Update(ctx, account)
}
patch := []byte(`{"metadata":{"annotations":{"version": "v2"}}}`)
_ = c.Patch(context.Background(), &corev1.Pod{
  ObjectMeta: metav1.ObjectMeta{
    Namespace: "namespace",
    Name:      "name",
  },
}, client.RawPatch(types.StrategicMergePatchType, patch))
// patch status

u := &unstructured.Unstructured{}
u.Object = map[string]interface{}{
  "metadata": map[string]interface{}{
    "name":      "foo",
    "namespace": "namespace",
  },
}
u.SetGroupVersionKind(schema.GroupVersionKind{
  Group:   "batch",
  Version: "v1beta1",
  Kind:    "CronJob",
})
patch := []byte(fmt.Sprintf(
  `{"status":{"lastScheduleTime":"%s"}}`, 
  time.Now().Format(time.RFC3339),
))
_ = c.Status().Patch(
  context.Background(), 
  u, 
  client.RawPatch(types.MergePatchType, patch),
)
_ = c.DeleteAllOf(
  context.Background(), 
  &corev1.Pod{}, 
  client.InNamespace("foo"), 
  client.MatchingLabels{"app": "foo"},
)

// Using an unstructured Object
u := &unstructured.Unstructured{}
u.SetGroupVersionKind(schema.GroupVersionKind{
  Group:   "apps",
  Kind:    "Deployment",
  Version: "v1",
})
_ = c.DeleteAllOf(
  context.Background(), 
  u, 
  client.InNamespace("foo"), 
  client.MatchingLabels{"app": "foo"},
)
AmitKumarDas commented 3 years ago

Usage

// This example shows how to set up and 
// consume a field selector over a pod's 
// volumes' secretName field
func ExampleFieldIndexer_secretName() {
  // someIndexer is a FieldIndexer over a Cache
  _ = someIndexer.IndexField(
    context.TODO(), 
    &corev1.Pod{}, 
    "spec.volumes.secret.secretName", 
    func(o client.Object) []string {
      var res []string
      for _, vol := range o.(*corev1.Pod).Spec.Volumes {
        if vol.Secret == nil {
          continue
        }
        // just return the raw field value 
        // indexer will take care of dealing with namespaces for us
        res = append(res, vol.Secret.SecretName)
      }
      return res
    })

  // elsewhere (e.g. in your reconciler)
  // derived from the reconcile.Request, for instance
  mySecretName := "someSecret"
  var podsWithSecrets corev1.PodList
  _ = c.List(
    context.Background(), 
    &podsWithSecrets, 
    client.MatchingFields{"spec.volumes.secret.secretName": mySecretName},
  )
}
AmitKumarDas commented 3 years ago

Usage

// list all child jobs in this namespace 
// that belong to this CronJob 
// use the List method to list the child jobs
// Notice that we use variadic options to set
// the namespace and field match 
// field match is actually an index lookup 
// that we set up below
var childJobs kbatch.JobList
err := r.List(
  ctx, 
  &childJobs, 
  client.InNamespace(req.Namespace), 
  client.MatchingFields{jobOwnerKey: req.Name},
)
// The reconciler fetches all jobs owned by the 
// cronjob. As our number of cronjobs increases, 
// looking these up can become quite slow as we 
// have to filter through all of them. For a 
// more efficient lookup, these jobs will be 
// indexed locally on the controller's name. A 
// jobOwnerKey field is added to the cached job
// objects. This key references the owning 
// controller and functions as the index.

// In order to allow our reconciler to quickly 
// look up Jobs by their owner, we’ll need an 
// index. We declare an index key that we can 
// later use with the client as a pseudo-field 
// name, and then describe how to extract the 
// indexed value from the Job object. The indexer
// will automatically take care of namespaces for 
// us, so we just have to extract the owner name
// if the Job has a CronJob owner.

// Additionally, we’ll inform the manager that this
// controller owns some Jobs, so that it will 
// automatically call Reconcile on the underlying 
// CronJob when a Job changes, is deleted, etc.

var (
  jobOwnerKey = ".metadata.controller"
  apiGVStr    = batch.GroupVersion.String()
)

func (r *CronJobReconciler) SetupWithManager(mgr ctrl.Manager) error {
  err := mgr.GetFieldIndexer().IndexField(
    context.Background(), 
    &kbatch.Job{}, 
    jobOwnerKey, 
    func(rawObj client.Object) []string {
      // grab the job object, extract the owner...
      job := rawObj.(*kbatch.Job)
      owner := metav1.GetControllerOf(job)
      if owner == nil {
        return nil
      }
      // ...make sure it's a CronJob...
      if owner.APIVersion != apiGVStr || owner.Kind != "CronJob" {
        return nil
      }

      // ...and if so, return it
      return []string{owner.Name}
    },
  )

  return ctrl.NewControllerManagedBy(mgr).
    For(&batch.CronJob{}).
    Owns(&kbatch.Job{}).
    Complete(r)
}