Github Action + EC2(ubuntu) + Nginx + Docker + JAR 무중단 배포 구현하기

jkky98·2025년 3월 4일
0

ProjectSpring

목록 보기
15/20
post-thumbnail

무중단 배포 자동화 프로세스

프로세스 설명

Job1 :: Build

  1. main 브랜치에 푸시 이벤트가 발생한다.
  2. github action으로 작성한 CICD.yml이 푸시 이벤트를 감지하여 Job1, Job2를 실행한다.
  3. main 브랜치의 내용을 빌드하기 위해 ubuntu-latest(ec2 ubuntu와 os를 맞춰주자) 가상 머신을 만들어 가상 머신에 main 브랜치 레포지토리를 체크아웃 한다.
  4. 가상 머신에 JDK 17을 설치한다.
  5. Gradle Build(빌드 툴로 Gradle 사용)를 하여 JAR파일을 뽑아낸다
  6. 도커 허브에 로그인 하고, 레포에 존재(프로젝트 루트 경로)하는 Dockerfile로 하여금 JAR파일을 도커 이미지 빌드를 한다.
  7. 도커 허브에 완성된 도커 이미지 파일을 PUSH 한다.

Job2 :: Deploy

  1. 서버IP/env를 확인해서 서버가 살아있는지 확인한다.
  2. docker pull시 이미지가 쌓이는데, 이를 방치하면 용량이 초과될 수 있으니 쓰지 않는 도커 이미지를 삭제한다.
  3. 도커 허브에서 이미지를 PULL 받고 미리 셋팅해둔 GITHUB SECRETS의 환경변수들을 전달받아 docker compose up을 수행한다. -> 도커 이미지로 하여금 컨테이너가 RUN 된다.
  4. Nginx가 바라보는 방향을 3.에서 띄운 컨테이너 포트로 설정해준다.
  5. 기존 컨테이너(3.과정 이전에 실행 컨테이너가 존재한다면)가 존재한다면 Stop 시킨다.

해야할 일

EC2 ubuntu

  1. Docker 다운로드
  2. docker Nginx이미지 받아서(오픈소스) Nginx 실행.
  3. docker-compose-blue.yml, docker-compose-green.yml 작성
  4. /etc/nginx/conf.d/service-env.inc으로 하여금 Nginx Change Upstream을 자동으로 바인딩 되도록 작성내용을 추가한다.

Github Secrets

EC2 원격 접속을 위한 key, 서버와 데이터베이스 연결을 위한 RDS username, pw 같은 것들을 업로드 해놓아야 한다.

Dockerfile

도커 파일(Dockerfile)을 프로젝트 루트 경로에 작성해야한다. 이때 Github secrets -> github action -> docker-compose로 이어져서 docker-compose가 컴포즈때 소지한 환경변수들을 받는 공간이 필요하다.

도커 파일-도커 이미지 프로세스는 공간만 마련해둔다. 이 공간에 환경변수를 집어넣는 일은 컴포즈 파일이 수행한다.

Dockerfile은 빈 집과 같다. 하지만 이 집에 들어올 사람의 부류는 명확하다. 예를 들어 무조건 음악방이 필요한 음악가가 들어오게 된다. 피아노나 기타 혹은 첼로를 보관하고 플레이할 수 있는 방을 확보해놓아야 한다.

이때 도커파일은 이 음악가용 하우스의 설계도가 되며, 도커 이미지는 하우스 껍데기 그자체가 된다. 도커 컴포즈 설정 파일을 통해 도커 이미지로 하여금 docker compose 명령을 수행하게 되면 어떤 음악가가 하우스를 계약하고 사용하는 단계로 해석할 수 있다.

application.yml

당연하게도 배포 환경에서 필요로하는 스프링부트 설정 파일을 구성해주어야 한다.
profile로 하여금 local, blue, green으로 구분할 수 있으며
blue, green은 배포용 설정으로,
local에는 로컬개발용 설정으로 설정파일을 구성해야한다.

