3.1 Лабораторийн зорилго

Энэ лабораторид та:

  1. GitHub Actions ашиглан Java/Spring Boot CI pipeline үүсгэх
  2. Автомат тест, код чанарын шалгалт нэмэх
  3. Docker image бүтээх pipeline нэмэх
  4. Multi-environment deploy тохируулах

Хэл: Java 17+ / Spring Boot 3.x | CI/CD: GitHub Actions | Container: Docker


3.2 Лаб 1: GitHub Actions CI Pipeline үүсгэх

Алхам 1: Хавтасын бүтэц

student-api/
├── .github/
│   └── workflows/
│       └── ci.yml          ← CI Pipeline
├── src/
│   ├── main/java/...
│   └── test/java/...
├── Dockerfile               ← Docker image
├── docker-compose.yml       ← Олон container
├── pom.xml
└── README.md

Алхам 2: CI Workflow файл үүсгэх

# .github/workflows/ci.yml
name: CI Pipeline

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

env:
  JAVA_VERSION: '17'

jobs:
  # ────────────────────────────────────────
  # Job 1: Build + Unit Test
  # ────────────────────────────────────────
  build:
    name: Build & Unit Test
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up JDK ${{ env.JAVA_VERSION }}
        uses: actions/setup-java@v4
        with:
          java-version: ${{ env.JAVA_VERSION }}
          distribution: 'temurin'

      - name: Cache Maven dependencies
        uses: actions/cache@v4
        with:
          path: ~/.m2/repository
          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
          restore-keys: |
            ${{ runner.os }}-maven-

      - name: Build and Run Unit Tests
        run: mvn clean verify -DskipITs

      - name: Upload Test Results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: target/surefire-reports/

      - name: Upload JAR Artifact
        uses: actions/upload-artifact@v4
        with:
          name: app-jar
          path: target/*.jar

Алхам 3: Push хийж Pipeline ажиллуулах

# 1. Хавтас үүсгэх
mkdir -p .github/workflows

# 2. ci.yml файл нэмэх (дээрх агуулга)

# 3. Commit + Push
git add .github/workflows/ci.yml
git commit -m "ci: add GitHub Actions CI pipeline"
git push origin main

# 4. GitHub → Actions tab → Pipeline ажиллаж буйг харах

3.3 Лаб 2: Тест + Code Coverage нэмэх

Алхам 1: JaCoCo нэмэх (pom.xml)

<!-- pom.xml дотор plugins хэсэгт -->
<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.8.11</version>
    <executions>
        <execution>
            <goals>
                <goal>prepare-agent</goal>
            </goals>
        </execution>
        <execution>
            <id>report</id>
            <phase>verify</phase>
            <goals>
                <goal>report</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Алхам 2: CI Pipeline-д Coverage нэмэх

# ci.yml-д нэмэх (build job-ийн steps дотор)
      - name: Generate Code Coverage Report
        run: mvn jacoco:report

      - name: Upload Coverage Report
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: target/site/jacoco/

      - name: Check Coverage Threshold
        run: |
          COVERAGE=$(grep -oP 'Total.*?(\d+)%' target/site/jacoco/index.html | grep -oP '\d+' | head -1)
          echo "Code Coverage: ${COVERAGE}%"
          if [ "$COVERAGE" -lt 70 ]; then
            echo "❌ Coverage ${COVERAGE}% is below 70% threshold!"
            exit 1
          fi
          echo "✅ Coverage ${COVERAGE}% meets threshold"

3.4 Лаб 3: Docker Image бүтээх Pipeline

Алхам 1: Dockerfile үүсгэх

# Dockerfile
# Multi-stage build — Жижиг image
FROM eclipse-temurin:17-jdk-alpine AS builder
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN apk add --no-cache maven && mvn clean package -DskipTests

FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

Алхам 2: Docker Build Pipeline нэмэх

# .github/workflows/ci.yml дотор шинэ job нэмэх
  docker:
    name: Build Docker Image
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build and Push Docker Image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ${{ secrets.DOCKER_USERNAME }}/student-api:latest
            ${{ secrets.DOCKER_USERNAME }}/student-api:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Алхам 3: Docker Compose (Локал тест)

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      SPRING_PROFILES_ACTIVE: dev
      SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/studentdb
      SPRING_DATASOURCE_USERNAME: admin
      SPRING_DATASOURCE_PASSWORD: secret
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:15-alpine
    ports:
      - "5432:5432"
    environment:
      POSTGRES_DB: studentdb
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: secret
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U admin -d studentdb"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  postgres_data:
# Ажиллуулах
docker-compose up -d

# Шалгах
curl http://localhost:8080/actuator/health

# Зогсоох
docker-compose down

3.5 Лаб 4: Multi-Environment Deploy Pipeline

Бүрэн CI/CD Workflow

# .github/workflows/deploy.yml
name: Deploy Pipeline

on:
  push:
    branches: [ main ]

jobs:
  # ──── 1. Build + Test ────
  build:
    name: Build & Test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'
      - name: Build & Test
        run: mvn clean verify
      - uses: actions/upload-artifact@v4
        with:
          name: app-jar
          path: target/*.jar

  # ──── 2. Docker Build ────
  docker:
    name: Docker Build & Push
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
      - uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ secrets.DOCKER_USERNAME }}/student-api:${{ github.sha }}

  # ──── 3. Deploy to Staging ────
  deploy-staging:
    name: Deploy to Staging
    needs: docker
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - name: Deploy to Staging Server
        run: |
          echo "Deploying to Staging..."
          echo "Image: student-api:${{ github.sha }}"
          # kubectl set image deployment/student-api \
          #   student-api=${{ secrets.DOCKER_USERNAME }}/student-api:${{ github.sha }} \
          #   --namespace=staging

  # ──── 4. Deploy to Production (Manual Approval) ────
  deploy-production:
    name: Deploy to Production
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Deploy to Production Server
        run: |
          echo "Deploying to Production..."
          echo "Image: student-api:${{ github.sha }}"
          # kubectl set image deployment/student-api \
          #   student-api=${{ secrets.DOCKER_USERNAME }}/student-api:${{ github.sha }} \
          #   --namespace=production

Pipeline визуал:

Push to main
    │
    ▼
┌──────────┐    ┌──────────┐    ┌──────────────┐    ┌──────────────────┐
│  Build   │───►│  Docker  │───►│   Staging    │───►│   Production     │
│  & Test  │    │  Build   │    │   Deploy     │    │   Deploy         │
│  (auto)  │    │  (auto)  │    │   (auto)     │    │   (manual ✋)    │
└──────────┘    └──────────┘    └──────────────┘    └──────────────────┘
   mvn verify    docker push     kubectl apply       Approve → Deploy