Skip to main content

Docker 기본 개념 정리

해당 글은 Docker & Kubernetes : 실전 가이드 강의 내용을 토대로 작성되었습니다.

이미지 & 컨테이너

도커의 기본 개념

도커는 컨테이너를 생성하고 관리하기 위한 도구이다.

  • 컨테이너란 표준화된 소프트웨어 유닛으로, 코드를 실행하는데 필요한 종속성과 도구과 포함된 코드 패키지이다.
  • 동일한 컨테이너는 어디서 누가 실행하든, 동일한 어플리케이션의 실행 및 동작을 생성한다.
  • 예를 들어, Node.js 어플리케이션을 구축하는 경우 도커로 빌드된 컨테이너에는 어플리케이션 소스 코드 뿐만 아니라 Node.js 런타임, 그리고 코드를 실행하는 데 필요한 기타 도구가 포함될 수 있다. 동일한 Node.js 코드와 도구를 사용하는 컨테이너는 항상 동일한 버전을 사용하는 Node.js 런타임에서 동일한 동작과 결과를 제공한다는 이점이 있다.
  • 도커는 이러한 컨테이너의 생성 및 관리를 간소화한다.

도커(컨테이너)가 필요한 이유

  • 프로덕션 환경과 똑같은 개발 환경에서 새로 개발되는 코드를 빌드하고 테스트할 수 있다.
  • 함께 개발하는 팀의 멤버들이 같은 프로젝트를 개발할 때 동일환 환경을 보장할 수 있다.
  • 작업 중인 프로젝트가 여러 개인 경우 프로젝트 별 개발 환경을 간편하게 분리할 수 있다.

도커와 가상머신의 차이점

스크린샷 2023-10-21 오후 10.55.07

가상머신으로도 컨테이너와 비슷한 맥락의 이점을 가져갈 수 있다. 하지만 가상머신은 실제로 우리 기기 위에서 실행되는 독립적인 컴퓨터와 같기 때문에 오버헤드가 발생한다. 모든 가상머신 환경에서 리눅스 OS를 사용하는 경우라도, 리눅스 OS가 모든 가상머신에 별도로 설치 돼야한다.

가상머신의 장점

  • 분리된 환경
  • 환경별 구성이 가능
  • 환경 구성을 안정적으로 공유 가능

가상머신의 단점

  • 중복 복제로 인한 공간 낭비
  • 성능 저하
  • 다른 컴퓨터 및 서버에서 복제하는 것이 까다로울 수 있음
  • 어플리케이션을 배포하려면 기존 세팅했던 가상머신과 동일한 방식으로 프로덕션 서버의 가상머신을 구성해야 한다.

스크린샷 2023-10-21 오후 11.07.55

도커 컨테이너를 사용하면 하나의 머신에 여러 대의 머신을 설치하지 않는다. 대신 운영 체제를 기본적으로 내재하고 있거나 컨테이너 에뮬레이트를 지원하는 내장 컨테이너를 활용한다. 그리고 그 위에 도커 엔진이라는 도구를 실행한다.

컨테이너에는 프로젝트 소스 코드와 코드를 실행하는 데 필요한 도구들이 포함되어있지만 부풀려진 운영 체제나 중복되는 추가 도구들은 포함되지 않는다. 또 컨테이너는 구성 파일을 사용하여 컨테이너를 구성하고 그에 대한 설명을 할 수 있기 때문에 다른 사람에게 컨테이너 구성을 공유하기가 용이하고 다른 시스템에서 동일한 컨테이너를 구성하고 실행하는 것이 가상머신에 비해 편리하다.

컨테이너의 장점

  • OS에 미치는 영향이 적고, 디스크 공간 사용량을 최소화한다.
  • 구성 공유와 재구축 및 배포가 간편하다.
  • 전체 머신의 캡슐화 대신 어플리케이션 및 환경의 캡슐화를 할 수 있다.

이미지와 컨테이너

스크린샷 2023-10-22 오후 5.32.51

도커 이미지는 컨테이너의 템플릿 / 블루프린트이다.

  • 이미지는 코드와 코드를 실행하는데 필요한 도구를 포함하고 있다.
  • 컨테이너가 실행되며 코드를 실행한다.
  • 이미지를 기반으로 여러 컨테이너를 만들 수 있다.
  • 즉 이미지란, 모든 설정 명령 및 코드가 포함된 공유 가능한 패키지이다. 그리고 컨테이너는 그러한 이미지의 구체적인 실행 인스턴스이다.

