Witajcie w tym blogu! To nasz trzeci post z serii, w której pokazujemy, jak wdrażać nowoczesne aplikacje webowe na platformie Red Hat OpenShift.

W dwóch wcześniejszych postach opowiedzieliśmy, jak wdrażać nowoczesne aplikacje webowe w zaledwie kilku krokach oraz jak wykorzystać nowy obraz S2I razem z gotowym obrazem serwera HTTP, takim jak NGINX, za pomocą powiązanych zbiorów (chained builds) do organizacji wdrożenia produkcyjnego.
Dziś pokażemy, jak uruchomić na platformie OpenShift serwer deweloperski dla swojej aplikacji i zsynchronizować go z lokalnym systemem plików, a także omówimy, czym są OpenShift Pipelines i jak można je stosować jako alternatywę dla powiązanych zbiorów.
OpenShift jako środowisko deweloperskie
Proces pracy dewelopera (development workflow)
Jak już wspomniano w , typowy proces rozwoju nowoczesnych aplikacji webowych to po prostu pewien „serwer deweloperski”, który śledzi zmiany w lokalnych plikach. Gdy do nich dochodzi, uruchamiana jest kompilacja aplikacji, a następnie jest ona aktualizowana w przeglądarce.
W większości nowoczesnych frameworków taki „serwer deweloperski” jest wbudowany w odpowiednie narzędzia wiersza poleceń.
Przykład lokalny
Na początku zobaczmy, jak to działa w przypadku lokalnego uruchamiania aplikacji. Jako przykład weźmiemy aplikację z wcześniejszych artykułów, chociaż prawie te same koncepcje procesu pracy zastosowane są także w innych nowoczesnych frameworkach.
Aby uruchomić „serwer deweloperski” w naszym przykładzie z React, wprowadzamy następujące polecenie:
$ npm run start
Wtedy w oknie terminala zobaczymy mniej więcej to:

A nasza aplikacja otworzy się w przeglądarce domyślnej:

Teraz, jeśli wprowadzimy zmiany w pliku, aplikacja powinna zaktualizować się w przeglądarce.
OK, z deweloperką w trybie lokalnym wszystko jasne, a jak osiągnąć to samo na OpenShift?
Serwer deweloperski na OpenShift
Jeśli pamiętasz, w , omawialiśmy tzw. etap uruchamiania (run phase) obrazu S2I i zobaczyliśmy, że domyślnie zarządzanie naszą aplikacją webową przejmuje moduł serve.
Jednak jeśli przyjrzymy się dokładniej z tego przykładu, to zawiera on zmienną środowiskową $NPM_RUN, która pozwala wykonać własne polecenie.
Na przykład, można użyć modułu nodeshift, aby wdrożyć nasze aplikacje:
$ npx nodeshift --deploy.env NPM_RUN="yarn start" --dockerImage=nodeshift/ubi8-s2i-web-app
Uwaga: powyższy przykład podany jest w skróconej formie, aby zilustrować ogólną ideę.
Dodaliśmy do naszego wdrożenia zmienną środowiskową NPM_RUN, która informuje etap wykonania, że powinien uruchomić polecenie yarn start, które uruchamia serwer deweloperski React w naszym podzie OpenShift.
Jeśli spojrzymy na logi działającego poda, to będzie wyglądać mniej więcej tak:

Oczywiście, wszystko to nie ma sensu, dopóki nie będziemy mogli synchronizować lokalnego kodu z kodem, który również jest kontrolowany pod kątem zmian, ale znajduje się na serwerze zdalnym.
Synchronizacja zdalnego i lokalnego kodu
Na szczęście, synchronizacja jest łatwa z pomocą nodeshift, a do śledzenia zmian możemy skorzystać z polecenia watch.
Tak więc, po wykonaniu polecenia do wdrożenia serwera deweloperskiego dla naszej aplikacji, możemy śmiało używać takiego polecenia:
$ npx nodeshift watch
W wyniku tego nastąpi połączenie z uruchomionym pod'em, który stworzyliśmy wcześniej, aktywuje synchronizację naszych lokalnych plików z zdalnym klastrem, a pliki na naszym lokalnym systemie zaczną być monitorowane pod kątem zmian.
Dlatego, jeśli teraz zaktualizujemy plik src/App.js, system zareaguje na te zmiany, skopiuje je do zdalnego klastra i uruchomi serwer deweloperski, który następnie zaktualizuje naszą aplikację w przeglądarce.
Dla pełnego obrazu pokażemy, jak wyglądają te polecenia w całości:
$ npx nodeshift --strictSSL=false --dockerImage=nodeshift/ubi8-s2i-web-app --build.env YARN_ENABLED=true --expose --deploy.env NPM_RUN="yarn start" --deploy.port 3000
$ npx nodeshift watch --strictSSL=false
Polecenie watch to abstrakcja nad poleceniem oc rsync, można dowiedzieć się więcej o tym, jak to działa .
To był przykład dla React, ale dokładnie tę samą metodę można zastosować z innymi frameworkami, wystarczy odpowiednio określić zmienną środowiskową NPM_RUN.
Pipelines Openshift

