[Docker] 볼륨과 인수 및 환경 변수

2024. 10. 15. 14:59·DevOps
728x90
반응형

다양한 종류의 데이터

도커에는 다양한 종류의 데이터가 있다.

  • Application(Code + Environment)
    • 개발자가 직접 작성하여 제공하는 것
    • 코드와 전체 환경이 빌드 단계에서 이미지에 추가됨 by Dockerfile
    • 이미지 빌드 시 변경되지 않고 '고정'됨, 변경 시 재빌드 필요 -> Read-only
  • Temporary App Data(e.g. entered user input)
    • 애플리케이션이 실행되는 동안 생성된 데이터로, 일시적으로 관리될 필요가 있는 데이터(삭제되어도 됨)
    • 실행 중인 컨테이너로부터 fetched, produced
    • 메모리, 데이터베이스, 일시적인 파일 등으로 저장됨
    • 주기적으로 cleared 됨
    • 일시적인 Read + Write 전용이므로 이미지가 아닌 컨테이너에 저장됨(이미지는 Read-only)
    • 컨테이너가 삭제되면, 내부에 있던 임시 데이터 역시 모두 삭제된다.
  • Permanent App Data(e.g. user accounts)
    • 지속되어야 하는 데이터
    • 파일이나 데이터베이스에 저장됨 -> 컨테이너가 중지/재실행 되더라도 데이터 보존 필요
    • 영구적인 Read + Write 전용이므로 컨테이너에 저장되지만, 영구 저장을 위해 볼륨의 도움을 받음

일반적으로 컨테이너나 이미지와 로컬 파일 시스템 간에는 연결이 없다. 때문에, 한번 이미지를 빌드한 후 이를 통해 실행된 컨테이너에서 여러 파일들을 생성하더라도 이는 로컬 파일 시스템에는 저장되지 않는다. 즉, 완전히 격리되어 있고 이것이 곧 도커의 의도이다.

볼륨(Volumes)

컨테이너는 기본적으로 삭제될 시 내부 파일 시스템에 있는 모든 데이터가 함꼐 삭제된다. 하지만, 우리는 삭제하지 않고 싶은 데이터가 있을 것이다. 이럴 경우 어떻게 해야할까?

 

도커에는 볼륨이라는 내장 기능이 있다. 볼륨은 데이터를 유지하도록 도우며, 위에서 언급한 문제를 해결하는데 도움이 된다.

볼륨은 호스트 머신의 폴더이다. 컨테이너나 이미지에 있는 것이 아니다. 즉, 호스트 컴퓨터에 장착된 하드 드라이브에 존재하여 사용 가능하거나, 컨테이너로 매핑되는 것을 의미한다. 즉, 볼륨은 도커가 인식하는 호스트 머신인 사용자의 컴퓨터에 있는 폴더로서 도커 컨테이너 내부의 폴더에 매핑된다. COPY 명령어와 다르게 일회용이 아니라, 지속적인 연결을 맺는다.

 

호스트 머신의 폴더와 컨테이너 내부 폴더 간의 연결이 생기므로, 두 폴더의 변경 사항은 서로에게 반영된다. 따라서 호스트 머신에 파일을 추가하면 컨테이너 내부 폴더에서도 액세스 가능하다. 그리고 이러한 볼륨은 호스트 머신의 데이터를 삭제하지 않는 한, 컨테이너가 종료된 경우에도 지속되며 계속 존재한다.

도커의 외부 데이터 저장 매커니즘