이미지를 사용하는 방식

  • 이미지를 사용하는 방식은 기본적으로 두 가지 방식이 있다.
    1. 이미 존재하는 이미지를 사용하는 방식 (예를 들어 도커 허브에 존재하는 Node와 같은 이미지)
    2. 존재하는 이미지를 기반으로 Dockerfile을 통해 커스텀 이미지를 생성하여 사용하는 방식

이미지의 특성

스크린샷 2023-10-22 오후 5.51.10

  • 이미지는 읽기 전용이다. (한 번 빌드되면 이미지를 다시 빌드하지 않는 한 이미지의 코드를 변경할 수 없다.)

  • CMD 명령은 이미지가 빌드될 때 실행되는 것이 아니라, 해당 이미지를 기반으로 컨테이너가 생성되고 시작될 때 실행되는 명령이다.

  • 이미지는 레이어 기반 아키텍처를 따른다.

    • 도커는 이미지를 빌드할 때마다 모든 명령 결과를 캐시하고, 이미지를 다시 빌드할 때 명령을 실행할 필요가 없으면 캐시된 결과를 사용한다. 이미지를 다시 빌드할 때 변경된 부분의 명령을 시작으로 그 이후의 모든 명령이 재평가된다. (레이어 변경 후의 모든 레이어)

      FROM node

      WORKDIR /app

      # 해당 레이어를 먼저 두어 package.json이 바뀌지 않았을 때는 npm install을 실행하지 않도록 할 수 있다.
      COPY package.json /app

      RUN npm install

      COPY . /app

      EXPOSE 80

      CMD ["node", "server.js"]
    • 이미지를 기반으로 컨테이너를 실행하면, 컨테이어는 기본적으로 Dockerfile에 지정한 명령을 실행한 결과로 이미지 위에 부가적인 레이어를 추가한다. 최종 명령 이전의 모든 명령은 이미 이미지의 일부이지만 별도의 레이어이다. 아무 것도 변경되지 않으면 이러한 레이어를 캐시에서 사용할 수 있다.

컨테이너의 특성

스크린샷 2023-10-22 오후 6.13.31

  • 컨테이너는 이미지를 기반으로 하는 실행 어플리케이션이다.
  • 컨테이너를 실행했을 때 이미지 위에 (읽기-쓰기가 가능한) 부가적인 레이어가 추가된다. 즉 새로운 컨테이너를 실행했을 때, 이미지에서 코드와 환경을 새 컨테이너로 모두 복사하지 않는다. 컨테이너는 이미지에 저장된 환경을 사용한다.
  • 모든 컨테이너는 독립적으로 실행되므로 어플리케이션 상태나 기록된 데이터를 공유하지 않는다.

이미지의 이름과 태그

스크린샷 2023-10-22 오후 9.01.35

  • 이미지의 태그는 이름과 태그 두 부분으로 구성된다.
  • 이름은 이미지의 레포지토리라고도 하며, 여러 개의 특정화된 이미지 그룹을 만들 수 있다.
  • 태그는 하나의 이미지 그룹 내에서 특정화된 버전을 정의하는데 사용된다.

이미지 공유 방식

스크린샷 2023-10-22 오후 9.10.56

  • Dockerfile을 공유할 수도 있지만(이 경우 소스코드도 함께 공유 돼야하기 때문에) 일반적으로 이미 빌드된 이미지를 공유하는 방식을 사용한다.
  • DockerHub를 사용하여 이미지를 공유하거나, 다른 여러 공급자들이 제공하는 개인 레지스트리를 활용하여 이미지를 공유할 수 있다.

