Docker 태스크 드라이버 설정하기

Docker 태스크 드라이버 설정하기

Nomad의 Docker 태스크 드라이버는 잡에서 Docker 기반 태스크를 실행하게 해줘요. 이 페이지에서는 Docker 태스크 드라이버의 플러그인 구성을 바꾸는 법과, CPU·메모리·파일시스템 IO·보안 리소스 격리, 그리고 Nomad가 떠다니는(dangling) 컨테이너를 어떻게 처리하는지를 다뤄요.

출처: Hashicorp 공식 문서 - Configure the Docker task driver

본문

이름(Name): docker

docker 드라이버는 Nomad에서 일급(first-class) Docker 워크플로를 제공해요. Docker 드라이버는 컨테이너 다운로드, 포트 매핑, 컨테이너 시작·감시·정리를 처리해요.

참고: Windows나 macOS에서 Docker Desktop을 사용한다면 FAQ를 확인해 보세요.

기능(Capabilities)

docker 드라이버는 다음 기능을 구현해요:

Feature Implementation
nomad alloc signal true
nomad alloc exec true
filesystem isolation image
network isolation host, group, task
volume mounting all

클라이언트 요구 사항 (Client Requirements)

Nomad는 host에 Nomad 에이전트와 함께 Docker가 설치·실행돼 있어야 해요.

기본적으로 Nomad는 데몬의 Unix 소켓을 사용해 Docker 데몬과 통신해요. Nomad는 이 소켓에 대한 읽기/쓰기 권한이 필요해요. Nomad를 root로 실행하지 않는다면, Nomad가 Docker 데몬과 통신할 수 있도록 Nomad 사용자를 Docker 그룹에 추가해야 해요.

예를 들어 Ubuntu에서는 usermod 명령으로 nomad 사용자를 docker 그룹에 추가해 root 없이 Nomad를 실행할 수 있어요:

$ sudo usermod -G docker -a nomad

Nomad 클라이언트는 코어를 예약하거나 공유하기 위해 각 태스크에 대해 cpuset cgroup을 관리해요. Nomad가 Docker 자체의 cgroups 관리와 호환되려면 Docker가 소유한 cgroups에 써야 하는데, 이는 root로 실행해야 가능해요. Nomad가 root로 실행되지 않으면 resources.cores가 있는 워크로드(같은 호스트에서 docker가 아닌 다른 태스크 드라이버를 사용하는 워크로드 포함)에 대해 CPU 격리와 NUMA-aware 스케줄링이 제대로 작동하지 않아요.

최상의 성능과 보안 기능을 위해 최신 버전의 Linux 커널과 Docker 데몬을 사용하는 걸 권장해요.

Nomad 클라이언트에서 docker 드라이버와 관련된 옵션을 바꾸고 싶다면 plugin 블록 문법으로 수정할 수 있어요. 아래는 구성의 예시예요(많은 값이 기본값이에요). 옵션에 대한 자세한 내용은 다음 섹션을 참고해요.

plugin "docker" {
  config {
    endpoint = "unix:///var/run/docker.sock"

    auth {
      config = "/etc/docker-auth.json"
      helper = "ecr-login"
    }

    tls {
      cert = "/etc/nomad/nomad.pub"
      key  = "/etc/nomad/nomad.pem"
      ca   = "/etc/nomad/nomad.cert"
    }

    extra_labels = ["job_name", "job_id", "task_group_name", "task_name", "namespace", "node_name", "node_id"]

    gc {
      image       = true
      image_delay = "3m"
      container   = true

      dangling_containers {
        enabled        = true
        dry_run        = false
        period         = "5m"
        creation_grace = "5m"
      }
    }

    volumes {
      enabled      = true
      selinuxlabel = "z"
    }

    allow_privileged = false
    allow_caps       = ["chown", "net_raw"]

    allowed_modes = {
      pid = ["container"]
      ipc= ["sharable"]
    }
  }
}

