Klaster OKD (OpenShift) – Projekt OpenShift java-hello

Testowy klaster OKD (OpenShift)

Poniższy wpis bazuje na konfiguracji klastra przeprowadzonej wg. wpisu https://itadmin.vblog.ovh/klaster-okd-openshift-na-maszynach-wirtualnych-proxmox-opis-instalacji/ oraz konfiguracji serwera NFS opisanej na stronie https://itadmin.vblog.ovh/klaster-okd-openshift-na-maszynach-wirtualnych-proxmox-przygotowanie-maszyny-storage-nfs/

Opis projektu OpenShift : java-hello

Projekt ma na celu weryfikację możliwości budowania projektów na nowym klastrze OKD. Bazuje na obrazie Docker-a eclipse-temurin:17-jdk-alpine . Dodatkowo projekt wykorzystuje configmapę oraz pvc.

W OpenShift PersistentVolumeClaim (PVC) to żądanie pamięci masowej przez użytkownika lub aplikację. Umożliwia ono dostęp do PersistentVolumes (PV), które zapewniają zasoby pamięci masowej, takie jak NFS, Ceph, AWS EBS itp. PVC abstrakcyjnie zarządzają pamięcią masową, umożliwiając użytkownikom żądanie pamięci masowej bez martwienia się o jej podstawową implementację.
PersistentVolume (PV): PersistentVolume to część pamięci masowej udostępniana przez administratora lub dynamicznie tworzona na podstawie klasy pamięci masowej. Reprezentuje rzeczywisty zasób pamięci masowej (np. udział NFS, cel iSCSI lub dysk pamięci masowej w chmurze).

ConfigMap to obiekt w Kubernetes/OpenShift, który przechowuje dane konfiguracyjne w postaci par klucz–wartość.

  • Dane te mogą być następnie udostępniane do podów jako:
    • zmienne środowiskowe (env w kontenerze),
    • albo pliki w wolumenie (volumeMount).

Dzięki temu nie musisz wbudowywać konfiguracji w obraz Dockera — aplikacja może być konfigurowana dynamicznie przez administratorów DevOps bez rekompilacji.

Przygotowanie rejestru obrazów

Aby nie było problemów z budowaniem obrazów należy najpierw zweryfikować ustawienia OpenShift Image Registry.

oc get route -n openshift-image-registry
# jeśli nie ma to należy utworzyć dla OKD 3.X
oc create route default --service=image-registry -n openshift-image-registry

#w razie błędu error: unknown flag: --service dla OKD 4.X można utworzyć w ten sposób niestandardową rutę
oc create route reencrypt image-registry \
  --service=image-registry \
  --port=5000 \
  -n openshift-image-registry
route/image-registry created

# usunięcie niestandardowej ruty 
oc delete route image-registry -n openshift-image-registry

Red Hat zaleca aby domyślna ruta została utworzona automatycznie a nie ręcznie jak powyżej.

oc get route -n openshift-image-registry
# jeśli nie ma ruty domyślnej 
oc get route openshift-image-registry
Error from server (NotFound): routes.route.openshift.io "openshift-image-registry" not found

#to należy utworzyć automatycznie
oc edit configs.imageregistry.operator.openshift.io/cluster

# dodać linię w spec: defaultRoute: true oraz poprawić managementState: Managed
apiVersion: imageregistry.operator.openshift.io/v1
kind: Config
metadata:
  creationTimestamp: "2025-10-01T14:32:39Z"
  finalizers:
  - imageregistry.operator.openshift.io/finalizer
  generation: 3
  name: cluster
  resourceVersion: "92189"
  uid: 8734dd7f-7fbb-466d-b2ff-5a8745394516
spec:
  defaultRoute: true
  logLevel: Normal
  managementState: Managed
  observedConfig: null
  operatorLogLevel: Normal
(...)

# po zapisaniu zmian i restarcie podów
oc get pods -n openshift-image-registry
NAME                                              READY   STATUS    RESTARTS   AGE
image-registry-56d5549677-s26xb                   1/1     Running   0          2m38s

# sprawdzamy czy domyślna ruta została utworzona
oc get route -n openshift-image-registry
NAME            HOST/PORT                                                              PATH   SERVICES         PORT    TERMINATION   WILDCARD
default-route   default-route-openshift-image-registry.apps.testcluster.okdlab.local          image-registry   <all>   reencrypt     None