FROM amazoncorretto:17-alpine
ARG JAR_FILE=build/libs/*.jar
ARG PROFILES
ARG ENV
ARG AWS_ACCESS_KEY_ID
ARG AWS_SECRET_ACCESS_KEY
ARG AWS_REGION
ARG S3_BUCKET_NAME
ARG RDS_ENDPOINT
ARG RDS_NAME
ARG RDS_PASSWORD
ARG RDS_USERNAME
ARG LIVE_SERVER_IP
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["sh", "-c", "java \
  -Dspring.profiles.active=${PROFILES} \
  -Dserver.env=${ENV} \
  -Dcloud.aws.credentials.access-key=${AWS_ACCESS_KEY_ID} \
  -Dcloud.aws.credentials.secret-key=${AWS_SECRET_ACCESS_KEY} \
  -Dcloud.aws.region.static=${AWS_REGION} \
  -Ds3.bucket=${S3_BUCKET_NAME} \
  -Dspring.datasource.url=jdbc:mysql://${RDS_ENDPOINT}:3306/${RDS_NAME} \
  -Dspring.datasource.username=${RDS_USERNAME} \
  -Dspring.datasource.password=${RDS_PASSWORD} \
  -jar app.jar"]

CICD.yml

솔직히 이 파일의 이름이 CICD라고 하는게 맞는지는 잘 모르겠다. 가장 정확한 표현은 무중단을 곁들인 배포 자동화라고 표현할 수 있을 것 같다.

name: CI

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set working directory
        working-directory: ./noteJ
        run: echo "Switched to ./noteJ"
      
      - name: Install JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Build with Gradle
        working-directory: ./noteJ
        run: |
          chmod +x gradlew
          ./gradlew clean build -x test

      - name: Login to DockerHub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build Docker
        run: docker build --platform linux/amd64 -t ${{ secrets.DOCKERHUB_USERNAME }}/live_server ./noteJ
      - name: Push Docker
        run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/live_server:latest
        
  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Set target IP
        run: |
          STATUS=$(curl -o /dev/null -w "%{http_code}" "http://${{ secrets.LIVE_SERVER_IP }}/env")
          echo $STATUS
          if [ $STATUS = 200 ]; then
            CURRENT_UPSTREAM=$(curl -s "http://${{ secrets.LIVE_SERVER_IP }}/env")
          else
            CURRENT_UPSTREAM=green
          fi
          echo CURRENT_UPSTREAM=$CURRENT_UPSTREAM >> $GITHUB_ENV
          if [ $CURRENT_UPSTREAM = blue ]; then
            echo "CURRENT_PORT=8080" >> $GITHUB_ENV
            echo "STOPPED_PORT=8081" >> $GITHUB_ENV
            echo "TARGET_UPSTREAM=green" >> $GITHUB_ENV
          else
            echo "CURRENT_PORT=8081" >> $GITHUB_ENV
            echo "STOPPED_PORT=8080" >> $GITHUB_ENV
            echo "TARGET_UPSTREAM=blue" >> $GITHUB_ENV
          fi
      - name: Remove unused Docker images
        uses: appleboy/ssh-action@master
        with:
          username: ubuntu
          host: ${{ secrets.LIVE_SERVER_IP }}
          key: ${{ secrets.EC2_SSH_KEY }}
          script_stop: true
          script: |
            echo "Removing unused Docker images..."
            sudo docker image prune -af
            echo "Unused Docker images removed."    
          
      - name: Docker compose
        uses: appleboy/ssh-action@master
        with:
          username: ubuntu
          host: ${{ secrets.LIVE_SERVER_IP }}
          key: ${{ secrets.EC2_SSH_KEY }}
          script_stop: true
          script: |
            sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/live_server:latest

            # 환경 변수를 직접 docker-compose 실행 시 전달
            sudo AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} \
                 AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} \
                 AWS_REGION=${{ secrets.AWS_REGION }} \
                 S3_BUCKET_NAME=${{ secrets.S3_BUCKET_NAME }} \
                 RDS_ENDPOINT=${{ secrets.RDS_ENDPOINT }} \
                 RDS_NAME=${{ secrets.RDS_NAME }} \
                 RDS_PASSWORD=${{ secrets.RDS_PASSWORD }} \
                 RDS_USERNAME=${{ secrets.RDS_USERNAME }} \
                 LIVE_SERVER_IP=${{ secrets.LIVE_SERVER_IP }} \
                 docker-compose -f docker-compose-${{ env.TARGET_UPSTREAM }}.yml up -d    
      
      - name: Check deploy server URL
        uses: jtalk/url-health-check-action@v3
        with:
          url: http://${{ secrets.LIVE_SERVER_IP }}:${{env.STOPPED_PORT}}/env
          max-attempts: 5
          retry-delay: 12s

      - name: Change nginx upstream
        uses: appleboy/ssh-action@master
        with:
          username: ubuntu
          host: ${{ secrets.LIVE_SERVER_IP }}
          key: ${{ secrets.EC2_SSH_KEY }}
          script_stop: true
          script: |
            sudo docker exec -i nginxserver bash -c 'echo "set \$service_url ${{ env.TARGET_UPSTREAM }};" > /etc/nginx/conf.d/service-env.inc && nginx -s reload' 

      - name: Stop current server
        uses: appleboy/ssh-action@master
        with:
          username: ubuntu
          host: ${{ secrets.LIVE_SERVER_IP }}
          key: ${{ secrets.EC2_SSH_KEY }}
          script_stop: true
          script: |
            sudo docker stop ${{env.CURRENT_UPSTREAM}}
            sudo docker rm ${{env.CURRENT_UPSTREAM}}

스크립트 설명

Job1 :: Build

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

permissions:
  contents: read
  • on : main 브랜치의 push, pull request시에 워크 플로우가 실행된다.
  • permissions : 워크플로우가 리포지토리에 대해 읽기 만 가능하다.

가상 머신 설정

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set working directory
        working-directory: ./noteJ
        run: echo "Switched to ./noteJ"
  1. runs-on: ubuntu-latest

    • 이 Job은 GitHub에서 제공하는 최신 Ubuntu 가상 머신 환경에서 실행된다.
  2. steps

    • actions/checkout@v3:
      리포지토리의 코드를 가상 머신에 체크아웃해서 이후 단계들이 해당 소스코드에 접근할 수 있다.
    • Set working directory:
      • working-directory: ./noteJ 설정으로 이 스텝은 ./noteJ 디렉토리에서 실행된다.
      • run: echo "Switched to ./noteJ" 명령은 단순히 디렉토리 변경이 제대로 적용되었는지 확인하기 위한 로깅이다.

JDK 다운로드

- name: Install JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

이 스텝은 GitHub Actions에서 제공하는 actions/setup-java@v3 액션을 사용해 빌드 머신에 JDK 17을 설치하는 작업이다.

  • java-version: '17'
    Java 17 설치

  • distribution: 'temurin'
    Temurin은 오픈 소스 JDK 배포판으로, 안정적이고 널리 사용되는 배포판이므로 선택

Gradle 빌드

- name: Build with Gradle
        working-directory: ./noteJ
        run: |
          chmod +x gradlew
          ./gradlew clean build -x test

이 스텝은 Gradle을 사용해 spring 애플리케이션을 빌드하는 작업이다.

  • 작업 디렉토리:
    working-directory: ./noteJ로 지정되어, ./noteJ 폴더에서 명령어들이 실행된다.

  • 실행 권한 부여:
    chmod +x gradlew 명령어로 Gradle Wrapper 파일인 gradlew에 실행 권한을 부여한다.

  • Gradle 빌드 실행:
    ./gradlew clean build -x test 명령어로 다음 작업을 수행한다.

    • clean: 이전 빌드 산출물을 제거하여 깨끗한 상태에서 빌드를 시작
    • build: 애플리케이션을 빌드
    • -x test: 테스트 단계를 건너뛰어 빌드 시간을 단축

DockerHub 로그인 -> 도커 이미지 빌드 -> 도커 허브에 이미지 푸시

      - name: Login to DockerHub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build Docker
        run: docker build --platform linux/amd64 -t ${{ secrets.DOCKERHUB_USERNAME }}/live_server ./noteJ
      - name: Push Docker
        run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/live_server:latest
  1. Login to DockerHub

    • 목적: DockerHub에 인증된 상태로 접근할 수 있도록 로그인
    • 동작:
      • docker/login-action@v1 액션을 사용하여, GitHub Secrets에 저장된 DOCKERHUB_USERNAMEDOCKERHUB_TOKEN을 통해 로그인
  2. Build Docker

    • 목적: 애플리케이션의 Docker 이미지를 생성
    • 동작:
      • docker build 명령어를 사용해 ./noteJ 디렉토리에 있는 Dockerfile을 기반으로 이미지를 빌드
      • --platform linux/amd64 옵션은 이미지가 Linux의 amd64 아키텍처용으로 빌드되도록 보장
      • 이미지 태그는 ${{ secrets.DOCKERHUB_USERNAME }}/live_server로 지정되어, DockerHub 저장소와 연동
  3. Push Docker

    • 목적: 빌드된 Docker 이미지를 DockerHub에 업로드
    • 동작:
      • docker push 명령어를 사용해 앞서 빌드한 이미지를 latest 태그로 DockerHub에 푸시

이 과정을 통해 애플리케이션의 최신 Docker 이미지가 DockerHub에 저장되며, 이후 배포 단계에서 이 이미지를 기반으로 컨테이너를 실행한다.

Job2 :: Deploy

이 부분은 GitHub Actions 워크플로우의 또 다른 Job인 deploy를 정의하는 부분이다.

  • needs: build
    deploy Job은 build Job이 완료되어야 실행된다. 즉, 빌드가 성공적으로 끝난 후 배포 작업이 진행된다.

  • runs-on: ubuntu-latest
    deploy Job 역시 GitHub Actions가 제공하는 최신 Ubuntu 가상 머신 환경에서 실행된다.

    이 머신은 빌드용 임시 환경과 동일하게 워크플로우 실행 후 종료된다.

Set target IP(배포 대상 서버 현재 상태 확인, 환경 전환 결정)

- name: Set target IP
        run: |
          STATUS=$(curl -o /dev/null -w "%{http_code}" "http://${{ secrets.LIVE_SERVER_IP }}/env")
          echo $STATUS
          if [ $STATUS = 200 ]; then
            CURRENT_UPSTREAM=$(curl -s "http://${{ secrets.LIVE_SERVER_IP }}/env")
          else
            CURRENT_UPSTREAM=green
          fi
          echo CURRENT_UPSTREAM=$CURRENT_UPSTREAM >> $GITHUB_ENV
          if [ $CURRENT_UPSTREAM = blue ]; then
            echo "CURRENT_PORT=8080" >> $GITHUB_ENV
            echo "STOPPED_PORT=8081" >> $GITHUB_ENV
            echo "TARGET_UPSTREAM=green" >> $GITHUB_ENV
          else
            echo "CURRENT_PORT=8081" >> $GITHUB_ENV
            echo "STOPPED_PORT=8080" >> $GITHUB_ENV
            echo "TARGET_UPSTREAM=blue" >> $GITHUB_ENV
          fi

이 스텝은 배포할 대상 서버의 현재 상태를 확인하고, Blue-Green 배포를 위해 어떤 환경(blue 또는 green)으로 전환할지 결정하는 작업이다.

  1. 현재 서버 상태 확인:
    • curl 명령어를 사용하여 http://${{ secrets.LIVE_SERVER_IP }}/env URL에 요청을 보내고, HTTP 상태 코드(예: 200)를 STATUS 변수에 저장한다.
    • 상태 코드가 200이면 서버가 정상 응답 중이므로, 같은 URL로 실제 환경(예: blue 또는 green) 정보를 가져와 CURRENT_UPSTREAM에 저장한다.
    • 응답이 없거나 상태 코드가 200이 아니면 CURRENT_UPSTREAMgreen으로 설정한다. (응답이 없다면 현재 실행되고 있는 서버가 없으니 무중단 로직을 위한 전환 로직이 아닌 단순히 그냥 서버를 켜야한다.)

응답이 없을 경우 green으로 설정하는 이유

CURRENT_UPSTREAM은 현재 어떤 서버가 켜져있는지에 대한 정보이다.
bluegreen도 실행되지 않은 상태에서는 우리는 blue를 처음으로 배포하게 된다.
이때 green을 현재 서버로 둘 경우 이후 프로세스에서 다음 서버는 자동적으로 blue가 되므로 green으로 설정해서 blue가 띄워질 수 있게 만드는 것이다.
즉, 아무것도 없을 경우 green이 허상으로 떠있도록 논리적인 상황을 만들어 그다음 blue가 실행되도록 하여 아무것도 없는 상황과, green or blue가 떠있는 상황을 모두 다음 프로세스에서 하나의 논리로 처리할 수 있게 된다.

  1. 환경 변수 설정:

    • CURRENT_UPSTREAM 값을 GitHub Actions의 환경 변수 파일에 기록하여 이후 스텝에서 사용할 수 있도록 한다.
  2. 포트 및 타겟 환경 결정:

    • 만약 CURRENT_UPSTREAM이 blue라면:
      • 현재 서비스 포트(CURRENT_PORT)는 8080, 중지할 포트(STOPPED_PORT)는 8081로 설정하고, 새로 배포할 대상 환경(TARGET_UPSTREAM)을 green으로 지정한다.
    • 그렇지 않다면(즉, green이 현재 활성 상태이면):
      • 현재 서비스 포트는 8081, 중지할 포트는 8080으로 설정하고, 새 배포 대상 환경을 blue로 지정한다.

이 과정을 통해 현재 활성화된 환경과 비활성 환경을 구분하여, 새로운 버전을 배포할 때 서비스 중단 없이 전환할 수 있도록 준비한다.

Remove unused Docker images

- name: Remove unused Docker images
        uses: appleboy/ssh-action@master
        with:
          username: ubuntu
          host: ${{ secrets.LIVE_SERVER_IP }}
          key: ${{ secrets.EC2_SSH_KEY }}
          script_stop: true
          script: |
            echo "Removing unused Docker images..."
            sudo docker image prune -af
            echo "Unused Docker images removed."  

이 스텝은 원격 서버에 SSH로 접속하여 사용하지 않는 Docker 이미지를 정리하는 작업을 수행한다.

  • SSH 접속:

    • appleboy/ssh-action@master 액션을 사용해, 지정된 사용자(ubuntu), 서버 주소(LIVE_SERVER_IP), 그리고 SSH 키(EC2_SSH_KEY)로 원격 서버에 접속한다.
  • 스크립트 실행:

    • 스크립트 내에서는 먼저 "Removing unused Docker images..."라는 메시지를 출력한 후,
    • sudo docker image prune -af 명령어를 실행
      • 이 명령어는 사용되지 않는 모든 Docker 이미지를 강제로 삭제하여 디스크 공간을 확보
    • 작업 완료 후 "Unused Docker images removed."라는 메시지를 출력하여 작업이 종료되었음을 알린다.
  • script_stop: true:

    • 명령어 실행 중 문제가 발생할 경우, 스크립트를 즉시 중단하도록 설정

docker compose

      - name: Docker compose
        uses: appleboy/ssh-action@master
        with:
          username: ubuntu
          host: ${{ secrets.LIVE_SERVER_IP }}
          key: ${{ secrets.EC2_SSH_KEY }}
          script_stop: true
          script: |
            sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/live_server:latest

            # 환경 변수를 직접 docker-compose 실행 시 전달
            sudo AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} \
                 AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} \
                 AWS_REGION=${{ secrets.AWS_REGION }} \
                 S3_BUCKET_NAME=${{ secrets.S3_BUCKET_NAME }} \
                 RDS_ENDPOINT=${{ secrets.RDS_ENDPOINT }} \
                 RDS_NAME=${{ secrets.RDS_NAME }} \
                 RDS_PASSWORD=${{ secrets.RDS_PASSWORD }} \
                 RDS_USERNAME=${{ secrets.RDS_USERNAME }} \
                 LIVE_SERVER_IP=${{ secrets.LIVE_SERVER_IP }} \
                 docker-compose -f docker-compose-${{ env.TARGET_UPSTREAM }}.yml up -d    
      

이 스텝은 원격 서버에 SSH로 접속해 Docker 관련 배포 작업을 수행한다.

  1. SSH 접속 및 Docker 이미지 업데이트

    • appleboy/ssh-action 액션을 사용해, 서버에 SSH로 접속.
    • 먼저, sudo docker pull 명령어로 DockerHub에 있는 최신 live_server 이미지를 가져와 서버의 이미지를 업데이트
  2. 환경 변수와 함께 docker-compose 실행

    • 이어서 여러 환경 변수(예: AWS, RDS, S3 관련 변수)를 커맨드 앞에 직접 설정
    • 그 후, docker-compose 명령어를 실행하는데, 사용하는 Compose 파일은 docker-compose-${{ env.TARGET_UPSTREAM }}.yml로, TARGET_UPSTREAM 값(예: blue 또는 green)에 따라 선택
    • up -d 옵션은 컨테이너를 백그라운드에서 실행

새로운 이미지를 기반으로 설정된 환경 변수와 함께 docker-compose를 실행하여, 배포 대상 컨테이너를 기동한다.

Check deploy server

      - name: Check deploy server URL
        uses: jtalk/url-health-check-action@v3
        with:
          url: http://${{ secrets.LIVE_SERVER_IP }}:${{env.STOPPED_PORT}}/env
          max-attempts: 5
          retry-delay: 12s

이 스텝은 배포된 서버의 상태를 확인하기 위해 헬스 체크를 수행하는 역할을 한다.

  • 헬스 체크 액션 사용:

    • jtalk/url-health-check-action@v3 액션을 사용해 지정된 URL에 접속해 응답 상태를 확인한다.
  • URL:

    • URL은 http://${{ secrets.LIVE_SERVER_IP }}:${{env.STOPPED_PORT}}/env로, LIVE_SERVER_IP와 STOPPED_PORT 변수에 따라 결정된다.(STOPPED_PORT가 이 스크립트에서 최종적으로 실행될 컨테이너의 포트이다.)
    • 이 URL은 배포 대상 서버의 특정 환경 상태를 나타내는 엔드포인트이다.
  • 시도 횟수와 재시도 간격:

    • max-attempts: 5로 설정되어 있어, 최대 5번까지 헬스 체크를 시도
    • 각 시도 사이에는 retry-delay: 12s로 12초의 간격을 두고 재시도를 수행

즉, 이 스텝은 새로 배포된 서버가 정상적으로 기동하여 /env 엔드포인트가 올바른 응답을 반환하는지 확인하는 과정이다.

Change Nginx Upstream

- name: Change nginx upstream
        uses: appleboy/ssh-action@master
        with:
          username: ubuntu
          host: ${{ secrets.LIVE_SERVER_IP }}
          key: ${{ secrets.EC2_SSH_KEY }}
          script_stop: true
          script: |
            sudo docker exec -i nginxserver bash -c 'echo "set \$service_url ${{ env.TARGET_UPSTREAM }};" > /etc/nginx/conf.d/service-env.inc && nginx -s reload' 

이 스텝은 원격 서버의 nginx 설정을 업데이트하여 트래픽이 새 배포 대상으로 전환되도록 합니다. 즉 nginx가 바라보는 방향을 방금 띄운 서버(STOPPED_PORT)로 전환해야한다.

  • SSH 접속:
    • appleboy/ssh-action을 사용해, username ubuntu, 서버 IP(LIVE_SERVER_IP), 그리고 SSH 키(EC2_SSH_KEY)로 원격 서버에 접속한다.
  • Docker 내 Nginx 컨테이너 실행:
    • sudo docker exec -i nginxserver bash -c '... ' 명령을 통해, 이름이 nginxserver인 Docker 컨테이너 내부에서 bash 명령어를 실행한다.
  • nginx 설정 파일 수정:
    • echo "set \$service_url ${{ env.TARGET_UPSTREAM }};" > /etc/nginx/conf.d/service-env.inc
      • 이 명령은 service-env.inc 파일에 set $service_url [TARGET_UPSTREAM 값]; 라는 내용을 기록한다.
      • 여기서 $TARGET_UPSTREAM은 배포 과정에서 결정된 새 환경(예: blue 또는 green)을 나타내며, nginx가 해당 값을 사용해 업스트림 서버를 결정한다.
  • nginx 재로드:
    • nginx -s reload 명령어로 nginx를 재시작 없이 설정을 재로드하여, 새로운 업스트림 설정이 즉시 반영되도록 한다.

Stop Current Server

      - name: Stop current server
        uses: appleboy/ssh-action@master
        with:
          username: ubuntu
          host: ${{ secrets.LIVE_SERVER_IP }}
          key: ${{ secrets.EC2_SSH_KEY }}
          script_stop: true
          script: |
            sudo docker stop ${{env.CURRENT_UPSTREAM}}
            sudo docker rm ${{env.CURRENT_UPSTREAM}}


이 시점에 blue 서버와 green 서버가 동시에 떠있다.

혹은 첫 배포라 green이 논리적으로만 떠있을 수도 있다.

어쨋든 green을 꺼줘야 한다. 만약 첫 배포라면 이 step에서 실패할 것이다.(첫 배포만 그냥 넘어가도록 하자. 스크립트를 개선한다면 이 또한 감지할 수 있을 것이다.)

이 스텝은 배포 후 기존에 실행 중이던 Docker 컨테이너(현재 활성화된 서버)를 종료하고 삭제하는 역할을 한다.

  • SSH로 원격 서버 접속:
    • appleboy/ssh-action@master 액션을 사용해, ubuntu 사용자로 지정된 라이브 서버(LIVE_SERVER_IP)에 SSH로 접속한다.
  • 스크립트 실행:
    • sudo docker stop ${{env.CURRENT_UPSTREAM}}
      • 환경 변수 CURRENT_UPSTREAM에 저장된 컨테이너(현재 활성화된 서버)를 중지
    • sudo docker rm ${{env.CURRENT_UPSTREAM}}
      • 중지된 컨테이너를 Docker 시스템에서 제거하여, 리소스를 해제하고 향후 충돌을 방지한다.

이 과정은 Blue-Green 배포 방식에서 새 서버로 트래픽 전환 후, 기존 서버를 안전하게 정리하기 위해 수행된다.

profile
자바집사의 거북이 수련법

0개의 댓글

관련 채용 정보