mirror of
https://github.com/kemko/nomad.git
synced 2026-01-03 17:05:43 +03:00
* client: refactor cpuset partitioning This PR updates the way Nomad client manages the split between tasks that make use of resources.cpus vs. resources.cores. Previously, each task was explicitly assigned which CPU cores they were able to run on. Every time a task was started or destroyed, all other tasks' cpusets would need to be updated. This was inefficient and would crush the Linux kernel when a client would try to run ~400 or so tasks. Now, we make use of cgroup heirarchy and cpuset inheritence to efficiently manage cpusets. * cr: tweaks for feedback
90 lines
2.5 KiB
Go
90 lines
2.5 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package proclib
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
)
|
|
|
|
// Task records the unique coordinates of a task from the perspective of a Nomad
|
|
// client running the task, that is to say (alloc_id, task_name). Also indicates
|
|
// whether the task is making use of reserved cpu cores.
|
|
type Task struct {
|
|
AllocID string
|
|
Task string
|
|
Cores bool
|
|
}
|
|
|
|
func (task Task) String() string {
|
|
return fmt.Sprintf("%s/%s", task.AllocID[0:8], task.Task)
|
|
}
|
|
|
|
type create func(Task) ProcessWrangler
|
|
|
|
// Wranglers keeps track of the ProcessWrangler created for each task. Some
|
|
// operating systems may implement ProcessWranglers to ensure that all of the
|
|
// processes created by a Task are killed, going a step beyond trusting the
|
|
// task drivers to properly clean things up. (Well, on Linux anyway.)
|
|
//
|
|
// This state must be restored on Client agent startup.
|
|
type Wranglers struct {
|
|
configs *Configs
|
|
create create
|
|
|
|
lock sync.Mutex
|
|
m map[Task]ProcessWrangler
|
|
}
|
|
|
|
// Setup any process management technique relevant to the operating system and
|
|
// its particular configuration.
|
|
func (w *Wranglers) Setup(task Task) error {
|
|
w.configs.Logger.Trace("setup client process management", "task", task)
|
|
|
|
// create process wrangler for task
|
|
pw := w.create(task)
|
|
|
|
// perform any initialization if necessary (e.g. create cgroup)
|
|
// if this doesn't work just keep going; it's up to each task driver
|
|
// implementation to decide if this is a failure mode
|
|
_ = pw.Initialize()
|
|
|
|
w.lock.Lock()
|
|
defer w.lock.Unlock()
|
|
|
|
// keep track of the process wrangler for task
|
|
w.m[task] = pw
|
|
|
|
return nil
|
|
}
|
|
|
|
// Destroy any processes still running that were spawned by task. Ideally the
|
|
// task driver should be implemented well enough for this to not be necessary,
|
|
// but we protect the Client as best we can regardless.
|
|
//
|
|
// Note that this is called from a TR.Stop which must be idempotent.
|
|
func (w *Wranglers) Destroy(task Task) error {
|
|
w.configs.Logger.Trace("destroy and cleanup remnant task processes", "task", task)
|
|
|
|
w.lock.Lock()
|
|
defer w.lock.Unlock()
|
|
|
|
if pw, exists := w.m[task]; exists {
|
|
pw.Kill()
|
|
pw.Cleanup()
|
|
delete(w.m, task)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// A ProcessWrangler "owns" a particular Task on a client, enabling the client
|
|
// to kill and cleanup processes created by that Task, without help from the
|
|
// task driver. Currently we have implementations only for Linux (via cgroups).
|
|
type ProcessWrangler interface {
|
|
Initialize() error
|
|
Kill() error
|
|
Cleanup() error
|
|
}
|