sysinfo_linux.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. package sysinfo // import "github.com/docker/docker/pkg/sysinfo"
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "path"
  7. "strings"
  8. "sync"
  9. "github.com/containerd/cgroups/v3"
  10. "github.com/containerd/cgroups/v3/cgroup1"
  11. "github.com/containerd/containerd/pkg/seccomp"
  12. "github.com/containerd/log"
  13. "github.com/moby/sys/mountinfo"
  14. )
  15. var (
  16. readMountinfoOnce sync.Once
  17. readMountinfoErr error
  18. cgroupMountinfo []*mountinfo.Info
  19. )
  20. // readCgroupMountinfo returns a list of cgroup v1 mounts (i.e. the ones
  21. // with fstype of "cgroup") for the current running process.
  22. //
  23. // The results are cached (to avoid re-reading mountinfo which is relatively
  24. // expensive), so it is assumed that cgroup mounts are not being changed.
  25. func readCgroupMountinfo() ([]*mountinfo.Info, error) {
  26. readMountinfoOnce.Do(func() {
  27. cgroupMountinfo, readMountinfoErr = mountinfo.GetMounts(
  28. mountinfo.FSTypeFilter("cgroup"),
  29. )
  30. })
  31. return cgroupMountinfo, readMountinfoErr
  32. }
  33. func findCgroupV1Mountpoints() (map[string]string, error) {
  34. mounts, err := readCgroupMountinfo()
  35. if err != nil {
  36. return nil, err
  37. }
  38. allSubsystems, err := cgroup1.ParseCgroupFile("/proc/self/cgroup")
  39. if err != nil {
  40. return nil, fmt.Errorf("Failed to parse cgroup information: %v", err)
  41. }
  42. allMap := make(map[string]bool)
  43. for s := range allSubsystems {
  44. allMap[s] = false
  45. }
  46. mps := make(map[string]string)
  47. for _, mi := range mounts {
  48. for _, opt := range strings.Split(mi.VFSOptions, ",") {
  49. seen, known := allMap[opt]
  50. if known && !seen {
  51. allMap[opt] = true
  52. mps[strings.TrimPrefix(opt, "name=")] = mi.Mountpoint
  53. }
  54. }
  55. if len(mps) >= len(allMap) {
  56. break
  57. }
  58. }
  59. return mps, nil
  60. }
  61. type infoCollector func(info *SysInfo)
  62. // WithCgroup2GroupPath specifies the cgroup v2 group path to inspect availability
  63. // of the controllers.
  64. //
  65. // WithCgroup2GroupPath is expected to be used for rootless mode with systemd driver.
  66. //
  67. // e.g. g = "/user.slice/user-1000.slice/user@1000.service"
  68. func WithCgroup2GroupPath(g string) Opt {
  69. return func(o *SysInfo) {
  70. if p := path.Clean(g); p != "" {
  71. o.cg2GroupPath = p
  72. }
  73. }
  74. }
  75. // New returns a new SysInfo, using the filesystem to detect which features
  76. // the kernel supports.
  77. func New(options ...Opt) *SysInfo {
  78. if cgroups.Mode() == cgroups.Unified {
  79. return newV2(options...)
  80. }
  81. return newV1()
  82. }
  83. func newV1() *SysInfo {
  84. var (
  85. err error
  86. sysInfo = &SysInfo{}
  87. )
  88. ops := []infoCollector{
  89. applyNetworkingInfo,
  90. applyAppArmorInfo,
  91. applySeccompInfo,
  92. applyCgroupNsInfo,
  93. }
  94. sysInfo.cgMounts, err = findCgroupV1Mountpoints()
  95. if err != nil {
  96. log.G(context.TODO()).Warn(err)
  97. } else {
  98. ops = append(ops,
  99. applyMemoryCgroupInfo,
  100. applyCPUCgroupInfo,
  101. applyBlkioCgroupInfo,
  102. applyCPUSetCgroupInfo,
  103. applyPIDSCgroupInfo,
  104. applyDevicesCgroupInfo,
  105. )
  106. }
  107. for _, o := range ops {
  108. o(sysInfo)
  109. }
  110. return sysInfo
  111. }
  112. // applyMemoryCgroupInfo adds the memory cgroup controller information to the info.
  113. func applyMemoryCgroupInfo(info *SysInfo) {
  114. mountPoint, ok := info.cgMounts["memory"]
  115. if !ok {
  116. info.Warnings = append(info.Warnings, "Your kernel does not support cgroup memory limit")
  117. return
  118. }
  119. info.MemoryLimit = ok
  120. info.SwapLimit = cgroupEnabled(mountPoint, "memory.memsw.limit_in_bytes")
  121. if !info.SwapLimit {
  122. info.Warnings = append(info.Warnings, "Your kernel does not support swap memory limit")
  123. }
  124. info.MemoryReservation = cgroupEnabled(mountPoint, "memory.soft_limit_in_bytes")
  125. if !info.MemoryReservation {
  126. info.Warnings = append(info.Warnings, "Your kernel does not support memory reservation")
  127. }
  128. info.OomKillDisable = cgroupEnabled(mountPoint, "memory.oom_control")
  129. if !info.OomKillDisable {
  130. info.Warnings = append(info.Warnings, "Your kernel does not support oom control")
  131. }
  132. info.MemorySwappiness = cgroupEnabled(mountPoint, "memory.swappiness")
  133. if !info.MemorySwappiness {
  134. info.Warnings = append(info.Warnings, "Your kernel does not support memory swappiness")
  135. }
  136. // Option is deprecated, but still accepted on API < v1.42 with cgroups v1,
  137. // so setting the field to allow feature detection.
  138. info.KernelMemory = cgroupEnabled(mountPoint, "memory.kmem.limit_in_bytes")
  139. // Option is deprecated in runc, but still accepted in our API, so setting
  140. // the field to allow feature detection, but don't warn if it's missing, to
  141. // make the daemon logs a bit less noisy.
  142. info.KernelMemoryTCP = cgroupEnabled(mountPoint, "memory.kmem.tcp.limit_in_bytes")
  143. }
  144. // applyCPUCgroupInfo adds the cpu cgroup controller information to the info.
  145. func applyCPUCgroupInfo(info *SysInfo) {
  146. mountPoint, ok := info.cgMounts["cpu"]
  147. if !ok {
  148. info.Warnings = append(info.Warnings, "Unable to find cpu cgroup in mounts")
  149. return
  150. }
  151. info.CPUShares = cgroupEnabled(mountPoint, "cpu.shares")
  152. if !info.CPUShares {
  153. info.Warnings = append(info.Warnings, "Your kernel does not support CPU shares")
  154. }
  155. info.CPUCfs = cgroupEnabled(mountPoint, "cpu.cfs_quota_us")
  156. if !info.CPUCfs {
  157. info.Warnings = append(info.Warnings, "Your kernel does not support CPU CFS scheduler")
  158. }
  159. info.CPURealtime = cgroupEnabled(mountPoint, "cpu.rt_period_us")
  160. if !info.CPURealtime {
  161. info.Warnings = append(info.Warnings, "Your kernel does not support CPU realtime scheduler")
  162. }
  163. }
  164. // applyBlkioCgroupInfo adds the blkio cgroup controller information to the info.
  165. func applyBlkioCgroupInfo(info *SysInfo) {
  166. mountPoint, ok := info.cgMounts["blkio"]
  167. if !ok {
  168. info.Warnings = append(info.Warnings, "Unable to find blkio cgroup in mounts")
  169. return
  170. }
  171. info.BlkioWeight = cgroupEnabled(mountPoint, "blkio.weight")
  172. if !info.BlkioWeight {
  173. info.Warnings = append(info.Warnings, "Your kernel does not support cgroup blkio weight")
  174. }
  175. info.BlkioWeightDevice = cgroupEnabled(mountPoint, "blkio.weight_device")
  176. if !info.BlkioWeightDevice {
  177. info.Warnings = append(info.Warnings, "Your kernel does not support cgroup blkio weight_device")
  178. }
  179. info.BlkioReadBpsDevice = cgroupEnabled(mountPoint, "blkio.throttle.read_bps_device")
  180. if !info.BlkioReadBpsDevice {
  181. info.Warnings = append(info.Warnings, "Your kernel does not support cgroup blkio throttle.read_bps_device")
  182. }
  183. info.BlkioWriteBpsDevice = cgroupEnabled(mountPoint, "blkio.throttle.write_bps_device")
  184. if !info.BlkioWriteBpsDevice {
  185. info.Warnings = append(info.Warnings, "Your kernel does not support cgroup blkio throttle.write_bps_device")
  186. }
  187. info.BlkioReadIOpsDevice = cgroupEnabled(mountPoint, "blkio.throttle.read_iops_device")
  188. if !info.BlkioReadIOpsDevice {
  189. info.Warnings = append(info.Warnings, "Your kernel does not support cgroup blkio throttle.read_iops_device")
  190. }
  191. info.BlkioWriteIOpsDevice = cgroupEnabled(mountPoint, "blkio.throttle.write_iops_device")
  192. if !info.BlkioWriteIOpsDevice {
  193. info.Warnings = append(info.Warnings, "Your kernel does not support cgroup blkio throttle.write_iops_device")
  194. }
  195. }
  196. // applyCPUSetCgroupInfo adds the cpuset cgroup controller information to the info.
  197. func applyCPUSetCgroupInfo(info *SysInfo) {
  198. mountPoint, ok := info.cgMounts["cpuset"]
  199. if !ok {
  200. info.Warnings = append(info.Warnings, "Unable to find cpuset cgroup in mounts")
  201. return
  202. }
  203. info.Cpuset = ok
  204. var err error
  205. cpus, err := os.ReadFile(path.Join(mountPoint, "cpuset.cpus"))
  206. if err != nil {
  207. return
  208. }
  209. info.Cpus = strings.TrimSpace(string(cpus))
  210. mems, err := os.ReadFile(path.Join(mountPoint, "cpuset.mems"))
  211. if err != nil {
  212. return
  213. }
  214. info.Mems = strings.TrimSpace(string(mems))
  215. }
  216. // applyPIDSCgroupInfo adds whether the pids cgroup controller is available to the info.
  217. func applyPIDSCgroupInfo(info *SysInfo) {
  218. _, ok := info.cgMounts["pids"]
  219. if !ok {
  220. info.Warnings = append(info.Warnings, "Unable to find pids cgroup in mounts")
  221. return
  222. }
  223. info.PidsLimit = true
  224. }
  225. // applyDevicesCgroupInfo adds whether the devices cgroup controller is available to the info.
  226. func applyDevicesCgroupInfo(info *SysInfo) {
  227. _, ok := info.cgMounts["devices"]
  228. info.CgroupDevicesEnabled = ok
  229. }
  230. // applyNetworkingInfo adds networking information to the info.
  231. func applyNetworkingInfo(info *SysInfo) {
  232. info.IPv4ForwardingDisabled = !readProcBool("/proc/sys/net/ipv4/ip_forward")
  233. info.BridgeNFCallIPTablesDisabled = !readProcBool("/proc/sys/net/bridge/bridge-nf-call-iptables")
  234. info.BridgeNFCallIP6TablesDisabled = !readProcBool("/proc/sys/net/bridge/bridge-nf-call-ip6tables")
  235. }
  236. // applyAppArmorInfo adds whether AppArmor is enabled to the info.
  237. func applyAppArmorInfo(info *SysInfo) {
  238. if _, err := os.Stat("/sys/kernel/security/apparmor"); !os.IsNotExist(err) {
  239. if _, err := os.ReadFile("/sys/kernel/security/apparmor/profiles"); err == nil {
  240. info.AppArmor = true
  241. }
  242. }
  243. }
  244. // applyCgroupNsInfo adds whether cgroupns is enabled to the info.
  245. func applyCgroupNsInfo(info *SysInfo) {
  246. if _, err := os.Stat("/proc/self/ns/cgroup"); !os.IsNotExist(err) {
  247. info.CgroupNamespaces = true
  248. }
  249. }
  250. // applySeccompInfo checks if Seccomp is supported, via CONFIG_SECCOMP.
  251. func applySeccompInfo(info *SysInfo) {
  252. info.Seccomp = seccomp.IsEnabled()
  253. }
  254. func cgroupEnabled(mountPoint, name string) bool {
  255. _, err := os.Stat(path.Join(mountPoint, name))
  256. return err == nil
  257. }
  258. func readProcBool(path string) bool {
  259. val, err := os.ReadFile(path)
  260. if err != nil {
  261. return false
  262. }
  263. return strings.TrimSpace(string(val)) == "1"
  264. }