A → Z (Spring Boot + React + Docker + Redis + Postgres + TravisCI + AWS) 1. Hissə

Afsun Khammadli
PASHA Bank
Published in
10 min readApr 2, 2020

Karantin günlərindən istifadə edib hamıya maraqlı olan geniş mövzunu bir məqalədə cəmləmək fikrinə gəldim. Məqaləni yazmaqda əsas məqsədim öz dilimizdə olan materialların sayının artırılmasıdır.

İlk öncə ən sonda hazırlayacağımız tətbiq haqqında yazım. Tətbiqin yekun forması və arxitekturası aşağıdakı kimidir:

Birinci şəkildən göründüyü kimi daxil edilən sözün palindrome olub-olmamasını yoxlayan bir tətbiq hazırlamışam. Palindrome verilən sözün tərsdən oxunduqda da eyni söz olmasına deyilir. Məsələn, ata, ana sözləri palindromedur, baba sözü isə deyil, çünki tərsdən oxuduqda abab olur :-).

Spring boot

Pivotal şirkətinin hazırladığı Spring-boot təməli java proqramlaşdırma dili olan və daha çox micro-service arxitekturalı tətbiqlərin yazılmasında istifadə olunan texnologiyadır. Mən hazırladığım tətbiqdə məntiqi hissəni hazırlamaq üçün bu texnologiyadan istifadə etmişəm. Hansı ki, burada daxil edilən sözün palindrome olub-olmaması yoxlanılır.

PostgreSQL

Verilənlər bazası(VB) kimi “open source” olan PostgreSQL istifadə etmişəm və spring-boot ilə inteqrasiya etmişəm. Axtarılan mətnlər hamısı tarixçə formasında burada saxlanılacaq.

Redis

C proqramlaşdırma dilində hazırlanmış olan istifadə etdiyimiz digər VB sistemi isə Redis-dir. Digər VB-lərdən fərqli olaraq Redis saxladığı məlumatı disk və ya SSD-də yox, RAM-da saxlayır, ona görə də digər VB-dan daha sürətli işləyir. Daha çox “key-value” data strukturu kimi istifadə olunur, həmçinin daha fərqli məlumat strukturlarını da biz Redis-də saxlaya bilərik. Bunlara misal olaraq: String, List, Sort, Hash, Bitmap və s. Bu texnologiyadan web tətbiqlərdə sessiya məlumatlarının saxlanılmasında, real vaxtlı məlumatların analiz edilməsində, sistemdə daha çox istifadə olunan məlumatların saxlanmasında, mesajlaşma tətbiqlərində və s. yerlərdə yüksək sürətli məhsul təqdim etmək üçün istifadə oluna bilər. Hazırladığım tətbiqdə isə mən Redisdən yoxlanılmış mətnləri və yoxlama nəticəsini key-value data strukturu ilə saxlamaq üçün istifadə etmişəm.

React

React - istifadəçi interfeyslərinin hazırlanması üçün istifadə olunan JavaScript kitabxanasıdır. Bu web texnologiyasını Facebook şirkəti hazırlamışdır. Tək səhifəlik(Single Page) web tətbiq və ya saytların hazırlamasında və mobil tətbiqlərin hazırlanmasında daha çox istifadə olunur. İri həcmli layihələri xırda komponentlərə ayırıb rahat işləmək imkanı yaradan bir texnologiyadır. Mən də yuxarıda gördüyünüz istifadəçi interfeysinin hazırlanmasında React-dan istifadə etmişəm.

Docker

Əvvəlcə, Docker haqqında ümumi məlumatlara və əsas əmrlərə yiyələnək, sonra isə hazırladığım tətbiqlər ilə onu inteqrasiya edək.

  • Docker nədir?

Go proqramlaşdırma dilində hazırlanan Docker tətbiqinizin hər hansı mühitdə problemsiz işləməsini təmin etmək üçün onun işləməsinə lazım olan bütün faylları, konfiqurasiyaları və s. bir container-də paketləyən bir platformadır. Təsəvvür edin, siz müştəri üçün spring-boot ilə bir web tətbiq hazırlamısınız və onu müştərinin kompüterində və ya serverində yerləşdirməlisiniz. Bunu etmək üçün biz hansı addımları keçməliyik?