플러그인 옵션 (Plugin Options)

  • endpoint - 비표준 소켓, HTTP, 다른 위치를 사용하거나 TLS를 사용할 때는 docker.endpoint를 설정해야 해요. 설정하지 않으면 Nomad는 DOCKER_HOST 환경 변수로 Docker 클라이언트를 만들려고 시도하고, 실패하면 해당 운영체제의 기본 수신 주소로 폴백해요. Unix 플랫폼에서는 기본적으로 unix:///var/run/docker.sock, Windows에서는 npipe:////./pipe/docker_engine이에요.

  • allow_privileged - 기본값은 false예요. true로 바꾸면 컨테이너가 privileged 모드를 사용할 수 있는데, 이는 컨테이너에 호스트 디바이스에 대한 전체 접근 권한을 부여해요. 이 작업이 동작하려면 Docker 데몬에도 비슷한 설정을 해야 해요.

  • allowed_modes - pid, ipc, userns, uts 컨테이너 네임스페이스에 대한 허용 목록(allowlist)을 활성화해요. 드라이버는 allow_privileged가 활성화됐는지와 무관하게 태스크 구성의 pid_mode, ipc_mode, userns_mode, uts_mode 값에 매칭되는 것을 받아들여요. 허용 목록 안에서는 글롭(예: container*)이 지원되고, 각 네임스페이스의 허용 옵션은:

  pid = ["", "host", "container"]
  ipc= ["", "none", "host", "container", "private", "sharable"]
  userns= ["", "host"]
  uts= ["", "host"]
  • pull_activity_timeout - 기본값은 2m이에요. 이미지 풀 중 이 시간 동안 Docker 엔진으로부터 통신을 받지 못하면 Nomad는 풀 명령을 시작한 요청을 타임아웃 처리해요. (최소 1m)

  • pids_limit - 기본값은 무제한(0)이에요. 해당 Nomad 클라이언트에서 실행 중인 모든 Docker 컨테이너의 pid 한도를 지정하는 정수 값이에요. 태스크 구성에 pids_limit을 설정해 이 한도를 덮어쓸 수 있어요. 이 값이 0보다 크면 태스크 pids_limit은 여기에 정의된 값보다 작거나 같아야 해요.

  • allow_caps - 허용되는 Linux 기능(capabilities) 목록이에요. 기본값은:

["audit_write", "chown", "dac_override", "fowner", "fsetid", "kill", "mknod",
 "net_bind_service", "setfcap", "setgid", "setpcap", "setuid", "sys_chroot"]

이는 docker 기본 허용 기능 목록과 같아요(NET_RAW 제외). 운영자가 cap_addcap_drop 옵션을 쓰는 태스크가 얻을 수 있는 기능을 제어하게 해줘요. 운영체제가 지원하는 모든 기능을 허용 목록에 넣는 단축값 "all"을 지원해요. Docker의 제약 때문에 비-root 사용자로 실행되는 태스크는 기본값을 넘어 기능을 확장할 수 없어요. 기능을 줄이는 것만 가능해요.

