使用 runC 容器启动应用程序

安全警告

重要

此功能不稳定。随着此功能的不断发展,API 可能无法维护,并且功能可能会更改或删除。

启用此功能并在集群中运行 runC 容器会产生安全影响。鉴于 runC 与许多强大的内核功能集成,因此管理员在启用此功能之前必须了解 runC 安全性。

概述

runC是一个 CLI 工具,用于根据开放容器计划 (OCI) 规范生成和运行容器。runC 最初是 原始 Docker 基础设施中 分离出来的。结合通过 squashFS 映像创建的 rootfs 挂载点,runC 使用户能够将应用程序与其首选执行环境捆绑在一起,以便在目标计算机上执行。有关 OCI 的更多信息,请参阅其 网站

Linux 容器执行器 (LCE) 允许 YARN NodeManager 启动 YARN 容器,以便直接在主机上、Docker 容器内以及现在 runC 容器内运行。请求资源的应用程序可以为每个容器指定其执行方式。LCE 还提供增强的安全性,并且在部署安全集群时需要。当 LCE 启动 YARN 容器在 runC 容器中执行时,应用程序可以指定要使用的 runC 映像。这些 runC 映像可以从 Docker 映像构建。

runC 容器提供了一个自定义执行环境,应用程序的代码在其中运行,与 NodeManager 和其他应用程序的执行环境隔离。这些容器可以包含应用程序所需的特殊库,并且它们可以具有不同版本的原生工具和库,包括 Perl、Python 和 Java。runC 容器甚至可以运行不同版本的 Linux,而不是在 NodeManager 上运行的版本。

用于 YARN 的 runC 提供了一致性(所有 YARN 容器都将具有相同的软件环境)和隔离性(不会干扰物理机上安装的任何内容)。

LCE 中的 runC 支持仍在不断发展。要跟踪进度并查看 runC 设计文档,请查看 YARN-9014,这是 runC 支持改进的总括性 JIRA。

集群配置

LCE 要求容器执行器二进制文件归 root:hadoop 所有并具有 6050 权限。为了启动 runC 容器,必须在将启动 runC 容器的所有 NodeManager 主机上安装 runC。

