github.com/quite/nomad@v0.8.6/client/driver/lxc.go (about) 1 //+build linux,lxc 2 3 package driver 4 5 import ( 6 "context" 7 "encoding/json" 8 "fmt" 9 "log" 10 "os" 11 "path/filepath" 12 "strconv" 13 "strings" 14 "syscall" 15 "time" 16 17 "github.com/hashicorp/nomad/client/fingerprint" 18 "github.com/hashicorp/nomad/client/stats" 19 "github.com/hashicorp/nomad/helper/fields" 20 "github.com/hashicorp/nomad/nomad/structs" 21 "github.com/mitchellh/mapstructure" 22 23 dstructs "github.com/hashicorp/nomad/client/driver/structs" 24 cstructs "github.com/hashicorp/nomad/client/structs" 25 lxc "gopkg.in/lxc/go-lxc.v2" 26 ) 27 28 const ( 29 // lxcConfigOption is the key for enabling the LXC driver in the 30 // Config.Options map. 31 lxcConfigOption = "driver.lxc.enable" 32 33 // lxcVolumesConfigOption is the key for enabling the use of 34 // custom bind volumes to arbitrary host paths 35 lxcVolumesConfigOption = "lxc.volumes.enabled" 36 lxcVolumesConfigDefault = true 37 38 // containerMonitorIntv is the interval at which the driver checks if the 39 // container is still alive 40 containerMonitorIntv = 2 * time.Second 41 ) 42 43 var ( 44 LXCMeasuredCpuStats = []string{"System Mode", "User Mode", "Percent"} 45 46 LXCMeasuredMemStats = []string{"RSS", "Cache", "Swap", "Max Usage", "Kernel Usage", "Kernel Max Usage"} 47 ) 48 49 // Add the lxc driver to the list of builtin drivers 50 func init() { 51 BuiltinDrivers["lxc"] = NewLxcDriver 52 } 53 54 // LxcDriver allows users to run LXC Containers 55 type LxcDriver struct { 56 DriverContext 57 fingerprint.StaticFingerprinter 58 } 59 60 // LxcDriverConfig is the configuration of the LXC Container 61 type LxcDriverConfig struct { 62 Template string 63 Distro string 64 Release string 65 Arch string 66 ImageVariant string `mapstructure:"image_variant"` 67 ImageServer string `mapstructure:"image_server"` 68 GPGKeyID string `mapstructure:"gpg_key_id"` 69 GPGKeyServer string `mapstructure:"gpg_key_server"` 70 DisableGPGValidation bool `mapstructure:"disable_gpg"` 71 FlushCache bool `mapstructure:"flush_cache"` 72 ForceCache bool `mapstructure:"force_cache"` 73 TemplateArgs []string `mapstructure:"template_args"` 74 LogLevel string `mapstructure:"log_level"` 75 Verbosity string 76 Volumes []string `mapstructure:"volumes"` 77 } 78 79 // NewLxcDriver returns a new instance of the LXC driver 80 func NewLxcDriver(ctx *DriverContext) Driver { 81 return &LxcDriver{DriverContext: *ctx} 82 } 83 84 // Validate validates the lxc driver configuration 85 func (d *LxcDriver) Validate(config map[string]interface{}) error { 86 fd := &fields.FieldData{ 87 Raw: config, 88 Schema: map[string]*fields.FieldSchema{ 89 "template": { 90 Type: fields.TypeString, 91 Required: true, 92 }, 93 "distro": { 94 Type: fields.TypeString, 95 Required: false, 96 }, 97 "release": { 98 Type: fields.TypeString, 99 Required: false, 100 }, 101 "arch": { 102 Type: fields.TypeString, 103 Required: false, 104 }, 105 "image_variant": { 106 Type: fields.TypeString, 107 Required: false, 108 }, 109 "image_server": { 110 Type: fields.TypeString, 111 Required: false, 112 }, 113 "gpg_key_id": { 114 Type: fields.TypeString, 115 Required: false, 116 }, 117 "gpg_key_server": { 118 Type: fields.TypeString, 119 Required: false, 120 }, 121 "disable_gpg": { 122 Type: fields.TypeString, 123 Required: false, 124 }, 125 "flush_cache": { 126 Type: fields.TypeString, 127 Required: false, 128 }, 129 "force_cache": { 130 Type: fields.TypeString, 131 Required: false, 132 }, 133 "template_args": { 134 Type: fields.TypeArray, 135 Required: false, 136 }, 137 "log_level": { 138 Type: fields.TypeString, 139 Required: false, 140 }, 141 "verbosity": { 142 Type: fields.TypeString, 143 Required: false, 144 }, 145 "volumes": { 146 Type: fields.TypeArray, 147 Required: false, 148 }, 149 }, 150 } 151 152 if err := fd.Validate(); err != nil { 153 return err 154 } 155 156 volumes, _ := fd.GetOk("volumes") 157 for _, volDesc := range volumes.([]interface{}) { 158 volStr := volDesc.(string) 159 paths := strings.Split(volStr, ":") 160 if len(paths) != 2 { 161 return fmt.Errorf("invalid volume bind mount entry: '%s'", volStr) 162 } 163 if len(paths[0]) == 0 || len(paths[1]) == 0 { 164 return fmt.Errorf("invalid volume bind mount entry: '%s'", volStr) 165 } 166 if paths[1][0] == '/' { 167 return fmt.Errorf("unsupported absolute container mount point: '%s'", paths[1]) 168 } 169 } 170 171 return nil 172 } 173 174 func (d *LxcDriver) Abilities() DriverAbilities { 175 return DriverAbilities{ 176 SendSignals: false, 177 Exec: false, 178 } 179 } 180 181 func (d *LxcDriver) FSIsolation() cstructs.FSIsolation { 182 return cstructs.FSIsolationImage 183 } 184 185 // Fingerprint fingerprints the lxc driver configuration 186 func (d *LxcDriver) Fingerprint(req *cstructs.FingerprintRequest, resp *cstructs.FingerprintResponse) error { 187 cfg := req.Config 188 189 enabled := cfg.ReadBoolDefault(lxcConfigOption, true) 190 if !enabled && !cfg.DevMode { 191 return nil 192 } 193 version := lxc.Version() 194 if version == "" { 195 return nil 196 } 197 resp.AddAttribute("driver.lxc.version", version) 198 resp.AddAttribute("driver.lxc", "1") 199 resp.Detected = true 200 201 // Advertise if this node supports lxc volumes 202 if d.config.ReadBoolDefault(lxcVolumesConfigOption, lxcVolumesConfigDefault) { 203 resp.AddAttribute("driver."+lxcVolumesConfigOption, "1") 204 } 205 206 return nil 207 } 208 209 func (d *LxcDriver) Prestart(*ExecContext, *structs.Task) (*PrestartResponse, error) { 210 return nil, nil 211 } 212 213 // Start starts the LXC Driver 214 func (d *LxcDriver) Start(ctx *ExecContext, task *structs.Task) (*StartResponse, error) { 215 sresp, err, errCleanup := d.startWithCleanup(ctx, task) 216 if err != nil { 217 if cleanupErr := errCleanup(); cleanupErr != nil { 218 d.logger.Printf("[ERR] error occurred while cleaning up from error in Start: %v", cleanupErr) 219 } 220 } 221 return sresp, err 222 } 223 224 func (d *LxcDriver) startWithCleanup(ctx *ExecContext, task *structs.Task) (*StartResponse, error, func() error) { 225 noCleanup := func() error { return nil } 226 var driverConfig LxcDriverConfig 227 if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil { 228 return nil, err, noCleanup 229 } 230 lxcPath := lxc.DefaultConfigPath() 231 if path := d.config.Read("driver.lxc.path"); path != "" { 232 lxcPath = path 233 } 234 235 containerName := fmt.Sprintf("%s-%s", task.Name, d.DriverContext.allocID) 236 c, err := lxc.NewContainer(containerName, lxcPath) 237 if err != nil { 238 return nil, fmt.Errorf("unable to initialize container: %v", err), noCleanup 239 } 240 241 var verbosity lxc.Verbosity 242 switch driverConfig.Verbosity { 243 case "verbose": 244 verbosity = lxc.Verbose 245 case "", "quiet": 246 verbosity = lxc.Quiet 247 default: 248 return nil, fmt.Errorf("lxc driver config 'verbosity' can only be either quiet or verbose"), noCleanup 249 } 250 c.SetVerbosity(verbosity) 251 252 var logLevel lxc.LogLevel 253 switch driverConfig.LogLevel { 254 case "trace": 255 logLevel = lxc.TRACE 256 case "debug": 257 logLevel = lxc.DEBUG 258 case "info": 259 logLevel = lxc.INFO 260 case "warn": 261 logLevel = lxc.WARN 262 case "", "error": 263 logLevel = lxc.ERROR 264 default: 265 return nil, fmt.Errorf("lxc driver config 'log_level' can only be trace, debug, info, warn or error"), noCleanup 266 } 267 c.SetLogLevel(logLevel) 268 269 logFile := filepath.Join(ctx.TaskDir.Dir, fmt.Sprintf("%v-lxc.log", task.Name)) 270 c.SetLogFile(logFile) 271 272 options := lxc.TemplateOptions{ 273 Template: driverConfig.Template, 274 Distro: driverConfig.Distro, 275 Release: driverConfig.Release, 276 Arch: driverConfig.Arch, 277 FlushCache: driverConfig.FlushCache, 278 DisableGPGValidation: driverConfig.DisableGPGValidation, 279 ExtraArgs: driverConfig.TemplateArgs, 280 } 281 282 if err := c.Create(options); err != nil { 283 return nil, fmt.Errorf("unable to create container: %v", err), noCleanup 284 } 285 286 // Set the network type to none 287 if err := c.SetConfigItem("lxc.network.type", "none"); err != nil { 288 return nil, fmt.Errorf("error setting network type configuration: %v", err), c.Destroy 289 } 290 291 // Bind mount the shared alloc dir and task local dir in the container 292 mounts := []string{ 293 fmt.Sprintf("%s local none rw,bind,create=dir", ctx.TaskDir.LocalDir), 294 fmt.Sprintf("%s alloc none rw,bind,create=dir", ctx.TaskDir.SharedAllocDir), 295 fmt.Sprintf("%s secrets none rw,bind,create=dir", ctx.TaskDir.SecretsDir), 296 } 297 298 volumesEnabled := d.config.ReadBoolDefault(lxcVolumesConfigOption, lxcVolumesConfigDefault) 299 300 for _, volDesc := range driverConfig.Volumes { 301 // the format was checked in Validate() 302 paths := strings.Split(volDesc, ":") 303 304 if filepath.IsAbs(paths[0]) { 305 if !volumesEnabled { 306 return nil, fmt.Errorf("absolute bind-mount volume in config but '%v' is false", lxcVolumesConfigOption), c.Destroy 307 } 308 } else { 309 // Relative source paths are treated as relative to alloc dir 310 paths[0] = filepath.Join(ctx.TaskDir.Dir, paths[0]) 311 } 312 313 mounts = append(mounts, fmt.Sprintf("%s %s none rw,bind,create=dir", paths[0], paths[1])) 314 } 315 316 for _, mnt := range mounts { 317 if err := c.SetConfigItem("lxc.mount.entry", mnt); err != nil { 318 return nil, fmt.Errorf("error setting bind mount %q error: %v", mnt, err), c.Destroy 319 } 320 } 321 322 // Start the container 323 if err := c.Start(); err != nil { 324 return nil, fmt.Errorf("unable to start container: %v", err), c.Destroy 325 } 326 327 stopAndDestroyCleanup := func() error { 328 if err := c.Stop(); err != nil { 329 return err 330 } 331 return c.Destroy() 332 } 333 334 // Set the resource limits 335 if err := c.SetMemoryLimit(lxc.ByteSize(task.Resources.MemoryMB) * lxc.MB); err != nil { 336 return nil, fmt.Errorf("unable to set memory limits: %v", err), stopAndDestroyCleanup 337 } 338 if err := c.SetCgroupItem("cpu.shares", strconv.Itoa(task.Resources.CPU)); err != nil { 339 return nil, fmt.Errorf("unable to set cpu shares: %v", err), stopAndDestroyCleanup 340 } 341 342 h := lxcDriverHandle{ 343 container: c, 344 initPid: c.InitPid(), 345 lxcPath: lxcPath, 346 logger: d.logger, 347 killTimeout: GetKillTimeout(task.KillTimeout, d.DriverContext.config.MaxKillTimeout), 348 maxKillTimeout: d.DriverContext.config.MaxKillTimeout, 349 totalCpuStats: stats.NewCpuStats(), 350 userCpuStats: stats.NewCpuStats(), 351 systemCpuStats: stats.NewCpuStats(), 352 waitCh: make(chan *dstructs.WaitResult, 1), 353 doneCh: make(chan bool, 1), 354 } 355 356 go h.run() 357 358 return &StartResponse{Handle: &h}, nil, noCleanup 359 } 360 361 func (d *LxcDriver) Cleanup(*ExecContext, *CreatedResources) error { return nil } 362 363 // Open creates the driver to monitor an existing LXC container 364 func (d *LxcDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) { 365 pid := &lxcPID{} 366 if err := json.Unmarshal([]byte(handleID), pid); err != nil { 367 return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err) 368 } 369 370 var container *lxc.Container 371 containers := lxc.Containers(pid.LxcPath) 372 for _, c := range containers { 373 if c.Name() == pid.ContainerName { 374 container = &c 375 break 376 } 377 } 378 379 if container == nil { 380 return nil, fmt.Errorf("container %v not found", pid.ContainerName) 381 } 382 383 handle := lxcDriverHandle{ 384 container: container, 385 initPid: container.InitPid(), 386 lxcPath: pid.LxcPath, 387 logger: d.logger, 388 killTimeout: pid.KillTimeout, 389 maxKillTimeout: d.DriverContext.config.MaxKillTimeout, 390 totalCpuStats: stats.NewCpuStats(), 391 userCpuStats: stats.NewCpuStats(), 392 systemCpuStats: stats.NewCpuStats(), 393 waitCh: make(chan *dstructs.WaitResult, 1), 394 doneCh: make(chan bool, 1), 395 } 396 go handle.run() 397 398 return &handle, nil 399 } 400 401 // lxcDriverHandle allows controlling the lifecycle of an lxc container 402 type lxcDriverHandle struct { 403 container *lxc.Container 404 initPid int 405 lxcPath string 406 407 logger *log.Logger 408 409 killTimeout time.Duration 410 maxKillTimeout time.Duration 411 412 totalCpuStats *stats.CpuStats 413 userCpuStats *stats.CpuStats 414 systemCpuStats *stats.CpuStats 415 416 waitCh chan *dstructs.WaitResult 417 doneCh chan bool 418 } 419 420 type lxcPID struct { 421 ContainerName string 422 InitPid int 423 LxcPath string 424 KillTimeout time.Duration 425 } 426 427 func (h *lxcDriverHandle) ID() string { 428 pid := lxcPID{ 429 ContainerName: h.container.Name(), 430 InitPid: h.initPid, 431 LxcPath: h.lxcPath, 432 KillTimeout: h.killTimeout, 433 } 434 data, err := json.Marshal(pid) 435 if err != nil { 436 h.logger.Printf("[ERR] driver.lxc: failed to marshal lxc PID to JSON: %v", err) 437 } 438 return string(data) 439 } 440 441 func (h *lxcDriverHandle) WaitCh() chan *dstructs.WaitResult { 442 return h.waitCh 443 } 444 445 func (h *lxcDriverHandle) Update(task *structs.Task) error { 446 h.killTimeout = GetKillTimeout(task.KillTimeout, h.killTimeout) 447 return nil 448 } 449 450 func (h *lxcDriverHandle) Exec(ctx context.Context, cmd string, args []string) ([]byte, int, error) { 451 return nil, 0, fmt.Errorf("lxc driver cannot execute commands") 452 } 453 454 func (h *lxcDriverHandle) Kill() error { 455 name := h.container.Name() 456 457 h.logger.Printf("[INFO] driver.lxc: shutting down container %q", name) 458 if err := h.container.Shutdown(h.killTimeout); err != nil { 459 h.logger.Printf("[INFO] driver.lxc: shutting down container %q failed: %v", name, err) 460 if err := h.container.Stop(); err != nil { 461 h.logger.Printf("[ERR] driver.lxc: error stopping container %q: %v", name, err) 462 } 463 } 464 465 close(h.doneCh) 466 return nil 467 } 468 469 func (h *lxcDriverHandle) Signal(s os.Signal) error { 470 return fmt.Errorf("LXC does not support signals") 471 } 472 473 func (h *lxcDriverHandle) Stats() (*cstructs.TaskResourceUsage, error) { 474 cpuStats, err := h.container.CPUStats() 475 if err != nil { 476 return nil, nil 477 } 478 total, err := h.container.CPUTime() 479 if err != nil { 480 return nil, nil 481 } 482 483 t := time.Now() 484 485 // Get the cpu stats 486 system := cpuStats["system"] 487 user := cpuStats["user"] 488 cs := &cstructs.CpuStats{ 489 SystemMode: h.systemCpuStats.Percent(float64(system)), 490 UserMode: h.systemCpuStats.Percent(float64(user)), 491 Percent: h.totalCpuStats.Percent(float64(total)), 492 TotalTicks: float64(user + system), 493 Measured: LXCMeasuredCpuStats, 494 } 495 496 // Get the Memory Stats 497 memData := map[string]uint64{ 498 "rss": 0, 499 "cache": 0, 500 "swap": 0, 501 } 502 rawMemStats := h.container.CgroupItem("memory.stat") 503 for _, rawMemStat := range rawMemStats { 504 key, val, err := keysToVal(rawMemStat) 505 if err != nil { 506 h.logger.Printf("[ERR] driver.lxc: error getting stat for line %q", rawMemStat) 507 continue 508 } 509 if _, ok := memData[key]; ok { 510 memData[key] = val 511 512 } 513 } 514 ms := &cstructs.MemoryStats{ 515 RSS: memData["rss"], 516 Cache: memData["cache"], 517 Swap: memData["swap"], 518 Measured: LXCMeasuredMemStats, 519 } 520 521 mu := h.container.CgroupItem("memory.max_usage_in_bytes") 522 for _, rawMemMaxUsage := range mu { 523 val, err := strconv.ParseUint(rawMemMaxUsage, 10, 64) 524 if err != nil { 525 h.logger.Printf("[ERR] driver.lxc: unable to get max memory usage: %v", err) 526 continue 527 } 528 ms.MaxUsage = val 529 } 530 ku := h.container.CgroupItem("memory.kmem.usage_in_bytes") 531 for _, rawKernelUsage := range ku { 532 val, err := strconv.ParseUint(rawKernelUsage, 10, 64) 533 if err != nil { 534 h.logger.Printf("[ERR] driver.lxc: unable to get kernel memory usage: %v", err) 535 continue 536 } 537 ms.KernelUsage = val 538 } 539 540 mku := h.container.CgroupItem("memory.kmem.max_usage_in_bytes") 541 for _, rawMaxKernelUsage := range mku { 542 val, err := strconv.ParseUint(rawMaxKernelUsage, 10, 64) 543 if err != nil { 544 h.logger.Printf("[ERR] driver.lxc: unable to get max kernel memory usage: %v", err) 545 continue 546 } 547 ms.KernelMaxUsage = val 548 } 549 550 taskResUsage := cstructs.TaskResourceUsage{ 551 ResourceUsage: &cstructs.ResourceUsage{ 552 CpuStats: cs, 553 MemoryStats: ms, 554 }, 555 Timestamp: t.UTC().UnixNano(), 556 } 557 558 return &taskResUsage, nil 559 } 560 561 func (h *lxcDriverHandle) run() { 562 defer close(h.waitCh) 563 timer := time.NewTimer(containerMonitorIntv) 564 for { 565 select { 566 case <-timer.C: 567 process, err := os.FindProcess(h.initPid) 568 if err != nil { 569 h.waitCh <- &dstructs.WaitResult{Err: err} 570 return 571 } 572 if err := process.Signal(syscall.Signal(0)); err != nil { 573 h.waitCh <- &dstructs.WaitResult{} 574 return 575 } 576 timer.Reset(containerMonitorIntv) 577 case <-h.doneCh: 578 h.waitCh <- &dstructs.WaitResult{} 579 return 580 } 581 } 582 } 583 584 func keysToVal(line string) (string, uint64, error) { 585 tokens := strings.Split(line, " ") 586 if len(tokens) != 2 { 587 return "", 0, fmt.Errorf("line isn't a k/v pair") 588 } 589 key := tokens[0] 590 val, err := strconv.ParseUint(tokens[1], 10, 64) 591 return key, val, err 592 }