使用 Docker 容器啟動應用程式

安全警告

重要 啟用此功能並在您的叢集中執行 Docker 容器會產生安全影響。由於 Docker 與許多強大的核心功能整合,因此在啟用此功能之前,管理員必須了解 Docker 安全性

概觀

Docker 結合了易於使用的 Linux 容器介面,以及這些容器易於建構的映像檔。簡而言之,Docker 使用者可以將應用程式與其偏好的執行環境綑綁在一起,並在目標機器上執行。有關 Docker 的更多資訊,請參閱其 文件

Linux 容器執行器 (LCE) 允許 YARN NodeManager 啟動 YARN 容器,直接在主機上或 Docker 容器內執行。請求資源的應用程式可以為每個容器指定執行方式。LCE 也提供增強的安全性,且在部署安全叢集時需要使用。當 LCE 啟動 YARN 容器在 Docker 容器中執行時,應用程式可以指定要使用的 Docker 映像檔。

Docker 容器提供自訂執行環境,應用程式的程式碼在其中執行,與 NodeManager 和其他應用程式的執行環境隔離。這些容器可以包含應用程式需要的特殊函式庫,且它們可以有不同版本的原生工具和函式庫,包括 Perl、Python 和 Java。Docker 容器甚至可以執行與 NodeManager 上執行的不同的 Linux 版本。

Docker for YARN 提供一致性(所有 YARN 容器都將擁有相同的軟體環境)和隔離(不干擾物理機器上安裝的任何內容)。

叢集組態

LCE 要求 container-executor 二進位檔由 root:hadoop 擁有,並具有 6050 權限。為了啟動 Docker 容器,Docker 守護程式必須在所有將啟動 Docker 容器的 NodeManager 主機上執行。Docker 用戶端也必須安裝在所有將啟動 Docker 容器且能夠啟動 Docker 容器的 NodeManager 主機上。

為了防止在啟動作業時逾時,應用程式要使用的任何大型 Docker 映像檔都應該已經載入 NodeManager 主機上 Docker 守護程式的快取中。載入映像檔的簡單方法是發出 Docker pull 要求。例如

    sudo docker pull library/openjdk:8

應在 yarn-site.xml 中設定下列屬性

<configuration>
  <property>
    <name>yarn.nodemanager.container-executor.class</name>
    <value>org.apache.hadoop.yarn.server.nodemanager.LinuxContainerExecutor</value>
    <description>
      This is the container executor setting that ensures that all applications
      are started with the LinuxContainerExecutor.
    </description>
  </property>

  <property>
    <name>yarn.nodemanager.linux-container-executor.group</name>
    <value>hadoop</value>
    <description>
      The POSIX group of the NodeManager. It should match the setting in
      "container-executor.cfg". This configuration is required for validating
      the secure access of the container-executor binary.
    </description>
  </property>

  <property>
    <name>yarn.nodemanager.linux-container-executor.nonsecure-mode.limit-users</name>
    <value>false</value>
    <description>
      Whether all applications should be run as the NodeManager process' owner.
      When false, applications are launched instead as the application owner.
    </description>
  </property>

  <property>
    <name>yarn.nodemanager.runtime.linux.allowed-runtimes</name>
    <value>default,docker</value>
    <description>
      Comma separated list of runtimes that are allowed when using
      LinuxContainerExecutor. The allowed values are default, docker, and
      javasandbox.
    </description>
  </property>

  <property>
    <name>yarn.nodemanager.runtime.linux.type</name>
    <value></value>
    <description>
      Optional. Sets the default container runtime to use.
    </description>
  </property>

  <property>
    <name>yarn.nodemanager.runtime.linux.docker.image-name</name>
    <value></value>
    <description>
      Optional. Default docker image to be used when the docker runtime is
      selected.
    </description>
  </property>

  <property>
    <name>yarn.nodemanager.runtime.linux.docker.image-update</name>
    <value>false</value>
    <description>
      Optional. Default option to decide whether to pull the latest image
      or not.
    </description>
  </property>

  <property>
    <name>yarn.nodemanager.runtime.linux.docker.allowed-container-networks</name>
    <value>host,none,bridge</value>
    <description>
      Optional. A comma-separated set of networks allowed when launching
      containers. Valid values are determined by Docker networks available from
      `docker network ls`
    </description>
  </property>

  <property>
    <name>yarn.nodemanager.runtime.linux.docker.default-container-network</name>
    <value>host</value>
    <description>
      The network used when launching Docker containers when no
      network is specified in the request. This network must be one of the
      (configurable) set of allowed container networks.
    </description>
  </property>

  <property>
    <name>yarn.nodemanager.runtime.linux.docker.host-pid-namespace.allowed</name>
    <value>false</value>
    <description>
      Optional. Whether containers are allowed to use the host PID namespace.
    </description>
  </property>

  <property>
    <name>yarn.nodemanager.runtime.linux.docker.privileged-containers.allowed</name>
    <value>false</value>
    <description>
      Optional. Whether applications are allowed to run in privileged
      containers. Privileged containers are granted the complete set of
      capabilities and are not subject to the limitations imposed by the device
      cgroup controller. In other words, privileged containers can do almost
      everything that the host can do. Use with extreme care.
    </description>
  </property>

  <property>
    <name>yarn.nodemanager.runtime.linux.docker.delayed-removal.allowed</name>
    <value>false</value>
    <description>
      Optional. Whether or not users are allowed to request that Docker
      containers honor the debug deletion delay. This is useful for
      troubleshooting Docker container related launch failures.
    </description>
  </property>

  <property>
    <name>yarn.nodemanager.runtime.linux.docker.stop.grace-period</name>
    <value>10</value>
    <description>
      Optional. A configurable value to pass to the Docker Stop command. This
      value defines the number of seconds between the docker stop command sending
      a SIGTERM and a SIGKILL.
    </description>
  </property>

  <property>
    <name>yarn.nodemanager.runtime.linux.docker.privileged-containers.acl</name>
    <value></value>
    <description>
      Optional. A comma-separated list of users who are allowed to request
      privileged containers if privileged containers are allowed.
    </description>
  </property>

  <property>
    <name>yarn.nodemanager.runtime.linux.docker.capabilities</name>
    <value>CHOWN,DAC_OVERRIDE,FSETID,FOWNER,MKNOD,NET_RAW,SETGID,SETUID,SETFCAP,SETPCAP,NET_BIND_SERVICE,SYS_CHROOT,KILL,AUDIT_WRITE</value>
    <description>
      Optional. This configuration setting determines the capabilities
      assigned to docker containers when they are launched. While these may not
      be case-sensitive from a docker perspective, it is best to keep these
      uppercase. To run without any capabilites, set this value to
      "none" or "NONE"
    </description>
  </property>

  <property>
    <name>yarn.nodemanager.runtime.linux.docker.enable-userremapping.allowed</name>
    <value>true</value>
    <description>
      Optional. Whether docker containers are run with the UID and GID of the
      calling user.
    </description>
  </property>

  <property>
    <name>yarn.nodemanager.runtime.linux.docker.userremapping-uid-threshold</name>
    <value>1</value>
    <description>
      Optional. The minimum acceptable UID for a remapped user. Users with UIDs
      lower than this value will not be allowed to launch containers when user
      remapping is enabled.
    </description>
  </property>

  <property>
    <name>yarn.nodemanager.runtime.linux.docker.userremapping-gid-threshold</name>
    <value>1</value>
    <description>
      Optional. The minimum acceptable GID for a remapped user. Users belonging
      to any group with a GID lower than this value will not be allowed to
      launch containers when user remapping is enabled.
    </description>
  </property>

