[Docker] Spring 프로젝트를 Docker를 이용해서 배포해봅시다

Doccimann·2022년 4월 20일
9

Docker

목록 보기
2/2

본격적으로 설명하기 전에

이번 포스트에서는 Docker를 이용해서 저희가 예전에 작성했던 프로젝트를 올리는 방법에 대해서 다룰 예정입니다.

배포 방법이 달라졌다보니 아예 처음부터 설명을 다시 해드리겠습니다. 물론 ec2를 어떻게 만드는지는 설명 안할겁니다

설명 과정에서 많은 부분이 생략될 수는 있는데, 아래의 링크에 있는 글과 같이 비교하시면서 보면 괜찮을 것 같습니다.

스프링 모놀리식 배포 - EC2 생성및 GitHub에 코드 배포

본격적으로 시작해봅시다!

👉 이번 예제를 위해서는 다음이 필요합니다

  • Docker container를 렉 없이 올리기 위해서는 충분히 큰 용량의 ec2가 필요합니다. 따라서 저는 t2.medium 기준으로 ec2를 마련했습니다.
    자신은 렉이 많이 걸려도 상관이 없고, 돈을 최대한 절약하기를 원한다고 하면 프리티어 단계에서 제공하는 t2.micro를 이용해서 따라오셔도 상관없습니다.

우선 EC2 환경설정부터 해봅시다

우선 ec2가 생성이 되었다면, update부터 시켜주고 git을 설치해줍니다.

# ec2 업데이트
sudo yum update -y
# git 설치하기
sudo yum install git

다음으로, jdk를 설치해줍시다.

# aws coreetto 다운로드
sudo curl -L https://corretto.aws/downloads/latest/amazon-corretto-11-x64-linux-jdk.rpm -o jdk11.rpm

# jdk11 설치
sudo yum localinstall jdk11.rpm -y

# jdk version 선택
sudo /usr/sbin/alternatives --config java

# java 버전 확인
java --version

# 다운받은 설치키트 제거
rm -rf jdk11.rpm

위의 커맨드를 모두 입력하고 java --version을 통해 아래와같이 뜬다면 성공한겁니다.

openjdk 11.0.15 2022-04-19 LTS
OpenJDK Runtime Environment Corretto-11.0.15.9.1 (build 11.0.15+9-LTS)
OpenJDK 64-Bit Server VM Corretto-11.0.15.9.1 (build 11.0.15+9-LTS, mixed mode)

이제부터 DockerFile을 작성해봅시다.

이전에 작성했던 프로젝트에 Dockerfile을 추가해줍시다. 아래와 같이 Dockerfile을 추가해줍니다.

FROM openjdk:11-jdk

# JAR_FILE 변수 정의 -> 기본적으로 jar file이 2개이기 때문에 이름을 특정해야함
ARG JAR_FILE=./build/libs/DevopsTestKotlin-0.0.1-SNAPSHOT.jar

# JAR 파일 메인 디렉토리에 복사
COPY ${JAR_FILE} app.jar

# 시스템 진입점 정의
ENTRYPOINT ["java","-jar","/app.jar"]

👉 주의

이번에 제가 사용하는 프로젝트는 이전에 Java로 작성한 것을 그대로 Kotlin으로 전환하여 DevopsTestKotlin 이라는 이름으로 사용중입니다. 이 부분이 여러분들의 프로젝트와 다를 수 있습니다!

제가 작성한 Dockerfile을 하나하나 알려드리겠습니다.

FROM openjdk:11-jdk

위의 커맨드는, Docker를 올릴 때 jdk11 버전을 이용해서 올리겠다라고 선언하는 커맨드입니다.

ARG JAR_FILE=./build/libs/DevopsTestKotlin-0.0.1-SNAPSHOT.jar

위의 커맨드는, JAR 파일의 위치를 환경변수의 형태로 선언하는 것입니다. 프로젝트를 빌드하게되면 build/libs/xxxx.jar 의 형태로 jar file이 생성이 되어있을텐데요, 그 파일의 위치를 변수로 저장하는 것입니다.

COPY ${JAR_FILE} app.jar

위의 커맨드는, 프로젝트의 jar 파일 위치를 참조하여 jar 파일을 가져와서 컨테이너의 루트 디렉토리에 app.jar의 이름으로 복사하는 커맨드입니다.

ENTRYPOINT ["java","-jar","/app.jar"]

위의 커맨드는, 도커파일이 도커엔진을 통해서 컨테이너로 올라갈 때, 도커 컨테이너의 시스템 진입점이 어디인지를 선언하는 커맨드입니다.

