3Tier 아키텍처란 무엇이며
어떻게 구성하는 것일까?
3 Tier 아키텍처
--
3 Tier 아키텍처는
소프트웨어 애플리케이션을 3 가지 계층으로 나누어 설계하는 구조를 의미한다.
(보통 웹 애플리케이션은 3 가지 종류의 서버로 구성된다.)
각 계층마다 특정한 기능을 담당하며, 서로 분리되어 독립적으로 동작한다.
이러한 구조는 시스템의 확장성, 유지 보수성, 관리 용이성을 높이는데 도움이 된다.
1. 프레젠테이션 계층 (Presentation Tier)
- 역할 : 사용자와 상호작용하는 인터페이스를 제공
- 구성 요소 : 웹 브라우저, 모바일 앱, 데스크 탑 애플리케이션 등
- 주요 기능 : 데이터를 사용자에게 표시하고, 사용자의 입력을 받아 비즈니스 계층으로 전달
해당 계층은 프론트엔드인 웹 서버를 의미한다.
2. 비즈니스 계층 (Business Logic Tier)
- 역할 : 애플리케이션의 핵심 로직을 처리
- 구성 요소 : 웹 서버, 애플리케이션 서버 등
- 주요 기능 : 프레젠테이션 계층으로부터 입력을 받아 처리 / 데이터 계층과 상호작용
해당 계층은 백엔드 애플리케이션을 의미한다.
3. 데이터 계층 (Data Tier)
- 역할 : 데이터를 영구적으로 저장 및 관리
- 구성 요소 : 데이터베이스 관리 시스템(DBMS), 파일 시스템 등
- 주요 기능 : 비즈니스 계층으로부터 요청받은 데이터를 처리
해당 계층은 데이터베이스 서버를 의미한다.
위 그림의 구조가 일반적인 3 Tier 구조이다.
(클라이언트가 직접 백엔드에 접근하지 못하고 웹 서버를 통해서만 접근이 가능하다.)
이때 Nginx의 Proxy 기능을 활용해서 웹 서버가 백엔드 애플리케이션에 접근할 수 있도록 정의했다.
--
프록시 (Proxy)
--
프록시는
클라이언트와 서버 사이에 위치하며
클라이언트 요청을 받아서 서버로 전달하고,
서버의 응답을 받아서 클라이언트에게 다시 전달하는 중계자 역할을 한다.
즉, 네트워크의 요청을 전달해 주는 기술이다.
주로 요청 및 응답, 보안, 로드밸런싱, 캐싱 등의 목적으로 사용된다.
Proxy를 사용하는 이유
- 보안 강화 : 클라이언트가 직접 백엔드 서버에 접근하지 못하도록 하여, 보안 취약점을 감소시킨다.
- CORS 문제 해결 : 프록시 서버를 통해 클라이언트와 서버 간의 크로스 도메인 요청 문제를 해결
- 로드밸런싱 : 프록시 서버가 여러 서버로 요청을 분산시켜 서버의 부하를 줄임
- SSL 종료 : 서버에서 SSL을 처리하여 백엔드 서버의 부하를 줄임
- 캐싱 : 프록시 서버에서 응답을 캐싱하여 성능을 향상
위 3 Tier 구조처럼
Nginx의 proxy 기술을 활용하면
특정 경로로 접근하는 요청은 원하는 곳으로 다시 전달해 줄 수 있다.
즉, 클라이언트가 요청을 하면 웹 서버는 해당 요청을 가지고 백엔드로 전달해 준다.
Nginx에서 Proxy 설정하는 방법
nginx.conf 파일
server {
listen 80;
server_name _;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location /api/ {
proxy_pass http://leafy:8080;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
위 내용은
Nginx 서버로 전달되는 요청 중에
"/api" 로 시작되는 모든 요청은 해당 proxy_pass에 작성된 주소로 요청을 다시 전달하게 된다.
즉, "/api"로 시작되는 요청을 하면 서버로 요청을 전달하게 도와주는 것이다.
추가적으로
API의 응답을 캐싱하거나 부하를 관리하고 접근 로그를 관리하는 것처럼
다양한 추가 기능을 Proxy를 통해서 활용할 수 있다.
--
Nginx의 Proxy 적용하기
--
웹 서버에 Nginx의 Proxy 적용하는 방법
1. nginx.conf 파일을 생성하고 proxy를 정의해 준다.
위 코드는
어떤 경로로 왔을 때 ("/" 또는 "/api") 어떠한 동작을 할지를 정의하는 파일이다.
"/" 경로로 요청이 오면
"/usr/share/nginx/html" 디렉터리에 있는 index.html 파일을 제공하고
"/api" 경로에 요청이 오면
proxy 기능을 활용하여 "http://leafy:8080"로 요청을 전달하게 된다.
"api" 경로에 대한 요청을 처리하는 proxy를 보면
"http://leafy:8080"으로 주소가 고정되어 작성되어 있다.
그렇게 되면 만약 백엔드 서버의 주소(컨테이너명)가 변경되면
해당 소스코드의 값도 직접 수정해줘야 한다.
이렇게 주소는 환경마다 바뀔 수 있기 때문에
컨테이너를 실행하는 시점에 환경 변수로 정의하는 방법을 사용하는 것이 좋다.
2. Nginx 웹 서버의 설정 파일인 "nginx.conf" 파일을 기존의 nginx 이미지에 덮어씌우기 위해 dockerfile에 지시어 추가하기
그러면 nginx 서버가 실행될 때 덮어씌운 설정 파일을 가지고 실행하게 된다.
결과적으로 이미지 빌드를 하게 되면
Nginx의 proxy가 적용된 이미지로 빌드가 된다.
정리
- 클라이언트가 웹 서버에게 "/" 주소 요청
- 웹 서버는 정적인 자료 html파일 클라이언트에게 응답
- 클라이언트가 웹 서버에게 "/api"로 시작하는 주소 요청
- 웹 서버는 요청받은 주소를 proxy하여 백엔드 애플리케이션으로 요청
- 벡엔드 애플리케이션은 요청 받은 주소로 로직 수행 (데이터가 필요하면 DB 서버와 상호작용) 후 웹 서버로 응답
- 웹 서버는 벡엔드 애플리케이션으로부터 응답받은 결과를 클라이언트에게 응답
--
환경 변수를 활용하여 동적 서버 구성하기
--
위 방법에서는
Nginx.conf 파일에 Nginx 서버 프록시 설정을 아래와 같이 했다.
location /api/ {
proxy_pass http://leafy:8080;
}
이렇게 프록시로 연결할 주소를 "http://leafy:8080"으로
백엔드 주소(컨테이너명)를 "leafy"라고 가정하고 작성하여
만약 백엔드 주소가 변경되면 해당 코드도 직접 변경해 주고 소스코드 내용이 변경되었기 때문에 이미지를 다시 빌드해서 새로 빌드한 이미지로 실행해줘야 한다.
이렇게 배포 환경별로 달라질 수 있는 값들은 이미지를 컨테이너로 실행하는 시점에서
환경 변수를 지정하는 방식을 사용하여 해결할 수 있다.
평소에 컨테이너를 실행할 때 "-e" 옵션을 사용해서 환경변수를 지정한 것과 동일한 방법이다.
[ -e DB_URL = leafy-postgres ]
환경 변수를 지정하는 방법
${환경변수}
Nginx.conf 파일에 Nginx 서버 프록시 설정에서 백엔드 주소를 환경 변수로 작성하기
location /api/ {
proxy_pass http://${BACKEND_HOST}:${BACKEND_PORT};
}
이렇게 기존 "leafy"를 ${BACKEND_HOST}로 변경하고 "8080"을 ${BACKEND_PORT}로 변경하여 정의했다.
즉, 이렇게 환경 변수처리를 하면
컨테이너를 실행할 때 해당 환경 변수에 원하는 값을 지정하여 사용할 수 있게 된다.
중요!!!
Spring Boot 애플리케이션 같은 경우에는
Spring Boot 에서 자체적으로 환경 변수를 자동으로 찾아서 값을 지정해 준다.
즉, 컨테이너를 실행할 때 "-e" 옵션을 작성해 주면 자동으로 해당 환경 변수를 찾아서 값을 적용해 준다.
다만 nginx 애플리케이션 같은 경우에는
이러한 기능이 별도로 존재하지 않기 때문에 직접 환경 변수로 지정한 부분을 찾아서
해당 환경 변수의 값을 직접 할당해 주거나 "-e" 옵션으로 정의한 값으로 할당해 주는 작업이 추가로 필요하다.
해당 작업은 dockerfile 또는 docker-entrypoint.sh 파일에 정의하여 적용해 주면 된다.
nginx.conf 파일에 환경 변수를 적용해 주기 위해 docker-entrypoint.sh 파일을 생성하고 추가 코드 작성하기
현재 "docker-entrypoint.sh"파일은 컨테이너가 실행될 때 실행되는 스크립트 파일로
환경 변수의 값을 Nginx 설정 파일 (nginx.conf)에 적용하고 Nginx 서버를 실행하는 작업을 수행한다.
"set -e"
스크립트 실행 중 오류 발생 시 즉시 스크립트를 종료
"envsubst"
환경 변수를 치환(적용)하는 유틸리티 명령어
"${BACKEND_HOST} ${BACKEND_PORT}"
적용할 환경 변수 목록을 지정
"< /etc/nginx/conf.d/default.conf.template"
여기서 "<"는 입력 리디렉션을 의미하여 해당 "template" 파일을 읽어서 "envsubst" 명령어에 입력으로 전달
"> /etc/nginx/conf.d/default.conf"
여기서 ">"는 출력 리디렉션을 의미하여 "envsubst" 명령어의 출력 결과를 해당 "default.conf" 파일에 저장
즉, "template"에서 해당 환경 변숫값을 적용하고 새로운 Nginx 설정 파일 "default.conf"로 저장한다.
"exec"
스크립트의 마지막 명령어로 다음으로 실행할 명령어를 실행한다.
"$@"
스크립트에 전달된 모든 인자를 의미하고
dockerfile에서 "ENTRYPOINT" 또는 "CMD" 지시어에 작성된 명령어를 가리킨다.
즉, dockerfile에서 "ENTRYPOINT" 또는 "CMD" 지시어에 작성된 명령어를 실행한다.
dockerfile 내용
"-e" 옵션으로 정의한 환경 변수를 적용하기 위해서는 빨간 박스로 표시한 지시어들이 추가로 필요하다.
- 환경 변수 값으로 정의해줘야 하는 "nginx.conf"파일을 이미지의 "template"파일로 복사
(해당 파일은 바로 설정되는 설정파일이 아니고 환경 변수의 내용을 변경한 다음에 설정 파일로 저장하기 위한 템플릿 역할을 하는 파일로 사용된다.) - ENV 지시어로 환경 변수의 기본값을 미리 지정해 준다.
(컨테이너 실행 시 "-e" 옵션으로 직접 지정해 줄 수 있지만 해당 옵션을 사용 안 했을 경우의 기본값을 지정하는 느낌) - 환경 변수를 지정해 주기 위한 "docker-entrypoint.sh"파일을 이미지의 "/bin/" 경로에 복사한다.
- RUN 지시어를 통해 복사한 "docker-entrypoint.sh"파일에서 파일을 실행할 수 있는 권한을 부여한다.
- ENTRYPOINT 지시어를 통해 "docker-entrypoint.sh"파일을 실행시킨다.
5번에서 실행한 "docker-entrypoint.sh"파일에서 순차적으로 명령어를 수행하고
마지막에 작성된 exec "$@"를 통해 dockerfile에서 CMD 지시어에 작성된 명령어가 마지막으로 실행된다.
dockerfile에서 ENTRYPOINT 지시어에서 "docker-entrypoint.sh" 파일을 실행하면서
CMD에 작성된 명령어를 ENTRYPOINT의 옵션으로 전달된다.
"$@"는 옵션으로 제공받은 값을 실행하는 명령어로
옵션으로 제공받았던 "nginx -g daemon off" 명령어가 실행된다.
이미지 빌드 명령어
docker build -t leafy-front:4.0.0-env .
빌드한 이미지를 컨테이너로 실행하면서 환경변수 "BACKEND_HOST"를 변경된 백엔드 주소(컨테이너명)인 "leafy-backend"로 지정하는 명령어
docker run -d -e BACKEND_HOST=leafy-backend -p 80:80 --name leafy-front --network leafy-network leafy-front:4.0.0-env
--
참고 및 출처
'Docker' 카테고리의 다른 글
도커 컴포즈 (Docker Compose) (0) | 2024.07.03 |
---|---|
컨테이너 최적화하기 (컨테이너에 CPU, 메모리 한도 지정) (0) | 2024.07.02 |
이미지 빌드시 캐싱 활용하기 (0) | 2024.06.30 |
이미지 레이어 관리하기 (0) | 2024.06.29 |
도커 볼륨, 마운트, 바인드 마운트 (+명령어) (0) | 2024.06.28 |