</configuration>

此外,container-executor.cfg 檔案必須存在,且包含容器執行器的設定。該檔案必須由 root 擁有,且權限為 0400。該檔案的格式是標準 Java 屬性檔格式,例如

`key=value`

啟用 Docker 支援需要下列屬性

組態名稱 說明
yarn.nodemanager.linux-container-executor.group NodeManager 的 Unix 群組。應與 yarn-site.xml 檔案中的 yarn.nodemanager.linux-container-executor.group 相符。

container-executor.cfg 必須包含一個區段,以決定允許容器使用的功能。它包含下列屬性

組態名稱 說明
module.enabled 必須為「true」或「false」,分別用於啟用或停用啟動 Docker 容器。預設值為 0。
docker.binary 用於啟動 Docker 容器的二進位檔。預設為 /usr/bin/docker。
docker.allowed.capabilities 允許容器新增的逗號分隔功能。預設不允許新增任何功能。
docker.allowed.devices 允許容器掛載的逗號分隔裝置。預設不允許新增任何裝置。
docker.allowed.networks 允許容器使用的逗號分隔網路。如果在啟動容器時未指定網路,將使用預設的 Docker 網路。
docker.allowed.ro-mounts 允許容器以唯讀模式掛載的逗號分隔目錄。預設不允許掛載任何目錄。
docker.allowed.rw-mounts 允許容器以讀寫模式掛載的逗號分隔目錄。預設不允許掛載任何目錄。
docker.allowed.volume-drivers 允許使用的逗號分隔磁碟區驅動程式清單。預設不允許任何磁碟區驅動程式。
docker.host-pid-namespace.enabled 設為「true」或「false」,以啟用或停用使用主機的 PID 名稱空間。預設值為「false」。
docker.privileged-containers.enabled 設為「true」或「false」,以啟用或停用啟動特權容器。預設值為「false」。
docker.privileged-containers.registries 用於執行特權 Docker 容器的特權 Docker 登錄檔的逗號分隔清單。預設未定義任何登錄檔。
docker.trusted.registries 用於執行受信任特權 Docker 容器的受信任 Docker 登錄檔的逗號分隔清單。預設未定義任何登錄檔。
docker.inspect.max.retries 檢查 Docker 容器準備就緒的整數值。每次檢查設定為延遲 3 秒。預設值 10 會在 Docker 容器標示為容器失敗之前等待 30 秒,直到它準備就緒。
docker.no-new-privileges.enabled 啟用/停用 docker run 的 no-new-privileges 旗標。設為「true」以啟用,預設為停用。
docker.allowed.runtimes 允許容器使用的逗號分隔執行時期。預設不允許新增任何執行時期。
docker.service-mode.enabled 設定為「true」或「false」以啟用或停用 Docker 容器服務模式。預設值為「false」。

請注意,如果您希望執行需要存取 YARN 本機目錄的 Docker 容器,您必須將它們新增至 docker.allowed.rw-mounts 清單。

此外,不允許容器以讀取寫入模式掛載 container-executor.cfg 目錄的任何父目錄。

下列屬性為選用

組態名稱 說明
min.user.id 允許啟動應用程式的最小 UID。預設值為沒有最小值
banned.users 不應允許啟動應用程式的使用者名稱清單(以逗號分隔)。預設設定為:yarn、mapred、hdfs 和 bin。
allowed.system.users 即使使用者的 UID 低於設定的最小值,也應允許啟動應用程式的使用者名稱清單(以逗號分隔)。如果使用者出現在 allowed.system.users 和 banned.users 中,使用者將被視為已禁止。
feature.tc.enabled 必須為「true」或「false」。「false」表示停用流量控制指令。「true」表示允許流量控制指令。
feature.yarn.sysfs.enabled 必須為「true」或「false」。有關詳細資訊,請參閱 YARN sysfs 支援。預設設定為停用。

允許啟動 Docker 容器的 container-executor.cfg 的一部分如下

yarn.nodemanager.linux-container-executor.group=yarn
[docker]
  module.enabled=true
  docker.privileged-containers.enabled=true
  docker.privileged-containers.registries=local
  docker.trusted.registries=centos
  docker.allowed.capabilities=SYS_CHROOT,MKNOD,SETFCAP,SETPCAP,FSETID,CHOWN,AUDIT_WRITE,SETGID,NET_RAW,FOWNER,SETUID,DAC_OVERRIDE,KILL,NET_BIND_SERVICE
  docker.allowed.networks=bridge,host,none
  docker.allowed.ro-mounts=/sys/fs/cgroup
  docker.allowed.rw-mounts=/var/hadoop/yarn/local-dir,/var/hadoop/yarn/log-dir

Docker 映像需求

為了與 YARN 搭配使用,Docker 映像有兩個需求。

