-
Notifications
You must be signed in to change notification settings - Fork 1.8k
doc: add migration guide #653
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
Merged
hasbro17
merged 12 commits into
operator-framework:master
from
hasbro17:add-migration-guide
Oct 25, 2018
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
37b4f34
doc: add migration guide
hasbro17 afa1b24
Update doc/migration/v0.1.0-migration-guide.md
estroz d39af22
Update doc/migration/v0.1.0-migration-guide.md
estroz 5a048c9
Update doc/migration/v0.1.0-migration-guide.md
estroz 0dd9e29
Update doc/migration/v0.1.0-migration-guide.md
estroz 1b44bda
Update doc/migration/v0.1.0-migration-guide.md
estroz 32a9b34
Update doc/migration/v0.1.0-migration-guide.md
estroz ec57205
doc: fix capitalization
hasbro17 de12e84
Update doc/migration/v0.1.0-migration-guide.md
AlexNPavel 1d4c5a2
doc/migration: pin example memcached-operator link to commit
hasbro17 bcace8c
doc/migration: format client comparisons as a table
hasbro17 281b24d
doc/migration: added section on periodic reconcile
hasbro17 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,303 @@ | ||
# Migration Guide from v0.0.x to v0.1.0 | ||
|
||
This document describes how to migrate an operator project built using Operator SDK `v0.0.x` to the project structure required by `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][v0.0.7-memcached-operator] and [v0.1.0 memcached-operator][v0.1.0-memcached-operator] project structures for pre- and post-migration examples, respectively. | ||
|
||
## Create a new v0.1.0 project | ||
|
||
Rename your `v0.0.x` project and create a new `v0.1.0` project in its place. | ||
|
||
```sh | ||
# Ensure SDK version is v0.1.0 | ||
$ operator-sdk --version | ||
operator-sdk version 0.1.0 | ||
|
||
# Create new project | ||
$ cd $GOPATH/src/github.com/example-inc/ | ||
$ mv memcached-operator old-memcached-operator | ||
$ operator-sdk new memcached-operator | ||
$ ls | ||
memcached-operator old-memcached-operator | ||
|
||
# Copy over .git from old project | ||
$ cp -rf old-memcached-operator/.git memcached-operator/.git | ||
``` | ||
|
||
## Migrate custom types from pkg/apis | ||
|
||
### Scaffold api for custom types | ||
|
||
Create the api for your custom resource (CR) in the new project with `operator-sdk add api --api-version=<apiversion> --kind=<kind>` | ||
```sh | ||
$ cd memcached-operator | ||
$ operator-sdk add api --api-version=cache.example.com/v1alpha1 --kind=Memcached | ||
|
||
$ tree pkg/apis | ||
pkg/apis/ | ||
├── addtoscheme_cache_v1alpha1.go | ||
├── apis.go | ||
└── cache | ||
└── v1alpha1 | ||
├── doc.go | ||
├── memcached_types.go | ||
├── register.go | ||
└── 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. | ||
```Go | ||
func init() { | ||
SchemeBuilder.Register(&Memcached{}, &MemcachedList{}) | ||
} | ||
``` | ||
|
||
## Migrate reconcile code | ||
|
||
### 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` | ||
```Go | ||
sdk.Watch("cache.example.com/v1alpha1", "Memcached", "default", time.Duration(5)*time.Second) | ||
``` | ||
|
||
For a `v0.1.0` project you define a [Controller][controller-go-doc] to watch resources. | ||
|
||
Add a controller to watch your CR type with `operator-sdk add controller --api-version=<apiversion> --kind=<kind>`. | ||
``` | ||
$ operator-sdk add controller --api-version=cache.example.com/v1alpha1 --kind=Memcached | ||
$ tree pkg/controller | ||
pkg/controller/ | ||
├── add_memcached.go | ||
├── controller.go | ||
└── memcached | ||
└── memcached_controller.go | ||
``` | ||
|
||
Inspect the `add()` function in your `pkg/controller/<kind>/<kind>_controller.go` file: | ||
```Go | ||
import ( | ||
cachev1alpha1 "github.com/example-inc/memcached-operator/pkg/apis/cache/v1alpha1" | ||
... | ||
) | ||
|
||
func add(mgr manager.Manager, r reconcile.Reconciler) error { | ||
c, err := controller.New("memcached-controller", mgr, controller.Options{Reconciler: r}) | ||
|
||
// Watch for changes to the primary resource Memcached | ||
err = c.Watch(&source.Kind{Type: &cachev1alpha1.Memcached{}}, &handler.EnqueueRequestForObject{}) | ||
|
||
// Watch for changes to the secondary resource Pods and enqueue reconcile requests for the owner Memcached | ||
err = c.Watch(&source.Kind{Type: &corev1.Pod{}}, &handler.EnqueueRequestForOwner{ | ||
IsController: true, | ||
OwnerType: &cachev1alpha1.Memcached{}, | ||
}) | ||
} | ||
``` | ||
Remove the second `Watch()` or modify it to watch a secondary resource type that is owned by your CR. | ||
|
||
Watching multiple resources lets you trigger the reconcile loop for multiple resources relevant to your application. See the [watching and eventhandling][watching-eventhandling-doc] doc and the Kubernetes [controller conventions][controller-conventions] doc for more details. | ||
hasbro17 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#### 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. | ||
```Go | ||
// Watch for changes to the primary resource Memcached | ||
err = c.Watch(&source.Kind{Type: &cachev1alpha1.Memcached{}}, &handler.EnqueueRequestForObject{}) | ||
|
||
// Watch for changes to the secondary resource AppService and enqueue reconcile requests for the owner Memcached | ||
err = c.Watch(&source.Kind{Type: &appv1alpha1.AppService{}}, &handler.EnqueueRequestForOwner{ | ||
IsController: true, | ||
OwnerType: &cachev1alpha1.Memcached{}, | ||
}) | ||
``` | ||
- Add a new controller to watch and reconcile the CR independently of the other CR. | ||
```sh | ||
$ operator-sdk add controller --api-version=app.example.com/v1alpha1 --kind=AppService | ||
``` | ||
```Go | ||
// Watch for changes to the primary resource AppService | ||
err = c.Watch(&source.Kind{Type: &appv1alpha1.AppService{}}, &handler.EnqueueRequestForObject{}) | ||
``` | ||
|
||
### Copy and modify reconcile code from pkg/stub/handler.go | ||
|
||
In a `v0.1.0` project the reconcile code is defined in the `Reconcile()` method of a controller's [Reconciler][reconciler-go-doc]. This is similar to the `Handle()` function in the older project. Note the difference in the arguments and return values: | ||
- Reconcile | ||
```Go | ||
func (r *ReconcileMemcached) Reconcile(request reconcile.Request) (reconcile.Result, error) | ||
``` | ||
- Handle | ||
```Go | ||
func (h *Handler) Handle(ctx context.Context, event sdk.Event) error | ||
``` | ||
|
||
Instead of receiving an `sdk.Event` (with the object), the `Reconcile()` function receives a [Request][request-go-doc] (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][result-go-doc] 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. | ||
|
||
```Go | ||
import ( | ||
apierrors "k8s.io/apimachinery/pkg/api/errors" | ||
cachev1alpha1 "github.com/example-inc/memcached-operator/pkg/apis/cache/v1alpha1" | ||
... | ||
) | ||
func (r *ReconcileMemcached) Reconcile(request reconcile.Request) (reconcile.Result, error) { | ||
// Fetch the Memcached instance | ||
instance := &cachev1alpha1.Memcached{} | ||
err := r.client.Get(context.TODO() | ||
request.NamespacedName, instance) | ||
if err != nil { | ||
if apierrors.IsNotFound(err) { | ||
// Request object not found, could have been deleted after reconcile request. | ||
// Owned objects are automatically garbage collected. | ||
// Return and don't requeue | ||
return reconcile.Result{}, nil | ||
} | ||
// Error reading the object - requeue the request. | ||
return reconcile.Result{}, err | ||
} | ||
joelanford marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Rest of your reconcile code goes here. | ||
... | ||
} | ||
``` | ||
#### 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` | ||
|
||
joelanford marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#### Periodic reconcile | ||
In order to periodically reconcile a CR in your controller you can set the [RequeueAfter][result-go-doc] field for reconcile.Result. | ||
This will cause the controller to requeue the `Request` and trigger the reconcile after the desired duration. Note that the default value of 0 means no requeue. | ||
|
||
```Go | ||
reconcilePeriod := 30 * time.Second | ||
reconcileResult := reconcile.Result{RequeueAfter: reconcilePeriod} | ||
... | ||
|
||
// Update the status | ||
err := r.client.Update(context.TODO(), memcached) | ||
if err != nil { | ||
log.Printf("failed to update memcached status: %v", err) | ||
return reconcileResult, err | ||
} | ||
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][client-api-doc] for more details. | ||
|
||
```Go | ||
// Create | ||
hasbro17 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
dep := &appsv1.Deployment{...} | ||
err := sdk.Create(dep) | ||
// v0.0.1 | ||
err := r.client.Create(context.TODO(), dep) | ||
|
||
// Update | ||
err := sdk.Update(dep) | ||
// v0.0.1 | ||
err := r.client.Update(context.TODO(), dep) | ||
|
||
// Delete | ||
err := sdk.Delete(dep) | ||
// v0.0.1 | ||
err := r.client.Delete(context.TODO(), dep) | ||
|
||
// List | ||
podList := &corev1.PodList{} | ||
labelSelector := labels.SelectorFromSet(labelsForMemcached(memcached.Name)) | ||
listOps := &metav1.ListOptions{LabelSelector: labelSelector} | ||
hasbro17 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
err := sdk.List(memcached.Namespace, podList, sdk.WithListOptions(listOps)) | ||
// v0.1.0 | ||
listOps := &client.ListOptions{Namespace: memcached.Namespace, LabelSelector: labelSelector} | ||
err := r.client.List(context.TODO(), listOps, podList) | ||
|
||
// Get | ||
dep := &appsv1.Deployment{APIVersion: "apps/v1", Kind: "Deployment", Name: name, Namespace: namespace} | ||
err := sdk.Get(dep) | ||
// v0.1.0 | ||
dep := &appsv1.Deployment{} | ||
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: | ||
|
||
```Go | ||
// newReconciler returns a new reconcile.Reconciler | ||
func newReconciler(mgr manager.Manager) reconcile.Reconciler { | ||
hasbro17 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return &ReconcileMemcached{client: mgr.GetClient(), scheme: mgr.GetScheme(), foo: "bar"} | ||
} | ||
|
||
// ReconcileMemcached reconciles a Memcached object | ||
type ReconcileMemcached struct { | ||
client client.Client | ||
scheme *runtime.Scheme | ||
// Other fields | ||
foo string | ||
} | ||
``` | ||
|
||
### Copy changes from main.go | ||
|
||
The main function for a `v0.1.0` operator in `cmd/manager/main.go` sets up the [Manager][manager-go-doc] 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 [register 3rd party resources][register-3rd-party-resources]. | ||
|
||
### 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. | ||
|
||
### Copy changes to deployment manifests | ||
hasbro17 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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` | ||
hasbro17 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
- 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/<group>_<version>_<kind>_cr.yaml` | ||
- `deploy/crd.yaml` to `deploy/crds/<group>_<version>_<kind>_crd.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 | ||
|
||
At this point you should be able to build and run your operator to verify that it works. See the [user-guide][user-guide-build-run] on how to build and run your operator. | ||
|
||
[v0.1.0-changes-doc]: ./v0.1.0-changes.md | ||
[v0.0.7-memcached-operator]: https://github.com/operator-framework/operator-sdk-samples/tree/aa15bd278eec0959595e0a0a7282a26055d7f9d6/memcached-operator | ||
[v0.1.0-memcached-operator]: https://github.com/operator-framework/operator-sdk-samples/tree/4c6934448684a6953ece4d3d9f3f77494b1c125e/memcached-operator | ||
[controller-conventions]: https://github.com/kubernetes/community/blob/master/contributors/devel/controllers.md#guidelines | ||
[reconciler-go-doc]: https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg/reconcile#Reconciler | ||
[watching-eventhandling-doc]: https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg#hdr-Watching_and_EventHandling | ||
[controller-go-doc]: https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg#hdr-Controller | ||
[request-go-doc]: https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg/reconcile#Request | ||
[result-go-doc]: https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg/reconcile#Result | ||
[client-api-doc]: ./../user/client.md | ||
[manager-go-doc]: https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg/manager | ||
[register-3rd-party-resources]: ./../user-guide.md#adding-3rd-party-resources-to-your-operator | ||
[user-guide-build-run]: ./../user-guide.md#build-and-run-the-operator |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.