본문 바로가기

엔지니어가 되자/데이터엔지니어링

AWS EC2에 Docker 환경 배포 작업기 - WAS(Django) / Web Server(Nginx) / Mysql DB

 

1. 작업 배경

 사이드 프로젝트로 Django 백엔드 웹사이트를 개발까지는 했는데 이걸 어떻게 배포해야하나 하면서 이리저리 삽질하면서 AWS EC2에 올린 작업기다. 혼자 알아본 내용으로만 끝났으면, 그냥 django runserver로 WAS만 올리고 끝났을텐데 동쪽의 귀인 이누가 나타나 같이 작업하면서 Nginx 웹서버와 Mysql DB 까지 도커라이징해서 배포하는 과정을 진행할 수 있었다. 그 과정을 남겨보았다.

 

 

2. 작업 계획

- 서버 구동: AWS ec2로 Ubuntu 서버를 띄운다.

- WAS 구동: 깃허브에 있는 django 소스를 클론해와서 django WAS를 도커 이미지로 빌드하고 해당 이미지 컨테이너를 띄운다.

    - WAS 구동은 gunicorn을 사용

    - python3.10 버전 buster 이미지를 풀링해서 사용

- WEB SERVER 구동: Nginx 웹서버 이미지를 풀링해와서 이미 띄워둔 Django WAS에 연동해서 클라이언트 요청시 django WAS에 접속할 수 있도록 컨테이너를 띄운다.

- Mysql DB 구동: ubuntu 이미지를 풀링해서 컨테이너를 띄우고 mysql db를 그 위에 설치해서 구동한다.

 

 처음에는 그냥 ec2 위에 의존성 설치를 하면서 진행했는데, 개별 애플리케이션들을 하나의 서버에 설치하다보면 개별 애플리케이션이 필요한 의존성간 충돌이 굉장히 자주 일어난다. 또한 AWS EC2를 구동시에 제공하는 여러가지 리눅스 버전이 있는데, 이러한 리눅스 버전별로 의존성을 설치하는 커맨드도 다소 상이하기 때문에 한번 세팅한 서버를 다른 서버에 이식하기가 까다롭다는 단점이 존재한다.

 

 도커 컨테이너를 사용할 경우 이러한 단점을 해소할 수 있다. 도커 이미지는 해당 애플리케이션을 실행하기 위한 환경을 가지고 있고 이를 컨테이너로 실행하더라도 컨테이너별로 격리되어 프로세스가 실행되기 때문에 의존성이 충돌하는 문제도 해결할 수 있고 다른 서버에 이식하더라도 도커 이미지만 있으면 동일하게 수행이 가능하다는 장점이 있다. 초반의 러닝 커브가 존재하지만 한번 그걸 넘고나면 효용이 더 클 것으로 보아 도커를 활용하여 배포를 진행했다. 

 

 

3. 작업 과정

 

애플리케이션 소스를 도커 이미지로 빌드하고 이를 컨테이너로 실행하는 일련의 과정을 도커라이징이라고 부르는데, 도커라이징을 하기 위한 작업 순서는 다음과 같다.

 

1. 애플리케이션 실행 환경 흐름 테스트 (e.g. 깃 소스 클론, 관련 의존성 설치 등)

2. 특정 루트에 Dockerfile 생성 및 작성

3. 도커 빌드 파일 build.sh 작성

4. 도커 이미지 빌드

5. 도커 컨테이너 실행

 

개별 로직은 다를 수 있지만 도커라이징을 하는 큰 흐름은 위의 순서를 따른다. 

 

 

3-1. WAS (Django 서버) 작업

 

WAS 서버를 구동하기 위해서 python:3.10-buster 이미지를 사용했다. 소스를 가져올 깃허브 레포지토리의 정보와 계정정보를 export로 주입해주고 gunicorn으로 서버를 올리는 과정을 Dockerfile로 작성한 내용이다.

 

Dockerfile

FROM python:3.10-buster

RUN apt update

WORKDIR /opt

ARG GITHUB_USER=${GITHUB_USER}
ARG GITHUB_TOKEN=${GITHUB_TOKEN}
ARG GITHUB_REPO_URL=${GITHUB_REPO_URL}

RUN git clone https://${GITHUB_USER}:${GITHUB_TOKEN}@${GITHUB_REPO_URL}

WORKDIR /opt/bluebird

RUN pip install -r requirements.txt

CMD ["gunicorn", "--bind", "0.0.0.0:8000", "backbone.wsgi:application"]

 