首先,Docker 容器將明確地以應用程式擁有者作為容器使用者啟動。如果應用程式擁有者不是 Docker 映像中的有效使用者,應用程式將會失敗。容器使用者由使用者的 UID 指定。如果使用者的 UID 在 NodeManager 主機和 Docker 映像之間不同,容器可能會以錯誤的使用者身分啟動,或可能無法啟動,因為 UID 不存在。有關更多詳細資訊,請參閱Docker 容器中的使用者管理區段。

其次,Docker 映像必須具備應用程式執行所需的任何內容。在 Hadoop(MapReduce 或 Spark)的情況下,Docker 映像必須包含 JRE 和 Hadoop 函式庫,並設定必要的環境變數:JAVA_HOME、HADOOP_COMMON_PATH、HADOOP_HDFS_HOME、HADOOP_MAPRED_HOME、HADOOP_YARN_HOME 和 HADOOP_CONF_DIR。請注意,Docker 映像中提供的 Java 和 Hadoop 元件版本必須與群集上安裝的版本以及用於同一個工作其他工作所使用的任何其他 Docker 映像中的版本相容。否則,在 Docker 容器中啟動的 Hadoop 元件可能無法與外部 Hadoop 元件通訊。

如果 Docker 映像設定了指令,行為將取決於 YARN_CONTAINER_RUNTIME_DOCKER_RUN_OVERRIDE_DISABLE 是否設定為 true。如果是,當 LCE 使用 YARN 的容器啟動指令啟動映像時,將會覆寫指令。

如果 Docker 映像設定了進入點,且 YARN_CONTAINER_RUNTIME_DOCKER_RUN_OVERRIDE_DISABLE 設定為 true,launch_command 將傳遞給 ENTRYPOINT 程式作為 Docker 中的 CMD 參數。launch_command 的格式如下:param1,param2,這會轉換為 Docker 中的 CMD [“param1”、“param2”]。

如果應用程式要求 Docker 映像,而該映像尚未由主機上的 Docker 程式載入,則 Docker 程式會隱含執行 Docker pull 指令。MapReduce 和 Spark 都假設需要超過 10 分鐘才能回報進度的任務已停滯,因此指定大型 Docker 映像可能會導致應用程式失敗。

CGroups 組態需求

Docker 外掛程式利用 cgroups 來限制個別容器的資源使用。由於啟動的容器屬於 YARN,因此指令列選項 --cgroup-parent 用於定義適當的控制群組。

Docker 支援兩種不同的 cgroups 驅動程式:cgroupfssystemd。請注意,僅支援 cgroupfs - 嘗試使用 systemd 啟動 Docker 容器會產生以下類似的錯誤訊息

Container id: container_1561638268473_0006_01_000002
Exit code: 7
Exception message: Launch container failed
Shell error output: /usr/bin/docker-current: Error response from daemon: cgroup-parent for systemd cgroup should be a valid slice named as "xxx.slice".
See '/usr/bin/docker-current run --help'.
Shell output: main : command provided 4

這表示您必須在使用 systemd 驅動程式的每個主機上重新設定 Docker 程式。

根據 Hadoop 執行的作業系統,重新設定可能需要不同的步驟。不過,如果為 cgroups 驅動程式選取 systemd,則系統上可能可以使用 systemctl 指令。

檢查 Docker 程式的 ExecStart 屬性

~$ systemctl show --no-pager --property=ExecStart docker.service
ExecStart={ path=/usr/bin/dockerd-current ; argv[]=/usr/bin/dockerd-current --add-runtime
docker-runc=/usr/libexec/docker/docker-runc-current --default-runtime=docker-runc --exec-opt native.cgroupdriver=systemd
--userland-proxy-path=/usr/libexec/docker/docker-proxy-current
--init-path=/usr/libexec/docker/docker-init-current
--seccomp-profile=/etc/docker/seccomp.json
$OPTIONS $DOCKER_STORAGE_OPTIONS $DOCKER_NETWORK_OPTIONS $ADD_REGISTRY $BLOCK_REGISTRY $INSECURE_REGISTRY $REGISTRIES ;
ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }

此範例顯示 native.cgroupdriversystemd。您必須在 daemon 的 unit 檔案中修改它。

~$ sudo systemctl edit --full docker.service

這會開啟整個組態以進行編輯。只要將 systemd 字串替換為 cgroupfs 即可。儲存變更並重新啟動 systemd 和 Docker 程式

~$ sudo systemctl daemon-reload
~$ sudo systemctl restart docker.service

應用程式提交

在嘗試啟動 Docker 容器之前,請確定 LCE 組態適用於要求一般 YARN 容器的應用程式。如果在啟用 LCE 之後,一個或多個 NodeManager 無法啟動,最有可能的原因是容器執行檔的所有權和/或權限不正確。檢查記錄檔以確認。

若要以 Docker 容器執行應用程式,請在應用程式的環境中設定下列環境變數

