Skip to content
Dokumentatsiya
Spring Boot Deployment

Java Spring Boot Deployment: Gitlab CI va Github Actions

Ushbu qo'llanma Java Spring Boot applicationlarnni GitLab CI/CD va GitHub Actions yordamida avtomatlashtirilgan tarzda deploy qilishni o'rganib chiqamiz. Bu amaliyot DevOps Engineerlar va Java dasturchilar uchun zarur bo'lgan bilim va ko'nikmalarni qamrab oladi. Qo'llanmada Java Spring Boot applicationining tuzilishini tushunish va konfiguratsiya qilish jarayonlari ko'rib chiqamiz.

Shuningdek, biz Dockerfile yozishni o'rganamiz va uni samarali ishlashi uchun optimallashtiramiz. Docker asosida konteynerlash jarayonlarini o'zlashtirgach, GitLab CI/CD va GitHub Actions yordamida CI/CD pipeline'larini sozlash va avtomatlashtirish usullarini ko'rib chiqamiz.

Ushbu qo'llanma orqali siz Java Spring Boot applicationlari uchun CI/CD jarayonlarini muvaffaqiyatli amalga oshirish bo'yicha amaliy yondashuvga ega bo'lasiz va real loyihalarda bu usullarni qo'llash imkoniyatiga ega bo'lasiz.

Ushbu amaliyotda quyidagi Java Spring Boot application kodlaridan foydalanamiz Gitlab-> gitlab.com/ismoilovdev/waifulist (opens in a new tab), Github-> github.com/devops-journey-uz/waifulist (opens in a new tab)

Loyihani tushunish

Spring Boot loyihasini muvaffaqiyatli deploy qilish uchun dastlab uning tuzilishini, ishlash prinsipini va muhim konfiguratsiyalarini to'liq tushunish lozim. Spring Boot loyihasi asosan quyidagi tarkibiy qismlardan iborat:

Masalan bizning loyihamiz tuzulishi:

/waifulist
├── Dockerfile
├── HELP.md
├── LICENSE
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── zawkin
    │   │       └── me
    │   │           └── asuna
    │   │               ├── config
    │   │               │   └── SwaggerConfig.java
    │   │               ├── controller
    │   │               │   └── WaifuController.java
    │   │               ├── dto
    │   │               │   └── WaifuDTO.java
    │   │               ├── entity
    │   │               │   └── WaifuEntity.java
    │   │               ├── repository
    │   │               │   └── WaifuRepository.java
    │   │               ├── service
    │   │               │   └── WaifuService.java
    │   │               └── WaifulistApplication.java
    │   └── resources
    │       └── application.properties
    └── test
        └── java
            └── zawkin
                └── me
                    └── asuna
                        └── WaifulistApplicationTests.java

src/main/java/zawkin/me/asuna katalogida loyihaning asosiy Java fayllari joylashgan. WaifulistApplication.java fayli Spring Boot applicationning asosiy dastur kodi hisoblanadi.

WaifulistApplication.java
package zawkin.me.asuna;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class WaifulistApplication {
 
	public static void main(String[] args) {
		SpringApplication.run(WaifulistApplication.class, args);
	}
 
}

@SpringBootApplication anotatsiyasi Spring Boot applicationning asosiy konfiguratsiyalarini o'z ichiga oladi. main metodida esa application ishga tushiriladi.

src/main/resources katalogida esa applicationning konfiguratsiyalarini saqlash uchun fayllar joylashgan. Bu katalogda application.properties yoki application.yml joylashadi. Bizning loyihamizda application.properties fayli mavjud.

application.properties
spring.application.name=waifulist

# DATABASE
spring.datasource.url=jdbc:postgresql://134.209.217.179:5432/waifulist
spring.datasource.username=postgres
spring.datasource.password=lwfjljqwotpreqwt2
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

logging.level.org.springframework.web=DEBUG
logging.level.org.springdoc=DEBUG
management.endpoints.web.exposure.include=health,info
management.endpoint.health.show-details=always

application.properties faylida applicationning konfiguratsiyalarini saqlash uchun kerak bo'lgan sozlamalar joylashgan. Masalan, spring.datasource.url sozlamasi PostgreSQL bazasiga ulanish uchun kerak bo'lgan ma'lumotlarni saqlaydi.

pom.xml yoki build.gradle fayllari dependencylar, pluginlar va boshqa loyihaning konfiguratsiyalarini saqlaydi. Bizning loyihamizda pom.xml fayli mavjud, ya'ni Maven loyihasi agar build.gradle fayli mavjud bo'lsa Gradle loyihasi deb hisoblanadi.

src/test/java/zawkin/me/asuna katalogida loyihaning test fayllari joylashgan. WaifulistApplicationTests.java fayli Spring Boot applicationning test kodi hisoblanadi.

WaifulistApplicationTests.java
package zawkin.me.asuna;
 
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
 
@SpringBootTest
class WaifulistApplicationTests {
 
	@Test
	void contextLoads() {
	}
 
}