Java Installer yüklənməli → Həmin installer ‘run’ edilməli → Son

Apache Tomcat və ya başqa ‘application server’ installer yüklənməli → Həmin installer ‘run’ edilməli → Son

Sonda isə tətbiqin arxiv faylını hazırlayıb bu serverə daşımalı və çalışdırılmalıyıq. Həmişə bu proses bu qədər axıcı olmaya bilər. Məsələn, versiya ilə bağlı hər hansı uyğunsuzluq ola bilər, əməliyyat sistemi səviyyəsində hər hansı uyğunsuzluq ola bilər və ya başqa bir problem. Problem çıxdıqda bunun üçün hər hansı həll tapıb yenidən prosesi başladırıq və ya hər şeyi silib tam başdan başlayırıq :-) Bu çox yorucu və bezdirici prosesdir. Docker container-lərdən istifadə edərək rahat bir şəkildə yuxarıda bizim sadaladığımız addımları həll etməsi üçün dizayn edilmişdir. Öz növbəsində daha çox konfiqurasiya və başqa işlərə vaxt ayırmaqdansa, proqramçılara daha çox proqram təminatının inkişaf etdirilməsinə fokuslanmağa imkan yaradır. Dockerin işləmə prinsipi bir az virtual maşına da oxşayır, ancaq ondan fərqli olaraq tamamilə yeni əməliyyat sistemi yaratmaq əvəzinə işlədikləri sistemin Linux nüvəsindən istifadə edir. Bu da öz növbəsində virtual maşınlardan fərqli olaraq tətbiqin həcmini azaldır və yüksək performans verir. Aşağıdakı şəkil Docker ilə Virtual maşın arasında fərqi göstərir:

  • Necə yükləməli?

Yükləmə və konfiqurasiya təlimatına əməliyyat sistemindən asılı olaraq WindowsMac üçün ətraflı baxa bilərsiniz. Addımları aşağıdakı şəkildə göstərmişəm:

Yükləmənin uğurlu olmasının yoxlanması üçün Terminal ilə aşağıdakı əmri icra edib yoxlaya bilərsiniz.

Buna oxşar bir şey görürsünüzsə, docker-i uğurlu sazlamısınız deməkdir :-) Dockeri yüklədikdən sonra bizə iki alət təqdim edir. Bunlar Docker CLI yəni Docker Client və Docker Server-dir. Docker Client bizə Docker Server ilə işləmək üçün əmrlərin ötürülməsində, Docker Server isə həmin əmrlərin icrasının edilməsində vasitə olacaq.

  • Docker image və container nədir?

Daha sadə dildə desək image tərkibində olan proqramın işləyə bilməsi üçün lazım olan konfiqurasiyalar və digər lazımi fayllardan ibarət olan bir fayldır. Məsələn, spring-boot ilə yazdığımız tətbiq üçün hazırlayacağımız image-in içərisində olmalı olan fayllardan biri tətbiqin arxiv faylıdır(*.jar və *.war). Containerin mahiyyəti isə image-in içində olan spesifik proqram təminatını çalışdırmaqdır. Bir image əsasında bir neçə container formalaşdırmaq olar. Container image-in işlək bir nümunəsidir, daha sadə dildə desək Docker image kompüterinizdə olan text fayldırsa , Docker Container isə həmin text faylın printerdən çıxmış kağız formasıdır demək olar. Docker image-i silə, başqaları ilə bölüşə, bir image əsasında yeni image formalaşdıra və s. bilərsiniz.

Docker Hub Docker image-lərin saxlanılması üçün istifadə olunur, yəni Docker image deposudur. Burada həm ümumi(public), həm də xüsusi(private) image-lər saxlanıla bilər. Həmçinin biz də öz hazırladığımız image-ləri docker push əmri ilə Docker Hub-da saxlaya bilərik. Ümumi olan image-lərdən hamı istifadə edərək, üzərində testlər apara bilər. Bunlardan bir neçəsini biz də istifadə edəcəyik.

