Docker로 Spring 애플리케이션 배포하기

2022. 3. 16. 13:46개발/Docker-K8s

이전에 구축했던 무중단 배포 프로젝트에 Docker를 적용하려고한다. 최종적인 아키텍쳐 모습은 다음과 같다. 

전체 아키텍처

Dockerfile과 배포 Script는 아래 저장소를 참고해주세요

 

GitHub - 92SooJong/Toy-Project-Board

Contribute to 92SooJong/Toy-Project-Board development by creating an account on GitHub.

github.com

 

Docker 설치


먼저 배포가 이뤄질 서버에 Docker 엔진을 설치한다. OS로 Amazon Linux 를 사용했기때문에 sudo amazon-linux-extras install docker 명령어를 통해 Docker를 설치한다

 

sudo service docker start 명령어를 통해 Docker를 시작한다. sudo docker --version 명령어를 통해 성공적으로 시작되었는지 확인한다.


 

Dockerfile 작성


먼저 이미지 생성을 위한 Dockerfile을 작성한다. 

새로 만들 이미지가 alpine Java8을 사용할수 있도록 FROM을 통해 부모 이미지를 추가한다

ARG JAR_FILE을 통해 이미지 생성시 사용한 Argument를 받을 수 있도록 한다.

COPY를 통해 JAR파일과 db 접속정보가 담긴 yaml을 이미지에 복사한다.

ENTRYPOINT를 통해 컨테이너 생성시 수행할 명령어를 셋팅한다. 

컨테이너 생성시 자동으로 셸이 실행되는게 아니기 때문에 ENTRYPOINT 첫 부분에는 셸 실행을 위한 명령어를 작성한다. 여기서 한가지 주의할 점은 jdk-alpine버전은 /bin/bash가 없어서 /bin/sh로 해야한다는 점이다. 

${IDLE_PROFILE}은 컨테이너를 실행할때 환경변수로 입력된 값을 받을 변수이다.

FROM openjdk:8-jdk-alpine

ARG JAR_FILE

COPY ${JAR_FILE} app.jar
COPY application-db.yml /application-db.yml

ENTRYPOINT ["/bin/sh","-c","java -Dspring.config.location=classpath:/application-${IDLE_PROFILE}.yml,/application-db.yml -Dspring.profiles.active=${IDLE_PROFILE} -jar ./app.jar"]

 

배포 스크립트 재작성


start.sh를 변경한다. 기존에 start.sh에서 Jar 명령어를 수행했다면, 이번엔 Jar 실행부분을 제거하고 Docker 이미지 빌드와 컨테이너 생성에 대한 명령어를 작성하면된다. 

기존엔 JDK가 배포서버에 설치되어 있어야만 java 명령어가 수행되었는데 Docker를 사용함으로써 배포 서버에 별도로 JDK를 설치하지 않아도 컨테이너를 통해 정상적인 배포가 가능하게된다. 따라서 배포 서버가 추가되더라도 귀찮은 설치작업을 하지 않아도 된다.

#!/usr/bin/env bash

PROJECT_PATH=/home/ec2-user/toy-project-board/project
DOCKER_PATH=/home/ec2-user/toy-project-board/deploy

ABSPATH=$(readlink -f $0) # 현재파일 절대경로 (링크가 있다면 실제 경로를 찾도록한다)
ABSDIR=$(dirname $ABSPATH) # ABSPATH가 있는 디렉토리 (ABSPATH는 파일명이 포함된 경로이기때문)
source ${ABSDIR}/profile.sh # profile.sh를 실행한다.


sudo rm $PROJECT_PATH/build/libs/*jar

echo "> Start Build"

cd $PROJECT_PATH # 프로젝트가 저장된 경로로 이동
sudo chmod ugo+rwx gradlew # 권한부여
sudo ./gradlew build # 빌드시작

sudo rm $DOCKER_PATH/demo*

echo "> copy Jar"
cp $PROJECT_PATH/build/libs/*jar $DOCKER_PATH/ # jar 파일을 복사

echo "> copy Dockerfile"
cp $PROJECT_PATH/Dockerfile $DOCKER_PATH/ # Dockerfile

JAR_NAME=$(ls -tr $DOCKER_PATH/*.jar | tail -n 1) # 실행할 Jar명 가져오기
echo "> Run $JAR_NAME"

# Docker 컨테이너 생성 및 실행 명령어 (run 명령어)
IDLE_PROFILE=$(find_idle_profile)
IDLE_PORT=$(find_idle_port)

echo ">Run $JAR_NAME with IDLE_PROFILE"
echo ">IDLE_PROFILE $IDLE_PROFILE"

# 이미지로 만들 파일 확인
JAR_FILE="${JAR_NAME##*/}"
echo ">JAR_FILE==> $JAR_FILE"

sudo docker build -t toy-project-board:sample --build-arg JAR_FILE=$JAR_FILE $DOCKER_PATH