!> 경고: 기본값을 넘어 더 많은 기능을 허용하면 바람직하지 않은 결과가 생길 수 있어요. 신뢰할 수 없는 태스크가 호스트 시스템을 손상시킬 수도 있죠.

  • allow_runtimes - 기본값은 ["runc", "nvidia"]예요. 태스크가 사용할 수 있는 허용된 docker 런타임 목록이에요.

  • auth 블록:

    • config - 운영자가 dockercfg 형식의 JSON 파일을 지정할 수 있게 해줘요. (순서대로) auths, credsStore, credHelpers 중 하나에서 개인 레지스트리용 인증 정보를 담고 있어요.

    • helper - 운영자가 $PATH에 있는 credsStore 같은 스크립트로 외부 소스에서 인증 정보를 조회하게 해줘요. 스크립트 이름은 docker-credential-로 시작해야 하고, 이 옵션에는 경로가 아닌 스크립트의 basename만 포함해야 해요.

      인증 헬퍼를 설정하면 공개 이미지를 포함한 모든 이미지에 적용돼요. 개인 이미지와 공개 이미지를 섞어 쓴다면, 공개 이미지를 쓰는 모든 잡에 auth_soft_fail=true를 포함해야 해요.

  • tls 블록:

    • cert - 서버 인증서 파일(.pem) 경로예요. keyca와 함께 지정해서 TLS 클라이언트로 docker 데몬에 연결해요. endpoint도 지정해야 하며, 그렇지 않으면 이 설정은 무시돼요.

    • key - 클라이언트 개인 키(.pem) 경로예요. certca와 함께 지정해서 TLS 클라이언트로 docker 데몬에 연결해요. endpoint도 지정해야 하며, 그렇지 않으면 이 설정은 무시돼요.

    • ca - 서버 CA 파일(.pem) 경로예요. certkey와 함께 지정해서 TLS 클라이언트로 docker 데몬에 연결해요. endpoint도 지정해야 하며, 그렇지 않으면 이 설정은 무시돼요.

  • disable_log_collection - 기본값은 false예요. true로 설정하면 Docker 태스크의 Nomad 로그 수집을 사용하지 않아요. nomad 로그 기능에 의존하지 않고 호스트 기반 로그 수집만 쓴다면, nomad 로그 수집 오버헤드를 끄기 위해 이 옵션을 고려할 수 있어요.

  • extra_labels - Docker 컨테이너에 추가할 라벨이에요. 사용 가능한 옵션은 job_name, job_id, task_group_name, task_name, namespace, node_name, node_id예요. 글롭이 지원돼요(예: task*)

  • logging 블록:

    • type - 기본값은 "json-file"이에요. Nomad가 시작하는 모든 컨테이너에 docker가 사용할 로깅 드라이버를 지정해요. 이전 버전의 Docker에서는 json-file이나 journald만 Nomad가 Docker API를 통해 드라이버 로그를 읽게 하고, 그러지 않으면 nomad alloc logs 같은 명령이 동작하지 않아요.

    • config - 기본값은 { max-file = "2", max-size = "2m" }이에요. 이 옵션은 로깅 드라이버에 추가 구성을 전달하는 데도 쓰일 수 있어요.

  • gc 블록:

    • image - 기본값은 true예요. false로 바꾸면 Nomad가 중지된 태스크의 이미지를 제거하지 않아요.

    • image_delay - 여기에 정의된 시간 기간으로, 기본값은 3m이에요. 이 지연은 이미지가 사용되지 않게 된 뒤 삭제되기까지 Nomad가 기다리는 시간을 제어해요. 지연 안에 같은 이미지를 쓰는 태스크를 받으면 이미지를 재사용해요. 이미지가 둘 이상의 태그로 참조되면 image_delay가 제대로 작동하지 않을 수 있어요.

    • container - 기본값은 true예요. 이 옵션은 태스크가 종료될 때 Nomad가 컨테이너를 제거하지 않도록 끌 수 있어요. 이름 충돌이 있으면 Nomad가 여전히 죽은 컨테이너를 제거할 수 있어요.

    • dangling_containers 블록 - 떠다니는 컨테이너 감지·정리를 제어해요:

      • enabled - 기본값은 true예요. 떠다니는 컨테이너 처리를 활성화해요.

      • dry_run - 기본값은 false예요. 정리하지 않고 떠다니는 컨테이너를 로그로만 남겨요.

      • period - 기본값은 "5m"이에요. Nomad가 떠다니는 컨테이너를 스캔하는 간격을 제어하는 시간 기간이에요.

      • creation_grace - 기본값은 "5m"이에요. 컨테이너가 생성된 후 GC가 무시하는 유예 기간이에요. GC에 등록되기 전에 새로 생성된 컨테이너가 GC에 의해 제거되지 않도록 막는 데만 사용해요. 더 높게 조정할 필요는 없지만, 더 공격적으로 GC하려면 낮출 수 있어요.

  • volumes 블록:

    • enabled - 기본값은 false예요. 태스크가 컨테이너 안에 호스트 경로(volumes)를 바인드하고 볼륨 드라이버(volume_driver)를 사용하게 해줘요. 상대 경로 바인딩은 항상 허용되며 할당의 디렉토리를 기준으로 해석돼요.

    • selinuxlabel - 운영자가 할당과 태스크 로컬 바인드-마운트에 SELinux 라벨을 컨테이너에 설정할 수 있게 해줘요. docker.volumes.enabled가 false로 설정돼 있어도 이 라벨은 컨테이너의 표준 바인드에 여전히 적용돼요.

  • infra_image - 태스크 간에 네트워크 네임스페이스를 공유할 때 필요한 부모 컨테이너를 만들 때 사용하는 Docker 이미지예요. 기본값은 registry.k8s.io/pause-<goarch>:3.3예요. 태그가 latest이거나 이미지가 로컬에 아직 없을 때만 컨테이너 레지스트리에서 이미지를 내려받아요.

  • infra_image_pull_timeout - infra_image에 지정된 Docker 이미지의 진행 중인 풀을 취소하기 전에 Nomad가 기다리는 시간을 제어하는 시간 기간이에요. 기본값은 "5m"이에요.

  • image_pull_timeout - (선택) 모든 태스크에서 image에 지정된 Docker 이미지의 진행 중인 풀을 취소하기 전에 Nomad가 기다리는 기본 시간을 제어해요. 기본값은 "5m"이에요.

  • windows_allow_insecure_container_admin - Windows에서 docker가 컨테이너를 내려받은 뒤 task.user 필드(설정되지 않았다면 컨테이너 이미지 매니페스트)를 확인해 ContainerAdmin으로 실행 중인지 확인함을 나타내요. 그렇다면 태스크 구성에 privileged=true가 없으면 오류로 종료돼요. 기본값은 false예요.