Weryfikacja praw na udziale NFS

Zanim będzie możliwie pobieranie obrazów Dockera należy sprawdzić czy można utworzyć jakiś plik wewnątrz poda w projekcie openshift-image-registry.
Więcej informacji o samym konfigurowaniu rejestru obrazów dla klastra OKD https://itadmin.vblog.ovh/klaster-okd-openshift-na-maszynach-wirtualnych-proxmox-przygotowanie-maszyny-storage-nfs/

oc exec pod/image-registry-76945ffb74-645b7 -n openshift-image-registry -- touch /registry/test.txt
oc exec pod/image-registry-76945ffb74-645b7 -n openshift-image-registry -- ls -la /registry/docker/registry/v2/blobs/sha256/

Wyświetlenie zawartości katalogu na udziale NFS widzianym wewnątrz poda

Weryfikacja uprawnień OpenShift do budowania obrazów w danym projekcie

Jeśli projekt tworzy i buduje użytkownik z ograniczonymi uprawnienia to trzeba zweryfikować i ewentualnie dodać odpowiednie uprawnienia do pushowania obrazów w projekcie java-hello do internal OpenShift registry
Więcej informacji o uprawnieniach i kontach użytkowników znajdziesz tutaj https://itadmin.vblog.ovh/klaster-okd-openshift-zarzadzanie-uzytkownikami-uprawnieniami-metodami-autoryzacji/

 oc policy add-role-to-user registry-viewer <twoj_user>
 oc policy add-role-to-user registry-editor <twoj_user>
 
# Jeśli masz konto developerskie w OKD, musisz mu dać rolę do pushowania do danego projektu: 
 oc policy add-role-to-user system:image-pusher <twoj_user> -n java-hello
 
 # nadać uprawnienie we wszystkich istniejących projektach
 for ns in $(oc get projects -o name | cut -d/ -f2); do
  oc policy add-role-to-user system:image-pusher <twoj_user> -n $ns
  oc policy add-role-to-user system:image-puller <twoj_user> -n $ns
done

#Stwórz nową ClusterRoleBinding, która powiąże użytkownika z rolą clusterową mającą prawa do image streamów i registry
oc adm policy add-cluster-role-to-user system:registry <twoj_user>

#oc policy add-role-to-user → działa tylko w jednym namespace.
#oc adm policy add-cluster-role-to-user → działa globalnie we wszystkich namespace'ach (również tych, które powstaną później).

Utworzenie nowego projektu OpenShift o nazwie java-hello

Najpierw należy przygotować kod aplikacji Java np. takiej jak poniżej, która wyświetla w pętli nazwę podanego użytkownika oraz datę i czas co ustalony interwał na konsoli oraz zapisuje wynik do pliku w pvc podłączonym do pod-a.

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class JavaHello {
    public static void main(String[] args) {
        // Pobranie zmiennych środowiskowych
        String userName = System.getenv("USER_NAME");
        if (userName == null || userName.isBlank()) {
            userName = "Anonim"; // fallback
        }

        String loopTimeEnv = System.getenv("LOOP_TIME");
        int loopTime = 5; // default 5 sekund
        try {
            if (loopTimeEnv != null) {
                loopTime = Integer.parseInt(loopTimeEnv);
            }
        } catch (NumberFormatException e) {
            System.out.println("Niepoprawna wartość LOOP_TIME, używam domyślnej 5s");
        }

        String filePath = System.getenv("FILE_PATH");
        if (filePath == null || filePath.isBlank()) {
            filePath = "/downloads/status.txt"; // fallback
        }

        File file = new File(filePath);
        try {
            if (!file.exists()) {
                file.getParentFile().mkdirs();
                file.createNewFile();
                System.out.println("Plik został utworzony: " + file.getAbsolutePath());
            }
        } catch (IOException e) {
            System.err.println("Błąd podczas tworzenia pliku: " + e.getMessage());
            return;
        }

        // Formattery do daty/czasu
        DateTimeFormatter consoleFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        DateTimeFormatter fileFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");

        // Nieskończona pętla
        while (true) {
            LocalDateTime now = LocalDateTime.now();

            // Log na konsolę
            String consoleText = String.format("Witaj %s dziś jest %s",
                    userName, now.format(consoleFormatter));
            System.out.println(consoleText);

            // Zapis do pliku
            try (FileWriter writer = new FileWriter(file, true)) {
                writer.write(userName + ";" + now.format(fileFormatter) + "\n");
            } catch (IOException e) {
                System.err.println("Błąd podczas zapisu do pliku: " + e.getMessage());
            }

            // Sleep
            try {
                Thread.sleep(loopTime * 1000L);
            } catch (InterruptedException e) {
                System.out.println("Przerwano pętlę");
                break;
            }
        }
    }
}

