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