도커 파일(Dockerfile)에서 ARG와 ENV는 각각 빌드 타임(build-time)과 런타임(run-time)에 사용되는 변수를 정의하는 데 사용된다. 이 두 지시어의 주요 차이는 변수의 사용 시점과 범위로 볼 수 있다. ARG는 도커 이미지를 빌드할 때 사용되는 변수를 정의한다. 그래서 빌드 중에만 사용되며, 이미지가 생성되는 동안에만 유효하다. ENV는 도커 이미지에서 실행 중인 컨테이너에서 사용되는 환경 변수를 정의하므로 런타임에 적용되며, 컨테이너가 실행되고 환경 변수가 설정된다. 위에서는 소스 클론을 이미지가 빌드되는 과정에서 사용할 것이므로 ARG를 사용해주었다.

 

 

 아래의 이미지를 참고하면 도커가 이미지를 어떻게 빌드하는지 알 수 있다. Python:3.10-buster 이미지는 도커의 공식 이미지 저장소인 도커허브에서 풀링해와서 사용하는 공식 이미지이다. "buster"는 Debian GNU/Linux 운영 체제의 코드 네임 중 하나로 "python:3.10-buster" Docker 이미지는 Python 3.10을 실행하는 데 필요한 환경을 Debian 10 "buster" 버전을 기반으로 제공하는 이미지를 의미한다. 도커 이미지는 다른 이미지 위에 레이어 형식으로 쌓아가면서 다른 이미지를 빌드할 수 있도록 설계되어있다. 그렇기 때문에 아래에 있는 이미지를 수정할 수는 없지만 기존 이미지를 활용하여 여러가지 커스텀된 이미지를 빌드할 수 있다는 특징을 지닌다.

python 도커 이미지를 활용하여 새로운 이미지를 빌드

 

 

위의 Dockerfile을 활용하여 build.sh 파일을 작성한다. 

 

build.sh $1

#!/bin/bash

docker build \
  -t was-dev:$1 \
  --build-arg GITHUB_USER=${GITHUB_USER} \
  --build-arg GITHUB_TOKEN=${GITHUB_TOKEN} \
  --build-arg GITHUB_REPO_URL=${GITHUB_REPO_URL} \
  .

 

 쉘파일의 첫 번째 인자는 해당 이미지의 버전을 설정한다.

 

 docker images 명령어로 확인해보면 해당 이미지가 빌드된 것을 확인할 수 있다. 

 

 빌드하다가 수정사항이 생긴 경우 TAG를 통해 버전을 관리했다. 이제 이 이미지를 docker run 명령어로 실행시켜보자

 

docker run -itd --name cheston-was-dev -p 8000:8000 cheston-was-dev:0.0.1

 

-itd는 도커 run의 옵션이며 데몬(d)으로 백그라운드 수행을 하고 컨테이너와 상호작용 가능한 대화형 터미널을 열어서(i) 가상 터미널을 할당(t)하는 모드로 수행한다는 의미이다. --name 옵션으로 컨테이너명을 지정해주고 -p 옵션으로 지정한 8000:8000은 호스트 os의 8000번 포트를 해당 도커컨테이너의 8000번 포트로 연동한다는 의미이다. 맨 마지막에는 컨테이너 실행에 사용할 이미지명과 버전을 작성한다. 이후에는 docker exec -it (컨테이너명) bash로 해당 컨테이너 내부에 접속해서 명령어를 작성할 수 있다.

 

 docker ps 명령어는 실행중인 도커 프로세스를 확인할 수 있다. was 서버 0.0.1 버전이 잘 올라가있다.

 

EC2의 인바운드 설정에서 8000번 포트에 대해 소스를 0.0.0.0/0 허용한다고 지정해준다. 0.0.0.0/0의 경우 모든 접근에 대해 허용한다는 의미다. 즉, 해당 django WAS가 떠있는 ec2 서버에 대해 8000번 포트로 접근하는 모든 트래픽을 허용한다는 의미로 해석할 수 있다.

 

 

 이렇게 하고 해당 ip의 8000번 포트로 접속 테스트를 브라우저나 curl을 통해 테스트해보고 정상 접근되는 것까지 확인하면  WAS 서버 컨테이너 구동 작업은 마무리된다. ec2의 특성상 인스턴스를 잠시 멈췄다가 다시 올릴 경우 ip 주소가 변경되기 때문에 이 경우 ip를 변경해서 확인해야한다.

 

 

3-2. WEB SERVER (Nginx) 작업

 

Nginx의 경우는 웹서버 제품이다. 웹서버를 사용하는 이유는 보안, 정적파일 제공 등 여러 이유가 있는데 Nginx는 특히 서버에 연결되는 여러개의 커넥션을 이벤트 기반으로 효율적으로 관리하는 데에 특화된 경량 웹서버다.

 

Dockerfile

FROM nginx:1.25.3

COPY ./proxy_params /etc/nginx/
COPY ./default.conf /etc/nginx/conf.d/default.conf

CMD ["nginx", "-g", "daemon off;"]

 

도커 허브에서 제공하는 nginx 1.25.3 공식 버전을 사용한다. 해당 이미지를 띄울 경우 디폴트값으로 설정되는데 이를 이전에 띄워둔 WAS로 연결되도록 호스트 커널 경로 내에 미리 작성해둔 proxy_params와 default.conf 파일을 해당 컨테이너 내부에 copy 명령어로 카피해준다.

 

 

build.sh

#!/bin/bash

docker build \
  -t cheston-web-server-dev:$1 \
  .

 

 

 

3-3. MYSQL DB

 

Mysql DB의 경우는 ubuntu 이미지를 사용해서 빌드했다. 

 

Dockerfile

