1. 개념 및 용어 정리

[1] DNS

DNS 는 도메인에 알맞는 IP 주소로 변환을 해주는 역할을 하는 서버이다. 유저는 IP보다 문자로 표현된 도메인 주소를 더욱 잘 기억하고 활용하기 때문에 도메인을 사용한다. 도메인을 IP 로 변환하는 과정은 아래와 같다.

  1. /etc/hosts 파일에 정적으로 설정한 정보를 조회한다.
  2. local DNS Cache 를  조회한다.
  3. /etc/resolv.conf 파일에 설정된 정보를 기반으로 DNS 서버에 질의한다.
  4. DNS server 는 정보가 있으면 IP 주소를 반환하고, 없으면 본인의 상위 DNS 서버에게 질의하여 정보를 받아온다.
  5. 도메인에 해당하는 IP 를 DNS Cache 에 추가한다.

 

[2] reverse-proxy

Reverse-Proxy 는 하나 이상의 웹 서버 앞에 위치하여 클라이언트 요청을 대리로 전달하는 서버이다. 일반적인 Proxy server 는 LAN → WAN 요청을 대리로 수행하는 반면, Reverse-Proxy 는 WAN → LAN 의 요청을 대리한다. 클라이언트의 요청을 대리로 전달함으로써 아래와 같은 이점이 존재한다. 

  • 공격으로부터 보호 : WAS 의 서버 스펙(port, ip, URL)을 을 숨길 수 있어 DDoS 와 같은 표적 공격을 활용하기 어렵다.
  • 부하분산 : 서버의 부하 분산을 제공하여 WAS 의 scale-out 환경을 구성할 수 있어 서비스의 안정성을 제공한다.
  • 캐싱 : 정적 컨텐츠를 캐싱할 수 있어 성능 향상에 이점이 있다.

 

[3] NGINX

 reverse-proxy 종류 중 하나로 싱글 스레드 기반 이벤트 루프 방식으로 동작한다. 즉, Queue 에 요청을 쌓아두고 하나씩 처리하며, async,non-blocking 방식으로 처리하므로  다수의 커넥션을 처리할 때 빠르다.

 NGINX 는 작은 데이터를 대량으로 전송하거나 HDD read/write 가 발생하지 않는 경우에 적합하다. 실제 데이터를 I/O 는 OS 와 H/W 사이에서 실행되어야 하기 때문에 데이터 I/O 작업이 큰 처리가 길어지면 Queue 에 요청이 많이 쌓여 성능 저하를 초래할 수 있다. 그러므로 복잡한 연산 처리, 동영상 데이터 처리, DB 작업은 WAS 에게 위임하여 처리하는 것이 적합하다. (reverse-proxy 를 구성하여 scale-out 환경이 가능하므로 WAS 에게 위임하는 것이 더욱 적합하다.)

 

[4] TLS (Transport Layer Security)

TLS 는 두 개의 통신하는 애플리케이션 간에 인증, 암호화 서비스를 제공하는 인터넷 보안 프로토콜이다. TLS 는 SSL 인증서를 사용하여 보안이 유지되는 HTTPS 연결에 사용된다. SSL 인증서는 인터넷을 통해 전송되는 데이터를 암호화하여 비밀번호, 신용카드 번호 등과 같은 민감한 정보를 보호한다.

 

 

2. 3-tier 구성하기

  1. public EC2 추가 구성
    1. swap 영역 생성
    2. gradle 설치
    3. github ssh 연동
    4. github submodule 설정
      1. git submodule 등록 방법
  2. TLS 설정
    1. TLS 인증서 발급받기 (by. letsencrypt)
    2. nginx container 생성
      1.  https 설정 & 프록시 설정
  3. DB 서버 구성
    1. mysql docker container 생성
  4. 운영 환경 프로퍼티 분리
    1. property 분리해서 빌드하기
    2. buld.gradle depdendency 추가 + resources 파일 설정
    3. 서버 실행
    4. URL 확인
  5. script practice

 

[1] public EC2 추가 구성

(1) swap 영역 생성

  • AWS t2.micro 제공 RAM 이 1GB 이므로 gradle 빌드 시 서버가 멈출 수 있어 swap 영역 생성하여 활용

 

a. swap 상태 확인 : 미설정 시

$ sudo swapon -s # 스왑 상태 확인 명령어
$ free  # 메모리 상태 확인 명령어

               total        used        free      shared  buff/cache   available
Mem:          972024      549984      110216         252      311824      263844
Swap:              0           0           0

 

b. swap partition 생성

 

(1) root file system 에 swapfile 생성

  • 128MB 씩 16번을 /dev/zero 로 부터 읽어 /swapfile 에 저장 (128MB  * 16 = 2GB)
$ sudo dd if=/dev/zero of=/swapfile bs=128M count=16

 

(2) swapfile 권한 부여

$ sudo chmod 600 /swapfile

 

(3) swap 영역 설정

  • mkswap [file] : swap partition 생성 명령어
$ sudo mkswap /swapfile

 

