프론트엔드, 백엔드, 데이터베이스를 서로 연결하여
애플리케이션 구현은 어떻게 할까?
"leafy"라는 식물 관리 애플리케이션을 구현해 보자.
구성 및 흐름
--
실습할 애플리케이션은 총 3개의 서버로 구성되어 있다.
- Vue.js 기반인 Nginx 웹 서버
- Spring Boot 기반인 Spring Boot Tomcat WAS 서버
- PostgreSQL 기반인 Database 서버
흐름 설명
- 클라이언트는 웹 서버의 주소를 브라우저에 입력하여 요청을 보냄
- 웹 서버에서 index.html 파일과 같이 웹페이지를 응답하여 제공
- 클라이언트가 웹 페이지를 읽는 과정에 어떠한 데이터 정보를 접근해야 하는 경우
브라우저에서 백엔드로 데이터 관련 요청을 보냄 - 백엔드에서 데이터 조회 및 저장을 하려면 postgreSQL에 접근하여 데이터 조회 및 저장을 수행
(클라이언트에게 응답할 데이터가 있으면 응답)
위 그림 구조를 보면
백엔드 애플리케이션이 외부(클라이언트)에 직접적으로 노출되어 있다.
즉, 외부에서 백엔드 애플리케이션으로 접근을 하는 구조로 되어 있다.
백엔드 애플리케이션은 시스템과 실제 데이터에 밀접한 연관이 있기 때문에 보안에 주의해야 한다.
예상하지 못한 API가 노출되어서 전체적인 시스템에 위험을 주는 일이 없어야 하기 때문이다.
그래서 백엔드 애플리케이션은 일반 사용자가 직접 접근하는 것을 못하도록 구현하는 것이 일반적이다.
(백엔드 서버는 웹 서버를 통해서만 접근이 가능하도록 한다.)
이번에는 간단히 구현하는 것에 초점을 맞췄기 때문에 위와 같은 구조로 구현하는 것이다.
--
애플리케이션이 사용할 네트워크 생성
--
네트워크 생성하는 명령어 형식
docker network create {네트워크명}
"leafy-network"라는 이름을 가진 네트워크 생성하는 명령어
docker network create leafy-network
--
PostgreSQL 데이터베이스 컨테이너 구현하기
--
우선
PostgreSQL은 데이터베이스이며
Nginx처럼 이미지에 소프트웨어가 포함되어 있기 때문에
별로의 설정 없이 기본으로 제공하는 PostgreSQL 이미지만 컨테이너로 실행해도 데이터베이스를 사용할 수 있다.
다만
기본 이미지에는 아무 데이터도 없기 때문에
초기 데이터가 있어야 한다면 초기 데이터를 구성해 주는 SQL문을 작성하여 PostgreSQL 이미지에 전달해줘야 한다.
즉, 초기 데이터가 필요 없다면 바로 기본 이미지를 사용하면 되고,
초기 데이터가 필요하다면 초기 데이터를 넣어주는 작업이 추가된다.
지금은 초기 데이터가 필요하기 때문에 초기 데이터를 넣는 과정도 포함하여 구현해 보자.
- 구현할 애플리케이션의 데이터베이스는 Postgres 13버전을 사용하므로 postgres 13버전을 베이스 이미지로 사용
- 데이터베이스 서버의 설정을 변경하기 위해 직접 작성한 설정 파일(postgresql.conf)을 이미지에 설정 파일(custom.conf)로 복사
즉, 빌드 콘텍스트에 존재하는 (./init/init.sql) 파일을 이미지에 존재하는 (/etc/postgresql/custom.conf) 에 복사한다. - 데이터베이스의 초기 데이터를 SQL로 작성해 놓은 파일(init.sql)을 이미지에 복사
- 컨테이너 실행 시 자동으로 동작할 명령어 지정
(작성한 명령어는 환경 설정파일을 지정하는 명령어다.)
환경 설정 파일 추가 설명
postgreSQL 이미지 내의 기본 환경 설정 파일은
"/var/lib/postgeresql/data/postgresql.conf"로 존재하고 있다.
즉, 해당 이미지는 환경 설정을 해당 경로에 있는 해당 이름의 파일을 가지고 자동으로 환경을 설정을 한다.
하지만 직접 커스텀한 환경 설정 파일을 "/etc/postgresql" 경로에 "custom.conf" 이름으로 변경하여 복사했다.
기본 환경 설정 파일이 아니라 복사한 환경 설정 파일(custom.conf)로 환경 설정하도록 하려면
환경 설정 파일이 "custom.conf" 파일이라고 지정해 주는 명령어를 작성해야 한다.
바로 그 명령어가 CMD 지시어에 작성한 명령어다.
초기 데이터, 이후 데이터 추가 설명
postgreSQL 이미지의 초기 데이터를 지정하는 파일은
"/docker-entrypoint-initdb.d/"라는 디렉터리는 Postgre 이미지가 컨테이너로 실행될 때
자동으로 해당 디렉터리에 있는 SQL문을 실행해 주므로 초기 데이터가 생성되는 것이다.
애플리케이션이 실행된 이후에 추가 및 관리되는 데이터는
"/var/lib/postgresql/data" 경로에 쌓이게 된다.
환경 설정 파일(postgresql.conf ) 내용 참고
초기 데이터 파일(init.sql) 내용 참고
Dockerfile 내용 참고
PostgreSQL의 계정 정보를 설정해 주기 위해 "ENV" 지시어로 환경 변수를 지정해 준다.
POSTGRES_USER | PostgreSQL의 계정 사용자 이름 |
POSTGRES_PASSWORD | PostgreSQL의 계정 사용자 비밀번호 |
POSTGRES_DB | 사용할(생성할) 데이터베이스의 이름 |
Dockerfile을 사용하여 데이터베이스 서버 구현하기
"anijy/leafy-postgres:1.0.0" 이름으로 이미지 빌드하는 명령어
docker build -t anijy/leafy-postgres:1.0.0 .
빌드된 이미지를 내 도커 허브에 업로드하는 명령어
docker push anijy/leafy-postgres:1.0.0
이미지를 컨테이너로 실행할 때 네트워크도 지정해 주는 명령어 형식
docker run [--name 컨테이너명] [--network 네트워크명] [이미지명]
빌드한 이미지를 네트워크를 지정하여 컨테이너로 실행하는 명령어
docker run -d --name leafy-postgres --network leafy-network anijy/leafy-postgres:1.0.0
데이터베이스 서버는 백엔드 서버와 연결을 해줘야 하기 때문에
데이터베이스 서버의 컨테이너명은 잘 작성하자.
(백엔드 서버를 구현할 때 연결할 데이터베이스 서버의 컨테이너명을 지정해 줘야 한다.)
Postgres에서 SQL 데이터 조회 해보기
postgres 컨테이너의 터미널(shell) 내부에 접근하는 명령어
docker exec -it leafy-postgres su postgres bash -c "psql --username=myuser --dbname=mydb"
접근한 컨테이너에서 조회하는 SQL문 작성하기
SELECT * FROM users;
해당 테이블에서 빠져나오려면 "q"를 누르면 된다.
컨테이너의 터미널(shell) 내부를 빠져나오려면 "exit" 명령어를 입력하면 된다.
--
Spring Boot 백엔드 컨테이너 구현하기
--
자바로 개발된 소스 코드를 애플리케이션으로 빌드를 하려면 "Maven" 또는 "Gradle" 빌드 도구가 필요하다.
해당 소스 코드를 빌드하게 되면 "jar" 또는 "war" 파일이 생성된다.
이렇게 생성된 "jar" 또는 "war" 파일을 실행시키기 위해서는 OS에 Java Runtime이 설치되어 있어야 한다.
위 그림에서 왼쪽 구성은
백엔드 애플리케이션을 실행하는 이미지에 소스 코드를 빌드하는 과정("jar"파일을 생성하는 과정)도 포함되어 있어서
이미지의 용량이 무거워지고 실행 과정 시간도 더 들게 된다.
이러한 경우 실행하기 위해 필요한 내용만 가지고 있는 이미지를 만들기 위해 "멀티 스테이지 빌드"기술을 사용한다.
위 그림에서 오른쪽 구성이 바로 "멀티 스테이지 빌드" 기술을 활용한 구성이다.
애플리케이션을 실행하기 위해 필요한 "jar"파일을 만드는 용도인 빌드 스테이지를 구성하고
만들어진 "jar" 파일만을 가지고 애플리케이션을 실행하는 용도인 실행 스테이지를 구성하여
두 과정을 둘로 나눠 최종 실행 이미지를 가볍게 만든다.
애플리케이션의 구성 정보 파일(application.properties) 내용 참고
PostgreSQL 데이터베이스에 접근(연결) 하기 위한 "URL", "DB명", "사용자명", "비밀번호" 등을 정의해 준다.
해당 값들은 바로 값을 지정할 수도 있지만 소스 코드에 계정 정보를 저장하는 경우에는 보안의 위험이 있다.
그래서 애플리케이션이 실행되는 시점(컨테이너 실행)에서 환경 변수(--env)를 통해서 값을 설정한다.
일반적으로 애플리케이션을 실행하는 시점에 OS의 시스템 환경 변수를 읽어와서 실행하도록 값을 설정한다.
환경 변수를 통해 지정된 값을 가져오는 명령어 형식
${시스템 환경 변수명 : 기본값}
애플리케이션을 실행시킬 때 해당 "시스템 환경 변수"의 값을 지정했다면
해당 "시스템 환경 변수"는 지정한 값으로 할당되어 사용되고
만약 "시스템 환경 변수"의 값을 지정하지 않았다면
해당 "시스템 환경 변수"의 값은 "기본값"으로 할당되어 사용된다.
예시
"spring.datasource.username=${DB_USERNAME:myuser}"
애플리케이션을 실행 시킬 때 환경 변수 "DB_USERNAME"의 값을 지정하는 명령어를 작성
". . . --env DB_USERNAME=anijy . . ."
그러면 "spring.datasource.username=anijy" 가 되고
만약 환경 변수를 지정하는 명령어를 생략했다면
"spring.datasource.username=myuser"가 된다.
spring.datasource.url | 데이터베이스에 연결(접근)하기 위한 JDBC URL |
spring.datasource.username | 데이터베이스에 연결(접근)하기 위한 사용자 이름 |
spring.datasource.password | 데이터베이스에 연결(접근)하기 위한 비밀번호 |
${DB_URL:localhost} | 데이터베이스의 URL |
${DB_PORT:5432} | 데이터베이스의 포트번호 |
${DB_NAME:mydb} | 데이터베이스명 |
${DB_USERNAME:myuser} | 데이터베이스 계정 사용자명 |
${DB_PASSWORD:mypassword} | 데이터베이스 계정 비밀번호 |
Dockerfile 내용 참고
빌드 스테이지
- 빌드용 베이스 이미지는 gradle 7.6.1 버전을 사용하고 현재 스테이지 명을 "build"라고 명시
- WORKDIR 지시어를 통해 이미지에서 작업할 위치 경로를 지정하여 이동
(이미지 내에서 현재 경로(작업할 경로)를 "/app" 디렉터리로 이동 ["/app" 디렉터리가 없으면 생성 후 이동]) - COPY 지시어를 통해 빌드 콘텍스트에 있는 모든 소스코드를 이미지의 "/app" 디렉터리로 복사
- RUN 지시어를 통해 gradle 빌드를 실행하여 애플리케이션 jar 파일 생성
실행 스테이지
- 실행용 베이스 이미지는 openjdk 11-jre-slim 버전을 사용
(jar 파일을 실행하기 위해 최소한의 필요한 내용만 가지고 있는 이미지) - WORKDIR 지시어를 통해 이미지에서 작업할 위치 경로를 지정하여 이동
(이미지 내에서 현재 경로(작업할 경로)를 "/app" 디렉토리로 이동 ["/app" 디렉터리가 없으면 생성 후 이동]) - COPY 지시어를 통해 빌드 스테이지 "build"에서 생성한 jar 파일을 현재 이미지 내부로 복사
- EXPOST 지시어로 사용하는 포트 번호 8080 명시
- ENTRYPOINT, CMD 지시어로 jar 파일을 실행
Dockerfile을 사용하여 백엔드 서버 구현하기
"anijy/leafy-backend:1.0.0" 이름으로 이미지 빌드하는 명령어
docker build -t anijy/leafy-backend:1.0.0 .
빌드한 이미지를 도커 허브에 업로드하는 명령어
docker push anijy/leafy-backend:1.0.0
이미지를 컨테이너로 실행할 때 네트워크도 지정해 주는 명령어 형식
docker run [--name 컨테이너명] [--network 네트워크명] [이미지명]
빌드한 이미지를 네트워크를 지정하여 컨테이너로 실행하는 명령어
docker run -d -p 8080:8080 -e DB_URL=leafy-postgres --name leafy --network leafy-network anijy/leafy-backend:1.0.0
"-e" 옵션과 "--env" 옵션은 동일
해당 옵션을 통해 환경 변수 "DB_URL"에 "leafy-postgres"로 값 할당
("leafy-postgres"는 데이터베이스 컨테이너명)
다른 환경 변수는 기본값으로 데이터베이스의 정보를 할당해 놓아서 환경 변수를 할당할 필요가 없음
(만약 데이터베이스의 정보가 변경된다면 해당 값들도 환경 변수 할당해줘야 함)
--
Vue.js 프론트엔드 컨테이너 구현하기
--
프론트엔드의 소스 코드를 빌드하면 "HTML", "CSS", "JS" 등의 파일들이 생성된다.
생성된 파일들은 이미지 내부의 root 디렉터리에 있는 "/dist" 디렉토리에 존재하게 된다.
이 또한 Spring Boot로 이미지를 빌드했던 것과 동일하게
멀티 스테이지 빌드를 사용하여 애플리케이션을 실행할 때에 필요한 "/dist" 디렉터리를 빌드 스테이지에서 생성한 다음
실행 스테이지에서는 "/dist" 디렉터리만 복사해 와서 실행한다.
Dockerfile 내용 참고
빌드 스테이지
- FROM 지시어를 사용하여 node 14버전을 베이스 이미지로 지정하고 스테이지 명을 "build"로 명시
(해당 이미지는 애플리케이션을 빌드하기 위한 이미지) - WORKDIR 지시어를 통해서 이미지 내부에 "/app" 디렉터리 경로로 위치 이동
("/app" 디렉터리가 없다면 생성 후 위치 이동) - COPY 지시어를 통해 의존 라이브러리 설치에 필요한 파일만 이미지 내부로 복사
- RUN 지시어를 통해 필요한 라이브러리 설치
- COPY 지시어를 통해 빌드 콘텍스트에 있는 소스코드들을 이미지 내부로 복사
- RUN 지시어를 통해 빌드하여 결과 파일들(실행에 필요한 파일들) 생성
(생성된 파일들은 "/dist" 디렉터리에 담긴다.)
실행 스테이지
- FROM 지시어를 통해 nginx 1.21.4-alpine 버전을 베이스 이미지로 지정
(해당 이미지는 애플리케이션을 실행하기 위한 이미지) - COPY 지시어를 통해 Nginx 설정 파일을 복사
- ENV 지시어를 통해 백엔드 애플리케이션과 통신(연결) 하기 위해 환경 변수를 지정한다.
("BACKEND_HOST"는 백엔드 컨테이너명 / "BACKEND_PORT"는 백엔드의 포트번호) - COPY 지시어를 통해 엔트리포인트 스크립트 파일을 복사
- RUN 지시어를 통해 복사한 엔트리포인트 스크립트 파일에 실행 권한 부여
- COPY 지시어를 통해 빌드 스테이지 "build"에서 생성한 "/dist" 디렉터리를 이미지로 복사
- 프론트엔드 애플리케이션이 사용하는 포트 번호 80을 명시
- ENTRYPOINT, CMD 지시어를 통해 실행
Dockerfile을 사용하여 프론트엔드 서버 구현하기
"anijy/leafy-frontend:1.0.0" 이름으로 이미지 빌드하는 명령어
docker build -t anijy/leafy-frontend:1.0.0 .
빌드된 이미지를 도커 허브에 업로드하는 명령어
docker push anijy/leafy-frontend:1.0.0
빌드된 이미지를 컨테이너로 실행하는 명령어
docker run -d -p 80:80 --name leafy-frontend --network leafy-network anijy/leafy-frontend:1.0.0
--
최종 애플리케이션 아키텍처
--
--
참고 및 출처
'Docker' 카테고리의 다른 글
Docker의 가상 네트워크 (+명령어) (0) | 2024.06.27 |
---|---|
docker 가상 네트워크 포트포워딩과 도커 DNS (+명령어) (0) | 2024.06.26 |
클라우드 네이티브 애플리케이션 (0) | 2024.06.24 |
멀티 스테이지 빌드 (Multi-Stage build) 란? (+실습) (0) | 2024.06.22 |
Dockerfile의 지시어 정리 (0) | 2024.06.21 |