環境變數名稱 說明
YARN_CONTAINER_RUNTIME_TYPE 決定應用程式是否會在 Docker 容器中啟動。如果值為「docker」,應用程式會在 Docker 容器中啟動。否則,將使用一般程序樹容器。
YARN_CONTAINER_RUNTIME_DOCKER_IMAGE 命名將用於啟動 Docker 容器的映像。可以使用任何可以傳遞給 Docker 客戶端執行指令的映像名稱。映像名稱可以包含儲存庫字首。
YARN_CONTAINER_RUNTIME_DOCKER_RUN_OVERRIDE_DISABLE 控制是否覆寫 Docker 容器的預設指令。設定為 true 時,Docker 容器的指令會是「bash path_to_launch_script」。未設定或設定為 false 時,會使用 Docker 容器的預設指令。
YARN_CONTAINER_RUNTIME_DOCKER_CONTAINER_NETWORK 設定 Docker 容器要使用的網路類型。它必須是 yarn.nodemanager.runtime.linux.docker.allowed-container-networks 屬性所決定的有效值。
YARN_CONTAINER_RUNTIME_DOCKER_PORTS_MAPPING 允許使用者為橋接網路 Docker 容器指定埠映射。環境變數的值應該是埠映射的逗號分隔清單。它與 Docker run 指令的「-p」選項相同。如果值為空,會新增「-P」。
YARN_CONTAINER_RUNTIME_DOCKER_CONTAINER_PID_NAMESPACE 控制 Docker 容器要使用的 PID 名稱空間。預設情況下,每個 Docker 容器都有自己的 PID 名稱空間。若要共用主機的名稱空間,必須將 yarn.nodemanager.runtime.linux.docker.host-pid-namespace.allowed 屬性設定為 true。如果允許使用主機 PID 名稱空間,且此環境變數設定為 host,Docker 容器會共用主機的 PID 名稱空間。不允許使用其他值。
YARN_CONTAINER_RUNTIME_DOCKER_RUN_PRIVILEGED_CONTAINER 控制 Docker 容器是否為特權容器。若要使用特權容器,必須將 yarn.nodemanager.runtime.linux.docker.privileged-containers.allowed 屬性設定為 true,且應用程式擁有者必須出現在 yarn.nodemanager.runtime.linux.docker.privileged-containers.acl 屬性的值中。如果此環境變數設定為 true,會在允許的情況下使用特權 Docker 容器。不允許使用其他值,因此環境變數應該保持未設定,而不是設定為 false。
YARN_CONTAINER_RUNTIME_DOCKER_MOUNTS 將額外的磁碟區掛載點新增到 Docker 容器。環境變數的值應該是掛載點的逗號分隔清單。所有這些掛載點都必須以 source:dest[:mode] 的格式提供,且模式必須是「ro」(唯讀)或「rw」(讀寫),以指定所要求的存取類型。如果未指定,會假設為讀寫。模式可以包含繫結傳播選項。在這種情況下,模式應為 [選項]rw+[選項]ro+[選項] 的格式。有效的繫結傳播選項包括 shared、rshared、slave、rslave、private 和 rprivate。請求的掛載點會根據 container-executor.cfg 中設定的 docker.allowed.ro-mountsdocker.allowed.rw-mounts 值,由 container-executor 驗證。
YARN_CONTAINER_RUNTIME_DOCKER_TMPFS_MOUNTS 將額外的 tmpfs 掛載點新增到 Docker 容器。環境變數的值應該是容器內絕對掛載點的逗號分隔清單。
YARN_CONTAINER_RUNTIME_DOCKER_DELAYED_REMOVAL 允許使用者要求逐個容器延遲刪除 Docker 容器。如果為 true,Docker 容器將不會被移除,直到 yarn.nodemanager.delete.debug-delay-sec 所定義的持續時間經過。管理員可以透過 yarn-site 屬性 yarn.nodemanager.runtime.linux.docker.delayed-removal.allowed 來停用此功能。此功能預設為停用。當此功能被停用或設為 false 時,容器將會在它結束時立即被移除。
YARN_CONTAINER_RUNTIME_YARN_SYSFS_ENABLE 啟用將容器工作目錄 sysfs 子目錄掛載到 Docker 容器 /hadoop/yarn/sysfs。這對於將叢集資訊填入容器中很有用。
YARN_CONTAINER_RUNTIME_DOCKER_SERVICE_MODE 啟用服務模式,它會執行由映像定義的 docker 容器,但不會設定使用者 (–user 和 –group-add)。

前兩個是必需的。其餘的可以視需要設定。雖然透過環境變數控制容器類型有點不理想,但它允許不了解 YARN 的 Docker 支援的應用程式(例如 MapReduce 和 Spark)透過支援設定應用程式環境來利用它。

一旦應用程式提交在 Docker 容器中啟動,應用程式將會像任何其他 YARN 應用程式一樣運作。記錄將會被彙總並儲存在相關的歷程伺服器中。應用程式生命週期將與非 Docker 應用程式相同。

使用 Docker Bind 掛載的磁碟區

警告 啟用此功能時應小心。不建議啟用對目錄的存取,例如但不限於 /、/etc、/run 或 /home,否則可能會導致容器對主機產生負面影響或洩漏敏感資訊。警告

Docker 容器中通常需要主機的檔案和目錄,Docker 會透過磁碟區提供。範例包括本地化資源、Apache Hadoop 二進位檔和 socket。為了滿足此需求,YARN-6623 新增了管理員設定主機目錄白名單的功能,允許將其掛載為容器中的磁碟區。YARN-5534 新增了使用者提供掛載清單的功能,如果管理白名單允許,這些掛載將會掛載到容器中。

為了使用此功能,必須設定下列事項。

  • 管理員必須在 container-executor.cfg 中定義磁碟區白名單,方法是將 docker.allowed.ro-mountsdocker.allowed.rw-mounts 設定為允許掛載的父目錄清單。
  • 應用程式提交者在應用程式提交時使用 YARN_CONTAINER_RUNTIME_DOCKER_MOUNTS 環境變數要求所需的磁碟區。

管理員提供的白名單定義為允許掛載到容器中的目錄的逗號分隔清單。使用者提供的來源目錄必須與指定的目錄相符或為其子目錄。

使用者提供的掛載清單定義為逗號分隔清單,格式為 來源:目的地來源:目的地:模式。來源是主機上的檔案或目錄。目的地是容器中將會繫結掛載來源的路徑。模式定義使用者對掛載預期的模式,可以是 ro(唯讀)或 rw(讀寫)。如果未指定,則假設為 rw。模式還可以包含繫結傳播選項(shared、rshared、slave、rslave、private 或 rprivate)。在這種情況下,模式應為 選項、rw+選項 或 ro+選項 的格式。

以下範例說明如何使用此功能將通常需要的 /sys/fs/cgroup 目錄掛載到在 YARN 上執行的容器中。

管理員在 container-executor.cfg 中將 docker.allowed.ro-mounts 設定為 “/sys/fs/cgroup”。現在,應用程式可以要求將 “/sys/fs/cgroup” 從主機以唯讀模式掛載到容器中。

在應用程式提交時,可以設定 YARN_CONTAINER_RUNTIME_DOCKER_MOUNTS 環境變數來要求此掛載。在此範例中,環境變數會設定為 “/sys/fs/cgroup:/sys/fs/cgroup:ro”。目的地路徑沒有限制,“/sys/fs/cgroup:/cgroup:ro” 也會在範例管理員白名單中有效。

Docker 容器中的使用者管理

