v0.0.x to v0.1.0

    The recommended way to migrate your project is to initialize a new v0.1.0 project, then copy your code into the new project and modify as described below.

    This guide goes over migrating the memcached-operator, an example project from the user guide, to illustrate migration steps. See the v0.0.7 memcached-operator and project structures for pre- and post-migration examples, respectively.

    Rename your v0.0.x project and create a new v0.1.0 project in its place.

    Create the api for your custom resource (CR) in the new project with operator-sdk add api --api-version=<apiversion> --kind=<kind>

    1. $ cd memcached-operator
    2. $ operator-sdk add api --api-version=cache.example.com/v1alpha1 --kind=Memcached
    3. $ tree pkg/apis
    4. pkg/apis/
    5. ├── addtoscheme_cache_v1alpha1.go
    6. ├── apis.go
    7. └── cache
    8. └── v1alpha1
    9. ├── doc.go
    10. ├── memcached_types.go
    11. ├── register.go
    12. └── zz_generated.deepcopy.go

    Repeat the above command for as many custom types as you had defined in your old project. Each type will be defined in the file pkg/apis/<group>/<version>/<kind>_types.go.

    Copy the contents of the type

    Copy the Spec and Status contents of the pkg/apis/<group>/<version>/types.go file from the old project to the new project’s pkg/apis/<group>/<version>/<kind>_types.go file.

    Note: Each <kind>_types.go file has an init() function. Be sure not to remove that since that registers the type with the Manager’s scheme.

    1. func init() {
    2. SchemeBuilder.Register(&Memcached{}, &MemcachedList{})
    3. }

    Add a controller to watch your CR

    In a v0.0.x project you would define what resource to watch in cmd/<operator-name>/main.go

    1. sdk.Watch("cache.example.com/v1alpha1", "Memcached", "default", time.Duration(5)*time.Second)

    For a v0.1.0 project you define a Controller to watch resources.

    Add a controller to watch your CR type with operator-sdk add controller --api-version=<apiversion> --kind=<kind>.

    1. $ operator-sdk add controller --api-version=cache.example.com/v1alpha1 --kind=Memcached
    2. $ tree pkg/controller
    3. pkg/controller/
    4. ├── add_memcached.go
    5. ├── controller.go
    6. └── memcached
    7. └── memcached_controller.go

    Inspect the add() function in your pkg/controller/<kind>/<kind>_controller.go file:

    Watching multiple resources lets you trigger the reconcile loop for multiple resources relevant to your application. See the doc and the Kubernetes controller conventions doc for more details.

    Multiple custom resources

    If your operator is watching more than 1 CR type then you can do one of the following depending on your application:

    • If the CR is owned by your primary CR then watch it as a secondary resource in the same controller to trigger the reconcile loop for the primary resource.

      1. // Watch for changes to the primary resource Memcached
      2. err = c.Watch(&source.Kind{Type: &cachev1alpha1.Memcached{}}, &handler.EnqueueRequestForObject{})
      3. // Watch for changes to the secondary resource AppService and enqueue reconcile requests for the owner Memcached
      4. err = c.Watch(&source.Kind{Type: &appv1alpha1.AppService{}}, &handler.EnqueueRequestForOwner{
      5. IsController: true,
      6. OwnerType: &cachev1alpha1.Memcached{},
    • Add a new controller to watch and reconcile the CR independently of the other CR.

      1. $ operator-sdk add controller --api-version=app.example.com/v1alpha1 --kind=AppService
      1. // Watch for changes to the primary resource AppService
      2. err = c.Watch(&source.Kind{Type: &appv1alpha1.AppService{}}, &handler.EnqueueRequestForObject{})

    In a v0.1.0 project the reconcile code is defined in the Reconcile() method of a controller’s Reconciler. This is similar to the Handle() function in the older project. Note the difference in the arguments and return values:

    • Reconcile

      1. func (r *ReconcileMemcached) Reconcile(request reconcile.Request) (reconcile.Result, error)
    • Handle

    Instead of receiving an (with the object), the Reconcile() function receives a (Name/Namespace key) to lookup the object.

    If the Reconcile() function returns an error, the controller will requeue and retry the Request. If no error is returned, then depending on the Result the controller will either not retry the Request, immediately retry, or retry after a specified duration.

    Copy the code from the old project’s Handle() function over the existing code in your controller’s Reconcile() function. Be sure to keep the initial section in the Reconcile() code that looks up the object for the Request and checks to see if it’s deleted.

    1. import (
    2. apierrors "k8s.io/apimachinery/pkg/api/errors"
    3. cachev1alpha1 "github.com/example/memcached-operator/pkg/apis/cache/v1alpha1"
    4. ...
    5. )
    6. func (r *ReconcileMemcached) Reconcile(request reconcile.Request) (reconcile.Result, error) {
    7. // Fetch the Memcached instance
    8. instance := &cachev1alpha1.Memcached{}
    9. err := r.client.Get(context.TODO()
    10. request.NamespacedName, instance)
    11. if err != nil {
    12. if apierrors.IsNotFound(err) {
    13. // Request object not found, could have been deleted after reconcile request.
    14. // Owned objects are automatically garbage collected.
    15. // Return and don't requeue
    16. return reconcile.Result{}, nil
    17. }
    18. // Error reading the object - requeue the request.
    19. return reconcile.Result{}, err
    20. }
    21. // Rest of your reconcile code goes here.
    22. ...
    23. }

    Update return values

    Change the return values in your reconcile code:

    • Replace return err with return reconcile.Result{}, err
    • Replace return nil with return reconcile.Result{}, nil

    Periodic reconcile

    1. reconcilePeriod := 30 * time.Second
    2. reconcileResult := reconcile.Result{RequeueAfter: reconcilePeriod}
    3. ...
    4. // Update the status
    5. err := r.client.Update(context.TODO(), memcached)
    6. if err != nil {
    7. log.Info(fmt.Sprintf("Failed to update memcached status: %v", err))
    8. return reconcileResult, err
    9. }
    10. return reconcileResult, nil

    Update client

    Replace the calls to the SDK client(Create, Update, Delete, Get, List) with the reconciler’s client.

    See the examples below and the controller-runtime client API doc for more details.

    1. // Create
    2. dep := &appsv1.Deployment{...}
    3. // v0.0.1
    4. err := sdk.Create(dep)
    5. err := r.client.Create(context.TODO(), dep)
    6. // Update
    7. // v0.1.0
    8. err := sdk.Update(dep)
    9. err := r.client.Update(context.TODO(), dep)
    10. // Delete
    11. err := sdk.Delete(dep)
    12. // v0.1.0
    13. err := r.client.Delete(context.TODO(), dep)
    14. // List
    15. podList := &corev1.PodList{}
    16. labelSelector := labels.SelectorFromSet(labelsForMemcached(memcached.Name))
    17. listOps := &metav1.ListOptions{LabelSelector: labelSelector}
    18. err := sdk.List(memcached.Namespace, podList, sdk.WithListOptions(listOps))
    19. // v0.1.0
    20. listOps := &client.ListOptions{Namespace: memcached.Namespace, LabelSelector: labelSelector}
    21. err := r.client.List(context.TODO(), listOps, podList)
    22. // Get
    23. dep := &appsv1.Deployment{APIVersion: "apps/v1", Kind: "Deployment", Name: name, Namespace: namespace}
    24. err := sdk.Get(dep)
    25. // v0.1.0
    26. dep := &appsv1.Deployment{}
    27. err = r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, dep)
    28. // v0.1.0 with unstructured
    29. dep := &unstructured.Unstructured{}
    30. dep.SetGroupVersionKind(schema.GroupVersionKind{Group:"apps", Version: "v1", Kind:"Deployment"})
    31. err = r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, dep)

    Lastly copy and initialize any other fields that you may have had in your Handler struct into the Reconcile<Kind> struct:

    1. // newReconciler returns a new reconcile.Reconciler
    2. func newReconciler(mgr manager.Manager) reconcile.Reconciler {
    3. return &ReconcileMemcached{client: mgr.GetClient(), scheme: mgr.GetScheme(), foo: "bar"}
    4. }
    5. // ReconcileMemcached reconciles a Memcached object
    6. type ReconcileMemcached struct {
    7. client client.Client
    8. scheme *runtime.Scheme
    9. // Other fields
    10. foo string
    11. }

    Copy changes from main.go

    The main function for a v0.1.0 operator in cmd/manager/main.go sets up the Manager which registers the custom resources and starts all the controllers.

    There is no need to migrate the SDK functions sdk.Watch(),sdk.Handle(), and sdk.Run() from the old main.go since that logic is now defined in a controller.

    However if there are any operator specific flags or settings defined in the old main file copy those over.

    If you have any 3rd party resource types registered with the SDK’s scheme, then register those with the Manager’s scheme in the new project. See how to .

    operator-sdk now expects cmd/manager/main.go to be present in Go operator projects. Go project-specific commands, ex. add [api, controller], will error if main.go is not found in its expected path.

    Copy user defined files

    If there are any user defined pkgs, scripts, and docs in the older project, copy these files into the new project.

    For any updates made to the following manifests in the old project, copy over the changes to their corresponding files in the new project. Be careful not to directly overwrite the files but inspect and make any changes necessary.

    • tmp/build/Dockerfile to build/Dockerfile
      • There is no tmp directory in the new project layout
    • RBAC rules updates from deploy/rbac.yaml to deploy/role.yaml and deploy/role_binding.yaml
    • deploy/cr.yaml to deploy/crds/<full group>_<version>_<kind>_cr.yaml

    Copy user defined dependencies

    For any user defined dependencies added to the old project’s Gopkg.toml, copy and append them to the new project’s Gopkg.toml. Run dep ensure to update the vendor in the new project.

    Confirmation