github.com/anuvu/nomad@v0.8.7-atom1/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 "os/exec" 12 "path/filepath" 13 "regexp" 14 "strconv" 15 "strings" 16 "syscall" 17 "time" 18 19 "github.com/hashicorp/nomad/client/fingerprint" 20 "github.com/hashicorp/nomad/client/stats" 21 "github.com/hashicorp/nomad/helper/fields" 22 "github.com/hashicorp/nomad/nomad/structs" 23 "github.com/mitchellh/mapstructure" 24 25 dstructs "github.com/hashicorp/nomad/client/driver/structs" 26 cstructs "github.com/hashicorp/nomad/client/structs" 27 lxc "gopkg.in/lxc/go-lxc.v2" 28 ) 29 30 const ( 31 // lxcConfigOption is the key for enabling the LXC driver in the 32 // Config.Options map. 33 lxcConfigOption = "driver.lxc.enable" 34 35 // lxcVolumesConfigOption is the key for enabling the use of 36 // custom bind volumes to arbitrary host paths 37 lxcVolumesConfigOption = "lxc.volumes.enabled" 38 lxcVolumesConfigDefault = true 39 40 // containerMonitorIntv is the interval at which the driver checks if the 41 // container is still alive 42 containerMonitorIntv = 2 * time.Second 43 ) 44 45 var ( 46 LXCMeasuredCpuStats = []string{"System Mode", "User Mode", "Percent"} 47 48 LXCMeasuredMemStats = []string{"RSS", "Cache", "Swap", "Max Usage", "Kernel Usage", "Kernel Max Usage"} 49 ) 50 51 // Add the lxc driver to the list of builtin drivers 52 func init() { 53 BuiltinDrivers["lxc"] = NewLxcDriver 54 } 55 56 // LxcDriver allows users to run LXC Containers 57 type LxcDriver struct { 58 DriverContext 59 fingerprint.StaticFingerprinter 60 lxcPath string 61 } 62 63 // LxcCommonDriverConfig is configuration that's common between 64 // container types - new containers created with Create and run with 65 // Start; and containers created from a rootfs clone and started using 66 // StartExecute. 67 type LxcCommonDriverConfig struct { 68 LogLevel string `mapstructure:"log_level"` 69 Verbosity string `mapstructure:"verbosity"` 70 UseExecute bool `mapstructure:"use_execute"` 71 Volumes []map[string]string `mapstructure:"volumes"` 72 } 73 74 // LxcStartDriverConfig is the configuration for containers that will 75 // be created with Create and run with Start 76 type LxcStartDriverConfig struct { 77 Template string 78 Distro string 79 Release string 80 Arch string 81 ImageVariant string `mapstructure:"image_variant"` 82 ImageServer string `mapstructure:"image_server"` 83 GPGKeyID string `mapstructure:"gpg_key_id"` 84 GPGKeyServer string `mapstructure:"gpg_key_server"` 85 DisableGPGValidation bool `mapstructure:"disable_gpg"` 86 FlushCache bool `mapstructure:"flush_cache"` 87 ForceCache bool `mapstructure:"force_cache"` 88 TemplateArgs []string `mapstructure:"template_args"` 89 LxcCommonDriverConfig 90 } 91 92 // LxcExecuteDriverConfig is configuration for containers that will be 93 // created by cloning a rootfs and run using StartExecute 94 type LxcExecuteDriverConfig struct { 95 LxcCommonDriverConfig 96 BaseRootFsPath string `mapstructure:"base_rootfs_path"` 97 BaseConfigPath string `mapstructure:"base_config_path"` 98 NetworkMode string `mapstructure:"network_mode"` // The network mode of the container - host or bridge 99 CmdArgs []string `mapstructure:"cmd_args"` 100 } 101 102 // NewLxcDriver returns a new instance of the LXC driver 103 func NewLxcDriver(ctx *DriverContext) Driver { 104 d := &LxcDriver{DriverContext: *ctx} 105 return d 106 } 107 108 // Validate validates the lxc driver configuration 109 func (d *LxcDriver) Validate(config map[string]interface{}) error { 110 commonFieldSchema := map[string]*fields.FieldSchema{ 111 "log_level": { 112 Type: fields.TypeString, 113 Required: false, 114 }, 115 "verbosity": { 116 Type: fields.TypeString, 117 Required: false, 118 }, 119 "use_execute": { 120 Type: fields.TypeBool, 121 Required: false, 122 }, 123 "volumes": { 124 Type: fields.TypeArray, 125 Required: false, 126 }, 127 } 128 fd := &fields.FieldData{ 129 Raw: config, 130 Schema: map[string]*fields.FieldSchema{ 131 "template": { 132 Type: fields.TypeString, 133 Required: true, 134 }, 135 "distro": { 136 Type: fields.TypeString, 137 Required: false, 138 }, 139 "release": { 140 Type: fields.TypeString, 141 Required: false, 142 }, 143 "arch": { 144 Type: fields.TypeString, 145 Required: false, 146 }, 147 "image_variant": { 148 Type: fields.TypeString, 149 Required: false, 150 }, 151 "image_server": { 152 Type: fields.TypeString, 153 Required: false, 154 }, 155 "gpg_key_id": { 156 Type: fields.TypeString, 157 Required: false, 158 }, 159 "gpg_key_server": { 160 Type: fields.TypeString, 161 Required: false, 162 }, 163 "disable_gpg": { 164 Type: fields.TypeString, 165 Required: false, 166 }, 167 "flush_cache": { 168 Type: fields.TypeString, 169 Required: false, 170 }, 171 "force_cache": { 172 Type: fields.TypeString, 173 Required: false, 174 }, 175 "template_args": { 176 Type: fields.TypeArray, 177 Required: false, 178 }, 179 }, 180 } 181 for k, v := range commonFieldSchema { 182 fd.Schema[k] = v 183 } 184 185 execFd := &fields.FieldData{ 186 Raw: config, 187 Schema: map[string]*fields.FieldSchema{ 188 "base_config_path": { 189 Type: fields.TypeString, 190 Required: true, 191 }, 192 "base_rootfs_path": { 193 Type: fields.TypeString, 194 Required: true, 195 }, 196 "cmd_args": { 197 Type: fields.TypeArray, 198 Required: false, 199 Default: []string{}, 200 }, 201 "network_mode": { 202 Type: fields.TypeString, 203 Required: false, 204 Default: "bridge", 205 }, 206 }, 207 } 208 209 for k, v := range commonFieldSchema { 210 execFd.Schema[k] = v 211 } 212 213 // default is to use lxc-start 214 useExecute := false 215 execConfig, ok := config["use_execute"] 216 if ok { 217 useExecute, ok = execConfig.(bool) 218 if !ok { 219 return fmt.Errorf("invalid value for use_execute config: %v", execConfig) 220 } 221 } 222 var volumes interface{} 223 if useExecute { 224 if err := execFd.Validate(); err != nil { 225 return err 226 } 227 volumes, ok = execFd.GetOk("volumes") 228 } else { 229 if err := fd.Validate(); err != nil { 230 return err 231 } 232 volumes, ok = fd.GetOk("volumes") 233 } 234 if ok { 235 return d.validateVolumesConfig(volumes.([]interface{})) 236 } else { 237 return nil 238 } 239 } 240 241 func (d *LxcDriver) validateVolumesConfig(volumes []interface{}) error { 242 for _, volSpecI := range volumes { 243 volSpec := volSpecI.(map[string]interface{}) 244 _, sourcePresent := volSpec["source"] 245 dest, destPresent := volSpec["dest"] 246 if !sourcePresent || !destPresent { 247 return fmt.Errorf("invalid volume bind mount entry: empty src or dest %v", volSpec) 248 } 249 250 if dest.(string)[0] == '/' { 251 return fmt.Errorf("unsupported absolute container mount point: '%s'", dest) 252 } 253 mode, modePresent := volSpec["mode"] 254 if modePresent && mode.(string) != "ro" && mode.(string) != "rw" { 255 return fmt.Errorf("invalid mode in bind vol: '%s'", mode.(string)) 256 } 257 } 258 return nil 259 } 260 261 func (d *LxcDriver) Abilities() DriverAbilities { 262 return DriverAbilities{ 263 SendSignals: false, 264 Exec: false, 265 } 266 } 267 268 func (d *LxcDriver) FSIsolation() cstructs.FSIsolation { 269 return cstructs.FSIsolationImage 270 } 271 272 // Fingerprint fingerprints the lxc driver configuration 273 func (d *LxcDriver) Fingerprint(req *cstructs.FingerprintRequest, resp *cstructs.FingerprintResponse) error { 274 cfg := req.Config 275 276 enabled := cfg.ReadBoolDefault(lxcConfigOption, true) 277 if !enabled && !cfg.DevMode { 278 return nil 279 } 280 version := lxc.Version() 281 if version == "" { 282 return nil 283 } 284 resp.AddAttribute("driver.lxc.version", version) 285 resp.AddAttribute("driver.lxc", "1") 286 resp.Detected = true 287 288 // Advertise if this node supports lxc volumes 289 if d.config.ReadBoolDefault(lxcVolumesConfigOption, lxcVolumesConfigDefault) { 290 resp.AddAttribute("driver."+lxcVolumesConfigOption, "1") 291 } 292 resp.AddAttribute("driver.lxc.execute", "true") 293 294 return nil 295 } 296 297 func (d *LxcDriver) Prestart(_ *ExecContext, task *structs.Task) (*PrestartResponse, error) { 298 return nil, nil 299 } 300 301 func (d *LxcDriver) getContainerName(task *structs.Task) string { 302 return fmt.Sprintf("%s-%s", task.Name, d.DriverContext.allocID) 303 } 304 305 // Start starts the LXC Driver 306 func (d *LxcDriver) Start(ctx *ExecContext, task *structs.Task) (*StartResponse, error) { 307 sresp, err, errCleanup := d.startWithCleanup(ctx, task) 308 if err != nil { 309 if cleanupErr := errCleanup(); cleanupErr != nil { 310 d.logger.Printf("[ERR] error occurred while cleaning up from error in Start: %v", cleanupErr) 311 } 312 } 313 return sresp, err 314 } 315 316 func (d *LxcDriver) startWithCleanup(ctx *ExecContext, task *structs.Task) (*StartResponse, error, func() error) { 317 noCleanup := func() error { return nil } 318 319 var commonConfig LxcCommonDriverConfig 320 if err := mapstructure.WeakDecode(task.Config, &commonConfig); err != nil { 321 return nil, err, noCleanup 322 } 323 324 d.lxcPath = lxc.DefaultConfigPath() 325 if path := d.config.Read("driver.lxc.path"); path != "" { 326 d.lxcPath = path 327 } 328 329 containerName := d.getContainerName(task) 330 c, err := lxc.NewContainer(containerName, d.lxcPath) 331 if err != nil { 332 return nil, fmt.Errorf("unable to initialize container: %v", err), noCleanup 333 } 334 335 var verbosity lxc.Verbosity 336 switch commonConfig.Verbosity { 337 case "verbose": 338 verbosity = lxc.Verbose 339 case "", "quiet": 340 verbosity = lxc.Quiet 341 default: 342 return nil, fmt.Errorf("lxc driver config 'verbosity' can only be either quiet or verbose"), noCleanup 343 } 344 c.SetVerbosity(verbosity) 345 346 var logLevel lxc.LogLevel 347 switch commonConfig.LogLevel { 348 case "trace": 349 logLevel = lxc.TRACE 350 case "debug": 351 logLevel = lxc.DEBUG 352 case "info": 353 logLevel = lxc.INFO 354 case "warn": 355 logLevel = lxc.WARN 356 case "", "error": 357 logLevel = lxc.ERROR 358 default: 359 return nil, fmt.Errorf("lxc driver config 'log_level' can only be trace, debug, info, warn or error"), noCleanup 360 } 361 c.SetLogLevel(logLevel) 362 363 logFile := filepath.Join(ctx.TaskDir.Dir, fmt.Sprintf("%v-lxc.log", task.Name)) 364 c.SetLogFile(logFile) 365 366 if commonConfig.UseExecute { 367 d.logger.Printf("[INFO] Using lxc-execute to start application container") 368 return d.executeContainer(ctx, c, task, &commonConfig) 369 } else { 370 d.logger.Printf("[INFO] Using lxc-start to start system container") 371 return d.startContainer(ctx, c, task, &commonConfig) 372 } 373 } 374 375 func (d *LxcDriver) startContainer(ctx *ExecContext, c *lxc.Container, task *structs.Task, commonConfig *LxcCommonDriverConfig) (*StartResponse, error, func() error) { 376 noCleanup := func() error { return nil } 377 378 var startConfig LxcStartDriverConfig 379 if err := mapstructure.WeakDecode(task.Config, &startConfig); err != nil { 380 return nil, err, noCleanup 381 } 382 383 options := lxc.TemplateOptions{ 384 Template: startConfig.Template, 385 Distro: startConfig.Distro, 386 Release: startConfig.Release, 387 Arch: startConfig.Arch, 388 FlushCache: startConfig.FlushCache, 389 DisableGPGValidation: startConfig.DisableGPGValidation, 390 ExtraArgs: startConfig.TemplateArgs, 391 } 392 393 if err := c.Create(options); err != nil { 394 return nil, fmt.Errorf("unable to create container: %v", err), noCleanup 395 } 396 397 if err := d.setCommonContainerConfig(ctx, c, commonConfig); err != nil { 398 return nil, err, c.Destroy 399 } 400 401 // Start the container 402 if err := c.Start(); err != nil { 403 return nil, fmt.Errorf("unable to start container: %v", err), c.Destroy 404 } 405 406 stopAndDestroyCleanup := func() error { 407 if err := c.Stop(); err != nil { 408 return err 409 } 410 return c.Destroy() 411 } 412 413 if err := setLimitsOnContainer(c, task); err != nil { 414 return nil, err, stopAndDestroyCleanup 415 } 416 417 h := lxcDriverHandle{ 418 container: c, 419 initPid: c.InitPid(), 420 lxcPath: d.lxcPath, 421 logger: d.logger, 422 killTimeout: GetKillTimeout(task.KillTimeout, d.DriverContext.config.MaxKillTimeout), 423 maxKillTimeout: d.DriverContext.config.MaxKillTimeout, 424 totalCpuStats: stats.NewCpuStats(), 425 userCpuStats: stats.NewCpuStats(), 426 systemCpuStats: stats.NewCpuStats(), 427 waitCh: make(chan *dstructs.WaitResult, 1), 428 doneCh: make(chan bool, 1), 429 } 430 431 go h.run() 432 433 return &StartResponse{Handle: &h}, nil, noCleanup 434 } 435 436 func (d *LxcDriver) executeContainer(ctx *ExecContext, c *lxc.Container, task *structs.Task, commonConfig *LxcCommonDriverConfig) (*StartResponse, error, func() error) { 437 noCleanup := func() error { return nil } 438 var executeConfig LxcExecuteDriverConfig 439 if err := mapstructure.WeakDecode(task.Config, &executeConfig); err != nil { 440 return nil, err, noCleanup 441 } 442 443 containerPath := filepath.Join(d.lxcPath, c.Name()) 444 containerRootfsPath := filepath.Join(containerPath, "rootfs") 445 if err := os.MkdirAll(containerRootfsPath, 0711); err != nil { 446 return nil, fmt.Errorf("unable to create container directory at %s", containerPath), noCleanup 447 } 448 449 if executeConfig.BaseRootFsPath[:4] != "lvm:" { 450 return nil, fmt.Errorf("only LVM is supported as a base to clone from"), noCleanup 451 } 452 453 baseLvName := executeConfig.BaseRootFsPath[4:] 454 lvCreateCmd := exec.Command("lvcreate", "-kn", "-n", c.Name(), "-s", baseLvName) 455 if err := lvCreateCmd.Run(); err != nil { 456 return nil, fmt.Errorf("could not create thin pool snapshot with cmd '%v': %v: %s", lvCreateCmd.Args, err, err.(*exec.ExitError).Stderr), noCleanup 457 } 458 459 vgName, err := extractVgName(baseLvName) 460 if err != nil { 461 return nil, fmt.Errorf("Could not parse LVM Volume Group name from '%s'", baseLvName), noCleanup 462 } 463 464 removeLVCleanup := func() error { 465 lvRemoveCmd := exec.Command("lvremove", "-f", fmt.Sprintf("%s/%s", vgName, c.Name())) 466 if err := lvRemoveCmd.Run(); err != nil { 467 return fmt.Errorf("could not remove thin pool snapshot with cmd '%v': %v: %s", lvRemoveCmd.Args, err, err.(*exec.ExitError).Stderr) 468 } 469 return nil 470 } 471 472 tr := func(s string) string { 473 return strings.Replace(s, "-", "--", -1) 474 } 475 476 storageName := fmt.Sprintf("lvm:/dev/mapper/%s-%s", tr(vgName), tr(c.Name())) 477 478 initialConfigFilePath := filepath.Join(d.lxcPath, c.Name(), "config.initial") 479 480 if err := exec.Command("cp", executeConfig.BaseConfigPath, initialConfigFilePath).Run(); err != nil { 481 return nil, fmt.Errorf("could not copy initial container config from '%s' to '%s': %v", 482 executeConfig.BaseConfigPath, initialConfigFilePath, err), removeLVCleanup 483 } 484 485 finalConfigFilePath := filepath.Join(d.lxcPath, c.Name(), "config") 486 d.logger.Printf("[INFO] %s initial config path is %s, final config path is %s", c.Name(), initialConfigFilePath, finalConfigFilePath) 487 488 if err := c.LoadConfigFile(initialConfigFilePath); err != nil { 489 return nil, fmt.Errorf("unable to read config file for container: %v", err), removeLVCleanup 490 } 491 492 if err := d.setCommonContainerConfig(ctx, c, commonConfig); err != nil { 493 return nil, err, removeLVCleanup 494 } 495 496 if err := c.SetConfigItem("lxc.rootfs.path", storageName); err != nil { 497 return nil, fmt.Errorf("unable to set lxc.rootfs.path to '%s': %v", storageName, err), removeLVCleanup 498 } 499 500 // Replace any env vars in Cmd with value from Nomad env: 501 parsedArgs := ctx.TaskEnv.ParseAndReplace(executeConfig.CmdArgs) 502 d.logger.Printf("[INFO] env vars substituted in command \"%#v\" - new command is \"%#v\"", executeConfig.CmdArgs, parsedArgs) 503 504 if err := c.SetConfigItem("lxc.execute.cmd", strings.Join(parsedArgs, " ")); err != nil { 505 return nil, fmt.Errorf("unable to set final parsed command to \"%s\"", parsedArgs), removeLVCleanup 506 } 507 508 // if networkmode is host, clear the start-host and stop hooks (which we assume are just being used for CNI setup) 509 // we assume if the network mode is not host, it has been configured elsewhere (yes, including 'bridge') 510 if executeConfig.NetworkMode == "host" { 511 keysToClear := []string{"lxc.hook.stop", "lxc.hook.start-host", "lxc.net.0.type", "lxc.net.0.flags", "lxc.net.0.ipv4.address"} 512 d.logger.Printf("[INFO] network_mode=host, so clearing all of %v and setting lxc.net.0.type=none.", keysToClear) 513 514 for _, item := range keysToClear { 515 if err := c.ClearConfigItem(item); err != nil { 516 return nil, fmt.Errorf("Unable to clear hook config '%s': %v", item, err), removeLVCleanup 517 } 518 } 519 520 if err := c.SetConfigItem("lxc.net.0.type", "none"); err != nil { 521 return nil, fmt.Errorf("Unable to set net.1.type=none: %v", err), removeLVCleanup 522 } 523 } 524 525 // Write out final config file for debugging and use with lxc-attach: 526 // Do not edit config after this. 527 528 savedConfigFile := filepath.Join(ctx.TaskDir.Dir, fmt.Sprintf("%v-lxc.config", task.Name)) 529 for _, fp := range []string{finalConfigFilePath, savedConfigFile} { 530 if err := c.SaveConfigFile(fp); err != nil { 531 return nil, fmt.Errorf("unable to write final config file to \"%s\"", fp), removeLVCleanup 532 } 533 } 534 535 if err := c.StartExecute(parsedArgs); err != nil { 536 return nil, fmt.Errorf("unable to execute with args %#v: %v", parsedArgs, err), removeLVCleanup 537 } 538 539 stopAndRemoveLVCleanup := func() error { 540 removeLVCleanup() 541 if err := c.Stop(); err != nil { 542 return err 543 } 544 return nil 545 } 546 547 if err := setLimitsOnContainer(c, task); err != nil { 548 return nil, err, stopAndRemoveLVCleanup 549 } 550 551 h := lxcDriverHandle{ 552 container: c, 553 initPid: c.InitPid(), 554 lxcPath: d.lxcPath, 555 logger: d.logger, 556 killTimeout: GetKillTimeout(task.KillTimeout, d.DriverContext.config.MaxKillTimeout), 557 maxKillTimeout: d.DriverContext.config.MaxKillTimeout, 558 totalCpuStats: stats.NewCpuStats(), 559 userCpuStats: stats.NewCpuStats(), 560 systemCpuStats: stats.NewCpuStats(), 561 waitCh: make(chan *dstructs.WaitResult, 1), 562 doneCh: make(chan bool, 1), 563 } 564 565 go h.run() 566 567 net := cstructs.DriverNetwork{} 568 ipv4Addrs, err := c.IPv4Addresses() 569 if err != nil { 570 d.logger.Printf("[ERROR] error getting IPv4Addresses for container %s: %v", c.Name(), err) 571 } else if len(ipv4Addrs) == 0 { 572 d.logger.Printf("[INFO] No ipv4 address found for container %s", c.Name()) 573 } else { 574 d.logger.Printf("[INFO] Found ipv4 address %#v for container %s", ipv4Addrs[0], c.Name()) 575 net = cstructs.DriverNetwork{ 576 IP: ipv4Addrs[0], 577 AutoAdvertise: true, 578 } 579 } 580 581 resp := &StartResponse{ 582 Handle: &h, 583 Network: &net, 584 } 585 return resp, nil, noCleanup 586 } 587 588 func extractVgName(baseLvName string) (string, error) { 589 vgName := "" 590 devMapperRE := regexp.MustCompile("/dev/mapper/([a-zA-Z0-9_+.-]*[^-])-{1}[^-]") 591 if !strings.HasPrefix(baseLvName, "/") { 592 // vgname/lvname 593 c := strings.Split(baseLvName, "/") 594 if len(c) != 2 { 595 return "", fmt.Errorf("unexpected number of components in baseLvName '%s': %d", baseLvName, len(c)) 596 } 597 vgName = c[0] 598 } else { 599 // /dev/mapper/vg--name-lv--name 600 matches := devMapperRE.FindAllStringSubmatch(baseLvName, 1) 601 if matches != nil { 602 vgName = matches[0][1] 603 } else { 604 if strings.HasPrefix(baseLvName, "/dev/") { 605 // /dev/vg/lv 606 vgName = baseLvName[len("/dev/"):strings.LastIndex(baseLvName, "/")] 607 } 608 } 609 } 610 if len(vgName) == 0 { 611 return "", fmt.Errorf("could not parse volume group name from '%v':, baseLvName") 612 } 613 return vgName, nil 614 } 615 616 func (d *LxcDriver) setCommonContainerConfig(ctx *ExecContext, c *lxc.Container, commonConfig *LxcCommonDriverConfig) error { 617 618 if err := c.SetConfigItem("lxc.uts.name", c.Name()); err != nil { 619 return fmt.Errorf("unable to set lxc.uts.name to '%s': %v", c.Name(), err) 620 } 621 622 // Set the network type to none if not previously set 623 netZeroType := c.ConfigItem("lxc.net.0.type") 624 if len(netZeroType) == 0 || netZeroType[0] == "" { 625 if err := c.SetConfigItem("lxc.net.0.type", "none"); err != nil { 626 return fmt.Errorf("error setting network type configuration: %v", err) 627 } 628 } else { 629 630 d.logger.Printf("[INFO] driver.lxc: lxc.net.0.type set as %#v, not overriding.", netZeroType) 631 } 632 633 // Bind mount the shared alloc dir and task local dir in the container 634 mounts := []string{ 635 fmt.Sprintf("%s local none rw,bind,create=dir", ctx.TaskDir.LocalDir), 636 fmt.Sprintf("%s alloc none rw,bind,create=dir", ctx.TaskDir.SharedAllocDir), 637 fmt.Sprintf("%s secrets none rw,bind,create=dir", ctx.TaskDir.SecretsDir), 638 } 639 for _, mnt := range mounts { 640 if err := c.SetConfigItem("lxc.mount.entry", mnt); err != nil { 641 return fmt.Errorf("error setting bind mount %q error: %v", mnt, err) 642 } 643 } 644 645 volumesEnabled := d.config.ReadBoolDefault(lxcVolumesConfigOption, lxcVolumesConfigDefault) 646 647 for _, volSpec := range commonConfig.Volumes { 648 mode, modePresent := volSpec["mode"] 649 650 if !modePresent { 651 mode = "rw" 652 } 653 654 srcPath := volSpec["source"] 655 if filepath.IsAbs(srcPath) { 656 if !volumesEnabled { 657 return fmt.Errorf("absolute bind-mount volume in config but '%v' is false", lxcVolumesConfigOption) 658 } 659 } else { 660 // Relative source paths are treated as relative to alloc dir 661 srcPath = filepath.Join(ctx.TaskDir.Dir, srcPath) 662 } 663 664 createType := "dir" 665 srcFileInfo, err := os.Stat(srcPath) 666 if err != nil { 667 return fmt.Errorf("couldn't Stat src='%s' in mount directive: %v", srcPath, err) 668 } 669 670 if srcFileInfo.Mode().IsRegular() { 671 createType = "file" 672 } 673 674 mounts = append(mounts, fmt.Sprintf("%s %s none %s,bind,create=%s", srcPath, volSpec["dest"], mode, createType)) 675 } 676 677 for _, mnt := range mounts { 678 if err := c.SetConfigItem("lxc.mount.entry", mnt); err != nil { 679 return fmt.Errorf("error setting bind mount %q error: %v", mnt, err) 680 } 681 } 682 683 for _, envVar := range ctx.TaskEnv.List() { 684 if err := c.SetConfigItem("lxc.environment", envVar); err != nil { 685 return fmt.Errorf("error setting environment variable '%s': %v", envVar, err) 686 } 687 } 688 689 return nil 690 } 691 692 func setLimitsOnContainer(c *lxc.Container, task *structs.Task) error { 693 // Set the resource limits 694 if err := c.SetMemoryLimit(lxc.ByteSize(task.Resources.MemoryMB) * lxc.MB); err != nil { 695 return fmt.Errorf("unable to set memory limits: %v", err) 696 } 697 if err := c.SetCgroupItem("cpu.shares", strconv.Itoa(task.Resources.CPU)); err != nil { 698 return fmt.Errorf("unable to set cpu shares: %v", err) 699 } 700 return nil 701 } 702 703 func (d *LxcDriver) Cleanup(ctx *ExecContext, resources *CreatedResources) error { 704 705 return nil 706 } 707 708 // Open creates the driver to monitor an existing LXC container 709 func (d *LxcDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) { 710 pid := &lxcPID{} 711 if err := json.Unmarshal([]byte(handleID), pid); err != nil { 712 return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err) 713 } 714 715 var container *lxc.Container 716 containers := lxc.Containers(pid.LxcPath) 717 for _, c := range containers { 718 if c.Name() == pid.ContainerName { 719 container = c 720 break 721 } 722 } 723 724 if container == nil { 725 return nil, fmt.Errorf("container %v not found", pid.ContainerName) 726 } 727 728 handle := lxcDriverHandle{ 729 container: container, 730 initPid: container.InitPid(), 731 lxcPath: pid.LxcPath, 732 logger: d.logger, 733 killTimeout: pid.KillTimeout, 734 maxKillTimeout: d.DriverContext.config.MaxKillTimeout, 735 totalCpuStats: stats.NewCpuStats(), 736 userCpuStats: stats.NewCpuStats(), 737 systemCpuStats: stats.NewCpuStats(), 738 waitCh: make(chan *dstructs.WaitResult, 1), 739 doneCh: make(chan bool, 1), 740 } 741 go handle.run() 742 743 return &handle, nil 744 } 745 746 // lxcDriverHandle allows controlling the lifecycle of an lxc container 747 type lxcDriverHandle struct { 748 container *lxc.Container 749 initPid int 750 lxcPath string 751 752 logger *log.Logger 753 754 killTimeout time.Duration 755 maxKillTimeout time.Duration 756 757 totalCpuStats *stats.CpuStats 758 userCpuStats *stats.CpuStats 759 systemCpuStats *stats.CpuStats 760 761 waitCh chan *dstructs.WaitResult 762 doneCh chan bool 763 } 764 765 type lxcPID struct { 766 ContainerName string 767 InitPid int 768 LxcPath string 769 KillTimeout time.Duration 770 } 771 772 func (h *lxcDriverHandle) ID() string { 773 pid := lxcPID{ 774 ContainerName: h.container.Name(), 775 InitPid: h.initPid, 776 LxcPath: h.lxcPath, 777 KillTimeout: h.killTimeout, 778 } 779 data, err := json.Marshal(pid) 780 if err != nil { 781 h.logger.Printf("[ERR] driver.lxc: failed to marshal lxc PID to JSON: %v", err) 782 } 783 return string(data) 784 } 785 786 func (h *lxcDriverHandle) WaitCh() chan *dstructs.WaitResult { 787 return h.waitCh 788 } 789 790 func (h *lxcDriverHandle) Update(task *structs.Task) error { 791 h.killTimeout = GetKillTimeout(task.KillTimeout, h.killTimeout) 792 return nil 793 } 794 795 func (h *lxcDriverHandle) Exec(ctx context.Context, cmd string, args []string) ([]byte, int, error) { 796 return nil, 0, fmt.Errorf("lxc driver cannot execute commands") 797 } 798 799 func (h *lxcDriverHandle) Kill() error { 800 if h.container.Running() { 801 name := h.container.Name() 802 if err := h.container.Stop(); err != nil { 803 h.logger.Printf("[WARN] driver.lxc: error stopping container %q: %v", name, err) 804 return fmt.Errorf("could not stop container: %v", err) 805 } 806 h.logger.Printf("[INFO] driver.lxc: stopped running container %q", name) 807 } 808 809 close(h.doneCh) 810 return nil 811 } 812 813 func (h *lxcDriverHandle) Signal(s os.Signal) error { 814 return fmt.Errorf("LXC does not support signals") 815 } 816 817 func (h *lxcDriverHandle) Stats() (*cstructs.TaskResourceUsage, error) { 818 cpuStats, err := h.container.CPUStats() 819 if err != nil { 820 return nil, nil 821 } 822 total, err := h.container.CPUTime() 823 if err != nil { 824 return nil, nil 825 } 826 827 t := time.Now() 828 829 // Get the cpu stats 830 system := cpuStats["system"] 831 user := cpuStats["user"] 832 cs := &cstructs.CpuStats{ 833 SystemMode: h.systemCpuStats.Percent(float64(system)), 834 UserMode: h.systemCpuStats.Percent(float64(user)), 835 Percent: h.totalCpuStats.Percent(float64(total)), 836 TotalTicks: float64(user + system), 837 Measured: LXCMeasuredCpuStats, 838 } 839 840 // Get the Memory Stats 841 memData := map[string]uint64{ 842 "rss": 0, 843 "cache": 0, 844 "swap": 0, 845 } 846 rawMemStats := h.container.CgroupItem("memory.stat") 847 for _, rawMemStat := range rawMemStats { 848 key, val, err := keysToVal(rawMemStat) 849 if err != nil { 850 h.logger.Printf("[ERR] driver.lxc: error getting stat for line %q", rawMemStat) 851 continue 852 } 853 if _, ok := memData[key]; ok { 854 memData[key] = val 855 856 } 857 } 858 ms := &cstructs.MemoryStats{ 859 RSS: memData["rss"], 860 Cache: memData["cache"], 861 Swap: memData["swap"], 862 Measured: LXCMeasuredMemStats, 863 } 864 865 mu := h.container.CgroupItem("memory.max_usage_in_bytes") 866 for _, rawMemMaxUsage := range mu { 867 val, err := strconv.ParseUint(rawMemMaxUsage, 10, 64) 868 if err != nil { 869 h.logger.Printf("[ERR] driver.lxc: unable to get max memory usage: %v", err) 870 continue 871 } 872 ms.MaxUsage = val 873 } 874 ku := h.container.CgroupItem("memory.kmem.usage_in_bytes") 875 for _, rawKernelUsage := range ku { 876 val, err := strconv.ParseUint(rawKernelUsage, 10, 64) 877 if err != nil { 878 h.logger.Printf("[ERR] driver.lxc: unable to get kernel memory usage: %v", err) 879 continue 880 } 881 ms.KernelUsage = val 882 } 883 884 mku := h.container.CgroupItem("memory.kmem.max_usage_in_bytes") 885 for _, rawMaxKernelUsage := range mku { 886 val, err := strconv.ParseUint(rawMaxKernelUsage, 10, 64) 887 if err != nil { 888 h.logger.Printf("[ERR] driver.lxc: unable to get max kernel memory usage: %v", err) 889 continue 890 } 891 ms.KernelMaxUsage = val 892 } 893 894 taskResUsage := cstructs.TaskResourceUsage{ 895 ResourceUsage: &cstructs.ResourceUsage{ 896 CpuStats: cs, 897 MemoryStats: ms, 898 }, 899 Timestamp: t.UTC().UnixNano(), 900 } 901 902 return &taskResUsage, nil 903 } 904 905 func (h *lxcDriverHandle) run() { 906 defer close(h.waitCh) 907 timer := time.NewTimer(containerMonitorIntv) 908 for { 909 select { 910 case <-timer.C: 911 process, err := os.FindProcess(h.initPid) 912 if err != nil { 913 h.waitCh <- &dstructs.WaitResult{Err: err} 914 goto DESTROY 915 } 916 if err := process.Signal(syscall.Signal(0)); err != nil { 917 h.waitCh <- &dstructs.WaitResult{} 918 goto DESTROY 919 } 920 timer.Reset(containerMonitorIntv) 921 case <-h.doneCh: 922 h.waitCh <- &dstructs.WaitResult{} 923 goto DESTROY 924 } 925 } 926 927 DESTROY: 928 name := h.container.Name() 929 if err := h.container.Destroy(); err != nil { 930 h.logger.Printf("[ERR] driver.lxc: error destroying container %q: %v.", name, err) 931 } else { 932 if err := h.container.Release(); err != nil { 933 h.logger.Printf("[ERR] driver.lxc: failed to release container %q after successful destroy: %v", name, err) 934 } else { 935 h.logger.Printf("[INFO] driver.lxc: successfully destroyed and released container %q.", name) 936 } 937 } 938 } 939 940 func keysToVal(line string) (string, uint64, error) { 941 tokens := strings.Split(line, " ") 942 if len(tokens) != 2 { 943 return "", 0, fmt.Errorf("line isn't a k/v pair") 944 } 945 key := tokens[0] 946 val, err := strconv.ParseUint(tokens[1], 10, 64) 947 return key, val, err 948 }