클라이언트 구성 (Client Configuration)

~> 참고: 클라이언트 구성 옵션은 곧 폐기될 예정이에요. 대신 plugin 옵션을 사용해 주세요. 자세한 내용은 plugin 블록 문서를 참고해요.

docker 드라이버에는 다음 클라이언트 구성 옵션이 있어요:

  • docker.endpoint - 비표준 소켓, HTTP, 다른 위치를 사용하거나 TLS를 사용할 때는 docker.endpoint를 설정해야 해요. 설정하지 않으면 Nomad는 DOCKER_HOST 환경 변수로 Docker 클라이언트를 만들려고 시도하고, 실패하면 해당 운영체제의 기본 수신 주소로 폴백해요. Unix 플랫폼에서는 기본적으로 unix:///var/run/docker.sock, Windows에서는 npipe:////./pipe/docker_engine이에요.

  • docker.auth.config - 운영자가 dockercfg 형식의 JSON 파일을 지정하게 해줘요. (순서대로) auths, credsStore, credHelpers 중 하나에서 개인 레지스트리용 인증 정보를 담고 있어요.

  • docker.auth.helper - 운영자가 $PATH에 있는 credsStore 같은 스크립트로 외부 소스에서 인증 정보를 조회하게 해줘요. 스크립트 이름은 docker-credential-로 시작해야 하고, 이 옵션에는 경로가 아닌 스크립트의 basename만 포함해야 해요.

  • docker.tls.cert - 서버 인증서 파일(.pem) 경로예요. docker.tls.keydocker.tls.ca와 함께 지정해서 TLS 클라이언트로 docker 데몬에 연결해요. docker.endpoint도 지정해야 하며, 그렇지 않으면 이 설정은 무시돼요.

  • docker.tls.key - 클라이언트 개인 키(.pem) 경로예요. docker.tls.certdocker.tls.ca와 함께 지정해서 TLS 클라이언트로 docker 데몬에 연결해요. docker.endpoint도 지정해야 하며, 그렇지 않으면 이 설정은 무시돼요.

  • docker.tls.ca - 서버 CA 파일(.pem) 경로예요. docker.tls.certdocker.tls.key와 함께 지정해서 TLS 클라이언트로 docker 데몬에 연결해요. docker.endpoint도 지정해야 하며, 그렇지 않으면 이 설정은 무시돼요.

  • docker.cleanup.image 기본값은 true예요. false로 바꾸면 Nomad가 중지된 태스크의 이미지를 제거하지 않아요.

  • docker.cleanup.image.delay 여기에 정의된 시간 기간으로, 기본값은 3m이에요. 이 지연은 이미지가 사용되지 않게 된 뒤 삭제되기까지 Nomad가 기다리는 시간을 제어해요. 지연 안에 같은 이미지를 쓰는 태스크를 받으면 이미지를 재사용해요.

  • docker.volumes.enabled: 기본값은 false예요. 태스크가 컨테이너 안에 호스트 경로(volumes)를 바인드하고 볼륨 드라이버(volume_driver)를 사용하게 해줘요. 상대 경로 바인딩은 항상 허용되며 할당의 디렉토리를 기준으로 해석돼요.

  • docker.volumes.selinuxlabel: 운영자가 할당과 태스크 로컬 바인드-마운트에 SELinux 라벨을 컨테이너에 설정하게 해줘요. docker.volumes.enabled가 false로 설정돼 있어도 이 라벨은 컨테이너의 표준 바인드에 여전히 적용돼요.

  • docker.privileged.enabled 기본값은 false예요. true로 바꾸면 컨테이너가 privileged 모드를 사용할 수 있는데, 이는 컨테이너에 호스트 디바이스에 대한 전체 접근 권한을 부여해요. 이 작업이 동작하려면 Docker 데몬에도 비슷한 설정을 해야 해요.

  • docker.caps.allowlist: 허용되는 Linux 기능 목록이에요. 기본값은 "CHOWN,DAC_OVERRIDE,FSETID,FOWNER,MKNOD,NET_RAW,SETGID,SETUID,SETFCAP, SETPCAP,NET_BIND_SERVICE,SYS_CHROOT,KILL,AUDIT_WRITE"로, 여기에 정의된 docker 기본 허용 기능 목록이에요. 운영자가 cap_addcap_drop 옵션을 쓰는 태스크가 얻을 수 있는 기능을 제어하게 해줘요. 모든 기능을 허용 목록에 넣는 단축값 "ALL"을 지원해요.

  • docker.cleanup.container: 기본값은 true예요. 이 옵션은 태스크가 종료될 때 Nomad가 컨테이너를 제거하지 않도록 끌 수 있어요. 이름 충돌이 있으면 Nomad가 여전히 죽은 컨테이너를 제거할 수 있어요.

  • docker.nvidia_runtime: 기본값은 nvidia예요. 이 옵션을 통해 운영자가 컨테이너에 Nvidia GPU를 노출하기 위해 사용할 런타임을 선택할 수 있어요.

