Spring Boot Docker-Compose 무중단 배포

Jaeyoung·2023년 5월 11일
1
post-thumbnail

프로젝트가 거의 완성되가고 있어서 AWS EC2에 어플리케이션을 배포하게 되었는데 배포할때 마다 서비스가 계속 중단되는 것을 방지하기 위해 NginX 웹서버에 있는 로드밸런서 기능을 이용해서 무중단 배포를 구현해보려고합니다. EC2에 어플리케이션 배포부터 하나씩 정리해도록 하겠습니다.

AWS EC2

보통 어플리케이션을 배포 하기위해 EC2를 많이들 사용하는데요 EC2는 AWS에서 제공하는 클라우드 컴퓨팅 서비스 중 하나로 Elastic Compute Cloud의 약어입니다. 이 서비스를 사용하면 클라우드에서 가상 컴퓨터를 호스팅하고 실행할 수 있기 때문에 어플리케이션 또한 실행할 수 있기 때문에 많이 사용합니다.

EC2 인스턴스 생성

  1. https://aws.amazon.com/ko/ec2/ 사이트에 접속해서 EC2 시작하기를 누릅니다
  2. 대시보드로 이동이 완료되었다면 인스턴스 시작을 누릅니다.
  3. 이름을 설정해줍니다.
  4. 어플리케이션 및 OS 이미지는 저 같은 경우는 ubuntu로 설정해주었습니다. 경우에 따라 원하시는 OS를 설정하시면 될것같습니다.
  5. 인스턴스 유형은 프리 티어를 이용하기 위해 t2.micro를 설정해줍니다.
  6. 그 다음 키페어를 생성해줘야하는데 나중에 이걸 통해서 ssh 접속을 진행할 수 있습니다. 키 페어 유형은 RSA를 선택하고 파일 형식은 Window인 경우 .ppk 다른 ubuntu나 mac인 경우 .pem을 선택해주시면 됩니다.(저장된 키 페어 파일은 유실되지 않게 조심히 보관하도록 합시다)
  7. 이제 네트워크 설정을 해줘야하는데 ssh 트래픽은 내 IP로 설정하도록 합니다. 이름이나 설명에 대해 수정하시려면 편집 버튼을 눌러 변경하시면 됩니다.
  8. 스토리지 구성은 프리 티어인 경우 최대 30GB까지 허용되니 30으로 설정해주고 gp2를 사용해주도록 합시다
  9. 인스턴스 시작을 눌러줍시다.

EC2 SSH 연결

EC2 인스턴스를 이용하기 위해 ssh 연결을 해보도록 하겠습니다.

  1. ~/.ssh 폴더로 이동 하고 아까 생성했던 key pair 파일을 해당 폴더로 이동 시켜줍니다.

    $ cd ~/.ssh
    $ cp (키파일 경로) ./
  2. 그 다음 .pem 파일에 읽기 권한을 설정합니다

    $ chmod 400 (키파일 경로)
  3. 그 다음 접속하기 위한 설정을 해주어야 하는데요 ssh user@ip -i pem경로 이런식으로 접속해야하는데 자주 접속할 것 이기 떄문에 좀 더 간단히 접속할 수 있도록 Config 파일을 생성하고 설정한 다음 간단하게 접속할 수 있도록 설정 해보겠습니다.

    Host 이름
            HostName IP주소
            User ubuntu(우분투인경우 ubuntu, linux 인경우 ec2-user로 설정)
            IdentityFile (키파일 경로)
  4. 이제는 ssh 명령어를 통해 Host 옆에 작성했던 이름을 통해 접속하도록 하겠습니다.

    $ ssh 이름
  5. EC2서버에 접속

EC2 기본 설정

프리 티어 환경에서의 EC2는 어플리케이션을 구동하기에 너무 적은 메모리를 가지고 있습니다. 이때 Swap이라는 파일을 통해 메모리를 더 늘려줄 수 있는 방법이 있습니다. Swap 파일은 컴퓨터의 실제 메모리, 즉 램의 가상 메모리 확장으로 사용되는 하드디스크 상의 한 공간입니다. 그렇기 때문에 더 많은 양의 램을 가지고 있는 것처럼 동작할 수 있습니다. 그래서 먼저 Swap 파일을 설정해주도록 하겠습니다.

