1. Wstęp
W tym wpisie rozszerzamy poprzedni przykład prostego klastra Kubernetes, dodając do niego Helm charts. Najpierw wprowadzam kilka zmian w środowisku, następnie przechodzę do minimalnego przykładu pracy z Helmem, pokazując podstawową strukturę chartu i sposób wykonania pierwszego wdrożenia. Celem jest pokazanie, że Helm można wykorzystywać w bardzo prosty sposób, jednocześnie stanowi on solidny fundament pod bardziej złożone scenariusze.
2. Czym jest Helm
Helm to menedżer pakietów dla Kubernetes, którego zadaniem jest uproszczenie procesu wdrażania i zarządzania aplikacjami. Kubernetes często wymaga wielu manifestów YAML opisujących różne zasoby: Deployment, Service, ConfigMapy, ingressy i inne. Helm pozwala zebrać je w jedną, spójną strukturę, którą można łatwo instalować, aktualizować i usuwać. Helm działa jako warstwa nad Kubernetesem — generuje manifesty, a następnie stosuje je w klastrze. Największą korzyścią jest możliwość parametryzacji i ponownego wykorzystania tych samych definicji w różnych środowiskach.
2.1 Czym są Helm Charts
Helm charts to paczki opisujące aplikację lub jej fragment w Kubernetesie.
Można o nich myśleć jak o projekcie, który zawiera wszystkie pliki
potrzebne do wdrożenia konkretnego komponentu.
Typowy chart składa się z czterech głównych elementów:
- Chart – metadane chartu: nazwa, wersja, opis, wersja API.
- Values – plik z parametrami konfiguracyjnymi.
- katalog templates/ – szablony manifestów Kubernetes
(Deployment, Service, ConfigMap itd.). - katalog charts/ – zależności (w prostych projektach zwykle pusty).
Chart jest uporządkowanym zbiorem plików, dzięki czemu umożliwia wdrażanie aplikacji w sposób przewidywalny i powtarzalny. Sprawdza się zarówno w małych eksperymentach, jak i w dużych środowiskach produkcyjnych.
3. Implementacja
Kod źródłowy do serii wpisów znajduje się w repozytorium GitHub:
https://github.com/mpiotro4/k8s_demo/tree/blog/2024-11-25-helm
3.1 Kilka zmian na początku
Na wstępie parę zmian w porównaniu do ostatniego wpisu. Po pierwsze nasze proste API stało się mniej proste, wyświetla parę dodatkowych informacji:
mpio@Marcins-MacBook-Air ~ % curl localhost:8080 | jq
{
"hostname": "Marcins-MacBook-Air.local",
"message": "default message",
"namespace": null,
"node_name": null,
"pod_ip": null,
"status": "ok"
}
Oprócz tego przerzuciłem się z klastra Ranchera na Kind.
mpio@Marcins-MacBook-Air ~ % kind create cluster
Creating cluster "kind" ...
✓ Ensuring node image (kindest/node:v1.34.0) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
Set kubectl context to "kind-kind"
Ponieważ Kind nie posiada wbudowanego load balancera, typ LoadBalancer nie działa — Service pozostaje w stanie „pending”. Najprostsze obejście to kubectl port-forward:
kubectl port-forward service/demo-api 8080:8080
Po przekierowaniu portów aplikacja działa wewnątrz klastra:
{
"hostname": "demo-api-7df9444d9c-f2w47",
"namespace": "default",
"node_name": "kind-control-plane",
"pod_ip": "10.244.0.5",
"status": "ok"
}
3.2 Minimalny przykład
Aby rozpocząć pracę z Helmem, generujemy przykładowy chart:
helm create simple-chart
Powstaje pełny „starter chart” z wieloma plikami. Aby uprościć projekt, usuwamy zbędne elementy i zostawiamy jedynie:
simple-chart/
├── Chart.yaml
├── values.yaml
└── templates/
├── deployment.yaml
└── service.yaml
Plik Chart.yaml zawiera metadane, values.yaml może przechowywać parametry konfiguracyjne, a katalog templates/ zawiera szablony manifestów.
Teraz można zainstalować chart:
helm install my-release-name ./demo-chart
W efekcie aplikacja zostaje wdrożona do klastra, podobnie jak w poprzednim wpisie.
3.3 Trochę mniej minimalny przykład
Warto uporządkować nazwy i parametry, zamiast powtarzać identyczne wartości w wielu miejscach. Przykład z deployment.yaml pokazuje powtarzającą się nazwę demo-api. Lepszym rozwiązaniem jest przeniesienie tych wartości do values.yaml.
appName: demo-api
config:
APP_PORT: "8080"
APP_MESSAGE: "Hello from Helm Values"
W szablonie Deploymentu można teraz odwoływać się do nich przez .Values:
metadata:
name: { { .Values.appName } }
labels:
app: { { .Values.appName } }
Analogiczną zmianę wykonujemy w configmap.yaml.
Po zaktualizowaniu chartu wykonujemy:
helm upgrade my-release-name ./demo-chart
Aby Pody otrzymały nowe wartości, należy wymusić rollout Deploymentu:
kubectl rollout restart deployment demo-api
Po restarcie:
{
"hostname": "demo-api-865cc7f5d4-8bhkl",
"message": "Hello from Helm Values",
"namespace": "default",
"node_name": "kind-control-plane",
"pod_ip": "10.244.0.20",
"status": "ok"
}
4. Podział na środowiska
Gdy mamy już za sobą pierwsze wykorzystanie Helm values, możemy przejść do czegoś bardziej praktycznego, czyli wdrażania tej samej aplikacji w kilku środowiskach. Jest to bardzo powszechna praktyka w organizacjach pracujących zespołowo, ponieważ pozwala rozwijać, testować i weryfikować zmiany bez wpływu na użytkowników końcowych.
Typowy podział środowisk wygląda następująco:
- PROD – środowisko produkcyjne, z którego korzystają użytkownicy końcowi,
- DEV – środowisko developerskie, wykorzystywane do bieżącej pracy i testowania zmian,
- QA – środowisko przeznaczone dla zespołów QA, gdzie aplikacja podlega testom przed wdrożeniem na produkcję.
Dzięki Helm values możemy dla każdego środowiska definiować inne parametry takie jak:
- wersja obrazu Dockera,
- konfiguracja aplikacji (np. zmienne środowiskowe),
- integracje z zewnętrznymi systemami (np. mocki zamiast prawdziwych usług).
Uwaga praktyczna:
W tym przykładzie środowiska (DEV/QA/PROD) są rozdzielone przy pomocy namespace’ów w jednym klastrze Kubernetes. Jest to podejście często stosowane lokalnie, w małych zespołach oraz w celach edukacyjnych, ponieważ jest tanie i proste w utrzymaniu.
W środowiskach produkcyjnych najczęściej spotyka się jednak inny model, w którym każde środowisko działa w osobnym klastrze Kubernetes (np. osobny cluster dla DEV, STAGE i PROD), a namespace’y służą głównie do separacji aplikacji, zespołów lub tenantów w obrębie jednego środowiska.
Na potrzeby tego wpisu namespace’y pełnią rolę środowisk, co pozwala skupić się na mechanice Helm values bez wprowadzania dodatkowej złożoności infrastrukturalnej.
4.1 Implementacja
Na początek rozszerzyłem plik values.yaml z poprzedniej części o nowy parametr namespace. To on będzie odpowiadał za logiczne odseparowanie poszczególnych środowisk w klastrze Kubernetes.
appName: demo-api
config:
name: demo-api-config
APP_PORT: 8081
APP_MESSAGE: "Hello from Helm Values"
namespace: "default"
image:
name: kddny/demo_api
tag: latest
W bazowym pliku values.yaml parametr namespace ustawiony jest na default, jednak w praktyce rzadko korzysta się z tego namespace’u dla aplikacji. Zamiast tego tworzymy osobne pliki z nadpisaniami wartości dla konkretnych środowisk.
Przykładowy plik values-dev.yaml dla środowiska DEV może wyglądać następująco:
config:
APP_MESSAGE: "Hello from DEV"
namespace: dev
Zdefiniowałem tutaj wyłącznie te wartości, które różnią się od domyślnych. Pozostałe pola zostaną automatycznie odziedziczone z głównego values.yaml. W analogiczny sposób można przygotować pliki values-qa.yaml oraz values-prod.yaml.
Aby wdrożyć aplikację w środowisku DEV, używam komendy helm install z dodatkowymi parametrami:
helm install my-release-name-dev ./demo-chart \
--values ./demo-chart/values.yaml \
-f ./demo-chart/values-dev.yaml \
-n dev
Znaczenie poszczególnych elementów:
my-release-name-dev– nazwa releasu, unikalna w obrębie klastra,./demo-chart– ścieżka do charta Helm,--values/-f– pliki z wartościami, które Helm łączy w jedną konfigurację (kolejne nadpisują poprzednie),-n dev– namespace, w którym zostaną utworzone zasoby.
W analogiczny sposób wdrożyłem aplikację również w środowisku PROD, używając osobnego pliku z wartościami i innej nazwy releasu.
Na koniec wystarczy wystawić odpowiednie porty lokalnie. Dla środowiska DEV przekierowuję port 8081:
kubectl port-forward -n dev service/demo-api 8081:8081
Ta komenda oznacza:
-n dev– namespace, w którym znajduje się Service,service/demo-api– nazwa zasobu Service,8081:8081– mapowanie portu lokalnego na port serwisu w klastrze.
Po uruchomieniu port-forward możemy sprawdzić działanie aplikacji:
curl localhost:8081 | jq
{
"hostname": "demo-api-5cd7597b5c-mdfmt",
"message": "Hello from DEV",
"namespace": "dev",
"node_name": "kind-control-plane",
"pod_ip": "10.244.0.22",
"status": "ok"
}
Analogicznie działa środowisko produkcyjne, uruchomione w innym namespace’ie i z inną konfiguracją:
curl localhost:8082 | jq
{
"hostname": "demo-api-5cd7597b5c-22l9m",
"message": "Hello from PROD",
"namespace": "prod",
"node_name": "kind-control-plane",
"pod_ip": "10.244.0.23",
"status": "ok"
}
Dzięki takiemu podejściu jedna definicja Helm chartu może obsługiwać wiele środowisk, różniących się wyłącznie konfiguracją, a nie kodem czy manifestami Kubernetes.
5. Podsumowanie
W tym wpisie pokazano podstawy pracy z Helmem: od stworzenia minimalnego chartu, przez parametryzację manifestów za pomocą values.yaml, aż po wdrażanie aplikacji w wielu środowiskach.
Helm nie zastępuje Kubernetes, ale upraszcza zarządzanie manifestami, pozwala uniknąć duplikacji i ułatwia aktualizacje aplikacji. Dzięki temu ta sama definicja chartu może być używana wielokrotnie, zmieniając jedynie konfigurację.