github.com/SamarSidharth/kpt@v0.0.0-20231122062228-c7d747ae3ace/site/guides/namespace-provisioning-cli.md (about) 1 # Namespace provisioning using kpt CLI 2 3 In this guide, we will learn how to create a kpt package from scratch using 4 `kpt CLI`. We will also learn how to enable customization of the package 5 with minimal manual steps for package consumers. 6 7 ## What package are we creating ? 8 9 Onboarding a new application or a micro-service is a very common task for a 10 platform team. It involves provisioning a dedicated namespace (and other 11 associated resources) where all resources that belong to the application reside. 12 In this guide, we will create a package that will be used for provisioning a namespace. 13 14 ## Prerequisites 15 16 ### Repositories 17 18 Platform teams will have to setup two repos: 19 1. Blueprint repo where reusable kpt packages will live. 20 2. Deployment repo where instances of packages that will be deployed to a 21 kubernetes cluster will live. Users will create deployable packages from the 22 packages in the blueprint repo. 23 24 We will refer to the repos with environment variables named $BLUEPRINT_REPO and $DEPLOYMENT_REPO. 25 26 If you don’t have these two repositories, you can follow the steps below using 27 Github’s CLI [gh](https://cli.github.com/) to set up the repos, or set them up 28 via the GitHub GUI. 29 30 ```shell 31 32 # (optional) skip if you've authenticated. 33 # Authenticate gh to create a repository. 34 35 $ gh auth login 36 37 # Create the "blueprint" and "deployment" repos if you don't have them yet 38 39 $ gh repo create blueprint 40 $ gh repo create deployment 41 42 43 # clone and enter the blueprint repo 44 $ USER=<YOUR GITHUB USERNAME> 45 $ BLUEPRINT_REPO=git@github.com:${USER}/blueprint.git 46 $ DEPLOYMENT_REPO=git@github.com:${USER}/deployment.git 47 48 $ git clone ${BLUEPRINT_REPO} 49 $ git clone ${DEPLOYMENT_REPO} 50 51 $ cd blueprint 52 53 ``` 54 55 ### kube-gen.sh 56 57 We will be writing kubernetes manifests from scratch, feel free to use whatever 58 tools (IDE/extensions) you are most comfortable with. We have created this 59 little script that helps in generating kubernetes manifests just with kubectl. 60 So ensure that you have kubectl installed and configured to talk to a 61 kubernetes cluster. 62 63 ```shell 64 # quick check is kubectl is working correctly 65 66 $ kubectl get pods 67 No resources found. 68 ``` 69 70 Run the following bash snippet to create our little k8s manifest generator called 71 kube-gen.sh. 72 73 ```shell 74 $ cat << 'EOF' > kube-gen.sh 75 76 #!/usr/bin/env bash 77 #kube-gen.sh resource-type args 78 res="${1}" 79 shift 1 80 if [[ "${res}" != namespace ]] ; then 81 namespace="--namespace=example" 82 else 83 namespace="" 84 fi 85 kubectl create "${res}" -o yaml --dry-run=client "${@}" ${namespace} |\ 86 egrep -v "creationTimestamp|status" 87 EOF 88 ``` 89 90 Follow the steps below to make sure that script can be invoked from the command line. 91 92 ```shell 93 # make the script executable 94 $ chmod a+x kube-gen.sh 95 96 # let's make it available in our $PATH 97 $ sudo mv kube-gen.sh /usr/local/bin 98 99 # test the script out 100 $ kube-gen.sh --help 101 Create a resource from a file or from stdin. 102 JSON and YAML formats are accepted. 103 .... 104 ``` 105 106 ## Steps 107 108 ### Initialize a package 109 110 ```shell 111 # You should be under the `./blueprint` git directory. If not, check the above 112 # section "Prerequisites | Repositories" 113 114 # create a directory 115 $ mkdir basens 116 117 # let's initialize the package 118 $ kpt pkg init basens --description "kpt package for provisioning namespace" 119 writing basens/Kptfile 120 writing basens/README.md 121 writing basens/package-context.yaml 122 123 # examine the package content 124 $ kpt pkg tree basens 125 Package "basens" 126 ├── [Kptfile] Kptfile tenant 127 └── [package-context.yaml] ConfigMap kptfile.kpt.dev 128 ``` 129 130 ## Adding Resources 131 132 ### Namespace 133 134 Now that we have the package initialized we are ready to add basic resources for 135 provisioning a namespace. 136 137 ```shell 138 # ensure that we are working in the basens directory 139 $ cd basens 140 141 # create namespace 142 $ kube-gen.sh namespace example > namespace.yaml 143 144 # you should see namespace resource 145 $ kpt pkg tree 146 Package "basens" 147 ├── [Kptfile] Kptfile tenant 148 ├── [namespace.yaml] Namespace example 149 └── [package-context.yaml] ConfigMap kptfile.kpt.dev 150 ``` 151 152 Before we add more resources to the package, let's configure our package to 153 ensure that the namespace for new resources in the package is set correctly. 154 kpt offers a set of common functions as part of [kpt-function-catalog](https://catalog.kpt.dev) 155 and it has a [set-namespace](https://catalog.kpt.dev/set-namespace) function 156 that can be used to ensure all resources in a package use the same namespace. 157 158 ```shell 159 # You should be under the "./blueprint/basens" directory. 160 # Make sure you have kpt autocomplete enabled. 161 # How it works: 162 # Reset your brain, assume you do not know how to use `kpt fn eval`, the goal 163 # is to find and add a "namespace" function. 164 # Press the keyboard key `tab` or `tab tab` after each flag `--type`, 165 # `--keywords`, `--image`, `--fn-config` to see available choices, click `tab` 166 # to autocomplete your choice or to see further options. 167 168 $ kpt fn eval --type mutator --keywords namespace --image set-namespace:v0.4.1 --fn-config package-context.yaml 169 [RUNNING] "gcr.io/kpt-fn/set-namespace:v0.4.1" 170 [PASS] "gcr.io/kpt-fn/set-namespace:v0.4.1" in 600ms 171 Results: 172 [info]: namespace "example" updated to "example", 0 values changed 173 174 # let's add `set-namespace` to rendering workflow so that it is invoked whenever 175 # package is rendered. 176 $ kpt fn eval -i set-namespace:v0.4.1 --fn-config package-context.yaml --save -t mutator 177 [RUNNING] "gcr.io/kpt-fn/set-namespace:v0.4.1" 178 [PASS] "gcr.io/kpt-fn/set-namespace:v0.4.1" in 600ms 179 Results: 180 [info]: namespace "example" updated to "example", 0 values changed 181 Added "gcr.io/kpt-fn/set-namespace:v0.4.1" as mutator in the Kptfile. 182 183 # Let's take a look at Kptfile to see if `set-namespace` is added in the 184 # rendering pipeline. 185 $ cat Kptfile 186 apiVersion: kpt.dev/v1 187 kind: Kptfile 188 metadata: 189 name: basens 190 annotations: 191 config.kubernetes.io/local-config: "true" 192 info: 193 description: kpt package for provisioning namespace 194 pipeline: 195 mutators: 196 - image: gcr.io/kpt-fn/set-namespace:v0.4.1 197 configPath: package-context.yaml 198 199 # render the package to ensure we have a working package. 200 $ kpt fn render 201 Package "tenant": 202 [RUNNING] "gcr.io/kpt-fn/set-namespace:v0.4.1" 203 [PASS] "gcr.io/kpt-fn/set-namespace:v0.4.1" in 600ms 204 Results: 205 [info]: namespace "example" updated to "example", 0 values changed 206 Successfully executed 1 function(s) in 1 package(s). 207 ``` 208 209 Note: if you are curious about how KRM functions are implemented. Take a look 210 at [set-namespace code](https://github.com/GoogleContainerTools/kpt-functions-catalog/blob/master/functions/go/set-namespace/transformer/namespace.go) 211 to get a feel for the implementation. 212 213 ### Permissions 214 215 Cluster roles for administering the workloads (say app-admin) will already be 216 created with the correct set of permissions. Organizations will have conventions 217 such as [example.admin@bigco.com](mailto:tenant-name-admins@mycompany.com) 218 (think [order-service.admin@bigco.com](mailto:order-service-admins@googlegroups.com)) 219 as the group name responsible for administering namespace example. So let’s 220 create a rolebinding that grants permissions to the workload service account and 221 the group [example.admin@bigco.com](mailto:tenant-name-admins@mycompany.com) for 222 managing this tenant. 223 224 ```shell 225 # create rolebinding and try out the simple value propagation scenario 226 $ kube-gen.sh rolebinding app-admin --clusterrole=app-admin --group=example.admin@bigco.com > rolebinding.yaml 227 228 $ cat rolebinding.yaml 229 apiVersion: rbac.authorization.k8s.io/v1 230 kind: RoleBinding 231 metadata: 232 name: app-admin 233 namespace: example 234 roleRef: 235 apiGroup: rbac.authorization.k8s.io 236 kind: ClusterRole 237 name: app-admin 238 subjects: 239 - apiGroup: rbac.authorization.k8s.io 240 kind: Group 241 name: example.admin@bigco.com 242 ``` 243 244 To enable automatic customization of this role binding for an instance of a 245 namespace package, we can bind the value of package instance name to the group 246 name in the above role binding resource. We will use the [apply-replacements function](https://catalog.kpt.dev/apply-replacements/v0.1/) 247 from kpt-function-catalog for binding the values. 248 Here is an snippet that does that: 249 250 ```shell 251 # get the value of package name from configmap in `package-context.yaml` 252 # and use it to update the name of the entry in subjects section of app-admin 253 # role binding with a Group kind. Save the config to update-rolebinding.yaml. 254 $ cat > update-rolebinding.yaml << EOF 255 256 apiVersion: fn.kpt.dev/v1alpha1 257 kind: ApplyReplacements 258 metadata: 259 name: update-rolebinding 260 annotations: 261 config.kubernetes.io/local-config: "true" 262 replacements: 263 - source: 264 kind: ConfigMap 265 name: kptfile.kpt.dev 266 fieldPath: data.name 267 targets: 268 - select: 269 name: app-admin 270 kind: RoleBinding 271 fieldPaths: 272 - subjects.[kind=Group].name 273 options: 274 delimiter: '.' 275 index: 0 276 EOF 277 ``` 278 279 Run following commands to add apply-replacements in the package rendering workflow. 280 281 ```shell 282 $ kpt fn eval -i apply-replacements:v0.1.1 --fn-config update-rolebinding.yaml --save -t mutator 283 [RUNNING] "gcr.io/kpt-fn/apply-replacements:v0.1.1" 284 [PASS] "gcr.io/kpt-fn/apply-replacements:v0.1.1" in 1s 285 Added "gcr.io/kpt-fn/apply-replacements:v0.1.1" as mutator in the Kptfile. 286 287 # ensure our package is being rendered correctly 288 $ kpt fn render 289 ``` 290 291 ### Quota 292 293 Let’s add quota limits for this tenant. 294 295 ```shell 296 $ kube-gen.sh quota default --hard=cpu=40,memory=40G > resourcequota.yaml 297 $ kpt fn render 298 299 $ cat resourcequota.yaml 300 apiVersion: v1 301 kind: ResourceQuota 302 metadata: 303 name: default 304 namespace: example 305 spec: 306 hard: 307 cpu: "40" 308 memory: 40G 309 310 ``` 311 312 So with that, we should have our basic namespace provisioning package ready. 313 Let’s take a look at what we have: 314 315 ```shell 316 $ kpt pkg tree 317 Package "basens" 318 ├── [Kptfile] Kptfile basens 319 ├── [namespace.yaml] Namespace example 320 ├── [package-context.yaml] ConfigMap kptfile.kpt.dev 321 ├── [resourcequota.yaml] ResourceQuota example/default 322 ├── [rolebinding.yaml] RoleBinding example/app-admin 323 └── [update-rolebinding.yaml] ApplyReplacements update-rolebinding 324 ``` 325 326 ## Publishing the package 327 328 Now that we have a basic namespace package in place, let's publish it so that 329 other users can consume it. 330 331 ```shell 332 $ cd .. && git add basens && git commit -am "initial pkg" 333 $ git push origin main 334 335 $ git tag basens/v0 && git push origin basens/v0 336 ``` 337 338 So, now the package should be available in the `blueprint` repo. Consumers 339 (application teams or platform team provisioning namespace on behalf of 340 application team) will now use this published package to create deployable 341 instances of it. There are different ways to create deployable instances of 342 this package: 343 344 - [Use package orchestration CLI](guides/porch-user-guide.md) 345 - Use package orchestration UI (coming soon) 346 - Use kpt CLI without package orchestration as described in the next section. 347 348 ### Package Consumption Workflow (without package orchestration) 349 350 Assuming you are onboarding a new micro-service called backend, let’s go 351 through the process of creating an instance of the basens package for backend. 352 You need to do this step in the deployment repo. 353 354 ```shell 355 # Redirect yourself to $DEPLOYMENT_REPO, which is created in "Prerequisites – Repositories 356 $ cd ../deployment 357 358 $ kpt pkg get ${BLUEPRINT_REPO}/basens/@v0 backend --for-deployment 359 Package "backend": 360 Fetching ${BLUEPRINT_REPO}@v0 361 From ${BLUEPRINT_REPO} 362 * tag basens/v0 -> FETCH_HEAD 363 Adding package "basens". 364 Fetched 1 package(s). 365 366 Customizing package for deployment. 367 [RUNNING] "builtins/gen-pkg-context" 368 [PASS] "builtins/gen-pkg-context" in 0s 369 Results: 370 [info]: generated package context 371 372 Customized package for deployment. 373 ``` 374 375 Render the `backend` package so that the package is customized for the `backend`. 376 377 ```shell 378 379 $ kpt fn render backend 380 Package "backend": 381 [RUNNING] "gcr.io/kpt-fn/set-namespace:v0.4.1" 382 [PASS] "gcr.io/kpt-fn/set-namespace:v0.4.1" in 900ms 383 Results: 384 [info]: namespace "example" updated to "backend", 3 values changed 385 [RUNNING] "gcr.io/kpt-fn/apply-replacements:v0.1.1" 386 [PASS] "gcr.io/kpt-fn/apply-replacements:v0.1.1" in 1s 387 388 Successfully executed 2 function(s) in 1 package(s). 389 # Important thing to note in the above output is that the `set-namespace` 390 # function updated the namespace for the `backend` package instance automatically. 391 392 # examine the output of backend package 393 $ kpt pkg tree backend 394 Package "backend" 395 ├── [Kptfile] Kptfile backend 396 ├── [namespace.yaml] Namespace backend 397 ├── [package-context.yaml] ConfigMap kptfile.kpt.dev 398 ├── [resourcequota.yaml] ResourceQuota backend/default 399 ├── [rolebinding.yaml] RoleBinding backend/app-admin 400 └── [update-rolebinding.yaml] ApplyReplacements update-rolebinding 401 402 ``` 403 404 So with that we now have a backend service ready to be onboarded and applied to 405 the cluster. So the first step would be to commit and tag the backend package in 406 the deployment repo. 407 408 ```shell 409 # assuming you are in deployment repo 410 411 $ git add backend && git commit -am "initial pkg for deployment" 412 $ git push origin main 413 414 # tag the package 415 $ git tag backend/v0 main && git push origin backend/v0 416 417 ``` 418 419 ## Deploy the package in kubernetes cluster 420 421 Now let’s deploy the package using kpt live: 422 423 ```shell 424 # assuming you are in the deployment directory 425 426 $ kpt live init backend 427 initializing Kptfile inventory info (namespace: backend)...success 428 429 $ kpt live apply backend 430 namespace/backend unchanged 431 namespace/backend reconciled 432 resourcequota/default created 433 rolebinding.rbac.authorization.k8s.io/app-admin created 434 3 resource(s) applied. 2 created, 1 unchanged, 0 configured, 0 failed 435 resourcequota/default reconcile pending 436 rolebinding.rbac.authorization.k8s.io/app-admin reconcile pending 437 resourcequota/default reconciled 438 rolebinding.rbac.authorization.k8s.io/app-admin reconciled 439 3 resource(s) reconciled, 0 skipped, 0 failed to reconcile, 0 timed out 440 ``` 441 442 ## References 443 444 [Kubernetes RBAC reference documentation](https://kubernetes.io/docs/reference/access-authn-authz/rbac/)