컨테이너에 정의된 경로는 생성된 어떤 볼륨에 매핑된다. 그래서 호스트 머신 상의 생성된 경로로 연결된다.

  • Volume(Managed by Docker)
    • 일부 폴더와 경로를 호스트 머신에 설정
    • 익명 볼륨(Anonymous Volumes)
      • Dockerfile에서 VOLUME [ "/app/feedback" ]과 같이 컨테이너 내부의 경로만 지정
      • 호스트 머신의 경로는 지정하지 않았으므로 미러링된 폴더가 어디에 있는지 알 수 없다. (도커가 관리하는 어딘가에 있겠지만)
      • 이러한 볼륨에 접근하는 유일한 방법은 docker volume 커맨드를 사용하는 것이다.
      • 이러한 익명 볼륨은 도커에서 자동으로 명명하고 관리하며, 컨테이너 종료 시 자동으로 삭제된다.
        • 실제 호스트 머신과 연결되는 것은 맞지만, 어떤 폴더와 연결할 것인지를 명시해주지 않았기에, 도커가 임의로 관리하는 호스트 머신 상의 폴더와 연결을 하고, 이름도 지어준 것.
        • 도커에서 관리하는 폴더이기에 우리는 호스트 머신 상의 그 폴더의 경로를 알 수 없다.
      • --rm 옵션을 통해 컨테이너를 종료하면 익명 볼륨이 자동 제거되지만, docker rm ...을 통해 제거하면 익명 볼륨은 제거되지 않는다. 하지만, 다음에 컨테이너가 재시작될 때, 다른 익명 볼륨이 연결되기 때문에, 여전히 데이터는 유지가 되지 않는다.
        • 사용하지 않는 볼륨을 제거하려면 docker volume rm VOL_NAME 또는 docker volume prune을 사용하자!
    • 명명된 볼륨(Named Volumes)
      • 익명 볼륨과 다르게 컨테이너가 종료된 후에도 볼륨이 유지(호스트 머신의 하드드라이브의 폴더가 그대로 유지)된다.
      • 따라서 컨테이너가 삭제된 후, 새 컨테이너를 시작하면 볼륨과 폴더가 복구되어 해당 폴더에 저장된 모든 데이터를 계속 사용할 수 있다.
        • 컨테이너 삭제 후, docker volume ls를 통해 볼륨 리스트를 확인하면 삭제되지 않고 여전히 유지되어 있는 것을 확인할 수 있다.
      • 이 역시 도커가 폴더를 관리하며, 우리는 이 폳러에 액세스 할 수 없다. 익명 볼륨과의 차이점은 단지 이름을 우리가 지어주었다는 것이다.
      • 영구적이어야 하는 데이터나, 편집하거나 직접 볼 필요가 없는 중요한 데이터에 적합하다.
        • 실질적으로 호스트 머신의 폴더에 액세스 하지 않을 것이기 때문
      • docker run 시 -v 태그를 통해 Named Volumes를 추가할 수 있다.
        • ex) docker run -d -p 3000:80 --rm --name feedback-app -v feedback:/app/feedback feedback-node
          • -v [원하는 볼륨명]:[컨테이너 파일 시스템 내부 경로]
      • 다른 컨테이너에서도 동일한 볼륨을 사용할 수 있다.
  • Bind Mounts(Managed by you)
    • 우리가 소스 코드를 변경할 때마다, 이미지를 다시 빌드하지 않는 한 수정 사항은 실행 중인 컨테이너에 반영되지 않았다.
      • 개발하는 동안에는 너무 많은 것들이 변경되는데, 이를 위해 매번 이미지를 빌드하고 컨테이너를 재시작하는 것은 불편하다.
      • 여기에 도움을 주는 것이 바인드 마운트이다.
        • 볼륨의 경우 도커에 의해 관리되는 볼륨의 위치 즉 호스트 머시느이 파일 시스템 상의 볼륨이 어디에 있는지 알 수 없었다. 하지만, 바인드 마운트의 경우, 그 위치를 알고 있다.
      • 바인드 마운트는 호스트 머신 상에 매핑될 컨테이너의 경로를 설정하기 때문
    • 컨테이너 내부의 폴더가 우리가 지정한 호스트 머신의 폴더와 연결됨으로써 이미지를 재빌드하지 않더라도 해당 폴더의 최신 버전을 항상 참조하게 된다. -> 수정 사항이 즉각 반영된다.
    • 영구적이고 편집 가능한 데이터에 적합하다.
      • Named Volumes는 편집은 불가능했다.
    • 마찬가지로 docker run 시 -v 태그를 사용하면 된다.
      • ex) docker run -d -p 3000:80 --rm --name feedback-app -v /Users/maruhxn/study/docker-app:/app feedback-node
        • -v [호스트 폴더의 절대 경로]:[컨테이너 파일 시스템 내부 경로]
        • 만약 경로 상에 공백이 있거나 한 경우에는 큰따옴표(")로 전체 경로를 묶어주자
        • 항상 전체 경로를 복사하여 사용하고 싶지 않은 경우, shortcuts을 사용할 수 있다.
          • macOS / Linux: -v $(pwd):/app
          • Windows: -v "%cd%":/app

폴더를 컨테이너에 마운트하기 위해서는 바인딩 마운트로 공유 중인 폴더에 도커가 액세스할 수 있어야 한다!
이를 확인하기 위해서 Docker Desktop의 Preferences에 들어가서 Resources-File sharing을 확인하자.

만약 지금 공유하고 있는 폴더 혹은 그 폴더의 상위 폴더가 여기에 리스팅 되지 않았다면 도커가 해당 폴더에 액세스 할 수 없다.
하지만 보통 기본값으로 /Users와 같은 최상위 사용자 폴더가 포함되므로 따로 건들 일은 없다.

다른 볼륨 결합 & 병합하기

우리는 이미지를 만들기 위해 Dockerfile을 생성했다. 그리고 Dockerfile에서 이미지 생성 시 COPY . .를 통해 모든 것을 WORKDIR에 복사하게 했을 것이다. 하지만, 잘 생각해보면 바인드 마운트를 WORKDIR에 사용하게 되는 순간 이는 전부 무의미하다.

바인드 마운트를 사용하면 해당 폴더를 컨테이너 내부 폴더에 바인딩하게 된다. 즉, WORKIDR의 모든 것을 로컬 폴더로 덮어쓰게 되기 때문이다. (기껏 Dockerfile에서 복사해놨더니 docker run 시 바인드 마운트로인해 다시 전부 덮어쓰는 것)

 

그런데 이때, 로컬 폴더에는 소스 코드를 실행하기 위한 종속성이 있는 node_modules와 같은 폴더가 존재하지 않을 수도 있다. 이러면 소스 코드 실행 시 오류가 발생할 것이다. (바인드 마운트를 사용하지 않았다면 Dockerfile에서 잘 복사하고 npm install까지 하니 문제가 없었을 것이지만, 바인드 마운트로 인해 이 작업을 모두 덮어씀)

 

이를 어떻게 해결할 수 있을까??

 

먼저 컨테이너가 볼륨 및 바인드 마운트와 상호작용 하는 방식을 이해해보자.

  • 컨테이너는 -v 태그를 사용하여 Named Volume과 바인드 마운트를 동시에 사용할 수 있다.
  • 마운트된 데이터는 항상 컨테이너 내부의 데이터를 덮어쓴다
  • 볼륨과 바인드 마운트의 경우 모두 마운트 된 데이터가 우선시된다
    • 바인드 마운트와 Named Volume 모두 호스트나 Volume에 저장된 데이터가 우선 적용된다.
      = 컨테이너에만 데이터가 있고, 마운트된 경로에 데이터가 없을 경우:
      = Named Volume: 컨테이너의 데이터를 Volume으로 복사할 수 있다.
      • 바인드 마운트: 호스트 디렉토리가 비어 있다면 컨테이너의 데이터가 사라져 보이게 된다.
  • 이러한 성질로 인해, 위 예시에서 node_modules가 사라지는 상황이 발생하는 것이다..!

이를 해결하기 위해서는 도커가 덮어쓰지 않아야 할 특정 경로를 명시적으로 볼륨으로 지정해야 한다. 익명 볼륨이나 Named Volume을 사용하여 해당 경로를 마운트하면 된다.

 

이것이 가능한 이유는 도커는 여러 볼륨들이 있고 이들이 충돌할 경우, '더 구체적이고 자세한' 볼륨에 높은 우선순위를 매긴다는 특징이 있기 때문이다.

 

다음의 해결 예시를 보자.

  • 바인드 마운트 => -v ${pwd}:/app
  • 익명 볼륨 => -v /app/node_modules
    => docker run ... -v ${pwd}:/app -v /app/node_modules myapp

이렇게 설정하면 /app 폴더는 호스트의 폴더로 덮어씌워지지만, /app/node_modules는 익명 볼륨으로 인해 컨테이너 내부의 node_modules 폴더가 유지된다.

  1. Dockerfile에서 설치된 node_modules는 익명 볼륨을 통해 컨테이너 내부에 유지된다.
  2. 바인드 마운트를 통해 호스트의 /app 폴더가 /app 경로에 마운트되지만, node_modules 폴더는 호스트 폴더에서 전달되지 않는다.
  3. 익명 볼륨이 더 구체적이므로 /app/node_modules는 바인드 마운트로 덮어씌워지지 않고, 컨테이너 내부의 node_modules가 유지된다.

이와 같이 익명 볼륨을 사용하여 바인드 마운트 시 특정 폴더를 보호할 수 있다.

읽기 전용 볼륨

바인드 마운트를 사용하면 호스트 머신에서 파일을 변경하면 변경 사항을 컨테이너 내부에서 자동적으로 사용할 수 있다. 이는 컨테이너가 호스트 머신에 쓸 수 있어야 한다는 것이 아니라, 컨테이너는 호스트 머신에 있는 파일을 변경할 수 없어야 한다. 우리가 변경을 가할 수 있는 곳은 호스트 머신 파일 시스템이지, 컨테이너 내부가 아니다.

 

우리는 이러한 의도를 명확하게 하기 위해 바인드 마운트를 읽기 전용 볼륨으로 전환할 수 있다.

  • -v [호스트 폴더의 절대 경로]:[컨테이너 파일 시스템 내부 경로]:ro
  • 도커가 해당 폴더나 그 하위 폴더에 쓸 수 없게 된다. (컨테이너 내부에서)
  • 이때 해당 폴더 내의 몇개의 하위 폴더에는 쓰기 옵션을 주고 싶다면, 해당 폴더들만 따로 '익명 볼륨'을 만들면 해결 가능하다.

볼륨 관리하기

  • docker volume ls
    • 현재 활성화 중인 볼륨을 모두 리스팅한다.
    • *바인드 마운트는 도커에 의해 관리되는 볼륨이 아니기 때문에 이 리스트에 표시되지 않는다. *
  • docker volume inspect [볼륨명]
    • 볼륨에 대한 몇 가지 정보를 확인할 수 있다
    • CreatedAt, Driver, Labels, Moutpoint, Name, Options, Scope를 확인할 수 있다.
  • docker volume rm [볼륨명]
    • 사용 중이지 않은 볼륨을 제거한다.
    • 해당 볼륨이 특정 컨테이너에서 사용 중이라면 에러가 발생한다.
  • docker volume prune
    • 사용하지 않는 모든 볼륨을 제거한다.

COPY 사용 vs 바인드 마운트

바인드 마운트를 사용하면 코드 변경 사항을 자동으로 반영하는데도 Dockerfile에서 COPY . .를 하는 이유는 무엇일까?
즉, 바인드 마운트로 전체 폴더를 볼륨으로 사용하는데 이미지가 생성될 때 COPY를 통해 복사하는 이유가 뭘까? COPY 단계를 제거할 수 있지 않을까?

 

가능하다.

 

하지만, 잊지말아야 할 것은 docker run ... 명령은 개발 중에 사용하는 명령이다. 바인드 마운트는 개발 중에 사용되어 코드의 변경 사항을 실행 중인 컨테이너에 즉시 반영하였다. 개발을 마친 뒤에는 실제로 배포를 할 때에는 바인드 마운트로 실행하지 않을 것이다. 데이터가 유지되도록 다른 볼륨을 사용할 수는 있지만 우리가 개발 중에 사용한 바인드 마운트를 사용하지는 않을 것이다. 컨테이너가 서버 상의 제품 상태로 실행 중이라면, 싱행되는 동안 실시간으로 업데이트 되는 연결된 소스 코드가 없기 때문이다. 배포 시에는 항상 스냅샷이 필요할 것이며, 이를 위해서는 COPY 명령을 유지해야 한다.

.dockerignore

COPY 명령 시 복사하지 않을 것을 따로 명시해줄 수 있는데, '.dockerignore' 파일에서 이를 관리할 수 있다. 이는 git의 '.gitignore'와 유사하다.

인수와 환경 변수

  • 빌드 타임 인수
    • Dockerfile에서 특정 Dockerfile 명령으로 다른 값을 추출하는데 사용할 수 있는 유연한 데이터 변수를 설정
    • docker build를 실행할 때, --build-arg 옵션과 함께 제공되는 인수를 기반으로 한다
    • Dockerfile에서 ARG DEFAULT_PORT=80 처럼 설정할 수 있고, 이를 통해 빌드 인수가 넘어올 경우 해당 값을, 넘어오지 않으면 기본값인 80을 사용하도록 설정할 수 있다.
    • 해당 Dockerfile 내에서 빌드 인수를 사용 가능하다. 단, CMD와 같은 런타임 명령어에서는 사용 불가하다. 런타임 환경 변수의 기본 값을 전달하기 위해 빌드 인수를 넘겨주는 방식으로 주로 사용한다. ex) ENV PORT $DEFAULT_PORT
    • 이미지를 빌드할 때 특정 값을 설정할 수 있고, Dockerfile을 매번 변경하지 않고도 유연한 방식으로 다른 이미지를 빌드할 수 있게 된다
      • docker build -t myapp -> Dockerfile에 명시된 기본값인 80 포트 사용
      • docker build -t myapp --build-arg DEFAULT_PORT=8000 -> Dockerfile에 명시된 값은 무시하고 전달된 8000포트 사용
  • 런타임 환경 변수
    • 빌드 타임 인수처럼 Dockerfile 내부에서 사용 가능하지만, 환경 변수는 애플리케이션 코드 내에서도 사용 가능하다.
    • Dockerfile 내부의 ENV 명령 또는 docker run 시 --env 옵션(또는 -e)을 통해 설정할 수 있다.
    • ex) Dockerfile 내부에서 ENV PORT 80 or 컨테이너 실행 시 docker run -d --rm -p 3000:8000 --env PORT=8000 myapp
    • 또 다른 방법으로는 .env라는 파일을 만들어서 해당 파일을 참조하여 환경변수를 설정하도록 할 수도 있다.
      • 이때는 -env 옵션이 아니라 --env-file 옵션을 사용해야 한다.
      • ex) docker run --env-file ./.env myapp
    • Dockerfile에 명시한 환경 변수는 이미지에 포함되어 docker history <이미지>를 통해 값을 읽을 수 있어서 보안과 관련된 환경 변수는 Dockerfile에 추가하지 않는 것이 좋다. 따라서, 중요한 환경 변수의 경우 런타임에만 사용될 수 있도록 docker run 시 옵션으로 런타임에만 제공하자.