YARN 的 Docker 容器支援使用 NodeManager 主機上定義的使用者 uid:gid 識別碼啟動容器程序。NodeManager 主機和容器之間的使用者和群組名稱不符可能會導致權限問題、容器啟動失敗,甚至安全漏洞。集中管理主機和容器的使用者和群組可以大幅降低這些風險。在 YARN 上執行容器化應用程式時,有必要了解哪個 uid:gid 配對將用於啟動容器的程序。

以下是一個 uid:gid 配對的範例。預設情況下,在非安全模式下,YARN 會以使用者 nobody 身分啟動程序(請參閱 使用 CGroups 與 YARN 底部的表格,了解如何在非安全模式下判斷執行身分)。在 CentOS 系統上,nobody 使用者的 uid 是 99nobody 群組是 99。因此,YARN 會使用 --user 99:99 呼叫 docker run。如果 nobody 使用者在容器中沒有 uid 99,啟動可能會失敗或產生意外結果。

此規則的一個例外是使用特權 Docker 容器。特權容器在啟動容器時不會設定 uid:gid 配對,並且會遵循 Dockerfile 中的 USER 或 GROUP 項目。這允許以任何使用者身分執行特權容器,這會產生安全性影響。在啟用特權 Docker 容器之前,請了解這些影響。

有很多方式可以處理使用者和群組管理。Docker 預設會在容器內對照 /etc/passwd(和 /etc/shadow)驗證使用者。使用 Docker 映像中提供的預設 /etc/passwd 不太可能包含適當的使用者條目,而且會導致啟動失敗。強烈建議集中管理使用者和群組。以下是幾種使用者和群組管理方法的說明。

靜態使用者管理

管理使用者和群組最基本的方法是在 Docker 映像中修改使用者和群組。此方法僅適用於非安全模式,其中所有容器程序都將以單一已知使用者(例如 nobody)啟動。在這種情況下,唯一的需求是 nobody 使用者和群組的 uid:gid 配對必須與主機和容器相符。在 CentOS 的系統上,這表示容器中的 nobody 使用者需要 UID 99,而容器中的 nobody 群組需要 GID 99

變更 UID 和 GID 的一種方法是利用 usermodgroupmod。下列設定 nobody 使用者/群組的正確 UID 和 GID。

usermod -u 99 nobody
groupmod -g 99 nobody

由於無法新增使用者,因此不建議在測試以外使用此方法。

繫結掛載

當組織已經自動化在每個系統上建立本機使用者的流程時,繫結掛載 /etc/passwd 和 /etc/group 到容器中可能是適當的替代方案,而不是直接修改容器映像。若要啟用繫結掛載 /etc/passwd 和 /etc/group 的功能,請更新 container-executor.cfg 中的 docker.allowed.ro-mounts 以包含這些路徑。提交應用程式時,YARN_CONTAINER_RUNTIME_DOCKER_MOUNTS 需要包含 /etc/passwd:/etc/passwd:ro/etc/group:/etc/group:ro

此繫結掛載方法有幾個需要考量的挑戰。

  1. 映像中定義的任何使用者和群組都將被主機的使用者和群組覆寫
  2. 容器啟動後無法新增使用者和群組,因為 /etc/passwd 和 /etc/group 在容器中是不可變的。請勿將這些檔案掛載為可讀寫,因為這可能會使主機無法操作。

由於無法修改正在執行的容器,因此不建議在測試以外使用此方法。

SSSD

允許集中管理使用者和群組的另一種方法是 SSSD。系統安全服務守護程式 (SSSD) 提供對不同身分和驗證提供者的存取,例如 LDAP 或 Active Directory。

Linux 驗證的傳統架構如下

application -> libpam -> pam_authenticate -> pam_unix.so -> /etc/passwd

如果我們使用 SSSD 來查詢使用者,則會變成

application -> libpam -> pam_authenticate -> pam_sss.so -> SSSD -> pam_unix.so -> /etc/passwd

我們可以將 SSSD 通訊的 UNIX socket 繫結掛載到容器中。這將允許 SSSD 伺服器端程式庫對執行於主機上的 SSSD 進行驗證。因此,使用者資訊不需要存在於 docker 映像的 /etc/passwd 中,而是由 SSSD 提供服務。

主機和容器的分步設定

  1. 主機設定
  • 安裝套件
    # yum -y install sssd-common sssd-proxy
    
  • 為容器建立 PAM 服務。
    # cat /etc/pam.d/sss_proxy
    auth required pam_unix.so
    account required pam_unix.so
    password required pam_unix.so
    session required pam_unix.so
    
  • 建立 SSSD 設定檔,/etc/sssd/sssd.conf 請注意,權限必須為 0600,且檔案必須由 root:root 擁有。
    # cat /etc/sssd/sssd/conf
    [sssd]
    services = nss,pam
    config_file_version = 2
    domains = proxy
    [nss]
    [pam]
    [domain/proxy]
    id_provider = proxy
    proxy_lib_name = files
    proxy_pam_target = sss_proxy
    
  • 啟動 sssd
    # systemctl start sssd
    
  • 驗證使用者可以使用 sssd 擷取
    # getent passwd -s sss localuser
    
  1. 容器設定

由於 SSSD UNIX socket 位於其中,因此將 /var/lib/sss/pipes 目錄從主機繫結掛載到容器中非常重要。

-v /var/lib/sss/pipes:/var/lib/sss/pipes:rw
  1. 容器設定

