|
| 1 | +# Migrating Existing Kubernetes API's |
| 2 | + |
| 3 | +Kubernetes API's are assumed to evolve over time, hence the well-defined API [versioning scheme][k8s-versioning]. Upgrading your operator's API's can be a non-trivial task, one that will involve changing quite a few source files and manifests. This document aims to identify the complexities of migrating an operator project's API using examples from existing operators. |
| 4 | + |
| 5 | +While examples in this guide follow particular types of API migrations, most of the documented migration steps can be generalized to all migration types. A thorough discussion of migration types for a particular project type (Go, Ansible, Helm) is found at the end of each project type's section. |
| 6 | + |
| 7 | +## Golang: Upgrading one Kind to a new Version from a Version with multiple Kind's |
| 8 | + |
| 9 | +**Scenario:** your Go operator test-operator has one API version `v1` for group `operators.example.com`. You would like to migrate (upgrade) one kind `CatalogSourceConfig` to `v2` while keeping the other `v1` kind `OperatorGroup` in `v1`. These kind's will remain in group `operators.example.com`. Your project structure looks like the following: |
| 10 | + |
| 11 | +```console |
| 12 | +$ tree pkg/apis |
| 13 | +pkg/apis/ |
| 14 | +├── addtoscheme_operators_v1.go |
| 15 | +├── apis.go |
| 16 | +└── operators |
| 17 | + └── v1 |
| 18 | + ├── catalogsourceconfig_types.go |
| 19 | + ├── catalogsourceconfig.go |
| 20 | + ├── doc.go |
| 21 | + ├── operatorgroup_types.go |
| 22 | + ├── operatorgroup.go |
| 23 | + ├── phase.go |
| 24 | + ├── phase_types.go |
| 25 | + ├── register.go |
| 26 | + ├── shared.go |
| 27 | + ├── zz_generated.deepcopy.go |
| 28 | + ├── zz_generated.openapi.go |
| 29 | +``` |
| 30 | + |
| 31 | +Relevant files: |
| 32 | + |
| 33 | +- `catalogsourceconfig_types.go` and `catalogsourceconfig.go` contain types and functions used by API kind type `CatalogSourceConfig`. |
| 34 | +- `operatorgroup_types.go` and `operatorgroup.go` contain types and functions used by API kind type `OperatorGroup`. |
| 35 | +- `phase_types.go` and `phase.go` contain types and functions used by *non-API* type `Phase`, which is used by both `CatalogSourceConfig` and `OperatorGroup` types. |
| 36 | +- `shared.go` contain types and functions used by both `CatalogSourceConfig` and `OperatorGroup` types. |
| 37 | + |
| 38 | +#### Questions to ask yourself |
| 39 | +1. **Scope:** what files, Go source and YAML, must I modify when migrating? |
| 40 | +1. **Shared code:** do I have shared types and functions between `CatalogSourceConfig` and `OperatorGroup`? How do I want shard code refactored? |
| 41 | +1. **Imports:** which packages import those I am migrating? How do I modify these packages to import `v2` and possible new shared package(s)? |
| 42 | +1. **Backwards-compatibility:** do I want to remove code being migrated from `v1` entirely, forcing use of `v2`, or support both `v1` and `v2` going forward? |
| 43 | + |
| 44 | +--- |
| 45 | + |
| 46 | +### Setting up to upgrade versions |
| 47 | + |
| 48 | +Creating the new version `v2` is the first step in upgrading your kind `CatalogSourceConfig`. Use the `operator-sdk` to do so by running the following command: |
| 49 | + |
| 50 | +```console |
| 51 | +$ operator-sdk add api --api-version operators.example.com/v2 --kind CatalogSourceConfig |
| 52 | +``` |
| 53 | + |
| 54 | +This command creates a new API version `v2` under group `operators`: |
| 55 | + |
| 56 | +```console |
| 57 | +$ tree pkg/apis |
| 58 | +pkg/apis/ |
| 59 | +├── addtoscheme_operators_v1.go |
| 60 | +├── addtoscheme_operators_v2.go # new addtoscheme source file for v2 |
| 61 | +├── apis.go |
| 62 | +└── operators |
| 63 | + └── v1 |
| 64 | + | ├── catalogsourceconfig_types.go |
| 65 | + | ├── catalogsourceconfig.go |
| 66 | + | ├── doc.go |
| 67 | + | ├── operatorgroup_types.go |
| 68 | + | ├── operatorgroup.go |
| 69 | + | ├── phase.go |
| 70 | + | ├── phase_types.go |
| 71 | + | ├── register.go |
| 72 | + | ├── shared.go |
| 73 | + | ├── zz_generated.deepcopy.go |
| 74 | + | ├── zz_generated.openapi.go |
| 75 | + └── v2 # new version dir with source files for v2 |
| 76 | + ├── catalogsourceconfig_types.go |
| 77 | + ├── doc.go |
| 78 | + ├── register.go |
| 79 | + ├── zz_generated.deepcopy.go |
| 80 | + ├── zz_generated.openapi.go |
| 81 | +``` |
| 82 | + |
| 83 | +In addition to creating a new API version, the command creates an `addtoscheme_operators_v2.go` file that exposes an `AddToScheme()` function for registering `v2.CatalogSourceConfig` and `v2.CatalogSourceConfigList`. |
| 84 | + |
| 85 | +### Moving shared type definitions and functions to a separate package |
| 86 | + |
| 87 | +Now that `v2` and all related project structure exist, we can begin moving types and functions around. First we must move anything shared between `CatalogSourceConfig` and `OperatorGroup` to a separate package that can be imported by both `v1` and `v2`. We've identified the files containing these types above: `phase.go`, `phase_types.go`, and `shared.go`. |
| 88 | + |
| 89 | +#### Creating a new 'shared' package |
| 90 | + |
| 91 | +Lets create a new package `shared` at `pkg/apis/operators/shared` for these files: |
| 92 | + |
| 93 | +```console |
| 94 | +$ pwd |
| 95 | +/home/user/projects/test-operator |
| 96 | +$ mkdir pkg/apis/operators/shared |
| 97 | +``` |
| 98 | + |
| 99 | +This package is not a typical API because it contains types only to be used as parts of larger schema, and therefore should not be created with `operator-sdk add api`. It should contain a `doc.go` file with some package-level documentation and annotations: |
| 100 | + |
| 101 | +```console |
| 102 | +$ cat > pkg/apis/operators/shared/doc.go <<EOF |
| 103 | +// +k8s:deepcopy-gen=package,register |
| 104 | + |
| 105 | +// Package shared contains types and functions used by API definitions in the |
| 106 | +// operators package |
| 107 | +// +groupName=operators.example.com |
| 108 | +package shared |
| 109 | +EOF |
| 110 | +``` |
| 111 | + |
| 112 | +Global annotations necessary for using `shared` types in API type fields: |
| 113 | + |
| 114 | +- `+k8s:deepcopy-gen=package,register`: directs [`deepcopy-gen`][deepcopy-gen] to generate `DeepCopy()` functions for all types in the `shared` package. |
| 115 | +- `+groupName=operators.example.com`: defines the fully qualified API group name for [`client-gen`][client-gen]. Note: this annotation *must* be on the line above `package shared`. |
| 116 | + |
| 117 | +We recommend adding more comments explaining what types and functions exist in `shared` and how they are intended to be used. If you have any comments in `pkg/apis/operators/v1/doc.go` related to copied source code, ensure they are moved into `pkg/apis/operators/v2/doc.go`. |
| 118 | + |
| 119 | +#### Moving types to package shared |
| 120 | + |
| 121 | +The three files containing shared code (`phase.go`, `phase_types.go`, and `shared.go`) can *almost* be copied as-is from `v1` to `shared`. The only changes necessary are: |
| 122 | + |
| 123 | +- Changing the package statements in each file: `package v1` -> `package shared`. |
| 124 | +- Exporting types, their methods, and functions used by external API types. |
| 125 | + |
| 126 | +This will regenerate deepcopy code for all tagged types in `pkg/apis`. |
| 127 | + |
| 128 | +Additionally, `deepcopy-gen` must be run on the new package to generate `DeepCopy()` and `DeepCopyInto()` methods, which are necessary for all Kubernetes API code. Doing so will also remove deepcopy code for the now `shared` types from `v1`. To do so, run the following command: |
| 129 | + |
| 130 | +```console |
| 131 | +$ operator-sdk generate k8s |
| 132 | +``` |
| 133 | + |
| 134 | +Now that shared types and functions have their own package we can update types in package `v1`, and any other package that imports them from `v1`, to use those in `shared`. The source file `catalogsourceconfig_types.go` imports and uses a type defined in `shared`, `ObjectPhase`, as a field in `CatalogSourceConfigSpec`. |
| 135 | + |
| 136 | +```Go |
| 137 | +import ( |
| 138 | + "github.com/test-org/test-operator/pkg/apis/operators/shared" |
| 139 | + |
| 140 | + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| 141 | +) |
| 142 | + |
| 143 | +... |
| 144 | + |
| 145 | +type CatalogSourceConfig struct { |
| 146 | + metav1.TypeMeta `json:",inline"` |
| 147 | + metav1.ListMeta `json:"metadata,omitempty"` |
| 148 | + |
| 149 | + Spec CatalogSourceConfigSpec `json:"spec,omitempty"` |
| 150 | + Status CatalogSourceConfigStatus `json:"status,omitempty"` |
| 151 | +} |
| 152 | + |
| 153 | +type CatalogSourceConfigSpec struct { |
| 154 | + ... |
| 155 | +} |
| 156 | + |
| 157 | +type CatalogSourceConfigStatus struct { |
| 158 | + // The type was previously ObjectPhase, now shared.ObjectPhase. |
| 159 | + CurrentPhase shared.ObjectPhase `json:"currentPhase,omitempty"` |
| 160 | + ... |
| 161 | +} |
| 162 | +``` |
| 163 | + |
| 164 | +Do this for all instances of types previously in `v1` that are now in `shared`. Once done, remove `phase.go`, `phase_types.go`, and `shared.go` from `pkg/apis/operators/v1`. |
| 165 | + |
| 166 | +### Updating empty v2 types using v1 types |
| 167 | + |
| 168 | +The `CatalogSourceConfig` type and schema code were generated by `operator-sdk add api`, but the types are not populated. We need to move existing type data from `v1` to `v2`. This process is similar to migrating shared code, except we do not need to export any types or functions. |
| 169 | + |
| 170 | +Remove `pkg/apis/operators/v2/catalogsourceconfig_types.go` and copy `catalogsourceconfig.go` and `catalogsourceconfig_types.go` from `pkg/apis/operators/v1` to `pkg/apis/operators/v2`: |
| 171 | + |
| 172 | +```console |
| 173 | +$ rm pkg/apis/operators/v2/catalogsourceconfig_types.go |
| 174 | +$ cp pkg/apis/operators/v1/catalogsourceconfig*.go pkg/apis/operators/v2 |
| 175 | +``` |
| 176 | + |
| 177 | +If you have any comments or custom code in `pkg/apis/operators/v1` related to source code in either copied file, ensure that is copied to `doc.go` or `register.go` in `pkg/apis/operators/v2`. |
| 178 | + |
| 179 | +You can now run `operator-sdk generate k8s` to generate deepcopy code for the migrated `v2` types. Once this is done, update all packages that import the migrated `v1` types to use those in `v2`. |
| 180 | + |
| 181 | +The final step is to remove `catalogsourceconfig.go` and `catalogsourceconfig_types.go` and any related comments or custom code in `doc.go` or `register.go` from `pkg/apis/operators/v1`. |
| 182 | + |
| 183 | +**Note:** updating package import paths will likely be the most pervasive change lines-of-code-wise in this process. Luckily the Go compiler will tell you which import path's you have missed once `CatalogSourceConfig` types are removed from `v1`! |
| 184 | + |
| 185 | +### Updating CustomResourceDefinition manifests and generating OpenAPI code |
| 186 | + |
| 187 | +Now that we've migrated all Go types to their destination packages, we must update the corresponding CustomResourceDefinition (CRD) manifests in `deploy/crds` and generate [OpenAPI][openapi-gen] validation source. |
| 188 | + |
| 189 | +Doing so can be as simple as running the following command: |
| 190 | + |
| 191 | +```console |
| 192 | +$ operator-sdk generate openapi |
| 193 | +``` |
| 194 | + |
| 195 | +This command will automatically update all CRD manifests. |
| 196 | + |
| 197 | +#### CRD Versioning |
| 198 | + |
| 199 | +Kubernetes 1.11+ supports CRD [`spec.versions`][crd-versions] and `spec.version` is [deprecated][crd-version-deprecated] as of Kubernetes 1.12. SDK versions `v0.9.x` and below leverage [`controller-tools`][controller-tools]' CRD generator `v0.1.x` which generates a now-deprecated `spec.version` value based on the version contained in an API's import path. Names of CRD manifest files generated by those SDK versions contain the `spec.version`, i.e. one CRD manifest is created *per version in a group* with the form `<group>_<version>_<kind>_crd.yaml`. The SDK is in the process of upgrading to `controller-tools` `v0.2.x`, which generates `spec.versions` but not `spec.version` by default. Once the upgrade is complete, future SDK versions will place all versions in a group in `spec.versions`. File names will then have the format `<group>_<resource>_crd.yaml`. |
| 200 | + |
| 201 | +**Note:** If your operator does not have custom data manually added to its CRD's, you can skip to the [following section](#golang-api-migrations-types-and-commonalities); `operator-sdk generate openapi` will handle CRD updates in that case. |
| 202 | + |
| 203 | +Upgrading from `spec.version` to `spec.versions` will be demonstrated using the following CRD manifest example: |
| 204 | + |
| 205 | +`deploy/crds/operators_v1_catalogsourceconfig_crd.yaml`: |
| 206 | +```yaml |
| 207 | +apiVersion: apiextensions.k8s.io/v1beta1 |
| 208 | +kind: CustomResourceDefinition |
| 209 | +metadata: |
| 210 | + name: catalogsourceconfigs.operators.coreos.com |
| 211 | +spec: |
| 212 | + group: operators.coreos.com |
| 213 | + names: |
| 214 | + kind: CatalogSourceConfig |
| 215 | + listKind: CatalogSourceConfigList |
| 216 | + plural: catalogsourceconfigs |
| 217 | + singular: catalogsourceconfig |
| 218 | + scope: Namespaced |
| 219 | + validation: |
| 220 | + openAPIV3Schema: |
| 221 | + properties: |
| 222 | + apiVersion: |
| 223 | + type: string |
| 224 | + kind: |
| 225 | + type: string |
| 226 | + metadata: |
| 227 | + type: object |
| 228 | + spec: |
| 229 | + properties: |
| 230 | + size: |
| 231 | + format: int32 |
| 232 | + type: integer |
| 233 | + test: |
| 234 | + type: string |
| 235 | + required: |
| 236 | + - size |
| 237 | + type: object |
| 238 | + status: |
| 239 | + properties: |
| 240 | + nodes: |
| 241 | + items: |
| 242 | + type: string |
| 243 | + type: array |
| 244 | + required: |
| 245 | + - nodes |
| 246 | + type: object |
| 247 | + version: v1 |
| 248 | + subresources: |
| 249 | + status: {} |
| 250 | +``` |
| 251 | +
|
| 252 | +Steps to upgrade the above CRD: |
| 253 | +
|
| 254 | +1. Rename your CRD manifest file from `deploy/crds/operators_v1_catalogsourceconfig_crd.yaml` to `deploy/crds/catalogsourceconfigs.operators.coreos.com_catalogsourceconfigs_crd.yaml` |
| 255 | + |
| 256 | + ```console |
| 257 | + $ mv deploy/crds/cache_v1alpha1_memcached_crd.yaml deploy/crds/catalogsourceconfigs.operators.coreos.com_catalogsourceconfigs_crd.yaml |
| 258 | + ``` |
| 259 | + |
| 260 | +1. Create a `spec.versions` list that contains two elements for each version that now exists (`v1` and `v2`): |
| 261 | + |
| 262 | + ```yaml |
| 263 | + spec: |
| 264 | + ... |
| 265 | + # version is now v2, as it must match the first element in versions. |
| 266 | + version: v2 |
| 267 | + versions: |
| 268 | + - name: v2 |
| 269 | + # Set to true for this CRD version to be enabled in-cluster. |
| 270 | + served: true |
| 271 | + # Exactly one CRD version should be a storage version. |
| 272 | + storage: true |
| 273 | + - name: v1 |
| 274 | + served: true |
| 275 | + storage: false |
| 276 | + ``` |
| 277 | + |
| 278 | + The first version in `spec.versions` *must* match that in `spec.version` if `spec.version` exists in the manifest. |
| 279 | + |
| 280 | +1. *Optional:* `spec.versions` elements have a `schema` field that holds a version-specific OpenAPIV3 validation block to override the global `spec.validation` block. `spec.validation` will be used by the API server to validate one or more version in `spec.versions` that does not have a `schema` block. If all versions have the same schema, leave `spec.validation` as-is and skip to the [following section](#golang-api-migrations-types-and-commonalities). If your CRD versions differ in scheme, copy `spec.validation` YAML to the `schema` field in each `spec.versions` element, then modify as needed: |
| 281 | + |
| 282 | + ```yaml |
| 283 | + spec: |
| 284 | + ... |
| 285 | + version: v2 |
| 286 | + versions: |
| 287 | + - name: v2 |
| 288 | + served: true |
| 289 | + storage: true |
| 290 | + schema: # v2-specific OpenAPIV3 validation block. |
| 291 | + openAPIV3Schema: |
| 292 | + properties: |
| 293 | + apiVersion: |
| 294 | + type: string |
| 295 | + ... |
| 296 | + - name: v1 |
| 297 | + served: true |
| 298 | + storage: false |
| 299 | + schema: # v1-specific OpenAPIV3 validation block. |
| 300 | + openAPIV3Schema: |
| 301 | + properties: |
| 302 | + apiVersion: |
| 303 | + type: string |
| 304 | + ... |
| 305 | + ``` |
| 306 | + |
| 307 | + The API server will validate each version by its own `schema` if the global `spec.validation` block is removed. No validation will be performed if a `schema` does not exist for a version and `spec.validation` does not exist. |
| 308 | + |
| 309 | + If the CRD targets a Kubernetes 1.13+ cluster with the `CustomResourceWebhookConversion` feature enabled, converting between multiple versions can be done using a [conversion webhooks][crd-conv-webhook]. |
| 310 | + |
| 311 | + **Note:** read the [CRD versioning][crd-versions] docs for detailed CRD information, notes on conversion webhooks, and CRD versioning case studies. |
| 312 | + |
| 313 | +1. *Optional:* `spec.versions` elements have a `subresources` field that holds CR subresource information to override the global `spec.subresources` block. `spec.subresources` will be used by the API server to assess subresource requirements of any version in `spec.versions` that does not have a `subresources` block. If all versions have the same requirements, leave `spec.subresources` as-is and skip to the [following section](#golang-api-migrations-types-and-commonalities). If CRD versions differ in subresource requirements, add a `subresources` section in each `spec.versions` entry with differing requirements and add each subresource's spec and status as needed: |
| 314 | + |
| 315 | + ```yaml |
| 316 | + spec: |
| 317 | + ... |
| 318 | + version: v2 |
| 319 | + versions: |
| 320 | + - name: v2 |
| 321 | + served: true |
| 322 | + storage: true |
| 323 | + subresources: |
| 324 | + ... |
| 325 | + - name: v1 |
| 326 | + served: true |
| 327 | + storage: false |
| 328 | + subresources: |
| 329 | + ... |
| 330 | + ``` |
| 331 | + |
| 332 | + Remove the global `spec.subresources` block if all versions have different subresource requirements. |
| 333 | + |
| 334 | +1. *Optional:* remove `spec.version`, as it is deprecated in favor of `spec.versions`. |
| 335 | + |
| 336 | +### Golang API Migrations: Types and Commonalities |
| 337 | + |
| 338 | +This version upgrade walkthrough demonstrates only one of several possible migration scenarios: |
| 339 | + |
| 340 | +- Group migration, ex. moving an API from group `operators.example.com/v1` to `new-group.example.com/v1alpha1`. |
| 341 | +- Kind change, ex. `CatalogSourceConfig` to `CatalogSourceConfigurer`. |
| 342 | +- Some combination of group, version, and kind migration. |
| 343 | + |
| 344 | +Each case is different; one may require many more changes than others. However there are several themes common to all: |
| 345 | + |
| 346 | +1. Using `operator-sdk add api` to create the necessary directory structure and files used in migration. |
| 347 | + - Group migration using the same version, for each kind in the old group `operators.example.com` you want to migrate: |
| 348 | + |
| 349 | + ```console |
| 350 | + $ operator-sdk add api --api-version new-group.example.com/v1 --kind YourKind |
| 351 | + ``` |
| 352 | + |
| 353 | + - Kind migration, using the same group and version as `CatalogSourceConfig`: |
| 354 | + |
| 355 | + ```console |
| 356 | + $ operator-sdk add api --api-version operators.example.com/v1 --kind CatalogSourceConfigurer |
| 357 | + ``` |
| 358 | + |
| 359 | +1. Moving code from one Go package to another, ex. from `v1` to `v2` and `shared`. |
| 360 | +1. Changing import paths in project Go source files to those of new packages. |
| 361 | +1. Updating CRD manifests. |
| 362 | + - In many cases, having sufficient [code annotations][kubebuilder-api-annotations] and running `operator-sdk generate openapi` will be enough. |
| 363 | + |
| 364 | +The Go toolchain can be your friend here too. Running `go vet ./...` can tell you what import paths require changing and what type instantiations are using fields incorrectly. |
| 365 | + |
| 366 | +## Helm |
| 367 | + |
| 368 | +TODO |
| 369 | + |
| 370 | +## Ansible |
| 371 | + |
| 372 | +TODO |
| 373 | + |
| 374 | +[k8s-versioning]:https://kubernetes.io/docs/concepts/overview/kubernetes-api/#api-versioning |
| 375 | +[deepcopy-gen]:https://godoc.org/k8s.io/gengo/examples/deepcopy-gen |
| 376 | +[client-gen]:https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/generating-clientset.md |
| 377 | +[openapi-gen]:https://github.com/kubernetes/kube-openapi |
| 378 | +[controller-tools]:https://github.com/kubernetes-sigs/controller-tools |
| 379 | +[crd-versions]:https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definition-versioning/ |
| 380 | +[crd-conv-webhook]:https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definition-versioning/#webhook-conversion |
| 381 | +[kubebuilder-api-annotations]:https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html |
| 382 | +[crd-version-deprecated]:https://github.com/kubernetes/apiextensions-apiserver/commit/d1c6536f26319513417b12245c6e3aee5ca005ca |
0 commit comments