| 1 | --- |
| 2 | title: "Part 4: Install a Helm Chart" |
| 3 | description: Install metrics-server from its Helm chart with Kubernetes.HelmChart, pass chart values, and check pod metrics with kubectl top. |
| 4 | sidebar: |
| 5 | order: 4 |
| 6 | --- |
| 7 | |
| 8 | import Terminal from "../../../../components/Terminal.astro"; |
| 9 | import { Code, Tabs, TabItem } from "@astrojs/starlight/components"; |
| 10 | |
| 11 | In [Part 3](/kubernetes/tutorial/part-3) you ran your own Effect |
| 12 | programs as Jobs. Now you'll install third-party software from a |
| 13 | Helm chart, which is how most clusters get it. |
| 14 | |
| 15 | The chart is [metrics-server](https://github.com/kubernetes-sigs/metrics-server), |
| 16 | which collects CPU and memory usage from each node and powers |
| 17 | `kubectl top`. Local kind clusters don't ship with it. |
| 18 | |
| 19 | ## Install helm |
| 20 | |
| 21 | `Kubernetes.HelmChart` renders charts with the `helm` CLI on your |
| 22 | machine. If you haven't installed it, follow the |
| 23 | [instructions](https://helm.sh/docs/intro/install/). For example: |
| 24 | |
| 25 | ```sh |
| 26 | brew install helm |
| 27 | ``` |
| 28 | |
| 29 | ## Add the chart |
| 30 | |
| 31 | ```diff lang="typescript" |
| 32 | +const metricsServer = yield* Kubernetes.HelmChart("MetricsServer", { |
| 33 | + cluster, |
| 34 | + chart: "metrics-server", |
| 35 | + repo: "https://kubernetes-sigs.github.io/metrics-server/", |
| 36 | + version: "3.14.0", |
| 37 | + namespace: "kube-system", |
| 38 | +}); |
| 39 | ``` |
| 40 | |
| 41 | `chart` and `repo` name a chart in a classic Helm repository. `chart` |
| 42 | also accepts an `oci://` reference or a local chart directory. |
| 43 | `version` pins the chart so every deploy renders the same objects. |
| 44 | Cluster add-ons conventionally live in `kube-system`, which already |
| 45 | exists, so no Namespace resource is needed. |
| 46 | |
| 47 | ## Name the release |
| 48 | |
| 49 | ```diff lang="typescript" |
| 50 | version: "3.14.0", |
| 51 | +releaseName: "metrics-server", |
| 52 | namespace: "kube-system", |
| 53 | ``` |
| 54 | |
| 55 | Charts name their objects after the Helm release name. Without |
| 56 | `releaseName`, Alchemy generates one from the stack, stage, and |
| 57 | logical ID, and the chart's Deployment ends up with a long generated |
| 58 | name. Choose the release name before the first deploy. Changing it |
| 59 | later replaces the whole chart. |
| 60 | |
| 61 | ## Pass chart values |
| 62 | |
| 63 | kind's kubelets serve self-signed certificates, so metrics-server |
| 64 | must skip verifying them. Charts take their configuration as |
| 65 | **values**, in the same shape as a `values.yaml` file: |
| 66 | |
| 67 | ```diff lang="typescript" |
| 68 | namespace: "kube-system", |
| 69 | + values: { |
| 70 | + args: ["--kubelet-insecure-tls"], |
| 71 | + }, |
| 72 | }); |
| 73 | ``` |
| 74 | |
| 75 | ## Deploy |
| 76 | |
| 77 | <Tabs syncKey="pkgManager"> |
| 78 | <TabItem label="bun" icon="bun"> |
| 79 | <Code code="bun alchemy deploy" lang="sh" /> |
| 80 | </TabItem> |
| 81 | <TabItem label="npm" icon="npm"> |
| 82 | <Code code="npm run alchemy deploy" lang="sh" /> |
| 83 | </TabItem> |
| 84 | <TabItem label="pnpm" icon="pnpm"> |
| 85 | <Code code="pnpm alchemy deploy" lang="sh" /> |
| 86 | </TabItem> |
| 87 | <TabItem label="yarn" icon="seti:yarn"> |
| 88 | <Code code="yarn alchemy deploy" lang="sh" /> |
| 89 | </TabItem> |
| 90 | </Tabs> |
| 91 | |
| 92 | <Terminal content={`[u]Plan[/u]: [g]1 to create[/g] |
| 93 | |
| 94 | [g]+[/g] [b]MetricsServer[/b] [d](Kubernetes.HelmChart)[/d] |
| 95 | |
| 96 | Proceed? |
| 97 | ◉ Yes ○ No |
| 98 | [d]Rendering Helm chart metrics-server@3.14.0...[/d] |
| 99 | [d]Applying 9 objects from metrics-server...[/d] |
| 100 | [g]✓[/g] [b]MetricsServer[/b] [d](Kubernetes.HelmChart)[/d] [g]created[/g]`} /> |
| 101 | |
| 102 | Alchemy ran `helm template` locally and applied the nine rendered |
| 103 | objects through the same server-side apply path as a Manifest. They |
| 104 | include a Deployment, a Service, RBAC roles and bindings, and the |
| 105 | `APIService` that registers the metrics API. There is no |
| 106 | `helm install` and no release record in the cluster, so |
| 107 | `helm list -A` shows nothing. Alchemy owns the objects directly. |
| 108 | |
| 109 | ## Verify it worked |
| 110 | |
| 111 | Wait for metrics-server to roll out, give it a minute to collect its |
| 112 | first samples, then ask for pod usage: |
| 113 | |
| 114 | ```sh |
| 115 | kubectl --context kind-alchemy -n kube-system rollout status deployment/metrics-server |
| 116 | kubectl --context kind-alchemy top pods -n my-app |
| 117 | ``` |
| 118 | |
| 119 | ```text |
| 120 | NAME CPU(cores) MEMORY(bytes) |
| 121 | web-c65f4dd44-h8d8d 1m 16Mi |
| 122 | web-c65f4dd44-rw7jm 1m 16Mi |
| 123 | ``` |
| 124 | |
| 125 | ## Change a value |
| 126 | |
| 127 | Values are part of the chart's identity. If you change one, for |
| 128 | example to run two replicas, the next deploy re-renders the chart and |
| 129 | applies the difference: |
| 130 | |
| 131 | ```diff lang="typescript" |
| 132 | values: { |
| 133 | args: ["--kubelet-insecure-tls"], |
| 134 | + replicas: 2, |
| 135 | }, |
| 136 | ``` |
| 137 | |
| 138 | If a new render leaves out an object that the previous render |
| 139 | contained, Alchemy deletes it from the cluster. |
| 140 | |
| 141 | ## Recap |
| 142 | |
| 143 | You now have a complete stack on your local cluster: |
| 144 | |
| 145 | - A podinfo Deployment in its own Namespace, configured from |
| 146 | TypeScript |
| 147 | - An Effect smoke test that re-runs when it changes, and the same |
| 148 | program on a schedule |
| 149 | - metrics-server installed from a pinned Helm chart, with Alchemy |
| 150 | owning every rendered object |
| 151 | |
| 152 | In [Part 5](/kubernetes/tutorial/part-5), you'll point this stack at a |
| 153 | cluster of your own. |