Dockerfile 내부에서 선언된 환경 변수는 같은 Dockerfile 내부에서 $를 붙여 재사용할 수 있다.

ENV PORT 80을 선언했으면, 이제 그 밑의 명령어에서 $PORT로 해당 환경 변수를 사용 가능하다.

 

이를 이용한다면 중요한 변수 값을 하드 코딩할 필요 없이, 보다 유연한 이미지완 컨테이너를 만들 수 있다.
대신, 이미지를 빌드할 때 또는 컨테이너를 실행할 때만 동적으로 설정 가능하다는 것을 잊지말자!

정리

  • 컨테이너는 도커의 핵심이며 데이터를 읽고 쓸 수 있다. (컨테이너는 이미지 위에 read-write 레이어를 추가한다)
  • 하지만, 컨테이너가 제거되면 컨테이너 내부 데이터가 손실된다. -> 유지하고 싶은 데이터가 있을 텐데 이를 위해 볼륨을 추가한다
  • 볼륨은 컨테이너가 실행되는 호스트 머신 상의 폴더이다. 도커에 의해 관리되며, 도커 컨테이너에 마운트된다.
    • 컨테이너 내부의 매핑된 경로에 기록된 모든 내용은 호스트 머신에도 저장되며, 이 데이터는 컨테이너가 제거된 경우에도 유지된다.
  • Named 볼륨은 컨테이너가 제거되어도 살아남기 때문에 유용하다. 이는 데이터를 영구 저장하려는 경우에 필요하다.
  • 익명 볼륨은 컨테이너에 연결되며 컨테이너가 --rm 옵션에 의해 제거되면 함께 제거된다. 이는 영구 데이터를 저장하는데는 유용하지 않다.
    • 컨테이너를 효율적으로 만들기 위해 임시 데이터를 저장하는데는 여전히 유용하다.
    • 컨테이너에 더 적은 데이터를 저장하고, 더 많은 데이터를 호스트 시스템에 아웃소싱하기 때문이다.
  • 이러한 볼륨들이 바인드 마운트와 함께 유용하게 사용될 수 있다. 바인드 마운트는 호스트 머신의 로컬 폴더를 직접 매핑할 수 있다.
    • 여기서 익명 볼륨과 함께 사용하여, 덮어쓰고 싶지 않은 컨테이너에 이미 존재하는 폴더를 실수로 덮어쓰지 않도록 할 수 있다.
    • 바인드 마운트는 Named 볼륨과 비슷하지만, 데이터가 미러링되는 호스트 머신의 경로를 알고 있고, 실제로 그 경로를 사용하여 데이터를 컨테이너에 전달하여 호스트 머신에서 그 데이터를 변경할 수 있다는 점에서 차이가 있다.
  • 빌드 인수와 런타임 환경 변수를 통해 이미지와 컨테이너를 동적으로 구성 가능하게 만들 수 있다.
    • Dockerfile이나 소스 코드에서 변수를 하드코딩할 필요 없이, 컨테이너를 실행할 때 이를 구성할 수 있게 된다.
