이번 미션은 Django 서버를 Docker와 GitHub Action으로 배포하는 것이었다
배포 아키텍처에 대한 이론을 정리하고 실습 내용을 간단하게 정리해보겠다!
WSGI
는 Web Server Gateway Interface
의 약자로(위스키라고 읽음), Web Server가 요청받은 정보를 Application에 전달하는 역할을 한다
Web Server는 Client의 정적인 요청을 처리하는 프로그램이며, 대표적으로 Apache
, Nginx
가 있다
만약 Client로부터 동적인 요청이 들어오면 Web Application Server(WAS)
에요청을 위임하고, 요청에 대한 응답을 Web Server
로 보낸다
이때 파이썬 애플리케이션(파이썬 스크립트)가 Web Server와 통신하기 위한 인터페이스가 WSGI
이다
Gunicorn
은 Python WSGI로, Web Server(Nginx)로부터 요청을 받아 서버 애플리케이션(Django)으로 전달해주는 역할을 수행한다
Django의 runserver
역시 똑같은 역할을 수행하지만, 보안과 성능상의 이유로 production 환경에서는 사용할 수 없다고 한다
컨테이너는 애플리케이션을 관련 라이브러리 등과 함께 패키지로 묶어 소프트웨어 구동을 위해 만들어진 환경이다 이를 프로세스의 자원을 격리한다고 말한다
즉 OS 레벨의 가상화 기술인 것이다
컨테이너들은 하나의 운영체제를 공유해서 사용하지만, 컨테이너 각각은 독립된 프로세스와 메모리 영역을 사용한다
도커(Docker)는 컨테이너(Container)를 관리하고 다루는 소프트웨어다
즉, 도커는 컨테이너 기반의 오픈소스 가상화 플랫폼이라고 볼 수 있다
Docker를 사용하면 OS에 관계없이 항상 같은 환경에서 서버가 실행되게 도와준다
Docker
에서 Image
는 컨테이너를 정의하는 읽기 전용 템플릿이다
Image
는 컨테이너 실행에 필요한 파일과 설정값 등을 포함하고 있고, 상태값을 가지지 않고 변하지 않는다
그렇기 때문에 이 이미지를 이용한다면 언제든지 동일한 컨테이너를 만들 수 있다
Image
는Container
의 스냅샷(snapshot)
Container
는Image
의 한 인스턴스(instance)
라고 생각하자
Docker compose
란 이미지를 여러 개 띄워서 이미지간 네트워크도 만들어주고 컨테이너의 밖의 호스트와도 어떻게 연결할지, 파일 시스템은 어떻게 공유할지(volumes) 제어해주는 기술이다
쉽게
Docker
는Dockerfile
(서버 운영 기록을 코드화한 것)을 실행시켜주고docker-compose
는docker-compose.yml
파일을 실행시켜준다고 생각하자!
로컬 환경에서 Docker를 실행시켜 서버를 띄우기 위해
Dockerfile과 docker-compose.yml 파일을 작성해준다
# Dockerfile
FROM python:3.8.3-alpine
ENV PYTHONUNBUFFERED 1
RUN mkdir /app
WORKDIR /app
# dependencies for psycopg2-binary
RUN apk add --no-cache mariadb-connector-c-dev
RUN apk update && apk add python3 python3-dev mariadb-dev build-base && pip3 install mysqlclient && apk del python3-dev mariadb-dev build-base
# By copying over requirements first, we make sure that Docker will cache
# our installed requirements rather than reinstall them on every build
COPY requirements.txt /app/requirements.txt
RUN pip install -r requirements.txt
# Now copy in our code, and run it
COPY . /app/
Dockerfile은 하나의 이미지를 만들기 위한 과정이다
RUN pip install -r requirements.txt
명령어를 통해 Docker로 띄운 환경에 라이브러리들을 설치한다
# docker-compose.yml
version: '3'
services:
db:
container_name: db
#image: mysql:5.7 #window
image: mariadb:latest #mac
restart: always
environment:
MYSQL_ROOT_HOST: '%'
MYSQL_ROOT_PASSWORD: mysql
expose:
- 3306
ports:
- "3307:3306"
env_file:
- .env
volumes:
- dbdata:/var/lib/mysql
web:
container_name: web
build: .
command: sh -c "python manage.py migrate && python manage.py runserver 0.0.0.0:8000"
environment:
MYSQL_ROOT_PASSWORD: -
DATABASE_NAME: mysql
DATABASE_USER: 'root'
DATABASE_PASSWORD: -
DATABASE_PORT: 3306
DATABASE_HOST: db
DJANGO_SETTINGS_MODULE: django_rest_framework_17th.settings.dev
restart: always
ports:
- "8000:8000"
volumes:
- .:/app
depends_on:
- db
volumes:
app:
dbdata:
docker-compose -f docker-compose.yml up --build
명령어를 통해 로컬에서 docker를 실행시켜 서버를 띄우고 db를 연결한다
서버가 잘 띄워지는 모습이다!
AWS EC2와 RDS를 사용하여 배포할 것이다
EC2와 RDS를 생성하고 연결하는 과정은 velog 이전 포스팅에 잘 정리해놓았으니 간단하게만 설명하겠다
EC2를 생성할 때 가장 중요한 보안그룹 설정!
ssh(22), HTTP(80), HTTPS(443), Django(8000)을 인바운드 보안 그룹 규칙으로 추가해준다
보통 ssh 접속은 보안상의 이유로 내 아이피만 접속 허용하게 설정해두는 것이 좋다 나중에 바꿀 수 있으니 일단 모든 접속을 허용해놓았다
EC2 생성 완료 후 터미널로 접속해보았다
환경변수 설정을 통해ssh 설정한 명령어
만을 통해 터미널로 EC2에 접속 가능하게 설정해두었다
자세한 설정은 이전 포스팅을 참고하자!
AWS RDS를 설정하고 앞서 만든 EC2와 연결한 후 EC2에 접속하여 테스트해보았다
이렇게 mysql workbench랑도 연결해주었당
로컬 환경과 다르게 ec2 실 배포 환경은 다르기 때문에 파일을 나누어 상황에 맞게 사용해야한다
# Dockerfile.prod
# BUILDER #
###########
# pull official base image
FROM python:3.8.3-alpine as builder
# set work directory
WORKDIR /usr/src/app
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# install psycopg2 dependencies
RUN apk update && apk add python3 python3-dev mariadb-dev build-base && pip3 install mysqlclient
# install dependencies
COPY ./requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /usr/src/app/wheels -r requirements.txt
#########
# FINAL #
#########
# pull official base image
FROM python:3.8.3-alpine
# create directory for the app user
RUN mkdir -p /home/app
# create the app user
RUN addgroup -S app && adduser -S app -G app
# create the appropriate directories
ENV HOME=/home/app
ENV APP_HOME=/home/app/web
RUN mkdir $APP_HOME
RUN mkdir $APP_HOME/static
RUN mkdir $APP_HOME/media
WORKDIR $APP_HOME
# install dependencies
RUN apk update && apk add libpq
RUN apk update \
&& apk add --virtual build-deps gcc python3-dev musl-dev \
&& apk add --no-cache mariadb-dev
COPY --from=builder /usr/src/app/wheels /wheels
COPY --from=builder /usr/src/app/requirements.txt .
RUN pip install mysqlclient
RUN pip install --no-cache /wheels/*
RUN apk del build-deps
# copy entrypoint-prod.sh
COPY ./config/docker/entrypoint.prod.sh $APP_HOME
# copy project
COPY . $APP_HOME
# chown all the files to the app user
RUN chown -R app:app $APP_HOME
# change to the app user
USER app
# docker-compose.prod.yml
version: '3'
services:
web:
container_name: web
build:
context: ./
dockerfile: Dockerfile.prod
command: gunicorn django_rest_framework_17th.wsgi:application --bind 0.0.0.0:8000
environment:
DJANGO_SETTINGS_MODULE: django_rest_framework_17th.settings.prod
env_file:
- .env
volumes:
- static:/home/app/web/static
- media:/home/app/web/media
expose:
- 8000
entrypoint:
- sh
- config/docker/entrypoint.prod.sh
nginx:
container_name: nginx
build: config/nginx
volumes:
- static:/home/app/web/static
- media:/home/app/web/media
ports:
- "80:80"
depends_on:
- web
volumes:
static:
media:
브랜치에 코드가 푸쉬되면 GitHub Action이 자동으로 deploy.sh를 실행해준다
# deploy.sh
#!/bin/bash
# Installing docker engine if not exists
if ! type docker > /dev/null
then
echo "docker does not exist"
echo "Start installing docker"
sudo apt-get update
sudo apt install -y apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
sudo apt update
apt-cache policy docker-ce
sudo apt install -y docker-ce
fi
# Installing docker-compose if not exists
if ! type docker-compose > /dev/null
then
echo "docker-compose does not exist"
echo "Start installing docker-compose"
sudo curl -L "https://github.com/docker/compose/releases/download/1.27.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
fi
echo "start docker-compose up: ubuntu"
sudo docker-compose -f /home/ubuntu/srv/ubuntu/docker-compose.prod.yml up --build -d
sudo docker-compose -f /home/ubuntu/srv/ubuntu/docker-compose.prod.yml up --build -d
deploy.sh의 마지막 명령어이다
결국 이 명령어를 실행시키는 것이 목적!
up
: docker-compose 파일(f 파라미터가 가리키는)에 정의된 모든 컨테이너를 띄우라는 명령어--build
: up할때마다 새로 build를 수행하도록 강제하는 파라미터-d
: daemon 실행깃허브 레포지토리에 필요한 secret들을 설정해주고
코드 푸쉬를 하면 자동으로 deploy.yml
을 통해 deploy.sh
가 실행되고 서버가 띄워진다
name: Deploy to EC2
on:
push:
branches:
- dev
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@master
- name: create env file
run: |
touch .env
echo "${{ secrets.ENV_VARS }}" >> .env
- name: create remote directory
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ubuntu
key: ${{ secrets.KEY }}
script: mkdir -p /home/ubuntu/srv/ubuntu
- name: copy source via ssh key
uses: burnett01/rsync-deployments@4.1
with:
switches: -avzr --delete
remote_path: /home/ubuntu/srv/ubuntu/
remote_host: ${{ secrets.HOST }}
remote_user: ubuntu
remote_key: ${{ secrets.KEY }}
- name: executing remote ssh commands using password
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ubuntu
key: ${{ secrets.KEY }}
script: |
sh /home/ubuntu/srv/ubuntu/config/scripts/deploy.sh
dev
브랜치에 코드가 푸쉬되면 deploy.yml이 실행되므로 dev
브랜치를 새로 만들어주었다
api 테스트까지 완료 잘 돌아간당~
sudo docker logs --tail 20 -f 실행되고 있는 docker 컨테이너 ID
직접 그려보았다 맞나요...?
EC2, RDS, GitHub Actions를 통한 서버 배포와 CD는 이전 프로젝트 배포에서 해보아서 이해하는 것에 크게 어려움은 없어서 수월하게 한 것 같다
이번 미션을 통해 Container와 Docker에 대해 조금 알게 되었다 더 공부해봐야겠당