FROM ubuntu:22.04

RUN apt update && \
    apt install -y mysql-server

RUN sed -i "s/127.0.0.1/0.0.0.0/g" /etc/mysql/mysql.conf.d/mysqld.cnf

COPY ./mysql-bootstrap.sh /tmp

CMD ["./tmp/mysql-bootstrap.sh"]

 

sed 명령어를 통해 127.0.0.1 로컬호스트로 지정된 부분을 0.0.0.0으로 변경해주고 mysql-bootstrap.sh에 이미 작성해준 mysql 계정 생성 쉘 스크립트를 컨테이너 내에 COPY하고 CMD로 실행하는 이미지를 빌드했다.

 

mysql-bootstrap.sh

#!/bin/bash

service mysql start

if [ -n "${MYSQL_USER}" ]; then
    mysql -uroot -e "create user '${MYSQL_USER}'@'%' identified by '${MYSQL_PASSWORD}'"

    mysql -uroot -e "grant all privileges on *.* to '${MYSQL_USER}'@'%'"
    mysql -uroot -e "flush privileges"
fi

# 포어그라운드로 프로세스 생성 (데몬 죽지 않도록)
sleep infinity

 

 

 

3-4. Docker-compose로 묶어주기

 

docker-compose로 묶어줄 경우 여러개의 이미지를 하나의 서비스로 올릴 수 있다.  아래와 같이 yaml 파일을 작성하고 docker compose up -d 명령어로 실행한다.

services:
  cheston-db-dev:
    image: cheston-db-dev:0.0.3
    ports:
    - "3306:3306"
    volumes:
    - mysql:/var/lib/mysql
    environment:
    - MYSQL_USER=cheston
    - MYSQL_PASSWORD=cheston
    networks:
    - cheston

  cheston-was-dev:
    image: cheston-was-dev:0.0.1
    ports:
    - "8000:8000"
    networks:
    - cheston

  cheston-web-server-dev:
    depends_on:
    - cheston-was-dev
    image: cheston-web-server-dev:0.0.3
    ports:
    - "80:80"
    networks:
    - cheston

volumes:
  mysql: {}

networks:
  cheston:
    driver: bridge

 

 

 

 

3-5. Cloud Watch + Amazon SNS 를 조합해서 모니터링 값 세팅하기

 

  • AWS CloudWatch:
    • AWS CloudWatch는 AWS 클라우드 환경에서 리소스의 모니터링, 로깅 및 경고를 수행하는 서비스다.
    • CloudWatch는 사용자 지정 대시보드를 생성하고 알람을 설정하여 특정 이벤트가 발생할 때 경고를 받고 로그 데이터를 수집하여 검색, 분석 및 저장할 수 있다.
  • Amazon SNS (Simple Notification Service):
    • Amazon SNS는 다양한 종류의 알림 메시지를 효율적으로 관리하고 전송하는 데 사용되는 서비스다.
    • 다양한 통신 프로토콜을 지원하며, 푸시 알림, 이메일, SMS 등 다양한 수단을 통해 메시지를 전달할 수 있도록 해준다.
    • 일종의 Pub/Sub 서비스이며 이벤트 트리거를 통해서 다양한 알림을 제공해주는 역할을 해준다고 할 수 있다.

 따라서, CloudWatch AWS 환경에서의 리소스 모니터링 로깅을 담당하고, SNS 알림 메시지 전송 서비스로 사용되어 다양한 애플리케이션 서비스에서 발생하는 이벤트에 대한 실시간 알림을 관리하는 방식으로 조합해서 사용할 수 있다.

 

 AWS SNS에 토픽으로 경고 이메일을 보내는 토픽을 생성하고, AWS 클라우드워치에 EC2 관련 노티 정책 중 StatusCheckFailed를 SUM으로 0.99 값 이상시 트리거 되도록 설정했다. 개략적으로 아래의 구조라고 보면 된다.

 

 

작업 순서는 토픽생성 > 구독생성 > 알림생성 순으로 진행했다.

 

 

생성된 토픽을 대상으로 프로토콜을 이메일로 설정하고 엔드포인트를 알림을 받을 이메일 주소를 설정해주면 된다.

 

아래 처럼 이메일에서 구독 인증 절차를 거쳐야만 정상적으로 구독이 등록이 된다. (아무 이메일에나 보내면 폭탄이 될 수도 있으니)

 

 

테스트는 우분투의 stress 패키지를 통해 수행해서 알림이 정상 동작하는 것을 확인했다. 일시적으로 테스트를 위해 임계값을 낮게 설정해주고 테스트를 진행했다.

nohup stress -c 1 &

 

아래의 이메일이 뜨는 것을 통해 알림 정상 작동을 확인할 수 있다.

 

 

 

 

 

 

참고 자료

https://hub.docker.com/_/python

 

python - Official Image | Docker Hub

python •• Python is an interpreted, interactive, object-oriented, open-source programming language.

hub.docker.com

 

 

https://www.youtube.com/watch?v=6FAwAXXj5N0

https://www.youtube.com/watch?v=IiNI6XAYtrs

 

반응형