docker run <image-name>: Bu əmr ilə ümumi Docker Hub deposunda olan hello-world image-ni çalışdıraq və hansı proseslərin baş verdiyini incələyək.

Hər iki şəkildən də gördüyünüz kimi hər ikisində eyni əmri icra etmişəm, ancaq birincidə bir az əlavə proseslər icra olunub. Bunun daha aydın olması üçün aşağıdakı şəklə baxaq.

Biz ilk dəfə əmri icra etdikdə docker client əmri docker serverə ötürür, o da öz növbəsində Local Image Cache-də həmin adlı image axtarır, image ilk əmrdə olmadığına görə Docker Hub-dan həmin image yüklənir, daha sonra icra olunur. İkinci dəfə eyni əmri icra etdikdə isə artıq image-in kopyasının cache-da olmasına görə Docker Hub-a əlavə müraciət getmir və bir başa çalışdırılır.

docker images əmri Local Image Cached-da olan image-lərin siyahısına baxmaq üçün istifadə olunan əmrdir.

docker ps -a yaradılmış bütün containerlərin siyahısına baxmaq üçün istifadə olunan əmrdir. docker ps isə hal-hazırda işlək olan containerlərin siyahısı üçün istifadə edilir. docker rm <container-id> container id əsasında container-in silinməsi docker image rm <image-id> image id əsasında image-in silinməsi əmrləridir. Digər vacib əmrlər haqqında isə istifadə etdikcə məlumat verəcəyəm.

İndi isə spring-boot ilə yazdığımız tətbiqin image-ni hazırlayaq. Spring-boot tətbiq yaratmaq üçün https://start.spring.io/ saytından istifadə edə bilərsiniz. Mən “build tool” kimi gradle seçmişəm.

Contolləri əlavə etdikdən sonra tətbiqin işləməsindən əmin olmaq üçün onu yoxlayıb sonra image-in yaradılması mərhələsinə keçə bilərik. Image-in hazırlanması Dockerfile-in içərisində olan əmrlər vasitəsilə edilir. Bunun üçün biz test tətbiqinin daxilin Dockerfile və onun içində müvafiq əmrləri yazmalıyıq.

  • FROM <base-image>: Biz hazırlayacağımız yeni image openjdk base image-i əsasında hazırlayacağıq. Base image həm ümumi, həm xüsusi, həm də özümüzün hazırladığı hər hansı image ola bilər. Bu image java proqramlaşdırma dilində olan tətbiqlərin containerinin istifadə edilməsi üçündür.
  • WORKDIR: Bu əmr docker containerin iş qovluğunu təmin etmək üçündür.
  • COPY: Ünvanı göstərilən faylların image-də kopyasının yaradılması üçündür
  • ENTRYPOINT: Container işə düşdükdə hansı əmrlərin icra edilməsini bununla göstərə bilərik.

Image yaratmaq üçün tətbiqin qovluğunda şəkildəki əmrləri icra etmək lazımdır.

docker build . əmri həmin qovluqda olan Dockerfile ilə image yaradılmasını icra edir. Aldığımız xətanın iki səbəbi var. Birincisi biz hələki gradle ilə arxiv fayl yaratmamışıq əgər arxiv fayl yaratmış olsa idik yenə eyni xəta olacaqdı, onun da səbəbi yaradılmış arxiv faylının adı bizim qeyd etdiyimiz kimi deyil ona görə də gradle.build faylında müvafiq dəyişikliklər edilməlidir sonra da gradle ilə arxiv fayl yaradılmalıdır.

bootJar {
archiveName = "${project.name}.jar"
}

İndi isə yenidən image yaradılmasına cəhd edək.

Bu dəfə biz uğurla image yaratdıq. Artıq həmin image çalışdıra bilərik.

Və artıq container-imiz işlək formadadır. Yeni terminal açıb orada docker ps ilə işlək containerlərə baxsaq bu container orada görə bilərik.

