1.1.4.5 外部 containerizer
- EC = 外部 containerizer 。 mesos slave 的一部分其提供 API ,通过可执行的外部插件来支持 containerizing ( 集装箱化 )。
- ECP = 外部 containerizer 程序。 一个通过 containering system 接口实现了真正的集装箱化的可执行外部插件 。 ( 例如 Docker )
总体概述
EC 调用 ECP 作为一个 shell 进程,将 shell 中的命令作为参数传递给 ECP 执行 。 额外的数据则通过 stdin 和 stdout 传递。
ECP 会对所有它能够处理的命令返回一个 " 0 "状态码 ,非 " 0 " 状态码则表示发出的错误信号。下面你会找到必须在 ECP 上实现的命令的概述,以及它们的调用机制。
ECP 期望能够使用 stderr 来显示更多的调试信息和状态信息。这些信息将被记录到一个文件中。 请参考 Enviroment: Sandbox
接口描述了一个 ECP 具有通过命令调用来实现的功能。在 ECP 上的许多调用还将 protobuf 消息通过 stdin 在相互间传递。有些 ECP 之上的调用被期望能够通过 strout 提供一个 protobuf 消息返回。所有的 protobuf 消息以他们的原始长度作为前缀 - 这有时被称为"Record-IO"格式。 请参考Record-IO 序列化/反序列化示例
- launch < containerizer::Launch
- update < containerizer::Update
- usage < containerizer::Usage > mesos::ResourceStatistics
- wait < containerizer::Wait > containerizer::Termination
- destroy < containerizer::Destroy
- containers > containerizer::Containers
- recover
命令几乎可以以任意顺序执行,对此只有一个例外: 当启动一个 task 时, EC 将确保 ECP 首先在指定的容器上接收一个 launch 。所有的命令都需要排在后边,直到 luanch 从 ECP 能够返回。
- EC 在 ECP 上调用 launch 命令。
- 随着调用, ECP 会通过 stdin 接收到一个 " containerizer::Launch " protobuf 消息。
- ECP 确保 executor 已经启动。注意:启动是不应该被阻塞的。它应该触发 executor / command 后立即返回 ,这可以通过 ECP 中的 fork-exec 完成。
- EC 在 ECP 上调用 wait 命令。
- 随着调用,ECP 会通过 stdin 接受到一个 " containerizer::Wait " protobuf 消息。
- ECP 现在阻塞,直到再次收到启动命令。启动命令可以通过 ECP 中的 waitpid 实现。
- 一但收到命令, ECP 应该通过 strout 提供一个 " containerizer::Termination " protobuf 消息, 返回给 EC 。
容器 生命周期图
一个容器在准备状态,现在开始启动和保留直到其进入一个最终状态。
一个容器在某个点上得到启用,当下被 slave 认作将进入一个非终端状态,以下的命令在 ECP 将得到多次触发在一个容器的整个生命周期中。其触发序列没有没定义。
当一个容器运行,一个资源限制(如:超过内存容量)将通过 ECP 隔离模式的选择来定义
- 通过监测点状态来恢复 Slave 。
- EC 通过 ECP 调用 recover ,这里没有 protobuf 消息发送或者如预期的那样从该命令返回一个结果。
- 如果有必要, ECP可以通过自己的故障恢复机制来恢复内部的状态。
- 当 recover 命令返回, EC 将通过 ECP 执行 container。
- ECP 会返回一个当前处于 active 状态的 cantainers 列表。注意:这些 container 应该是被 ECP 所知的。但实际情况下部分是未知的 slave (例如 slave 启动后失败或者正在等待)。这些 container 被认为是孤儿,
- EC 会把 slave 已知的列表中 containers 与 Containers 命令列出的进行比较。对于每个被标记为孤儿的 container, slave 将在 ECP 上调用 wait 命令后,再调用 destroy 销毁它们。
- slave 在 ECP 上调用(通过 EC) wait 命令来恢复所有 containers。其将在等待无限的命令收割中再次被执行
当容器运行时, slave 将执行故障转移。
容器被 ECP 所识别的将被运行但 slave 状态可恢复的能力将被终止。
通过一个执行者来传递所有的 ECP 需要启用一个作业需要的信息。这个调用不会等待 执行器/命令返回。 容器化的实际结果命令通过 wait 调用。
launch < containerizer::Launch
这个调用通过 stdin 接收 containerizer::Launch protobuf
/**
* 对发送到外部容器化程序的启用指令进行解码。
*/
message Launch {
required ContainerID container_id = 1;
optional TaskInfo task_info = 2;
optional ExecutorInfo executor_info = 3;
optional string directory = 4;
optional string user = 5;
optional SlaveID slave_id = 6;
optional string slave_pid = 7;
optional bool checkpoint = 8;
}
这个调用不会从 stdout 返回任何数据。
这个调用被期望获得到执行者/命令。这个调用会阻直到执行者/命令被终止。
wait < containerizer::Wait > containerizer::Termination
这个调用通过 stdin 接收 containerizer::Wait protobuf 。
/**
* Encodes the wait command sent to the external containerizer
* program.
*/
message Wait {
required ContainerID container_id = 1;
}
这个调用被期望能够通过 stdout 返回 containerizer::Termination。
/**
* 关于一个容器的终止信息, containerizer 到 slave 的返回
*/
message Termination {
// 一个容器可能被杀死如果它超过了自己的资源使用上限;其将被 killed=true 所标注并被信息串所描述。
required bool killed = 1;
required string message = 2;
// 进程的退出状态值
optional int32 status = 3;
}
终止的属性 killed 只有当 containerizer 或底层隔离必须通过杀死任务来实现限制时才被设置(比如:task 超过了建议的内存限制)。
用来针对给定的容器发送(新的)资源限制指令。在容器化的 task 的整个生命周期中附加给容器的资源限制可能会改变。
update < containerizer::Update
这个调用通过 stdin 接收 containerizer::Update
/**
* 解析更新命令并发送给外部 containerizer 程序。
*/
message Update {
required ContainerID container_id = 1;
repeated Resource resources = 2;
}
这个调用不会通过 stdout 返回任何数据。
用于调查当前容器的资源使用情况
usage < containerizer::Usage > mesos::ResourceStatistics 这个调用通过 stdin 接收 containerizer::Usage protobuf 。
/**
*解码 usage 命令并发送到外部 containerizer 程序
*/
message Usage {
required ContainerID container_id = 1;
}
这个调用被期望能通过 stdout 返回 mesos::ResourceStatistics 。
/*
* 一个资源使用统计的快照。
*/
message ResourceStatistics {
required double timestamp = 1; // Snapshot time, in seconds since the Epoch.
// CPU Usage Information: CPU 使用信息:
// 用户模式和内核模式下总 CPU 使用时间。
optional double cpus_user_time_secs = 2;
optional double cpus_system_time_secs = 3;
// 多少个 CPU 被分配。
optional double cpus_limit = 4;
// cpu.stat 针对进程限制(对于竞争问题)
optional uint32 cpus_nr_periods = 7;
optional uint32 cpus_nr_throttled = 8;
optional double cpus_throttled_time_secs = 9;
// 内存使用信息:
optional uint64 mem_rss_bytes = 5; // Resident Set Size.
// 多少内存资源被分配。
optional uint64 mem_limit_bytes = 6;
// 分别的内存使用信息 (files, anonymous, and mmaped files)
optional uint64 mem_file_bytes = 10;
optional uint64 mem_anon_bytes = 11;
optional uint64 mem_mapped_file_bytes = 12;
}
终止容器的执行者
在比较少见的情况下才被使用。像 slave 安全的关闭,但同时也出现在 slave 故障转移的场景 - 更多信息请参考 Slave 恢复。
destroy < containerizer::Destroy
这个调用通过 stdin 接收 containerizer::Destroy protobuf 。
/**
* 解码销毁命令并传递给外部的 containerizer 应用
*/
message Destroy {
required ContainerID container_id = 1;
}
这个调用不返回任何数据。
返回所有目前 active 状态的 container 标识符。
containers > containerizer::Containers
这个调用不通过 stdin 接收任何额外的数据。
此调用有望通过 stdout ,再经过 " containerizer::Containers "返回。
/**
* containerizer 将所有活跃的容器信息返回给 slave.
*/
message Containers {
repeated ContainerID containers = 1;
}
ECP 内部状态恢复
允许 ECP 对自己的状态进行恢复。如果 ECP 使用 check-pointing 定期保存状态,例如通过文件系统。则此调用将会是很好的时机来反序列化状态信息来进行恢复。更多的信息请参考 slave 恢复概述
recover
此调用不会通过 stdin 接收任何额外数据。也不通过 stdout 返回数据。
更多以上提及的 protobufs 和 Protobuf Message 在新版本的引用是否雷同。请检查:
- containerizer::XXX 在 include/mesos/containerizer/containerizer.proto 中定义。
- mesos::XXX 在 include/mesos/mesos.proto 中定义。
一个沙盒环境由 cd 命令进入到执行者的工作目录以及 stderr 重定向到执行者的 " strerr " 日志 目录。注意:不是所有的执行都有一个完整的沙盒环境。
其他的环境变量
此外,当调用 ECP 的时候,要设置一些新的环境变量。
- MESOS_LIBEXEC_DIRECTORY = path to mesos-executor, mesos-usage, … 这些信息总是存在的。
- MESOS_WORK_DIRECTORY = slave work directory. 此变量被用来区分 slave 实例。这些信息同样也总是存在的。
注意,将一组容器打包到一个 slave 实例,从而在需要的时候可以恢复。这点是很有帮助性的。
- MESOS_DEFAULT_CONTAINER_IMAGE = 默认的镜像通过 slave 的标志位设置来提供( default_container_image )。只是在调用 launch 时需要被提供。
为了能够从 EC 接收到一个高等级的状态信息,需要使用 GLOG 详细等级.在 Mesos 启动前,通过调用并设置为大于等于二来增加日记管理级别。
GLOG_v=2 ./bin/mesos-slave --master=[...]
所有输出到你的 ECP 都将记录到执行者的 " stderr " 日志文件。该特定的目录可以从 EC 的 增强的详细日志记录 中被提取出来。
日志输出例子:
I0603 02:12:34.165662 174215168 external_containerizer.cpp:1083] Invoking external containerizer for method 'launch'
I0603 02:12:34.165675 174215168 external_containerizer.cpp:1100] calling: [/Users/till/Development/mesos-till/build/src/test-containerizer launch]
I0603 02:12:34.165678 175824896 slave.cpp:497] Successfully attached file '/tmp/ExternalContainerizerTest_Launch_lP22ci/slaves/20140603-021232-16777343-51377-7591-0/frameworks/20140603-021232-16777343-51377-7591-0000/executors/1/runs/558e0a69-70da-4d71-b4c4-c2820b1d6345'
I0603 02:12:34.165686 174215168 external_containerizer.cpp:1101] directory: /tmp/ExternalContainerizerTest_Launch_lP22ci/slaves/20140603-021232-16777343-51377-7591-0/frameworks/20140603-021232-16777343-51377-7591-0000/executors/1/runs/558e0a69-70da-4d71-b4c4-c2820b1d6345
ECP 针对该调用的 stderr 输出 可以在路径位置显示在日志的最后的标注行的 stderr 文件中找到:
cat /tmp/ExternalContainerizerTest_Launch_lP22ci/slaves/20140603-021232-16777343-51377-7591-0/frameworks/20140603-021232-16777343-51377-7591-0000/executors/1/runs/558e0a69-70da-4d71-b4c4-c2820b1d6345/stderr
这里显示一个完整的 record-io 格式化的 protobuf.
- length: 00 - 03 = record length in byte
- payload: 04 - (length + 4) = protobuf payload
长度样例: 00000240h = 576 byte 整个 protobuf 大小
十六进制格式样例
00000000: 4002 0000 0a26 0a24 3433 3532 3533 6162 2d64 3234 362d 3437 :@....&.$435253ab-d246-47
00000018: 6265 2d61 3335 302d 3335 3432 3034 3635 6438 3638 1a81 020a :be-a350-35420465d868....
00000030: 030a 0131 2a16 0a04 6370 7573 1000 1a09 0900 0000 0000 0000 :...1*...cpus............
00000048: 4032 012a 2a15 0a03 6d65 6d10 001a 0909 0000 0000 0000 9040 :@2.**...mem............@
00000060: 3201 2a2a 160a 0464 6973 6b10 001a 0909 0000 0000 0000 9040 :2.**...disk............@
00000078: 3201 2a2a 180a 0570 6f72 7473 1001 220a 0a08 0898 f201 1080 :2.**...ports..".........
00000090: fa01 3201 2a3a 2a1a 2865 6368 6f20 274e 6f20 7375 6368 2066 :..2.*:*.(echo 'No such f
000000a8: 696c 6520 6f72 2064 6972 6563 746f 7279 273b 2065 7869 7420 :ile or directory'; exit
000000c0: 3142 2b0a 2932 3031 3430 3532 362d 3031 3530 3036 2d31 3637 :1B+.)20140526-015006-167
000000d8: 3737 3334 332d 3535 3430 332d 3632 3536 372d 3030 3030 4a3d :77343-55403-62567-0000J=
000000f0: 436f 6d6d 616e 6420 4578 6563 7574 6f72 2028 5461 736b 3a20 :Command Executor (Task:
00000108: 3129 2028 436f 6d6d 616e 643a 2073 6820 2d63 2027 7768 696c :1) (Command: sh -c 'whil
00000120: 6520 7472 7565 203b 2e2e 2e27 2952 0131 22c5 012f 746d 702f :e true ;...')R.1"../tmp/
00000138: 4578 7465 726e 616c 436f 6e74 6169 6e65 7269 7a65 7254 6573 :ExternalContainerizerTes
00000150: 745f 4c61 756e 6368 5f6c 5855 6839 662f 736c 6176 6573 2f32 :t_Launch_lXUh9f/slaves/2
00000168: 3031 3430 3532 362d 3031 3530 3036 2d31 3637 3737 3334 332d :0140526-015006-16777343-
00000180: 3535 3430 332d 3632 3536 372d 302f 6672 616d 6577 6f72 6b73 :55403-62567-0/frameworks
00000198: 2f32 3031 3430 3532 362d 3031 3530 3036 2d31 3637 3737 3334 :/20140526-015006-1677734
000001b0: 332d 3535 3430 332d 3632 3536 372d 3030 3030 2f65 7865 6375 :3-55403-62567-0000/execu
000001c8: 746f 7273 2f31 2f72 756e 732f 3433 3532 3533 6162 2d64 3234 :tors/1/runs/435253ab-d24
000001e0: 362d 3437 6265 2d61 3335 302d 3335 3432 3034 3635 6438 3638 :6-47be-a350-35420465d868
000001f8: 2a04 7469 6c6c 3228 0a26 3230 3134 3035 3236 2d30 3135 3030 :*.till2(.&20140526-01500
00000210: 362d 3136 3737 3733 3433 2d35 3534 3033 2d36 3235 3637 2d30 :6-16777343-55403-62567-0
00000228: 3a18 736c 6176 6528 3129 4031 3237 2e30 2e30 2e31 3a35 3534 ::.slave(1)@127.0.0.1:554
00000240: 3033 4000
用 Python 如何发送和接送 record-io 格式化的信息 代码片段来自于 src/examples/python/test_containerizer.py
#Read a data chunk prefixed by its total size from stdin.
def receive():
# Read size (uint32 => 4 bytes).
size = struct.unpack('I', sys.stdin.read(4))
if size[0] <= 0:
print >> sys.stderr, "Expected protobuf size over stdin. " \
"Received 0 bytes."
return ""
# Read payload.
data = sys.stdin.read(size[0])
if len(data) != size[0]:
print >> sys.stderr, "Expected %d bytes protobuf over stdin. " \
"Received %d bytes." % (size[0], len(data))
return ""
return data
# Write a protobuf message prefixed by its total size (aka recordio)
# to stdout.
def send(data):
# Write size (uint32 => 4 bytes).
sys.stdout.write(struct.pack('I', len(data)))
# Write payload.
sys.stdout.write(data)