应在 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,runc</value>
    <description>
      Comma separated list of runtimes that are allowed when using
      LinuxContainerExecutor. The allowed values are default, docker, runc, 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>
    <description>The runC image tag to manifest plugin
      class to be used.</description>
    <name>yarn.nodemanager.runtime.linux.runc.image-tag-to-manifest-plugin</name>
    <value>org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.runc.ImageTagToManifestPlugin</value>
  </property>

  <property>
    <description>The runC manifest to resources plugin class to
      be used.</description>
    <name>yarn.nodemanager.runtime.linux.runc.manifest-to-resources-plugin</name>
    <value>org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.runc.HdfsManifestToResourcesPlugin</value>
  </property>

  <property>
    <description>The HDFS location under which the oci image manifests, layers,
      and configs directories exist.</description>
    <name>yarn.nodemanager.runtime.linux.runc.image-toplevel-dir</name>
    <value>/runc-root</value>
  </property>

  <property>
    <description>Target count of layer mounts that we should keep on disk
      at one time.</description>
    <name>yarn.nodemanager.runtime.linux.runc.layer-mounts-to-keep</name>
    <value>100</value>
  </property>

  <property>
    <description>The interval in seconds between executions of
      reaping layer mounts.</description>
    <name>yarn.nodemanager.runtime.linux.runc.layer-mounts-interval-secs</name>
    <value>600</value>
  </property>

  <property>
    <description>Image to be used if no other image is specified.</description>
    <name>yarn.nodemanager.runtime.linux.runc.image-name</name>
    <value></value>
  </property>

  <property>
    <description>Allow or disallow privileged containers.</description>
    <name>yarn.nodemanager.runtime.linux.runc.privileged-containers.allowed</name>
    <value>false</value>
  </property>

  <property>
    <description>The set of networks allowed when launching containers
      using the RuncContainerRuntime.</description>
    <name>yarn.nodemanager.runtime.linux.runc.allowed-container-networks</name>
    <value>host,none,bridge</value>
  </property>

  <property>
    <description>The set of runtimes allowed when launching containers
      using the RuncContainerRuntime.</description>
    <name>yarn.nodemanager.runtime.linux.runc.allowed-container-runtimes</name>
    <value>runc</value>
  </property>

  <property>
    <description>ACL list for users allowed to run privileged
      containers.</description>
    <name>yarn.nodemanager.runtime.linux.runc.privileged-containers.acl</name>
    <value></value>
  </property>

  <property>
    <description>Allow host pid namespace for runC containers.
      Use with care.</description>
    <name>yarn.nodemanager.runtime.linux.runc.host-pid-namespace.allowed</name>
    <value>false</value>
  </property>

  <property>
    <description>The default list of read-only mounts to be bind-mounted
      into all runC containers that use RuncContainerRuntime.</description>
    <name>yarn.nodemanager.runtime.linux.runc.default-ro-mounts</name>
    <value></value>
  </property>

  <property>
    <description>The default list of read-write mounts to be bind-mounted
      into all runC containers that use RuncContainerRuntime.</description>
    <name>yarn.nodemanager.runtime.linux.runc.default-rw-mounts</name>
    <value></value>
  </property>

  <property>
    <description>Path to the seccomp profile to use with runC
      containers</description>
    <name>yarn.nodemanager.runtime.linux.runc.seccomp-profile</name>
    <value></value>
  </property>

  <property>
    <description>The HDFS location where the runC image tag to hash
      file exists.</description>
    <name>yarn.nodemanager.runtime.linux.runc.image-tag-to-manifest-plugin.hdfs-hash-file</name>
    <value>/runc-root/image-tag-to-hash</value>
  </property>

  <property>
    <description>The local file system location where the runC image tag
      to hash file exists.</description>
    <name>yarn.nodemanager.runtime.linux.runc.image-tag-to-manifest-plugin.local-hash-file</name>
    <value></value>
  </property>

  <property>
    <description>The interval in seconds between refreshing the hdfs image tag
      to hash cache.</description>
    <name>yarn.nodemanager.runtime.linux.runc.image-tag-to-manifest-plugin.cache-refresh-interval-secs</name>
    <value>60</value>
  </property>

  <property>
    <description>The number of manifests to cache in the image tag
      to hash cache.</description>
    <name>yarn.nodemanager.runtime.linux.runc.image-tag-to-manifest-plugin.num-manifests-to-cache</name>
    <value>10</value>
  </property>

  <property>
    <description>The timeout value in seconds for the values in
      the stat cache.</description>
    <name>yarn.nodemanager.runtime.linux.runc.hdfs-manifest-to-resources-plugin.stat-cache-timeout-interval-secs</name>
    <value>360</value>
  </property>

  <property>
    <description>The size of the stat cache which stores stats of the
      layers and config.</description>
    <name>yarn.nodemanager.runtime.linux.runc.hdfs-manifest-to-resources-plugin.stat-cache-size</name>
    <value>500</value>
  </property>
</configuration>

此外,必须存在一个 container-executor.cfg 文件并包含容器执行器的设置。该文件必须归 root 所有,权限为 0400。该文件的格式是标准 Java 属性文件格式,例如

`key=value`

启用 runC 支持需要以下属性

配置名称 说明
yarn.nodemanager.linux-container-executor.group NodeManager 的 Unix 组。它应与 yarn-site.xml 文件中的 yarn.nodemanager.linux-container-executor.group 匹配。

container-executor.cfg 必须包含一个部分来确定允许容器的功能。它包含以下属性

