使用 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 容器的所有 NodeManager 主机上,并且能够启动 Docker 容器。

为了防止在启动作业时超时,应用程序要使用的任何大型 Docker 映像都应已加载到 NodeManager 主机上 Docker 守护程序的缓存中。加载映像的一个简单方法是发出 Docker 拉取请求。例如

    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 将作为 CMD 参数传递给 Docker 中的 ENTRYPOINT 程序。launch_command 的格式如下所示:param1,param2,在 Docker 中转换为 CMD [“param1”、“param2”]。

如果应用程序请求的 Docker 映像尚未由其执行所在主机的 Docker 守护程序加载,则 Docker 守护程序将隐式执行 Docker pull 命令。MapReduce 和 Spark 都假定需要 10 分钟以上才能报告进度的任务已停止,因此指定较大的 Docker 映像可能会导致应用程序失败。

CGroups 配置要求

Docker 插件利用 cgroup 来限制各个容器的资源使用。由于启动的容器属于 YARN,因此使用命令行选项 --cgroup-parent 来定义适当的控制组。

Docker 支持两种不同的 cgroup 驱动程序: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 运行在哪个操作系统上,重新配置可能需要不同的步骤。但是,如果为 cgroup 驱动程序选择了 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。您必须在守护程序的单元文件中进行修改。

~$ 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 客户端的 run 命令的任何映像名称。映像名称可能包含 repo 前缀。
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”(读写)以指定所请求的访问类型。如果未指定,则假定为读写。该模式可能包括绑定传播选项。在这种情况下,该模式应为 [option]rw+[option]ro+[option] 形式。有效的绑定传播选项包括 shared、rshared、slave、rslave、private 和 rprivate。请求的装载将根据 container-executor.cfg 中为 docker.allowed.ro-mountsdocker.allowed.rw-mounts 设置的值由 container-executor 验证。
YARN_CONTAINER_RUNTIME_DOCKER_TMPFS_MOUNTS 向 Docker 容器添加其他 tmpfs 装载。环境变量的值应为容器内绝对装载点的逗号分隔列表。
YARN_CONTAINER_RUNTIME_DOCKER_DELAYED_REMOVAL 允许用户逐个容器请求延迟删除 Docker 容器。如果为 true,则在 yarn.nodemanager.delete.debug-delay-sec 定义的持续时间过去之前,不会删除 Docker 容器。管理员可以通过 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 绑定挂载卷

警告启用此功能时应小心。不建议启用对目录(包括但不限于 /、/etc、/run 或 /home)的访问,这可能导致容器对主机产生负面影响或泄露敏感信息。警告

Docker 容器通常需要来自主机的文件和目录,Docker 通过提供这些文件和目录。示例包括本地化资源、Apache Hadoop 二进制文件和套接字。为了满足这一需求,YARN-6623 添加了管理员设置主机目录白名单的功能,允许将这些目录作为卷绑定挂载到容器中。YARN-5534 添加了用户提供挂载列表的功能,如果管理员白名单允许,这些挂载将被挂载到容器中。

为了使用此功能,必须配置以下内容。

  • 管理员必须通过将 docker.allowed.ro-mountsdocker.allowed.rw-mounts 设置为允许挂载的父目录列表,在 container-executor.cfg 中定义卷白名单。
  • 应用程序提交者在应用程序提交时使用 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 启动进程(请参阅 在 YARN 中使用 CGroups 底部的表格,了解如何在非安全模式下确定运行用户)。在基于 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 套接字绑定挂载到容器中。这将允许 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. 容器设置

将 /var/lib/sss/pipes 目录从主机绑定挂载到容器非常重要,因为 SSSD UNIX 套接字位于那里。

-v /var/lib/sss/pipes:/var/lib/sss/pipes:rw
  1. 容器配置

以下所有步骤都应在容器本身上执行。

  • 仅安装 sss 客户端库

    # yum -y install sssd-client
    
  • 确保在中为 passwd 和组数据库配置 sss

    /etc/nsswitch.conf
    
  • 配置应用程序用来调用 SSSD 的 PAM 服务

    # 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 容器。仅当启用了 ENTRYPOINT 的 Docker 镜像,并且 docker.privileged-containers.enabled 设置为 enabled 时,才允许使用特权 Docker。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 运行将从 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 配置是存储安全仓库凭据的位置,因此不建议使用此方法将 LCE 与安全的 Docker 仓库结合使用。

YARN-5428 为分布式 Shell 添加了安全提供 Docker 客户端配置的支持。有关用法,请参阅分布式 Shell 帮助。计划支持更多框架。

作为一种解决方法,您可以使用 Docker login 命令将每个 NodeManager 主机上的 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 Trusted Registry 集成。Docker 注册表可以使用 CSI 驱动程序将 Docker 镜像存储在 HDFS、S3 或外部存储中。

Docker Registry 在 HDFS 上

NFS Gateway 提供将 HDFS 挂载为 NFS 挂载点功能。Docker Registry 可以配置为使用标准文件系统 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/[email protected]</value>
    </property>

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

使用以下命令以 hdfs 用户身份在所有数据节点上运行 NFS Gateway

$ $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 配置为允许来自库的受信任 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

可以使用 YARN 服务启动 Docker Registry:registry.json

{
  "name": "docker-registry",
  "version": "1.0",
  "kerberos_principal" : {
    "principal_name" : "registry/[email protected]",
    "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 受信任注册表部署在 YARN 框架中,访问注册表的 URL 遵循 Hadoop 注册表 DNS 格式

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

当 docker-registry 应用程序在 YARN 中达到 STABLE 状态时,用户可以通过将注册表名称 registry.docker-registry.registry.example.com:5000/ 添加到镜像名称前缀来将 docker 镜像推送到 Docker 受信任注册表或从中拉取 docker 镜像。

Docker Registry 在 S3 上

Docker Registry 提供了自己的 S3 驱动程序和 YAML 配置。YARN 服务配置可以生成 YAML 模板,并启用 Docker Registry 直接连接到 S3 存储。此选项是将 Docker 受信任注册表部署到 AWS 的首选。将 Docker 注册表存储驱动程序配置为 S3 需要挂载 /etc/docker/registry/config.yml 文件(通过 YARN_CONTAINER_RUNTIME_DOCKER_MOUNTS),该文件需要使用相应的 accesskey 和 secretKey 配置 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#

可以使用 YARN 服务启动 Docker Registry:registry.json

{
  "name": "docker-registry",
  "version": "1.0",
  "kerberos_principal" : {
    "principal_name" : "registry/[email protected]",
    "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://docs.docker.net.cn/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 时,该平台旨在在 Docker 容器内运行现有的 Hadoop 程序。日志重定向和环境设置与节点管理器集成。在 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 容器 YARN SysFS 支持

YARN SysFS 是 YARN 框架提供的伪文件系统,它将有关集群信息的信息导出到 Docker 容器。集群信息导出到 /hadoop/yarn/sysfs 路径。此 API 允许应用程序开发人员在没有外部服务依赖关系的情况下获取集群信息。自定义应用程序主服务器可以通过调用节点管理器 REST API 来填充集群信息。YARN 服务框架自动将集群信息填充到 /hadoop/yarn/sysfs/app.json。有关 YARN 服务的更多信息,请参阅:YARN 服务

Docker 容器服务模式

Docker 容器服务模式运行由映像定义的容器,但不设置用户(–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,在作业级别启用或禁用服务模式。