위의 커맨드에서는, java -jar 명령어를 이용해서 컨테이너의 루트에 위치한 app.jar을 실행하라는 뜻의 커맨드입니다.

위의 Dockerfile의 작성이 완료되었다면, git commit and push를 해준 다음에 ec2에 코드를 옮겨주면됩니다. 이 부분은 아래의 글에 잘 소개가 되어있기 때문에, 생략하겠습니다.

스프링 모놀리식 배포 - EC2 생성및 GitHub에 코드 배포


Docker-compose도 작성해줍시다

일단은 ec2에 프로젝트가 복사가 되어있다는 전제로 시작하겠습니다.

저희는 spring-boot 프로젝트를 nginx를 이용해서 배포할 예정이기 때문에, nginx의 이미지와 함께 사용을 하기 위해 docker-compose.yml 파일을 작성하여 docker-compose로 올릴 계획입니다.

일단은 docker와 docker-compose를 ec2에 설치를 하겠습니다.

sudo yum install docker

# 잘 설치가 되었는지 확인
sudo docker --version

Docker의 버전을 확인할 때, 다음과 같이 뜨면 정상입니다.

Docker version 20.10.7, build f0df350

다음으로, docker-compose를 설치하겠습니다.

sudo curl -L "https://github.com/docker/compose/releases/download/1.28.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

그리고 버전을 확인해보겠습니다.

docker-compose --version

버전 확인 커맨드 입력시 아래와 같이 뜬다면, 정상입니다.

docker-compose version 1.28.2, build 67630359

여기까지 끝났다면, docker-compose.yml을 프로젝트의 루트 디렉토리에 생성하고, 다음과 같이 작성을 해줍니다.

version: "3"
services:
  web:
    image: nginx
    ports:
      - 80:80
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
    depends_on:
      - spring
  spring:
    build: .
    ports:
      - 8080:8080

위의 docker-compose 파일을 해석해보겠습니다.

nginx 이미지의 경우 인바운드로 들어오는 80번 포트를 docker container 내부에 꽂히는 80번 포트로 포워딩해주는 기능을 수행합니다. 이는 Http 요청을 nginx 내부로 그대로 흘리겠다는 뜻으로 해석할 수 있습니다.

그리고 volumes를 통해서 ec2에 있는 nginx 설정 파일을 컨테이너의 /etc/nginx/conf.d 폴더로 옮겨서 실행시키는 것도 볼수 있습니다.

그런데 depends_on 이라는 속성이 보이는데, 이는 nginx가 실행되는 시점은 spring이 컨테이너로 올라간 다음에 올리겠다는 뜻 입니다.

그리고 spring은 매우 간단합니다. 현재 디렉토리에 있는 Docker image를 빌드시키고, 8080번 포트로 인바운드 들어오는 신호를 컨테이너 내부의 8080번 포트로 그대로 꽂아주겠다는 의미입니다.

그런데, 저희는 아직 nginx의 설정 파일을 건든적이 없으며, 작성한 적도 없습니다. 따라서 nginx의 설정 파일인 conf.d를 작성하겠습니다.

우선 아래의 커맨드를 통해서 nginx/conf.d 폴더 내부에 app.conf를 생성하겠습니다.

mkdir nginx
mkdir nginx/conf.d
cd nginx/conf.d
sudo vim app.conf

그리고 app.conf에는 다음과 같이 작성합니다.