이미지 & 컨테이너 관련 명령어

  • docker build . : 도커파일을 기반으로 이미지를 빌드하고 생성한다.
    • -t NAME:TAG : 이미지에 이름과 태그를 할당한다.
  • docker run IMAGE_NAME : 이미지를 기반으로 새 컨테이너를 생성 및 시작한다. (또는 이미지 id 사용)
    • --name NAME : 컨테이너에 이름을 할당한다. 이 이름은 중지 및 제거 등에 사용할 수 있다.
    • -d : 컨테이너를 분리 모드(detached mode) 로 실행한다. 컨테이너의 출력이 표시되지 않고, 명령 프롬프트/터미널이 컨테이너가 중지될 때까지 기다리지 않는다.
    • -it : 컨테이너를 대화형 모드(interactive mode) 로 실행한다. 컨테이너/응용프로그램이 명령 프롬프트/터미널을 통해 입력을 받을 준비가 된다. CTRL + C로 컨테이너를 중지할 수 있다.
    • --rm : 컨테이너가 중지되면 자동으로 제거한다.
  • docker ps : 실행 중인 모든 컨테이너를 나열한다.
    • -a : 중지된 컨테이너를 포함한 모든 컨테이너를 나열한다.
  • docker images : 로컬에 저장된 모든 이미지를 나열한다.
  • docker rm CONTAINER : 이름이 CONTAINER인 컨테이너를 제거한다. (또는 컨테이너 id 사용 가능)
  • docker rmi IMAGE : 이름/id로 이미지를 제거한다.
  • docker container prune : 중지된 모든 컨테이너를 제거한다.
  • docker image prune : dangling된 (태그가 없는 이미지)를 모두 제거한다.
    • -a : 현재 컨테이너에서 사용하고 있지 않은 이미지를 모두 제거한다.
  • docker push image : 이미지를 도커허브(또는 다른 레지스트리)로 푸시한다. 이미지 이름/태그에는 레포지토리 이름/URL이 포함되어야 한다.
  • docker pull image : 도커허브(또는 다른 레지스트리)에서 이미지를 가져온다. docker run image 를 실행했을 때 이미지를 가져오지 않은 경우 자동으로 수행된다.

데이터 관리

데이터의 종류

스크린샷 2023-10-22 오후 9.46.14

어플리케이션을 실행하고 운영하는 데 필요한 데이터의 종류는 크게 3가지로 나눌 수 있다.

  1. 어플리케이션 데이터 (소스 코드와 실행 환경)
    • 이미지 빌드 단계에서 복사된 이후로 고정되므로 수정하려면 이미지를 재빌드해야 한다.
    • 즉 읽기 전용 데이터이다.
  2. 임시 어플리케이션 데이터
    • 어플리케이션이 실행되는 동안 생성된 데이터 중에 영구적으로 저장할 필요가 없는 임시 데이터
    • 메모리 및 파일에 저장되지만 컨테이너가 종료 됐을 때 잃어도 상관 없는 데이터
    • 임시 데이터는 읽고-쓰기 때문에 이미지가 아닌 컨테이너에 저장된다. (이미지는 읽기 전용이기 때문에)
  3. 영구 어플리케이션 데이터
    • 사용자 정보처럼 지속되어야하는 데이터
    • 파일 및 데이터베이스에 저장되는 데이터
    • 컨테이너가 제거되더라도 그대로 보존되어야 한다.

컨테이너 데이터 관리의 문제

이미지 위에 도커에 의해 추가된 부가(extra) 레이어에는 이미지 및 파일 시스템을 인식하며 복사하지 않고 파일 시스템을 미러링하는 로직이 있다. 여기에서 도커는 읽기-쓰기 권한을 가지며 파일의 시스템을 조작할 수 있다. 도커는 컨테이너의 변경 사항을 추적하고 이미지의 파일 시스템을 가져와 최종 파일 시스템을 파생시켜 읽기-쓰기 레이어에 저장된 변경 사항과 결합시킨다. 그러나 읽기-쓰기 컨테이너를 사용하더라도 어플리케이션의 데이터를 관리함에 있어 두 가지 문제가 존재한다.

  1. 컨테이너에 쓰여진 데이터는 지속되지 않는다. 컨테이너를 중지하고 제거하면 컨테이너에 기록된 모든 데이터가 손실된다.
  2. 컨테이너가 호스트 파일시스템과 상호 작용할 수 없다. 호스트 프로젝트 폴더에서 무언가를 변경하더라도 실행 중인 컨테이너에 해당 변경 사항이 반영되지 않는다. (이미지를 다시 빌드하고 새 컨테이너를 시작해야 한다.)

볼륨 & 바인드마운트

스크린샷 2023-10-22 오후 10.02.33

도커에는 볼륨이라는 내장 기능이 있다. 볼륨은 도커가 인식하는 호스트 머신에 있는 폴더로서 도커 컨테이너 내부의 폴더에 매핑된다. Dockerfile의 COPY 명령은 복사하도록 명령한 경로와 파일의 스냅샷을 취한 다음 이러한 파일 및 폴더를 이미지에 복사하는 것이 전부이기 때문에 지속적인 관계나 연결이 없다. 하지만 볼륨은 컨테이너 내부의 폴더를 호스트 머신 상의 컨테이너 외부 폴더에 연결할 수 있다. 볼륨은 컨테이너가 제거되어도 데이터를 유지할 수 있다.

볼륨에는 두 가지 유형이 있다.