Teraz porozmawiamy o narzędziu, jakim są OpenShift Pipelines, i jak można je wykorzystać jako alternatywę dla powiązanych buildów chained build.
Czym są OpenShift Pipelines
OpenShift Pipelines to chmurowy system CI/CD do ciągłej integracji i dostarczania, zaprojektowany do organizowania potoków przy użyciu Tekton. Tekton to elastyczny, natywny dla Kubernetes framework CI/CD z otwartym kodem źródłowym, który pozwala na automatyzację wdrażania na różnych platformach (Kubernetes, serverless, maszyny wirtualne itp.) poprzez abstrakcję od poziomu bazowego.
Aby zrozumieć ten artykuł, potrzebna jest określona wiedza na temat Pipelines, dlatego stanowczo zalecamy najpierw zapoznać się z .
Konfiguracja środowiska roboczego
Aby móc wypróbować przykłady z tego artykułu, najpierw musisz przygotować środowisko robocze:
- Zainstalować i skonfigurować klaster OpenShift 4. W naszych przykładach używamy CodeReady Containers (CRD), a instrukcje dotyczące instalacji można znaleźć .
- Po przygotowaniu klastra musisz zainstalować Pipeline Operator. Nie martw się, to proste, instrukcje dotyczące instalacji .
- Pobierz (tkn) .
- Uruchom narzędzie wiersza poleceń create-react-app, aby stworzyć aplikację, która następnie zostanie wdrożona (to prosta aplikacja ).
- (Opcjonalnie) Skopiuj repozytorium, aby lokalnie uruchomić przykład aplikacji za pomocą polecenia npm install, a następnie npm start.
W repozytorium aplikacji znajdziesz również folder k8s, w którym znajdują się pliki YAML Kubernetes/OpenShift, używane do wdrażania aplikacji. Będą tam Tasks, ClusterTasks, Resources i Pipelines, które stworzymy w tym .
Zaczynamy
Pierwszym krokiem w naszym przykładzie jest stworzenie nowego projektu w klastrze OpenShift. Nazwijmy ten projekt webapp-pipeline i utwórzmy go następującą komendą:
$ oc new-project webapp-pipeline
Następnie ta nazwa projektu będzie występować w kodzie, więc jeśli zdecydujesz się nazwać go inaczej, nie zapomnij odpowiednio zmodyfikować kodu w przykładach. Od tego momentu będziemy postępować z dołu do góry: to znaczy, na początku stworzymy wszystkie składniki potoku, a dopiero potem sam potok.
Tak więc, na początek...
Zadania Tasks
Stworzymy kilka zadań (tasks), które pomogą w wdrożeniu aplikacji w ramach naszego konwoju pipeline. Pierwsze zadanie – apply_manifests_task – odpowiada za zastosowanie YAML tych zasobów Kubernetes (service, deployment i route), które znajdują się w folderze k8s naszej aplikacji. Drugie zadanie – update_deployment_task – odpowiada za aktualizację już wdrożonego obrazu na nowy, który jest tworzony przez nasz konwój.
Nie martw się, jeśli to na razie nie jest jasne. W rzeczywistości, te zadania to coś w rodzaju narzędzi, które omówimy bardziej szczegółowo później. Na razie po prostu je stwórzmy:
$ oc create -f https://raw.githubusercontent.com/nodeshift/webapp-pipeline-tutorial/master/tasks/update_deployment_task.yaml
$ oc create -f https://raw.githubusercontent.com/nodeshift/webapp-pipeline-tutorial/master/tasks/apply_manifests_task.yaml
Następnie za pomocą polecenia tkn CLI sprawdzimy, czy zadania zostały utworzone:
$ tkn task ls
NAME AGE
apply-manifests 1 minute ago
update-deployment 1 minute ago
Uwaga: to są lokalne zadania twojego aktualnego projektu.
Zadania klastra Cluster tasks
Zadania klastra to zasadniczo to samo, co po prostu zadania. To znaczy, że są to wielokrotnego użytku kolekcje kroków, które są łączone w różny sposób podczas uruchamiania konkretnego zadania. Różnica polega na tym, że zadanie klastra jest dostępne wszędzie w obrębie klastra. Aby zobaczyć listę zadań klastra, które automatycznie tworzą się po dodaniu Operatora Pipeline, ponownie użyjemy polecenia tkn CLI:
$ tkn clustertask ls
NAME AGE
buildah 1 day ago
buildah-v0-10-0 1 day ago
jib-maven 1 day ago
kn 1 day ago
maven 1 day ago
openshift-client 1 day ago
openshift-client-v0-10-0 1 day ago
s2i 1 day ago
s2i-go 1 day ago
s2i-go-v0-10-0 1 day ago
s2i-java-11 1 day ago
s2i-java-11-v0-10-0 1 day ago
s2i-java-8 1 day ago
s2i-java-8-v0-10-0 1 day ago
s2i-nodejs 1 day ago
s2i-nodejs-v0-10-0 1 day ago
s2i-perl 1 day ago
s2i-perl-v0-10-0 1 day ago
s2i-php 1 day ago
s2i-php-v0-10-0 1 day ago
s2i-python-3 1 day ago
s2i-python-3-v0-10-0 1 day ago
s2i-ruby 1 day ago
s2i-ruby-v0-10-0 1 day ago
s2i-v0-10-0 1 day ago
A teraz stwórzmy dwa zadania klastra. Pierwsze będzie generować obraz S2I i wysyłać go do wewnętrznego rejestru OpenShift; drugie – budować nasz obraz oparty na NGINX, używając jako zawartości aplikacji, którą już stworzyliśmy.
Tworzymy i wysyłamy obraz
Podczas tworzenia pierwszego zadania powtórzymy to, co już zrobiliśmy w poprzednim artykule o powiązanych kompilacjach. Przypomnijmy, że użyliśmy obrazu S2I (ubi8-s2i-web-app), aby "zbudować" naszą aplikację, a ostatecznie uzyskaliśmy obraz, który był przechowywany w wewnętrznym rejestrze OpenShift. Teraz użyjemy tego obrazu S2I aplikacji webowej, aby stworzyć DockerFile dla naszej aplikacji, a następnie skorzystamy z Buildah, aby przeprowadzić właściwą kompilację i wysłać uzyskany obraz do wewnętrznego rejestru OpenShift, ponieważ to właśnie OpenShift robi, gdy wdrażasz swoje aplikacje za pomocą NodeShift.
Zadajecie pytanie, skąd to wszystko wiemy? Z , po prostu skopiowaliśmy ją i dostosowaliśmy do siebie.
Zatem teraz tworzymy zadanie klastra s2i-web-app:
$ oc create -f https://raw.githubusercontent.com/nodeshift/webapp-pipeline-tutorial/master/clustertasks/s2i-web-app-task.yaml
Nie będziemy tego dokładnie analizować, a jedynie zatrzymamy się na parametrze OUTPUT_DIR:
params:
- name: OUTPUT_DIR
description: Lokalizacja katalogu wyjściowego kompilacji
default: build
Domyślnie ten parametr wynosi build, to właśnie tam React umieszcza skompilowane treści. W innych frameworkach używane są inne ścieżki, na przykład, w Ember to dist. Wynik naszego pierwszego zadania klastra będzie stanowił obraz zawierający skompilowane przez nas HTML, JavaScript i CSS.
Budujemy obraz na bazie NGINX
Jeśli chodzi o nasze drugie zadanie klastra, powinno ono zbierać dla nas obraz na podstawie NGINX, używając treści wcześniej skompilowanej aplikacji. W zasadzie to ta część poprzedniego rozdziału, w której omawialiśmy powiązane kompilacje chained builds.
Aby to zrobić – dokładnie tak, jak trochę wyżej – stworzymy zadanie klastra webapp-build-runtime:
$ oc create -f https://raw.githubusercontent.com/nodeshift/webapp-pipeline-tutorial/master/clustertasks/webapp-build-runtime-task.yaml
Jeśli spojrzeć na kod tych zadań klastra, widać, że nie podano konkretnego repozytorium Git, z którym pracujemy, ani nazw obrazów, które tworzymy. Wskazujemy jedynie, co dokładnie przekazujemy do Git lub jakiegoś obrazu, w który ma zostać wyprowadzony obraz końcowy. Dlatego te zadania klastra można ponownie wykorzystać także przy pracy z innymi aplikacjami.
I tutaj w elegancki sposób przechodzimy do następnego punktu…
Resources
Zatem, ponieważ, jak właśnie powiedzieliśmy, zadania klastrowe muszą być jak najbardziej ogólne, musimy stworzyć zasoby, które będą używane na wejściu (repozytorium Git) i na wyjściu (ostateczne obrazy). Pierwszym zasobem, którego potrzebujemy, jest Git, gdzie znajduje się nasza aplikacja, coś takiego jak:
# This resource is the location of the git repo with the web application source
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
name: web-application-repo
spec:
type: git
params:
- name: url
value: https://github.com/nodeshift-starters/react-pipeline-example
- name: revision
value: master
Tutaj PipelineResource ma typ git. Klucz url w sekcji params wskazuje na konkretne repozytorium i ustawia gałąź master (to jest opcjonalne, ale piszemy to dla pełności).
Teraz musimy stworzyć zasób dla obrazu, gdzie będą przechowywane wyniki wykonania zadania s2i-web-app, robi się to w ten sposób:
# This resource is the result of running "npm run build", the resulting built files will be located in /opt/app-root/output
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
name: built-web-application-image
spec:
type: image
params:
- name: url
value: image-registry.openshift-image-registry.svc:5000/webapp-pipeline/built-web-application:latest
Tutaj PipelineResource ma typ image, a wartość parametru url wskazuje na wewnętrzny OpenShift Image Registry, konkretnie na ten, który znajduje się w przestrzeni nazw webapp-pipeline. Nie zapomnij zmienić tego parametru, jeśli używasz innej przestrzeni nazw.
I w końcu, ostatni zasób, którego będziemy potrzebować, również będzie miał typ image i to będzie końcowy obraz NGINX, który następnie będzie używany podczas wdrażania:
# This resource is the image that will be just the static html, css, js files being run with nginx
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
name: runtime-web-application-image
spec:
type: image
params:
- name: url
value: image-registry.openshift-image-registry.svc:5000/webapp-pipeline/runtime-web-application:latest
I ponownie zwróć uwagę, że ten zasób przechowuje obraz w wewnętrznym rejestrze OpenShift w przestrzeni nazw webapp-pipeline.
Aby jednocześnie stworzyć wszystkie te zasoby, skorzystamy z polecenia create:
$ oc create -f https://raw.githubusercontent.com/nodeshift/webapp-pipeline-tutorial/master/resources/resource.yaml
Można sprawdzić, czy zasoby zostały stworzone w ten sposób:
$ tkn resource ls
Rurociąg (pipeline)
Teraz, gdy mamy wszystkie niezbędne składniki, zbudujmy z nich rurociąg, tworząc go następującym poleceniem:
$ oc create -f https://raw.githubusercontent.com/nodeshift/webapp-pipeline-tutorial/master/pipelines/build-and-deploy-react.yaml
Ale zanim uruchomimy to polecenie, rozważmy te składniki. Pierwszym jest nazwa:
apiVersion: tekton.dev/v1alpha1
kind: Pipeline
metadata:
name: build-and-deploy-react
Następnie w sekcji spec widzimy wskazanie zasobów, które stworzyliśmy wcześniej:
spec:
resources:
- name: web-application-repo
type: git
- name: built-web-application-image
type: image
- name: runtime-web-application-image
type: image
Następnie tworzymy zadania, które ma wykonać nasz rurociąg. Na początku musi wykonać już stworzone przez nas zadanie s2i-web-app:
tasks:
- name: build-web-application
taskRef:
name: s2i-web-app
kind: ClusterTask
To zadanie pobiera wejściowe (zasób gir) i wyjściowe (zasób built-web-application-image) parametry. Przekazujemy jej również specjalny parametr, aby nie weryfikowała TLS, ponieważ używamy certyfikatów samopodpisanych:
zasoby:
wejścia:
- nazwa: źródło
zasób: web-application-repo
wyjścia:
- nazwa: obraz
zasób: built-web-application-image
parametry:
- nazwa: TLSVERIFY
wartość: "false"
Następne zadanie jest prawie takie same, tylko w tym przypadku wywołujemy już utworzone przez nas zadanie klastra webapp-build-runtime:
nazwa: build-runtime-image
odwołanieZadania:
nazwa: webapp-build-runtime
rodzaj: ClusterTask
Podobnie jak w poprzednim zadaniu, przekazujemy zasób, ale teraz jest to built-web-application-image (wyjście naszego poprzedniego zadania). A jako wyjście ponownie określamy obraz. Ponieważ to zadanie musi być wykonane po poprzednim, dodajemy pole runAfter:
zasoby:
wejścia:
- nazwa: obraz
zasób: built-web-application-image
wyjścia:
- nazwa: obraz
zasób: runtime-web-application-image
parametry:
- nazwa: TLSVERIFY
wartość: "false"
runAfter:
- build-web-application
Następne dwa zadania odpowiadają za zastosowanie plików YAML usługi, trasy i wdrożenia, które znajdują się w katalogu k8s naszego aplikacji webowej, a także za aktualizowanie tego wdrożenia przy tworzeniu nowych obrazów. Te dwa zadania klastra zdefiniowaliśmy już na początku artykułu.
Uruchamianie potoku
Wszystkie części naszego potoku zostały utworzone, a my uruchomimy go następującą komendą:
$ tkn pipeline start build-and-deploy-react
Na tym etapie wiersz poleceń działa w trybie interaktywnym i należy wybierać odpowiednie zasoby w odpowiedzi na każde jego zapytanie: dla zasobu git wybieramy web-application-repo, następnie dla zasobu pierwszego obrazu – built-web-application-image, a na koniec dla zasobu drugiego obrazu – runtime-web-application-image:
? Choose the git resource to use for web-application-repo: web-application-repo (https://github.com/nodeshift-starters/react-pipeline-example)
? Choose the image resource to use for built-web-application-image: built-web-application-image (image-registry.openshift-image-registry.svc:5000/webapp-pipeline/built-web-
application:latest)
? Choose the image resource to use for runtime-web-application-image: runtime-web-application-image (image-registry.openshift-image-registry.svc:5000/webapp-pipeline/runtim
e-web-application:latest)
Pipelinerun started: build-and-deploy-react-run-4xwsr
Teraz sprawdzimy status potoku za pomocą następującej komendy:
$ tkn pipeline logs -f
Po uruchomieniu potoku i wdrożeniu aplikacji, zapytamy o opublikowaną trasę następującą komendą:
$ oc get route react-pipeline-example --template='http://{{.spec.host}}'
Aby uzyskać lepszą wizualność, możemy zobaczyć nasz potok w trybie dewelopera w sekcji Pipelines, jak pokazano na Rys. 1.

Rys. 1. Przegląd uruchomionych potoków.
Kliknięcie na uruchomiony potok wyświetla dodatkowe informacje, jak pokazano na Rys. 2.

Rys. 2. Dodatkowe informacje o potoku.
Po dodatkowych informacjach można zobaczyć uruchomione aplikacje w widoku Topology, jak pokazano na Rys. 3.

Rys 3. Uruchomiony pod.
Kliknięcie w kółko w prawym górnym rogu ikony otwiera naszą aplikację, jak pokazano na Rys. 4.

Rys. 4. Uruchomiona aplikacja React.
Podsumowanie
Tak, pokazaliśmy, jak uruchomić na OpenShift serwer deweloperski dla swojej aplikacji i zsynchronizować go z lokalnym systemem plików. Zbadaliśmy również, jak zasymulować szablon chained-build przy użyciu OpenShift Pipelines. Wszystkie kody przykładów z tego artykułu można znaleźć .
Dodatkowe zasoby (EN)
- Darmowa e-książka
- Inne artykuły na temat na stronie Red Hat
Zapowiedzi nadchodzących webinarów
Rozpoczynamy serię piątkowych webinarów na temat natywnego doświadczenia korzystania z Red Hat OpenShift Container Platform i Kubernetes:
Źródło: habr.com