sudo docker run -d -p ${IDLE_PORT}:${IDLE_PORT} --name ${IDLE_PROFILE} -e "IDLE_PROFILE=${IDLE_PROFILE}" toy-project-board:sample

 

stop.sh를 변경한다. PID를 찾는대신 컨테이너의 ID를 찾도록 한다. docker ps 명령어에 -q 옵션을 사용하면 결과 중에서 컨테이너ID만 출력한다. (-a는 전체 , -f는 필터를 의미한다.)

#!/usr/bin/env bash

ABSPATH=$(readlink -f $0) # 현재파일 절대경로 (링크가 있다면 실제 경로를 찾도록한다)
ABSDIR=$(dirname $ABSPATH) # ABSPATH가 있는 디렉토리 (ABSPATH는 파일명이 포함된 경로이기때문)
source ${ABSDIR}/profile.sh # profile.sh를 실행한다.

IDLE_PROFILE=$(find_idle_profile)

CONTAINER_NAME=$(sudo docker ps -aqf "name=${IDLE_PROFILE}")

echo ">$CONTAINER_NAME"

if [ -z ${CONTAINER_NAME} ]
then
        echo ">구동중인 컨테이너가 없습니다."
else
        echo ">docker rm -f $CONTAINER_NAME"
        sudo docker rm -f ${CONTAINER_NAME}
        sleep 5
fi

확인


Jenkins를 통해 2회 배포요청을 한 결과 호스트의 포트 8081과 8082가 정상적으로 컨테이너와 매핑된걸 확인할 수 있다. 잘보면 IMAGE의 이름이 하나는 Hash값인데 toy-proejct-board:sampel 이미지를 만들때 기존에 존재하던 이름이 있으면, 기존의 이름을 <none>으로 만들어버린다. 그렇기 때문에 8081포트가 사용하는 이미지는 이름 대신 이미지ID가 출력된다.

2회 배포시 컨테이너
2회 배포시 이미지
3회 배포시


발생한 에러


1. ENTRYPOINT 구동시 jar의 Argument를 인식하지 못하는 문제

 Dockerfile에서 Jar파일을 실행할때 ENTRYPOINT을 잘못 작성해서 발생한 문제였다. java -jar [Argument] [실행할 Jar파일] 순으로 입력했어야했는데 java -jar [실행파일] [Argument]순으로 실행했다. 이렇게 하면 Application 실행은 잘 실행되나 환경변수들이 하나도 인식되지 않았다. 당연히 실행 명령어를 의심하지 않았고, 이 과정에서 시간을 많이 잡아먹었다.

 

 

참고자료


컨테이너 생성시 Spring Profile 적용하는 방법

 

How can I start spring boot application in docker with profile?

I have a simple spring-boot project: -resources -application.yaml -application-test.yaml And I have this Dockerfile: FROM openjdk:8-jdk-alpine EXPOSE 8080 ADD micro-boot.jar micro-boot.jar

stackoverflow.com

--build-arg로 전달한 파라미터를 Dockerfile에서 사용하는 방법

 

Docker, one or more build-args where not consumed

I'm trying to build Oracle WebLogic Docker image with custom environment variables $ docker build -t 12213-domain --build-arg ADMIN_PORT=8888 --build-arg ADMIN_PASSWORD=wls . but I get the follow...

stackoverflow.com

Bash 셸 스크립트에서 특정 문자 기준으로 Split하고 가장 마지막 인덱스 값을 찾는방법

 

How to split a string in shell and get the last field

Suppose I have the string 1:2:3:4:5 and I want to get its last field (5 in this case). How do I do that using Bash? I tried cut, but I don't know how to specify the last field with -f.

stackoverflow.com

ENRTYPOINT에서 /bin/bash 안될때

 

[docker] starting container process caused exec bash 에러

docker 이미지가 alpine이라면 /bin/bash를 지원하지 않을 수 있다. /bin/bash을 지원하는 sh가 많아지고 있다. $ docker exec -it 9198cbd7a9da /bin/bash OCI runtime exec failed: exec failed: container_lin..

knight76.tistory.com

셸 스크립트에서 컨테이너 이름으로 Docker ID얻기

 

Get Docker container id from container name

What is the command to get the Docker container id from the container name?

stackoverflow.com

nginx의 Configuration을 확인하는 방법

 

NGINX failed to work while configuring server for subdomain

Job for nginx.service failed because the control process exited with error code. See "systemctl status nginx.service" and "journalctl -xe" for details. Prior to this prompt, the

stackoverflow.com

 

CodeDeploy Agent 설치

 

Amazon Linux 또는 RHEL용 CodeDeploy 에이전트 설치 - AWS CodeDeploy

네 번째 명령에서 /home/ec2-user는 Amazon Linux 또는 RHEL Amazon EC2 인스턴스의 기본 사용자 이름을 나타냅니다. 사용자 지정 AMI를 사용하여 인스턴스를 만든 경우 AMI 소유자가 다른 기본 사용자 이름을

docs.aws.amazon.com