Web browser-də http://localhost:8080 ilə container-i test etmək istədikdə həmin ünvana qoşula bilməməyinizlə bağlı xəta mesajı alacaqsınız. Bunun səbəbi isə odur ki, tətbiq containerin daxilində həmin port-da çalışır. Ümumiyyətlə, Docker container-lər əməliyyat sistemində olan ünvanlara qoşulma imkanı var, ancaq Docker-dən ayrı yəni əməliyyat sistemində çalışan tətbiqlərin birbaşa Docker Container-ə qoşulma icazəsi yoxdur. Bütün qoşulmalar əsas kompüterdən yarandığı kimi anlaşılır. Docker əməliyyat sisteməni yükləndiyində onunla birgə Docker-dən xaric əlaqə üçün onun körpü şəbəkəsi(bridge network) də yaranır. Docker öz iptables masquerading qanunu yaradır. Container-ə Docker-dən kənar qoşulma üçün biz “port-mapping” etməliyik.

Yuxarıdaki əmrlə biz yenidən container yaratsaq artıq biz web browser-də http://localhost:4000/ ünvanına qoşularaq tətbiqi test edə bilərik.

  • Docker Multi-stage build

17. versiya ilə docker-ə yenə özəllik yəni çox mərhələli image-in yaradılması xüsusiyyəti gəldi. Yəni biz bir Dockerfile ilə həm spring-boot tətbiqimizin arxiv faylını hazırlayacağıq, həm də həmin arxiv faylın çalışdırılması üçün image. İlkin Dockerfile ilə biz yalnızca əvvəlcə arxivləşdirilmiş faylı image-ə kopyalayırdıq, indi isə biz həmin hissəni “multi-stage build” özəlliyi ilə edəcəyik. Bu özəllik gəlməzdən əvvəl problemin həlli üçün biz bir neçə Dockerfile yaratmalı idik.

FROM openjdk:12-jdk-alpine AS builder
WORKDIR /code/
COPY build.gradle settings.gradle gradlew /code/
COPY gradle /code/gradle
RUN ./gradlew build || return 0
COPY . .
RUN ./gradlew build


FROM openjdk:12-jdk-alpine
WORKDIR /code/
COPY --from=builder /code/build/libs/test.jar .
ENTRYPOINT ["java","-jar","test.jar"]

Birinci image-də tətbiqin arxiv faylının yaradılması üçün lazımi faylları kopyalayıb gradle vasitəsilə arxiv faylını yaratdıq, ikinci image isə birinci hazırladığı arxiv faylını özünə kopyalayır. Bizim nəticə olaraq əldə etdiyimiz image ikinci olacaq.

  • React ilə Docker

React-app-i də hazırlamaq üçün isə https://create-react-app.dev/docs/getting-started/#quick-start ünvanındaki addımları izləyə bilərik. Həmçinin biz bu tətbiq üçün də “multi-stage build” istifadə edəcəyik.

FROM node:alpine as builder_image
WORKDIR /app
COPY ./package.json ./
RUN npm install
COPY . .
RUN npm run build

FROM nginx
EXPOSE 3000
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
COPY --from=builder_image /app/build /usr/share/nginx/html

Artıq tətbiqin texnologiyların docker vasitəsilə bir-birilə ilə işləməsinə keçə bilərik. Bu məqamda docker-compose haqqında yazmaq istəyirəm. Arxitekturadan da göründüyü kimi bir neçə container-dən istifadə edəcəyik və bu container-lər bir-birilə məlumat mübadiləsi etməlidir. Bunun üçün biz ya container-lər arasında şəbəkə yaratmalıyıq, ya da docker-compose alətinin özəlliklərindən istifadə etməliyik. Docker-compose ilə avtomatik olaraq bir neçə container işə sala və onlar arasında şəbəkə yarada bilərik. Containerlərin yaradılması və onlar arasında şəbəkə sazlamaları docker-compose.yml faylının tərkibində olacaq. Əvvəlcə bizim arxitekturamıza uyğun hansı containerlərin yaradılacağına və iş görəcəklərinə aşağıdaki şəkildə ətraflı baxaq. Daha sonra isə həmin şəkil əsasında docker-compose.yml faylının tərkibini formalaşdıraq:

version: '3'

