Post

대용량 트래픽 경험해보기(4/14) - 초기 인프라 구축

AWS 서버 환경 구축

초기 인프라 구축의 경우 AWS의 EC2, ElastiCache, RDS 서비스를 이용해 인프라를 구축했다.

API 서버의 경우 컨테이너 기반의 환경을 구성하기 위해 Docker, docker-compose를 이용했으며 토큰 관리를 위해 ElastiCache의 Redis를 이용했다. RDS는 프리티어에서 사용할 수 있으며 가장 쉽게 접할 수 있는 MySQL를 이용해 초기 서버 환경을 구축 했다.

EC2 서버 스팩

아래 EC2 서버 스팩의 경우 Free Tier이기도 하며 SpringBoot 프로젝트 배포 시 최소한의 스팩에 맞도록 인스턴스 유형을 설정

OSAmazon Linux 2
인스턴스 유형t2.micro
vCPU1(1 Core)
Memory1GB
NetworkLow to Moderate

RDS 스팩

Free Tier 기준으로 초기 RDS 인스턴스 구성

DBMSMySQL 8.0.33
인스턴스 유형db.t3.micro
vCPU2
Memory1GB
Network최대 2,085Mbps
스토리지 유형20GiB(최대 60 IOPS)

API 서버 환경 구축

Docker 설치

1
2
3
4
5
6
7
8
# yum 업데이트
$ sudo yum update -y 

# amazon linux extras 저장소에서 Docker 설치한다.
$ sudo amazon-linux-extras install docker 

# Docker 서비스 실행
$ sudo service docker start

초기에 Docker를 설치하면 docker 그룹의 유저만 Docker를 관리할 수 있다. ec2-user도 Docker를 관리할 수 있도록 하기 위해 docker 그룹에 ec2-user를 추가해준다.

1
$ sudo usermod -a -G docker ec2-user
  • -a : 유저 추가
  • -G : 그룹 편집

설치 및 유저 수정 후 도커 컨테이너 정보를 확인하다보면 아래와 같이 권한 오류가 발생한다면 ssh를 재접속하면 된다.

Docker Pemission Error

1
2
# Docker 정보 확인
$ docker info

Docker 자동실행

시스템 부팅 시 Docker 서비스가 자동으로 시작되도록 설정한다.

1
$ sudo systemctl enable docker 

Docker Compose 설치

amazon linux extras에서 Docker Compose를 관리하지 않기에 아래와 같이 링크를 통해 설치한다.

1
$ sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose

설치가 완료되면 Docker Compose에 대한 권한이 없기에 아래와 같이 설치한 docker-compose 디렉토리의 권한을 변경해준다.

1
2
3
4
5
# docker-compose 권한 변경
$ sudo chmod +x /usr/local/bin/docker-compose

# docker-compose 버전 확인
$ docker-compose version

Nginx 테스트

docker-compose 설정

1
2
3
4
5
6
7
8
9
services:
  nginx:
    image: nginx:latest
    container_name: nginx # 사용할 컨테이너 이름
    environment:
      - TZ=Asia/Seoul
    ports:
      - 80:80 # 외부 포트 : 내부 포트
    restart: always

위와 같이 docker-compose.yml를 작성하면 nginx 이미지를 pull 한 뒤 컨테이너가 실행된다.

그렇지만, 브라우저로 접속하게되면 실행되지 않는다. 아래 명령어를 통해 docker 내부에 접속해 conf 파일을 보면

1
2
3
4
# nginx container 접속
$ docker exec -it nginx /bin/bash

$ cd /etc/nginx

Nginx Container 내부에 접속해 conf.d 내부를 보면 비어있을 것이다. 그리고 nginx.conf 내부를 보면 아래 코드가 있다.

1
2
3
4
5
...

