作者您好,我是《自己动手写Docker》的一名读者,也是蚂蚁的一名校招生(花名昼梦,之前实习过一段时间),现在是大四,最近正在尝试自己实现一个类似于runC的容器运行时,但是在实现cgroups时遇到了一些问题。

    • 首先描述一下parent process(runc run进程)和child process(runc init进程)的交互:

    在parent process执行了cmd.Start(),拿到了init process的PID,然后将处理Cgroups,以memory subsystem为例,会将创建一个/sys/fs/cgroup/memory/$container_id这样一个目录,然后在tasks文件中写入PID,在memory.limit_in_bytes中写入限制的内存大小。然后处理其他的初始化,最后将InitConfig通过管道传输给init process。init process拿到config后会执行一系列的初始化,比如pivot_root等,最后发给parent process一个信号,通知父进程自己已经初始化完毕了,等待执行syscall.Exec(即真正的命令),然后parent process在收到该信号后,如果是run操作(即create+start),再发一个信号给init process,让init process执行最后的syscall.Exec。

    • 我遇到的问题是,在tasks写入的PID,在init process执行syscall.Exec之前都是存在的,但是在init process执行syscall.Exec之后,这个cgroup目录还存在,但是tasks文件会变为空,并且内存限制也没有生效。
    • 相关的代码:
      • parent process:

    https://github.com/songxinjianqwe/rune/blob/master/libcapsule/parent_process_init_impl.go

    • init process:

    https://github.com/songxinjianqwe/rune/blob/master/libcapsule/initializer_standard_impl.go

    这个程序内存限制是生效的,并且tasks文件在syscall.Exec之后也不是空的。

    • 我推测可能跟我代码中一些其他的初始化操作有关。

    • 我自己一直以来是做Java开发的,对Linux编程不是特别熟悉,但是实习期间接触了云计算的一些知识,产生了一些兴趣,打算把手写runC作为本科毕设。

    • 我的钉钉二维码在下面,希望能和您有深入的交流。

    image.png

    补充一点,在stress进程(syscall.Exec)启动后,在tasks文件为空的情况下,我手动给tasks文件写入了PID,但内存限制仍未生效(但是memory.limit_in_bytes里面是写成功了的)。

    如果您想自己运行一下的话,可以使用类似于runC的方式,建一个目录,目录里放一个rootfs目录(里面是容器rootfs),还有一个config.json文件。

    1. /mycontainer/
    2. ├── rootfs/
    3. ├── bin -> usr/bin/
    4. ├── dev/
    5. ├── etc/
    6. ├── home/
    7. ├── lib -> usr/lib/
    8. ├── lib64 -> usr/lib64/
    9. ├── media/
    10. ├── mnt/
    11. ├── opt/
    12. ├── proc/
    13. ├── root/
    14. ├── run/
    15. ├── sbin -> usr/sbin/
    16. ├── srv/
    17. ├── sys/
    18. ├── tmp/
    19. ├── usr/
    20. └── var/
    21. ├── config.json
    1. {
    2. "ociVersion": "1.0.1-dev",
    3. "process": {
    4. "user": {
    5. "uid": 0,
    6. "gid": 0
    7. },
    8. "args": [
    9. "stress",
    10. "--vm-bytes",
    11. "256m",
    12. "--vm-keep",
    13. "-m",
    14. "1"
    15. ],
    16. "env": [
    17. "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
    18. "TERM=xterm"
    19. ],
    20. "cwd": "/"
    21. },
    22. "root": {
    23. "path": "rootfs",
    24. "readonly": true
    25. },
    26. "hostname": "rune",
    27. "mounts": [
    28. {
    29. "destination": "/proc",
    30. "type": "proc",
    31. "source": "proc"
    32. },
    33. {
    34. "destination": "/dev",
    35. "type": "tmpfs",
    36. "source": "tmpfs",
    37. "options": [
    38. "nosuid",
    39. "strictatime",
    40. "mode=755",
    41. "size=65536k"
    42. ]
    43. },
    44. {
    45. "destination": "/dev/pts",
    46. "type": "devpts",
    47. "source": "devpts",
    48. "options": [
    49. "nosuid",
    50. "noexec",
    51. "newinstance",
    52. "ptmxmode=0666",
    53. "mode=0620",
    54. "gid=5"
    55. ]
    56. },
    57. {
    58. "destination": "/dev/shm",
    59. "type": "tmpfs",
    60. "source": "shm",
    61. "options": [
    62. "nosuid",
    63. "noexec",
    64. "nodev",
    65. "mode=1777",
    66. "size=65536k"
    67. ]
    68. },
    69. {
    70. "destination": "/dev/mqueue",
    71. "type": "mqueue",
    72. "source": "mqueue",
    73. "options": [
    74. "nosuid",
    75. "noexec",
    76. "nodev"
    77. ]
    78. },
    79. {
    80. "destination": "/sys",
    81. "type": "sysfs",
    82. "source": "sysfs",
    83. "options": [
    84. "nosuid",
    85. "noexec",
    86. "nodev",
    87. "ro"
    88. ]
    89. }
    90. ],
    91. "linux": {
    92. "resources": {
    93. "devices": [
    94. {
    95. "allow": false,
    96. "access": "rwm"
    97. }
    98. ],
    99. "memory": {
    100. "limit": 102400
    101. },
    102. "cpu": {
    103. "shares": 10
    104. }
    105. },
    106. "namespaces": [
    107. {
    108. "type": "pid"
    109. },
    110. {
    111. "type": "network"
    112. },
    113. {
    114. "type": "ipc"
    115. },
    116. {
    117. "type": "uts"
    118. },
    119. {
    120. "type": "mount"
    121. }
    122. ]
    123. }
    124. }

    您好,我在阅读runC的代码时发现cgroup manager#apply时有时间会将PID写入到tasks文件,有时候会写入到cgroup.procs文件中,于是我尝试将代码改为写入到cgroup.procs中,问题就解决了。

    tasks: list of tasks (by PID) attached to that cgroup. This list is not guaranteed to be sorted. Writing a thread ID into this file moves the thread into this cgroup. cgroup.procs: list of thread group IDs in the cgroup. This list is not guaranteed to be sorted or free of duplicate TGIDs, and userspace should sort/uniquify the list if this property is required. Writing a thread group ID into this file moves all threads in that group into this cgroup.

    runC代码地址https://github.com/opencontainers/runc/blob/master/libcontainer/cgroups/fs/apply_raw.go
    感谢您的阅读。其实我提问还是着急了一些,自己多研究一下还是可以解决大部分问题的。