Następnie należy przygotować odpowiedni Dockerfile, który posłuży do zbudowania obrazu aplikacji.

FROM eclipse-temurin:17-jdk-alpine
WORKDIR /app
COPY JavaHello.java /app
# Kompilacja
RUN javac JavaHello.java
# Uruchomienie
CMD ["java", "JavaHello"]

Teraz możemy przystąpić do utworzenia projektu o nazwie java-hello oraz build configa i image stream-a o wskazanej dowolnej nazwie np. java-hello-build

oc new-project java-hello
# stworzenie build nowego obrazu bazującego na Dockerfile 
oc new-build --strategy=docker --binary --name=java-hello-build

# sprawdzamy czy powstał build config oraz image stream
oc get all
NAME                                              TYPE     FROM     LATEST
buildconfig.build.openshift.io/java-hello-build   Docker   Binary   0
NAME                                              IMAGE REPOSITORY                                                                                   TAGS   UPDATED
imagestream.image.openshift.io/java-hello-build   default-route-openshift-image-registry.apps.testcluster.okdlab.local/java-hello/java-hello-build

Następny krokiem jest zbudowanie obrazu czyli builda o nazwie java-hello-build

cd /home/bastuser/repo/java-hello
oc start-build java-hello-build --from-dir=. --follow

# obserwujmey postęp budowania obrazu
Uploading directory "." as binary input for the build ...
...
Uploading finished
build.build.openshift.io/java-hello-build-1 started
Receiving source from STDIN as archive ...
time="2025-10-02T09:29:47Z" level=info msg="Not using native diff for overlay, this may cause degraded performance for building images: kernel has CONFIG_OVERLAY_FS_REDIRECT_DIR enabled"
I1002 09:29:47.626854       1 defaults.go:112] Defaulting to storage driver "overlay" with options [mountopt=metacopy=on].
Caching blobs under "/var/cache/blobs".

Pulling image eclipse-temurin:17-jdk-alpine ...
Resolving "eclipse-temurin" using unqualified-search registries (/etc/containers/registries.conf)
Trying to pull registry.redhat.io/eclipse-temurin:17-jdk-alpine...
Trying to pull registry.access.redhat.com/eclipse-temurin:17-jdk-alpine...
Trying to pull quay.io/eclipse-temurin:17-jdk-alpine...
Trying to pull docker.io/library/eclipse-temurin:17-jdk-alpine...
Getting image source signatures
Copying blob sha256:c9bb5da1a24523a0af1966f13d7bc9ffb567c894c91c19968adcfc1fe0ea1653
Copying blob sha256:9824c27679d3b27c5e1cb00a73adb6f4f8d556994111c12db3c5d61a0c843df8
Copying blob sha256:889e463e11980d977e77842be54e94ad63e877ec78f1f31eb301c571bf31e842
Copying blob sha256:4d125b9b630f45fe9fdd3dc16a36920f92fa7c1e01344de92d6b6e6f6b50c5db
Copying blob sha256:5491fe72c6c247575080814d36438026b9d982ae49ccfcf86ff607e533f3b376
Copying config sha256:5d83145c93bb23306382c14e5390c60e7368cf12116f26337c3ed0d1149b29f8
Writing manifest to image destination
STEP 1/7: FROM eclipse-temurin:17-jdk-alpine
STEP 2/7: WORKDIR /app
--> 1204025c7bc0
STEP 3/7: COPY JavaHello.java /app
--> d066b3c172e4
STEP 4/7: RUN javac JavaHello.java
--> 8298966ffa3a
STEP 5/7: CMD ["java","JavaHello"]
--> f8076bce274a
STEP 6/7: ENV "OPENSHIFT_BUILD_NAME"="java-hello-build-1" "OPENSHIFT_BUILD_NAMESPACE"="java-hello"
--> 6760ba58180a
STEP 7/7: LABEL "io.openshift.build.name"="java-hello-build-1" "io.openshift.build.namespace"="java-hello"
COMMIT temp.builder.openshift.io/java-hello/java-hello-build-1:7df6d36a
--> 1a2b08ee857f
Successfully tagged temp.builder.openshift.io/java-hello/java-hello-build-1:7df6d36a
1a2b08ee857fb0199c2617f6e78b036cdd95137f8d4623d1a9bb9f1acca9df0a