참고: -dev 플래그로 테스트하거나 사용할 때 DOCKER_HOST, DOCKER_TLS_VERIFY, DOCKER_CERT_PATH로 Nomad 동작을 사용자 정의할 수 있어요. docker.endpoint가 설정되면 Nomad는 오직 구성 파일에서만 클라이언트 구성을 읽어요.

예시는 아래와 같아요:

client {
  options {
    "docker.cleanup.image" = "false"
  }
}

클라이언트 속성 (Client Attributes)

docker 드라이버는 다음 클라이언트 속성을 설정해요:

  • driver.docker - 드라이버가 사용 가능함을 나타내는 "1"로 설정돼요.

  • driver.docker.bridge_ip - Docker 브리지 네트워크가 있다면 그 IP예요.

  • driver.docker.version - docker 서버의 버전으로 설정돼요.

잡 파일에서 이 속성들을 어떻게 쓰는지 예시를 보여드릴게요:

job "docs" {
  # Require docker version higher than 1.2.
  constraint {
    attribute = "${attr.driver.docker.version}"
    operator  = ">"
    version   = "1.2"
  }
}

리소스 격리 (Resource Isolation)

CPU

Nomad는 CPU 공유(CPU shares)를 기준으로 컨테이너의 CPU를 제한해요. CPU 공유는 컨테이너가 CPU 한도를 넘어 버스트할 수 있게 해요. CPU 한도는 리소스에 경합이 있을 때만 적용돼요. 호스트에 부하가 걸리면 프로세스는 보유한 공유 수에 따라 QoS를 안정화하도록 스로틀될 수 있어요. 프로세스가 사용할 수 있는 CPU 공유 수는 NOMAD_CPU_LIMIT을 읽어 확인할 수 있어요. 공유 1000개는 대략 1 GHz와 같아요.

