Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Archives
Today
Total
관리 메뉴

참새의 이야기

nginx와 spring boot 서버를 docker-compose로 배포 본문

deploy

nginx와 spring boot 서버를 docker-compose로 배포

참새짹짹! 2024. 1. 17. 23:45

지난 글을 읽다보면, 한 가지 이상한 점이 있다.

docker compose를 EC2 인스턴스에 install 해두고 실제로는 사용을 하지 않았다.

이번에는 nginx를 프록시 서버로 추가하고 이 모든 것을 docker compose를 이용하여 EC2 인스턴스에 구성해보려고 한다.

workflow의 개선

이에 앞서, 지난번에 작성한 workflow도 개선을 해보려고 한다.

  1. github actions의 environment 기능을 사용하여 배포 환경을 분리하고
  2. 명령을 직접 입력했던 것들을 Github actions의 Marketplace에 있는 공용 action으로 replace하려고 한다.
  3. 일부 action의 경우, 현 시점 latest 버전으로 최신화도 해줄 것이다.

아래의 workflow는 최종본이 아니고, 1차 수정본이다.

최종본이 궁금하다면, 여기에서 확인할 수 있다.

name: build and deploy

on:
  push:
    branches: [ "master" ]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest

    # 배포 환경에 따라 다른 secret key를 사용하거나 다른 설정이 필요한 경우에 사용하면 좋다.
    environment: master
    steps:
      - name: Checkout Github actions
        # v4가 가장 최신 버전이기 때문에 변경
        uses: actions/checkout@v4

      - name: Set up JDK 17
        # v4가 가장 최신 버전이기 때문에 변경
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Run chmod to make gradlew executable
        run: chmod +x ./gradlew

      - name: Build with Gradle
        # 명령으로 했던 빌드를 공용 action으로 replace
        uses : gradle/gradle-build-action@v2
        with:
          arguments: |
            build
            --scan

      - name: Login to DockerHub
        # v3가 가장 최신 버전이기 때문에 변경
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

        # 명령어를 공용 action으로 replace
      - name: Docker에 Build & Push
        uses: docker/build-push-action@v5
        with:
          # context를 안 넣어주면 Dockerfile에서 'COPY ${JAR_FILE} app.jar'를 실행하다가 오류가 난다.
          context: .
          push: true
          tags: ${{ secrets.DOCKERHUB_USERNAME }}/infra_practice_server:latest

        # docker-compose 파일을 EC2 인스턴스에 복사하기 위해 추가한 것. deploy 과정의 ssh-action과 다른거다.
      - name: copy docker-compose -> EC2 server
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.INFRA_PRACTICE_SERVER_IP }}
          username: ubuntu
          key: ${{ secrets.EC2_SSH_KEY }}
          source: docker-compose.yml
          target: /home/ubuntu/

  deploy:
    runs-on: ubuntu-latest
    environment: master
    needs: build
    steps:
      - name: Docker compose
        uses: appleboy/ssh-action@v1.0.3
        env:
          DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
        with:
          host: ${{ secrets.INFRA_PRACTICE_SERVER_IP }}
          username: ubuntu
          key: ${{ secrets.EC2_SSH_KEY }}
          envs: DOCKERHUB_USERNAME
          script: |
            echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin
            sudo docker pull ${{ env.DOCKERHUB_USERNAME }}/infra_practice_server:latest
            # port 등 세부 설정을 docker-compose로 옮기고 docker-compose를 up 명령으로 실행한다.
            sudo docker compose -f /home/ubuntu/docker-compose.yml up -d
            # 사용하지 않는 이미지들은 제거한다.
            sudo docker image prune -a -f

Build with Gradle 에서의 argument, Docker에 Build & Push 에서의 context 등을 제대로 입력해주지 않아서 고생했다.

주석으로 일부 설명해뒀으니 참고하면 좋을 것 같다.

docker compose 제대로 활용하기

이제부터는 위에서 추가한 docker compose up 이 사용할 docker-compose.yml 파일을 생성해보자.

nginx와 관련된 내용은 아래에서 추가할 것이므로 여기서는 spring boot 서버만 잘 돌아갈 수 있도록 구성할 것이다.

version: "3.8"

services:
  backend:
    image: ${DOCKERHUB_USERNAME}/infra_practice_server:latest
    container_name: deploy_container
    restart: always
    ports:
      - "8080:8080"
    network_mode: host
    env_file:
      - .env
    environment:
      - TZ=Asia/Seoul

docker-compose 파일을 처음 사용해보는 것이기 때문에 아래의 네 가지는 어떤 목적인지 알아보자.

 

1. restart

컨테이너가 종료되거나 실행에 실패할 경우 자동으로 다시 시작할 것인지를 나타낸다.

