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/)