配置名称 说明
module.enabled 必须为“true”或“false”以分别启用或禁用启动 runC 容器。默认值为 0。
runc.binary 用于启动 runC 容器的二进制文件。默认情况下为 /usr/bin/runc。
runc.run-root 将放置所有运行时挂载和覆盖挂载的目录。
runc.allowed.ro-mounts 允许容器以只读模式挂载的逗号分隔目录。默认情况下,不允许挂载任何目录。
runc.allowed.rw-mounts 允许容器以读写模式挂载的逗号分隔目录。默认情况下,不允许挂载任何目录。

请注意,如果您希望运行需要访问 YARN 本地目录的 runC 容器,则必须将它们添加到 runc.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 支持。默认设置为禁用。

允许启动 runC 容器的 container-executor.cfg 的一部分如下

yarn.nodemanager.linux-container-executor.group=yarn
[runc]
  module.enabled=true
  runc.binary=/usr/bin/runc
  runc.run-root=/run/yarn-container-executor
  runc.allowed.ro-mounts=/sys/fs/cgroup
  runc.allowed.rw-mounts=/var/hadoop/yarn/local-dir,/var/hadoop/yarn/log-dir

镜像要求

runC 容器在源自 Docker 镜像的镜像中运行。Docker 镜像会转换为一组 squashFS 文件镜像,并上传到 HDFS 中。为了与 YARN 配合使用,这些 Docker 镜像有一些要求。

  1. runC 容器将明确地以应用程序所有者作为容器用户启动。如果应用程序所有者不是 Docker 镜像中的有效用户,应用程序将失败。容器用户由用户的 UID 指定。如果用户 UID 在 NodeManager 主机和 Docker 镜像之间不同,则容器可能会以错误的用户启动,或者可能因为 UID 不存在而无法启动。有关更多详细信息,请参阅runC 容器中的用户管理部分。

  2. 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 镜像中安装的版本兼容。否则,在 runC 容器中启动的 Hadoop 组件可能无法与外部 Hadoop 组件通信。

  3. /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
    
  4. 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 镜像设置了入口点,则入口点将作为其参数使用容器的启动命令执行。

从 Docker 镜像派生的 runC 镜像会本地化到 runC 容器将执行的主机上,就像任何其他本地化资源一样。MapReduce 和 Spark 都假定需要花费 10 分钟以上报告进度的任务已停止,因此,如果本地化花费的时间过长,指定一个较大的镜像可能会导致应用程序失败。

将 Docker 镜像转换为 runC 镜像

每个 Docker 镜像由 3 件事组成:- 创建文件系统的层集。- 包含与镜像环境相关信息的配置文件。- 描述该镜像所需的层和配置的清单。

这 3 个部分结合在一起创建了符合开放容器计划 (OCI) 的镜像。runC 在 OCI 兼容容器之上运行,但有小小的不同。runC 运行时使用的每个层都压缩到 squashFS 文件系统中。squashFS 层以及配置和清单将与 image-tag-to-hash 映射 文件一起上传到 HDFS,该文件描述了镜像标记与该镜像关联的清单之间的映射。完成所有这些设置是一个复杂且繁琐的过程。在 YARN-9564 上有一个补丁,其中包含一个名为 docker-to-squash.py 的非官方 Python 脚本,以帮助完成转换过程。此工具将 Docker 镜像作为输入,将其所有层转换为 squashFS 文件系统,并将 squashFS 层、配置和清单上传到 runc-root 下方的 HDFS。它还将创建或更新 image-tag-to-hash 映射文件。以下是该脚本的一个示例调用,用于将名为 centos:latest 的镜像上传到 HDFS,runC 镜像名为 centos

