컨테이너(container): 기본적으로 호스트(host) 컴퓨터에서 돌아가고 있는 하나의 프로세스입니다.
하지만, 일반 프로세스와는 다르게 container는 host 컴퓨터와 그 위에서 돌아가는 다른 프로세스들로부터
격리되도록 설계되어 있습니다.
VM | Container |
---|---|
자체적인 운영체제를 포함하고 있어서 | VM처럼 격리된 환경에서도 |
하드웨어 리소스를 많이 잡아 먹어서 | 일반 프로세스처럼 빠르고 가볍게 돌아감 |
느리고 무거움 |
이미지(image) = container image
: 애플리케이션 코드 뿐만 아니라 애플리케이션 실행에 필요한 최소한의 환경(언어 런타임, 라이브러리 패키지 등)을
포함하고 있는 바이너리(binary) 파일입니다.
Image는 container의 스냅샷을 떠놓은 것으로 생각하면 됩니다.
반대로 container는 컴퓨터에서 살아서 돌아가고 있는 image의 한 인스턴스(instance)라고 생각할 수 있습니다.
image를 실행시키면(run) 이미지가 container가 되고, 컨테이너가 실행되면서
컨테이너 안에 포함되어 있는 실행되도록 조치되어있는 프로그램이 실행되게 됩니다.
program | image |
---|---|
process | container |
<용어>
호스트(host): 운영체제가 설치된 컴퓨터
컨테이너(container): 호스트에서 실행되는 격리된 각각의 실행환경
각각의 컨테이너에는 운영체제 전체가 설치되어 있는 것이 아니고
앱을 실행하는 데 필요한 라이브러리와 실행 파일들만 포함되어 있습니다.
각각의 앱은 이렇게 컨테이너라는 격리된 공간에서 실행됩니다.
이미 존재하는 운영체제를 공유하니까 무엇인가를 설치할 필요도 없고,
운영체제가 하나니까 속도도 느려지지 않고, 저장장치의 용량을 줄일수도 있습니다.
컨테이너 기술을 이용해서 이런 일을 쉽게 해주는 소프트웨어들 중 가장 많이 이용하는 제품이
"도커" 입니다.
포트(port)에 대해 우리는 도커를 공부하기 이전까지 웹 서버의 포트번호에 대해서만 알고 있었을 것입니다.
저도 이전까지는 컴퓨터에 몇만개의 포트가 있고,
이것이 컴퓨터에 설치되어 있는 여러 소프트웨어들을 네트워크적으로 구분해준다는 것,
그리고 일반적으로 웹 서버는 80번 포트를 이용한다 이정도만 알고 있었는데,
도커에서는 호스트와 컨테이너가 각각의 포트를 이용한다는 것을 알게 되었습니다.
그래서 도커의 네트워크에 관해 찾아보게 되었습니다.
도커 호스트(docker host) = 컨테이너가 설치된 운영 체제
하나의 도커 호스트에는 여러 개의 컨테이너가 만들어질 수 있습니다
컨테이너와 호스트 모두 독립적인 실행환경이기 때문에
각자 독립적인 포트와 파일 시스템을 가지고 있습니다.
위 그림과 같은 예시에서, 웹 브라우저로 웹 서버에 접속하려면
호스트의 80번 포트와 컨테이너의 80번 포트를 연결해주어야 합니다.
명령어: $ docker run -p 80:80 httpd
(httpd apache 서버를 이용하였다고 가정)
-> 이 명령어를 통해서 호스트의 80번으로 들어온 신호가 컨테이너의 80번 포트로 전송됩니다.
== 이렇게 연결된 포트로 신호를 전송하는 것 "포트 포워딩(port forwarding)" 이라고 합니다.
①서버에 접속해서 docker를 실행시킨다.
②그리고 방금 master에 푸시된 커밋을 복사한다.
####--> 이 전 과정: CD(Continuous Delivery)
용어 정리)
컨테이너화된 소프트웨어 프로젝트에서는 모든 개발 작업이 Docker 컨테이너 안에서 이루어집니다.
따라서 개발자의 로컬 컴퓨터에서 파이썬을 설치하거나 프로젝트에 필요한
패키지를 설치할 필요가 없습니다.
대신 Docker 컨테이너 안에서 해당 어플리케이션이 돌아갈 수 있는 환경을 이미지(image)를
떠 놓아야 합니다. 이렇게 이미지를 떠 놓으면 개발자들은 번거로운 사전 세팅을 생략하고
바로 해당 이미지를 컨테이너 안에서 실행할 수 있습니다.
: 하나의 이미지(내가 구축한 환경을 스냅샷 찍어둔 것)를 만들기 위한 과정으로
(==> Docker 이미지가 빌드(build)될 때 거쳐야하는 단계를 정의하고 있음)
이 '이미지'만 있으면 다른 컴퓨터에서도 똑같은 환경을 올릴 수 있다.
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/
FROM 명령문
FROM <이미지>
FROM <이미지>:<태그>
FROM python:3.8.3-alpine #Python 3.8
(alpine 리눅스 기반)을 base 이미지로 사용
WORKDIR 명령문
WORKDIR <이동할 경로>
WORKDIR 명령문은 쉘(shell)의 cd 명령문처럼 컨테이너 상에서 작업 디렉토리로 전환을 위해서
사용됩니다. WORKDIR 명령문으로 작업 디렉터리를 전환하면 그 이후에 등장하는 모든
RUN, ENTRYPOINT, COPY, ADD 명령문은 해당 디렉터리를 기준으로 실행됩니다.
```WORKDIR /app ``` # /app 으로 디렉터리 전환
COPY 명령문
COPY ...
COPY ["",... ""]
COPY 명령문은 호스트 컴퓨터에 있는 디렉터리나 파일을 Docker 이미지의 파일 시스템으로
복사하기 위해서 사용됩니다.
COPY requirements.txt /app/requirements.txt
COPY . /app/
# 이미지를 빌드한 디렉터리의 모든 파일을 컨테이너의 app/ 디렉터리로 복사
<명령어> | <용도> |
---|---|
FROM | base 이미지 설정 |
WORKDIR | 작업 디렉터리 설정 |
RUN | 이미지 빌드 시 커맨드 실행 |
ENTRYPOINT | 이미지 실행 시 항상 실행되야 하는 커맨드 설정 |
CMD | 이미지 실행 시 디폴트 커맨드 또는 파라미터 설정 |
EXPOSE | 컨테이너가 리스닝할 포트 및 프로토콜 설정 |
COPY/ADD | 이미지의 파일 시스템으로 파일 또는 디렉터리 복사 |
ENV | 환경 변수 설정 |
ARG | 빌드 시 넘어올 수 있는 인자 설정 |
이렇게 작성한 Dockerfile를 이용해서 이미지 빌드하기
$ docker build .
마지막에 Successfully built <이미지ID>가 출력되었다면 제대로 이미지가 빌드가 된 것입니다.
##docker-compose
docker-compose --(실행시킴)--> docker-compose.yml
version: "3.5"
services:
web:
웹 애플리케이션 설정
db:
데이터베이스 설정
networks:
네트워크 설정
volumes:
볼륨 설정
version: '3'
services:
db:
container_name: db
image: mysql:5.7
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: mysql
DATABASE_NAME: mysql
DATABASE_USER: 'root'
DATABASE_PASSWORD: mysql
DATABASE_PORT: 3306
DATABASE_HOST: db
DJANGO_SETTINGS_MODULE: django-rest-framework-14th.settings.dev
restart: always
ports:
- "8000:8000"
volumes:
- .:/app
depends_on:
- db
volumes:
app:
dbdata:
command: sh -c "python manage.py migrate && python manage.py runserver 0.0.0.0:8000"
ports: "8000:8000"
volumes: - .:/app
depends_on: - db
일반적인 사용 사례는 프로덕션, 준비, CI 또는 개발과 같은 여러 환경을 대상으로
지정할 수 있도록 여러 compose 파일을 정의하는 경우입니다. 이러한 차이를
지원하기 위해 다음 그림에 나와 있는 것처럼 Compose 구성을 여러 파일로 분할할 수 있습니다.
(설명: 기본 docker-compose.yml 파일에서 값을 재정의하는 다중 docker-compose 파일)
-> 로컬이 아닌 서버에서 실행되는 파일
-> 배포를 위한 파일 (prod -> 배포를 위한 것)
version: '3'
services:
web:
container_name: web
build:
context: ./
dockerfile: Dockerfile.prod
command: gunicorn django-rest-framework-14th.wsgi:application --bind 0.0.0.0:8000
environment:
DJANGO_SETTINGS_MODULE: django-rest-framework-14th.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:
docker-compose.yml | docker-compose.prod.yml |
---|---|
db 컨테이너가 O | db 컨테이너가 X |
nginx 컨테이너 X | nginx 컨테이너 O |
application과 server 구분하기!
application-django <----------> server-nginx
백엔드 개발자가 코드를 짜는 일-> 비즈니스를 구현하는 것 -> django가 이를 대신함 = application
nginx : 이 application(=여기서는 django)에 접근하고 요청과 응답을 전달할 수 있게 해줌
nginx <-> gunicorn or uwsgi <-> wsgi <-> django
.config/nginx/ 경로에 dockerfile이 있는 것을 확인할 수 있다.
FROM nginx:1.19.0-alpine
# nginx의 1.19.0-alpine 버전 이미지를 사용합니다.
# 이 이미지는 이미 누군가가 만들어놨고, nginx 구동에 필요한 환경이 이 이미지 안에 다 들어가있어요.
RUN rm /etc/nginx/conf.d/default.conf
# default config 파일을 삭제합니다. 아래에서 우리가 원하는 설정파일로 바꿔줄 생각이에요.
COPY nginx.conf /etc/nginx/conf.d
# nginx.conf라는 파일을 옮겨줍니다.
여기서의 nginx.conf 파일을 확인해 보면 다음과 같습니다.
nginx.conf도 위의 nginx Dockerfile과 같은 경로에 놓여 있습니다.
upstream django_docker { # django_docker라는 upstream 서버를 정의합니다.
server web:8000; # web의 8000포트에 연결합니다. web은 docker container에요.
}
server { # nginx server를 정의합니다.
listen 80; # 80포트를 열어줍니다 (http)
location / { # "/" 도메인에 도달하면 아래 proxy를 수행합니다.
proxy_pass http://django_docker; # django_docker라는 upstream으로 요청을 전달
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # header 설정
proxy_set_header Host $host;
proxy_redirect off;
}
location /static/ { # "/static/" 도메인에 도달하면 아래 alias를 수행합니다.
alias /home/app/web/static/; # 아래 디렉토리 (서버의 파일시스템)을 맵핑합니다.
}
location /media/ {
alias /home/app/web/media/;
}
}
일반적인 프록시 구조에서, 요청을 받는 쪽을 upstream,
응답을 받는 쪽을 downstream이라고 한다.
Github Actions 가 -------(실행)------->> "docker-compose.prod.yaml"
.github/workflows/deploy.yml 을 확인해보면(Github Actions가 실행시켜주는 파일)
sh /home/ubuntu/srv/ubuntu/config/scripts/deploy.sh
를 확인할 수 있습니다.
config/scripts/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
if ! type docker > /dev/null, if ! type docker-compose > /dev/null
sudo docker-compose -f /home/ubuntu/srv/ubuntu/docker-compose.prod.yml up --build -d
--> 그러면 up을 한다고 서버가 뜰까?
docker-compose가 django를 알고 서버를 띄워주는 것이 아니다.
docker-compose는 django를 모른다.
그래서 우리는 up이 되었을때 django를 실행시키기 위해 command와 entrypoint를 정의합니다.
docker-compose.prod.yml을 보면 다음과 같은 코드를 확인할 수 있습니다.
web:
command: gunicorn django_docker.wsgi:application --bind 0.0.0.0:8000
web:
entrypoint:
- sh
- config/docker/entrypoint.prod.sh
name: Deploy to EC2
on: [push]
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
env:
DEPLOY_USERNAME: hanqyu
with:
host: ${{ secrets.HOST }}
username: ubuntu
key: ${{ secrets.KEY }}
script: |
sh /home/ubuntu/srv/ubuntu/config/scripts/deploy.sh
제가 추가로 궁금했었던 점이 있어 질문한 내용이 있는데, 여기에도 공유하겠습니다
Q:
github actions를 이용하지 않고 그냥 바로 docker-compose명령어를 터미널에서 치고
컨테이너를 빌드해서 서버를 실행해도 되지 않나요?
로컬에서 수정한 변경사항을 따로 입력하지 않아도 github actions를 수행함으로써 바로바로 push가 되니까
그 편리함 때문에 github actions로 docker-compose를 수행하는 건가요?
즉 github actions를 같이 쓰는 명확한 이유가 궁금합니다!
찾아보니까 docker를 이용할 때 github actions를 꼭 같이 쓰지 않는 경우도 있어서요
A:
바로 docker-compose.prod를 올리는 명령어로 서버 배포를 진행할 수도 있지만,
푸쉬도 하고 도커컴포즈도 입력하는 그 작업을 반복하다보면 생각보다 일이 될 수가 있어요.
그래서 푸쉬를 하면 자동으로 배포가 될 수 있게 자동화를 목표로 github actions, travis, jenkins 등을 이용합니다!
참고로 깃헙액션에서는 푸쉬할때 말고도 다른 원하는 행위나 다른 브랜치 대해 workflow를 다양하게 정의해서 사용할 수도 있어요
예를들어 .github/workflows 및에 dev.yml prod.yml 나눠서 개발과 운영을 분리한 후
서로 다른 작업을 수행하게끔 정의할 수 있는 걸로 알고 있습니다!
(개인적으로 로컬에서의 테스트 말고 개발용 서버와 디비를 따로 또 판 경우에 이렇게 하면 좋을 것 같네용)
또 깃헙 액션만의 장점이 있다면 코드가 배포에 문제가 없는지 확인하고,
에러가 난다면 어디서 나는지 빌드 로그를 볼 수 있는 등
배포관련된 작업들을 외부의 다른 툴 없이 저희가 기존에 사용하던 github이라는 코드 관리 공간에서
같이 할 수 있다는 점이 이점이 되겠네요
안녕하세요. 글 정말 잘 봤습니다.
블로그만 수십개 찾아보고 있는데
제가 찾던 내용이랑 가장 비슷하네요.
질문이 몇 개 있습니다.
docker_compose 가 정확히는
Dockerfile을 이용해서 이미지를 빌드하고,
그 이미지를 이용해 컨테이너를 실행시키나요?
git actions가 소스만 복사해준다면
이미지를 빌드하는게 우선인 것 같아서요.
docker_compose 가 실행되는 시점?이 의문입니다. 본문에 소개된 시점은 서버의 deploy.sh이죠.
우선 빌드된 '이미지'가 있다면 이것이 어떤 환경이든 실행 가능하다고 이해했습니다.
컨테이너라는 프로세스로요.
궁금한 점은 아래와 같습니다.
로컬에서 제 환경에서만 제대로 동작하는 코드를 한 줄 적었다고 가정합니다.
만약 제 환경에서 docker_compose 를 실행시켜 성공적으로 이미지를 빌드한다면,
이 이미지는 서버에서도 컨테이너로 잘 실행될 겁니다.
그런데 이 코드를 깃헙에 푸시한다면
서버에서 이미지를 빌드하는 것인데,
푸시한 코드는 제 환경에서만 동작합니다.
이때 서버에서 이미지가 성공적으로 빌드된다는 보장이 없지 않나요?
다른 개발환경을 가진 팀원과 협업 workflow를 어떻게 짜야할까요? 위의 질문과 비슷합니다.
Dockerfile, docker_compose 을 깃헙에 푸시하고
팀원들은 각자 풀 받아서 이미지를 빌드한다면, 각자 다른 이미지를 가지고 있는게 아닌가요?
팀원 A가 로컬에서 코드를 수정하고 성공적으로 이미지가 빌드됨을 확인했다고 가정합니다. 이때 다른 팀원도 이 수정된 코드에 대한 이미지를 빌드할 수 있음을 어떻게 확신하나요?
로컬에서 테스트를 어떻게 진행하나요?
도커를 안 쓰는 경우 장고는 runserver 로 서버를 실행시킨 상태에서 코드 수정 후 저장하면 즉시 리로드가 되어 아시다시피 바로 수정내용을 확인할 수 있죠.
도커를 사용할 경우 어떻게 달라지나요?
코드 수정 -> dockerfile로 이미지 'B' 생성 -> 예전 이미지 'A'로 실행한 컨테이너 삭제 -> 예전 이미지 'A' 삭제 -> 이미지 'B' 실행 순으로 진행해야 하나요?
고봉밥 질문 죄송합니다.. 여기가 가장 만족스러워서요..
안녕하세요
docker-compose는 배포시에는 아무런 영향을 안주는 건가요?