[Docker] (Leafy) 3Tier 아키텍처 구성

▶︎ 3Tier

image
보통 엔터프라이즈 웹 애플리케이션은 세 가지 종류의 서버로 구성된다.

  • 프론트엔드 서비스를 제공하는 웹 서버
  • 비즈니스 로직을 수행하는 웹 애플리케이션(WAS) 서버
  • 데이터를 저장하는 데이터베이스 서버

이 세 가지 종류의 서버가 유기적으로 상호작용하며 하나의 애플리케이션으로 구성되는 것을

3Tier 아키텍처라고 부른다.


하지만 위 아키텍처의 경우 웹 애플리케이션 서버가 클라이언트에게 그대로 노출되어 있기에 보안상 위험이 뒤따른다.

그래서 nginx의 프록시 기능을 활용해서 백엔드 애플리케이션으로의 접근을 제한해야 한다.

아래와 같이 변경된 구조가 더 개선된 3Tier 구조라고 볼 수 있겠다.
image


‣ 구성도

image

  • 프론트엔드 컨테이너는 HostOS의 80 포트로 접속
  • 백엔드 컨테이너는 HostOS의 80 포트의 /api 경로로 접근
    • Nginx 서버에서 프록시되어 외부에서는 접근 불가
  • 데이터베이스 컨테이너는 포트포워딩이 지정되지 않았기 때문에 외부에서는 접근 불가
    • 내부 DNS 서버에 등록된 레코드를 통해 백엔드 컨테이너에서 leafy-postgres로 접속

‣ 소스코드


프로젝트 clone

git clone https://github.com/JaehyoJJAng/3-tier-image-deploy
cd 3-tier-image-deploy

프로젝트 구조

tree -L 2 .

image


‣ 도커 네트워크

frontend, backend 네트워크 생성

docker network create leafy-frontend
docker network create leafy-backend

‣ 도커 로그인

  • ${DOCKER_PASSWORD} : DOCKER_PASSWORD 라는 변수에 도커 허브 패스워드 기입.
  • ${DOCKER_USER} : DOCKER_USER 라는 변수에 도커 허브 유저명 기입.
# Docker login
echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USER}" --password-stdin

‣ Docker Run

A. leafy-frontend

Dockerfile 작성

#### Build Stage
# 빌드 이미지로 node:14 지정
FROM node:14 AS build

# 작업 디렉토리 지정
WORKDIR /app

# 라이브러리 설치 파일 복사
COPY package*.json .

# 라이브러리 설치
RUN npm ci

# 소스코드 전체 복사
COPY . .

# 소스코드 빌드
RUN npm run build

#### Production Stage
# 빌드 이미지로 nginx 지정
FROM nginx:1.21.4-alpine

# nginx config 파일 복사
COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf.template

# 환경변수 설정
ENV BACKEND_HOST=leafy
ENV BACKEND_PORT=8080

# docker-entrypoint.sh 파일 복사
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh

# 빌드 이미지에서 생성된 dist 폴더를 nginx 이미지로 복사
COPY --from=build /app/dist /usr/share/nginx/html

# 컨테이너 포트 지정
ARG NGINX_PORT=80
EXPOSE $NGINX_PORT

# ENTRYPOINT 지정
ENTRYPOINT ["docker-entrypoint.sh"]

# COMMAND 지정
CMD ["nginx", "-g", "daemon off;"]

1. 도커 이미지 build & push

# build
docker build --tag yshrim12/leafy-frontend:latest .

# push
docker push yshrim12/leafy-frontend:latest

2. 컨테이너 실행

docker run -d -it --name leafy-front -p 80:80 yshrim12/leafy-fronend

3. leafy-front 컨테이너에 도커 네트워크 연결

docker network connect leafy-frontend leafy-front
docker network connect leafy-backend leafy-front

4. leafy-front 컨테이너 재시작

docker restart leafy-front

5. 컨테이너 로그 조회

docker logs leafy-front

B. leafy-backend

leafy-backend/Dockerfile

#### Build Stage
# 빌드 이미지로 OpenJDK 11 & Gradle 지정
FROM gradle:7.6.1-jdk11 AS build

# 작업 디렉토리 지정
WORKDIR /app

# 라이브러리 설치 파일 복사
COPY build.gradle settings.gradle ./

# gradle dependencies
RUN gradle dependencies --no-daemon

# 소스코드 전체 복사
COPY ./ ./

