|
| 1 | +# Variant construction pattern |
| 2 | + |
| 3 | +If you look at the config workflows, you will notice that creating a variant |
| 4 | +of a package is a very frequent operation, so reducing the steps |
| 5 | +required to create a variant can have significant benefits for the |
| 6 | +package consumers. In this guide, we will look at some techniques |
| 7 | +that a package author can use to enable automatic variant construction of a package. |
| 8 | + |
| 9 | +## Types of packages |
| 10 | + |
| 11 | +kpt packages comes in two flavors: `abstract package` and |
| 12 | +`deployable instance`. An `abstract` package is a reususable package that |
| 13 | +is used to create deployable instances that can be deployed to a |
| 14 | +kubernetes cluster. In programming language terms, you can think of an `abstract` |
| 15 | +packages as the class and `deployable instance` as the instances of the class. |
| 16 | +`deployable` instances of package are also referred to as `variant` of the package. |
| 17 | + |
| 18 | +Figure below shows a `package catalog` on the left that has `abstract` packages |
| 19 | +and `deployable instances` on the right. A good pattern is to keep the abstract |
| 20 | +packages and instance packages in separate repos and typically |
| 21 | +`deployable instances` repo will be setup to auto deploy to a kubernetes cluster |
| 22 | +using gitops tools such as `config-sync`, `fluxcd`, `argocd`. |
| 23 | + |
| 24 | + |
| 25 | + |
| 26 | +Resources in an `abstract` package have placeholder values that need to be |
| 27 | +substituted with actual values to make them ready for deployment. |
| 28 | +For example, the name of the namespace resource below has `example` as a placeholder |
| 29 | +value. This is a part of the `abstract package`. |
| 30 | + |
| 31 | +```yaml |
| 32 | +apiVersion:v1 |
| 33 | +kind: Namespace |
| 34 | +metadata: |
| 35 | + name: example # <-- this is a placeholder value |
| 36 | +``` |
| 37 | +
|
| 38 | +## Customizing identity of resources |
| 39 | +
|
| 40 | +A kpt package contains kubernetes resources. Whenever you are creating a |
| 41 | +variant of the package, first step is to ensure unique identity of the |
| 42 | +resources in that variant. For example, if the abstract package contains a |
| 43 | +`namespace` resource, then the variant package should contain a `namespace` resource |
| 44 | +corresponding to that variant. |
| 45 | + |
| 46 | +In a kubernetes cluster, resources are identified by their group, version, kind, |
| 47 | +namespace and name (also referred to as GVKNN). If resource is cluster scoped, |
| 48 | +then the `metadata.name` uniquely identifies the resource in a cluster. If the resource |
| 49 | +is namespace scoped, then (`metadata.namespace`, `name`) together identifies the |
| 50 | +resource uniquely. |
| 51 | + |
| 52 | +[kpt-function-catalog](https://catalog.kpt.dev) provides two function that helps |
| 53 | +with customizing the identify of the resources: |
| 54 | + |
| 55 | +1. [set-namespace](https://catalog.kpt.dev/set-namespace/v0.3/): sets the |
| 56 | + namespace for all resources in the package. |
| 57 | +2. [ensure-name-substring](https://catalog.kpt.dev/ensure-name-substring/v0.2/): |
| 58 | + sets the name of the resources in the package. |
| 59 | + |
| 60 | +You can use the appropriate functions from the catalog or implement a custom |
| 61 | +function to ensure unique identity of the resources. |
| 62 | + |
| 63 | +## Customizing non-identifier fields of resources |
| 64 | + |
| 65 | +Packages can use other functions such as `set-labels`, `set-annotations`, `apply-replacements` |
| 66 | +or custom functions to transform other fields of resources. |
| 67 | + |
| 68 | +## Core mechanism |
| 69 | + |
| 70 | +Enabling automatic variant construction involves two steps: |
| 71 | + |
| 72 | +1. Use functions to customize identity or other fields of the resources |
| 73 | +2. Generating inputs for the functions declarared in the package |
| 74 | + |
| 75 | +Here is an example of `Kptfile` of a package that uses `set-namespace` and `apply-transform` |
| 76 | +to enable customization. |
| 77 | + |
| 78 | +```Kptfile |
| 79 | +# Kptfile |
| 80 | +... |
| 81 | +pipeline: |
| 82 | + mutators: |
| 83 | + - image: set-namespace:v0.3.4 |
| 84 | + configPath: ... |
| 85 | + - image: apply-transform:v0.1.0 |
| 86 | + configPath: ... |
| 87 | +... |
| 88 | +``` |
| 89 | + |
| 90 | +Now let's talk about the input to the functions. In most cases, variant's name |
| 91 | +(deployable instance name) itself can be used to derive unique identity |
| 92 | +of the resources in the variant. For example, if I create a variant of |
| 93 | +`microservice` package, I will name the deployable instance to |
| 94 | +`user-service` or `order-service`. So if the package's name is available to the |
| 95 | +functions, then they can use it to customize the name/namespace of the resources. |
| 96 | +So, starting with `kpt v1.0.0-beta.15+`, kpt makes `package name` available |
| 97 | +in a `ConfigMap` at a well-known path `package-context.yaml` in `data.name` field. |
| 98 | +The `package-context.yaml` is available to functions during `kpt fn render|eval`. |
| 99 | + |
| 100 | +Here are examples of `package-context.yaml` for abstract and deployable instance: |
| 101 | + |
| 102 | +```yaml |
| 103 | +# package-context.yaml |
| 104 | +# package context for an abstract package. |
| 105 | +# This is automatically created on `kpt pkg init`. |
| 106 | +apiVersion: v1 |
| 107 | +kind: ConfigMap |
| 108 | +data: |
| 109 | + name: example # <-- placeholder value |
| 110 | +``` |
| 111 | +
|
| 112 | +```yaml |
| 113 | +# package-context.yaml |
| 114 | +# package context for a deployable instance of a package. |
| 115 | +# This is automatically populated during `kpt pkg get`. |
| 116 | +apiVersion: v1 |
| 117 | +kind: ConfigMap |
| 118 | +data: |
| 119 | + name: my-pkg-instance # <- deployable instance name |
| 120 | +``` |
| 121 | +
|
| 122 | +kpt supports a way to create `deployable instance` such that `package-context.yaml` |
| 123 | +is automatically populated with the `deployable instance`'s name. |
| 124 | + |
| 125 | +```shell |
| 126 | +$kpt pkg get <pkg-location> my-pkg-instance --for-deployment |
| 127 | +``` |
| 128 | + |
| 129 | +Now, let's look at how to provide the input to the functions. |
| 130 | + |
| 131 | +If you are using `set-namespace` function in your package, then |
| 132 | +`set-namespace` function supports reading input from `package-context.yaml`. |
| 133 | +Here is an example: |
| 134 | + |
| 135 | +```Kptfile |
| 136 | +... |
| 137 | +pipeline: |
| 138 | + mutators: |
| 139 | + - image: set-namespace:v0.3.4 |
| 140 | + configPath: package-context.yaml |
| 141 | +... |
| 142 | +``` |
| 143 | + |
| 144 | +By using `package-context.yaml` as input, `set-namespace` uses the value `example` |
| 145 | +for an `abstract` package and variant's name for a deployable instance. The |
| 146 | +same pattern can be applied to other functions also. For example, the |
| 147 | +[`namespace provisioning`](https://github.com/GoogleContainerTools/kpt-samples/tree/main/basens) |
| 148 | +package uses `apply-replacements` function to set the RoleBinding group |
| 149 | +using the name of the package. |
| 150 | + |
| 151 | +In some cases, the inputs needed to generate the variant will come from |
| 152 | +some external system or environment. Those can be generated imperatively or |
| 153 | +manually edited after the package is forked using `kpt pkg get`. Additional |
| 154 | +customizations could also be made at that point. |
| 155 | + |
| 156 | +So for a package consumer, creating a deployable instance involves the following: |
| 157 | + |
| 158 | +```shell |
| 159 | +# pick name of the deployable instance say `my-pkg-instance` |
| 160 | +$ kpt pkg get <path-to-abstract-pkg> <my-pkg-instance> --for-deployement |
| 161 | + |
| 162 | +$ kpt fn render <my-pkg-instance> |
| 163 | + |
| 164 | +``` |
| 165 | + |
| 166 | +## See it in action |
| 167 | + |
| 168 | +If you want to see `variant constructor pattern` in action for a real use-case, |
| 169 | +check out [`namespace provisioning using kpt CLI guide`](/guides/namespace-provisioning-cli.md). |
| 170 | + |
| 171 | +## Summary |
| 172 | + |
| 173 | +With the above pattern and workflow, you can see - how a package publisher can |
| 174 | +enable automatic customization of deployable instance of a package with minimal |
| 175 | +input i.e. package instance name. |
| 176 | + |
| 177 | +## Future plans |
| 178 | + |
| 179 | +Currently `--for-deployment` steps invokes built-in function to generate |
| 180 | +`package-context.yaml`. We would like to make it extensible for users to invoke their |
| 181 | +custom function for deploy workflow. |
0 commit comments