services:
redis-server:
image: 'redis'
postgres:
image: postgres:latest
environment:
- POSTGRES_PASSWORD=admin
- POSTGRES_USER=postgres
- POSTGRES_DB=test
spring-boot-app:
build: ./demo
react-app:
build: ./react-app
proxy:
build: ./nginx
restart: always
ports:
- "80:80"

docker-compose.yml bir neçə Dockerfile-ın eyni icrasını və onlar arasında şəbəkinin təmin edilməsi üçündür.

redis-server və postgres Docker Hub-da olan ümumi image olduqlarına görə onların container-ni yaratmaq üçün yalnız image adı ilə containerləri yarada bilərik əlavə heç bir konfiqurasiyaya ehtiyac yoxdur. spring-boot-app, react-app və proxy yə containerləri üçün docker build əmrinin işlədiyində Dockerfile tapması üçün həmin ünvanı göstərməliyik. Containerlər arasında digər fərqlilik restart:always sazlamasının olmasıdır. Yalnız proxy container-nə Docker-dən xaric əlaqə olacaq. Əgər həmin container-də hər hansı problem olarsa digərləri ilə işləmək mümkün olmayacaq. Bu sazlama container-də xəta olarsa yenidən çalışdırılması mənasına gəlir. Digər bir fərqlilik isə “port-mapping” də yalnız proxy container-də olmasıdır. Biz bütün “routing” əməliyyatlarımızı həmin container üzərindən edəcəyimizə görə docker-dən xaric yalnız həmin container ilə əlaqə olacaq.

docker-compose ilə container-lər yaradıldıqda bütün containerlər eyni şəbəkədə yaranacaq, yəni hər bir container bir-biri ilə mübadilə edə bilər və heç bir “port-mapping” və ya əlavə xüsusi sazlamaya ehtiyac yoxdur. Service-in adı mübadilədə host kimi istifadə olunacaq. Yəni bizim spring-boot-app postgres bazaya qoşulmaq üçün host aşağıdaki kimi olacaq:

spring:
datasource:
url: jdbc:postgresql://postgres:5432/test

Nginx

Rus mühəndis Igor Sysoev tərəfindən hazırlanan “open source” alətdir. Hal-hazırda milyonlarla web saytlar nginx ilə çalışır. Buna misal olaraq Netflix, WordPress, Airbnb və s. sadalamaq olar. Yalnız web-server kimi yox, proxy server, web akselerator(load balancer) kimi də istifadə olunur. Biz Nginx-dən öz tətbiqimizdə həm react-app üçün web-server kimi, həm də routing üçün proxy server kimi istifadə etmişik. Proxy server Dockerfile və routing üçün konfiqurasiya faylından ibarət olacaq. Aşağıdakı bizim konfiqurasiya faylımızdır:

upstream react-app {
server react-app:3000;
}

upstream spring-boot-app {
server spring-boot-app:8080;
}

server {
listen 80;

location / {
proxy_pass http://react-app;
}

location /palindrome {
proxy_pass http://spring-boot-app;
}
}

İlk öncə service-lərimizi tanıdıb sonra da routing üçün sazlamalarımızı edirik. “/palindrome” ünvanı ilə başlayan bütün müraciətlər spring-boot-app container-nə digərləri yəni web səhifənin formalaşdırılması üçün lazımi fayllar(html, css, js) üçün müraciətləri isə react-app container-nə yönləndiriləcək.

Şəkildəki əmri docker-compose.yml faylının olduğu qovluqda çalışdıraraq bütün containerləri işə sala bilərsiniz. Bütün container-lər işə başladıqdan sonra browserdə http://localhost/ ünvanına müraciət edərək test etməyi unutmayın :-) Tətbiqin kodlarını GitHub-dan əldə edə bilərsiniz.

Ümid edirəm bu hissəyə kimi oxumusunuz və faydalı olmuşdur. Bu məqalənin ikinci hissəsində isə həmin tətbiqi TravisCI ilə AWS-də yerləşdirəcəyik və Kubernetes ilə containerlərin idarə edilməsini təmin edəcəyik.

--

--