gitee.com/leisunstar/runtime@v0.0.0-20200521203717-5cef3e7b53f9/virtcontainers/pkg/cgroups/manager.go (about) 1 // Copyright (c) 2020 Intel Corporation 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 // 5 6 package cgroups 7 8 import ( 9 "bufio" 10 "fmt" 11 "io/ioutil" 12 "os" 13 "path/filepath" 14 "strconv" 15 "strings" 16 "sync" 17 18 "github.com/kata-containers/runtime/virtcontainers/pkg/rootless" 19 libcontcgroups "github.com/opencontainers/runc/libcontainer/cgroups" 20 libcontcgroupsfs "github.com/opencontainers/runc/libcontainer/cgroups/fs" 21 libcontcgroupssystemd "github.com/opencontainers/runc/libcontainer/cgroups/systemd" 22 "github.com/opencontainers/runc/libcontainer/configs" 23 "github.com/opencontainers/runc/libcontainer/specconv" 24 "github.com/opencontainers/runtime-spec/specs-go" 25 "github.com/sirupsen/logrus" 26 ) 27 28 type Config struct { 29 // Cgroups specifies specific cgroup settings for the various subsystems that the container is 30 // placed into to limit the resources the container has available 31 // If nil, New() will create one. 32 Cgroups *configs.Cgroup 33 34 // CgroupPaths contains paths to all the cgroups setup for a container. Key is cgroup subsystem name 35 // with the value as the path. 36 CgroupPaths map[string]string 37 38 // Resources represents the runtime resource constraints 39 Resources specs.LinuxResources 40 41 // CgroupPath is the OCI spec cgroup path 42 CgroupPath string 43 } 44 45 type Manager struct { 46 sync.Mutex 47 mgr libcontcgroups.Manager 48 } 49 50 const ( 51 // file in the cgroup that contains the pids 52 cgroupProcs = "cgroup.procs" 53 ) 54 55 var ( 56 // If set to true, expects cgroupsPath to be of form "slice:prefix:name", otherwise cgroups creation will fail 57 systemdCgroup *bool 58 59 cgroupsLogger = logrus.WithField("source", "virtcontainers/pkg/cgroups") 60 ) 61 62 func EnableSystemdCgroup() { 63 systemd := true 64 systemdCgroup = &systemd 65 } 66 67 func UseSystemdCgroup() bool { 68 if systemdCgroup != nil { 69 return *systemdCgroup 70 } 71 return false 72 } 73 74 // returns the list of devices that a hypervisor may need 75 func hypervisorDevices() []specs.LinuxDeviceCgroup { 76 devices := []specs.LinuxDeviceCgroup{} 77 78 // Processes running in a device-cgroup are constrained, they have acccess 79 // only to the devices listed in the devices.list file. 80 // In order to run Virtual Machines and create virtqueues, hypervisors 81 // need access to certain character devices in the host, like kvm and vhost-net. 82 hypervisorDevices := []string{ 83 "/dev/kvm", // To run virtual machines 84 "/dev/vhost-net", // To create virtqueues 85 "/dev/vfio/vfio", // To access VFIO devices 86 } 87 88 for _, device := range hypervisorDevices { 89 ldevice, err := DeviceToLinuxDevice(device) 90 if err != nil { 91 cgroupsLogger.WithError(err).Warnf("Could not get device information") 92 continue 93 } 94 devices = append(devices, ldevice) 95 } 96 97 return devices 98 } 99 100 // New creates a new CgroupManager 101 func New(config *Config) (*Manager, error) { 102 var err error 103 useSystemdCgroup := UseSystemdCgroup() 104 105 devices := config.Resources.Devices 106 devices = append(devices, hypervisorDevices()...) 107 // Do not modify original devices 108 config.Resources.Devices = devices 109 110 newSpec := specs.Spec{ 111 Linux: &specs.Linux{ 112 Resources: &config.Resources, 113 }, 114 } 115 116 rootless := rootless.IsRootless() 117 118 cgroups := config.Cgroups 119 cgroupPaths := config.CgroupPaths 120 121 // Create a new cgroup if the current one is nil 122 // this cgroups must be saved later 123 if cgroups == nil { 124 if config.CgroupPath == "" && !rootless { 125 cgroupsLogger.Warn("cgroups have not been created and cgroup path is empty") 126 } 127 128 newSpec.Linux.CgroupsPath, err = ValidCgroupPath(config.CgroupPath, useSystemdCgroup) 129 if err != nil { 130 return nil, fmt.Errorf("Invalid cgroup path: %v", err) 131 } 132 133 if cgroups, err = specconv.CreateCgroupConfig(&specconv.CreateOpts{ 134 // cgroup name is taken from spec 135 CgroupName: "", 136 UseSystemdCgroup: useSystemdCgroup, 137 Spec: &newSpec, 138 RootlessCgroups: rootless, 139 }); err != nil { 140 return nil, fmt.Errorf("Could not create cgroup config: %v", err) 141 } 142 } 143 144 // Set cgroupPaths to nil when the map is empty, it can and will be 145 // populated by `Manager.Apply()` when the runtime or any other process 146 // is moved to the cgroup. 147 if len(cgroupPaths) == 0 { 148 cgroupPaths = nil 149 } 150 151 if useSystemdCgroup { 152 systemdCgroupFunc, err := libcontcgroupssystemd.NewSystemdCgroupsManager() 153 if err != nil { 154 return nil, fmt.Errorf("Could not create systemd cgroup manager: %v", err) 155 } 156 libcontcgroupssystemd.UseSystemd() 157 return &Manager{ 158 mgr: systemdCgroupFunc(cgroups, cgroupPaths), 159 }, nil 160 } 161 162 return &Manager{ 163 mgr: &libcontcgroupsfs.Manager{ 164 Cgroups: cgroups, 165 Rootless: rootless, 166 Paths: cgroupPaths, 167 }, 168 }, nil 169 } 170 171 // read all the pids in cgroupPath 172 func readPids(cgroupPath string) ([]int, error) { 173 pids := []int{} 174 f, err := os.Open(filepath.Join(cgroupPath, cgroupProcs)) 175 if err != nil { 176 return nil, err 177 } 178 defer f.Close() 179 buf := bufio.NewScanner(f) 180 181 for buf.Scan() { 182 if t := buf.Text(); t != "" { 183 pid, err := strconv.Atoi(t) 184 if err != nil { 185 return nil, err 186 } 187 pids = append(pids, pid) 188 } 189 } 190 return pids, nil 191 } 192 193 // write the pids into cgroup.procs 194 func writePids(pids []int, cgroupPath string) error { 195 cgroupProcsPath := filepath.Join(cgroupPath, cgroupProcs) 196 for _, pid := range pids { 197 if err := ioutil.WriteFile(cgroupProcsPath, 198 []byte(strconv.Itoa(pid)), 199 os.FileMode(0), 200 ); err != nil { 201 return err 202 } 203 } 204 return nil 205 } 206 207 func (m *Manager) logger() *logrus.Entry { 208 return cgroupsLogger.WithField("source", "cgroup-manager") 209 } 210 211 // move all the processes in the current cgroup to the parent 212 func (m *Manager) moveToParent() error { 213 m.Lock() 214 defer m.Unlock() 215 for _, cgroupPath := range m.mgr.GetPaths() { 216 pids, err := readPids(cgroupPath) 217 if err != nil { 218 return err 219 } 220 221 if len(pids) == 0 { 222 // no pids in this cgroup 223 continue 224 } 225 226 cgroupParentPath := filepath.Dir(filepath.Clean(cgroupPath)) 227 if err = writePids(pids, cgroupParentPath); err != nil { 228 if !strings.Contains(err.Error(), "no such process") { 229 return err 230 } 231 } 232 } 233 return nil 234 } 235 236 // Add pid to cgroups 237 func (m *Manager) Add(pid int) error { 238 if rootless.IsRootless() { 239 m.logger().Debug("Unable to setup add pids to cgroup: running rootless") 240 return nil 241 } 242 243 m.Lock() 244 defer m.Unlock() 245 return m.mgr.Apply(pid) 246 } 247 248 // Apply constraints 249 func (m *Manager) Apply() error { 250 if rootless.IsRootless() { 251 m.logger().Debug("Unable to apply constraints: running rootless") 252 return nil 253 } 254 255 cgroups, err := m.GetCgroups() 256 if err != nil { 257 return err 258 } 259 260 m.Lock() 261 defer m.Unlock() 262 return m.mgr.Set(&configs.Config{ 263 Cgroups: cgroups, 264 }) 265 } 266 267 func (m *Manager) GetCgroups() (*configs.Cgroup, error) { 268 m.Lock() 269 defer m.Unlock() 270 return m.mgr.GetCgroups() 271 } 272 273 func (m *Manager) GetPaths() map[string]string { 274 m.Lock() 275 defer m.Unlock() 276 return m.mgr.GetPaths() 277 } 278 279 func (m *Manager) Destroy() error { 280 // cgroup can't be destroyed if it contains running processes 281 if err := m.moveToParent(); err != nil { 282 return fmt.Errorf("Could not move processes into parent cgroup: %v", err) 283 } 284 285 m.Lock() 286 defer m.Unlock() 287 return m.mgr.Destroy() 288 } 289 290 // AddDevice adds a device to the device cgroup 291 func (m *Manager) AddDevice(device string) error { 292 cgroups, err := m.GetCgroups() 293 if err != nil { 294 return err 295 } 296 297 ld, err := DeviceToCgroupDevice(device) 298 if err != nil { 299 return err 300 } 301 302 m.Lock() 303 cgroups.Devices = append(cgroups.Devices, ld) 304 m.Unlock() 305 306 return m.Apply() 307 } 308 309 // RemoceDevice removed a device from the device cgroup 310 func (m *Manager) RemoveDevice(device string) error { 311 cgroups, err := m.GetCgroups() 312 if err != nil { 313 return err 314 } 315 316 m.Lock() 317 for i, d := range cgroups.Devices { 318 if d.Path == device { 319 cgroups.Devices = append(cgroups.Devices[:i], cgroups.Devices[i+1:]...) 320 m.Unlock() 321 return m.Apply() 322 } 323 } 324 m.Unlock() 325 return fmt.Errorf("device %v not found in the cgroup", device) 326 }