Swap 파일 설정

  1. ec2에 접속합니다

  2. dd 명령아를 사용앟여 루트 파일 시스템에 Swap 파일을 생성합니다. 명령에서 bs는 블록 크기이고 count는 블록 수입니다. Swap 파일의 크기는 dd 명령의 블록 크기 옵션에 블록 수 옵션을 곱한 값입니다. Swap 파일에 메모리 할당 2GB(128M*16)로 할당해주도록 합시다.

    $ sudo dd if=/dev/zero of=/swapfile bs=128M count=16
  3. Swap 파일에 Read Write 권한을 설정해줍니다.

    sudo chmod 600 /swapfile
  4. Swap 영역을 설정해줍니다.

    $ sudo mkswap /swapfile
  5. Swap 공간에 Swap 파일을 추가하여 즉시 사용할 수 있도록 합니다.

    $ sudo swapon /swapfile
  6. 프로시저가 성공적인지 확인합니다.

    $ sudo swapon -s
  7. /etc/fstab 파일을 편집하여 부팅 시 Swap 파일을 시작합니다.

    $ sudo nano /etc/fstab
    //파일 끝에 다음 줄 추가
    $ /swapfile swap swap defaults 0 0

프로젝트 설정 및 빌드

먼저 Jar파일을 통해 배포를 해야하기 때문에 프로젝트를 Repository에서 가져와서 빌드한 후 Jar로 배포하기 위한 과정을 먼저 진행하도록 하겠습니다.

먼저 Repository에서 프로젝트를 가져오기 위해 Git을 통해 Repository를 clone 해주겠습니다.

$ git clone [repository]

그 다음 빌드하기 위해 자바를 설치해줘야하는데요 자기 프로젝트에 맞는 자바버전을 설치하도록 해줍시다. 저는 17버전을 사용중이기 때문에 17버전을 설치하겠습니다.

$ sudo apt update
$ sudo apt install openjdk-17-jdk

자바 설치가 완료되었다면 프로젝트 폴더에 들어가서 빌드를 해서 Jar 파일을 생성하도록 하겠습니다.

$ cd repository
$ ./gradlew build

도커 설치 및 도커 파일 생성

도커로 어플리케이션을 실행할 것이기 때문에 도커엔진하고 도커 컴포즈를 먼저 설치하겠습니다. 아래는 설치 명령어 입니다.

// 도커 엔진 설치
$ sudo apt-get update
$ sudo apt-get install ca-certificates curl gnupg
$ sudo install -m 0755 -d /etc/apt/keyrings
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
$ sudo chmod a+r /etc/apt/keyrings/docker.gpg
$ echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
// 도커 컴포즈 설치
$ sudo curl -L https://github.com/docker/compose/releases/download/1.27.0-rc2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose

도커파일

FROM amazoncorretto:17
ARG JAR_PATH=./build/libs
VOLUME ["/log"]
COPY ${JAR_PATH}/MindTravel-0.0.1-SNAPSHOT.jar app.jar
CMD ["java","-jar","app.jar"]
  • 먼저 jar를 실행시키기 위한 java를 설치해 줍니다.
  • 그리고 만약에 log 폴더와 같이 컨테이너가 종료되도 계속 유지 시켜야하는 폴더는 VOLUME으로 지정해 줍니다.
  • jar를 app.jar에 카피해주도록 합니다.
  • app.jar를 명령어를 통해 실행시켜 줍니다.

도커 컴포즈 파일

version: '3.9'
services:
  nginx:
    image: nginx
    restart: always
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    ports:
      - "80:80"

  mysql:
    image: mysql:latest
    restart: always
    environment:
      MYSQL_DATABASE: mind_travel
      MYSQL_ROOT_PASSWORD: 1234
    ports:
      - "3306:3306"
    volumes:
      - db_data:/var/lib/mysql

  redis:
    image: redis:alpine
    restart: always
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

  blue:
    build: .
    restart: always
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/mind_travel
      SERVER_PORT: 8080
    ports:
      - "8080:8080"
    depends_on:
      - mysql
      - redis

  green:
    build: .
    restart: always
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/mind_travel
      SERVER_PORT: 8081
    ports:
      - "8081:8081"
    depends_on:
      - mysql
      - redis

volumes:
  db_data:
  redis_data:

Blue Green 배포를 통해 배포를 할 것이기 때문에 blue, green 두개의 서비스를 추가하고 저같은 경우는 redis와 mysql, Spring Boot를 사용하기 때문에 위와같이 설정해 주었습니다. Green Blue 배포를 위한 nginx를 제외하고 이건 프로젝트마다 다르게 설정되기 떄문에 자신의 프로젝트에 맞게 설정해주도록 합시다.

💡 처음에 도커 컴포즈를 실행할 때 실제 호스트의 ./nginx.conf 파일과 /etc/nginx/nginx.conf 도커 컨테이너 파일을 볼륨 마운트 설정을 해주었는데 제 로컬 PC에서는 정상적으로 적용이 되었었는데 EC2 Ubuntu 환경에서는 반영이 안되는 문제가 있었습니다. 이러한 문제를 해결하기 위해 파일 권한도 수정해보고 했는데 결과적으로 User 설정을 해주어서 해결하게 되었습니다. EC2는 보안적인 문제로 Root 계정으로 접속되는게 아니기 때문에 User 설정을 통해 권한을 주어야 도커 컨테이너에서 호스트 파일에 접근이 가능해서 발생한 문제였습니다.