always로 설정했기 때문에 컨테이너가 정상적으로 실행되지 않으면 계속해서 실행을 시도한다.

docker-compose 파일에 nginx 설정을 할 때 어려움을 겪었는데, log를 까보면 1분 주기로 컨테이너를 실행하려고 재시도하고 있는 것을 확인할 수 있었다.

 

2. ports

이 컨테이너를 호스트 머신과 연결할 때 어떤 포트로 연결할 것인지를 나타낸다.

사실 스프링 부트는 기본적으로 8080 포트를 사용하기 때문에 다른 포트를 사용할 때에만 ports를 명시하면 된다.

이 파일에서 ports를 명시하는 것과 별개로 Dockerfile에는 EXPOSE로 어떤 포트를 사용하는지 알릴 수 있는데, 이는 문서화의 목적일 뿐이고 실제로 포트를 매핑하는 것은 docker-compose의 ports이다.

Dockerfile의 EXPOSE는 문서화의 목적이므로 없어도 괜찮다.

 

3. network_mode

도커의 네트워크에 대해서는 따로 공부를 더해줘야 할 것 같다.

우선은 컨테이너가 호스트의 네트워크를 공유하도록 하기 위해 host로 설정했다.

 

4. env_file

docker-compose.yml 파일에서 사용하는 값들 중 DOCKERHUB_USERNAME처럼 외부에 노출하고 싶지 않은 값들을 미리 인스턴스의 파일에 저장해두고 사용하는 것이다.

nginx를 사용하기 위한 세팅

nginx.conf 작성하기

events {
    worker_connections 10;
}

http {
    upstream deploy_container {
        server  localhost:8080;
    }

    server{
        listen 80;

        location / {
            proxy_pass http://deploy_container/test;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

events 블록이 없으면 nginx: [emerg] no "events" section in configuration 이 뜨면서 무한 restart가 시작된다.

worker_connections는 동시에 처리할 수 있는 클라이언트 연결 수를 의미한다.

infra 구축 연습용이기 때문에 10으로 작게 설정했다.

 

아래의 http 블록은 웹 서버의 전반적인 설정을 가지고 있다.

upstream은 여러 포트를 사용하는 경우에 사용하면 좋다.

현재는 8080 포트에서만 서버가 돌아가고 있으므로, upstream블록이 없어도 되지만 가능한 다양한 설정을 경험해보기 위해서 upstream블록도 넣어봤다.

upstream을 사용하지 않는다면, proxy_pass의 deploy_container를 localhost:8080로 대체하면 된다.

 

server 블록의 listen은 nginx가 80 포트에 대해 열려있음을 의미하고, location / 는 /로 들어오는 요청들을 proxy_pass로 넘긴다는 것을 의미한다.

github actions로 nginx.conf EC2에 복사하기

이제 위의 nginx.conf 파일을 EC2 인스턴스에서 사용할 수 있도록 복사해보자.

workflow에서 copy docker-compose -> EC2 server 의 source에 nginx.conf를 추가하면 된다.

- name: copy docker-compose -> EC2 server
  uses: appleboy/scp-action@v0.1.7
  with:
    host: ${{ secrets.INFRA_PRACTICE_SERVER_IP }}
    username: ubuntu
    key: ${{ secrets.EC2_SSH_KEY }}
    # 추가한 부분
    source: docker-compose.yml, nginx.conf
    target: /home/ubuntu/

docker-compose에 nginx 관련 내용 추가

version: "3.8"

services:
  nginx:
    image: nginx
    container_name: nginxserver
    restart: always
    ports:
      - "80:80"
    volumes:
      - "./nginx.conf:/etc/nginx/nginx.conf"
    depends_on:
      - backend
    network_mode: host
    environment:
      - TZ=Asia/Seoul
  backend:
    image: ${DOCKERHUB_USERNAME}/infra_practice_server:latest
    container_name: deploy_container
    restart: always
    ports:
      - "8080:8080"
    network_mode: host
    env_file:
      - .env
    environment:
      - TZ=Asia/Seoul

docker-compose 파일에 추가한 것들 중, 새롭게 볼만한 것으로는 volumes가 있다.

./nginx.conf:/etc/nginx/nginx.conf 는 EC2 인스턴스의 ./nginx.conf를 컨테이너의 /etc/nginx/nginx.conf에 연결한다는 것이다.

복사의 개념이 아니라 연결이라는 것에 주목하면 좋을 것 같다.

정리

이제 nginx로 웹 서버를 구성하기 위한 모든 작업을 마쳤다.

docker compose up이 docker-compose에 있는 두 컨테이너가 실행되도록 하고, 실제로 잘 돌아가는 것을 docker의 log로 확인했다.

'deploy' 카테고리의 다른 글

배포 자동화 도전기 (github actions+docker+ec2)  (4) 2024.01.06