server {
    listen 80;
    access_log off;

    location / {
        proxy_pass http://spring:8080;
        proxy_set_header Host $host:$server_port;
        proxy_set_header X-Forwarded-Host $server_name;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

대충 위의 내용을 해석하겠습니다.

일단은 nginx는 외부의 80번 포트의 신호를 읽어냅니다. 그리고 신호를 pass 시킬때는 spring의 8080번 포트로 포워딩시켜서 패스시킵니다. 그리고 이렇게 포워딩 된 신호는 spring 입장에서는 인바운드에 해당하기 때문에 위에서 docker-compose.yml을 작성한대로 spring은 그에 해당하는 신호를 8080번 포트로 포워딩시켜서 container 내부로 꽂아버립니다.

그리고 그 아래 3개의 라인은 모두 proxy에 header를 채우는 코드들입니다.


이제 본격적으로 프로젝트를 올려봅시다.

아래의 커맨드를 통해서 프로젝트를 빌드시켜봅시다.

# gradlew의 권한 변경
sudo chmod + ./gradlew

# 빌드
./gradlew build

그리고 docker를 실행시킵니다.

sudo systemctl start docker

그 다음에 docker가 정상적으로 동작하고있는지 확인해봅시다.

systemctl status docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
   Active: active (running) since 수 2022-04-20 17:48:42 UTC; 51s ago
     Docs: https://docs.docker.com
  Process: 3380 ExecStartPre=/usr/libexec/docker/docker-setup-runtimes.sh (code=exited, status=0/SUCCESS)
  Process: 3379 ExecStartPre=/bin/mkdir -p /run/docker (code=exited, status=0/SUCCESS)
 Main PID: 3384 (dockerd)
    Tasks: 8
   Memory: 119.3M
   CGroup: /system.slice/docker.service
           └─3384 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --default-ulimit nofile=32768:65536

이제 docker-compose를 통해서 프로젝트를 올립니다.

# docker-compose를 백그라운드에서 동작시키기
docker-compose up --build -d
Building with native build. Learn about native build in Compose here: https://docs.docker.com/go/compose-native-build/
Creating network "order-example-kotlin_default" with the default driver
Building spring
Sending build context to Docker daemon  45.52MB
Step 1/4 : FROM openjdk:11-jdk
11-jdk: Pulling from library/openjdk
6aefca2dc61d: Pulling fs layer
967757d56527: Pulling fs layer
c357e2c68cb3: Pulling fs layer
c766e27afb21: Pulling fs layer
a747e81e6111: Pulling fs layer
2859d18181fd: Pulling fs layer
3c6d59134c80: Pulling fs layer
c766e27afb21: Waiting
a747e81e6111: Waiting
3c6d59134c80: Waiting
2859d18181fd: Waiting
c357e2c68cb3: Verifying Checksum
c357e2c68cb3: Download complete
967757d56527: Verifying Checksum
967757d56527: Download complete
6aefca2dc61d: Verifying Checksum
6aefca2dc61d: Download complete
a747e81e6111: Verifying Checksum
a747e81e6111: Download complete
2859d18181fd: Verifying Checksum
2859d18181fd: Download complete
c766e27afb21: Verifying Checksum
c766e27afb21: Download complete
3c6d59134c80: Verifying Checksum
3c6d59134c80: Download complete
6aefca2dc61d: Pull complete
967757d56527: Pull complete
c357e2c68cb3: Pull complete
c766e27afb21: Pull complete
a747e81e6111: Pull complete
2859d18181fd: Pull complete
3c6d59134c80: Pull complete
Digest: sha256:95b2daeb07a18121a4309a053ff99aa741888528e5da068beef36db092a03e25
Status: Downloaded newer image for openjdk:11-jdk
 ---> e67a33049aa6
Step 2/4 : ARG JAR_FILE=./build/libs/DevopsTestKotlin-0.0.1-SNAPSHOT.jar
 ---> Running in a1ebd6481830
Removing intermediate container a1ebd6481830
 ---> e448756e884e
Step 3/4 : COPY ${JAR_FILE} app.jar
 ---> 1c1a9cfead2f
Step 4/4 : ENTRYPOINT ["java","-jar","/app.jar"]
 ---> Running in 7dfbfc10fc7e
Removing intermediate container 7dfbfc10fc7e
 ---> e1bfd62d0398
Successfully built e1bfd62d0398
Successfully tagged order-example-kotlin_spring:latest
Pulling web (nginx:)...
latest: Pulling from library/nginx
1fe172e4850f: Pull complete
35c195f487df: Pull complete
213b9b16f495: Pull complete
a8172d9e19b9: Pull complete
f5eee2cb2150: Pull complete
93e404ba8667: Pull complete
Digest: sha256:694f2ecdb88498325d70dbcb4016e90ab47b5a8e6cd97aaeec53f71f62536f99
Status: Downloaded newer image for nginx:latest
Creating order-example-kotlin_spring_1 ... done
Creating order-example-kotlin_web_1    ... done

행복사

그리고 정상적으로 동작하고 있는지 확인을 해봅시다.

네! 정상적으로 동작하고 있는 것을 확인할 수 있습니다.


다음 포스트에서는, Docker image를 이용해서 AWS에서 제공하는 fargate라는 서비스를 통해서 위에서 한 방법보다 훨씬 간단하고, 그리고 직관적으로 배포하는 방법에 대해서 소개하겠습니다.

다음 포스트에서 뵙겠습니다!

profile
Hi There 🤗! I'm college student majoring Mathematics, and double majoring CSE. I'm just enjoying studying about good architectures of back-end system(applications) and how to operate the servers efficiently! 🔥

3개의 댓글

comment-user-thumbnail
2022년 4월 20일

중간고사 공부 안하나 ㄷㄷㄷ

1개의 답글
comment-user-thumbnail
2022년 12월 21일

공부를 해도 잘 이해가 안갔는데 이 게스글을 천천히 읽어보니 이해가 잘되네요. 좋은 컨텐츠 감사합니다.

답글 달기