EC2와 RDS, nohup으로 gunicorn을 사용해 background에서 서버를 가동시켜 배포하는것까지 해보았다. 이번에는 가상환경 세팅까지 필요없도록 Docker를 이용해 배포해보도록 하자!
프로젝트 명 : jellogram
Docker에 대한 전반적인 이해를 쉽게 해주었던 글이 있다!! 모두 읽어보시는걸 추천드립니다.
https://khj93.tistory.com/entry/Docker-Docker-%EA%B0%9C%EB%85%90
아래 개념적인 내용은 해당 게시글을 읽고 정리한 글이다.
Docker는 가상화 기술이다. 이 기술은 Docker라는 회사가 개발한 container virtualization으로 해당 기술품은 기존에도 많이 존재했지만 Docker가 많이 사용되면서 사람들이 container virtualization 기술을 아예 Docker라고 부르게 되었고 Docker = container virtualization
로 고착된 것이다. 원래는 그런 의미가 아님! 상처에 붙이는 밴드를 흔히들 "데일밴드"라고 부르는 거랑 같은 격이다
Docker를 이용하면 container 가상화 실행 환경 위에 application 배포 엔진을 추가하여 언제 어디서든 코드가 실행될수 있도록 제공한다. 이점을 이용해 요즘엔 MSA나 CI/CD 와 함께 사용되는데 때문에 하나의 container당 하나의 application이나 프로세스를 실행하는 것이 권장된다.
사진 출처 : https://khj93.tistory.com/entry/Docker-Docker-%EA%B0%9C%EB%85%90
좌측이 VM 모식도 이고 우측이 container 가상화 모식도이다.
VM ?
하나의 물리적 시스템에서 각각 자체 운영 체제(OS)와 application을 운영할수 있다. 단, VM은 서로에 대해서 알지 못하고, host의 OS도 알지 못한다. (완전 독립적!)
때문에 실제 하드웨어간의 조정을 위해 "hypervisor"라고 불리는 경량 소프트웨어 계층이 필요하다. hypersivor 계층이 VM을 각각 분리하여 서로간에 간섭을 막는다.
<대표 장점>
물리적 서버의 리소스를 더 효율적으로 운용할 수 있다.
: 한 서버에 한 OS가동시 낭비되는 리소스(CPU, 메모리, 스토리지 등..) 가 존재하지만 한 서버에 여러개의 OS를 가동하면 idle상태로 낭비시키지 않고 필요한 OS에 리소스(예를 들면 CPU)가 할당되어 효율적인 운용이 가능하다. / 중저가의 하드웨어 여러개를 운용하는 것보다 고가의 하드웨어로 한 서버에 여러개의 OS를 운용하는것이 더욱 효율적이다.
정리하자면 Host OS엔진 위해 가상화를 시키기 위한 Hypervisor 엔진, 그리고 그 위에 Guest OS를 가동시키는 것과 달리 컨테이너 기반 가상화는 Docker 엔진 위에 Application 실행에 필요한 바이너리만 올라가게 된다. 때문에 OS위의 OS동작으로 인해 속도가 느린 VM에 비해 성능의 효율이 더 뛰어나다는 장점이 있다.
container를 사용한다는 것은 VM을 생성하는 것이 아니라 Host OS가 사용하는 리소스를 분리하여 여러 환경을 만들 수 있도록 하는 것이다.
4가지 부분으로 구성되어 있다.
- client와 server(=docker engine)
- image
- registries
- containers
여기서 server는 docker의 engine이라고도 불린다.
둘은 같은 Host이거나, 혹은 다른 Host일때도 함께 운용될 수 있다.
Dockr의 이미지를 그림으로 표현하면 다음과 같다.
해당 이미지에 저장된 값을 토대로 환경이 셋팅되는것인데 해당 image에는 container를 실행할 수 있는 실행파일, 설정값 등을 갖고 있다.
이 이미지를 활용해 배포한다면 프로젝트에 필요한 환경까지 모두 공유가 가능해지는 것이다. 단순히 서버와 DB를 공유하는것에서 끝나는것이 아닌! 세팅까지!
docker의 image를 저장하는 repository이다. (=Githup의 repo)
docker의 Image를 실행시키는 가상화 공간이다. 원래 docker container는 두개 이상의 프로세스를 실행 할 수 있지만 권장사항에서는 하나의 container당 하나의 process만 실행하는것을 권장한다.
나의 경우 MacOS이기 때문에 Docker 사이트에서 프로그램을 다운로드 하였다. https://docker.com
EC2는 Ubuntu 운영체제이다! 따라서 프로그램 설치가 아니라 terminal에서 명령어를 입력해 설치를 진행해 주어야 한다. 이후 진행할 예정
먼저 지난번에 실습하면서 Docker image를 만든적이 있다 해당 Docker는 이용중지상태인데 가동시키지 않은 상태에서 현재 실행중인 컨테이너와 실행이 종료된 상태의 컨테이너 목록을 살펴보자
# 실행중인 container 목록
docker ps
# 실행종료된것 포함 모든 container 목록
docker ps -a
# 생성&다운로드 된 image 목록
docker images
# 모든 image 목록
docker images -a
명령문을 살펴보면 -a
라는 의미가 all 같다!
내 터미널에 명령어를 입력하면 다음과 같다.
그때 docker_train 이라는 이름으로 생성했었다
Docker file에 대한 개념을 잡아야 한다.
이는 image 생성의 출발점으로 image를 구성하기 위한 명령어들을 작성하여 image를 구성할 수 있다. 즉, Docker File을 읽을 수 있다면 해당 Image가 어떻게 구성되어 있는지 알 수 있다.
따라서 Docker File을 작성해서 image를 생성하도록 한다
#./Dockerfile
FROM python:3
#기반이 될 이미지
# 작업디렉토리(default)설정
WORKDIR /usr/src/app
## Install packages
#현재 패키지 설치 정보를 도커 이미지에 복사
COPY requirements.txt ./
#설치정보를 읽어 들여서 패키지를 설치
RUN pip install -r requirements.txt
## Copy all src files
#현재경로에 존재하는 모든 소스파일을 이미지에 복사
COPY . .
## Run the application on the port 8080
#8000번 포트를 외부에 개방하도록 설정
EXPOSE 8000
#CMD ["python", "./setup.py", "runserver", "--host=0.0.0.0", "-p 8080"]
#gunicorn을 사용해서 서버를 실행
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "docker_train.wsgi:application"]
gitignore와 같이 dockeringore도 작성해서 이미지 작성시 이미지에 들어가지 않을 내용을 선별할 수 있다.
# vi dockerignore
npn-debug.log
Dockerfile*
docker-compose*
.dockerignore
.git
.gitignore
.vscode
# 그 외 추가할 사항등 입력 후 :wq
위에서 Docker File을 작성하였다면 해당 file이 있는 위치에서 다음의 명령어를 입력한다
docker build -t [만들려는 이미지 이름]
# 권장 방식
docker build -t [도커허브에 가입한 계정명/이미지명(프로젝트명을 권장한다)] : [버전(0.0.0 이런형태 권장)]
ex) docker build -t chyun403/docker_train:0.1.0
docker run --name '컨테이너 명' -d'데몬으로 실행하기 위한 옵션' -p '호스트 포트':'컨테이너 포트' '이미지명'
ex) docker run --name docker_train -d -p 8000:8000 chyun403/docker_train:0.1.0
명령어를 통해 Docker의 pull부터 실행까지 방법에 대한 것이다. 참고한 사이트에 더 많은 명령어들이 있다!
출처 : khj93.tistory
docker pull [이미지 이름]
docker images
참고로 이미 빌드된 이미지로 컨테이너에서 실행이 되고있다면 해당 컨테이너를 먼저 삭제한 후 이미지 삭제가 가능하다.
docker rmi [이미지 이름]
docker push [레지스트리url/이미지:버전]
docker run <옵션> <이미지 이름, ID> <명령> <매개변수>
docker run --name [컨테이너이름] -d [데몬으로 실행하기 위한 옵션] -p [호스트 포트]:[컨테이너 포트] [이미지이름]
# 예시
docker run --name [컨테이너이름] -v [호스트경로]:[컨테이너경로] -p [호스트포트]:[컨테이너포트] -d 이미지:버전
진행할 프로젝트 : jellogram
server : nohup으로 gunicorn을 이용해서 가동 (Ubuntu)
#./Dockerfile
FROM python:3
#기반이 될 이미지
# 작업디렉토리(default)설정
WORKDIR /usr/src/app
## Install packages
#현재 패키지 설치 정보를 도커 이미지에 복사
COPY requirements.txt ./
#설치정보를 읽어 들여서 패키지를 설치
RUN pip install -r requirements.txt
## Copy all src files
#현재경로에 존재하는 모든 소스파일을 이미지에 복사
COPY . .
## Run the application on the port 8080
#8000번 포트를 외부에 개방하도록 설정
EXPOSE 8000
#CMD ["python", "./setup.py", "runserver", "--host=0.0.0.0", "-p 8080"]
#gunicorn을 사용해서 서버를 실행
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "jellogram.wsgi:application"]
docker build -t chyun403/jellogram_g:0.1.0 .
Dockerfile에 입력한 대로 [n/5]만큼 설치되는걸 확인할 수 있다.
❯ docker images -a
REPOSITORY TAG IMAGE ID CREATED SIZE
chyun403/jellogram_g 0.1.0 2ed9fab3474a 9 minutes ago 1GB
chyun403/docker_train 0.1 0a40a7b73545 2 months ago 932MB
docker run -d -p 8000:8000 --name kch chyun403/jellogram_g:0.1.0
-d
gunicorn으로 서버를 가동시켰을때 background에서도 가동하기 위해 nohup을 사용했었다! 여기서 입력한 명령어 중
-d
는 데몬화 한다는 의미로 백그라운드에서 서버를 가동하도록 한다는 의미이다.
즉, docker안에 있는 gunicorn 설정이 백그라운드에서도 가동되도록 하겠다는 의미이다. (docker 프로세스 자체가 백그라운드에서 실행되는 의미가 아님!)
-p
port forwarding이라는 의미이다.
Dockerfile에서EXPOSE 8000
으로 입력해주었는데 이는 내가 띄울 Docker 서버에 8000 포트를 연다는 의미이다.
-p @@@@:####
에서 앞@
는 내 컴퓨터에서 호출하는 포트번호 이고 뒤#
은 가상환경이 받아들이는 포트번호이다.
따라서 Dockerfile에 설정한 대로 8000은 뒤#
에 입력되어야 하고, 만약 내 컴퓨터에서 이미 포트번호 8000번을 사용한다면 다른 번호를 지정해서 앞@
에 입력해 주면 된다.만약
-p 9000:8000
이라면 내 컴퓨터에서 포트번호 9000으로 호출하면 가상환경의 포트번호 8000으로 들어가게 된다는 의미이다.
--name [컨테이너이름]
원래 docker에서 랜덤하게 의미없는 이름으로 '_'로 구분하면서 자동으로 지정해준다. (왜냐면 나중되면 docker OS 를 진짜 많이 만들어야 하는데 그때마다 naming하기가 버겁기 때문)
< random 하게 이름 생성 됐을 경우 >
docker run -d -p 8000:8000 chyun403/jellogram_g:0.1.0
<내가 이름을 지정해주었을 경우>
docker run -d -p 8000:8000 --name jello0512 chyun403/jellogram_g:0.1.0
참고로 --name [컨테이너 명]
의 위치는 무관하다 따라서 다음의 명령어도 똑같이 기능한다.
docker run --name jello0512 -d -p 8000:8000 chyun403/jellogram_g:0.1.0
❯ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
08fdf94dae7c chyun403/jellogram_g:0.1.0 "gunicorn --bind 0.0…" 2 minutes ago Created 0.0.0.0:8000->8000/tcp jello0512
3958fbd0aff0 chyun403/jellogram_g:0.1.0 "gunicorn --bind 0.0…" 4 minutes ago Created 0.0.0.0:8000->8000/tcp angry_heyrovsky
d65e82315f3f chyun403/docker_train:0.1 "gunicorn --bind 0.0…" 2 months ago Exited (0) 2 months ago docker_train
Docker를 통해 background에서 gunicorn으로 서버가 가동되는것 까지 세팅되도록 했다! 바로 통신으로 확인해 본다.
# 실행 명령어
❯ docker run -d -p 8000:8000 --name jellogram chyun403/jellogram:0.1.0
컨테이너 생성 확인
postman으로 확인
이미지를 빌드하고 빌드한 이미지를 실행시키고자 하면 다음과 같은 에러가 발생했다.
docker: Error response from daemon: OCI runtime create failed: container_linux.go:367: starting container process caused: exec: "gunicorn": executable file not found in $PATH: unknown.
말그대로 gunicorn이 없다고 하는것 같은데 구글링도 하면서 혹시몰라 requirements.txt
를 확인해보았다.
gunicorn이 없었다. ....
처음엔 container만 삭제하고 gunicorn을 requirements.txt에 추가한 뒤다시 실행하면 될거라 생각했지만 같은 에러가 발생했다.
다시 생각해보니 이미지를 만든 그 시점에 대한 환경으로 계속해서 같은 실행이 반복되었던 것 때문에 환경이 변한다면 변화할때마다 이미지를 새로 생성해 주어야 한다
기존의 컨테이너와 이미지를 모두 삭제한 후
을 진행하였고 postman으로 통신을 확인할 수 있었다.
통신까지 확인한 후에는 컨테이너 실행을 종료 시켜주었다 (삭제 아님!)
EC2는 우분투! 우분투 명령어를 사용해 내 환경에 docker를 설치해준다.
가상환경 켜.. 제발..
sudo apt update
sudo apt install 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 docker-ce
참고로.. 마지막 명령어 직전까지 또 base 에서 해버렸다. 인간은 똑같은 실수를 반복한다지...
이후 혹시나 싶어서 MacOS에서 만들었던 container를 start해보려니 그런 container는 없다고 한다.
따라서 imgae build부터 다시 실행해 준다.
이후 docker run
으로 실행시켜 새로운 container를 생성후 통신으로 확인한다!
🔥 참고로 http://localhost:8000
으로 통신해도 값은 나오더라!
확실히 혼자서 진행해보니 Docker를 어떻게 사용하는지, 왜 사용하는지, 사용했을때의 효과로 무엇이 있는지 좀더 느끼기 쉬웠다!
가상환경을 셋팅해줄 필요가 없다는것. pip install -r이 필요가 없으니!
또한 그때그때 OS에 맞춰 번역까지 해주니 OS에 제한되는것 없이 업데이트가 가능하다는 점. 등 Docker에 대한 기본적인 개념을 알 수 있었다.
이후 프로젝트를 진행하면서 새로운 모듈을 더 설치하게 될텐데 주기적으로 docker로 배포를 진행하면서 관리해보도록 하자!