728x90
반응형

'DevOps' 카테고리의 다른 글

[Kubernetes] 쿠버네티스 기본 개념  (0) 2024.10.20
[Docker] Docker Compose  (1) 2024.10.16
[Docker] 네트워킹: (교차) 컨테이너 통신  (1) 2024.10.15
[Docker] 이미지와 컨테이너  (3) 2024.10.14
[Docker] Docker란?  (1) 2024.10.14
'DevOps' 카테고리의 다른 글
  • [Docker] Docker Compose
  • [Docker] 네트워킹: (교차) 컨테이너 통신
  • [Docker] 이미지와 컨테이너
  • [Docker] Docker란?
mxruhxn
mxruhxn
소소하게 개발 공부 기록하기
    반응형
    250x250
  • mxruhxn
    maruhxn
    mxruhxn
  • 전체
    오늘
    어제
    • 분류 전체보기 (150)
      • Java (21)
      • Spring (4)
      • Database (13)
      • Operating Syste.. (1)
      • Computer Archit.. (0)
      • Network (24)
      • Data Structure (6)
      • Algorithm (11)
      • Data Infra (7)
      • DevOps (12)
      • ETC (27)
      • Project (21)
      • Book (1)
      • Look Back (1)
  • 블로그 메뉴

    • 링크

      • Github
    • 공지사항

    • 인기 글

    • 태그

    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.0
    mxruhxn
    [Docker] 볼륨과 인수 및 환경 변수
    상단으로

    티스토리툴바