뭔가 배포를 관리하는 CI/CD는 이해를 잘 했는데 Docker에 대한 개념은 와닿지 않았다.
다시 해보자
이미지를 설계도같은 것
명령어
FROM : 베이스 이미지를 선택
MAINTAINER : 이미지를 만든 사람의 정보를 입력
LABEL : 이미지에 메타데이터를 추가
RUN : 이미지에 명령을 실행하여 파일을 추가하거나 삭제
COPY : 파일을 이미지에 복사
EXPOSE : 컨테이너가 노출할 포트를 설정
CMD : 컨테이너가 실행될 때 실행할 명령을 설정
ENTRYPOINT : 컨테이너가 시작할 때, 실행할 명령어를 입력 (컨테이너를 시작할 때마다 실행, 추가적인 명령어 존재 여부와 상관 없이 무조건 실행)
ENV : 환경 변수를 설정, 이미지 안에 각종 환경 변수를 지정
WORKDIR : 작업 디렉터리를 지정
USER : 사용자를 설정
EXPOSE : 컨테이너에서 노출할 포트를 설정
EX. FastAPI 앱을 실행하는 예제
FROM python:3.11
RUN pip install pipenv
WORKDIR /app
ADD . /app/
RUN pipenv --python 3.11
RUN pipenv run pip install poetry
RUN pipenv sync
RUN pipenv run pip install certifi
ARG STAGE
RUN sh -c 'echo "STAGE=$STAGE" > .env'
RUN sh -c 'echo "PYTHONPATH=." >> .env'
RUN chmod +x ./scripts/run.sh
RUN chmod +x ./scripts/run-worker.sh
CMD ["./scripts/run.sh"]
EX. nginx 이미지를 생성하는 예제
# Dockerfile
FROM ubuntu:22.04
MAINTAINER your-name <your-email@example.com>
LABEL purpose=Web Server
# nginx 패키지 설치
RUN apt-get update && apt-get install -y nginx
# nginx 설정 파일 복사
COPY nginx.conf /etc/nginx/nginx.conf
# Nginx 실행
CMD ["nginx", "-g", "daemon off;"]
nginx.conf
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
gzip on;
gzip_disable "msie6";
include /etc/nginx/conf.d/*.conf;
}
현재 디렉터리에서 Dockerfile을 기반으로 my-nginx:latest라는 이름의 Docker 이미지를 생성하는 예제임
# 안될 때 사용
docker buildx build -t my-nginx:latest .
# 정상 docker 이미지 생성 명령어
docker build -t my-nginx:latest .
Docker 이미지는 Docker CLI를 사용하여 컨테이너로 실행할 수 있음
my-nginx:latest이미지를 기반으로 컨테이너를 실행하고, 80번 포트를 호스트 머신의 80번 포트로 매핑하는 예제
docker run -d -p 80:80 my-nginx:latest
docker stop my-nginx
참고
생성한 Docker image는 Docker Registry라는 저장공간에 저장됨
Docker Compose는 여러 컨테이너를 한 번에 적어서 설정할 수 있음. 즉 무슨 이미지를 쓸 지, 어떤 포트를 사용할 지, 환경 변수는 뭐가 필요한지 등을 적어줌 -> 여러 컨테이너를 한 번에 쉽게 설정하기 위함
앱을 개발할 때, 앱을 따로 떼어 놓고 실행하고 테스트할 수 있는 환경이 필요함 -> Docker Compose 사용
Compose 파일은 앱이 필요로 하는 모든 서비스들 (데이터 베이스, 큐, 캐시, 웹 API 등)을 정리해주고 docker compose up 명령어로 모든 걸 시작할 수 있음
EX. 자동화된 테스트 실행하기
# 테스트 환경을 만듦
docker compose up -d
# 테스트를 실행
./run_tests
# 환경 없애기
docker compose down
EX. Docker Compose 실행하기
# 1. 각 애플리케이션의 Dockerfile 작성
# 2. docker-compose.yaml 파일 작성
# 3. docker compose up으로 실행하기
docker-compose.yaml 파일 예시
services:
web:
build:
context: . # Dockerfile 의 위치
dockerfile: Dockerfile # Dockerfile 파일명
container_name: testapp_web_1 # 생략하는 경우
# 자동으로 부여 docker run 의 --name 옵션과 동일
ports: "8080:8080" # docker run 의 -p 옵션과 동일
expose: "8080" # 호스트머신과 연결이 아니라
# 링크로 연결된 서비스 간 통신이 필요할 때 사용
networks: testnetwork # networks 를 최상위에 정의한다면 해당 이름을 사용
# docker run의 --net 옵션과 동일
volumes: .:/var/lib/nginx/html # docker run 의 -v 옵션과 동일
environment:
- APPENV=TEST # docker run 의 -e옵션과 동일
command: npm start # docker run 의 가장 마지막
restart: always # docker run 의 --restart 옵션과 동일
depends_on: db # 이 옵션에 지정된 서비스가 시작된 이후에 `web`서비스가 실행
links: db # Docker가 네트워크를 통해 컨테이너를 연결하도록 정의합니다.
# 컨테이너를 연결할 때 Docker는 환경 변수를 만들고
# 컨테이너를 알려진 호스트 목록에 추가하여 서로를 검색할 수 있도록 합니다.
deploy: # 서비스의 복제본 개수 등 지정
replicas: 3
mode: replicated
Dockerfile -> docker-compose.yaml 플로우
Dockerfile
주로 사용하는 프로그래밍 언어, 프레임워크, 필요한 라이브러리, 실행 파일 등을 정의합니다.
# 보통 경량화를 중요시 한다면 alpine OS를 많이 사용함
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY build/libs/spring-boot-0.0.1-SNAPSHOT.jar /app
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app/spring-boot-0.0.1-SNAPSHOT.jar"]
docker-compose.yaml
애플리케이션을 구성하는 여러 서비스를 정의하고, 컨테이너 간의 관계를 설정합니다.
version: "3"
services:
mysql:
image: mysql:latest
ports:
- 23306:3306
environment:
- MYSQL_DATABASE=test
- MYSQL_ROOT_PASSWORD=password
redis:
image: redis:latest
ports:
- 26379:6379
nginx:
image: nginx:latest
ports:
- 80:80
depends_on:
- redis
- mysql
컨테이너의 집합
간단히 말해서
Dockerfile은 붕어빵 기계 설계도
Image는 설계도를 통해 만들어진 붕어빵 기계
Container는 붕어빵 기계를 통해 만들어진 실제 붕어빵 (프로그램)
인 것이다.
# python 이미지의 상태를 가져와 줘
docker ps | grep python
Flask 실습
Dockerfile
# Python 3.8을 쓸건데, 가장 가벼운 이미지로 골라줘,
FROM python:3.8-slim
# Copy 내_실제_파일 컨테이너에_들어갈_파일
COPY app.py /app.py
# Flask 설치해야 함
RUN pip install flask
# RUN과 CMD의 차이
# RUN은 그냥 돌리는 거고,
# CMD는 형식이 정해져 있을 때
CMD ["python", "/app.py"]
app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, Docker World!'
# port번호를 명시해주니 제대로 실행이 되었음
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5002)
Docker 이미지 빌드 및 실행
# 앞쪽 포트 번호 (5002): **호스트(로컬 머신)**의 포트 번호입니다. 브라우저에서 접근하거나 클라이언트가 요청할 때 사용하는 포트입니다.
# 뒤쪽 포트 번호 (5002): 컨테이너 내부에서 애플리케이션이 실제로 바인딩된 포트 번호입니다. -> __main__의 포트 번호와 반드시 일치해야 함
docker build -t flask-app .
docker run -d -p 5002:5002 flask-app
ex.
# 외부에서는 localhost:8080으로 접근하지만, 컨테이너 내부에서는 Flask가 5002 포트를 사용합니다.
docker run -d -p 8080:5002 flask-app
docker stats
디스크 사용량 확인 명령어
df -h
디렉터리 별 사용 공간 확인 명령어
du -sh
특정 컨테이너의 로그 보기
docker run --name logs-test --rm -d ubuntu:22.04 /bin/bash -c 'while true; do date; sleep 1; done'
# logs-test 컨테이너의 로그를 전체 출력하기
docker logs logs-test
# logs-test 컨테이너의 로그를 tailing하기
docker logs -f logs-test
# 마지막 10줄부터 로그를 계속 보기
docker logs -f --tail 10 logs-test
흠 근데 너무 복잡하다 지피티에게 물어보니
아래 코드가 제일 간단하다고 함
docker exec -it <컨테이너 이름> sh
: 데이터를 남아있게 하기 위해서 임
: 코드와 데이터를 따로 두려고 (볼륨을 쓰면 코드를 바꿔도 데이터는 그대로 유지할 수 있어서, 개발이나 운영할 때 편리함)
tmpfs mount
컴퓨터 메모리를 사용해서 일시적인 데이터를 저장하는 방법. 컨테이너 안에서 잠깐 필요한 데이터를 다룰 때 유용
바인드 마운트
바인드 마운트는 컴퓨터의 특정 폴더나 파일을 Docker 컨테이너 안에서 직접 쓸 수 있게 해주는 거이. 볼륨보다 기능이 좀 덜하지만, 특정 상황(실시간 코드 변경 확인)에서 유용함
사용방법 : 컴퓨터에서 어떤 폴더나 파일을 골라서 Docker 컨테이너에 "붙여 넣는" 것과 비슷함. 그러면 컨테이너 안에서도 그 파일이나 폴더를 마치 자기 것처럼 쓸 수 있음
어디에 있는 파일이나 폴더를 사용함? 바인드 마운트는 컴퓨터 안의 정확한 위치(절대 경로)에 있는 파일이나 폴더를 사용함 때문에 컨테이너가 어디에 있든, 그 파일이나 폴더를 똑같이 사용할 수 있음
-> 바인드 마운트를 사용하면 컨테이너에서 컴퓨터의 특정 파일이나 폴더를 쉽게 접근하고 사용할 수 있음. 이런 방식은 특정 개발 작업이나 데이터를 다룰 때 도움이 될 수 있음
코드를 올리면 CI/CD가 사용자에게 가는 것
(프레임을 만들면서 검증, 배포를 하는 것)
CI는 개발 CD 계속하는 것
ECR에 이미지를 올림. 컨테이너를 띄워서
로드 밸런싱 헬름
80 = http
443 = https
22 = SSH