새소식

Docker

Docker - Docker + Spring Boot + Github Action CICD

  • -

개요

github push 이벤트 발생 시 github action을 사용해 Docker에 띄워놓은 spring boot 컨테이너에 이미지를 빌드하여 CICD 환경을 구축하는 작업


준비물

  1. 도메인으로 ssh 접속이 가능하고 docker, docker-compose가 정상 설치된 서버
  2. docker hub 계정
  3. spring boot 프로젝트 repository

서버 디렉토리 구조

~.ssh/
├── project
│   └── docker-compose.yml
│   └── nginx
│         └── default.conf
└── authorized_keys

~.ssh/project/docker-compose.yml 파일 작성

version: '3'  # Docker Compose의 버전 지정

services:  # 서비스 정의 시작
  nginx:  # nginx 서비스 정의
    image: nginx:latest  # 사용할 이미지 지정 (최신 버전)
    volumes:  # 볼륨 설정 - 호스트의 nginx/default.conf 파일을 컨테이너 내의 /etc/nginx/nginx.conf 파일로 마운트
      - ./nginx/default.conf:/etc/nginx/nginx.conf
    restart: always  # 컨테이너가 비정상적으로 종료될 경우 항상 재시작
    ports:  # 포트 포워딩 설정 - 호스트의 80 포트와 443 포트를 컨테이너의 80 포트와 443 포트로 매핑
      - 80:80
      - 443:443
    networks:  # 네트워크 설정 - webnet 네트워크에 연결
      - webnet

  spring:  # spring 서비스 정의
    container_name: 프로젝트명  # 컨테이너 이름 설정
    image: 도커허브사용자명/프로젝트명:latest  # 사용할 이미지 지정 (도커 허브에 등록된 이미지)
    ports:  # 포트 포워딩 설정 - 호스트의 8080 포트를 컨테이너의 8080 포트로 매핑
      - "8080:8080"
    networks:  # 네트워크 설정 - webnet 네트워크에 연결
      - webnet

networks:  # 네트워크 정의 시작
  webnet:  # webnet 네트워크 정의
    # 서비스 간의 커뮤니케이션을 위한 도커 내장 네트워크, 별도 설정 없이 기본 값으로 사용

!!!!!!!!!주의!!!!!!!!! - yml 코드 내 도커허브사용자명, 프로젝트명 부분 꼭 수정할 것


~.ssh/project/nginx/default.conf 작성

events {
    worker_connections  1024;
}


http {
    server {
        listen 80; # 80번 포트에서 요청 수신

        server_name 도메인;

        location / {
         proxy_pass http://프로젝트명:8080; # 요청을 Spring 서비스로 프록시
         proxy_set_header Host $host; # 요청의 호스트 헤더 설정
         proxy_set_header X-Real-IP $remote_addr; # 클라이언트의 실제 IP 설정
        }
    }
}

!!!!!!!!!주의!!!!!!!!! - yml 코드 내 도메인, 프로젝트명 부분 꼭 수정할 것


Github Action workflow 등록

name: CI/CD Pipeline  # GitHub Actions 워크플로우의 이름

on:
  push:  # master 브랜치로의 푸시 이벤트 발생 시 워크플로우 실행
    branches:
      - master
  pull_request:  # master 브랜치로의 풀 리퀘스트 이벤트 발생 시 워크플로우 실행
    branches:
      - master

