github.com/huiliang/nomad@v0.2.1-0.20151124023127-7a8b664699ff/client/driver/executor/exec_linux.go (about) 1 package executor 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "os" 9 "os/exec" 10 "os/user" 11 "path/filepath" 12 "strconv" 13 "strings" 14 "syscall" 15 16 "github.com/hashicorp/go-multierror" 17 "github.com/hashicorp/nomad/client/allocdir" 18 "github.com/hashicorp/nomad/client/driver/args" 19 "github.com/hashicorp/nomad/client/driver/environment" 20 "github.com/hashicorp/nomad/client/driver/spawn" 21 "github.com/hashicorp/nomad/nomad/structs" 22 23 "github.com/opencontainers/runc/libcontainer/cgroups" 24 cgroupFs "github.com/opencontainers/runc/libcontainer/cgroups/fs" 25 "github.com/opencontainers/runc/libcontainer/cgroups/systemd" 26 cgroupConfig "github.com/opencontainers/runc/libcontainer/configs" 27 28 cstructs "github.com/hashicorp/nomad/client/driver/structs" 29 ) 30 31 var ( 32 // A mapping of directories on the host OS to attempt to embed inside each 33 // task's chroot. 34 chrootEnv = map[string]string{ 35 "/bin": "/bin", 36 "/etc": "/etc", 37 "/lib": "/lib", 38 "/lib32": "/lib32", 39 "/lib64": "/lib64", 40 "/usr/bin": "/usr/bin", 41 "/usr/lib": "/usr/lib", 42 } 43 ) 44 45 func NewExecutor() Executor { 46 return NewLinuxExecutor() 47 } 48 49 func NewLinuxExecutor() Executor { 50 return &LinuxExecutor{} 51 } 52 53 // Linux executor is designed to run on linux kernel 2.8+. 54 type LinuxExecutor struct { 55 cmd exec.Cmd 56 user *user.User 57 58 // Isolation configurations. 59 groups *cgroupConfig.Cgroup 60 taskName string 61 taskDir string 62 allocDir string 63 64 // Spawn process. 65 spawn *spawn.Spawner 66 } 67 68 func (e *LinuxExecutor) Command() *exec.Cmd { 69 return &e.cmd 70 } 71 72 func (e *LinuxExecutor) Limit(resources *structs.Resources) error { 73 if resources == nil { 74 return errNoResources 75 } 76 77 return e.configureCgroups(resources) 78 } 79 80 // execLinuxID contains the necessary information to reattach to an executed 81 // process and cleanup the created cgroups. 82 type ExecLinuxID struct { 83 Groups *cgroupConfig.Cgroup 84 Spawn *spawn.Spawner 85 TaskDir string 86 } 87 88 func (e *LinuxExecutor) Open(id string) error { 89 // De-serialize the ID. 90 dec := json.NewDecoder(strings.NewReader(id)) 91 var execID ExecLinuxID 92 if err := dec.Decode(&execID); err != nil { 93 return fmt.Errorf("Failed to parse id: %v", err) 94 } 95 96 // Setup the executor. 97 e.groups = execID.Groups 98 e.spawn = execID.Spawn 99 e.taskDir = execID.TaskDir 100 return e.spawn.Valid() 101 } 102 103 func (e *LinuxExecutor) ID() (string, error) { 104 if e.groups == nil || e.spawn == nil || e.taskDir == "" { 105 return "", fmt.Errorf("LinuxExecutor not properly initialized.") 106 } 107 108 // Build the ID. 109 id := ExecLinuxID{ 110 Groups: e.groups, 111 Spawn: e.spawn, 112 TaskDir: e.taskDir, 113 } 114 115 var buffer bytes.Buffer 116 enc := json.NewEncoder(&buffer) 117 if err := enc.Encode(id); err != nil { 118 return "", fmt.Errorf("Failed to serialize id: %v", err) 119 } 120 121 return buffer.String(), nil 122 } 123 124 // runAs takes a user id as a string and looks up the user, and sets the command 125 // to execute as that user. 126 func (e *LinuxExecutor) runAs(userid string) error { 127 u, err := user.Lookup(userid) 128 if err != nil { 129 return fmt.Errorf("Failed to identify user %v: %v", userid, err) 130 } 131 132 // Convert the uid and gid 133 uid, err := strconv.ParseUint(u.Uid, 10, 32) 134 if err != nil { 135 return fmt.Errorf("Unable to convert userid to uint32: %s", err) 136 } 137 gid, err := strconv.ParseUint(u.Gid, 10, 32) 138 if err != nil { 139 return fmt.Errorf("Unable to convert groupid to uint32: %s", err) 140 } 141 142 // Set the command to run as that user and group. 143 if e.cmd.SysProcAttr == nil { 144 e.cmd.SysProcAttr = &syscall.SysProcAttr{} 145 } 146 if e.cmd.SysProcAttr.Credential == nil { 147 e.cmd.SysProcAttr.Credential = &syscall.Credential{} 148 } 149 e.cmd.SysProcAttr.Credential.Uid = uint32(uid) 150 e.cmd.SysProcAttr.Credential.Gid = uint32(gid) 151 152 return nil 153 } 154 155 func (e *LinuxExecutor) Start() error { 156 // Run as "nobody" user so we don't leak root privilege to the spawned 157 // process. 158 if err := e.runAs("nobody"); err != nil { 159 return err 160 } 161 162 // Parse the commands arguments and replace instances of Nomad environment 163 // variables. 164 envVars, err := environment.ParseFromList(e.cmd.Env) 165 if err != nil { 166 return err 167 } 168 169 e.cmd.Path = args.ReplaceEnv(e.cmd.Path, envVars.Map()) 170 e.cmd.Args = args.ParseAndReplace(e.cmd.Args, envVars.Map()) 171 172 spawnState := filepath.Join(e.allocDir, fmt.Sprintf("%s_%s", e.taskName, "exit_status")) 173 e.spawn = spawn.NewSpawner(spawnState) 174 e.spawn.SetCommand(&e.cmd) 175 e.spawn.SetChroot(e.taskDir) 176 e.spawn.SetLogs(&spawn.Logs{ 177 Stdout: filepath.Join(e.taskDir, allocdir.TaskLocal, fmt.Sprintf("%v.stdout", e.taskName)), 178 Stderr: filepath.Join(e.taskDir, allocdir.TaskLocal, fmt.Sprintf("%v.stderr", e.taskName)), 179 Stdin: os.DevNull, 180 }) 181 182 enterCgroup := func(pid int) error { 183 // Join the spawn-daemon to the cgroup. 184 manager := e.getCgroupManager(e.groups) 185 186 // Apply will place the spawn dameon into the created cgroups. 187 if err := manager.Apply(pid); err != nil { 188 return fmt.Errorf("Failed to join spawn-daemon to the cgroup (%+v): %v", e.groups, err) 189 } 190 191 return nil 192 } 193 194 return e.spawn.Spawn(enterCgroup) 195 } 196 197 // Wait waits til the user process exits and returns an error on non-zero exit 198 // codes. Wait also cleans up the task directory and created cgroups. 199 func (e *LinuxExecutor) Wait() *cstructs.WaitResult { 200 errs := new(multierror.Error) 201 res := e.spawn.Wait() 202 if res.Err != nil { 203 errs = multierror.Append(errs, res.Err) 204 } 205 206 if err := e.destroyCgroup(); err != nil { 207 errs = multierror.Append(errs, err) 208 } 209 210 if err := e.cleanTaskDir(); err != nil { 211 errs = multierror.Append(errs, err) 212 } 213 214 res.Err = errs.ErrorOrNil() 215 return res 216 } 217 218 func (e *LinuxExecutor) Shutdown() error { 219 return e.ForceStop() 220 } 221 222 // ForceStop immediately exits the user process and cleans up both the task 223 // directory and the cgroups. 224 func (e *LinuxExecutor) ForceStop() error { 225 errs := new(multierror.Error) 226 if err := e.destroyCgroup(); err != nil { 227 errs = multierror.Append(errs, err) 228 } 229 230 if err := e.cleanTaskDir(); err != nil { 231 errs = multierror.Append(errs, err) 232 } 233 234 return errs.ErrorOrNil() 235 } 236 237 // Task Directory related functions. 238 239 // ConfigureTaskDir creates the necessary directory structure for a proper 240 // chroot. cleanTaskDir should be called after. 241 func (e *LinuxExecutor) ConfigureTaskDir(taskName string, alloc *allocdir.AllocDir) error { 242 e.taskName = taskName 243 e.allocDir = alloc.AllocDir 244 245 taskDir, ok := alloc.TaskDirs[taskName] 246 if !ok { 247 fmt.Errorf("Couldn't find task directory for task %v", taskName) 248 } 249 e.taskDir = taskDir 250 251 if err := alloc.MountSharedDir(taskName); err != nil { 252 return err 253 } 254 255 if err := alloc.Embed(taskName, chrootEnv); err != nil { 256 return err 257 } 258 259 // Mount dev 260 dev := filepath.Join(taskDir, "dev") 261 if !e.pathExists(dev) { 262 if err := os.Mkdir(dev, 0777); err != nil { 263 return fmt.Errorf("Mkdir(%v) failed: %v", dev, err) 264 } 265 266 if err := syscall.Mount("", dev, "devtmpfs", syscall.MS_RDONLY, ""); err != nil { 267 return fmt.Errorf("Couldn't mount /dev to %v: %v", dev, err) 268 } 269 } 270 271 // Mount proc 272 proc := filepath.Join(taskDir, "proc") 273 if !e.pathExists(proc) { 274 if err := os.Mkdir(proc, 0777); err != nil { 275 return fmt.Errorf("Mkdir(%v) failed: %v", proc, err) 276 } 277 278 if err := syscall.Mount("", proc, "proc", syscall.MS_RDONLY, ""); err != nil { 279 return fmt.Errorf("Couldn't mount /proc to %v: %v", proc, err) 280 } 281 } 282 283 // Set the tasks AllocDir environment variable. 284 env, err := environment.ParseFromList(e.cmd.Env) 285 if err != nil { 286 return err 287 } 288 env.SetAllocDir(filepath.Join("/", allocdir.SharedAllocName)) 289 env.SetTaskLocalDir(filepath.Join("/", allocdir.TaskLocal)) 290 e.cmd.Env = env.List() 291 292 return nil 293 } 294 295 // pathExists is a helper function to check if the path exists. 296 func (e *LinuxExecutor) pathExists(path string) bool { 297 if _, err := os.Stat(path); err != nil { 298 if os.IsNotExist(err) { 299 return false 300 } 301 } 302 return true 303 } 304 305 // cleanTaskDir is an idempotent operation to clean the task directory and 306 // should be called when tearing down the task. 307 func (e *LinuxExecutor) cleanTaskDir() error { 308 // Unmount dev. 309 errs := new(multierror.Error) 310 dev := filepath.Join(e.taskDir, "dev") 311 if e.pathExists(dev) { 312 if err := syscall.Unmount(dev, 0); err != nil { 313 errs = multierror.Append(errs, fmt.Errorf("Failed to unmount dev (%v): %v", dev, err)) 314 } 315 316 if err := os.RemoveAll(dev); err != nil { 317 errs = multierror.Append(errs, fmt.Errorf("Failed to delete dev directory (%v): %v", dev, err)) 318 } 319 } 320 321 // Unmount proc. 322 proc := filepath.Join(e.taskDir, "proc") 323 if e.pathExists(proc) { 324 if err := syscall.Unmount(proc, 0); err != nil { 325 errs = multierror.Append(errs, fmt.Errorf("Failed to unmount proc (%v): %v", proc, err)) 326 } 327 328 if err := os.RemoveAll(proc); err != nil { 329 errs = multierror.Append(errs, fmt.Errorf("Failed to delete proc directory (%v): %v", dev, err)) 330 } 331 } 332 333 return errs.ErrorOrNil() 334 } 335 336 // Cgroup related functions. 337 338 // configureCgroups converts a Nomad Resources specification into the equivalent 339 // cgroup configuration. It returns an error if the resources are invalid. 340 func (e *LinuxExecutor) configureCgroups(resources *structs.Resources) error { 341 e.groups = &cgroupConfig.Cgroup{} 342 e.groups.Name = structs.GenerateUUID() 343 344 // TODO: verify this is needed for things like network access 345 e.groups.AllowAllDevices = true 346 347 if resources.MemoryMB > 0 { 348 // Total amount of memory allowed to consume 349 e.groups.Memory = int64(resources.MemoryMB * 1024 * 1024) 350 // Disable swap to avoid issues on the machine 351 e.groups.MemorySwap = int64(-1) 352 } 353 354 if resources.CPU < 2 { 355 return fmt.Errorf("resources.CPU must be equal to or greater than 2: %v", resources.CPU) 356 } 357 358 // Set the relative CPU shares for this cgroup. 359 e.groups.CpuShares = int64(resources.CPU) 360 361 if resources.IOPS != 0 { 362 // Validate it is in an acceptable range. 363 if resources.IOPS < 10 || resources.IOPS > 1000 { 364 return fmt.Errorf("resources.IOPS must be between 10 and 1000: %d", resources.IOPS) 365 } 366 367 e.groups.BlkioWeight = uint16(resources.IOPS) 368 } 369 370 return nil 371 } 372 373 // destroyCgroup kills all processes in the cgroup and removes the cgroup 374 // configuration from the host. 375 func (e *LinuxExecutor) destroyCgroup() error { 376 if e.groups == nil { 377 return errors.New("Can't destroy: cgroup configuration empty") 378 } 379 380 manager := e.getCgroupManager(e.groups) 381 pids, err := manager.GetPids() 382 if err != nil { 383 return fmt.Errorf("Failed to get pids in the cgroup %v: %v", e.groups.Name, err) 384 } 385 386 errs := new(multierror.Error) 387 for _, pid := range pids { 388 process, err := os.FindProcess(pid) 389 if err != nil { 390 multierror.Append(errs, fmt.Errorf("Failed to find Pid %v: %v", pid, err)) 391 continue 392 } 393 394 if err := process.Kill(); err != nil { 395 multierror.Append(errs, fmt.Errorf("Failed to kill Pid %v: %v", pid, err)) 396 continue 397 } 398 } 399 400 // Remove the cgroup. 401 if err := manager.Destroy(); err != nil { 402 multierror.Append(errs, fmt.Errorf("Failed to delete the cgroup directories: %v", err)) 403 } 404 405 if len(errs.Errors) != 0 { 406 return fmt.Errorf("Failed to destroy cgroup: %v", errs) 407 } 408 409 return nil 410 } 411 412 // getCgroupManager returns the correct libcontainer cgroup manager. 413 func (e *LinuxExecutor) getCgroupManager(groups *cgroupConfig.Cgroup) cgroups.Manager { 414 var manager cgroups.Manager 415 manager = &cgroupFs.Manager{Cgroups: groups} 416 if systemd.UseSystemd() { 417 manager = &systemd.Manager{Cgroups: groups} 418 } 419 return manager 420 }