Nomad에서 워크로드를 부하 테스트할 때는 CPU 공유의 의미를 염두에 두세요.

resources cores가 설정되면 태스크는 사용할 수 있는 격리된 예약 코어 집합을 받아요. 태스크가 실행될 수 있는 총 코어 집합은 전용(private) 집합과 예약되지 않은 코어의 가변 집합을 합친 것이에요. 전용 CPU 코어 집합은 NOMAD_CPU_CORES을 읽어 프로세스가 확인할 수 있어요.

메모리

Nomad는 총 가상 메모리를 기준으로 컨테이너의 메모리 사용을 제한해요. 즉 Nomad가 스케줄한 컨테이너는 swap을 사용할 수 없어요. 이는 스와피한 프로세스가 같은 호스트의 다른 워크로드 성능을 떨어뜨리지 않도록 하기 위해서예요.

메모리는 탄력적인(elastic) 리소스가 아니므로, 컨테이너가 할당된 메모리 양을 넘지 않도록 해야 해요. 넘으면 malloc을 시도할 때 종료되거나 크래시돼요. 프로세스는 NOMAD_MEMORY_LIMIT을 읽어 메모리 한도를 확인할 수 있지만, 자체 메모리 사용량은 스스로 추적해야 해요. 메모리 한도는 메가바이트로 표현되므로 1024 = 1 GB예요.

IO

Nomad의 Docker 통합은 현재 네트워크나 파일시스템 IO에 QoS를 제공하지 않아요. 이 기능은 이후 릴리스에서 추가될 예정이에요.

보안

Docker는 cgroups와 namespaces를 통해 리소스 격리를 제공해요. 컨테이너는 기본적으로 각자 고유한 가상 파일시스템을 가져요. 보안이나 다른 이유로 프로세스 간 더 높은 수준의 격리가 필요하다면 QEMU 같은 완전 가상화를 사용하는 걸 권장해요.

주의 사항 (Caveats)

떠다니는 컨테이너 (Dangling Containers)

Nomad에는 떠다니는 Docker 컨테이너, 즉 Nomad가 시작했지만 관리·추적하지 않는 컨테이너를 위한 탐지기(detector)와 수거기(reaper)가 있어요. 드물지만 잠재적으로 오래된 버전으로 예상치 못하게 실행 중인 서비스로 이어질 수 있어요.

Nomad가 태스크를 시작할 때 Docker 데몬을 사용할 수 없게 되면, Docker가 컨테이너를 성공적으로 시작했지만 API 호출에서 500 오류 코드를 반환할 수 있어요. 이런 경우 Nomad는 재시도하고 결국 그런 컨테이너를 죽이려고 해요. 하지만 Docker 엔진이 계속 비정상이면 이후 재시도와 중지 시도도 실패할 수 있고, 시작된 컨테이너는 Nomad가 더 이상 관리하지 않는 떠다니는 컨테이너가 돼요.

새로 추가된 수거기는 이런 컨테이너를 주기적으로 스캔해요. com.hashicorp.nomad.allocation_id 라벨이 있는 컨테이너만 대상으로 하거나, Nomad의 이름 지정·바인드-마운트 규칙(/alloc, /secrets, local)과 일치하는 컨테이너만 대상으로 해요. Nomad 컨테이너 패턴과 일치하지 않는 컨테이너는 건드리지 않아요.

운영자는 수거기를 dry-run 모드로 실행할 수 있는데, 이 경우 떠다니는 컨테이너 id를 죽이지 않고 로그로만 남겨요. 또는 gc.dangling_containers 구성 블록을 설정해 끌 수 있어요.

Windows용 Docker

Windows용 Docker는 Windows 컨테이너만 실행할 수 있어요. Windows용 Docker는 비교적 새롭고 빠르게 진화하고 있으므로 GitHub의 관련 이슈 목록을 확인해 보는 게 좋을 수 있어요.

다음 단계

잡에서 Docker 태스크 드라이버 사용하기.

더 알아보기