[Docker] 도커 이미지 빌드하기!
- -
Todo list
- 도커 컨테이너 이론
- 도커 파일 작성법
- 도커 이미지 빌드
- 도커 컨테이너 실행
도커는 학부 시절 수강했던 마이크로서비스 프로그래밍 과목에서 맨 처음 접했던 기술 스택이다.
수업에서는 '도커 컨테이너 두개를 올리고, 두 컨테이너간 무언가를 주고 받는 프로그램'을 만드는 것이 과제였고, 나는 A 컨테이너에서 검색할 제품을 넘기면, B컨테이너에서 해당 제품의 정보를 쿠팡 웹사이트에서 크롤링하여 CSV파일로 반환해주는 프로그램을 구현했다.
처음 과제를 완성했을 때는 오 그냥 좀 신기하네..? 정도였는데,
이후에 개인 프로젝트에서 개발한 서비스를 배포하면서 도커의 참맛을 깨달았다.
Nginx와 Django 이미지를 도커 컴포즈로 빌드하기만하면 서비스가 돌아가고, 수정이 필요할때는 도커 허브에서 이미지를 pull 받아서 재빌드하기만 하면 끝인... 콘다 가상환경을 넘어선 완벽 분리된 가상화...
깃허브에서 git clone으로 불러오고, 가상 환경 세팅하고, 파이썬 파일 실행시키는 과정을 한번이라도 경험했던 사람은 이게 얼마나 편하고, 감사한 툴인지 이해할 것이다.
암튼.. 지금부터 내가 공부하면서 깨달은 도커 컨테이너에 대한 필자 나름대로의 이해와, 팁 등을 정리할 예정이니, 아직 도커의 참맛을 못본 사람들은 차근 차근 따라해봅시다!
(원래 이런건 멍청한 사람이 더 쉽게 설명함 ㅎㅎ)
1. 도커 컨테이너가 뭔데?
일단 도커 컨테이너를 이해하기 위해선, '가상화'를 이해해야한다.
가상화란 물리적인 컴퓨터 리소스를 추상화하는 과정이다. 쉽게 말하면, 하나의 물리적 컴퓨터에서 여러 개의 독립된 컴퓨터 환경을 만들어내는 기술이라고 보면 된다.
1.1 가상화 vs 컨테이너화
일반적인 가상화(VM, Virtual Machine)와 컨테이너화의 가장 큰 차이점은 무엇일까?
VM(가상 머신) : 하이퍼바이저 위에 각각의 게스트 OS를 올리고, 그 위에 앱을 실행
- 장점: 완벽하게 분리된 환경
- 단점: 무겁고, 리소스를 많이 잡아먹음 (각각 OS를 올려야 하니까)
컨테이너 : 호스트 OS 위에서 컨테이너 엔진(도커)을 통해 앱을 실행
- 장점: 가볍고, 빠르게 시작/종료 가능, 리소스 효율적
- 단점: VM만큼 완벽한 분리는 아님 (같은 커널을 공유하기 때문)
그럼 도커는 뭐냐?
도커는 컨테이너를 만들고 관리하는 플랫폼이다.
쉽게 말해, 내 애플리케이션과 그 애플리케이션이 돌아가는데 필요한 모든 것(라이브러리, 실행환경 등)을 하나의 패키지로 묶어서 어디서든 똑같이 실행할 수 있게 해주는 도구라고 보면 된다.
2. 도커 파일 작성법
도커파일(Dockerfile)이란 도커 이미지를 만들기 위한 설계도라고 생각하면 된다.
"이렇게 이렇게 해서 이 프로그램 실행 가능한 환경을 만들어줘" 라고 적어놓은 명세서랄까?
기본적인 도커파일 명령어들을 살펴보자:
# 기본 이미지 지정
FROM python:3.9-slim
# 작업 디렉토리 설정
WORKDIR /app
# 필요한 파일 복사
COPY requirements.txt .
# 명령어 실행 (패키지 설치)
RUN pip install -r requirements.txt
# 나머지 소스코드 복사
COPY . .
# 컨테이너 실행 시 수행할 명령어
CMD ["python", "app.py"]
각 명령어의 의미는 다음과 같다:
- FROM: 베이스 이미지 지정 (어떤 환경에서 시작할지)
- WORKDIR: 작업 디렉토리 설정 (cd 명령어와 비슷)
- COPY: 로컬 파일을 컨테이너로 복사
- RUN: 이미지 빌드 시 실행할 명령어
- CMD: 컨테이너 실행 시 수행할 명령어
- ENV: 환경변수 설정
(다만 도커 파일에 민감한 환경변수는 적지 않는 것이 좋다, docker hub 등에 public으로 올릴 경우 유출될 수 있다.)
(chat gpt api key가 유출된다면....상상도 하기 싫다...)
여기서 RUN과 CMD의 차이가 처음에는 헷갈릴 수 있다.
- RUN: 이미지를 만들 때 실행되는 명령어
- CMD: 컨테이너가 시작될 때 실행되는 명령어
필자는 CMD설정을 빼놓고 빌드하는바람에 도커 이미지는 잘 빌드됐는데, 프로그램은 돌아가지 않아 당황했던...ㅎㅎ
3. 도커 이미지 빌드하기
도커파일을 작성했다면 이제 이미지를 빌드할 차례다.
도커 이미지란 쉽게 말해 실행 가능한 패키지라고 보면 된다.
이미지를 빌드하는 명령어는 간단하다:
docker build -t 이미지이름:태그 .
여기서 -t
는 이미지의 이름과 태그를 지정하는 옵션이고, 맨 뒤의 .
은 도커파일이 있는 현재 디렉토리를 의미한다.
예를 들어:
docker build -t my-python-app:1.0 .
이렇게 하면 my-python-app
이라는 이름에 1.0
이라는 태그의 이미지가 생성된다.
3.1 캐시를 활용한 빠른 빌드
도커 이미지를 빌드할 때 좀 더 효율적으로 하는 팁을 공유하자면, 자주 변경되는 레이어는 뒤쪽에 배치하는 것이다.
도커는 각 명령어마다 레이어를 생성하고, 변경이 없는 레이어는 캐시를 활용한다. 따라서 변경이 적은 부분(예: 기본 패키지 설치)은 앞쪽에, 자주 변경되는 부분(예: 소스코드)은 뒤쪽에 배치하면 빌드 시간을 단축할 수 있다.
잘못된 예:
FROM python:3.9-slim
WORKDIR /app
COPY . . # 모든 파일을 한번에 복사 (소스 변경 시 항상 캐시 미스)
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
개선된 예:
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt . # 먼저 requirements.txt만 복사
RUN pip install -r requirements.txt # 패키지 설치 (자주 변경되지 않음)
COPY . . # 그 다음 모든 파일 복사
CMD ["python", "app.py"]
이렇게 하면 소스코드만 변경했을 때, 패키지 설치 과정은 캐시를 활용하므로 훨씬 빠르게 빌드된다.
먼저 파일을 복사하게 되면 앞에서의 레이어에 변경이 발생했으므로, 뒤의 레이어도 다시 빌드하게 되어 불필요한 시간 소모가 발생한다.
4. 도커 컨테이너 실행하기
이미지를 빌드했다면 이제 컨테이너를 실행할 차례다.
컨테이너를 실행하는 명령어는 다음과 같다:
docker run [옵션] 이미지이름:태그
주요 옵션들:
-d
: 백그라운드 모드로 실행 (detached)-p 호스트포트:컨테이너포트
: 포트 매핑--name 컨테이너이름
: 컨테이너 이름 지정-v 호스트경로:컨테이너경로
: 볼륨 마운트--rm
: 컨테이너 종료 시 자동 삭제-e KEY=VALUE
: 환경변수 설정
예시:
docker run -d -p 8000:5000 --name my-app my-python-app:1.0
이 명령어는:
my-python-app:1.0
이미지로부터my-app
이라는 이름의 컨테이너를- 백그라운드에서 실행하고 (
-d
) - 호스트의 8000번 포트를 컨테이너의 5000번 포트에 연결한다 (
-p 8000:5000
)
여기서 포트뭔지 헷갈릴 수 있는데, 이건 나중에 호스트머신과 다른 서버와의 통신이 필요할때, 이 컨테이너만 콕집어서 연결해주기 위한 설정이라고 보면 된다. (호스트머신에 8000번 포트를 달고 들어온 request는 my-app 컨테이너로 들어간다는...)
4.1 볼륨 마운트 활용하기
개발 중에는 소스코드가 자주 변경된다. 그럴때마다 매번 이미지를 다시 빌드하고 컨테이너를 재시작하는 건 너무 번거롭다.
이때 볼륨 마운트를 활용하면 호스트의 파일 변경이 컨테이너에 즉시 반영된다.
docker run -d -p 8000:5000 -v $(pwd):/app my-python-app:1.0
이렇게 하면 현재 디렉토리($(pwd)
)의 파일들이 컨테이너의 /app
디렉토리에 마운트된다. 소스코드를 수정하면 바로 컨테이너에 반영된다!
(다만 이렇게 되면 초기 세팅이 쉽다는 이점은 사라진다는 것! 특히 docker hub에서 이미지를 최신화하는 CI/CD를 구축할 경우 시간은 좀 더 걸리지만, 여러 머신에 세팅되어있는 경우 훨씬 편하다는 이점도 있으니, 상황에 맞춰 사용하면 될 듯하다!)
(주로 배포 환경에서는 docker hub에서 pull받는 방식을, 개발 및 테스트 환경에서는 볼륨 마운트를 활용한다.)
5. 기타 유용한 도커 명령어
5.1 컨테이너 관리
# 실행 중인 컨테이너 목록 확인
docker ps
# 모든 컨테이너 목록 확인 (중지된 것 포함)
docker ps -a
# 컨테이너 중지
docker stop 컨테이너이름_또는_ID
# 컨테이너 재시작
docker restart 컨테이너이름_또는_ID
# 컨테이너 삭제
docker rm 컨테이너이름_또는_ID
5.2 이미지 관리
# 이미지 목록 확인
docker images
# 이미지 삭제
docker rmi 이미지이름:태그
# 태그가 지정되지 않은 이미지 모두 삭제
docker image prune
# 사용하지 않는 (컨테이너화되지 않은) 이미지 모두 삭제 (주의!)
docker image prune -a
5.3 컨테이너 로그 확인
# 컨테이너 로그 확인
docker logs 컨테이너이름_또는_ID
# 실시간 로그 확인
docker logs -f 컨테이너이름_또는_ID
5.4. 컨테이너 내부 접속
# 실행 중인 컨테이너에 접속
docker exec -it 컨테이너이름_또는_ID bash
# 컨테이너를 시작하면서 바로 쉘 접속
docker run -it 이미지이름:태그 bash
정리: 도커의 핵심 개념
지금까지 살펴본 도커의 핵심 개념들을 간단히 정리해보자:
- 도커파일(Dockerfile): 이미지 생성을 위한 설계도
- 이미지(Image): 애플리케이션 코드와 필요한 환경이 담긴 패키지
- 컨테이너(Container): 이미지를 실행한 인스턴스
도커의 진짜 강점은 "내 컴퓨터에서는 작동하는데... 왜 여기선 안돼지?"라는 문제를 거의 완벽하게 해결해준다는 점이다.
주의!! 맥북에서 빌드한 이미지가 우분투 OS에서 빌드되지 않는 경우가 존재한다.
따라서 이런경우엔 buildx로 OS 환경까지 지정해서 이미지를 빌드해야한다.
docker buildx build --platform linux/amd64 -t 이미지이름:태그
또한 마이크로서비스 아키텍처에서는 각 서비스를 독립적인 컨테이너로 실행하여 전체 시스템의 유연성과 확장성을 높일 수 있다.
필자도 처음에는 약간 어려워서 그냥 "신기하네" 정도로만 생각했지만, 필요성을 이해하고 실제 프로젝트에 적용해보니 이젠 없어서는 안 될 도구가 되었다.
아직 도커를 써보지 않았다면 이번 기회에 꼭 도전해보길 강력히 추천한다.
다음 포스팅에서는 이제 도커의 장점을 극대화할 수 있는 기능인 도커 컴포즈에 대해 설명할 예정이다~!
화이팅 화이팅!
ㄱㅊㅁ_ㅇㅈ
'기술스택 및 알쓸신잡' 카테고리의 다른 글
[CS 지식] CPU, RAM, SSD, HDD....스왑 메모리는 뭐야? (0) | 2025.03.22 |
---|---|
[Docker] 도커 컴포즈? 그냥 컨테이너 동시에 올리는거 아니야? (0) | 2025.03.21 |
[MultiThread, MultiProcess] 병렬처리...왜 필요할까? (0) | 2025.02.19 |
소중한 공감 감사합니다