Pushing image image-registry.openshift-image-registry.svc:5000/java-hello/java-hello-build:latest ...
Getting image source signatures
Copying blob sha256:59389359dcfe045e5018e571368fd9771e2f3ff666a49774075fd58f7fd6964a
Copying blob sha256:4d125b9b630f45fe9fdd3dc16a36920f92fa7c1e01344de92d6b6e6f6b50c5db
Copying blob sha256:5491fe72c6c247575080814d36438026b9d982ae49ccfcf86ff607e533f3b376
Copying blob sha256:9824c27679d3b27c5e1cb00a73adb6f4f8d556994111c12db3c5d61a0c843df8
Copying blob sha256:c9bb5da1a24523a0af1966f13d7bc9ffb567c894c91c19968adcfc1fe0ea1653
Copying blob sha256:889e463e11980d977e77842be54e94ad63e877ec78f1f31eb301c571bf31e842
Copying blob sha256:c2ba089e2966137b669a642480edf3d9b7639051fcc4f9113126883350252bd1
Copying config sha256:1a2b08ee857fb0199c2617f6e78b036cdd95137f8d4623d1a9bb9f1acca9df0a
Writing manifest to image destination
Successfully pushed image-registry.openshift-image-registry.svc:5000/java-hello/java-hello-build@sha256:b9d539feb351eb88f9b33818f2fb7b2bca81f2bcba0a41baebb5d01283df70d3
Push successful

Weryfikujemy czy powyższy build wykonał się poprawnie.

# sprawdzenie dostępnych buildów w projekcie
oc get builds
NAME                 TYPE     FROM     STATUS     STARTED         DURATION
java-hello-build-1   Docker   Binary   Complete   7 minutes ago   33s

# przejrzenie logów z builda
oc logs -f build/java-hello-build-1

# ostatnie zdarzenia w projekcie gdzie widać szczegóły powstawania builda w projekcie
oc get events --sort-by='{.lastTimestamp}' -n java-hello

Dodanie PV do klastra i PVC do projektu

Zanim utworzymy aplikację dobrze jest sobie przygotować jeśli będziemy tego używać w podzie volumen pvc oraz configmapę. Najpierw jeśli nie mamy to dodajemy obiekt PV widziany w całym OpenShift. PersistentVolume (PV) w OpenShift/Kubernetes to obiekt globalny (cluster-scoped)nie jest przypisany do projektu (namespace). Więcej o przygotowaniu udziałów NFS dla OpenShift można znaleźć tutaj https://itadmin.vblog.ovh/klaster-okd-openshift-na-maszynach-wirtualnych-proxmox-przygotowanie-maszyny-storage-nfs/

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pvc-01
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  nfs:
    path: /mnt/lv_pvc_01
    server: 192.168.40.20
  persistentVolumeReclaimPolicy: Retain
  storageClassName: ""   # Ważne, żeby nie był przypisany do dynamicznej klasy                                                                    6s

Tworzymy nowy obiekt PV o nazwie nfs-pvc-01

# tworzymy obiekt pv z powyższej definicji yaml
oc create -f /home/bastuser/pv-01.yaml

# weryfikujemy pv
oc get pv
NAME              CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                                       STORAGECLASS   REASON   AGE
nfs-pv-registry   100Gi      RWX            Retain           Bound       openshift-image-registry/nfs-pvc-registry                           123m
nfs-pvc-01        1Gi        RWX            Retain           Available                                                                       6s

Tworzymy nowy obiekt PVC tylko w naszym projekcie java-hello na bazie powyższego globalnego obiektu PV

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: java-hello-pvc
  namespace: java-hello
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
  volumeName: nfs-pvc-01    # jawne przypisanie do tego PV
  storageClassName: ""      # żeby nie szukało dynamicznej klasy
# tworzymy na podstawie definicji obiekt pvc
oc create -f /home/bastuser/pvc-java-hello.yaml -n java-hello

# sprwadzmy czy obiekt java-hello-pvc powstał w projekcie java-hello
oc get pvc -n java-hello
NAME             STATUS   VOLUME       CAPACITY   ACCESS MODES   STORAGECLASS   AGE
java-hello-pvc   Bound    nfs-pvc-01   1Gi        RWX                           23s

