mirror of
https://github.com/kemko/nomad.git
synced 2026-01-08 11:25:41 +03:00
While investigating a report around possible consul-template shutdown issues, which didn't bear fruit, I found that some of the logic around template runner shutdown is unintuitive. * Add some doc strings to the places where someone might think we should be obviously stopping the runner or returning early. * Mark context argument for `Poststart`, `Stop`, and `Update` hooks as unused. No functional code changes.
309 lines
8.5 KiB
Go
309 lines
8.5 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package taskrunner
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
log "github.com/hashicorp/go-hclog"
|
|
"github.com/hashicorp/nomad/client/allocrunner/interfaces"
|
|
ti "github.com/hashicorp/nomad/client/allocrunner/taskrunner/interfaces"
|
|
"github.com/hashicorp/nomad/client/allocrunner/taskrunner/template"
|
|
"github.com/hashicorp/nomad/client/config"
|
|
cstructs "github.com/hashicorp/nomad/client/structs"
|
|
"github.com/hashicorp/nomad/client/taskenv"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
)
|
|
|
|
const (
|
|
templateHookName = "template"
|
|
)
|
|
|
|
type templateHookConfig struct {
|
|
// the allocation
|
|
alloc *structs.Allocation
|
|
|
|
// logger is used to log
|
|
logger log.Logger
|
|
|
|
// lifecycle is used to interact with the task's lifecycle
|
|
lifecycle ti.TaskLifecycle
|
|
|
|
// events is used to emit events
|
|
events ti.EventEmitter
|
|
|
|
// templates is the set of templates we are managing
|
|
templates []*structs.Template
|
|
|
|
// clientConfig is the Nomad Client configuration
|
|
clientConfig *config.Config
|
|
|
|
// envBuilder is the environment variable builder for the task.
|
|
envBuilder *taskenv.Builder
|
|
|
|
// consulNamespace is the current Consul namespace
|
|
consulNamespace string
|
|
|
|
// nomadNamespace is the job's Nomad namespace
|
|
nomadNamespace string
|
|
|
|
// renderOnTaskRestart is flag to explicitly render templates on task restart
|
|
renderOnTaskRestart bool
|
|
|
|
// hookResources are used to fetch Consul tokens
|
|
hookResources *cstructs.AllocHookResources
|
|
}
|
|
|
|
type templateHook struct {
|
|
config *templateHookConfig
|
|
|
|
// logger is used to log
|
|
logger log.Logger
|
|
|
|
// templateManager is used to manage any consul-templates this task may have
|
|
templateManager *template.TaskTemplateManager
|
|
managerLock sync.Mutex
|
|
|
|
// driverHandle is the task driver executor used by the template manager to
|
|
// run scripts when the template change mode is set to script.
|
|
//
|
|
// Must obtain a managerLock before changing. It may be nil.
|
|
driverHandle ti.ScriptExecutor
|
|
|
|
// consulNamespace is the current Consul namespace
|
|
consulNamespace string
|
|
|
|
// vaultToken is the current Vault token
|
|
vaultToken string
|
|
|
|
// vaultNamespace is the current Vault namespace
|
|
vaultNamespace string
|
|
|
|
// nomadToken is the current Nomad token
|
|
nomadToken string
|
|
|
|
// consulToken is the Consul ACL token obtained from consul_hook via
|
|
// workload identity
|
|
consulToken string
|
|
|
|
// task is the task that defines these templates
|
|
task *structs.Task
|
|
|
|
// taskDir is the task directory
|
|
taskDir string
|
|
|
|
// taskID is a unique identifier for this templateHook, for use in
|
|
// downstream platform-specific template runner consumers
|
|
taskID string
|
|
}
|
|
|
|
func newTemplateHook(config *templateHookConfig) *templateHook {
|
|
return &templateHook{
|
|
config: config,
|
|
consulNamespace: config.consulNamespace,
|
|
logger: config.logger.Named(templateHookName),
|
|
}
|
|
}
|
|
|
|
func (*templateHook) Name() string {
|
|
return templateHookName
|
|
}
|
|
|
|
func (h *templateHook) Prestart(ctx context.Context, req *interfaces.TaskPrestartRequest, resp *interfaces.TaskPrestartResponse) error {
|
|
h.managerLock.Lock()
|
|
defer h.managerLock.Unlock()
|
|
|
|
// If we have already run prerun before exit early.
|
|
if h.templateManager != nil {
|
|
if !h.config.renderOnTaskRestart {
|
|
return nil
|
|
}
|
|
h.logger.Info("re-rendering templates on task restart")
|
|
h.templateManager.Stop()
|
|
h.templateManager = nil
|
|
}
|
|
|
|
// Store request information so they can be used in other hooks.
|
|
h.task = req.Task
|
|
h.taskDir = req.TaskDir.Dir
|
|
h.vaultToken = req.VaultToken
|
|
h.nomadToken = req.NomadToken
|
|
h.taskID = req.Alloc.ID + "-" + req.Task.Name
|
|
|
|
// Set the consul token if the task uses WI.
|
|
tg := h.config.alloc.Job.LookupTaskGroup(h.config.alloc.TaskGroup)
|
|
consulBlock := tg.Consul
|
|
if req.Task.Consul != nil {
|
|
consulBlock = req.Task.Consul
|
|
}
|
|
consulWIDName := consulBlock.IdentityName()
|
|
|
|
// Check if task has an identity for Consul and assume WI flow if it does.
|
|
// COMPAT simplify this logic and assume WI flow in 1.9+
|
|
hasConsulIdentity := false
|
|
for _, wid := range req.Task.Identities {
|
|
if wid.Name == consulWIDName {
|
|
hasConsulIdentity = true
|
|
break
|
|
}
|
|
}
|
|
if hasConsulIdentity {
|
|
consulCluster := req.Task.GetConsulClusterName(tg)
|
|
consulTokens := h.config.hookResources.GetConsulTokens()
|
|
clusterTokens := consulTokens[consulCluster]
|
|
|
|
if clusterTokens == nil {
|
|
return fmt.Errorf(
|
|
"consul tokens for cluster %s requested by task %s not found",
|
|
consulCluster, req.Task.Name,
|
|
)
|
|
}
|
|
|
|
consulToken := clusterTokens[consulWIDName]
|
|
if consulToken == nil {
|
|
return fmt.Errorf(
|
|
"consul tokens for cluster %s and identity %s requested by task %s not found",
|
|
consulCluster, consulWIDName, req.Task.Name,
|
|
)
|
|
}
|
|
|
|
h.consulToken = consulToken.SecretID
|
|
}
|
|
|
|
// Set vault namespace if specified
|
|
if req.Task.Vault != nil {
|
|
h.vaultNamespace = req.Task.Vault.Namespace
|
|
}
|
|
|
|
unblockCh, err := h.newManager()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Wait for the template to render
|
|
select {
|
|
case <-ctx.Done():
|
|
case <-unblockCh:
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *templateHook) Poststart(_ context.Context, req *interfaces.TaskPoststartRequest, resp *interfaces.TaskPoststartResponse) error {
|
|
h.managerLock.Lock()
|
|
defer h.managerLock.Unlock()
|
|
|
|
if h.templateManager == nil {
|
|
return nil
|
|
}
|
|
|
|
if req.DriverExec != nil {
|
|
h.driverHandle = req.DriverExec
|
|
h.templateManager.SetDriverHandle(h.driverHandle)
|
|
} else {
|
|
for _, tmpl := range h.config.templates {
|
|
if tmpl.ChangeMode == structs.TemplateChangeModeScript {
|
|
return fmt.Errorf("template has change mode set to 'script' but the driver it uses does not provide exec capability")
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *templateHook) newManager() (unblock chan struct{}, err error) {
|
|
unblock = make(chan struct{})
|
|
|
|
vaultCluster := h.task.GetVaultClusterName()
|
|
vaultConfig := h.config.clientConfig.GetVaultConfigs(h.logger)[vaultCluster]
|
|
|
|
// Fail if task has a vault block but not client config was found.
|
|
if h.task.Vault != nil && vaultConfig == nil {
|
|
return nil, fmt.Errorf("Vault cluster %q is disabled or not configured", vaultCluster)
|
|
}
|
|
|
|
tg := h.config.alloc.Job.LookupTaskGroup(h.config.alloc.TaskGroup)
|
|
consulCluster := h.task.GetConsulClusterName(tg)
|
|
consulConfig := h.config.clientConfig.GetConsulConfigs(h.logger)[consulCluster]
|
|
|
|
m, err := template.NewTaskTemplateManager(&template.TaskTemplateManagerConfig{
|
|
UnblockCh: unblock,
|
|
Lifecycle: h.config.lifecycle,
|
|
Events: h.config.events,
|
|
Templates: h.config.templates,
|
|
ClientConfig: h.config.clientConfig,
|
|
ConsulNamespace: h.config.consulNamespace,
|
|
ConsulToken: h.consulToken,
|
|
ConsulConfig: consulConfig,
|
|
VaultToken: h.vaultToken,
|
|
VaultConfig: vaultConfig,
|
|
VaultNamespace: h.vaultNamespace,
|
|
TaskDir: h.taskDir,
|
|
EnvBuilder: h.config.envBuilder,
|
|
MaxTemplateEventRate: template.DefaultMaxTemplateEventRate,
|
|
NomadNamespace: h.config.nomadNamespace,
|
|
NomadToken: h.nomadToken,
|
|
TaskID: h.taskID,
|
|
Logger: h.logger,
|
|
})
|
|
if err != nil {
|
|
h.logger.Error("failed to create template manager", "error", err)
|
|
return nil, err
|
|
}
|
|
|
|
h.templateManager = m
|
|
if h.driverHandle != nil {
|
|
h.templateManager.SetDriverHandle(h.driverHandle)
|
|
}
|
|
return unblock, nil
|
|
}
|
|
|
|
func (h *templateHook) Stop(_ context.Context, req *interfaces.TaskStopRequest, resp *interfaces.TaskStopResponse) error {
|
|
h.managerLock.Lock()
|
|
defer h.managerLock.Unlock()
|
|
|
|
// Shutdown any created template
|
|
if h.templateManager != nil {
|
|
h.templateManager.Stop()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Update is used to handle updates to vault and/or nomad tokens.
|
|
func (h *templateHook) Update(_ context.Context, req *interfaces.TaskUpdateRequest, resp *interfaces.TaskUpdateResponse) error {
|
|
h.managerLock.Lock()
|
|
defer h.managerLock.Unlock()
|
|
|
|
// no template manager to manage
|
|
if h.templateManager == nil {
|
|
return nil
|
|
}
|
|
|
|
// neither vault or nomad token has been updated, nothing to do
|
|
if req.VaultToken == h.vaultToken && req.NomadToken == h.nomadToken {
|
|
return nil
|
|
} else {
|
|
h.vaultToken = req.VaultToken
|
|
h.nomadToken = req.NomadToken
|
|
}
|
|
|
|
// shutdown the old template
|
|
h.templateManager.Stop()
|
|
h.templateManager = nil
|
|
|
|
// create the new template
|
|
if _, err := h.newManager(); err != nil {
|
|
err = fmt.Errorf("failed to build template manager: %v", err)
|
|
h.logger.Error("failed to build template manager", "error", err)
|
|
_ = h.config.lifecycle.Kill(context.Background(),
|
|
structs.NewTaskEvent(structs.TaskKilling).
|
|
SetFailsTask().
|
|
SetDisplayMessage(fmt.Sprintf("Template update %v", err)))
|
|
}
|
|
|
|
return nil
|
|
}
|