Profile picture

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

JaehyoJJAng2023년 05월 22일

▶︎ 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: {% raw %} ${{ secrets.DOCKERHUB_USERNAME }} {% endraw %}
          password: {% raw %} ${{ secrets.DOCKERHUB_PASSWORD }} {% endraw %}

      - name: "4. Build and push"
        uses: docker/build-push-action@v2
        with:
          context: ./leafy-frontend # Dockerfile이 있는 경로 지정
          file: ./leafy-frontend/Dockerfile # Dockerfile 지정
          # 이미지를 레지스트리에 배포
          push: true
          tags: {% raw %} ${{ secrets.DOCKERHUB_USERNAME }}/leafy-frontend:${{ github.sha }} {% endraw %}
          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: {% raw %} ${{ secrets.DOCKERHUB_USERNAME }} {% endraw %}
          password: {% raw %} ${{ secrets.DOCKERHUB_PASSWORD }} {% endraw %}

      - name: "4. Build and push"
        uses: docker/build-push-action@v2
        with:
          context: ./leafy-backend # Dockerfile이 있는 경로 지정
          file: ./leafy-backend/Dockerfile # Dockerfile 지정
          # 이미지를 레지스트리에 배포
          push: true
          tags: {% raw %} ${{ secrets.DOCKERHUB_USERNAME }}/leafy-backend:${{ github.sha }} {% endraw %}
          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: {% raw %} ${{ secrets.DOCKERHUB_USERNAME }} {% endraw %}
          password: {% raw %} ${{ secrets.DOCKERHUB_PASSWORD }} {% endraw %}

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

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


Loading script...