Dodanie configmapy do projektu

Tworzymy obiekt configmap o nazwie cm-java-hello przechowujący zmienne dla aplikacji java-hello

oc create configmap cm-java-hello \
  --from-literal=USER_NAME=Zbychu \
  --from-literal=LOOP_TIME=10 \
  --from-literal=FILE_PATH=/downloads/status.txt

# sprawdzamy czy istnieje taki obiekt cm
oc get cm
NAME                            DATA   AGE
cm-java-hello                   3      3s
java-hello-build-1-ca           1      41m
java-hello-build-1-global-ca    1      41m
java-hello-build-1-sys-config   0      41m
kube-root-ca.crt                1      80m
openshift-service-ca.crt        1      80m

# szczegółu obiektu cm cm-java-hello
oc describe cm/cm-java-hello
Name:         cm-java-hello
Namespace:    java-hello
Labels:       <none>
Annotations:  <none>
Data
====
FILE_PATH:
----
/downloads/status.txt
LOOP_TIME:
----
10
USER_NAME:
----
Zbychu
BinaryData
====
Events:  <none>

Utworzenie aplikacji OpenShift

Teraz możemy utworzyć aplikację na bazie istniejącego builda jako deployment-config do którego możemy dodać pvc oraz configmap.

# aplikacja czyli pody mają się zaczynąc od java-hello-app
oc new-app java-hello-build:latest --name=java-hello-app --as-deployment-config

warning: Cannot check if git requires authentication.
--> Found image 1a2b08e (13 minutes old) in image stream "java-hello/java-hello-build" under tag "latest" for "java-hello-build:latest"

    * This image will be deployed in deployment config "java-hello-app"
    * The image does not expose any ports - if you want to load balance or send traffic to this component
      you will need to create a service with 'oc expose dc/java-hello-app --port=[port]' later
    * WARNING: Image "java-hello/java-hello-build:latest" runs as the 'root' user which may not be permitted by your cluster administrator

--> Creating resources ...
Warning: apps.openshift.io/v1 DeploymentConfig is deprecated in v4.14+, unavailable in v4.10000+
    deploymentconfig.apps.openshift.io "java-hello-app" created
--> Success
    Run 'oc status' to view your app.

Podłączenie PVC jako wolumenu do deployment-config

oc set volume dc/java-hello-app \
  --add \
  --name=downloads \
  --mount-path=/downloads \
  --claim-name=java-hello-pvc

Podłączenie configmap-y do deploymentconfig.

oc set env dc/java-hello-app --from=configmap/cm-java-hello
Warning: apps.openshift.io/v1 DeploymentConfig is deprecated in v4.14+, unavailable in v4.10000+
deploymentconfig.apps.openshift.io/java-hello-app updated

#weryfikacja
oc set env dc/java-hello-app --list
Warning: apps.openshift.io/v1 DeploymentConfig is deprecated in v4.14+, unavailable in v4.10000+
# deploymentconfigs/java-hello-app, container java-hello-build
# FILE_PATH from configmap cm-java-hello, key FILE_PATH
# LOOP_TIME from configmap cm-java-hello, key LOOP_TIME
# USER_NAME from configmap cm-java-hello, key USER_NAME

Restart aplikacji aby dostała nowe ustawienia środowiskowe z configmap oraz volumen pvc zamontowany jako /downloads

oc rollout latest dc/java-hello-app
oc logs -f dc/java-hello-app

W razie wystąpienia błędów wewnątrz poda

oc logs -f java-hello-app-3-hzsfv
Błąd podczas tworzenia pliku: Permission denied

Należy na serwerze NFS w naszym przypadku storage.okdlab.local 192.168.40.20 wykonać polecenia

chown -R nobody:nobody /mnt/lv_pvc_01/
chmod -R 777 /mnt/lv_pvc_01/

Jeśli wszystko przebiegło ok to logi poda powinny wyglądać tak

oc logs -f java-hello-app-4-zt27r
Witaj Zbychu dziś jest 2025-10-02 16:25:14
Witaj Zbychu dziś jest 2025-10-02 16:25:24
Witaj Zbychu dziś jest 2025-10-02 16:25:34

Configmapa w formacie yml