(4) swap 공간에 swapfile 추가

  • swapon [file] : swapfile 활성화 명령어
$ sudo swapon /swapfile

 

(5) 스왑 공간 확인

$ sudo swapon -s

Filename	Type		Size		Used		Priority
/swapfile	file		2097148		329472		-2


$ free

               total        used        free      shared  buff/cache   available
Mem:          972024      551056      106940         252      314028      262784
Swap:        2097148      329472     1767676

 

(6) 부팅시 swapfile 활성화 설정

  • /etc/fstab : linux 에서 file system 정보를 저장하고 있는 파일이며, 부팅시 마운트 정보를 가지고 있음
  • 입력 내용 : [파일시스템장치][마운트포인트][파일시스템 종류][옵션][dump설정][파일점검옵션]
$ sudo vim /etc/fstab

/swapfile swap swap defaults 0 0

 

c. swap partition 제거

(1) 할당된 swap 공간 제거

$ sudo swapoff /swapfile

 

(2) swapfile 제거

sudo rm -rf /swapfile

 

(3) 부팅시, swapfile 활성화 설정 제거

$ sudo vim /etc/fstab

/swapfile swap swap defaults 0 0 # 해당 내용 제거

 

 

(2) install gradle

a. 설치 Gradle version 확인

 

b. 설치 버전 Gradle zip file 다운로드

# wget unzip 없을 경우 해당 package 설치

$ wget https://services.gradle.org/distributions/gradle-7.5.1-bin.zip -P /tmp
$ sudo unzip -d /opt/gradle /tmp/gradle-7.5.1-bin.zip

cunzip 되었는지 확인

c. 환경 변수 설정 (+ 권한 변경)

$ sudo vim /etc/profile.d/gradle.sh

export GRADLE_HOME=/opt/gradle/gradle-7.5.1
export PATH=${GRADLE_HOME}/bin:${PATH}


$ sudo chmod +x /etc/profile.d/gradle.sh
$ source /etc/profile.d/gradle.sh

 

d. Gradle  version 확인

gradle -v

 

 

(3) github ssh 등록

a. ssh 키생성

  • 비밀키 경로 : ~/.ssh/id_rsa
  • 공개키 경로 : ~/.ssh/id_rsa.pub
$ ssh-keygen

 

b. Github 계정 SSH 공개키 등록

 

(4) github submodule 설정

application.properties 의 경우, secret-key 를 관리하는 경우도 있어 보안상으로 private 을 통해 관리해야 한다.

  • github private repository 생성 → application.properties 설정 파일 커밋
  • github submodule 을 통해 특정 경로에 private repository 참조하도록 설정
    • 이후 소스코드 받을 때 submodule 까지 clone
$ git submodule add [자신의 private 저장소] ./src/main/resources/config
$ git clone --recurse-submodules [자신의 프로젝트 저장소]

 

  • 설정 파일 내용 변경된 경우, submodule 만 커밋하는 방법
$ git submodule foreach git pull origin main
$ git submodule foreach git add .
$ git submodule foreach git commit -m "commit message"
$ git submodule foreach git push origin main

 

[2] TLS 설정

(1) TLS 인증서 발급받기

  • HTTP 는 평문 통신이므로 스니핑(snipping) 하여 패킷을 가로채 확인할 우려가 있어 HTTPS 가 필요하다.
  • HTTPS 를 사용하기 위해서는 TLS 인증서가 필요하므로 letsencrypt 무료 TLS 인증서 활용
$ docker run -it --rm --name certbot \
  -v '/etc/letsencrypt:/etc/letsencrypt' \
  -v '/var/lib/letsencrypt:/var/lib/letsencrypt' \
  certbot/certbot certonly -d 'yourdomain.com' --manual --preferred-challenges dns --server https://acme-v02.api.letsencrypt.org/directory

 

 

 

  • 인증서 생성 후 유효한 URL 확인을 위해 DNS TXT 레코드 추가
  • DNS 설정 사이트에서 DNS TXT 레코드 추가 후, dig 명령어를 통해

내도메인.한국에서 DNS 설정

 

 

  • 생성한 인증서를 확용해 Reverse Proxy 에 TLS 설정 및 Dockerfile 가 위치한 현재 경로로 복사
$ cp /etc/letsencrypt/live/[도메인주소]/fullchain.pem ./
$ cp /etc/letsencrypt/live/[도메인주소]/privkey.pem ./

 

 

  • Dockerfile 설정
FROM nginx

COPY nginx.conf /etc/nginx/nginx.conf 
COPY fullchain.pem /etc/letsencrypt/live/[도메인주소]/fullchain.pem
COPY privkey.pem /etc/letsencrypt/live/[도메인주소]/privkey.pem

 

 

  • nginx.conf
events {}