Spring Boot loyihasini muvaffaqiyatli ishlashi uchun application.properties yoki application.yml fayllari muhim ahamiyatga egadir. Applicationning barcha konfiguratsiyalarini bu fayllar orqali o'zgartirish mumkin. Masalan bizning loyihamizdagi properties konfiguratsiya faylini ko'rib chiqamiz.

application.properties
spring.application.name=waifulist

# DATABASE
spring.datasource.url=jdbc:postgresql://134.209.217.179:5432/waifulist
spring.datasource.username=postgres
spring.datasource.password=lwfjljqwotpreqwt2
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

logging.level.org.springframework.web=DEBUG
logging.level.org.springdoc=DEBUG
management.endpoints.web.exposure.include=health,info
management.endpoint.health.show-details=always

Bu xavsfzilik uchun yaxshi yechim emas chunki secretlar ochiq holda turibdi buni Gitlab CI variable yoki Github Secretsga qo'shib ishlatishni ko'rib chiqamiz bu amaliyotda.

Qiziq savol tu'giladi nega ba'zi loyihalarda application.properties fayli bor ba'zilarida esa application.yml fayli bor?

application.properties va application.yml o'rtasidagi far quyidagicha:

Xusuysatlarapplication.propertiesapplication.yml
FormatOddiy key=value formatida yoziladi.YAML formatida, ierarxik struktura ko'rinishida.
O'qilish qulayligiKichik loyihalar uchun qulay.Murakkab konfiguratsiyalarda o'qish oson.
Profil boshqaruviHar bir profil uchun alohida fayl yaratiladi.Profil boshqaruvi oson va YAML ichida qilinadi.

Bu misolda application-dev.properties va application-prod.properties fayllarini alohida yaratish kerak bo'lsa, application-dev.yml va application-prod.yml fayllarini alohida yaratish kerak emas. YAML formati ierarxik struktura ko'rinishida yoziladi, shuning uchun profil boshqaruvi oson va qulaydir.

application.yml
spring:
  profiles:
    active: dev
---
spring:
  profiles: dev
  datasource:
    url: jdbc:postgresql://dev-db-url
---
spring:
  profiles: prod
  datasource:
    url: jdbc:postgresql://prod-db-url

Multi environment boshqaruvda application.properties va application.yml fayllarini quyidagicha ishlatamiz.

Masalan bizda dev, stage, prod environmentlar uchun alohida konfiguratsiyalar mavjud bo'lsa, application-dev.properties, application-stage.properties, application-prod.properties fayllarini yaratamiz. Bu fayllar alohida environmentlar uchun kerak bo'lgan konfiguratsiyalarni saqlaydi, agar application.yml faylini ishlatishni xohlasak, uni application-dev.yml, application-stage.yml, application-prod.yml yoki bitta application.yml faylida yozishimiz mumkin.

CI/CD'da ishga tushirishda esa docker run -e SPRING_PROFILES_ACTIVE=dev yoki docker run -e SPRING_PROFILES_ACTIVE=prod kabi environmentlar orqali alohida environmentni tanlashimiz mumkin.

Dockerfile yozish

Agar siz Docker bilan tanish bo'lmasangiz quyidagi qo'llanmalar orqali Docker bilan tanishingiz mumkin: Dockerga Kirish (opens in a new tab), Mastering Docker (opens in a new tab), Dockerfile yozish (opens in a new tab), Docker o'rnatish (opens in a new tab)

Spring Boot applicationni Docker konteyneriga joylash uchun Dockerfile yozish kerak. Dockerfile fayli konteynerning tuzilishini, ishlash prinsiplarini va muhim konfiguratsiyalarini saqlaydi. Docker orqali applicationlarimizni tezroq va xavfsiz tarzda deploy qilishimiz mumkin.

Dockerfile
FROM maven:3.9.5-eclipse-temurin-17-alpine AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B
 
FROM eclipse-temurin:17-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
RUN chown -R appuser:appgroup /app
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
    CMD wget --spider --quiet http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-jar", "app.jar"]

Dockerfileni ko'rib chiqadigan bo'lsa, u quyidagi qadamlardan iborat:

