1---
2title: "Part 4: Install a Helm Chart"
3description: Install metrics-server from its Helm chart with Kubernetes.HelmChart, pass chart values, and check pod metrics with kubectl top.
4sidebar:
5 order: 4
6---
7
8import Terminal from "../../../../components/Terminal.astro";
9import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
10
11In [Part 3](/kubernetes/tutorial/part-3) you ran your own Effect
12programs as Jobs. Now you'll install third-party software from a
13Helm chart, which is how most clusters get it.
14
15The chart is [metrics-server](https://github.com/kubernetes-sigs/metrics-server),
16which 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
22machine. If you haven't installed it, follow the
23[instructions](https://helm.sh/docs/intro/install/). For example:
24
25```sh
26brew 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`
42also accepts an `oci://` reference or a local chart directory.
43`version` pins the chart so every deploy renders the same objects.
44Cluster add-ons conventionally live in `kube-system`, which already
45exists, so no Namespace resource is needed.
46
47## Name the release
48
49```diff lang="typescript"
50version: "3.14.0",
51+releaseName: "metrics-server",
52namespace: "kube-system",
53```
54
55Charts name their objects after the Helm release name. Without
56`releaseName`, Alchemy generates one from the stack, stage, and
57logical ID, and the chart's Deployment ends up with a long generated
58name. Choose the release name before the first deploy. Changing it
59later replaces the whole chart.
60
61## Pass chart values
62
63kind's kubelets serve self-signed certificates, so metrics-server
64must 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
96Proceed?
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
102Alchemy ran `helm template` locally and applied the nine rendered
103objects through the same server-side apply path as a Manifest. They
104include 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
111Wait for metrics-server to roll out, give it a minute to collect its
112first samples, then ask for pod usage:
113
114```sh
115kubectl --context kind-alchemy -n kube-system rollout status deployment/metrics-server
116kubectl --context kind-alchemy top pods -n my-app
117```
118
119```text
120NAME CPU(cores) MEMORY(bytes)
121web-c65f4dd44-h8d8d 1m 16Mi
122web-c65f4dd44-rw7jm 1m 16Mi
123```
124
125## Change a value
126
127Values are part of the chart's identity. If you change one, for
128example to run two replicas, the next deploy re-renders the chart and
129applies the difference:
130
131```diff lang="typescript"
132values: {
133 args: ["--kubelet-insecure-tls"],
134+ replicas: 2,
135},
136```
137
138If a new render leaves out an object that the previous render
139contained, Alchemy deletes it from the cluster.
140
141## Recap
142
143You 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
152In [Part 5](/kubernetes/tutorial/part-5), you'll point this stack at a
153cluster of your own.