# Gradle 빌드 실행하여 JAR 파일 생성
RUN gradle clean build --no-daemon

#### Runtime stage
# 런타임 이미지로 OpenJDK 11-jre-slim 지정
FROM openjdk:11-jre-slim

# 작업 디렉토리 지정
WORKDIR /app

# 빌드 이미지에서 생성된 JAR 파일을 런타임 이미지로 복사
COPY --from=build /app/build/libs/*.jar ./leafy.jar

# 컨테이너 포트 지정
EXPOSE 8080

ENTRYPOINT ["java"]
CMD ["-jar", "leafy.jar"]

1. 도커 이미지 build & push

# build
docker build --tag yshrim12/leafy-backend:latest .

# push
docker push yshrim12/leafy-backend:latest

2. 컨테이너 실행

docker run -d -it --name leafy -p 8080:8080 \
--network leafy-backend \
-e DB_URL=leafy-postgres
yshrim12/leafy-backend

3. 컨테이너 로그 조회

docker logs leafy

C. leafy-postgresql

leafy-postgresql/Dockerfile

# PostgreSQL 13 버전을 베이스 이미지로 사용
FROM postgres:13

# init.sql 파일을 /docker-entrypoint-initdb.d/로 복사
# /docker-entrypoint-initdb.d/에 있는 sql문은 컨테이너 처음 실행 시 자동 실행됨.
COPY ./init/init.sql /docker-entrypoint-initdb.d/

# config/postgresql.conf 파일을 /etc/postgresql/postgresql.conf로 복사
# 기본 설정 파일을 덮어쓰기하여 새로운 설정 적용
COPY ./config/postgresql.conf /etc/postgresql/custom.conf

# 계정 정보 설정
ENV POSTGRES_USER=myuser
ENV POSTGRES_PASSWORD=mypassword
ENV POSTGRES_DB=mydb

# 컨테이너 포트 설정
EXPOSE 5432

CMD ["postgres", "-c", "config_file=/etc/postgresql/custom.conf"]

1. 도커 이미지 build & push

# build
docker build --tag yshrim12/leafy-postgres:latest .

# push
docker push yshrim12/leafy-postgres:latest

2. 컨테이너 실행

docker run -d -it --name leafy-postgres \
--network leafy-backend \
yshrim12/leafy-postgres

3. 컨테이너 로그 조회

docker logs leafy-postgre

4. 컨테이너 명령어 실행

docker exec -it leafy-postgres su postgres bash -c "psql --username=myuser --dbname=mydb"

5. 데이터 조회

$ mydb=# SELECT * FROM users;
$ mydb=# SELECT * FROM plants;
$ mydb=# SELECT * FROM user_plants;
$ mydb=# SELECT * FROM plant_logs;

‣ 도커 컴포즈

  • include 옵션을 사용하여 각 티어별 compose.yaml 분리

docker-compose.yaml

version: "3.9"

include:
  - "composes/postgresql.yaml"
  - "composes/backend.yaml"
  - "composes/frontend.yaml"

networks:
  leafy-backend:
    driver: bridge
    external: true
  leafy-frontend:
    driver: bridge
    external: true

volumes:
  mydata: {}

composes/postgresql.yaml

services:
  leafy-postgres:
    build: ../leafy-postgresql
    image: leafy-postgres:5.0.0-compose
    volumes:
      - type: volume
        source: "mydata"
        target: "/var/lib/postgresql/data"
    container_name: leafy-postgres
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 256M
    restart: always
    networks:
      - "leafy-backend"

composes/backend.yaml

services:
  leafy-backend:
    build: ../leafy-backend
    environment:
      - DB_URL=leafy-postgres
    depends_on:
      - "leafy-postgres"
    container_name: leafy-backend
    deploy:
      resources:
        limits:
          cpus: '1.5'
          memory: 512M
    restart: on-failure
    networks:
      - "leafy-backend"

composes/frontend.yaml

services:
  leafy-front:
    build: ../leafy-frontend
    image: leafy-front:5.0.0-compose
    environment:
      - BACKEND_HOST=leafy-backend
    ports:
      - 80:80
    depends_on:
      - "leafy-backend"
    container_name: leafy-frontend
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 64M
    restart: on-failure
    networks:
      - "leafy-frontend"
      - "leafy-backend"

▶︎ 파이프라인 구성

image


‣ 환경 변수 셋업

image


‣ leafy-frontend

.github/workflows/leafy-frontend.yaml

name: Frontend Build and Push

on:
  push:
    branches:
      # main 브랜치에 push 될 때 워크플로우 실행
      - main
    paths:
      # leafy-frontend 디렉토리에 변경점이 있을 경우에만 워크플로우 동작.
      - 'leafy-frontend/**'

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      - name: "1. Checkout Repository"
        uses: actions/checkout@v2
        
      - name: "2. Set up Docker buildx"
        uses: docker/setup-buildx-action@v1

      - name: "3. Login to dockerhub"
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_PASSWORD }}

      - name: "4. Build and push"
        uses: docker/build-push-action@v2
        with:
          context: ./leafy-frontend # Dockerfile이 있는 경로 지정
          file: ./leafy-frontend/Dockerfile # Dockerfile 지정
          # 이미지를 레지스트리에 배포
          push: true
          tags: ${{ secrets.DOCKERHUB_USERNAME }}/leafy-frontend:${{ github.sha }}
          platform: linux/amd64,linux/arm64,windows/amd64

배포 시 leafy-frontend 디렉토리에 변경점이 있을 경우 위 워크플로우가 자동으로 실행됨.
image


‣ leafy-backend

.github/workflows/leafy-backend.yaml

name: Backend Build and Push

on:
  push:
    branches:
      # main 브랜치에 push 될 때 워크플로우 실행
      - main
    paths:
      # leafy-backend 디렉토리에 변경점이 있을 경우에만 워크플로우 동작.
      - 'leafy-backend/**'

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      - name: "1. Checkout Repository"
        uses: actions/checkout@v2

      - name: "2. Set up Docker buildx"
        uses: docker/setup-buildx-action@v1

      - name: "3. Login to dockerhub"
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_PASSWORD }}

      - name: "4. Build and push"
        uses: docker/build-push-action@v2
        with:
          context: ./leafy-backend # Dockerfile이 있는 경로 지정
          file: ./leafy-backend/Dockerfile # Dockerfile 지정
          # 이미지를 레지스트리에 배포
          push: true
          tags: ${{ secrets.DOCKERHUB_USERNAME }}/leafy-backend:${{ github.sha }}
          platform: linux/amd64,linux/arm64,windows/amd64

배포 시 leafy-backend 디렉토리에 변경점이 있을 경우 위 워크플로우가 자동으로 실행됨.
image


‣ leafy-postgres

.github/workflows/leafy-postgres.yaml

name: DB Build and Push

on:
  push:
    branches:
      # main 브랜치에 push 될 때 워크플로우 실행
      - main
    paths:
      # leafy-postgresql 디렉토리에 변경점이 있을 경우에만 워크플로우 동작.
      - 'leafy-postgresql/**'

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      - name: "1. Checkout Repository"
        uses: actions/checkout@v2

      - name: "2. Set up Docker buildx"
        uses: docker/setup-buildx-action@v1

      - name: "3. Login to dockerhub"
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_PASSWORD }}

      - name: "4. Build and push"
        uses: docker/build-push-action@v2
        with:
          context: ./leafy-postgresql # Dockerfile이 있는 경로 지정
          file: ./leafy-postgresql/Dockerfile # Dockerfile 지정
          # 이미지를 레지스트리에 배포
          push: true
          tags: ${{ secrets.DOCKERHUB_USERNAME }}/leafy-postgresql:${{ github.sha }}
          platform: linux/amd64,linux/arm64,windows/amd64

배포 시 leafy-postgresql 디렉토리에 변경점이 있을 경우 위 워크플로우가 자동으로 실행됨.
image

관련 글

지식 공유2분 읽기

[Docker] 컨테이너(Container)에 할당되는 CPU 제한하기

개요 이번 게시글에서는 도커 컨테이너에서 CPU를 최적으로 활용하는 방법에 대해서 기록해보려고 한다. 호스트 CPU Core 정보 확인 먼저 컨테이너에 CPU를 최적으로 할당하기 위해서는, 현재 호스트 시스템의 CPU 정보에 대해서...

도커
지식 공유1분 읽기

[Docker] 배그 최고급 꾸러기 시뮬레이터 배포

개요 최고급 꾸러미에 대한 확률 체감을 위한 시뮬레이터 프로그램을 구현하고 도커로 배포하였음. 배포 로직은 AWS EC2 인스턴스에 도커를 사용하여 nginx proxy manager를 배포하고 리버스 프록시로 홈 서버에 있는 시뮬레...

도커