http {       
  upstream app {
    server 172.17.0.1:8080;
  }
  
  # Redirect all traffic to HTTPS
  server {
    listen 80;
    return 301 https://$host$request_uri;
  }

  server {
    listen 443 ssl;  
    ssl_certificate /etc/letsencrypt/live/[도메인주소]/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/[도메인주소]/privkey.pem;

    # Disable SSL
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    # 통신과정에서 사용할 암호화 알고리즘
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;

    # Enable HSTS
    # client의 browser에게 http로 어떠한 것도 load 하지 말라고 규제합니다.
    # 이를 통해 http에서 https로 redirect 되는 request를 minimize 할 수 있습니다.
    add_header Strict-Transport-Security "max-age=31536000" always;

    # SSL sessions
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;      

    location / {
      proxy_pass http://app;    
    }
  }
}

 

 

  • nginx docker container 실행
    • docker build -t cooper/reverse-proxy:0.0.1 . : Dockerfile 기반으로 이미지 빌드
    • 빌드한 이미지를 기반으로 컨테이너 실행
$ docker stop proxy && docker rm proxy
$ docker build -t brainbackdoor/reverse-proxy:0.0.2 .
$ docker run -d -p 80:80 -p 443:443 --name proxy cooper/reverse-proxy:0.0.2

 

 

[3] DB 서버 구성

(1) 운영 DB 구성 (by. mysql container)

  • private EC2 에 해당 이미지 기반 컨테이너 생성
# id: root, pw: masterpw
$ docker run -d -p 3306:3306 brainbackdoor/data-subway:0.0.1

 

 

[4] 운영 환경 프로퍼티 분리

(1) property 설정하여 빌드하기

SPRING_PROFILES_ACTIVE=local ./gradlew clean build

 

(2) buld.gradle depdendency 추가 + resources 파일 설정

  • 운영 환경 mysql 를 관리하기 위한 의존성 추가
  • sourceSets 를 통한 main resources directory 경로 설정
dependencies {
	runtimeOnly 'mysql:mysql-connector-java'
}

sourceSets {
	main {
		resources {
			srcDir "src/main/resources/config"
		}
	}
}

 

 

(3) 서버 실행하기

  • -Dspring.profiles.active : 활성할 프로퍼티 설정
  • -Dspring.config.location: 사용할 property 파일 경로

$ nohup java -jar -Dspring.profiles.active=prod [jar파일명] 1> [로그파일명] 2>&1  &

 

(4) URL 접속

 

 

 

[5] script practice

# ./color.sh

## 변수 설정
txtrst='\033[1;37m' # White
txtred='\033[1;31m' # Red
txtylw='\033[1;33m' # Yellow
txtpur='\033[1;35m' # Purple
txtgrn='\033[1;32m' # Green
txtgra='\033[1;30m' # Gray
# ./colorTest.sh
source $(dirname $0)/color.sh

echo -e "$(dirname $0)"
echo -e "${txtylw}=======================================${txtrst}"
echo -e "${txtgrn}  << 색깔 변수들 🧐 >>${txtrst}"
echo -e "${txtrst}txtrst='\033[1;37m' # White${txtrst}"
echo -e "${txtred}txtred='\033[1;31m' # Red${txtrst}"
echo -e "${txtylw}txtylw='\033[1;33m' # Yellow${txtrst}"
echo -e "${txtpur}txtpur='\033[1;35m' # Purple${txtrst}"
echo -e "${txtylw}txtgrn='\033[1;32m' # Green${txtrst}"
echo -e "${txtgra}txtgra='\033[1;30m' # Gray${txtrst}"
echo -e "${txtylw}=======================================${txtrst}"

 

# ./parameterTest.sh
source $(dirname $0)/color.sh

# 변수 선언
EXECUTION_PATH=$(pwd) # 해당 스크립트를 실행시킨 경로
SHELL_SCRIPT_PATH=$(dirname $0) # 해당 스크립트가 실행된 현재 경로
FIRST=$1 # 1 번째 입력 값
SECOND=$2 # 2 번째 입력 값

# 변수 출력
echo -e "${txtylw}============= 변수 출력 ===============${txtrst}"
echo -e "${txtgrn} EXECUTION_PATH: ${EXECUTION_PATH}"
echo -e "${txtgrn} SHELL_SCRIPT_PATH: ${SHELL_SCRIPT_PATH}"
echo -e "${txtgrn} FIRST: ${FIRST}"
echo -e "${txtgrn} SECOND: ${SECOND}"
echo -e "${txtgrn} \$#: $#"  # 입력 인자 개수

## 조건 설정
if [[ $# -ne 0 ]] # 입력 인자 개수($#) 가 0이 아닌 경우(-ne)
then
    echo -e "${txtylw}=======================================${txtrst}"
    echo -e "${txtgrn}  << 스크립트 🧐 >>${txtrst}"
    echo -e ""
    echo -e "${txtgrn} $0 브랜치이름 ${txtred}{ prod | dev }"
    echo -e "${txtylw}=======================================${txtrst}"
    exit
fi

 

# ./functionTest.sh
source $(dirname $0)/color.sh

function pull() {

  echo -e ">> Pull Request 🏃"
  git pull origin master
}

pull;

 

Reference