Dockerfile ikki qismga bo'linadi, build bosqichi(build stage) va runtime bosqichi(runtime stage). Build stageda Maven asosida loyihani build qilish va kerakli librarylarni yuklab olish uchun ishlatiladi va bunda biz yengil va kichik alpine imagedan foydalanamiz. runtime stageda esa yengil eclipse-temurin:17-alpine imagedan foydalanamiz bu Spring Boot uchun eng optimal JDK imagedir. Birinchi bosqich 1-6 qadamlar Maven asosida loyihani build qilish uchun kerakli jar faylini yaratadi. Ikkinchi bosqich 7-14 qadamlar esa jar faylini ishga tushiradi.

  1. FROM maven:3.9.5-eclipse-temurin-17-alpine AS builder - Maven va Java 17 asosida Docker imageni yaratamiz bu bizning builder imagemiz bo'ladi.
  2. WORKDIR /app - /app papkasiga o'tamiz yani barcha ishlarimizni bu papkada amalga oshiramiz.
  3. COPY pom.xml . - pom.xml faylini /app papkasiga ko'chiramiz bu faylda Maven dependencylar saqlanadi.
  4. RUN mvn dependency:go-offline -B - Maven dependencylarini yuklab olamiz.
  5. COPY src ./src - src katalogini /app/src papkasiga ko'chiramiz bu katalogda Java fayllar joylashgan.
  6. RUN mvn package -DskipTests -B - Spring Boot applicationni jar faylini yaratamiz va testlarni ishga tushirmasdan ishga tushiramiz.
  7. FROM eclipse-temurin:17-alpine - Java 17 asosida Docker imageni yaratamiz bu bizning asosiy imagemiz bo'ladi.
  8. RUN addgroup -S appgroup && adduser -S appuser -G appgroup - appgroup va appuser guruhlarini yaratamiz bu xavfsizlik uchun kerak bo'ladi.
  9. WORKDIR /app - /app papkasiga o'tamiz.
  10. COPY --from=builder /app/target/*.jar app.jar - builder imagedan jar faylini /app papkasiga ko'chiramiz bu jar fayl Spring Boot applicationni ishga tushirish uchun kerak bo'ladi.
  11. RUN chown -R appuser:appgroup /app - /app papkasiga appuser va appgroup guruhlariga ega bo'lgan permissionlarni beramiz
  12. USER appuser - appuser foydalanuvchiga o'tamiz.
  13. EXPOSE 8080 - 8080 portni ochamiz.
  14. HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD wget --spider --quiet http://localhost:8080/actuator/health || exit 1 - Spring Boot applicationning actuator/health endpointiga HTTP so'rov yuborishni tekshiramiz va 3 marta urinishdan keyin xatolik chiqsa konteynerni qayta ishga tushiramiz, bu helatchek deyiladi.
  15. ENTRYPOINT ["java", "-jar", "app.jar"] - Spring Boot applicationni ishga tushiramiz.

Eslatma: Ushbu helatchekni ishlatish uchun Spring Boot applicationda spring-boot-starter-actuator dependencyni qo'shishingiz kerak bo'ladi.

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
    CMD wget --spider --quiet http://localhost:8080/actuator/health || exit 1

pom.xml faylida spring-boot-starter-actuator dependencyni qo'shamiz.

pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

.properties yoki .yml fayllariga quyidagi konfiguratsiyalarni ham qo'shish kerak bo'ladi.

management.endpoints.web.exposure.include=health,info
management.endpoint.health.show-details=always

Konfiguratsiya fayllari bilan ishlash

Spring Boot applicationni Docker konteyneriga joylashda application.properties yoki application.yml fayllarini ishlatish kerak bo'ladi. Bu fayllar applicationning konfiguratsiyalarini saqlash uchun kerak bo'ladi. Bizning loyihamizda application.properties fayli mavjud lekin unda secretlar ochiq holda shuning uchun secretlarni Gitlab CI variable yoki Github Secretsga qo'shib multi-environment qilib sozlashimiz kerak bo'ladi. src/main/resources katalogida application-dev.propertiesva application-prod.properties fayllarini yaratamiz va ularni alohida environmentlar uchun kerakli konfiguratsiyalarni saqlaymiz.

application-dev.properties faylida dev environment uchun kerakli konfiguratsiyalar saqlanadi.

application-dev.properties
spring.application.name=waifulist
# DATABASE
spring.datasource.url=${DEV_DATABASE_URL}
spring.datasource.username=${DEV_DATABASE_USERNAME}
spring.datasource.password=${DEV_DATABASE_PASSWORD}
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

logging.level.org.springframework.web=DEBUG
logging.level.org.springdoc=DEBUG
management.endpoints.web.exposure.include=health,info
management.endpoint.health.show-details=always

application-prod.properties faylida esa prod environment uchun kerakli konfiguratsiyalar saqlanadi.

application-prod.properties
spring.application.name=waifulist
# DATABASE
spring.datasource.url=${PROD_DATABASE_URL}
spring.datasource.username=${PROD_DATABASE_USERNAME}
spring.datasource.password=${PROD_DATABASE_PASSWORD}
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

logging.level.org.springframework.web=DEBUG
logging.level.org.springdoc=DEBUG
management.endpoints.web.exposure.include=health,info
management.endpoint.health.show-details=always

VM Tayyorlash

CI/CD deployment bosqichida biz applicationimizni vm(virtual machine)ga deploy qilamiz bu uchun vm tayyorlashimiz kerak bo'ladi, yani pipeline serverga ssh bilan ulanib ishlaydigan qismi bor shu qismi uchun ssh sozlashimiz va docker o'rnatishimiz kerak bo'ladi chuni applicationimizni dockerda ishga tushiramiz.

Docker o'rnatish uchun quyidagi qo'llanmadan foydalanishingiz mumkin - Linux serverlarga Docker o'rnatish (opens in a new tab)

CD pipelien serverga ssh orqali kira olishi kerak buning uchun serverda ssh-key generatsiya qilishimiz kerak bo'ladi.

ssh-keygen

Yuqoridagi buyruqni yozib ENTERni bosib key generatsiya qilib oling

yuqorida buyruq ikkita key generatsiya qiladi public(id_rsa.pub) va private(id_rsa) keylar, bu keylar ~/.ssh papkasiga saqlanadi. CD pipeline serverga kira olishi uchun private(id_rsa) keyni authorized_keys fayliga qo'shib chiqishimiz kerak bo'ladi.

cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys

Keyin esa private(id_rsa) keyni Gitlab CI variable yoki Github Secretsga SSH_PRIVATE_KEY nomi bilan qo'shamiz private keyni cat ~/.ssh/id_rsa buyruqini ishga tushirib olishimiz mumkin.

cat ~/.ssh/id_rsa

Gitlab CI/CD pipeline

Okey yuqorida biz loyihani tushundik, Dockerfile yozdik va konfiguratsiyalarimizni sozladik endi Gitlab CI/CD pipeline'larimizni yozamiz. Gitlab CI/CD pipeline'larini yozish uchun .gitlab-ci.yml faylini loyihaning asosiy katalogiga yaratamiz.

CI pipeline

Birinchi navbatda loyihani test qilish uchun CI pipeline'ni yozamiz.

.gitlab-ci.yml
stages:
  - build_and_push
 
variables:
  IMAGE_NAME: waifulist
  REPO_NAME: $CI_PROJECT_PATH
  REGISTRY: "registry.gitlab.com"
 
build_and_push:
  stage: build_and_push
  image: docker:stable
 
  services:
    - docker:dind
 
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
 
  script:
    - docker build -t $REGISTRY/$REPO_NAME/$IMAGE_NAME:$CI_COMMIT_SHA .
    - docker push $REGISTRY/$REPO_NAME/$IMAGE_NAME:$CI_COMMIT_SHA

Ushbu CI pipeline'ni tushunish uchun quyidagi qadamlarni ko'rib chiqamiz:

Agar siz Gitlab CI/CD bilan tanish bo'lmasangiz quyidagi qo'llanmalar orqali Gitlab CI/CD bilan tanishingiz mumkin: Gitlab CI bilan CI/CD (opens in a new tab), Gitlab CI | Releaselar va Integrationlar (opens in a new tab).

  1. stages - Pipeline'ning bosqichlarini bildiradi bunda faqat build_and_push bosqich bor.
  2. variables - Pipeline'ning ishlatadigan o'zgaruvchilar belgilanadi bu yerda IMAGE_NAME, REPO_NAME, REGISTRY o'zgaruvchilari belgilangan container registry sifatida gitlab container registry ishlatilgan.
  3. build_and_push - Pipeline'ning birinchi bosqichidir bu bosqichda loyihani build qilish va Gitlab registryga imageni push qilish amalga oshiriladi.
  4. image - Docker imageni ishlatish uchun docker:stable imageni ishlatamiz.
  5. services - Docker daemonni ishlatish uchun docker:dind servisini ishlatamiz.
  6. before_script - Pipeline boshlanishida Gitlab registryga kirish uchun bajariladigan amallar belgilanadi bu holda docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY amalini bajaradi yani Gitlab registryga kiradi.
  7. script - Docker imageni build qilish va Gitlab registryga push qilish amallari belgilanadi bu holda docker build -t $REGISTRY/$REPO_NAME/$IMAGE_NAME:$CI_COMMIT_SHA . va docker push $REGISTRY/$REPO_NAME/$IMAGE_NAME:$CI_COMMIT_SHA amallari bajariladi.

Buni qisqacha qilib tushuntiradigan bo'lsam CI pipeline belgilangan branchda o'zgarish bo'lsa vatomatik ishga tushadi va docker login qilib Gitlab Container Registrga kiradi va docker yordamida loyihani build qilib uni Gitlab Container Registryga imageni push qiladi.

.gilab-ci.yml faylini yuqoridagidek konfiguratsiya yozib gitlabga push qilamiz va bizda avtomatik CI pipeline ishga tushadi.

Ko'k rang bilan ishlab turgan job ustiga bosib CI pipeline'ni ko'rishimiz mumkin va u ochilganda biz build_and_push jobini ko'rishimiz mumkin. Joblar ko'k rangda pipeline ishga tushayotganini bildiradi.

Okey bizning CI pipeline muvaffaqiyatli ishga tushdi va Gitlab registryga imageni push qildik endi uni tekshirib ko'rishimiz kerak bo'ladi.

Repositoriyadagi -> Deploy -> Container Registry bo'limiga o'tib CI pipeline orqali build qilingan imageni ko'rishimiz mumkin. Mana bizning waifulist docker imagemiz.

Biz CI bosqichini muvaffaqiyatli ishga tushirdik endi esa CD pipeline'ni yozamiz.

CD pipeline

CD pipelineda biz CI bosqichida build bo'lgan docker imageni Gitlab registrydan olib, ssh orqali vm'ga kirib docker konteynerini ishga tushiramiz. Bu amaliyotda deploy qilish uchun vm(virtual mashina) ishlatamiz, CD pipelineda serverga kirib docker konteynerini ishga tushiramiz.

Buning uchun .gitlab-ci.yml faylini quyidagicha o'zgartiramiz.

.gitlab-ci.yml
stages:
  - build_and_push
  - deploy
 
variables:
  IMAGE_NAME: waifulist
  CONTAINER_NAME: waifulist
  PORT: "8080:8080"
  REPO_NAME: $CI_PROJECT_PATH
  REGISTRY: "registry.gitlab.com"
  SSH_HOST: $SERVER_IP
  SSH_USER: $SERVER_USERNAME
  SSH_KEY: $SSH_PRIVATE_KEY
  SPRING_PROFILES_ACTIVE: dev
  DEV_DATABASE_URL: $DEV_DATABASE_URL
  DEV_DATABASE_USERNAME: $DEV_DATABASE_USERNAME
  DEV_DATABASE_PASSWORD: $DEV_DATABASE_PASSWORD
 
build_and_push:
  stage: build_and_push
  image: docker:stable
  services:
    - docker:dind
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
  script:
    - docker build -t "$REGISTRY/$REPO_NAME/$IMAGE_NAME:$CI_COMMIT_SHA" .
    - docker push "$REGISTRY/$REPO_NAME/$IMAGE_NAME:$CI_COMMIT_SHA"
 
deploy:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add --update --no-cache openssh-client
  script:
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - ssh-keyscan -H $SSH_HOST >> ~/.ssh/known_hosts 
    - ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST "echo "$CI_JOB_TOKEN" | docker login -u gitlab-ci-token --password-stdin $CI_REGISTRY"
    - ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST "docker pull $REGISTRY/$REPO_NAME/$IMAGE_NAME:$CI_COMMIT_SHA"
    - ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST "docker stop $CONTAINER_NAME || true"
    - ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST "docker rm $CONTAINER_NAME || true"
    - ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no "$SSH_USER@$SSH_HOST" "docker run -d --name $CONTAINER_NAME --restart=always -p $PORT -e SPRING_PROFILES_ACTIVE=$SPRING_PROFILES_ACTIVE -e DEV_DATABASE_URL=$DEV_DATABASE_URL -e DEV_DATABASE_USERNAME=$DEV_DATABASE_USERNAME -e DEV_DATABASE_PASSWORD=$DEV_DATABASE_PASSWORD $REGISTRY/$REPO_NAME/$IMAGE_NAME:$CI_COMMIT_SHA"

Ushbu CD pipeline'ni tushunish uchun quyidagi qadamlarni ko'rib chiqamiz:

  1. stages - ga deploy bosqichini qo'shamiz bu bosqichda loyihani deploy qilish amalga oshiriladi.
  2. variables - ga yangi o'zgaruvchilar qo'shamiz bu o'zgaruvchilar CONTAINER_NAME, PORT, SSH_HOST, SSH_USER, SSH_KEY, SPRING_PROFILES_ACTIVE, DEV_DATABASE_URL, DEV_DATABASE_USERNAME, DEV_DATABASE_PASSWORD o'zgaruvchilari belgilangan.
  3. deploy - Pipeline'ning ikkinchi bosqichidir bu bosqichda loyihani deploy qilish amalga oshiriladi bu bosqichda ssh orqali serverga kirib docker konteynerini ishga tushiramiz.
  4. image - Docker imageni ishlatish uchun alpine:latest imageni ishlatamiz.
  5. before_script - Pipeline boshlanishida openssh-clientni o'rnatish uchun bajariladigan amallar belgilanadi.
  6. script - SSH orqali serverga kirib docker konteynerini ishga tushirish amallari belgilanadi.
  7. mkdir -p ~/.ssh - ~/.ssh papkasini yaratamiz bu papkada ssh keylar saqlanadi.
  8. echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa - SSH private keyni ~/.ssh/id_rsa fayliga yozamiz bu key serverga kirish uchun kerak bo'ladi.
  9. chmod 600 ~/.ssh/id_rsa - ~/.ssh/id_rsa faylining permissionlarni belgilaymiz bu keyni faqat foydalanuvchi o'qishi mumkin.
  10. ssh-keyscan -H $SSH_HOST >> ~/.ssh/known_hosts - SSH hostning public keyini ~/.ssh/known_hosts fayliga yozamiz bu hostga birinchi marta ulanishda xatolik chiqmasligi uchun kerak bo'ladi.
  11. ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST " bu amal SSH orqali serverga kirish amaliyotini bajaradi.
  12. echo "$CI_JOB_TOKEN" | docker login -u gitlab-ci-token --password-stdin $CI_REGISTRY" - Gitlab registryga kirish uchun bajariladigan amal buning sababi Gitlab container registryga kirish va CI pipelineda yaratilgan imageni olish uchun kerak bo'ladi.
  13. docker pull $REGISTRY/$REPO_NAME/$IMAGE_NAME:$CI_COMMIT_SHA - Gitlab registrydan imageni pull qilib oladi.
  14. docker stop $CONTAINER_NAME || true - Bu eski Docker konteynerini to'xtatadi.
  15. docker rm $CONTAINER_NAME || true - Bu eski Docker konteynerini o'chiradi.
  16. docker run -d --name $CONTAINER_NAME -p $PORT -e SPRING_PROFILES_ACTIVE=$SPRING_PROFILES_ACTIVE -e DEV_DATABASE_URL=$DEV_DATABASE_URL -e DEV_DATABASE_USERNAME=$DEV_DATABASE_USERNAME -e DEV_DATABASE_PASSWORD=$DEV_DATABASE_PASSWORD $REGISTRY/$REPO_NAME/$IMAGE_NAME:$CI_COMMIT_SHA - Bu buyruq esa Gitlab CI pipelineda yaratilgan imageni belgilangan variablelar bilan ishga tushiradi, yani belgilangan portda, belgilangan nomda, belgilangan environment variablelar bilan ishga tushiradi.

Qisqacha qilib CD pipeline tushuntiradigan bo'lsam u alpine:latest imagedan foydalanib unga openssh-clientni o'rnatadi bu serverga ssh orqali kirish uchun kerak va SSH_HOST, SSH_USER, SSH_KEY o'zgaruvchilardan foydalanib serverga ssh bilan kiradi va docker login qilib Gitlab Container Registryga kiradi va CI pipelineda build qilinib yaratilgan docker imageni pull qilib oladi va eski ishlab turgan containerni to'xtatib o'chirib yangi containerni belgilangan o'zgaruvchilar va secretlar bilan ishga tushiradi.

Bu CD pipelineni ishga tushirish uchun kerakli secretlarni Gitlab CI variablega qo'shib chiqamiz.

Yuqorida biz VM tayyorlash bosqichida serverga docker install qilib ssh-key generatsiya qilgandik endi shu keylarni Gitlab CI variablega qo'shib chiqishimiz kerak bo'ladi. -> Settings -> CI/CD -> Variables bo'limiga o'tamiz va SERVER_IP nomli variablega server ip manzilini(Gitlab bo'glana oladigan IP) yozamiz. SERVER_USERNAME nomli variablega serverga kirish uchun user nomini yozamiz, userni whoami buyrug'ini ishga tushirib olishimiz mumkin.

whoami

SSH_PRIVATE_KEY nomli variablega esa yuqorida yaratib olgan private keyni yozamiz.

Environment variablelarni Gitlab CI variablega qo'shib chiqishimiz kerak bo'ladi. Repositoriyaga o'tib -> Settings -> CI/CD -> Variables bo'limiga o'tamiz va kerakli environment variablelarni qo'shib chiqamiz.

DEV_DATABASE_URL ga jdbc:postgresql://134.209.217.179:5432/waifulist formatda secret keyni yozamiz buyerda IP manzil PostgreSQL server manzili va porti waifulist esa database nomi bo'ladi.

DEV_DATABASE_USERNAME ga postgres secret keyni yozamiz buyerda PostgreSQL serverga ulanish uchun kerak bo'lgan foydalanuvchi nomi. DEV_DATABASE_PASSWORD ga lwfjljqwotpreqwt2 secret keyni yozamiz bu PostgreSQL serverga yuqoria belgilangan user bilan ulanish uchun kerak user paroli(yani postgres user paroli). Gitlab CI variablega secret keylarni qo'shib chiqdik endi Gitlab CI/CD pipeline'larimizda bu secret keylarni ishlatishimiz mumkin.

Gitlab CI variablelarni to'gri qo'yib chiqganimizdan keyin yangilangan .gitlab-ci.yml faylini gitlabga push qilamiz va avtomatik CI/CD pipeline ishga tushadi. Joblar ro'yxatini ko'rsak bu safar biz build_and_push va deploy joblarini ko'ramiz.

Okeey pipeliene muvaffaqiyatli ishga tushdi endi esa uni tekshirib ko'rishimiz kerak bo'ladi.

Serverga kirib docker containerlar ro'yxatini ko'ramiz.

docker ps

Docker containerlar ro'yxatida waifulist nomli containerni ko'rib turibmiz porti 8080 va statusi healthy bo'lganini ko'ramiz, keling endi bu API'ning swagger interfeysiga kirib ko'ramiz, bunig uchun brauzerdan http://server-ip::8080/swagger-ui/index.html manziliga kirib ko'ramiz bizda API swagger interfeysi ochilishi kerak.

Keling endi PostgreSQL databazani tekshirib ko'ramiz.

Github Actions CI/CD

Bu amaliyotimizda biz ham Gitlab CI bilan CI/CD pipeline va Github Actions bilan CI/CD pipeline yozamiz. Yuqorida biz Gitlab CI bilan pipeline yozishni ko'rib chiqdik bu qismda biz Github Actions bilan CI/CD pipeline yozamiz.

Agar siz Github Actions bilan tanish bo'lmasangiz quyidagi qo'llanma orqali o'rganib chiqishingiz mumkin: Github Actions CI/CD (opens in a new tab).

CI pipeline

Github Actions CI pipeline'ni yozish uchun birinchi navbatda .github/workflows papkasini yaratib olamiz va ichiga ci-cd.yml faylini yaratamiz ba CI pipeline'ni quyidagicha yozamiz.

.github/workflows/ci.yml
name: Github Actions CI/CD
 
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
 
env:
  REPO_NAME: ${{ github.repository }}
  REGISTRY: ghcr.io
 
jobs:
  build_and_push:
    runs-on: ubuntu-latest
    permissions:
        contents: read
        packages: write
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
 
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
 
      - name: Log in to the Container registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
 
      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: "${{ env.REGISTRY }}/${{ env.REPO_NAME }}:${{ github.sha }}"
          cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.REPO_NAME }}:buildcache
          cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.REPO_NAME }}:buildcache,mode=max

Agar siz Github Actions CI/CD (opens in a new tab) qo'llanmasini o'qib chiqgan bo'lsangiz bu CI pipeline'ni oson tushunasiz. Qisqa qilib aytganda bu CI juda oddiy pipeline bo'lib Github Actions orqali main branchga push qilganda yoki pull request ochganda avtomatik ishga tushadi va Docker imageni build qilib Github Container registryga push qiladi.

CI pipelineni sinash uchub Githubdagi repositoriyamiz main branchiga push qilamiz pipeline avtomatik ishga tushishi kerak.


Rasmda Github Actions ishga tushayotganini ko'rishingiz mumkin.


Repositoriyadan -> Actions bo'limiga o'tib pipelinedagi joblarni ko'rishimiz mumkin.


Bu qismda faqat bizda CI pipelineda build_and_push jobi ishlayotanini ko'rishimiz mumkin.


Bu rasmda esa CI pipeliendagi build_and_push jobi muvaffaqiyatli ishga tushganini ko'rishimiz mumkin.

CI pipeline muvaffaqiyatli ishga tushdi endi esa uni teklshirib ko'rishimiz kerak bo'ladi. Biz container registry sifatida Github Container registryni ishlatdik keling container registryda CI pipelienda yaratilgan imageni tekshirib ko'ramiz, buning uchun -> Packages bo'limiga o'tib container registryda yaratilgan imageni ko'ramiz.


Ko'rib turganingizdek bizda waifulist nomli docker image mavjud.

Biz Github Actionsda CI pipeline muvaffaqiyatli yozib ishga tushirdik endi esa CD pipeline'ni yozamiz.

CD pipeline

CD pipelineda biz CI pipeline'da build qilingan docker imageni Github Actions orqali serverga kirib docker imageni Github Container registrydan pull qilib belgilan qiymatlar bilan uni ishga tushiramiz. Buning uchun ci-cd.yml faylini o'zgartirishlar kiritishimiz kerak bo'ladi.

.github/workflows/ci-cd.yml
name: Github Actions CI/CD
 
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
 
env:
  REPO_NAME: ${{ github.repository }}
  CONTAINER_NAME: waifulist
  REGISTRY: ghcr.io
  SSH_HOST: ${{ secrets.SERVER_IP }}
  SSH_USER: ${{ secrets.SERVER_USERNAME }}
  SSH_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
  PORT: 8080:8080
  SPRING_PROFILES_ACTIVE: dev
 
jobs:
  build_and_push:
    runs-on: ubuntu-latest
    permissions:
        contents: read
        packages: write
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
 
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
 
      - name: Log in to the Container registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
 
      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: "${{ env.REGISTRY }}/${{ env.REPO_NAME }}:${{ github.sha }}"
          cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.REPO_NAME }}:buildcache
          cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.REPO_NAME }}:buildcache,mode=max
 
  deploy:
    needs: build_and_push
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - name: Executing remote SSH commands to deploy
        uses: appleboy/ssh-action@master
        with:
          host: "${{ env.SSH_HOST }}"
          username: "${{ env.SSH_USER }}"
          key: "${{ env.SSH_KEY }}"
          script: |
            echo ${{ secrets.GITHUB_TOKEN }} | docker login -u ${{ github.actor }} --password-stdin ${{ env.REGISTRY }}
            docker pull "${{ env.REGISTRY }}/${{ env.REPO_NAME }}:${{ github.sha }}"
            docker stop "${{ env.CONTAINER_NAME }}" || true
            docker rm "${{ env.CONTAINER_NAME }}" || true
            docker run -d --name ${{ env.CONTAINER_NAME }} --restart=always -p ${{ env.PORT }} \
            -e SPRING_PROFILES_ACTIVE=${{ env.SPRING_PROFILES_ACTIVE }} \
            -e DEV_DATABASE_URL=${{ secrets.DEV_DATABASE_URL }} \
            -e DEV_DATABASE_USERNAME=${{ secrets.DEV_DATABASE_USERNAME }} \
            -e DEV_DATABASE_PASSWORD=${{ secrets.DEV_DATABASE_PASSWORD }} \
            ${{ env.REGISTRY }}/${{ env.REPO_NAME }}:${{ github.sha }}

Ushbu CD pipelineni qisqacha tushintiradigan bo'lsam cd-cd.yml ga CONTAINER_NAME, PORT, SSH_HOST, SSH_USER, SSH_KEY, SPRING_PROFILES_ACTIVE, DEV_DATABASE_URL, DEV_DATABASE_USERNAME, DEV_DATABASE_PASSWORD nomli yangi o'zgaruvchilari qo'shildi. deploy uchun deploy nomli job yozdik bu job faqat build_and_push jobi tugagandan keyin ishlaydi va berilgan SSH_HOST, SSH_USER, SSH_KEY secretlarini Github Secretsdan olib serverga ssh orqali kiradi va birinchi navbatda Github Container Registyrga login qiladi va build_and_push jobida build qilingan imageni pull qilib oladi va hozir ishlab turgan eski containerni to'xtatatib belgilangan variablelar(SPRING_PROFILES_ACTIVE, PORT, CONTAINER_NAME, DEV_DATABASE_URL, DEV_DATABASE_USERNAME, DEV_DATABASE_PASSWORD) orqali yangi containerni ishga tushiradi. application-dev.properties faylida dev environment uchun kerakli secretlarni esa Github Secretsdan olib ishlatadi.

Ushbu yangilangan to'liq CI/CD pipelineni ishga tushirishdan oldin kerakli secretlarni Github Secretga qo'shib chiqishimiz kerak bo'ladi.

Buning uchun -> Settings -> Secrets and variable -> New repository secret bo'limiga o'tib kerakli secretlarni qo'shib chiqamiz.

DEV_DATABASE_URL ga jdbc:postgresql://134.209.217.179:5432/waifulist formatda secret keyni yozamiz buyerda IP manzil PostgreSQL server manzili va porti waifulist esa database nomi bo'ladi. DEV_DATABASE_USERNAME ga postgres secret keyni yozamiz buyerda PostgreSQL serverga ulanish uchun kerak bo'lgan foydalanuvchi nomi. DEV_DATABASE_PASSWORD ga lwfjljqwotpreqwt2 secret keyni yozamiz bu PostgreSQL serverga yuqoria belgilangan user bilan ulanish uchun kerak user paroli(yani postgres user paroli).

Yuqorida biz VM tayyorlash bosqichida serverga docker install qilib ssh-key generatsiya qilgandik endi shu keylarni Github Actions Secretga qo'shib chiqishimiz kerak bo'ladi.

SERVER_IP nomli variablega server ip manzilini(Github bo'glana oladigan IP) yozamiz. SERVER_USERNAME nomli variablega serverga kirish uchun user nomini yozamiz, userni whoami buyruqini ishga tushirib olishimiz mumkin.

whoami

SSH_PRIVATE_KEY nomli variablega esa yuqorida yaratib olgan private keyni yozamiz.

Bizda secretlar umumiy ro'yxati.

Okeyy biz barcha secret va variablelarni to'g'ri qo'shib chiqdik endi esa to'liq CI/CD pipelineni ishga tushirish uchun o'zgarishlarni main branchga push qilamiz. CI/CD avtomatik ishga tushishi kerak.

Ko'rib turganingizdek bu safar bizda joblar ikkita build_and_push va deploy joblarini ko'ramiz va ular ketma-ketlikda ishlaydi.

Okey bizda to'liq CI/CD muvaffaqiyatli ishladi endi uni tekshirib ko'rishimiz kerak bo'ladi.

Serverga kirib docker containerlar ro'yxatini ko'ramiz.

docker ps

Bizda waifulist nomli docker containerimiz mavjud va statusi healthy bo'lganini ko'ramiz, keling endi bu API'ning swagger interfeysiga kirib ko'ramiz, bunig uchun brauzerdan http://server-ip::8080/swagger-ui/index.html manziliga kirib ko'ramiz bizda API swagger interfeysi ochilishi kerak.

Keling endi PostgreSQL databazani tekshirib ko'ramiz.

Niyohat biz bugun Spring Boot applicationni Docker konteynerida ishga tushirishni o'rgandik, Gitlab CI/CD va Github Actions CI/CD pipelinelarini yozdik va loyihamiz jarayonlarni avtomatlashtirdik, Spring Bootda multi-environment konfiguratsiyalar bilan ishlashni o'rgandik va secretlarimizni Gitlab CI variablega/Github Actionsga qo'shib ishladik. Bugungi amaliyot boshlang'ich darajada hisoblanadi bu orqali siz ko'p narsalarni o'rganib chiqasiz, qolganlari esa keyingi boqichlarda.

Qo'shimcha