주변에서 여러번 들어봤지만, 워낙 관련 개념이 방대하고, 사용이 어렵게 느껴질 수 있습니다.
차근차근 굵직한 개념들을 하나씩 알아본 뒤 관련 도커 실행 방법까지 알아보겠습니다.
시작해볼까요?
Docker(이하 도커)는 리눅스의 경량 가상화 기술인 리눅스 컨테이너 기능들을 쉽게 사용할 수 있도록 추상화해둔 리눅스 명령어이자 서버 애플리케이션입니다.
0.9 버전부터는 직접 개발한 libcontainer 컨테이너를 사용
리눅스 컨테이너(LXC)에 대해 간략하게 설명해보자면
리눅스 컨테이너는 리눅스 커널의 기능들을 사용해서 격리된 프로세스를 만드는 기술입니다.
사용된 리눅스 커널의 기능으로는
등이 있습니다.
이 쯤 알아두어도 충분하다고 생각합나다.
도커는 위와 같은 리눅스 컨테이너 기술을 구현한 대표적인 도구입니다.
도커의 가장 중요한 특징은 편리한 인터페이스와 Docker Hub를 기반으로 한 뛰어난 이식성에 있습니다.
특히 리눅스가 아닌 맥이나 윈도우에서도 도커를 사용할 수 있도록 Docker Desktop을 제공하고 있습니다.
그럼 본격적으로 컨테이너, 이미지부터 작동원리, Docker Hub 관련 개념까지 하나하나 알아보겠습니다!
도커를 정의할 때 무조건 등장하는 ‘가상화’ 개념에 대해 자세히 알아보겠습니다.
먼저 가상화란 CPU, Memory 같은 물리적인 하드웨어 자원을 논리적인(추상화) 리소스로 제공하기 위한 기술입니다.
가상화 기술에는 두가지 방식이 있습니다.
Hypervisor 가상화(VM)
물리적인 서버에서 하나 이상의 VM(Virtual Machine)이라는 독립된 가상환경을 만들고, VM에서 Hypervisor를 통해 독립적인 게스트 OS를 생성후 구동하는 구조입니다.
즉, 물리적 서버의 OS 위에서 다른 여러개의 독립적인 OS가 ‘가상적’으로 돌아가고 있는것이죠.
하나에 서버에서 돌아가지만, 완전히 독립적으로 운영되는 OS입니다.
Idle 상태 - 시스템이 사용자 또는 자동화된 작업 요청을 처리하지 않고, 대기 상태인 경우, 즉 CPU가 쉬고 있는 상태
컨테이너 가상화
컨테이너 가상화 방법은 호스트 OS 커널 위의 유저 공간(User Space)에서 실행합니다.
또한, 독립적인 OS를 가상화 하는 것이 아닌 독립적인 유저 공간을 가상화합니다.
즉, 호스트 서버 OS에서 여러 독립적인 유저 공간 인스턴스들을 가상적으로 실행할 수 있습니다.
Hypervisor 가상화 보다 훨씬 가볍기 때문에 독립적인 가상 환경 실행이 빠르고 쉽습니다.
(도커는 컨테이너 가상화를 사용하기 때문에 emulator 가 아닌 시스템 API 인터페이스 사용)
정리해보면 컴퓨터의 리소스와 성능을 낭비하지않고, 효율적으로 사용하기 위한 기술이 바로 ‘가상화’입니다.
컨테이너 개념에 대해 좀 더 자세히 살펴보겠습니다!
컨테이너와 VM 모두 다른 시스템과 격리되어 실행되는 측면은 유사하지만, 게스트 OS의 존재 유뮤로 인한 확장성과 이식성, 자원 효율성 등에 있어 차이가 존재합니다.
VM 과 다르게 컨테이너는 호스트OS를 공유하기에 자체적인 하나의 완전한 OS가 필요하지 않습니다.
따라서 CPU, RAM 같은 많은 양의 시스템 리소스를 확보할 수 있고, OS 패티 및 유지보수 오버헤드 감소 등 VM의 단점을 보완 할 수 있습니다.
실행 측면에서도 VM의 경우 큰 시간이 소요되지만, 컨테이너는 이미지(애플리케이션 구동을 위한 패키지)만 있다면 구동이 가능하기 때문에, 가볍고 빠르게 실행할 수 있습니다.
개인적으로 느끼기에 컨테이너 방식의 가장 큰 장점은 디스크 공간 점유도입니다.
VM의 경우 완전한 OS를 포함하기 때문에 디스크 공간을 사용할 수 밖에 없지만, 컨테이너는 이미지로부터 Copy on Write 방식으로 만들어지기 때문에 별도의 디스크 공간을 사용하지 않습니다.
장점만 있을 것 같지만 단점도 분명히 존재합니다.
이러한 컨테이너 기술을 구현한 오픈 소스가 바로 도커입니다.
도커에서 사용되는 이미지(Image)란 컨테이너를 실행할 수 있는 실행파일, 설정 값들을 가지고 있습니다.
이러한 이미지를 컨테이너에 담고, 실행을 시키면 해당 프로세스가 동작하게 됩니다.
그럼 이미지는 어떻게 만들어질까요?
위 예시를 보면, ubuntu
이미지를 만들기 위해 Layer A,B,C가 포함됩니다.
nginx
이미지를 만들기 위해선 마찬가지로 Layer A,B,C가 필요하고, 추가로 nginx
가 포함됩니다.
이때, 기존의 ubuntu 이미지를 베이스로 nginx
만 더하여 nginx
이미지를 만들게 됩니다.
즉, ubuntu + nginx
인 것이죠.
web app
이미지를 만들때도 동일하게 적용됩니다.
그렇다면 이미지는 누가 만드는 것일까요?
위에서 설명한 이미지들을 저장하고 배포할 때 Docker Hub(뒤에서 다룰 예정입니다)을 일반적으로 사용합니다.
하지만 만약 배포판이 없거나, 더 보완된 이미지를 생성하고 싶은 경우 Docker File을 사용할 수 있습니다.
Docker File에 이미지를 구성하기 위한 명령어들을 작성하여 이미지를 구성할 수 있습니다.
쉽게 말해 이미지를 만들기 위한 설정 파일입니다. Docker File을 통해 이미지의 구성과 생성에 관여할 수 있는것이죠.
Docker File의 구조는 대략 아래와 같습니다.
$ vim Dockerfile
FROM ubuntu:14.04
# app 디렉토리 생성
RUN mkdir -p /app
#Docker 이미지 내부에서 RUN, CMD, ENTRYPOINT의 명령이 실행될 디렉터리를 설정
WORKDIR /app
# 현재 디렉터리에 있는 파일들을 이미지 내부 /app 디렉터리에 추가
ADD . /app
RUN apt-get update
RUN apt-get install apache2
RUN service apache2 start
VOLUME ["/data", "/var/log/httpd"]
# 하기 포트를 외부로 노출
EXPOSE 80
# 쉘을 사용하지 않고 컨테이너가 시작되었을 때 logbackup 스크립트를 실행
CMD ["/app/log.backup.sh"]
FROM
기반이 되는 이미지 레이어입니다.
<이미지 이름>:<태그> 형식으로 작성
RUN
도커이미지가 생성되기 전에 수행할 쉘 명령어입니다.
VOLUME
VOLUME은 디렉터리의 내용을 컨테이너에 저장하지 않고 호스트에 저장하도록 설정합니다.
데이터 볼륨을 호스트의 특정 디렉터리와 연결하려면 docker run 명령에서 -v 옵션을 사용해야 합니다.
CMD
컨테이너가 시작되었을 때 실행할 실행 파일 또는 셸 스크립트입니다.
해당 명령어는 DockerFile내 1회만 쓸 수 있습니다.
WORKDIR
CMD에서 설정한 실행 파일이 실행될 디렉터리입니다.
EXPOSE
호스트와 연결할 포트 번호입니다.
아래 명령어를 통해 작성된 Docker File로 이미지를 생성할 수 있습니다.
# DockerFile 경로에서 입력해야 합니다
$ docker build -t [만들고싶은 이미지 이름]
정리해보자면, Docker File을 통해 도커 이미지를 생성할 수 있고, 생성된 이미지로 도커 컨테이너를 생성할 수 있습니다.
이 때 생성된 도커 컨테이너는 독립적인 프로세스 가상화 공간입니다!
조금 이해가 되셨나요?
도커 컨테이너를 생성하고, 실행하는 핵심 SW인 도커 엔진에 대해 자세히 한 번 알아보겠습니다.
조금은 어려운 내용이라 이해가 잘 되지 않아도 좋고, 그냥 넘어가셔도 괜찮습니다!
도커 엔진은 Docker Daemon, REST API, API를 통해 Docker Daemon과 통신하는 CLI로 구성되어 있습니다.
도커 컨테이너를 빌드, 실행, 배포하는 등의 작업은 Docker Daemon이 하고, Docker Client(CLI)는 이러한 명령을 함으로써 Docker Daemon과 통신합니다.
통신할 때는 CLI에 입력한 명령어를 REST API로 변환해 Docker Daemon에 POST 요청하게 됩니다.
Docker Daemon은 구체적으로 도커 이미지 관리, 빌드, REST API, 인증, 보안 등 다양한 작업을 수행합니다.
정리해보자면, Docker Client 를 통해 명령어를 작성 → Docker Daemon에 POST 요청 → 도커 컨테이너 빌드, 실행, 배포 등의 명령 수행 입니다!
조금 더 심화 단계로 가볼까요?
도커 프로젝트가 커지면서 Docker Daemon이 더이상 ‘직접’ 컨테이너를 실행시키지 않고,
컨테이너의 실행과 런타임 코드를 모듈화하여 Containerd 로 분리하였습니다.
따라서, 만약 CLI가 새로운 도커 컨테이너를 생성을 명령하면, Docker Daemon이 Containerd를 호출합니다.
이때 통신은 gRPC로 하게됩니다.
containerd에 대해 좀 더 알아보겠습니다.
containerd는 현재는 도커에서 분리되어 오픈소스로 운영되고 있는 컨테이너 런타임 코드입니다.
도커 컨테이너를 생성할 시, containerd는 도커 이미지를 가져온 뒤 컨테이너 구성을 적용하여 runC가 실행할 수 있는 OCI 번들로 변환합니다.
이후 runC를 통해 도커 컨테이너를 생성하게 됩니다.
OCI - Open Container Initiative는 커널의 컨테이너 관련 기술을 다루는 인터페이스를 표준화한 기준입니다
runC는 libcontainer 용 CLI Wrapper로, 독립된 컨테이너 런타임입니다.
도커에서 runC는 도커 컨테이너 생성을 목적으로만 쓰입니다.
Libcontainer - 도커 사가 multi-platform 서비스를 만들기 위해 Go언어로 작성된 패키지
따라서 실제 컨테이너를 생성시 사용되는 기술의 집합으로, runC를 통해 새로운 컨테이너를 생성합니다.
정리해보자면 현재 도커는 CLI를 통해 컨테이너 생성 명령어를 작성하면, Docker Daemon으로 요청이 날라가고,
Docker Daemon이 containerd 를 호출한 뒤 containerd 가 도커 이미지를 OCI 번들로 변환 후
마지막으로 runC가 컨테이너를 생성하게 됩니다.
위에서 잠깐 언급했던 Docker Hub에 대해서 알아보겠습니다.
Docker Hub은 도커에서 운영하는 도커 컨테이너 이미지들을 모아 놓은 원격 저장소입니다.
이러한 Docker 이미지를 저장하는 원격 저장소를 Image Registry라고 부르고, Docker Hub는 Docker의 기본이자 공식 Image Registry입니다.
(2021년 8월 기준 8,297,936개의 이미지가 업로드 되어 있습니다.)
도커 컨테이너를 생성할 수 있는 이미지를 빌드, 저장 및 공유할 수 있는 중앙 리포지토리로 사용됩니다.
유저들은 Docker Hub을 통해 이미지를 검색, 다운로드, 업로드하여 다른 유저와 공유할 수도 있습니다.
즉, Docker hub을 통해 다양한 컨테이너 이미지들을 손쉽게 사용할 수 있게됩니다.
간단하게 일반적인 사용법을 알아볼까요?
아래의 기능 사용을 위해 Docker Hub 계정 생성 및 Docker CLI를 통해 명령어를 사용해야 합니다.
Docker Hub에 저장되어 있는 다양한 이미지들을 검색하기 위해 아래 명령어을 사용합니다.
bash docker search <이미지_이름>
검색한 이미지를 다운로드(Pull) 하기 위해 아래 명령어를 사용합니다.
#<태그>는 이미지 버전을 의미 (default는 가장 최신 버전)
bash docker pull <이미지_이름>:<태그>
이미지를 Docker Hub로 업로드(Push)하려면 먼저 이미지 이름을 Docker Hub 계정 이름과 함께 태그한 뒤 업로드 해야합니다.
bash docker tag <로컬_이미지_이름> <Docker_Hub_계정_이름**************>/<**************이미지_이름>:<태그>
bash docker push <Docker_Hub_계정_이름>/<이미지_이름>:<태그>
다운로드한 이미지를 통해 컨테이너를 생성 및 실행하기 위해 아래 명령어를 사용합니다.
bash docker run (<옵션>) <이미지_이름>:<태그> (<명령어>)
사용법은 크게 어렵지 않은 것 같습니다.
마지막으로 장단점을 알아보겠습니다.
장점
단점
이번 기회에 이론뿐 아니라 직접 한 번 사용해보시는 걸 추천합니다!
이렇게해서 도커의 등장 배경과 관련 개념, 사용 방법까지 간략하게 알아봤습니다.
현재도 실제 활발하게 사용되는 도구인만큼 이론도 중요하지만, 직접 설치 후 원하는 이미지를 실행해보길 권장합니다.
긴 글 읽어주셔서 감사합니다!!
References
[Docker] 도커 컨테이너, 가상화, 내부구조 (feat. VM vs Container)