include /etc/nginx/conf.d/*.conf;

...

위 부분은 server 설정 정보를 읽어오는 부분이고 관련된 파일이 없어 실행되지 않는다. 해결방법으로 기본 nginx 컨테이너를 실행하여 접속 후 관련 파일들을 가져온다.

1
2
3
4
5
6
7
8
# 기본 nginx 이미지를 기반으로 컨테이너 실행
$ docker run -d --name temp-nginx nginx

# temp-nginx의 nginx.conf 파일을 서버에 복사
$ docker cp temp-nginx:/etc/nginx/nginx.conf {nginx.conf 파일을 복사할 경로}

# temp-nginx의 conf.d 디렉토리를 서버에 복사
$ docker cp temp-nginx:/etc/nginx/conf.d {conf.d 디렉토리을 복사할 경로}

위 명령어를 실행하고 서버 경로에 들어가면 default.conf 파일을 볼 수 있다. 우선 기본 파일로 두고 위 파일 및 경로를 실제 운영할 Nginx 컨테이너 볼륨으로 연결해준다.

1
2
3
4
5
6
7
8
9
10
11
12
services:
  nginx:
    image: nginx:latest
    container_name: nginx # 사용할 컨테이너 이름
    environment:
      - TZ=Asia/Seoul
    ports:
      - 80:80 # 외부 포트 : 내부 포트
    volumes: # 외부(서버) 경로 : 컨테이너 내부 경로
      - {conf.d 디렉토리가 복사된 경로}/conf.d:/etc/nginx/conf.d
      - {nginx.conf 파일을 복사된 경로}/nginx.conf:/etc/nginx/nginx.conf
    restart: always

이 후 컨테이너를 실행하면 아래와 같이 정상적으로 실행되는 것을 볼 수 있다.

1
$ docker-compose up -d # -d : 백그라운드로 실행

http://{호스트 DNS or IP} 로 접속하면 아래와 같이 Nginx가 정상적으로 구동되는 것을 확인할 수 있다.

Nginx Default Page Success

Docker Compose 환경변수 파일 설정

환경변수 파일 생성 및 설정

docker-compose.yml 파일이 있는 디렉토리에 .env 파일 생성 후 필요한 파라미터 값을 설정해준다.

1
2
3
4
# .env 파일
CPUS_LIMIT=1
MEMORY_LIMIT=128m
MEMORY_RESERVE=32m

docker-compose 파일에서 .env 파일에 설정한 변수를 가져와 사용할 수 있다.

1
2
3
4
5
6
7
# docker-compose.yml
services:
  nginx:
    cpus: ${CPUS_LIMIT}
    mem_limit: ${MEMORY_LIMIT}
    mem_reservation: ${MEMORY_RESERVE}
    ...

환경변수 파일 설정 후 아래 명령어를 통해 정상적으로 설정되었는지 확인 할 수 있다.

1
$ docker-compose config

커스텀 환경변수 파일 생성 및 설정

만약 docker-compose.yml과 다른 폴더 혹은 다른 파일명으로 환경설정 파일을 사용하기 위해서는 아래와 같이 직접 명령어를 통해 컨테이너를 실행해야 한다.

1
2
$ docker-compose --env-file {적용할 환경변수 파일 경로}{적용할 환경변수 파일명} up # 컨테이너 시작
$ docker-compose --env-file {적용할 환경변수 파일 경로}{적용할 환경변수 파일명} down # 컨테이너 종료

별도 환경변수 파일 설정 후 아래 명령어를 통해 정상적으로 설정되었는지 확인 할 수 있다.

1
$ docker-compose --env-file {적용할 env 파일 경로}{적용할 env 파일명} config

컨테이너 시작/종료 명령어를 통해 설정하지 않고 docker-compose 파일에 env_file 옵션을 이용해서 서비스별 환경변수를 설정할 수 있다고 하는데 테스트 해보았을 때 적용되지 않는다…

Docker Compose Container 자동실행

시스템 부팅 시 docker-compose로 실행하는 컨테이너를 자동으로 시작되도록 설정한다.

서비스 파일 생성 및 설정

1
2
3
4
# 시스템 서비스 관리 경로로 이동
$ cd /etc/systemd/system
# 실행할 컨테이너 서비스 파일 생성
$ sudo vi {서비스명}.service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Unit]
Description={서비스 파일 설명}
Requires=docker.service
After=docker.service

[Service]
RemainAfterExit=yes
Restart=on-failure
RestartSec=5s
WatchdogSec=30s
WorkingDirectory=/my-app
ExecStart=/usr/local/bin/docker-compose up 
ExecStop=/usr/local/bin/docker-compose down 

[Install]
WantedBy=multi-user.target
  • [Unit]
    • Requires : Docker 서비스 필수 설정
    • After : Docker 서비스 실행 후 실행
  • [Service]
    • RemainAfterExit : docker-compose를 백그라운드로 실행(docker-compose up)하지 않아도 실행 종료 후 active 상태로 유지
    • Restart : 서비스가 비정상 종료(exit code ≠ 0, 시그널 종료 등)된 경우에만 재시작
    • RestartSec : 서비스가 종료된 후, 재시작되기 전에 대기 시간 설정 (단위 : min, s, ms)
    • WatchdogSec : 서비스가 응답하지 않거나 오류 상태에 빠졌을 때 systemd가 이를 자동으로 감지하고 서비스를 재시작 (단위 : min, s, ms)
    • WorkingDirectory : docker-compose.yml 파일 디렉토리 경로
    • ExecStart : docker-compose 실행 명령어
    • ExecStop : docker-compose 중지 명령어
  • [Install]
    • WantedBy=multi-user.target : 시스템이 멀티 사용자 모드로 부팅될 때 해당 서비스가 자동으로 시작되도록 설정

Elasticache - Redis

Refresh Token의 경우 고유 방문자수가 최대 886,440명 기준 약 300MB 정도의 크기가 스토리지가 필요하다. Refresh Token 저장소로 RDB 혹은 In-Memory DB가 있다. 처음에는 아래의 이유로 RDB에 저장하려고 했었다.

  1. RDB 여유 공간 이용 가능
  2. In-Memory DB 사용시 별도 서비스 이용으로 비용 발생

여러 개발 블로그를 찾아보았지만, 대부분 Redis와 같은 In-Memory DB에 저장하는 것을 권장한다. 이유는 아래와 같다.

  1. 데이터의 만료일을 지정
  2. 뛰어난 조회 성능으로 Reissue 호출 빈도가 높아지면 병목현상 방지
  3. 특정 토큰 탈취의 경우 블랙리스팅의 필요성

이러한 이유로, In-Memory DB인 Redis를 구성하기로 했다.

Token 관리용 Redis 서버

최대 886,440명의 사용자 기준으로 최소 300MB정도의 메모리가 필요하다. AWS에서 In-Memory DB 서비스로 ElastiCache, Amazon MemoryDB for Redis가 있다.

서비스 구분노드vCPU메모리(GIB)시간당 요금월 요금
Elasticachecache.t2.micro10.555USD 0.026USD 18.72
Amazon MemoryDB for Redisdb.t4g.small21.37USD 0.07USD 50.4

필요한 최소 메모리 기준 및 낮은 비용으로 ElastiCache를 사용하기로 했으며, ElastiCache 구성시에 우선 Refresh 토큰 저장을 목적으로 두고 있기에 별도 Slave 노드는 구성하지 않고 Master 노드만 구성했다.

Redis 접속을 위한 Redis CLI 설정

Redis CLI 설치

1
2
3
4
5
$ sudo wget https://download.redis.io/redis-stable.tar.gz
$ tar xvzf redis-stable.tar.gz
$ cd redis-stable
$ make
$ sudo cp /src/redis-cli /usr/bin/

Redis CLI 접속

1
$ redis-cli -h {ElastiCache Redis OSS 기본 엔드포인트(포트제외)} -p 6379

redis-cli 접속 후 ping 입력 → PONG 응답이 보이면 된다.

Docker Project 배포

Local → Docker Hub 배포

  1. 프로젝트 루트 경로 이동 후 빌드
1
$ ./gradlew clean build
  • $ ./gradlew clean build -x test → test를 Skip하고 Build
  1. Local Docker에 이미지 생성
1
$ docker build --build-arg DEPENDENCY=build/dependency -t {Docker 사용자}/{이미지명}:{TAG} --platform linux/amd64 .
  1. Docker Push
1
$ docker push {Docker 사용자}/{이미지명}:{TAG}

프로젝트 컨테이너 실행

  1. Docker Project Image 가져오기
1
$ docker pull {Docker 사용자}/{이미지명}:{TAG}
  1. docker-compose 작성
1
2
3
4
5
6
7
8
services:
서비스명: 
  container_name: {컨테이너 명}
  image: {Docker 사용자}/{이미지명}:{TAG}
  ports:
    - "8080:8080"
  environment:
    - SPRING_PROFILES_ACTIVE={SPRING_PROFILES_ACTIVE 환경변수}
  1. 컨테이너 실행
1
$ docker-compose up -d

Nginx Proxy 설정

  1. default.conf 파일 수정
1
2
3
4
5
6
location / {
      proxy_pass http://{호스트 DNS 혹은 IP}:8080;
      proxy_set_header X-Real_IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $host;
}
  • Nginx 컨테이너 재시작
This post is licensed under CC BY 4.0 by the author.