jobs:
  build:  # build라는 이름의 job 정의
    runs-on: ubuntu-latest  # 최신 버전의 Ubuntu에서 실행

    steps:  # job을 구성하는 단계들 정의
      - name: Checkout repository  # Step 1: 코드 저장소 체크아웃
        uses: actions/checkout@v3  # 최신 버전(v3)의 checkout 액션 사용

      - name: JDK 21 설정  # Step 2: JDK 21 설정
        uses: actions/setup-java@v4  # 버전 4의 setup-java 액션 사용
        with:
          java-version: '21'  # JDK 21 버전 지정
          distribution: 'temurin'  # Temurin 배포판 사용
          java-package: 'jdk'  # JDK 패키지 설치
          check-latest: false  # 최신 버전 확인 안 함
          server-id: 'github'  # GitHub 서버 ID 설정
          server-username: ${{ github.actor }}  # GitHub 액터의 사용자 이름 사용
          server-password: ${{ secrets.GITHUB_TOKEN }}  # GitHub 토큰 사용
          overwrite-settings: true  # 기존 설정 덮어쓰기

      - name: Cache Gradle packages  # Step 3: Gradle 패키지 캐시
        uses: actions/cache@v3  # 최신 버전(v3)의 캐싱 액션 사용
        with:
          path: ~/.gradle  # Gradle 패키지의 캐시 경로
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}  # Gradle 파일의 해시를 기반으로한 캐시 키
          restore-keys: ${{ runner.os }}-gradle  # 캐시 복원 키

      - name: Build with Gradle  # Step 4: Gradle로 프로젝트 빌드
        run: ./gradlew clean build  # Gradle의 clean 및 build 명령 실행

      - name: Login to Docker Hub  # Step 5: Docker Hub 로그인
        run: docker login -u "${{ secrets.DOCKER_USERNAME }}" -p "${{ secrets.DOCKER_PASSWORD }}"  # GitHub Secrets에서 저장한 Docker 자격 증명 사용

      - name: Build Docker image  # Step 6: 어플리케이션용 Docker 이미지 빌드
        run: |
          docker build . -t ${{ secrets.DOCKER_USERNAME }}/프로젝트명:${{ github.sha }}  # GitHub SHA로 태그한 Docker 이미지 빌드
          docker tag ${{ secrets.DOCKER_USERNAME }}/프로젝트명:${{ github.sha }} ${{ secrets.DOCKER_USERNAME }}/프로젝트명:latest  # Docker 이미지를 'latest'로 태그

      - name: Push Docker image to Docker Hub  # Step 7: Docker 이미지를 Docker Hub에 푸시
        run: |
          docker push ${{ secrets.DOCKER_USERNAME }}/프로젝트명:${{ github.sha }}  # GitHub SHA로 태그된 Docker 이미지 푸시
          docker push ${{ secrets.DOCKER_USERNAME }}/프로젝트명:latest  # 'latest' Docker 이미지 푸시

      - name: Deploy to server  # Step 8: 서버에 어플리케이션 배포
        uses: appleboy/ssh-action@v1.0.3  # SSH 액션(v1.0.3)을 사용하여 SSH로 배포
        with:
          host: ${{ secrets.DEPLOY_SERVER }}  # GitHub Secrets에서 저장한 서버 호스트명 또는 IP 주소 사용
          username: ${{ secrets.SSH_USER }}  # GitHub Secrets에서 저장한 SSH 사용자 이름 사용
          key: ${{ secrets.SSH_PRIVATE_KEY }}  # GitHub Secrets에서 저장한 SSH 개인 키 사용
          port: 22  # SSH 포트
          script: |
            sudo docker pull ${{ secrets.DOCKER_USERNAME }}/프로젝트명:${{ github.sha }}  # 특정 태그의 Docker 이미지 풀
            sudo docker stop 프로젝트명 || true  # 실행 중인 Docker 컨테이너 중지 (오류 무시)
            sudo docker rm 프로젝트명 || true  # Docker 컨테이너 삭제 (오류 무시)
            sudo docker run -d -p 8080:8080 --network 프로젝트명_webnet --name 프로젝트명 ${{ secrets.DOCKER_USERNAME }}/프로젝트명:${{ github.sha }}  # 지정된 옵션으로 Docker 컨테이너 실행

!!!!!!!!!주의!!!!!!!!! - yml 코드 내 프로젝트명 부분 꼭 수정할 것


Github Repository Secrets 등록

repository -> Settings -> Secrets and Variables -> Actions

등록할 Secrets 목록

1. DEPLOY_SERVER - 서버 주소(ip, 도메인 중 택 1)
2. SSH_USER - SSH 사용자명
3. DOCKER_USERNAME - docker hub 유저명
4. DOCKER_PASSWORD - docker hub 비밀번호
5. SSH_PRIVATE_KEY - 서버 SSH 접속 시 사용하는 private key

서버 authorized_keys 설정

cd ~.ssh
vi authorized_keys ## 서버 SSH 접속 시 사용하는 public key 복붙 후 저장
chmod 600 authorized_keys

실행 및 마무리

위의 세팅을 마치고 ~.ssh/project 경로에서 docker compose up 명령어를 사용하면 spring 이미지 빌드 시 에러가 발생할 겁니다. 우선 git push 이벤트를 발생시켜 docker hub에 도커허브사용자명/프로젝트명:latest 이미지를 push 하고 그 후에 docker compose up 명령어를 사용해보세요. 물론 그렇게 하면 또 docker 에러가 발생할텐데 도커 컨테이너명이 겹쳐서 발생하는 오류니 이케이케 해결하시면 됩니다.
해결 하셨다면 docker compose up 명령어를 사용해보세요. 에러가 발생한다구요? 오타 나신거 아니에요? 브라우저에서 접속이 안 된다구요? 포트 안 열어놓은거 아니세요? 하하 화이팅입니다.
여차저차해서 nginx 컨테이너와 spring 컨테이너를 띄워 서버가 잘 돌아간다면 그 이후로는 master 브런치로 push 이벤트 발생 시 잘 빌드가 될 거예요.

간단 실행 순서

1. git push 이벤트 발생 시키기
2. 서버 ~.ssh/project 경로 접속
3. docker compose down 프로젝트명
4. docker rm 프로젝트명
5. docker compose up
6. git push 이벤트 발생 시키기 (cicd 최종 테스트)

반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.