Nginx Config 파일들

#blue_nginx.conf
events{}
http{
    server {
        listen 80;
        server_name localhost;

        location /v1 {
            proxy_pass http://blue:8080;
        }
    }
}
#green_nginx.conf
events{}
http{
    server {
        listen 80;
        server_name localhost;

        location /v1 {
            proxy_pass http://green:8081;
        }
    }
}

나머지 설정은 서비스에 맞게 설정해주시면 됩니다.

배포 스크립트 파일

APPLICATION=어플리케이션 이름
echo $APPLICATION

#현재 어플리케이션이 실행중인지 IS_RUNNING 환경변수에 추가
IS_RUNNING=$(sudo docker compose ls | grep $APPLICATION | grep running | sed 's/.*/true/')

#만약 실행중이면 현재 실행중인 어플리케이션을 확인후 
#Blue가 실행중이면 Green을 실행시키고 Nginx가 Green에게 라우팅 하도록 설정후 Blue Down
#Green이 실행중이면 Blue을 실행시키고 Nginx가 Blue에게 라우팅 하도록 설정후 Green Down
if [ "$IS_RUNNING" = "true" ]
then
    echo $APPLICATION 실행중
		#Blue가 현재 실행중인지 체크 
    IS_BLUE_RUNNING=$(sudo docker-compose -p $APPLICATION ps | grep blue | grep Up | sed 's/.*/true/')
    echo $CUR_APPLICATION
		#만약 Blue가 실행중이라면
    if [ "$IS_BLUE_RUNNING" = "true" ]
    then
        CUR_APPLICATION="blue"
        DEPLOY_APPLICATION="green"
				#nginx.conf를 green_nginx.conf 변경
        sudo cp green_nginx.conf nginx.conf
		#Green이 실행중이라면
    else
        CUR_APPLICATION="green"
        DEPLOY_APPLICATION="blue"
				#nginx.conf를 blue_nginx.conf 변경
        sudo cp blue_nginx.conf nginx.conf
    fi

    echo 사용하지 않는 이미지 정리
    sudo docker rmi $(sudo docker images -f "dangling=true" -q)

    echo 배포시작 : $DEPLOY_APPLICATION
	# 해당 어플리케이션 이미지 캐시가 적용되기 때문에 새로 반영한 것을 적용하기 위해서 별도로 빌드
	sudo docker-compose build --no-cache $DEPLOY_APPLICATION
	# 배포할 어플리케이션만 실행하도록 설정
    sudo docker-compose -p $APPLICATION up -d --no-deps $DEPLOY_APPLICATION$DEPLOY_APPLICATION
		# 어플리케이션이 정상적으로 실행될때까지 기다리기 위해 Sleep 설정
		#이건 직접 어플리케이션 요청을 계속 보내면서 확인하는게 가장 좋은 방법 같습니다.
    sleep 30
		# 배포된 어플리케이션의 상태를 확인
    DEPLOY_STATUS=$(sudo docker-compose -p $APPLICATION ps $DEPLOY_APPLICATION | grep "Up" | sed 's/.*/true/')
    echo $(sudo docker-compose -p $APPLICATION ps $DEPLOY_APPLICATION)
    
		if [ "$DEPLOY_STATUS" != "true" ]
    then
        echo 배포실패 
    else
        echo 배포 성공
				#Nginx의 설정정보를 Reload해줍니다.
        sudo docker-compose -p $APPLICATION exec nginx nginx -s reload
				#이전에 실행중이던 어플리케이션을 종료해줍니다.
        sudo docker-compose -p $APPLICATION stop $CUR_APPLICATION
        break
    fi
else
    echo $APPLICATION 실행중이지 않음
		# blue green 새로운 이미지로 빌드
    sudo docker-compose build --no-cache green blue
		# docker-compose 실행
    sudo docker-compose -p $APPLICATION up -d
		# 초기 설정으로 blue_nginx.conf 설정
    sudo cp blue_nginx.conf nginx.conf
		# 어플리케이션이 정상적으로 실행될때까지 기다리기 위해 Sleep 설정
		#이건 직접 어플리케이션 요청을 계속 보내면서 확인하는게 가장 좋은 방법 같습니다.
    sleep 30
		#Nginx의 설정정보를 Reload해줍니다.
    sudo docker-compose -p $APPLICATION exec nginx nginx -s reload
		#Green 어플리케이션을 종료해줍니다.
    sudo docker-compose -p $APPLICATION stop green
    echo $APPLICATION 실행
fi
profile
Programmer

0개의 댓글