以下所有步驟都應在容器本身執行。

  • 僅安裝 sss 伺服器端程式庫

    # yum -y install sssd-client
    
  • 確保 sss 已設定為 passwd 和 group 資料庫

    /etc/nsswitch.conf
    
  • 設定應用程式使用的 PAM 服務,以呼叫 SSSD

    # cat /etc/pam.d/system-auth
    #%PAM-1.0
    # This file is auto-generated.
    # User changes will be destroyed the next time authconfig is run.
    auth        required      pam_env.so
    auth        sufficient    pam_unix.so try_first_pass nullok
    auth        sufficient    pam_sss.so forward_pass
    auth        required      pam_deny.so
    
    account     required      pam_unix.so
    account     [default=bad success=ok user_unknown=ignore] pam_sss.so
    account     required      pam_permit.so
    
    password    requisite     pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
    password    sufficient    pam_unix.so try_first_pass use_authtok nullok sha512 shadow
    password    sufficient    pam_sss.so use_authtok
    password    required      pam_deny.so
    
    session     optional      pam_keyinit.so revoke
    session     required      pam_limits.so
    -session     optional      pam_systemd.so
    session     [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
    session     required      pam_unix.so
    session     optional      pam_sss.so
    
  • 儲存 docker 映像,並將 docker 映像用作應用程式的基礎映像。

  • 測試在 YARN 環境中啟動的 docker 映像。

    $ id
    uid=5000(localuser) gid=5000(localuser) groups=5000(localuser),1337(hadoop)
    

特權容器安全考量

特權 docker 容器可以與主機系統裝置互動。這可能會在沒有適當照護的情況下損害主機作業系統。為了降低允許特權容器在 Hadoop 群集上執行的風險,我們實作了一個受控程序,以沙盒化未經授權的特權 docker 映像。

預設行為不允許任何特權 docker 容器。特權 docker 僅允許使用啟用 ENTRYPOINT 的 docker 映像,且 docker.privileged-containers.enabled 設定為已啟用。Docker 映像可以在 docker 容器中以 root 權限執行,但會停用對主機層級裝置的存取。這允許開發人員和測試人員從網際網路執行 docker 映像,並有一些限制來防止損害主機作業系統。

當 docker 映像已由開發人員和測試人員認證為可信賴時。可信賴的映像可以提升至可信賴的 docker 註冊中心。系統管理員可以定義 docker.trusted.registries,並設定私人 docker 註冊中心伺服器來提升可信賴的映像。系統管理員可以選擇允許 Docker Hub 的官方 docker 映像成為可信賴的註冊中心的一部分。「library」是使用來信賴官方 docker 映像的名稱。Container-executor.cfg 範例

[docker]
  docker.privileged-containers.enabled=true
  docker.trusted.registries=library

也可以使用 docker.privileged-containers.registries 定義細緻的存取控制,以僅允許一部分 Docker 映像以特權容器執行。如果未定義 docker.privileged-containers.registries,YARN 將回退使用 docker.trusted.registries 作為特權 Docker 映像的存取控制。細緻存取控制範例

[docker]
  docker.privileged-containers.enabled=true
  docker.privileged-containers.registries=local/centos:latest
  docker.trusted.registries=library

在開發環境中,可以標記本地映像的儲存庫名稱字首以啟用信任。建議選擇儲存庫名稱是使用本地主機名稱和埠號,以防止意外從 Docker Hub 拉取 docker 映像或使用保留的 Docker Hub 關鍵字:「local」。如果映像不存在本地,Docker run 將在 Docker Hub 上尋找 docker 映像。在映像名稱中使用本地主機名稱和埠號可以防止意外從 docker hub 拉取正規映像。將映像標記為 localhost:5000 作為可信賴註冊中心的範例

docker tag centos:latest localhost:5000/centos:latest

假設您有一個 Ubuntu 基礎映像,在當地儲存庫中有一些變更,而且您想要使用它。下列範例標記 local_ubuntu 映像

docker tag local_ubuntu local/ubuntu:latest

接著,您必須將 local 新增到 docker.trusted.registries。可以使用 local/ubuntu 參照映像。

受信任的映像允許透過 NFS 閘道器掛載外部裝置,例如 HDFS,或主機層級 Hadoop 組態。如果系統管理員允許使用 docker.allow.rw-mounts 指令 寫入外部磁碟區,則特權 docker 容器可以完全控制預先定義磁碟區中的主機層級檔案。

對於 YARN 服務 HTTPD 範例,container-executor.cfg 必須定義受信任的 centos docker 註冊表,才能執行範例。

容器重新取得需求

重新啟動時,NodeManager 作為 NodeManager 復原程序的一部分,會透過檢查 /proc 檔案系統中容器的 PID 目錄是否存在,來驗證容器是否仍在執行。出於安全目的,作業系統管理員可能會為 /proc 檔案系統啟用 hidepid 掛載選項。如果啟用了 hidepid 選項,則必須透過設定 gid 掛載標記(類似於下方)將 yarn 使用者的主要群組加入白名單。如果未將 yarn 使用者的主要群組加入白名單,則容器重新取得會失敗,而且容器會在 NodeManager 重新啟動時終止。

proc     /proc     proc     nosuid,nodev,noexec,hidepid=2,gid=yarn     0 0

連線到 Docker 受信任註冊表

Docker 客戶端指令會從預設位置取得其組態,也就是 NodeManager 主機上的 $HOME/.docker/config.json。Docker 組態是儲存安全儲存庫憑證的地方,因此不建議使用此方法搭配安全 Docker 儲存庫使用 LCE。

YARN-5428 已新增對分散式 Shell 的支援,以安全地提供 Docker 客戶端組態。請參閱分散式 Shell 說明以了解用法。計畫支援其他架構。

作為解決方法,您可以在每個 NodeManager 主機上使用 Docker 登入指令手動將 Docker 惡魔登入安全儲存庫

  docker login [OPTIONS] [SERVER]

  Register or log in to a Docker registry server, if no server is specified
  "https://index.docker.io/v1/" is the default.

  -e, --email=""       Email
  -p, --password=""    Password
  -u, --username=""    Username

請注意,這種方法表示所有使用者都可以存取安全儲存庫。

Hadoop 透過 YARN 服務 API 與 Docker 受信任註冊表整合。Docker 註冊表可以使用 CSI 驅動程式將 Docker 映像儲存在 HDFS、S3 或外部儲存裝置上。

HDFS 上的 Docker 註冊表

NFS 閘道器提供將 HDFS 掛載為 NFS 掛載點的功能。Docker 註冊表可以組態為使用標準檔案系統 API 寫入 HDFS 掛載點。

在 hdfs-site.xml 中,組態 NFS 組態

    <property>
      <name>nfs.exports.allowed.hosts</name>
      <value>* rw</value>
    </property>

    <property>
      <name>nfs.file.dump.dir</name>
      <value>/tmp/.hdfs-nfs</value>
    </property>

    <property>
      <name>nfs.kerberos.principal</name>
      <value>nfs/_HOST@EXAMPLE.COM</value>
    </property>

    <property>
      <name>nfs.keytab.file</name>
      <value>/etc/security/keytabs/nfs.service.keytab</value>
    </property>

使用以下方式,以 hdfs 使用者身分在所有資料節點上執行 NFS 閘道器

$ $HADOOP_HOME/bin/hdfs --daemon start nfs3

在每個資料節點上,nfs 掛載點會使用以下方式公開至 /hdfs

# mount -t nfs -o vers=3,proto=tcp,nolock,noacl,sync $DN_IP:/ /hdfs

其中 DN_IP 是資料節點的 IP 位址。

Container-executor.cfg 已設定為允許來自 library 的受信任 Docker 映像。

[docker]
  docker.privileged-containers.enabled=true
  docker.trusted.registries=library,registry.docker-registry.registry.example.com:5000
  docker.allowed.rw-mounts=/tmp,/usr/local/hadoop/logs,/hdfs

Docker Registry 可使用 YARN 服務啟動:registry.json

{
  "name": "docker-registry",
  "version": "1.0",
  "kerberos_principal" : {
    "principal_name" : "registry/_HOST@EXAMPLE.COM",
    "keytab" : "file:///etc/security/keytabs/registry.service.keytab"
  },
  "components" :
  [
    {
      "name": "registry",
      "number_of_containers": 1,
      "artifact": {
        "id": "registry:latest",
        "type": "DOCKER"
      },
      "resource": {
        "cpus": 1,
        "memory": "256"
      },
      "run_privileged_container": true,
      "configuration": {
        "env": {
          "YARN_CONTAINER_RUNTIME_DOCKER_RUN_OVERRIDE_DISABLE":"true",
          "YARN_CONTAINER_RUNTIME_DOCKER_MOUNTS":"/hdfs/apps/docker/registry:/var/lib/registry"
        },
        "properties": {
          "docker.network": "host"
        }
      }
    }
  ]
}

YARN 服務會設定 Docker 掛載,從 /hdfs/apps/docker/registry 到 Docker 容器內的 /var/lib/registry。

yarn app -launch docker-registry /tmp/registry.json

Docker 受信任 Registry 已部署在 YARN 架構中,而存取 Registry 的 URL 遵循 Hadoop Registry DNS 格式

registry.docker-registry.$USER.$DOMAIN:5000

當 docker-registry 應用程式在 YARN 中達到 STABLE 狀態時,使用者可以透過將映像名稱加上 registry.docker-registry.registry.example.com:5000/ 前綴,將 Docker 映像推播或拉取至 Docker 受信任 Registry。

S3 上的 Docker Registry

Docker Registry 提供自己的 S3 驅動程式和 YAML 設定檔。YARN 服務設定檔可以產生 YAML 範本,並讓 Docker Registry 直接連線到 S3 儲存空間。此選項是將 Docker 受信任 Registry 部署在 AWS 上的最佳選擇。要將 Docker Registry 儲存空間驅動程式設定為 S3,需要掛載 /etc/docker/registry/config.yml 檔案(透過 YARN_CONTAINER_RUNTIME_DOCKER_MOUNTS),其中需要設定 S3 儲存空間桶以及相對應的存取金鑰和秘密金鑰。

範例 config.yml

version: 0.1
log:
    fields:
        service: registry
http:
    addr: :5000
storage:
    cache:
        blobdescriptor: inmemory
    s3:
        accesskey: #AWS_KEY#
        secretkey: #AWS_SECRET#
        region: #AWS_REGION#
        bucket: #AWS_BUCKET#
        encrypt: #ENCRYPT#
        secure:  #SECURE#
        chunksize: 5242880
        multipartcopychunksize: 33554432
        multipartcopymaxconcurrency: 100
        multipartcopythresholdsize: 33554432
        rootdirectory: #STORAGE_PATH#

Docker Registry 可使用 YARN 服務啟動:registry.json

{
  "name": "docker-registry",
  "version": "1.0",
  "kerberos_principal" : {
    "principal_name" : "registry/_HOST@EXAMPLE.COM",
    "keytab" : "file:///etc/security/keytabs/registry.service.keytab"
  },
  "components" :
  [
    {
      "name": "registry",
      "number_of_containers": 1,
      "artifact": {
        "id": "registry:latest",
        "type": "DOCKER"
      },
      "resource": {
        "cpus": 1,
        "memory": "256"
      },
      "run_privileged_container": true,
      "configuration": {
        "env": {
          "YARN_CONTAINER_RUNTIME_DOCKER_RUN_OVERRIDE_DISABLE":"true",
          "YARN_CONTAINER_RUNTIME_DOCKER_MOUNTS":"<path to config.yml>:/etc/docker/registry/config.yml",
        },
        "properties": {
          "docker.network": "host"
        }
      }
    }
  ]
}

如需 S3 儲存空間驅動程式中可以設定的進一步詳細資料和參數,請參閱 https://docker-docs.dev.org.tw/registry/storage-drivers/s3/

範例:MapReduce

此範例假設 Hadoop 已安裝至 /usr/local/hadoop

此外,container-executor.cfg 中的 docker.allowed.ro-mounts 已更新為包含下列目錄:/usr/local/hadoop,/etc/passwd,/etc/group

若要提交 pi 工作在 Docker 容器中執行,請執行下列指令

  HADOOP_HOME=/usr/local/hadoop
  YARN_EXAMPLES_JAR=$HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar
  MOUNTS="$HADOOP_HOME:$HADOOP_HOME:ro,/etc/passwd:/etc/passwd:ro,/etc/group:/etc/group:ro"
  IMAGE_ID="library/openjdk:8"

  export YARN_CONTAINER_RUNTIME_TYPE=docker
  export YARN_CONTAINER_RUNTIME_DOCKER_IMAGE=$IMAGE_ID
  export YARN_CONTAINER_RUNTIME_DOCKER_MOUNTS=$MOUNTS

  yarn jar $YARN_EXAMPLES_JAR pi \
    -Dmapreduce.map.env.YARN_CONTAINER_RUNTIME_TYPE=docker \
    -Dmapreduce.map.env.YARN_CONTAINER_RUNTIME_DOCKER_MOUNTS=$MOUNTS \
    -Dmapreduce.map.env.YARN_CONTAINER_RUNTIME_DOCKER_IMAGE=$IMAGE_ID \
    -Dmapreduce.reduce.env.YARN_CONTAINER_RUNTIME_TYPE=docker \
    -Dmapreduce.reduce.env.YARN_CONTAINER_RUNTIME_DOCKER_MOUNTS=$MOUNTS \
    -Dmapreduce.reduce.env.YARN_CONTAINER_RUNTIME_DOCKER_IMAGE=$IMAGE_ID \
    1 40000

請注意,應用程式主控程式、對應工作和縮減工作是獨立設定的。在此範例中,我們對所有三者都使用 openjdk:8 映像。

範例:Spark

此範例假設 Hadoop 已安裝至 /usr/local/hadoop,而 Spark 已安裝至 /usr/local/spark

此外,container-executor.cfg 中的 docker.allowed.ro-mounts 已更新為包含下列目錄:/usr/local/hadoop,/etc/passwd,/etc/group

若要在 Docker 容器中執行 Spark shell,請執行下列指令

  HADOOP_HOME=/usr/local/hadoop
  SPARK_HOME=/usr/local/spark
  MOUNTS="$HADOOP_HOME:$HADOOP_HOME:ro,/etc/passwd:/etc/passwd:ro,/etc/group:/etc/group:ro"
  IMAGE_ID="library/openjdk:8"

  $SPARK_HOME/bin/spark-shell --master yarn \
    --conf spark.yarn.appMasterEnv.YARN_CONTAINER_RUNTIME_TYPE=docker \
    --conf spark.yarn.appMasterEnv.YARN_CONTAINER_RUNTIME_DOCKER_IMAGE=$IMAGE_ID \
    --conf spark.yarn.appMasterEnv.YARN_CONTAINER_RUNTIME_DOCKER_MOUNTS=$MOUNTS \
    --conf spark.executorEnv.YARN_CONTAINER_RUNTIME_TYPE=docker \
    --conf spark.executorEnv.YARN_CONTAINER_RUNTIME_DOCKER_IMAGE=$IMAGE_ID \
    --conf spark.executorEnv.YARN_CONTAINER_RUNTIME_DOCKER_MOUNTS=$MOUNTS

請注意,應用程式主控程式和執行器是獨立設定的。在此範例中,我們對兩者都使用 openjdk:8 映像。

Docker 容器 ENTRYPOINT 支援

當 Docker 支援導入 Hadoop 2.x 時,此平台的設計是讓現有的 Hadoop 程式在 Docker 容器內執行。記錄導向和環境設定已與節點管理員整合。在 Hadoop 3.x 中,Hadoop Docker 支援已擴充至執行 Hadoop 工作負載之外,並使用 Dockerfile 中的 ENTRYPOINT 以 Docker 原生形式支援 Docker 容器。應用程式可以透過定義 YARN_CONTAINER_RUNTIME_DOCKER_RUN_OVERRIDE_DISABLE 環境變數,決定是否將 YARN 模式或 Docker 模式設為預設。系統管理員也可以將 ENTRY_POINT 設為預設運作模式,作為叢集的預設設定。

在 yarn-site.xml 中,將 YARN_CONTAINER_RUNTIME_DOCKER_RUN_OVERRIDE_DISABLE 加入節點管理員環境白名單

<property>
        <name>yarn.nodemanager.env-whitelist</name>
        <value>JAVA_HOME,HADOOP_COMMON_HOME,HADOOP_HDFS_HOME,HADOOP_CONF_DIR,HADOOP_YARN_HOME,HADOOP_HOME,PATH,LANG,TZ,HADOOP_MAPRED_HOME,YARN_CONTAINER_RUNTIME_DOCKER_RUN_OVERRIDE_DISABLE</value>
</property>

在 yarn-env.sh 中,定義

export YARN_CONTAINER_RUNTIME_DOCKER_RUN_OVERRIDE_DISABLE=true

不使用 ENTRYPOINT 時的要求 (YARN 模式)

不使用 ENTRYPOINT 時有兩個要求

  1. 映像內部必須提供 /bin/bash。這通常為真,但微小的 Docker 映像(例如使用 busybox 進行 shell 命令的映像)可能未安裝 bash。在此情況下,會顯示下列錯誤

    Container id: container_1561638268473_0015_01_000002
    Exit code: 7
    Exception message: Launch container failed
    Shell error output: /usr/bin/docker-current: Error response from daemon: oci runtime error: container_linux.go:235: starting container process caused "exec: \"bash\": executable file not found in $PATH".
    Shell output: main : command provided 4
    
  2. 映像內部也必須提供 find 命令。未提供 find 會導致此錯誤

    Container exited with a non-zero exit code 127. Error file: prelaunch.err.
    Last 4096 bytes of prelaunch.err :
    /tmp/hadoop-systest/nm-local-dir/usercache/hadoopuser/appcache/application_1561638268473_0017/container_1561638268473_0017_01_000002/launch_container.sh: line 44: find: command not found
    

Docker Container YARN SysFS 支援

YARN SysFS 是由 YARN 架構提供的偽檔案系統,會將叢集資訊的資訊匯出至 Docker container。叢集資訊會匯出至 /hadoop/yarn/sysfs 路徑。此 API 允許應用程式開發人員在沒有外部服務相依性的情況下取得叢集資訊。自訂應用程式主控端可透過呼叫節點管理員 REST API 來填入叢集資訊。YARN 服務架構會自動將叢集資訊填入 /hadoop/yarn/sysfs/app.json。如需有關 YARN 服務的詳細資訊,請參閱:YARN 服務

Docker Container 服務模式

Docker Container 服務模式會執行映像定義的 container,但不會設定使用者(–user 和 –group-add)。此模式預設為停用。管理員在 docker 區段的 container-executor.cfg 中將 docker.service-mode.enabled 設定為 true 以啟用。

允許 docker 服務模式的 container-executor.cfg 部分如下

yarn.nodemanager.linux-container-executor.group=yarn
[docker]
  module.enabled=true
  docker.privileged-containers.enabled=true
  docker.service-mode.enabled=true

應用程式使用者可以在應用程式的環境中匯出環境變數 YARN_CONTAINER_RUNTIME_DOCKER_SERVICE_MODE,並分別使用值 true 或 false 來在工作層級啟用或停用服務模式。