

Założenia i funkcjonalności projektu healthlog
- przedstawienie różnic pomiędzy standardową metodą instalacji i konfiguracji projektu OpenShift a instalacją projektu i konfiguracją aplikacji z wykorzystaniem Helm Chart oraz OpenShift Kustomize
- maksymalnie prosta aplikacja gui w jednym pliku korzystająca z wielu zmiennych środowiskowych
- obsługa Liveness i Readiness Probe
- wykorzystanie zewnętrznej bazy danych PostgreSQL
Praktyczne wykorzystanie Helm Chart oraz Kustomize overlay
- Helm Chart wygeneruje Deployment/Service/Route/ConfigMap/Secret/probes (to co wcześniej robiłeś komendami oc). Helm = “szablony + wartości (values) + release’y” (parametryzacja, wersjonowanie)
- Kustomize overlay będzie trzymał różnice środowisk (np. namespace, host route, wartości env, liczba replik, resources, itd.) i będzie uruchamiał “helm template” jako generator manifestów. Kustomize = “nakładki/overlaye” (np. prod/dev) bez dotykania bazowych manifestów.
Poniższy opis tworzenia 3 projektów healthlog-prod oraaz healthlog-helm-prod i healthlog-helm-dev ma pokazać różnice pomiędzy standardowym tworzeniem projektów OpenShift a projektów z wykorzystaniem Helm Charts i Kustomize. W dużym skrócie:
- Helm “renderuje”
- Kustomize “nakłada”
Później dopiero można automatyzować render w pipeline.
Helm jest świetny do szablonów i values. Kustomize jest świetny do:
- środowisk (prod/dev/test)
- drobnych patchy (np. zmiana repliki, limity, host route)
- dokładania dodatkowych zasobów specyficznych dla klastra
W wersji “Helm + Kustomize” robimy typowy pattern:
- Helm generuje “bazę” manifestów (helm template)
- Kustomize nakłada overlay (namespace, patch, wartości)
W OKD 4.19 Kustomize jest dostępny jako oc apply -k … (jeśli masz włączone w kliencie — zwykle tak).
Różnice pomiędzy standardową ręczną metodą konfiguracji projektu OpenShift a projektami z użyciem Helm Charts i OpenShfit Kustomize
W projekcie healthlog-prod standardowo proces instalacji i konfiguracji wygląda tak:
- build obrazu przez oc new-build + oc start-build –from-dir=…
- deploy przez oc new-app
- config przez oc apply CM/Secret
- env przez oc set env … –from=…
- networking przez oc expose + oc create route edge
- probes przez oc set probe
W projektach healthlog-helm-prod i healthlog-helm-dev będzie wyglądać to tak:
- build obrazu nadal możesz robić tak samo ale deployment nie będzie “klikany”/“set env”-owany ręcznie
zamiast tego:
- instalujesz/reinstalujesz release Helma (albo generujesz manifesty z Helma i aplikujesz)
- config/probes/route są w YAML jako kod
Znika:
- oc apply -f cm… (bo CM jest w Helm)
- oc apply -f secret… (bo Secret jest w Helm)
- oc set env … (bo envFrom jest w Deployment template)
- oc expose … (bo Service jest w Helm)
- oc create route … (bo Route jest w Helm)
- oc set probe … (bo probes są w Helm)
Zostaje:
- oc new-project …
- oc new-build …
- oc start-build …
(i zamiast reszty) 1 komenda render/deploy manifestów
Porównanie sposobu poprawiania konfiguracji projektu i aplikacji standardowego projektu OpenShift z projektami z użyciem Helm Charts i OpenShfit Kustomize
W healthlog-prod standardowo
oc project healthlog-prod oc apply -f /repo/pakiety/healthlog/1.0.0/healthlog-gui/cm-healthlog-gui.yaml oc rollout restart deployment/healthlog-gui -n healthlog-prod # protip: sprawdzenie różnic w pliku i cm w projekcie bez zaczytywania zmian oc diff -f /repo/pakiety/healthlog/1.0.0/healthlog-gui/cm-healthlog-gui.yaml
W healthlog-helm-prod / healthlog-helm-dev (Helm Charts + Kustomize) będzie
#np. zmieniasz wartości baz danych w pliku /repo/pakiety/healthlog-helm/1.0.0/healthlog-gui/kustomize/overlays/prod/values-prod.yaml # robisz: helm template healthlog-gui ./helm -f kustomize/overlays/prod/values-prod.yaml | oc apply -n healthlog-helm-prod -f -
Porównanie struktury katalogów projektów: healthlog-prod oraz healthlog-helm-prod
cm-healthlog-gui.yaml Dockerfile index.php secret-healthlog-gui.yaml
Dockerfile
index.php
helm/
Chart.yaml
values.yaml
templates/
deployment.yaml
service.yaml
route.yaml
configmap.yaml
secret.yaml
kustomize/
base/
kustomization.yaml
overlays/
dev/
kustomization.yaml
values-dev.yaml
prod/
kustomization.yaml
values-prod.yamlDwa overlaye (dev/prod) + dwa projekty OpenShift (healthlog-helm-dev, healthlog-helm-prod) i możliwość wdrożenia z jednego katalogu. Cel: jedna baza Helma + dwa overlaye Kustomize + dwa namespace’y. To jest gitops-friendly, ale też proste do ręcznego odpalania.
Co robi Helm ? Helm bierze:
- helm/Chart.yaml (metadane)
- helm/values.yaml + nadpisania z -f values-*.yaml
- wszystkie pliki YAML w helm/templates/
i renderuje je do jednego strumienia YAML.
Czyli jeśli w helm/templates/ leży:
- deployment.yaml → powstaje Deployment
- service.yaml → powstaje Service
- route.yaml → powstaje Route
- kustomization.yaml → powstaje “manifest” kind: Kustomization
Co robi oc apply ?
oc apply próbuje zastosować każdy dokument YAML, jaki mu podasz.
Przygotowanie 1 bazy danych pod projekt healthlog-prod
Poniższy opis zakłada, że został zainstalowany i uruchomiony serwer bazy danych PostgreSQL na bazie wpisu na blogu https://itadmin.vblog.ovh/klaster-okd-4-19-openshift-na-proxmox-9-opis-postgresql-baza-danych-dla-aplikacji-openshift/
sudo -iu postgres psql -- podstawowe komendy do poruszania się po bazach PostgreSQL \l \c nazwa bazy \dt \d nazwa_tabeli \d+ nazwa_tabeli -- 1) Tworzymy rolę/użytkownika CREATE ROLE usr_healthlog WITH LOGIN PASSWORD 'Heal26thlog37!'; -- 2) Tworzymy bazę danych i ustawiamy właściciela CREATE DATABASE dba_healthlog OWNER usr_healthlog ENCODING 'UTF8' TEMPLATE template0; -- (opcjonalnie) Odbierz domyślne uprawnienia PUBLIC REVOKE ALL ON DATABASE dba_healthlog FROM PUBLIC; -- Pozwól userowi się łączyć GRANT CONNECT, TEMPORARY ON DATABASE dba_healthlog TO usr_healthlog; -- W psql przełącz bazę: \c dba_healthlog CREATE TABLE IF NOT EXISTS healthlogs ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, weight NUMERIC(6,2) NOT NULL, CONSTRAINT healthlogs_weight_check CHECK (weight > 0) ); -- Uprawnienia do sekwencji (identity) GRANT USAGE ON SCHEMA public TO usr_healthlog; GRANT SELECT, INSERT, UPDATE, DELETE ON healthlogs TO usr_healthlog; -- kluczowe dla IDENTITY: GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO usr_healthlog;
Przygotowanie 2 baz danych pod projekty healthlog-helm-prod oraz healthlog-helm-dev
Poniższy opis zakłada, że został zainstalowany i uruchomiony serwer bazy danych PostgreSQL na bazie wpisu na blogu https://itadmin.vblog.ovh/klaster-okd-4-19-openshift-na-proxmox-9-opis-postgresql-baza-danych-dla-aplikacji-openshift/
sudo -iu postgres psql -- podstawowe komendy do poruszania się po bazach PostgreSQL \l \c nazwa bazy \dt \d nazwa_tabeli \d+ nazwa_tabeli -- 1) Tworzymy rolę/użytkownika CREATE ROLE usr_healthlog_prod WITH LOGIN PASSWORD 'Heal26thlog37!PROD'; CREATE ROLE usr_healthlog_dev WITH LOGIN PASSWORD 'Heal26thlog37!DEV'; -- w razie reset hasła ALTER ROLE usr_healthlog_dev WITH PASSWORD 'NOWE_HASLO'; -- 2) Tworzymy bazę danych i ustawiamy właściciela CREATE DATABASE dba_healthlog_prod OWNER usr_healthlog_prod ENCODING 'UTF8' TEMPLATE template0; CREATE DATABASE dba_healthlog_dev OWNER usr_healthlog_dev ENCODING 'UTF8' TEMPLATE template0; -- (opcjonalnie) Odbierz domyślne uprawnienia PUBLIC REVOKE ALL ON DATABASE dba_healthlog_prod FROM PUBLIC; REVOKE ALL ON DATABASE dba_healthlog_dev FROM PUBLIC; -- Pozwól userowi się łączyć GRANT CONNECT, TEMPORARY ON DATABASE dba_healthlog_prod TO usr_healthlog_prod; GRANT CONNECT, TEMPORARY ON DATABASE dba_healthlog_dev TO usr_healthlog_dev; -- W psql przełącz bazę: \c dba_healthlog_prod CREATE TABLE IF NOT EXISTS healthlogs ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, weight NUMERIC(6,2) NOT NULL, CONSTRAINT healthlogs_weight_check CHECK (weight > 0) ); -- Uprawnienia do sekwencji (identity) GRANT USAGE ON SCHEMA public TO usr_healthlog_prod; GRANT SELECT, INSERT, UPDATE, DELETE ON healthlogs TO usr_healthlog_prod; -- kluczowe dla IDENTITY: GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO usr_healthlog_prod; \c dba_healthlog_dev CREATE TABLE IF NOT EXISTS healthlogs ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, weight NUMERIC(6,2) NOT NULL, CONSTRAINT healthlogs_weight_check CHECK (weight > 0) ); -- Uprawnienia do sekwencji (identity) GRANT USAGE ON SCHEMA public TO usr_healthlog_dev; GRANT SELECT, INSERT, UPDATE, DELETE ON healthlogs TO usr_healthlog_dev; -- kluczowe dla IDENTITY: GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO usr_healthlog_dev;
Ustawienie dostępu tylko z sieci 192.168.40.0/24 dla konkretnej bazy / użytkownika
host dba_healthlog usr_healthlog 192.168.40.0/24 scram-sha-256
Lub całkowicie dla całej sieci 192.168.40.0/24
# TYPE DATABASE USER ADDRESS METHOD # "local" is for Unix domain socket connections only local all all peer # IPv4 local connections: host all all 127.0.0.1/32 ident # IPv6 local connections: host all all ::1/128 ident # Allow replication connections from localhost, by a user with the # replication privilege. local replication all peer host replication all 127.0.0.1/32 ident host replication all ::1/128 ident # usr_editor może łączyć się do dba_users z dowolnego IPv4 #host dba_users usr_editor 192.168.40.0/24 scram-sha-256 # LAN klastra host all all 192.168.40.0/24 scram-sha-256 host all all 192.168.8.0/24 scram-sha-256 host all all 192.168.10.0/24 scram-sha-256
# Restart PostgreSQL po zmianach sudo systemctl restart postgresql sudo systemctl status postgresql --no-page
Weryfikacja z innego hosta możliwości połączenia się z bazą dba_healthlog przez użytkownika usr_healthlog.
psql -h 192.168.40.25 -U usr_healthlog -d dba_healthlog
dba_healthlog=> \dt
List of tables
Schema | Name | Type | Owner
--------+------------+-------+----------
public | healthlogs | table | postgres
(1 row)Przygotowanie wspólnego Dockerfile pod projekty 3 healthlog-prod oraz healthlog-helm-prod/dev
FROM registry.access.redhat.com/ubi9/php-83:latest
USER 0
# Zainstaluj sterowniki PostgreSQL do PHP (pdo_pgsql jest w php-pgsql)
RUN set -eux; \
if command -v microdnf >/dev/null 2>&1; then \
microdnf -y install php-pgsql && microdnf clean all; \
elif command -v dnf >/dev/null 2>&1; then \
dnf -y install php-pgsql && dnf clean all; \
elif command -v yum >/dev/null 2>&1; then \
yum -y install php-pgsql && yum clean all; \
else \
echo "ERROR: No package manager found (microdnf/dnf/yum)"; exit 1; \
fi
WORKDIR /var/www/html
COPY index.php /var/www/html/index.php
RUN chgrp -R 0 /var/www/html && chmod -R g+rwX /var/www/html
EXPOSE 8080
USER 1001
CMD ["php", "-S", "0.0.0.0:8080", "-t", "/var/www/html"]Uruchomienie projektu healthlog-prod
Poniżej opisany jest standardowy, ręczny sposób instalacji i konfiguracji projektu OpenShift oraz aplikacji wewnątrz tego projektu.
apiVersion: v1 kind: ConfigMap metadata: name: cm-healthlog-gui namespace: healthlog-prod data: HL_APP_VERSION: "1.0.0" HL_DBA_SERVER: "192.168.40.25" HL_DBA_PORT: "5432" HL_DBA_USER: "usr_healthlog" HL_DBA_NAME: "dba_healthlog" HL_GUI_CONFIG_SHOW_VERSION: "1" HL_GUI_SHOW_ENVIRONMENT_VARIABLES: "1"
apiVersion: v1 kind: Secret metadata: name: secret-healthlog-gui namespace: healthlog-prod type: Opaque stringData: HL_DBA_PASSWORD: "Heal26thlog37!"
# Utworzenie projektu oc new-project healthlog-prod # BuildConfig (binary build z Dockerfile) oc new-build --name=healthlog-gui --binary --strategy=docker -n healthlog-prod # Start builda z katalogu (Dockerfile + index.php) oc start-build healthlog-gui --from-dir=. -n healthlog-prod --follow # Utworzenie aplikacji z ImageStream po buildzie oc new-app -n healthlog-prod --image-stream=healthlog-gui --name=healthlog-gui # Dodanie ConfigMap i Secret oc apply -n healthlog-prod -f cm-healthlog-gui.yaml oc apply -n healthlog-prod -f secret-healthlog-gui.yaml # Wstrzyknięcie zmiennych środowiskowych do Deploymentu oc set env -n healthlog-prod deployment/healthlog-gui --from=configmap/cm-healthlog-gui oc set env -n healthlog-prod deployment/healthlog-gui --from=secret/secret-healthlog-gui # To tworzy Service (zwykle svc/healthlog-gui), który: # - wybiera Pody po selektorach (labelach) z Deploymentu # - wystawia stabilny adres w klastrze (ClusterIP) # - mapuje porty: port: 8080 (na Service) targetPort: 8080 (na kontener w Podzie) # - Bez Service Route nie ma do czego wskazywać. oc expose -n healthlog-prod deployment/healthlog-gui --port=8080 --target-port=8080 # To tworzy Route (HTTP) bez TLS edge (zwykły http) wskazuje na svc/healthlog-gui na porcie 8080 to jest szybki skrót „zrób mi route do tego service” # ten krok można pominąć w tym przykładzie oc expose -n healthlog-prod service/healthlog-gui --name=healthlog-gui --port=8080 # To tworzy route edge TLS # - router robi terminację TLS (HTTPS do routera) # - potem router leci do service zwykle HTTP (w środku klastra) oc create route edge healthlog-gui --service=healthlog-gui --port=8080 -n healthlog-prod # Dodanie Readiness probe oc set probe deployment/healthlog-gui -n healthlog-prod \ --readiness \ --get-url=http://:8080/health/ready \ --initial-delay-seconds=10 \ --timeout-seconds=3 \ --period-seconds=10 \ --failure-threshold=3 # Dodanie Liveness probe oc set probe deployment/healthlog-gui -n healthlog-prod \ --liveness \ --get-url=http://:8080/health/live \ --initial-delay-seconds=20 \ --timeout-seconds=3 \ --period-seconds=20 \ --failure-threshold=3 # weryfikacja Readiness / Liveness probe oc describe deployment healthlog-gui -n healthlog-prod | egrep -n "Liveness|Readiness|http-get" # po zmianach w cm oc rollout restart deployment/healthlog-gui # Sprawdzenie: oc get all -n healthlog-prod oc get route -n healthlog-prod
Testowanie Liveness probe
sdasdsa
Testowanie Readiness probe
asdsad
Instalacja helm 3 bez roota (do $HOME/bin) – polecane na bastionach
mkdir -p "$HOME/bin" export HELM_INSTALL_DIR="$HOME/bin" export USE_SUDO=false export DESIRED_VERSION="v3.20.0" curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash # upewnij się, że PATH zawiera ~/bin (na stałe) grep -q 'export PATH=$HOME/bin:$PATH' ~/.bashrc || echo 'export PATH=$HOME/bin:$PATH' >> ~/.bashrc source ~/.bashrc hash -r helm version --short v3.20.0+gb2e4314
Uruchomienie projektów healthlog-helm-prod / healthlog-helm-dev
Poniżej opisany jest nowoczesny sposób instalacji i konfiguracji projektu OpenShift oraz aplikacji wewnątrz tego projektu oparty na Helm Chart oraz OpenShift Kustomize.
apiVersion: v2 name: healthlog-gui description: Simple healthlog GUI (PHP + Bootstrap) for OKD/OpenShift type: application version: 0.1.0 appVersion: "1.0.0"
- version = wersja chartu (infra)
- appVersion = wersja aplikacji (to, co pokazywałeś w GUI)
Poniżej znajduje się bazowy zestaw wartości domyślnych dla chartu. A values-dev.yaml i values-prod.yaml to nadpisania (override) dla konkretnych środowisk.
nameOverride: ""
fullnameOverride: ""
image:
# w OpenShift typowo wskazujesz na ImageStreamTag albo registry wewnętrzny
# na start: zakładamy imagestream "healthlog-gui:latest" w namespace
repository: healthlog-gui
tag: latest
pullPolicy: IfNotPresent
service:
port: 8080
targetPort: 8080
route:
enabled: true
name: healthlog-gui
tlsTermination: edge
# host opcjonalny (jak nie ustawisz, OpenShift wygeneruje)
host: ""
config:
HL_APP_VERSION: "1.0.0"
HL_DBA_SERVER: "192.168.40.25"
HL_DBA_PORT: "5432"
HL_DBA_USER: "usr_healthlog"
HL_DBA_NAME: "dba_healthlog"
HL_GUI_CONFIG_SHOW_VERSION: "1"
HL_GUI_SHOW_ENVIRONMENT_VARIABLES: "1"
secret:
HL_DBA_PASSWORD: "Heal26thlog37!"
probes:
readiness:
path: /health/ready
initialDelaySeconds: 10
timeoutSeconds: 3
periodSeconds: 10
failureThreshold: 3
liveness:
path: /health/live
initialDelaySeconds: 20
timeoutSeconds: 3
periodSeconds: 20
failureThreshold: 3
resources: {}
replicaCount: 1Helm templates: to zastępuje ręczne oc apply, oc set env, oc set probe, oc expose, oc create route
ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-healthlog-gui
data:
{{- range $k, $v := .Values.config }}
{{ $k }}: {{ $v | quote }}
{{- end }}Secret
apiVersion: v1
kind: Secret
metadata:
name: secret-healthlog-gui
type: Opaque
stringData:
{{- range $k, $v := .Values.secret }}
{{ $k }}: {{ $v | quote }}
{{- end }}
Deployment
To jest najważniejsze — tu wchodzi:
- obraz
- envFrom (ConfigMap + Secret)
- readiness + liveness (to co wcześniej
oc set probe) - port 8080
To zastępuje:
- oc new-app … (deployment)
- oc set env … –from=configmap/secret
- oc set probe …
Automatyczny rollout po zmianie CM/Secret
Teraz, gdy zmienisz ConfigMap/Secret, Deployment może nie zrestartować Podów automatycznie (env zaciąga się tylko przy starcie kontenera).
Najprościej: dodanie checksum anotacje do helm/templates/deployment.yaml.
- checksum/config: {{ include (print $.Template.BasePath „/configmap.yaml”) . | sha256sum }}
- checksum/secret: {{ include (print $.Template.BasePath „/secret.yaml”) . | sha256sum }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: healthlog-gui
labels:
app: healthlog-gui
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: healthlog-gui
template:
metadata:
labels:
app: healthlog-gui
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
spec:
containers:
- name: healthlog-gui
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.service.targetPort }}
name: web
envFrom:
- configMapRef:
name: cm-healthlog-gui
- secretRef:
name: secret-healthlog-gui
readinessProbe:
httpGet:
path: {{ .Values.probes.readiness.path }}
port: {{ .Values.service.targetPort }}
initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }}
timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }}
periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
failureThreshold: {{ .Values.probes.readiness.failureThreshold }}
livenessProbe:
httpGet:
path: {{ .Values.probes.liveness.path }}
port: {{ .Values.service.targetPort }}
initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }}
timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }}
periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
failureThreshold: {{ .Values.probes.liveness.failureThreshold }}
resources:
{{ toYaml .Values.resources | indent 12 }}Service
To zastępuje:
- oc expose deployment/… (tworzenie Service)
apiVersion: v1
kind: Service
metadata:
name: healthlog-gui
labels:
app: healthlog-gui
spec:
selector:
app: healthlog-gui
ports:
- name: web
port: {{ .Values.service.port }}
targetPort: {{ .Values.service.targetPort }}Route
To zastępuje:
- oc create route edge …
{{- if .Values.route.enabled }}
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: {{ .Values.route.name }}
spec:
{{- if .Values.route.host }}
host: {{ .Values.route.host | quote }}
{{- end }}
to:
kind: Service
name: healthlog-gui
port:
targetPort: web
tls:
termination: {{ .Values.route.tlsTermination }}
{{- end }}Kustomize base: generator z Helma
Funkcja: miejsce na wspólne elementy. Na przyszłość (wspólne patche, labelki itd.). Na start nie musi robić nic.
apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: []
Kustomize overlay prod: healthlog-helm-prod
- Ustawia namespace healthlog-helm-prod
- Renderuje chart Helma z values values-prod.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: healthlog-helm-prod
resources:
- ../../base
helmGlobals:
chartHome: ../../../
helmCharts:
- name: helm
releaseName: healthlog-gui
valuesFile: values-prod.yamlKustomize overlay dev: healthlog-helm-dev
- Ustawia namespace healthlog-helm-dev
- Renderuje chart Helma z values values-dev.yaml
Wynik to komplet zasobów gotowych do oc apply
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: healthlog-helm-dev
resources:
- ../../base
helmGlobals:
chartHome: ../../../
helmCharts:
- name: helm
releaseName: healthlog-gui
valuesFile: values-dev.yamlDwa różne values: dev i prod. Tu realizujesz różnice:
- inne DB name, user, password
- inne flagi GUI
- opcjonalnie inny host route
- opcjonalnie inny tag obrazu/pullPolicy
image: repository: image-registry.openshift-image-registry.svc:5000/healthlog-helm-prod/healthlog-gui tag: latest pullPolicy: Always route: enabled: true name: healthlog-gui tlsTermination: edge host: "healthlog-gui-healthlog-helm-prod.apps.testcluster.okdlab.local" config: HL_APP_VERSION: "1.0.0" HL_DBA_SERVER: "192.168.40.25" HL_DBA_PORT: "5432" HL_DBA_USER: "usr_healthlog_prod" HL_DBA_NAME: "dba_healthlog_prod" HL_GUI_CONFIG_SHOW_VERSION: "1" HL_GUI_SHOW_ENVIRONMENT_VARIABLES: "0" # prod zwykle nie pokazuje envów secret: HL_DBA_PASSWORD: "Heal26thlog37!PROD"
image: # jeśli budujesz obraz w tym samym projekcie, najlepiej używać wewnętrznego registry repository: image-registry.openshift-image-registry.svc:5000/healthlog-helm-dev/healthlog-gui tag: latest pullPolicy: Always route: enabled: true name: healthlog-gui tlsTermination: edge host: "healthlog-gui-healthlog-helm-dev.apps.testcluster.okdlab.local" config: HL_APP_VERSION: "1.0.0-dev" HL_DBA_SERVER: "192.168.40.25" HL_DBA_PORT: "5432" HL_DBA_USER: "usr_healthlog_dev" HL_DBA_NAME: "dba_healthlog_dev" HL_GUI_CONFIG_SHOW_VERSION: "1" HL_GUI_SHOW_ENVIRONMENT_VARIABLES: "1" secret: HL_DBA_PASSWORD: "DevHeal26thlog37!DEV"
Wdrożenie i aktualizacja projektów healthlog-helm-prod lub healthlog-helm-dev
cd /repo/pakiety/healthlog-helm/1.0.0/healthlog-gui # Jednorazowo: utworzenie projektów oc new-project healthlog-helm-dev oc new-project healthlog-helm-prod # Jednorazowo: BuildConfig w obu projektachg oc new-build --name=healthlog-gui --binary --strategy=docker -n healthlog-helm-dev oc new-build --name=healthlog-gui --binary --strategy=docker -n healthlog-helm-prod # Start builda oc start-build healthlog-gui --from-dir=. -n healthlog-helm-dev --follow oc start-build healthlog-gui --from-dir=. -n healthlog-helm-prod --follow # Helm render → oc apply – wariant “zawsze działa” helm template healthlog-gui ./helm -f kustomize/overlays/dev/values-dev.yaml | oc apply -n healthlog-helm-dev -f - helm template healthlog-gui ./helm -f kustomize/overlays/prod/values-prod.yaml | oc apply -n healthlog-helm-prod -f -
Zmiana konfiguracji aplikacji w projekcie Helm
# Aktualizacja po zmianie values (env / config).Zmieniłeś tylko values-dev.yaml albo values-prod.yaml # np. HL_GUI_SHOW_ENVIRONMENT_VARIABLES: "1" w /repo/pakiety/healthlog-helm/1.0.0/healthlog-gui/kustomize/overlays/prod/values-prod.yaml helm template healthlog-gui ./helm -f kustomize/overlays/dev/values-dev.yaml | oc apply -n healthlog-helm-dev -f - helm template healthlog-gui ./helm -f kustomize/overlays/prod/values-prod.yaml | oc apply -n healthlog-helm-prod -f - # pokaże się komunikat i aplikacja ma zmieniona konfiguracje secret/secret-healthlog-gui configured configmap/cm-healthlog-gui configured service/healthlog-gui unchanged deployment.apps/healthlog-gui configured route.route.openshift.io/healthlog-gui unchanged # jeśli NIE masz checksum anotacji w Deployment: oc rollout restart deployment/healthlog-gui -n healthlog-helm-dev oc rollout restart deployment/healthlog-gui -n healthlog-helm-prod # Zmieniłeś kod (index.php) lub Dockerfile Lepszy scenariusz (polecany pod CI/CD): unikalne tagi obrazu oc start-build healthlog-gui --from-dir=. -n healthlog-helm-dev --follow oc start-build healthlog-gui --from-dir=. -n healthlog-helm-prod --follow # jeśli tag jest "latest" (najczęściej potrzebne): oc rollout restart deployment/healthlog-gui -n healthlog-helm-dev oc rollout restart deployment/healthlog-gui -n healthlog-helm-prod
Zmiana Dockerfile lub kodu aplikacji w projekcie Helm
Jeśli używany jest tag latest czyli w values-prod.yaml jest coś takiego
image: repository: image-registry.openshift-image-registry.svc:5000/healthlog-helm-prod/healthlog-gui tag: latest pullPolicy: Always
cd /repo/pakiety/healthlog-helm/1.0.0/healthlog-gui oc start-build healthlog-gui --from-dir=. -n healthlog-helm-prod --follow # Wymuś przeładowanie Podów (bo tag się nie zmienił) oc rollout restart deployment/healthlog-gui -n healthlog-helm-prod oc rollout status deployment/healthlog-gui -n healthlog-helm-prod
W lepszym scenariuszu pod CI/CD są unikalne tagi obrazu. Można tak jak poniżej po zbudowaniu obrazu otagować wynikowy ImageStreamTag na np.1.0.1
oc tag -n healthlog-helm-prod healthlog-gui:latest healthlog-gui:1.0.1
# potem w pliku /repo/pakiety/healthlog-helm/1.0.0/healthlog-gui/kustomize/overlays/prod/values-prod.yaml zmieniamy tag: latest na tag: 1.0.1
image:
repository: image-registry.openshift-image-registry.svc:5000/healthlog-helm-prod/healthlog-gui
tag: 1.0.1
pullPolicy: Always
# Deployment widzi nowy tag → rollout idzie automatycznie
helm template healthlog-gui ./helm -f kustomize/overlays/prod/values-prod.yaml | oc apply -n healthlog-helm-prod -f -
# Jak sprawdzić, czy pod naprawdę ma nową wersję?
POD=$(oc get pod -n healthlog-helm-prod -l app=healthlog-gui -o name | head -n1)
oc get -n healthlog-helm-prod $POD -o jsonpath='{.spec.containers[0].image}{"\n"}'
image-registry.openshift-image-registry.svc:5000/healthlog-helm-prod/healthlog-gui:1.0.1Rozwiązywanie problemów
# w razie błedu jak ponizej nalezy się upewnić że user usr_healthlog_dev ma poprawne hasło takie jak w secret ma być w PSSQL
"error":"SQLSTATE[08006] [7] FATAL: password authentication failed for user \"usr_healthlog_dev\""
# wtedy na maszynie database-1
sudo -iu postgres psql
ALTER ROLE usr_healthlog_dev WITH PASSWORD 'NOWE_HASLO';
# weryfikacja aktualnego secret
oc get secret -n healthlog-helm-dev secret-healthlog-gui -o jsonpath='{.data.HL_DBA_PASSWORD}' | base64 -d; echo
DevHeal26thlog37!DEV
# jeśli / zwraca 200, ale /health/ready 503 → problem to logika ready
POD=$(oc get pod -n healthlog-helm-dev -l app=healthlog-gui -o name | head -n1)
oc rsh -n healthlog-helm-dev $POD sh -lc 'curl -sS -i http://127.0.0.1:8080/health/ready || true'
oc rsh -n healthlog-helm-dev $POD sh -lc 'curl -sS -i http://127.0.0.1:8080/health/live || true'
oc rsh -n healthlog-helm-dev $POD sh -lc 'curl -sS -i http://127.0.0.1:8080/ || true'
# sprawdź dostęp sieciowy do PostgreSQL z DEV poda
oc rsh -n healthlog-helm-dev $POD sh -lc 'timeout 3 bash -lc "</dev/tcp/192.168.40.25/5432" && echo OK || echo FAIL'
# sprawdź jakie env-y faktycznie siedzą w Podzie (DEV)
oc set env -n healthlog-helm-dev deployment/healthlog-gui --list | egrep 'HL_|POSTGRES|DBA' || true
# weryfikacja rediness probe z poda
oc rsh -n healthlog-helm-dev $POD sh -lc 'curl -sS -i http://127.0.0.1:8080/health/ready || true'
HTTP/1.1 200 OK
Host: 127.0.0.1:8080
Date: Tue, 17 Feb 2026 15:01:35 GMT
Connection: close
X-Powered-By: PHP/8.3.29
Content-Type: application/json; charset=utf-8Wspólny kod aplikacji index.php pod projekty healthlog-prod oraz healthlog-helm-prod
<?php
declare(strict_types=1);
// --- health endpoints (must be BEFORE any HTML output) ---
$uriPath = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
if ($uriPath === '/health/live' || $uriPath === '/healthz') {
// Liveness: odpowiadaj 200 jeśli PHP działa
header('Content-Type: application/json; charset=utf-8');
http_response_code(200);
echo json_encode([
"status" => "ok",
"check" => "live",
"ts" => date('c'),
], JSON_UNESCAPED_UNICODE);
exit;
}
if ($uriPath === '/health/ready') {
// Readiness: 200 tylko jeśli DB działa (i driver jest)
header('Content-Type: application/json; charset=utf-8');
$driverOk = in_array('pgsql', PDO::getAvailableDrivers(), true);
if (!$driverOk) {
http_response_code(503);
echo json_encode([
"status" => "fail",
"check" => "ready",
"reason" => "pdo_pgsql driver missing",
"drivers" => PDO::getAvailableDrivers(),
"ts" => date('c'),
], JSON_UNESCAPED_UNICODE);
exit;
}
try {
// Tu korzystamy z tych samych env co reszta aplikacji
$HL_DBA_SERVER = getenv("HL_DBA_SERVER") ?: "127.0.0.1";
$HL_DBA_PORT = getenv("HL_DBA_PORT") ?: "5432";
$HL_DBA_USER = getenv("HL_DBA_USER") ?: "usr_healthlog";
$HL_DBA_NAME = getenv("HL_DBA_NAME") ?: "dba_healthlog";
$HL_DBA_PASSWORD = getenv("HL_DBA_PASSWORD") ?: "";
$dsn = sprintf("pgsql:host=%s;port=%s;dbname=%s;sslmode=prefer", $HL_DBA_SERVER, $HL_DBA_PORT, $HL_DBA_NAME);
$pdo = new PDO($dsn, $HL_DBA_USER, $HL_DBA_PASSWORD, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_TIMEOUT => 2, // krótki timeout do readiness
]);
// super-lekki test
$pdo->query("SELECT 1");
http_response_code(200);
echo json_encode([
"status" => "ok",
"check" => "ready",
"db" => "ok",
"ts" => date('c'),
], JSON_UNESCAPED_UNICODE);
exit;
} catch (Throwable $e) {
http_response_code(503);
echo json_encode([
"status" => "fail",
"check" => "ready",
"db" => "error",
"error" => $e->getMessage(),
"ts" => date('c'),
], JSON_UNESCAPED_UNICODE);
exit;
}
}
function envv(string $key, ?string $default = null): string {
$v = getenv($key);
if ($v === false || $v === null || $v === '') return (string)$default;
return $v;
}
function bool_env(string $key, string $default = "0"): bool {
$v = trim(envv($key, $default));
return ($v === "1" || strcasecmp($v, "true") === 0 || strcasecmp($v, "yes") === 0 || strcasecmp($v, "on") === 0);
}
function h(string $s): string { return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
$HL_APP_VERSION = envv("HL_APP_VERSION", "1.0.0");
$HL_DBA_SERVER = envv("HL_DBA_SERVER", "127.0.0.1");
$HL_DBA_PORT = envv("HL_DBA_PORT", "5432");
$HL_DBA_USER = envv("HL_DBA_USER", "usr_healthlog");
$HL_DBA_NAME = envv("HL_DBA_NAME", "dba_healthlog");
$HL_DBA_PASSWORD= envv("HL_DBA_PASSWORD", ""); // z Secret
$SHOW_VERSION = bool_env("HL_GUI_CONFIG_SHOW_VERSION", "1");
$SHOW_ENVVARS = bool_env("HL_GUI_SHOW_ENVIRONMENT_VARIABLES", "1");
$envList = [
"HL_APP_VERSION" => $HL_APP_VERSION,
"HL_DBA_SERVER" => $HL_DBA_SERVER,
"HL_DBA_PORT" => $HL_DBA_PORT,
"HL_DBA_USER" => $HL_DBA_USER,
"HL_DBA_NAME" => $HL_DBA_NAME,
"HL_GUI_CONFIG_SHOW_VERSION" => envv("HL_GUI_CONFIG_SHOW_VERSION", "1"),
"HL_GUI_SHOW_ENVIRONMENT_VARIABLES" => envv("HL_GUI_SHOW_ENVIRONMENT_VARIABLES", "1"),
];
// --- DB connect ---
$pdo = null;
$dbError = null;
try {
$dsn = sprintf("pgsql:host=%s;port=%s;dbname=%s;sslmode=prefer", $HL_DBA_SERVER, $HL_DBA_PORT, $HL_DBA_NAME);
$pdo = new PDO($dsn, $HL_DBA_USER, $HL_DBA_PASSWORD, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
} catch (Throwable $e) {
$dbError = $e->getMessage();
}
// --- handle POST (save weight) ---
$flashOk = null;
$flashErr = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$raw = trim((string)($_POST['weight'] ?? ''));
// Akceptuj "98,2" i "98.2"
$normalized = str_replace(' ', '', $raw);
$normalized = str_replace(',', '.', $normalized);
if ($normalized === '') {
$flashErr = "Podaj wagę.";
} elseif (!preg_match('/^\d{1,3}(\.\d{1,2})?$/', $normalized)) {
$flashErr = "Nieprawidłowy format wagi. Przykład: 98,2 albo 98.20";
} else {
$weight = (float)$normalized;
if ($weight <= 0) {
$flashErr = "Waga musi być > 0.";
} elseif ($pdo === null) {
$flashErr = "Brak połączenia z bazą – nie zapisano.";
} else {
try {
$stmt = $pdo->prepare("INSERT INTO healthlogs (weight) VALUES (:w)");
// NUMERIC(6,2) -> zapisuj z 2 miejscami
$stmt->execute([':w' => number_format($weight, 2, '.', '')]);
$flashOk = "Zapisano wagę: " . number_format($weight, 2, ',', '') . " kg";
// PRG: przekierowanie żeby nie dublować wpisu po odświeżeniu
header("Location: " . strtok($_SERVER["REQUEST_URI"], '?') . "?saved=1");
exit;
} catch (Throwable $e) {
$flashErr = "Błąd zapisu do bazy: " . $e->getMessage();
}
}
}
}
// --- Read last 14 for table (newest first) + chart (oldest first) ---
$rowsNewest = [];
$chartLabels = [];
$chartData = [];
if ($pdo !== null) {
try {
$stmt = $pdo->query("
SELECT id, created_at, weight
FROM healthlogs
ORDER BY id DESC
LIMIT 14
");
$rowsNewest = $stmt->fetchAll();
$rowsOldest = array_reverse($rowsNewest);
foreach ($rowsOldest as $r) {
// Format label: YYYY-MM-DD HH:MM
$dt = new DateTime((string)$r['created_at']);
$chartLabels[] = $dt->format('Y-m-d H:i');
$chartData[] = (float)$r['weight'];
}
} catch (Throwable $e) {
$dbError = $dbError ?: $e->getMessage();
}
}
// Helper for nice dt in table
function fmt_dt(string $ts): string {
try {
$dt = new DateTime($ts);
return $dt->format('Y-m-d H:i:s');
} catch (Throwable $e) {
return $ts;
}
}
?>
<!doctype html>
<html lang="pl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>healthlog</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
</head>
<body class="bg-light">
<nav class="navbar navbar-expand-lg bg-white border-bottom">
<div class="container">
<span class="navbar-brand fw-semibold">healthlog</span>
<div class="ms-auto small text-muted">
<?php if ($pdo !== null && $dbError === null): ?>
<span class="badge text-bg-success">DB: OK</span>
<?php else: ?>
<span class="badge text-bg-danger">DB: ERROR</span>
<?php endif; ?>
</div>
</div>
</nav>
<main class="container py-4">
<div class="row g-4">
<div class="col-12 col-lg-5">
<div class="card shadow-sm border-0">
<div class="card-body">
<h5 class="card-title mb-3">Dodaj wpis</h5>
<?php if ($flashErr): ?>
<div class="alert alert-danger mb-3"><?= h($flashErr) ?></div>
<?php elseif (isset($_GET['saved'])): ?>
<div class="alert alert-success mb-3">Zapisano.</div>
<?php endif; ?>
<?php if ($dbError): ?>
<div class="alert alert-warning">
<div class="fw-semibold mb-1">Uwaga: problem z bazą danych</div>
<div class="small text-muted" style="word-break: break-word;">
<?= h($dbError) ?>
</div>
</div>
<?php endif; ?>
<form method="post" class="row g-2 align-items-end">
<div class="col-8">
<label for="weight" class="form-label">Waga (kg)</label>
<input
type="text"
inputmode="decimal"
class="form-control form-control-lg"
id="weight"
name="weight"
placeholder="np. 98,2"
autocomplete="off"
required
>
<div class="form-text">Akceptuje format: <code>xx,x</code> lub <code>xx.x</code> (do 2 miejsc po przecinku).</div>
</div>
<div class="col-4 d-grid">
<button type="submit" class="btn btn-primary btn-lg">Zapisz</button>
</div>
</form>
<hr class="my-4">
<div class="d-flex justify-content-between align-items-center">
<div class="fw-semibold">Ostatnie 14 wpisów</div>
<div class="small text-muted"><?= count($rowsNewest) ?> rekordów</div>
</div>
<div class="mt-3">
<canvas id="wChart" height="140"></canvas>
</div>
<div class="small text-muted mt-2">
Wykres pokazuje <b>kolejność czasową</b> (od najstarszego do najnowszego w ramach 14 wpisów).
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-7">
<div class="card shadow-sm border-0">
<div class="card-body">
<h5 class="card-title mb-3">Historia</h5>
<div class="table-responsive">
<table class="table table-striped table-hover align-middle mb-0">
<thead class="table-dark">
<tr>
<th style="width: 90px;">id</th>
<th>data</th>
<th style="width: 140px;" class="text-end">waga (kg)</th>
</tr>
</thead>
<tbody>
<?php if (empty($rowsNewest)): ?>
<tr>
<td colspan="3" class="text-muted">Brak danych. Dodaj pierwszy wpis.</td>
</tr>
<?php else: ?>
<?php foreach ($rowsNewest as $r): ?>
<tr>
<td class="fw-semibold"><?= (int)$r['id'] ?></td>
<td><?= h(fmt_dt((string)$r['created_at'])) ?></td>
<td class="text-end"><?= h(number_format((float)$r['weight'], 2, ',', '')) ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<?php if ($SHOW_ENVVARS): ?>
<div class="alert mt-4" style="background:#fff3cd; color:#000; border-color:#ffeeba;">
<div class="fw-semibold mb-2">Zmienne środowiskowe (z ConfigMap/Secret)</div>
<ul class="mb-0">
<?php foreach ($envList as $k => $v): ?>
<li><code><?= h($k) ?></code> = "<span><?= h($v) ?></span>"</li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<?php if ($SHOW_VERSION): ?>
<div class="text-center text-muted small mt-3">
healthlog-gui wersja <?= h($HL_APP_VERSION) ?>
</div>
<?php endif; ?>
</main>
<script>
(() => {
const labels = <?= json_encode($chartLabels, JSON_UNESCAPED_UNICODE) ?>;
const data = <?= json_encode($chartData, JSON_UNESCAPED_UNICODE) ?>;
const ctx = document.getElementById('wChart');
if (!ctx) return;
new Chart(ctx, {
type: 'line',
data: {
labels,
datasets: [{
label: 'Waga (kg)',
data,
tension: 0.25,
pointRadius: 3,
pointHoverRadius: 5,
borderWidth: 2,
fill: false
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: true }
},
scales: {
x: {
ticks: { maxRotation: 60, minRotation: 0 }
},
y: {
beginAtZero: false
}
}
}
});
})();
</script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