Aby stworzyć czytelniejszą niż para key-value w obecnej configmapie cm-java-hello wersję configmapy która będzie dostępna w podzie w katalogu /config/application.yml przygotowujemy odpowiedni plik yml.

appsettings:
  user_name: "Zbychu"
  loop_time: "10"
  file_path: "/downloads/status.txt"

Usuwamy starą configmapę i dodajemy nową

# usuniecie stare cm
oc delete cm cm-java-hello

# stworzenie nowej cm
oc create configmap cm-java-hello --from-file=application.yml=/home/bastuser/java-hello-cm-application.yml -n java-hello
configmap/cm-java-hello created

# podgląd nowej cm
oc describe cm cm-java-hello -n java-hello
Name:         cm-java-hello
Namespace:    java-hello
Labels:       <none>
Annotations:  <none>
Data
====
application.yml:
----
appsettings:
  USER_NAME: "Zbychu"
  LOOP_TIME: "10"
  FILE_PATH: "/downloads/status.txt"
BinaryData
====
Events:  <none>

# zamontowanie nowej cm w deplotment config
oc set volume dc/java-hello-app \
  --add \
  --name=config-volume \
  --mount-path=/config \
  --configmap-name=cm-java-hello

Poprawiony kod JavaHello.java

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;

import org.yaml.snakeyaml.Yaml;

public class JavaHello {
    public static void main(String[] args) {
        // Domyślne wartości
        String userName = "Anonim";
        int loopTime = 5;
        String filePath = "/downloads/status.txt";

        // Ścieżka do pliku config
        File configFile = new File("/config/application.yml");

        if (configFile.exists()) {
            try (InputStream in = new FileInputStream(configFile)) {
                Yaml yaml = new Yaml();
                Map<String, Object> data = yaml.load(in);

                if (data != null && data.containsKey("appsettings")) {
                    Map<String, Object> settings = (Map<String, Object>) data.get("appsettings");

                    if (settings.get("user_name") != null) {
                        userName = settings.get("user_name").toString();
                    }

                    if (settings.get("loop_time") != null) {
                        try {
                            loopTime = Integer.parseInt(settings.get("loop_time").toString());
                        } catch (NumberFormatException e) {
                            System.out.println("Niepoprawna wartość loop_time, używam domyślnej 5s");
                        }
                    }

                    if (settings.get("file_path") != null) {
                        filePath = settings.get("file_path").toString();
                    }
                }
            } catch (IOException e) {
                System.err.println("Błąd odczytu pliku konfiguracyjnego: " + e.getMessage());
            }
        } else {
            System.err.println("Nie znaleziono pliku konfiguracyjnego, używam domyślnych wartości.");
        }

        File file = new File(filePath);
        try {
            if (!file.exists()) {
                file.getParentFile().mkdirs();
                file.createNewFile();
                System.out.println("Plik został utworzony: " + file.getAbsolutePath());
            }
        } catch (IOException e) {
            System.err.println("Błąd podczas tworzenia pliku: " + e.getMessage());
            return;
        }

        // Formattery do daty/czasu
        DateTimeFormatter consoleFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        DateTimeFormatter fileFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");

        // Nieskończona pętla
        while (true) {
            LocalDateTime now = LocalDateTime.now();

            // Log na konsolę
            String consoleText = String.format("Witaj %s dziś jest %s",
                    userName, now.format(consoleFormatter));
            System.out.println(consoleText);

            // Zapis do pliku
            try (FileWriter writer = new FileWriter(file, true)) {
                writer.write(userName + ";" + now.format(fileFormatter) + "\n");
            } catch (IOException e) {
                System.err.println("Błąd podczas zapisu do pliku: " + e.getMessage());
            }

            // Sleep
            try {
                Thread.sleep(loopTime * 1000L);
            } catch (InterruptedException e) {
                System.out.println("Przerwano pętlę");
                break;
            }
        }
    }
}

Poprawiony kod Dockerfile z obsługą SnakeYAML do parsowania plików yml oraz prawidłowym czasem wewnątrz poda.
Dla zapewnienia długiej dostępności warto pobrać plik snakeyaml-2.2.jar na dysk.

cd /home/bastuser/repo/java-hello-application-yml
wget https://repo1.maven.org/maven2/org/yaml/snakeyaml/2.2/snakeyaml-2.2.jar
FROM eclipse-temurin:17-jdk-alpine
RUN apk add --no-cache tzdata \
    && cp /usr/share/zoneinfo/Europe/Warsaw /etc/localtime \
    && echo "Europe/Warsaw" > /etc/timezone