스크린샷 2023-10-29 오후 9.23.36

  • 익명(Anonymous) 볼륨

    • 단일 컨테이너를 위해 만들어진다.
    • 컨테이너를 실행할 때 --rm 명령을 추가하지 않는 한 컨테이너를 종료하고 재시작해도 유지된다. (익명 볼륨이 제거되지 않더라도 컨테이너를 제거하고 다시 시작하면 새 익명 볼륨이 생성된다. docker volume rm VOL_NAME 또는 docker volume prune 을 통해 삭제할 수 있다.)
    • 컨테이너 간에 공유될 수 없다.
    • 익명이므로 동일한 이미지에서도 재사용할 수 없다.
    • 도커는 호스트 머신에 폴더/경로를 설정하며, 정확한 위치는 사용자가 알 수 없으며 도커 볼륨 명령을 통해서만 관리된다.
  • 명명된(Named) 볼륨

    • 특정 컨테이너에 연결되지 않고 일반적으로 생성된다.
    • 컨테이너가 종료 및 삭제되어도 제거되지 않으며 별도의 Docker CLI를 통해 제거할 수 있다.
    • 컨테이너 간 공유가 가능하다.
    • 동일한 컨테이너에 재사용이 가능하다.
    • 도커는 호스트 머신에 폴더/경로를 설정하며, 정확한 위치는 사용자가 알 수 없으며 도커 볼륨 명령을 통해서만 관리된다.
    • 지속되어야하지만 호스트 머신에 의해 제어될 필요 없는 데이터에 유용하다.

바인드마운트

바인드 마운트를 사용하면 호스트 시스템의 파일이나 디렉터리가 컨테이너에 마운트된다.

  • 특정 컨테이너에 연결되지 않은 호스트 파일 시스템에 위치한다.
  • 컨테이너가 종료 및 삭제되어도 제거되지 않으며 호스트 fs에서 제거한다.
  • 컨테이너 간 공유가 가능하다.
  • 동일한 컨테이너에 재사용이 가능하다.
  • 호스트 머신 상에 매핑될 컨테이너의 경로를 직접 설정하기 때문에 경로를 알고 편집이 가능하다.
  • 지속되어야하고 컨테이너가 실행되는 동안 변경될 수 있는 데이터에 유용하다.

볼륨 & 바인드마운트 관련 명령어

  • docker run -v /path/in/container IMAGE : 컨테이너 안에 익명 볼륨을 생성한다.
  • docker run -v some-name:/path/in/container IMAGE : 컨테이너 내에 명명된 볼륨을 생성한다.
  • docker run -v /path/on/your/host/machine:path/in/container IMAGE : 바인드마운트를 생성하고 호스트 머신의 로컬 경로를 컨테이너의 특정 경로에 연결한다.
  • docker volume ls : 현재 활성/저장된 모든 볼륨을 나열한다.
  • docker volume create VOLUME_NAME : VOLUME_NAME 이라는 이름의 새 (명명된) 볼륨을 생성한다. 일반적으로 컨테이너를 실행할 때 볼륨이 존재하지 않는 경우 자동으로 생성되므로 이 작업을 꼭 수행할 필요는 없다.
  • docker volume rm VOLUME_NAME : 이름(또는 ID)로 볼륨을 제거한다.
  • docker volume prune : 사용하지 않는 모든 볼륨 (현재 실행 중이거나 중지된 컨테이너에 연결되지 않은 볼륨)을 제거한다.

환경변수 및 인수 사용

스크린샷 2023-10-29 오후 9.45.23

도커는 빌드 타임에서의 인수와 런타임 환경 변수를 지원한다. Argument를 사용하여 Dockerfile에서 유연한 데이터 비트, 즉 변수를 설정할 수 있다. 반면 환경변수는 실행 중인 어플리케이션의 전체 코드에서 사용할 수 있다.

FROM node:14

WORKDIR /app

COPY package.json .

RUN npm install

COPY . .

# ARG를 지정하지 않았을 때 기본 값 80
ARG DEFAULT_PORT=80

ENV PORT $DEFAULT_PORT

EXPOSE $PORT

# CMD 명령에서는 ARG 값을 사용할 수 없다. (컨테이너가 시작될 때 실행되는 런타임 명령이기 때문에)
CMD [ "npm", "start" ]

환경변수 및 인수 관련 명령어

  • docker run --env (-e 로 줄임 가능) ENV_NAME=ENV_VALUE : 환경 변수를 설정하여 컨테이너 실행
    • docker run --env-file /path/envfile : 파일을 통해 환경 변수를 설정
  • docker run --build-arg ARG_NAME=ARG_VALUE : 인수를 설정하여 컨테이너 실행