diff --git a/docs/pages/blog/2023-08-27.md b/docs/pages/blog/2023-08-27.md new file mode 100644 index 000000000..55453809b --- /dev/null +++ b/docs/pages/blog/2023-08-27.md @@ -0,0 +1,228 @@ +--- +author: Alessandro De Maria +author_gh_user: ademariag +read_time: 10m +publish_date: 27/08/2023 +--- + +# :kapitan-logo: **Deploying Keda with Kapitan** + +We have worked hard to bring out a brand new way of experience Kapitan, through something that we call [generators](https://generators.kapicorp.com) + +Although the concept is something we've introduced in 2020 with our blog post [Keep your ship together with Kapitan](https://medium.com/kapitan-blog/keep-your-ship-together-with-kapitan-d82d441cc3e7), the sheer amount of new capabilities (and frankly, the embarassing lack of documentation and examples) forces me to show you the new capabilities using a practicle example: deploying [Keda](https://keda.sh/docs/deploy/). + +## Objective of this tutorial + +We are going to deploy Keda using the helm chart approach. While Kapitan supports a native way to deploy helm charts using the helm input type, we are going instead to use a generator based approach using the "`charts`" generator. + +This tutorial will show you how to configure kapitan to: + +* download a helm chart +* compile a helm chart +* modify a helm chart using mutations + +The content of this tutorial is already available on the [`kapitan-reference`](https://github.com/kapicorp/kapitan-reference) + +## Deploying KEDA + +### Define parameters + +```yaml +## inventory/classes/components/keda.yml +parameters: + keda: + params: + # Variables to reference from other places + application_version: 2.11.2 + service_account_name: keda-operator + chart_name: keda + chart_version: 2.11.2 + chart_dir: system/sources/charts/${keda:params:chart_name}/${keda:params:chart_name}/${keda:params:chart_version}/${keda:params:application_version} + namespace: keda + helm_values: {} +... +``` + +!!! tip "Override Helm Values" + As an example we could be passing to helm an override to the default `values` parameters to make the operator deploy 2 replicas. + + ```yaml + helm_values: + operator: + replicaCount: 2 + ``` + +### Download the chart + +Kapitan supports downloading dependencies, including helm charts. + +When Kapitan is run with the `--fetch`, it will download the dependency if not already present. +Use `--force-fetch` if you want to download it every time. Learn more about [External dependencies](https://kapitan.dev/pages/external_dependencies/#defining-dependencies) + +```yaml +## inventory/classes/components/keda.yml +... + kapitan: + dependencies: + # Tells kapitan to download the helm chart into the chart_dir directory + - type: helm + output_path: ${keda:params:chart_dir} + source: https://kedacore.github.io/charts + version: ${keda:params:chart_version} + chart_name: ${keda:params:chart_name} +... +``` + +!!! tip "Parameter interpolation" + Notice how we are using parameter interpolation from the previously defined `keda.params` section. This will make it easier in the future to override some aspects of the configuration on a per-target base. + +### Generate the chart + +```yaml +## inventory/classes/components/keda.yml +... + charts: + # Configures a helm generator to compile files for the given chart + keda: + chart_dir: ${keda:params:chart_dir} + helm_params: + namespace: ${keda:params:namespace} + name: ${keda:params:chart_name} + helm_values: ${keda:params:helm_values} +``` + +### Compile + +Before we can see any effect, we need to attach the class to a target. We will create a simple target which looks like + +```yaml +# inventory/targets/tutorials/keda.yml +classes: +- common +- components.keda +``` + +Now when we run `kapitan compile` we will see the chart being donwloaded and the manifests being produced. + + +```shell +./kapitan compile -t keda --fetch +Dependency keda: saved to system/sources/charts/keda/keda/2.11.2/2.11.2 +Rendered inventory (1.87s) +Compiled keda (2.09s) +``` +!!! note "`kapitan compile` breakdown" + + * `--fetch` tells kapitan to fetch the chart if it is not found locally + * `-t keda` tells kapitan to compile only the previously defined `keda.yml` target + +```shell +ls -l compiled/keda/manifests/ +total 660 +-rw-r--r-- 1 ademaria root 659081 Aug 29 10:25 keda-bundle.yml +-rw-r--r-- 1 ademaria root 79 Aug 29 10:25 keda-namespace.yml +-rw-r--r-- 1 ademaria root 7092 Aug 29 10:25 keda-rbac.yml +-rw-r--r-- 1 ademaria root 1783 Aug 29 10:25 keda-service.yml +``` + +## Using mutations + +Now let's do a couple of things that would not be easy to do with helm natively. + +You can already notice that the content of the chart is being splitted into multiple files: this is because the Generator is configured to separate different resources types into different files for convenience and consistency. The mechanism behing it is the "Mutation" of type "bundle" which tells Kapitan which file to save a resource into. + +Here are some example "mutation" which separates different `kinds` into different files + +```yaml + mutations: + bundle: + - conditions: + kind: [Ingress] + filename: '{content.component_name}-ingress' + ... + - conditions: + kind: [HorizontalPodAutoscaler, PodDisruptionBudget, VerticalPodAutoscaler] + filename: '{content.component_name}-scaling' + - conditions: + kind: ['*'] + filename: '{content.component_name}-bundle' +``` + +!!! tip "Catch-all rule" + Notice the catchall rule at the end that puts everything that has not matched into the `bundle.yml` file + +### `bundle` mutation` + +Currently most of the keda related resources are bundled into the `-bundle.yml` file +Instead, we want to separate them into their own file. + +Let's add this configuration: + +```yaml + + charts: + # Configures a helm generator to compile files for the given chart + keda: + chart_dir: ${keda:params:chart_dir} + ... + mutations: + bundle: + - conditions: + # CRDs need to be setup separately + kind: [CustomResourceDefinition] + filename: '{content.component_name}-crds' +``` + +Upon compile, you can now see that the CRD are being moved to a different file: + +```shell +ls -l compiled/keda/manifests/ +total 664 +-rw-r--r-- 1 ademaria root 11405 Aug 29 10:56 keda-bundle.yml +-rw-r--r-- 1 ademaria root 647672 Aug 29 10:56 keda-crds.yml +-rw-r--r-- 1 ademaria root 79 Aug 29 10:56 keda-namespace.yml +-rw-r--r-- 1 ademaria root 7092 Aug 29 10:56 keda-rbac.yml +-rw-r--r-- 1 ademaria root 1783 Aug 29 10:56 keda-service.yml +``` + +### `patch` mutation + +As we are using Argo, we want to pass a special `argocd.argoproj.io/sync-options` annotation to the CRD only so that ArgoCD can handle them properly. + +For this we are going to use the `patch` mutation: + +```yaml + +... + mutations: +... + patch: + - conditions: + kind: [CustomResourceDefinition] + patch: + metadata: + annotations: + argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true,Replace=true +``` + +Upon compile, you can now see that the CRDs have been modified as required: + +```shell + +diff --git a/compiled/keda/manifests/keda-crds.yml b/compiled/keda/manifests/keda-crds.yml +index 2662bf3..9306c3a 100644 +--- a/compiled/keda/manifests/keda-crds.yml ++++ b/compiled/keda/manifests/keda-crds.yml +@@ -2,6 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 + kind: CustomResourceDefinition + metadata: + annotations: ++ argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true,Replace=true + controller-gen.kubebuilder.io/version: v0.12.0 + +``` + +## Summary + +With this tutorial have explored some capabilities of Kapitan to manage and perform changes to helm charts. +Next tutorial will show how to make use of Keda and deploy a generator for Keda resources \ No newline at end of file diff --git a/kapitan/resources.py b/kapitan/resources.py index e77f4c885..e568d6618 100644 --- a/kapitan/resources.py +++ b/kapitan/resources.py @@ -210,12 +210,14 @@ def search_imports(cwd, import_str, search_paths): - search_paths is the location where to look for import_str if not in cwd The only supported parameters are cwd and import_str, so search_paths needs to be closured. + This function returns a tuple[str, bytes] since jsonnet 0.19.0 require the + content of the file to be provided as a bytes type instead of a str. """ basename = os.path.basename(import_str) full_import_path = os.path.normpath(os.path.join(cwd, import_str)) if full_import_path in JSONNET_CACHE: - return full_import_path, JSONNET_CACHE[full_import_path] + return full_import_path, JSONNET_CACHE[full_import_path].encode() if not os.path.exists(full_import_path): # if import_str not found, search in install_path @@ -246,7 +248,7 @@ def search_imports(cwd, import_str, search_paths): normalised_path_content = f.read() JSONNET_CACHE[normalised_path] = normalised_path_content - return normalised_path, normalised_path_content + return normalised_path, normalised_path_content.encode() def inventory(search_paths, target, inventory_path=None): diff --git a/mkdocs.yml b/mkdocs.yml index 9f5c6fab8..9acf9bffd 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -119,6 +119,7 @@ nav: - Terraform: pages/examples/terraform.md - Blog: - 2023: + - Kapitan Generators: pages/blog/2023-08-27.md - New Kapitan release: pages/blog/2023-01-16.md - 2022: - 5 years of Kapitan: pages/blog/2022-12-04.md diff --git a/poetry.lock b/poetry.lock index 30d331b4b..cfe5354da 100644 --- a/poetry.lock +++ b/poetry.lock @@ -317,34 +317,34 @@ files = [ [[package]] name = "cryptography" -version = "41.0.2" +version = "41.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711"}, - {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7"}, - {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d"}, - {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f"}, - {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182"}, - {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83"}, - {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5"}, - {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58"}, - {file = "cryptography-41.0.2-cp37-abi3-win32.whl", hash = "sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76"}, - {file = "cryptography-41.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4"}, - {file = "cryptography-41.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a"}, - {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd"}, - {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766"}, - {file = "cryptography-41.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee"}, - {file = "cryptography-41.0.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831"}, - {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b"}, - {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa"}, - {file = "cryptography-41.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e"}, - {file = "cryptography-41.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14"}, - {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2"}, - {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f"}, - {file = "cryptography-41.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0"}, - {file = "cryptography-41.0.2.tar.gz", hash = "sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c"}, + {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507"}, + {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116"}, + {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c"}, + {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae"}, + {file = "cryptography-41.0.3-cp37-abi3-win32.whl", hash = "sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306"}, + {file = "cryptography-41.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4"}, + {file = "cryptography-41.0.3.tar.gz", hash = "sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34"}, ] [package.dependencies] @@ -396,13 +396,13 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.31" +version = "3.1.32" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" files = [ - {file = "GitPython-3.1.31-py3-none-any.whl", hash = "sha256:f04893614f6aa713a60cbbe1e6a97403ef633103cdd0ef5eb6efe0deb98dbe8d"}, - {file = "GitPython-3.1.31.tar.gz", hash = "sha256:8ce3bcf69adfdf7c7d503e78fd3b1c492af782d58893b650adb2ac8912ddd573"}, + {file = "GitPython-3.1.32-py3-none-any.whl", hash = "sha256:e3d59b1c2c6ebb9dfa7a184daf3b6dd4914237e7488a1730a6d8f6f5d0b4187f"}, + {file = "GitPython-3.1.32.tar.gz", hash = "sha256:8d9b8cb1e80b9735e8717c9362079d3ce4c6e5ddeebedd0361b228c3a67a62f6"}, ] [package.dependencies] @@ -410,12 +410,12 @@ gitdb = ">=4.0.1,<5" [[package]] name = "gojsonnet" -version = "0.17.0" +version = "0.20.0" description = "Python bindings for Jsonnet - The data templating language" optional = true python-versions = "*" files = [ - {file = "gojsonnet-0.17.0.tar.gz", hash = "sha256:f6fc47d9b1dd0099158978216880c86cb0fcae866b4ca54a5e42e7416bec3b98"}, + {file = "gojsonnet-0.20.0.tar.gz", hash = "sha256:9aede3b5734dee1c99dbec75dee3b086baaae92bd262d93f9217e21bf19c9682"}, ] [[package]] @@ -639,12 +639,12 @@ files = [ [[package]] name = "jsonnet" -version = "0.18.0" +version = "0.20.0" description = "Python bindings for Jsonnet - The data templating language" optional = false python-versions = "*" files = [ - {file = "jsonnet-0.18.0.tar.gz", hash = "sha256:4ccd13427e9097b6b7d6d38f78f638a55ab8b452a257639e8e9af2178ec235d4"}, + {file = "jsonnet-0.20.0.tar.gz", hash = "sha256:7e770c7bf3a366b97b650a39430450f77612e74406731eb75c5bd59f3f104d4f"}, ] [[package]] @@ -1366,4 +1366,4 @@ test = ["docker"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "705734ada27559c4ecbd058884e639fff92deaae1154b6b9ac900d2487fc24cb" +content-hash = "288cc31712df96bf1534fc72b893a5fd67f009e9ddc6b2bfe4c8c972da0489d4" diff --git a/pyproject.toml b/pyproject.toml index a97889c20..3d2662c91 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ gitpython = "^3.1.30" google-api-python-client = "^2.15.0" hvac = "^0.11.0" jinja2 = "^3.0.1" -jsonnet = "^0.18.0" +jsonnet = "^0.20.0" jsonschema = "^4.17.3" kadet = "^0.2.2" python-gnupg = "^0.4.7" @@ -62,7 +62,7 @@ certifi = "*" gitdb = "^4.0.10" packaging = "^23.0" typing-extensions = "^4.0.0" -gojsonnet = { version = "^0.17.0", optional = true } +gojsonnet = { version = "^0.20.0", optional = true } docker = { version = "^5.0.0", optional = true } [tool.poetry.extras]