WORKDIR /app
# Skopiuj źródła i bibliotekę
COPY JavaHello.java /app
COPY snakeyaml-2.2.jar /app
# Kompilacja z biblioteką w classpath
RUN javac -cp snakeyaml-2.2.jar JavaHello.java
# Uruchomienie z biblioteką w classpath
CMD ["java", "-cp", ".:snakeyaml-2.2.jar", "JavaHello"]

Wykonanie nowego buildconfiga, nowego imagestream i nowego builda oraz deployment configu

cd /home/bastuser/repo/java-hello-application-yml
oc new-build --strategy=docker --binary --name=java-hello-yaml-build
oc start-build java-hello-yaml-build --from-dir=. --follow
oc new-app java-hello-yaml-build:latest --name=java-hello-app-yaml --as-deployment-config

# zamontowanie configmapy do nowego dc java-hello-app-yaml
oc set volume dc/java-hello-app-yaml \
  --add \
  --name=config-volume \
  --mount-path=/config \
  --configmap-name=cm-java-hello
  
 # zamontowanie volumeny PVC do dc java-hello-app-yaml
 oc set volume dc/java-hello-app-yaml \
  --add \
  --name=downloads \
  --mount-path=/downloads \
  --claim-name=java-hello-pvc
  
# restart poda
oc rollout latest dc/java-hello-app-yaml

Sprawdzenie poprawności konfiguracji i działania poda aplikacji

# sprawdzamy logi poda
oc logs -f java-hello-app-yaml-10-dq7rk
Witaj Zbychu dziś jest 2025-10-02 16:48:27
Witaj Zbychu dziś jest 2025-10-02 16:48:37

# oc get pods, sprawdzamy nazwę poda i wchodzimy do niego 
oc exec -it java-hello-app-yaml-10-dq7rk sh
 
# w środku poda
/app $ cat /config/application.yml
appsettings:
  user_name: "Zbychu"
  loop_time: "10"
  file_path: "/downloads/status.txt"
  
/app $ ls -la /downloads/status.txt
-rw-r--r-- 1 1000710000 root 14256 Oct  2 16:30 /downloads/status.txt

Usunięcie zawartości projektu oraz samego projektu

# usunięcie zawartości bez usuwania projketu
oc delete all --all -n java-hello

pod "java-hello-app-yaml-10-deploy" deleted
pod "java-hello-app-yaml-10-dq7rk" deleted
pod "java-hello-app-yaml-7-deploy" deleted
pod "java-hello-app-yaml-8-deploy" deleted
pod "java-hello-app-yaml-9-deploy" deleted
pod "java-hello-build-1-build" deleted
pod "java-hello-yaml-build-1-build" deleted
pod "java-hello-yaml-build-2-build" deleted
pod "java-hello-yaml-build-3-build" deleted
pod "java-hello-yaml-build-4-build" deleted
replicationcontroller "java-hello-app-yaml-1" deleted
replicationcontroller "java-hello-app-yaml-10" deleted
replicationcontroller "java-hello-app-yaml-2" deleted
replicationcontroller "java-hello-app-yaml-3" deleted
replicationcontroller "java-hello-app-yaml-4" deleted
replicationcontroller "java-hello-app-yaml-5" deleted
replicationcontroller "java-hello-app-yaml-6" deleted
replicationcontroller "java-hello-app-yaml-7" deleted
replicationcontroller "java-hello-app-yaml-8" deleted
replicationcontroller "java-hello-app-yaml-9" deleted
Warning: apps.openshift.io/v1 DeploymentConfig is deprecated in v4.14+, unavailable in v4.10000+
deploymentconfig.apps.openshift.io "java-hello-app-yaml" deleted
buildconfig.build.openshift.io "java-hello-build" deleted
buildconfig.build.openshift.io "java-hello-yaml-build" deleted
imagestream.image.openshift.io "java-hello-build" deleted
imagestream.image.openshift.io "java-hello-yaml-build" deleted

# usuniecie wszystkich configmap
oc delete configmap --all -n java-hello

# usuniecie wszystkich secretow
oc delete secret --all -n java-hello

#usuniecie wszystkich pvc
oc delete pvc --all -n java-hello

# usunięcie całęgo projektu
oc delete project java-hello
project.project.openshift.io "java-hello" deleted