[user@foobar sbin]$ pwd
/home/user/hadoop/hadoop-dist/target/hadoop-3.3.0-SNAPSHOT/sbin
[user@foobar sbin]$ ls
distribute-exclude.sh  hadoop-daemons.sh        refresh-namenodes.sh  start-dfs.cmd        start-yarn.sh     stop-dfs.cmd        stop-yarn.sh
docker_to_squash.py    httpfs.sh                start-all.cmd         start-dfs.sh         stop-all.cmd      stop-dfs.sh         workers.sh
FederationStateStore   kms.sh                   start-all.sh          start-secure-dns.sh  stop-all.sh       stop-secure-dns.sh  yarn-daemon.sh
hadoop-daemon.sh       mr-jobhistory-daemon.sh  start-balancer.sh     start-yarn.cmd       stop-balancer.sh  stop-yarn.cmd       yarn-daemons.sh
[user@foobar sbin]$ hadoop fs -ls /
Found 3 items
drwxrwx---   - user supergroup          0 2019-08-07 19:35 /home
drwx------   - user supergroup          0 2019-08-07 19:35 /tmp
drwx------   - user supergroup          0 2019-08-07 19:35 /user
[user@foobar sbin]$ ./docker_to_squash.py --working-dir /tmp --log=DEBUG pull-build-push-update centos:latest,centos
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'version']
DEBUG: command: ['skopeo', '-v']
DEBUG: command: ['mksquashfs', '-version']
DEBUG: args: Namespace(LOG_LEVEL='DEBUG', check_magic_file=False, force=False, func=<function pull_build_push_update at 0x7fe6974cd9b0>, hadoop_prefix='/hadoop-2.8.6-SNAPSHOT', hdfs_root='/runc-root', image_tag_to_hash='image-tag-to-hash', images_and_tags=['centos:latest,centos'], magic_file='etc/dockerfile-version', pull_format='docker', replication=1, skopeo_format='dir', sub_command='pull-build-push-update', working_dir='/tmp')
DEBUG: extra: []
DEBUG: image-tag-to-hash: image-tag-to-hash
DEBUG: LOG_LEVEL: DEBUG
DEBUG: HADOOP_BIN_DIR: /hadoop-2.8.6-SNAPSHOT/bin
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-ls', '/runc-root']
ls: `/runc-root': No such file or directory
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-mkdir', '/runc-root']
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-ls', '/runc-root']
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-chmod', '755', '/runc-root']
DEBUG: Setting up squashfs dirs: ['/runc-root/layers', '/runc-root/config', '/runc-root/manifests']
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-ls', '/runc-root/layers']
ls: `/runc-root/layers': No such file or directory
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-mkdir', '/runc-root/layers']
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-ls', '/runc-root/layers']
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-chmod', '755', '/runc-root/layers']
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-ls', '/runc-root/config']
ls: `/runc-root/config': No such file or directory
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-mkdir', '/runc-root/config']
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-ls', '/runc-root/config']
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-chmod', '755', '/runc-root/config']
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-ls', '/runc-root/manifests']
ls: `/runc-root/manifests': No such file or directory
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-mkdir', '/runc-root/manifests']
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-ls', '/runc-root/manifests']
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-chmod', '755', '/runc-root/manifests']
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-ls', '/runc-root/image-tag-to-hash']
ls: `/runc-root/image-tag-to-hash': No such file or directory
INFO: Working on image centos:latest with tags ['centos']
DEBUG: command: ['skopeo', 'inspect', '--raw', 'docker://centos:latest']
DEBUG: skopeo inspect --raw returned a list of manifests
DEBUG: amd64 manifest sha is: sha256:ca58fe458b8d94bc6e3072f1cfbd334855858e05e1fd633aa07cf7f82b048e66
DEBUG: command: ['skopeo', 'inspect', '--raw', u'docker://centos@sha256:ca58fe458b8d94bc6e3072f1cfbd334855858e05e1fd633aa07cf7f82b048e66']
INFO: manifest: {u'layers': [{u'mediaType': u'application/vnd.docker.image.rootfs.diff.tar.gzip', u'digest': u'sha256:8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df', u'size': 75403831}], u'schemaVersion': 2, u'config': {u'mediaType': u'application/vnd.docker.container.image.v1+json', u'digest': u'sha256:9f38484d220fa527b1fb19747638497179500a1bed8bf0498eb788229229e6e1', u'size': 2182}, u'mediaType': u'application/vnd.docker.distribution.manifest.v2+json'}
INFO: manifest: {u'layers': [{u'mediaType': u'application/vnd.docker.image.rootfs.diff.tar.gzip', u'digest': u'sha256:8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df', u'size': 75403831}], u'schemaVersion': 2, u'config': {u'mediaType': u'application/vnd.docker.container.image.v1+json', u'digest': u'sha256:9f38484d220fa527b1fb19747638497179500a1bed8bf0498eb788229229e6e1', u'size': 2182}, u'mediaType': u'application/vnd.docker.distribution.manifest.v2+json'}
DEBUG: Layers: [u'8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df']
DEBUG: Config: 9f38484d220fa527b1fb19747638497179500a1bed8bf0498eb788229229e6e1
DEBUG: hash_to_tags is null. Not removing tag centos
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-ls', '/runc-root/manifests/ca58fe458b8d94bc6e3072f1cfbd334855858e05e1fd633aa07cf7f82b048e66']
ls: `/runc-root/manifests/ca58fe458b8d94bc6e3072f1cfbd334855858e05e1fd633aa07cf7f82b048e66': No such file or directory
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-ls', u'/runc-root/config/9f38484d220fa527b1fb19747638497179500a1bed8bf0498eb788229229e6e1']
ls: `/runc-root/config/9f38484d220fa527b1fb19747638497179500a1bed8bf0498eb788229229e6e1': No such file or directory
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-ls', u'/runc-root/layers/8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df.sqsh']
ls: `/runc-root/layers/8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df.sqsh': No such file or directory
DEBUG: skopeo_dir: /tmp/docker-to-squash/centos:latest
INFO: Pulling image: centos:latest
DEBUG: command: ['skopeo', 'copy', 'docker://centos:latest', 'dir:/tmp/docker-to-squash/centos:latest']
INFO: Squashifying and uploading layer: 8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-ls', u'/runc-root/layers/8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df.sqsh']
ls: `/runc-root/layers/8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df.sqsh': No such file or directory
DEBUG: command: ['sudo', 'tar', '-C', u'/tmp/docker-to-squash/expand_archive_8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df', '--xattrs', "--xattrs-include='*'", '-xzf', u'/tmp/docker-to-squash/centos:latest/8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df']
DEBUG: command: ['sudo', 'find', u'/tmp/docker-to-squash/expand_archive_8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df', '-name', '.wh.*']
DEBUG: command: ['sudo', 'mksquashfs', u'/tmp/docker-to-squash/expand_archive_8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df', u'/tmp/docker-to-squash/centos:latest/8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df.sqsh']
DEBUG: command: ['sudo', 'rm', '-rf', u'/tmp/docker-to-squash/expand_archive_8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df']
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-ls', u'/runc-root/layers/8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df.sqsh']
ls: `/runc-root/layers/8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df.sqsh': No such file or directory
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-put', u'/tmp/docker-to-squash/centos:latest/8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df.sqsh', u'/runc-root/layers/8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df.sqsh']
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-setrep', '1', u'/runc-root/layers/8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df.sqsh']
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-chmod', '444', u'/runc-root/layers/8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df.sqsh']
INFO: Uploaded file /runc-root/layers/8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df.sqsh with replication 1 and permissions 444
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-ls', u'/runc-root/config/9f38484d220fa527b1fb19747638497179500a1bed8bf0498eb788229229e6e1']
ls: `/runc-root/config/9f38484d220fa527b1fb19747638497179500a1bed8bf0498eb788229229e6e1': No such file or directory
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-put', u'/tmp/docker-to-squash/centos:latest/9f38484d220fa527b1fb19747638497179500a1bed8bf0498eb788229229e6e1', u'/runc-root/config/9f38484d220fa527b1fb19747638497179500a1bed8bf0498eb788229229e6e1']
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-setrep', '1', u'/runc-root/config/9f38484d220fa527b1fb19747638497179500a1bed8bf0498eb788229229e6e1']
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-chmod', '444', u'/runc-root/config/9f38484d220fa527b1fb19747638497179500a1bed8bf0498eb788229229e6e1']
INFO: Uploaded file /runc-root/config/9f38484d220fa527b1fb19747638497179500a1bed8bf0498eb788229229e6e1 with replication 1 and permissions 444
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-ls', '/runc-root/manifests/ca58fe458b8d94bc6e3072f1cfbd334855858e05e1fd633aa07cf7f82b048e66']
ls: `/runc-root/manifests/ca58fe458b8d94bc6e3072f1cfbd334855858e05e1fd633aa07cf7f82b048e66': No such file or directory
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-put', '/tmp/docker-to-squash/centos:latest/manifest.json', '/runc-root/manifests/ca58fe458b8d94bc6e3072f1cfbd334855858e05e1fd633aa07cf7f82b048e66']
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-setrep', '1', '/runc-root/manifests/ca58fe458b8d94bc6e3072f1cfbd334855858e05e1fd633aa07cf7f82b048e66']
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-chmod', '444', '/runc-root/manifests/ca58fe458b8d94bc6e3072f1cfbd334855858e05e1fd633aa07cf7f82b048e66']
INFO: Uploaded file /runc-root/manifests/ca58fe458b8d94bc6e3072f1cfbd334855858e05e1fd633aa07cf7f82b048e66 with replication 1 and permissions 444
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-put', '-f', '/tmp/docker-to-squash/image-tag-to-hash', '/runc-root/image-tag-to-hash']
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-setrep', '1', '/runc-root/image-tag-to-hash']
DEBUG: command: ['/hadoop-2.8.6-SNAPSHOT/bin/hadoop', 'fs', '-chmod', '444', '/runc-root/image-tag-to-hash']
DEBUG: command: ['sudo', 'rm', '-rf', '/tmp/docker-to-squash']
[user@foobar sbin]$ hadoop fs -ls /
Found 4 items
drwxrwx---   - user supergroup          0 2019-08-07 19:35 /home
drwxr-xr-x   - user supergroup          0 2019-08-08 22:38 /runc-root
drwx------   - user supergroup          0 2019-08-07 19:35 /tmp
drwx------   - user supergroup          0 2019-08-07 19:35 /user
[user@foobar sbin]$ hadoop fs -ls /runc-root/*
Found 1 items
-r--r--r--   1 user supergroup       2182 2019-08-08 22:38 /runc-root/config/9f38484d220fa527b1fb19747638497179500a1bed8bf0498eb788229229e6e1
-r--r--r--   1 user supergroup         86 2019-08-08 22:38 /runc-root/image-tag-to-hash
Found 1 items
-r--r--r--   1 user supergroup   73625600 2019-08-08 22:38 /runc-root/layers/8ba884070f611d31cb2c42eddb691319dc9facf5e0ec67672fcfa135181ab3df.sqsh
Found 1 items
-r--r--r--   1 user supergroup        529 2019-08-08 22:38 /runc-root/manifests/ca58fe458b8d94bc6e3072f1cfbd334855858e05e1fd633aa07cf7f82b048e66

应用程序提交

在尝试启动 runC 容器之前,请确保 LCE 配置对请求常规 YARN 容器的应用程序有效。如果启用 LCE 后一个或多个 NodeManager 无法启动,最可能的原因是容器执行程序二进制文件的所有权和/或权限不正确。检查日志以确认。

要在 runC 容器中运行应用程序,请在应用程序的环境中设置以下环境变量

环境变量名称 说明
YARN_CONTAINER_RUNTIME_TYPE 确定是否在 runC 容器中启动应用程序。如果值为“runc”,则应用程序将在 runC 容器中启动。否则将使用常规进程树容器。
YARN_CONTAINER_RUNTIME_RUNC_IMAGE 命名将用于启动 runC 容器的映像。
YARN_CONTAINER_RUNTIME_RUNC_CONTAINER_HOSTNAME 设置 runC 容器要使用的主机名。
YARN_CONTAINER_RUNTIME_RUNC_MOUNTS 向 runC 容器添加其他卷装载。环境变量的值应为装载的逗号分隔列表。所有此类装载都必须指定为“source:dest:mode”,并且模式必须为“ro”(只读)或“rw”(读写)以指定所请求的访问类型。如果未指定任何模式,则假定为读写。容器执行程序将根据 container-executor.cfg 中为 runc.allowed.ro-mounts 和 runc.allowed.rw-mounts 设置的值验证请求的装载。

前两个是必需的。其余的可以根据需要设置。虽然通过环境变量控制容器类型不太理想,但它允许不了解 YARN 的 runC 支持的应用程序(例如 MapReduce 和 Spark)通过其对配置应用程序环境的支持来利用它。

注意 如果在容器中将任何内容装载到 /tmp 或 /var/tmp,则运行时将不起作用。

一旦提交应用程序在 runC 容器中启动,该应用程序的行为将与任何其他 YARN 应用程序完全相同。日志将被聚合并存储在相关的历史记录服务器中。应用程序生命周期将与非 runC 应用程序相同。

使用 runC 绑定装载卷

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

runC 容器中通常需要来自主机的文件和目录,runC 通过将它们挂载到容器中来提供这些文件和目录。示例包括本地化资源、Apache Hadoop 二进制文件和套接字。

要将任何内容挂载到容器中,必须配置以下内容。

  • 管理员必须通过将 runc.allowed.ro-mountsrunc.allowed.rw-mounts 设置为允许挂载的父目录列表,在 container-executor.cfg 中定义卷白名单。

管理员提供的白名单定义为允许挂载到容器中的目录的逗号分隔列表。用户提供的源目录必须与指定目录匹配或为其子目录。

用户提供的挂载列表定义为逗号分隔列表,格式为 source:destinationsource:destination:mode。源是主机上的文件或目录。目标是源将被绑定挂载到的容器中的路径。模式定义用户对挂载的预期模式,可以是 ro(只读)或 rw(读写)。如果未指定,则假定为 rw。模式还可以包括绑定传播选项(shared、rshared、slave、rslave、private 或 rprivate)。在这种情况下,模式应为 option、rw+option 或 ro+option

以下示例概述了如何使用此功能将通常需要的 /sys/fs/cgroup 目录挂载到在 YARN 上运行的容器中。

管理员在 container-executor.cfg 中将 runc.allowed.ro-mounts 设置为 “/sys/fs/cgroup”。现在,应用程序可以请求将 “/sys/fs/cgroup” 从主机以只读模式挂载到容器中。

Nodemanager 有权通过 yarn-site.xml 中的 yarn.nodemanager.runtime.linux.runc.default-ro-mount" 和 yarn.nodemanager.runtime.linux.runc.default-rw-mounts 设置要添加到容器的默认只读或读写挂载列表。在此示例中,yarn.nodemanager.runtime.linux.runc.default-ro-mounts 将设置为 /sys/fs/cgroup:/sys/fs/cgroup

runC 容器中的用户管理

YARN 的 runC 容器支持使用 NodeManager 主机上定义的 uid:gid 标识来启动容器进程。NodeManager 主机和容器之间的用户和组名称不匹配可能导致权限问题、容器启动失败,甚至安全漏洞。集中管理主机和容器的用户和组可以极大地降低这些风险。在 YARN 上运行容器化应用程序时,有必要了解将用于启动容器进程的 uid:gid 对。

以下是一个 uid:gid 对含义的示例。默认情况下,在非安全模式下,YARN 将以用户 nobody 启动进程(请参阅 在 YARN 中使用 CGroups 底部的表格,了解如何在非安全模式下确定运行用户)。在基于 CentOS 的系统上,nobody 用户的 uid 为 99nobody 组为 99。因此,YARN 将使用 uid 99 和 gid 99 调用 runC。如果容器中 nobody 用户没有 uid 99,则启动可能会失败或产生意外结果。

有许多方法可以解决用户和组管理问题。默认情况下,runC 将在容器内的 /etc/passwd(和 /etc/shadow)中对用户进行身份验证。使用 runC 映像中提供的默认 /etc/passwd 不太可能包含适当的用户条目,并且会导致启动失败。强烈建议集中管理用户和组。以下概述了用户和组管理的几种方法。

静态用户管理

管理用户和组的最基本方法是在 runC 映像中修改用户和组。此方法仅适用于非安全模式,其中所有容器进程都将作为单个已知用户(例如 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 中的 runc.allowed.ro-mounts 以包括这些路径。要在 runC 上执行此操作,“yarn.nodemanager.runtime.linux.runc.default-ro-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. 容器设置

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

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

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

  • 仅安装 sss 客户端库

    # yum -y install sssd-client
    
  • 确保在中为 passwd 和 group 数据库配置 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)
    

示例:MapReduce

此示例假定 Hadoop 已安装到 /usr/local/hadoop

在使用该镜像运行之前,你还需要将 Docker 镜像压缩并上传到 HDFS。请参阅 将 Docker 镜像转换为 runC 镜像,了解如何将 Docker 镜像转换为 runC 可用的镜像。对于此示例,我们将假设你已使用名为 hadoop-image 的镜像完成了该操作。

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

要提交 pi 作业以在 runC 容器中运行,请运行以下命令

  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="hadoop-image"

  export YARN_CONTAINER_RUNTIME_TYPE=runc
  export YARN_CONTAINER_RUNTIME_RUNC_IMAGE=$IMAGE_ID
  export YARN_CONTAINER_RUNTIME_RUNC_MOUNTS=$MOUNTS

  yarn jar $YARN_EXAMPLES_JAR pi \
    -Dmapreduce.map.env.YARN_CONTAINER_RUNTIME_TYPE=runc \
    -Dmapreduce.map.env.YARN_CONTAINER_RUNTIME_RUNC_MOUNTS=$MOUNTS \
    -Dmapreduce.map.env.YARN_CONTAINER_RUNTIME_RUNC_IMAGE=$IMAGE_ID \
    -Dmapreduce.reduce.env.YARN_CONTAINER_RUNTIME_TYPE=runc \
    -Dmapreduce.reduce.env.YARN_CONTAINER_RUNTIME_RUNC_MOUNTS=$MOUNTS \
    -Dmapreduce.reduce.env.YARN_CONTAINER_RUNTIME_RUNC_IMAGE=$IMAGE_ID \
    1 40000

请注意,应用程序主控、映射任务和归约任务是独立配置的。在此示例中,我们对所有三个任务都使用 hadoop-image 镜像。

示例:Spark

此示例假定 Hadoop 已安装到 /usr/local/hadoop,Spark 已安装到 /usr/local/spark

在使用该镜像运行之前,你还需要将 Docker 镜像压缩并上传到 HDFS。请参阅 将 Docker 镜像转换为 runC 镜像,了解如何将 Docker 镜像转换为 runC 可用的镜像。对于此示例,我们将假设你已使用名为 hadoop-image 的镜像完成了该操作。

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

要在 runC 容器中运行 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="hadoop-image"

  $SPARK_HOME/bin/spark-shell --master yarn \
    --conf spark.yarn.appMasterEnv.YARN_CONTAINER_RUNTIME_TYPE=runc \
    --conf spark.yarn.appMasterEnv.YARN_CONTAINER_RUNTIME_RUNC_IMAGE=$IMAGE_ID \
    --conf spark.yarn.appMasterEnv.YARN_CONTAINER_RUNTIME_RUNC_MOUNTS=$MOUNTS \
    --conf spark.executorEnv.YARN_CONTAINER_RUNTIME_TYPE=runc \
    --conf spark.executorEnv.YARN_CONTAINER_RUNTIME_RUNC_IMAGE=$IMAGE_ID \
    --conf spark.executorEnv.YARN_CONTAINER_RUNTIME_RUNC_MOUNTS=$MOUNTS

请注意,应用程序主控和执行器是独立配置的。在此示例中,我们对两者都使用 hadoop-image 镜像。