1. 도커란?
도커는 가상화의 한 종류인 '컨테이너' 기반의 오픈소스 프로젝트이다.
컨테이너는 그 자체로 독립적인 실행 환경이다.
애플리케이션이 빠르고 안정적으로 실행할 수 있도록, 애플리케이션 파일과 실행에 필요한 모든 의존성을 하나의 패키지로 묶는다.
도커의 컨테이너 개념을 도입하여, 동일한 환경에서 실행되므로 '개발-테스트-프로덕션 환경' 간에도 일관성을 유지할 수 있다.
참고로 도커의 시작은 리눅스의 cgroups와 namespaces을 활용한 LXC 기반으로 시작했지만, 현재는 자체적인 컨테이너 기술을 개발하여 사용하고 있다.
2. 가상머신(Virtual Machine)과의 차이점은?
1) 기존 가상화 방식
도커는 컴퓨터 자원을 가상화 하는 방식 중 하나이다.
그럼 도커 이전에는 어떻게 가상화를 했을까?
기존의 가상화는 가상머신(Virtual Machine)을 사용했다.
(과거형처럼 말했지만, 현재도 잘 사용되고 있다.)
가상머신은 하이퍼바이저(Hypervisor)를 이용해 하나의 Host OS에서 여러 개의 Guest OS를 생성해서 사용하는 방식이다. VM 방식을 사용하는 툴로는 전공 수업에서 리눅스 환경을 만들기 위해 자주 사용했던 VMware, VirtualBox 등이 있다.
2) 각 방식은 Host OS의 커널과 어떻게 통신하는지..
하이퍼바이저를 이용해 가상화를 한다고 했지만 '그게 뭐 어때서?' 라는 생각이 들 수 있다.
이는 각 방식에서 Host OS의 커널과 어떻게 통신하는지 확인하면 알 수 있다.
답변을 먼저 말하자면 Host OS의 커널과의 공유 여부에 따라, 공유하지 않으면 '하드웨어 가상화' / 공유하면 '운영체제 가상화' 라고 볼 수있다.
두 가지 방식 중에 어느 것이 더 좋고 나쁨을 떠나 각 상황에 맞추어 적절하게 선택해서 사용해야 한다. 하드웨어 가상화 방식의 VM은 독립적인 운영체제 환경이 필요할 때 적합하고, 운영체제 가상화 방식의 Docker는 경량화와 효율성이 필요할 때 적합하다.
하드웨어 가상화(VM)
- 독립적인 Guest OS
가상 머신은 독립적인 운영체제(Guest OS)를 실행한다. 이 때 각 Guest OS는 자체 커널을 포함하고 있으며, 이는 Host OS의 커널과는 완전히 분리되어 있다. 이를 통해 각 Guest OS에서 실행되는 애플리케이션은 자체 커널을 통해 하드웨어에 접근한다.
- 하이퍼바이저
하이퍼바이저는 Host OS의 커널과 Guest OS의 커널 간의 중개자 역할을 한다.
이를 통해 Guest OS의 System call을 처리해 실제 하드웨어 자원에 접근할 수 있도록 한다.
이와 같이 Host OS의 각종 시스템 자원을 가상화하고 독립된 공간을 생성하는 작업, 그리고 System call은 하이퍼바이저를 거쳐야 해 일반 호스트와 비교해 성능 손실이 발생한다. 또한 애플리케이션으로 배포하고자 할 때 Guest OS를 사용하기 위한 운영체제, 라이브러리, 커널 등을 전부 포함하기 때문에 가상 머신을 배포하기 위한 이미지 용량이 매우 커지는 단점이 있다.
운영체제 가상화(Docker)
- 독립적인 컨테이너
"호스트 OS의 커널 공유", 이것이 핵심이다.
도커 컨테이너는 Guest OS와 동일하게 독립적인 환경에서 실행되지만, 도커 기술을 통해 Host OS의 커널을 공유해서 사용하기 때문에 VM보다 훨씬 가볍고 빠르다.
- 도커
도커는 리눅스의 LXC(namespace, cgroups)를 활용하여 각 컨테이너를 격리하고 자원을 제한한다.
(처음 개발될 당시에는 LXC 기반으로 구현됐지만, 최근에는 도커팀에서 자체적으로 개발한 runC를 사용한다.
runC는 libcontainer에서 발전된 형태로 우분투에 종속되지 않고 다양한 운영체제에서 리눅스 컨테이너를 실행할 있게 지원한다.)
3) 리눅스에서의 도커 동작은 이해됐어. 그럼 윈도우나 macOS에서 도커를 쓸 때는?
리눅스 Host OS에서 리눅스 Guest OS를 사용할 때는 "호스트 OS의 커널 공유"의 장점을 얻으며 컨테이너를 관리할 수 있다.
하지만 윈도우나 macOS에서는 어떨까??
당연하겠지만 Host OS와 Guest OS가 서로 다르므로 "Host OS의 커널 공유" 장점을 잃는다.
도커 또한 윈도우와 macOS에서 사용한다면 리눅스 커널을 직접 사용할 수 없기 때문에, 리눅스 환경을 실행하기 위해 하이퍼바이저가 필요하다. 따라서 하이퍼바이저를 통해 리눅스 가상 머신을 생성하고, 이 가상 머신 내에서 리눅스 커널을 실행하여 컨테이너를 동작시킨다.
(윈도우에서는 Hyper-V, MacOS에서는 HyperKit을 사용해 리눅스 VM을 실행한다.)
윈도우와 macOS에서 도커를 사용한다면 도커 또한 기존의 VM 방식과 동일하게 리눅스 운영체제를 가상화하는 작업이 필요하다.
Q. 그럼 윈도우와 macOS에서는 도커를 사용하는 것이 안 좋을까?
그래도 도커의 다른 장점인 애플리케이션 실행과 관리의 효율성은 그대로 가져갈 수 있다.
- 컨테이너 단위의 독립된 실행 환경 보장
- 도커 이미지를 이용한 동일한 배포 환경 구축 (개발-테스트-프로덕션 환경)
- 이미지 내용을 레이어 단위로 구성하여, 중복되는 레이어를 재사용하여 빠른 애플리케이션 배포
성능 측면에서는 정확하게 비교하기 위한 테스트가 필요하겠지만, 단일 리눅스 환경 실행 시는 도커와 기존의 VM의 리소스 소모는 크게 다르지 않을 수 있다. 하지만 다중 리눅스 환경을 실행한다면 동일한 리눅스 OS 커널을 공유하므로 리소스 효율성이 높아질 수 있다.
(단일에서도 도커용 VM은 컨테이너 단위 실행에 최적화되어 불필요한 기능이 제거되어 최적화 될 수 있다.)
3. Dockerfile
: 도커 이미지를 생성하기 위한 스크립트
이미지 어떻게 구성되고, 어떤 소프트웨어가 설치되는지, 그리고 어떤 설정이 적용되는지를 정의하는 명령어들이 포함되어 있다.
Dockerfile 예시 코드
# 1. 베이스 이미지로 적당한 JDK 버전 선택 (예: OpenJDK 17 사용)
FROM openjdk:17-jdk-alpine
# 2. 환경 변수 TZ를 설정하여 서울 시간대 지정
ENV TZ=Asia/Seoul
# 3. 작업 디렉토리 생성
WORKDIR /app
# 4. JAR 파일을 컨테이너의 작업 디렉토리로 복사
COPY project.jar /app/project.jar
# 5. 애플리케이션 실행 명령어 지정
ENTRYPOINT ["java", "-jar", "project.jar"]
*Dockerfile 명령어
FROM - FROM ubuntu:20.04 |
기본 이미지로 사용할 이미지를 지정한다. - 사용하려는 이미지가 docker engine에 없다면, 자동으로 도커 허브에서 pull을 시도한다. |
RUN - RUN apt-get update && apt-get install -y nginx |
컨테이너가 실행되는 동안 실행될 명령어를 정의한다. - 각 RUN 명령을 실행할 때마다 새로운 layer가 생성되고 캐시된다. - RUN 명령을 따로 실행한다면, apt-get update의 캐시된 목록을 사용하게 되어 최신 패키지를 설치할 수 없게 된다. |
COPY - COPY ./app /usr/src/app ADD - ADD https://example.com/file.tar.gz /usr/src/app/ |
build 명령 중에 호스트의 파일이나 디렉토리를 컨테이너 이미지 내로 모두 복사한다. ADD 명령문에서 URL에서 파일을 다운로드하거나 압축 파일을 자동으로 풀 수 있는 기능 추가 - 네트워크 상의 파일이나 압축 파일 다룰 때 사용 |
CMD - CMD ["nginx", "-g", "daemon off;"] ENTRYPOINT - ENTRYPOINT ["python3", "app.py"] |
컨테이너가 실행될 때 실행할 기본 명렁어를 정의한다. (docker run, 대체 가능) - Dockerfile에서 하나만 사용될 수 있으며, docker run 명령어를 사용될 때 대체된다. 컨테이너가 시작될 때 항상 실행할 명령어를 정의한다. (docker start, 대체 불가) - CMD를 인자로 받아 사용할 수 있다. - docker run에서 다른 명령을 지정해도 ENTRYPOINT는 대체되지 않고, ENTRYPOINT에서 정의한 명령의 인자로 전달된다. |
ENV - ENV APP_ENV=production |
컨테이너 내에서 사용할 수 있는 변수를 정의한다. |
EXPOSE - EXPOSE 80 |
컨테이너가 사용할 네트워크 포트를 지정한다. - 문서화 용도로 사용되어, 실제로 포트를 열지는 않는다. |
VOLUME - VOLUME ["/data"] |
호스트의 파일이나 디렉토리를 컨테이너와 공유하는 볼륨을 설정한다. |
WORKDIR - WORKDIR /usr/src/app |
명령어를 실행할 디렉토리를 설정한다. - 디렉토리가 존재하지 않으면 자동으로 생성된다. |
4. 도커 명령어
docker image
: 도커 컨테이너(애플리케이션)를 실행에 필요한 모든 파일을 포함한 템플릿
여러 이미지들을 Layer로 쌓아서 원하는 형태의 이미지를 효율적으로 만들 수 있다.
docker container
: docker image를 기반으로 실행되는 독립적인 실행 환경
*image 명령어
docker search ["Image Name:version"] → docker search ubuntu |
이미지 검색 - 태그(버전 정보)를 넣지 않으면, latest 태그가 붙어 최신 버전의 이미지를 조회함. |
docker pull ["Image Name"] → docker pull ubuntu |
이미지 다운로드 |
docker rmi ["Image ID / Image Repo Name] | 이미지 삭제 |
docker images |
다운로드 한 이미지 목록 |
docker build --tag "만들고 싶은 이미지 이름" -f ./ | Dockerfile로 이미지 생성 → --tag: 이미지 이름 설정 → -f: 빌드할 이미지 파일명 지정 (default: Dockerfile) → ./ : 현재 경로를 나타냄, 현재 폴더에 Dockerfile 파일이 있어야 함. |
*container 명령어
컨테이너 실행 상태, 라이프사이클 (STATUS)
Created(생성) → Up(실행중) → Pause(중지) → Exited(종료)
docker create ["image Name"] | 컨테이너 생성 - 바로 중지됨 |
docker start ["Container Name"] | 컨테이너 실행 |
docker run -it ["image Name"] | 컨테이너 생성 및 실행 |
docker stop ["Container ID / Name"] | 컨테이너 중지 |
docker rm ["Container ID / Name"] | 컨테이너 삭제 |
-i | 컨테이너 표준 입력(STDIN)을 활성화한다. |
-t | 가상 터미널(tty)을 할당하여, 컨테이너의 출력을 터미널 형태로 보여준다. |
-d | 컨테이너를 백그라운드에서 실행한다. |
-p | 호스트의 포트를 컨테이너 포트와 매핑한다. |
-v | 호스트의 파일이나 디렉토리를 컨테이너와 공유하는 볼륨을 설정한다. |
--name | 컨테이너 이름을 설정한다. |
--rm | 컨테이너 종료 시 컨테이너를 자동으로 삭제한다. |
*resource 관련 명령어
docker system df | 도커가 사용하고 있는 저장 공간 |
docker container stats | 실행 중인 모든 컨테이너 실시간 리소스 사용량 확인 |
*bulk delete 명령어
docker stop $(docker ps -a -q) | 모든 컨테이너 중지 |
docker rm $(docker ps -a -q) | 모든 컨테이너 삭제 |
docker rmi $(docker images -q) | 모든 이미지 삭제 |
참고문헌
1) Docker 개념 및 변천사
https://seosh817.tistory.com/345#google_vignette
https://medium.com/@devjohnpark/docker%EC%9D%98-%EB%B3%80%EC%B2%9C%EC%82%AC-9dfa764b217e
2) Dockerfile
https://wooono.tistory.com/123