mirror of
https://github.com/kemko/nomad.git
synced 2026-01-04 01:15:43 +03:00
Merge pull request #8011 from hashicorp/f-cnative-host
consul/connect: implement initial support for connect native
This commit is contained in:
@@ -110,6 +110,7 @@ type Service struct {
|
||||
Connect *ConsulConnect
|
||||
Meta map[string]string
|
||||
CanaryMeta map[string]string
|
||||
TaskName string `mapstructure:"task"`
|
||||
}
|
||||
|
||||
// Canonicalize the Service by ensuring its name and address mode are set. Task
|
||||
|
||||
@@ -69,7 +69,7 @@ func TestService_Connect_Canonicalize(t *testing.T) {
|
||||
t.Run("empty connect", func(t *testing.T) {
|
||||
cc := new(ConsulConnect)
|
||||
cc.Canonicalize()
|
||||
require.False(t, cc.Native)
|
||||
require.Empty(t, cc.Native)
|
||||
require.Nil(t, cc.SidecarService)
|
||||
require.Nil(t, cc.SidecarTask)
|
||||
})
|
||||
|
||||
@@ -52,7 +52,7 @@ func (*consulSockHook) Name() string {
|
||||
func (h *consulSockHook) shouldRun() bool {
|
||||
tg := h.alloc.Job.LookupTaskGroup(h.alloc.TaskGroup)
|
||||
for _, s := range tg.Services {
|
||||
if s.Connect != nil {
|
||||
if s.Connect.HasSidecar() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
221
client/allocrunner/taskrunner/connect_native_hook.go
Normal file
221
client/allocrunner/taskrunner/connect_native_hook.go
Normal file
@@ -0,0 +1,221 @@
|
||||
package taskrunner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
ifs "github.com/hashicorp/nomad/client/allocrunner/interfaces"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
"github.com/hashicorp/nomad/nomad/structs/config"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
connectNativeHookName = "connect_native"
|
||||
)
|
||||
|
||||
type connectNativeHookConfig struct {
|
||||
consulShareTLS bool
|
||||
consul consulTransportConfig
|
||||
alloc *structs.Allocation
|
||||
logger hclog.Logger
|
||||
}
|
||||
|
||||
func newConnectNativeHookConfig(alloc *structs.Allocation, consul *config.ConsulConfig, logger hclog.Logger) *connectNativeHookConfig {
|
||||
return &connectNativeHookConfig{
|
||||
alloc: alloc,
|
||||
logger: logger,
|
||||
consulShareTLS: consul.ShareSSL == nil || *consul.ShareSSL, // default enabled
|
||||
consul: newConsulTransportConfig(consul),
|
||||
}
|
||||
}
|
||||
|
||||
// connectNativeHook manages additional automagic configuration for a connect
|
||||
// native task.
|
||||
//
|
||||
// If nomad client is configured to talk to Consul using TLS (or other special
|
||||
// auth), the native task will inherit that configuration EXCEPT for the consul
|
||||
// token.
|
||||
//
|
||||
// If consul is configured with ACLs enabled, a Service Identity token will be
|
||||
// generated on behalf of the native service and supplied to the task.
|
||||
type connectNativeHook struct {
|
||||
// alloc is the allocation with the connect native task being run
|
||||
alloc *structs.Allocation
|
||||
|
||||
// consulShareTLS is used to toggle whether the TLS configuration of the
|
||||
// Nomad Client may be shared with Connect Native applications.
|
||||
consulShareTLS bool
|
||||
|
||||
// consulConfig is used to enable the connect native enabled task to
|
||||
// communicate with consul directly, as is necessary for the task to request
|
||||
// its connect mTLS certificates.
|
||||
consulConfig consulTransportConfig
|
||||
|
||||
// logger is used to log things
|
||||
logger hclog.Logger
|
||||
}
|
||||
|
||||
func newConnectNativeHook(c *connectNativeHookConfig) *connectNativeHook {
|
||||
return &connectNativeHook{
|
||||
alloc: c.alloc,
|
||||
consulShareTLS: c.consulShareTLS,
|
||||
consulConfig: c.consul,
|
||||
logger: c.logger.Named(connectNativeHookName),
|
||||
}
|
||||
}
|
||||
|
||||
func (connectNativeHook) Name() string {
|
||||
return connectNativeHookName
|
||||
}
|
||||
|
||||
func (h *connectNativeHook) Prestart(
|
||||
ctx context.Context,
|
||||
request *ifs.TaskPrestartRequest,
|
||||
response *ifs.TaskPrestartResponse) error {
|
||||
|
||||
if !request.Task.Kind.IsConnectNative() {
|
||||
response.Done = true
|
||||
return nil
|
||||
}
|
||||
|
||||
if h.consulShareTLS {
|
||||
// copy TLS certificates
|
||||
if err := h.copyCertificates(h.consulConfig, request.TaskDir.SecretsDir); err != nil {
|
||||
h.logger.Error("failed to copy Consul TLS certificates", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// set environment variables for communicating with Consul agent, but
|
||||
// only if those environment variables are not already set
|
||||
response.Env = h.tlsEnv(request.TaskEnv.EnvMap)
|
||||
|
||||
}
|
||||
|
||||
if err := h.maybeSetSITokenEnv(request.TaskDir.SecretsDir, request.Task.Name, response.Env); err != nil {
|
||||
h.logger.Error("failed to load Consul Service Identity Token", "error", err, "task", request.Task.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
// tls/acl setup for native task done
|
||||
response.Done = true
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
secretCAFilename = "consul_ca_file.pem"
|
||||
secretCertfileFilename = "consul_cert_file.pem"
|
||||
secretKeyfileFilename = "consul_key_file.pem"
|
||||
)
|
||||
|
||||
func (h *connectNativeHook) copyCertificates(consulConfig consulTransportConfig, dir string) error {
|
||||
if err := h.copyCertificate(consulConfig.CAFile, dir, secretCAFilename); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := h.copyCertificate(consulConfig.CertFile, dir, secretCertfileFilename); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := h.copyCertificate(consulConfig.KeyFile, dir, secretKeyfileFilename); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (connectNativeHook) copyCertificate(source, dir, name string) error {
|
||||
if source == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
original, err := os.Open(source)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to open consul TLS certificate")
|
||||
}
|
||||
defer original.Close()
|
||||
|
||||
destination := filepath.Join(dir, name)
|
||||
fd, err := os.Create(destination)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to create secrets/%s", name)
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
if _, err := io.Copy(fd, original); err != nil {
|
||||
return errors.Wrapf(err, "failed to copy certificate secrets/%s", name)
|
||||
}
|
||||
|
||||
if err := fd.Sync(); err != nil {
|
||||
return errors.Wrapf(err, "failed to write secrets/%s", name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// tlsEnv creates a set of additional of environment variables to be used when launching
|
||||
// the connect native task. This will enable the task to communicate with Consul
|
||||
// if Consul has transport security turned on.
|
||||
//
|
||||
// We do NOT set CONSUL_HTTP_TOKEN from the nomad agent's consul config, as that
|
||||
// is a separate security concern addressed by the service identity hook.
|
||||
func (h *connectNativeHook) tlsEnv(env map[string]string) map[string]string {
|
||||
m := make(map[string]string)
|
||||
|
||||
if _, exists := env["CONSUL_CACERT"]; !exists && h.consulConfig.CAFile != "" {
|
||||
m["CONSUL_CACERT"] = filepath.Join("/secrets", secretCAFilename)
|
||||
}
|
||||
|
||||
if _, exists := env["CONSUL_CLIENT_CERT"]; !exists && h.consulConfig.CertFile != "" {
|
||||
m["CONSUL_CLIENT_CERT"] = filepath.Join("/secrets", secretCertfileFilename)
|
||||
}
|
||||
|
||||
if _, exists := env["CONSUL_CLIENT_KEY"]; !exists && h.consulConfig.KeyFile != "" {
|
||||
m["CONSUL_CLIENT_KEY"] = filepath.Join("/secrets", secretKeyfileFilename)
|
||||
}
|
||||
|
||||
if _, exists := env["CONSUL_HTTP_SSL"]; !exists {
|
||||
if v := h.consulConfig.SSL; v != "" {
|
||||
m["CONSUL_HTTP_SSL"] = v
|
||||
}
|
||||
}
|
||||
|
||||
if _, exists := env["CONSUL_HTTP_SSL_VERIFY"]; !exists {
|
||||
if v := h.consulConfig.VerifySSL; v != "" {
|
||||
m["CONSUL_HTTP_SSL_VERIFY"] = v
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// maybeSetSITokenEnv will set the CONSUL_HTTP_TOKEN environment variable in
|
||||
// the given env map, if the token is found to exist in the task's secrets
|
||||
// directory AND the CONSUL_HTTP_TOKEN environment variable is not already set.
|
||||
//
|
||||
// Following the pattern of the envoy_bootstrap_hook, the Consul Service Identity
|
||||
// ACL Token is generated prior to this hook, if Consul ACLs are enabled. This is
|
||||
// done in the sids_hook, which places the token at secrets/si_token in the task
|
||||
// workspace. The content of that file is the SI token specific to this task
|
||||
// instance.
|
||||
func (h *connectNativeHook) maybeSetSITokenEnv(dir, task string, env map[string]string) error {
|
||||
if _, exists := env["CONSUL_HTTP_TOKEN"]; exists {
|
||||
// Consul token was already set - typically by using the Vault integration
|
||||
// and a template stanza to set the environment. Ignore the SI token as
|
||||
// the configured token takes precedence.
|
||||
return nil
|
||||
}
|
||||
|
||||
token, err := ioutil.ReadFile(filepath.Join(dir, sidsTokenFile))
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return errors.Wrapf(err, "failed to load SI token for native task %s", task)
|
||||
}
|
||||
h.logger.Trace("no SI token to load for native task", "task", task)
|
||||
return nil // token file DNE; acls not enabled
|
||||
}
|
||||
h.logger.Trace("recovered pre-existing SI token for native task", "task", task)
|
||||
env["CONSUL_HTTP_TOKEN"] = string(token)
|
||||
return nil
|
||||
}
|
||||
545
client/allocrunner/taskrunner/connect_native_hook_test.go
Normal file
545
client/allocrunner/taskrunner/connect_native_hook_test.go
Normal file
@@ -0,0 +1,545 @@
|
||||
package taskrunner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
consulapi "github.com/hashicorp/consul/api"
|
||||
consultest "github.com/hashicorp/consul/sdk/testutil"
|
||||
"github.com/hashicorp/nomad/client/allocdir"
|
||||
"github.com/hashicorp/nomad/client/allocrunner/interfaces"
|
||||
"github.com/hashicorp/nomad/client/taskenv"
|
||||
"github.com/hashicorp/nomad/client/testutil"
|
||||
agentconsul "github.com/hashicorp/nomad/command/agent/consul"
|
||||
"github.com/hashicorp/nomad/helper"
|
||||
"github.com/hashicorp/nomad/helper/testlog"
|
||||
"github.com/hashicorp/nomad/helper/uuid"
|
||||
"github.com/hashicorp/nomad/nomad/mock"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
"github.com/hashicorp/nomad/nomad/structs/config"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func getTestConsul(t *testing.T) *consultest.TestServer {
|
||||
testConsul, err := consultest.NewTestServerConfig(func(c *consultest.TestServerConfig) {
|
||||
if !testing.Verbose() { // disable consul logging if -v not set
|
||||
c.Stdout = ioutil.Discard
|
||||
c.Stderr = ioutil.Discard
|
||||
}
|
||||
})
|
||||
require.NoError(t, err, "failed to start test consul server")
|
||||
return testConsul
|
||||
}
|
||||
|
||||
func TestConnectNativeHook_Name(t *testing.T) {
|
||||
t.Parallel()
|
||||
name := new(connectNativeHook).Name()
|
||||
require.Equal(t, "connect_native", name)
|
||||
}
|
||||
|
||||
func setupCertDirs(t *testing.T) (string, string) {
|
||||
fd, err := ioutil.TempFile("", "connect_native_testcert")
|
||||
require.NoError(t, err)
|
||||
_, err = fd.WriteString("ABCDEF")
|
||||
require.NoError(t, err)
|
||||
err = fd.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
d, err := ioutil.TempDir("", "connect_native_testsecrets")
|
||||
require.NoError(t, err)
|
||||
return fd.Name(), d
|
||||
}
|
||||
|
||||
func cleanupCertDirs(t *testing.T, original, secrets string) {
|
||||
err := os.Remove(original)
|
||||
require.NoError(t, err)
|
||||
err = os.RemoveAll(secrets)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestConnectNativeHook_copyCertificate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f, d := setupCertDirs(t)
|
||||
defer cleanupCertDirs(t, f, d)
|
||||
|
||||
t.Run("no source", func(t *testing.T) {
|
||||
err := new(connectNativeHook).copyCertificate("", d, "out.pem")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("normal", func(t *testing.T) {
|
||||
err := new(connectNativeHook).copyCertificate(f, d, "out.pem")
|
||||
require.NoError(t, err)
|
||||
b, err := ioutil.ReadFile(filepath.Join(d, "out.pem"))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "ABCDEF", string(b))
|
||||
})
|
||||
}
|
||||
|
||||
func TestConnectNativeHook_copyCertificates(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f, d := setupCertDirs(t)
|
||||
defer cleanupCertDirs(t, f, d)
|
||||
|
||||
t.Run("normal", func(t *testing.T) {
|
||||
err := new(connectNativeHook).copyCertificates(consulTransportConfig{
|
||||
CAFile: f,
|
||||
CertFile: f,
|
||||
KeyFile: f,
|
||||
}, d)
|
||||
require.NoError(t, err)
|
||||
ls, err := ioutil.ReadDir(d)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 3, len(ls))
|
||||
})
|
||||
|
||||
t.Run("no source", func(t *testing.T) {
|
||||
err := new(connectNativeHook).copyCertificates(consulTransportConfig{
|
||||
CAFile: "/does/not/exist.pem",
|
||||
CertFile: "/does/not/exist.pem",
|
||||
KeyFile: "/does/not/exist.pem",
|
||||
}, d)
|
||||
require.EqualError(t, err, "failed to open consul TLS certificate: open /does/not/exist.pem: no such file or directory")
|
||||
})
|
||||
}
|
||||
|
||||
func TestConnectNativeHook_tlsEnv(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// the hook config comes from client config
|
||||
emptyHook := new(connectNativeHook)
|
||||
fullHook := &connectNativeHook{
|
||||
consulConfig: consulTransportConfig{
|
||||
Auth: "user:password",
|
||||
SSL: "true",
|
||||
VerifySSL: "true",
|
||||
CAFile: "/not/real/ca.pem",
|
||||
CertFile: "/not/real/cert.pem",
|
||||
KeyFile: "/not/real/key.pem",
|
||||
},
|
||||
}
|
||||
|
||||
// existing config from task env stanza
|
||||
taskEnv := map[string]string{
|
||||
"CONSUL_CACERT": "fakeCA.pem",
|
||||
"CONSUL_CLIENT_CERT": "fakeCert.pem",
|
||||
"CONSUL_CLIENT_KEY": "fakeKey.pem",
|
||||
"CONSUL_HTTP_AUTH": "foo:bar",
|
||||
"CONSUL_HTTP_SSL": "false",
|
||||
"CONSUL_HTTP_SSL_VERIFY": "false",
|
||||
}
|
||||
|
||||
t.Run("empty hook and empty task", func(t *testing.T) {
|
||||
result := emptyHook.tlsEnv(nil)
|
||||
require.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("empty hook and non-empty task", func(t *testing.T) {
|
||||
result := emptyHook.tlsEnv(taskEnv)
|
||||
require.Empty(t, result) // tlsEnv only overrides; task env is actually set elsewhere
|
||||
})
|
||||
|
||||
t.Run("non-empty hook and empty task", func(t *testing.T) {
|
||||
result := fullHook.tlsEnv(nil)
|
||||
require.Equal(t, map[string]string{
|
||||
// ca files are specifically copied into FS namespace
|
||||
"CONSUL_CACERT": "/secrets/consul_ca_file.pem",
|
||||
"CONSUL_CLIENT_CERT": "/secrets/consul_cert_file.pem",
|
||||
"CONSUL_CLIENT_KEY": "/secrets/consul_key_file.pem",
|
||||
"CONSUL_HTTP_SSL": "true",
|
||||
"CONSUL_HTTP_SSL_VERIFY": "true",
|
||||
}, result)
|
||||
})
|
||||
|
||||
t.Run("non-empty hook and non-empty task", func(t *testing.T) {
|
||||
result := fullHook.tlsEnv(taskEnv) // task env takes precedence, nothing gets set here
|
||||
require.Empty(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTaskRunner_ConnectNativeHook_Noop(t *testing.T) {
|
||||
t.Parallel()
|
||||
logger := testlog.HCLogger(t)
|
||||
|
||||
allocDir, cleanup := allocdir.TestAllocDir(t, logger, "ConnectNative")
|
||||
defer cleanup()
|
||||
|
||||
alloc := mock.Alloc()
|
||||
task := alloc.Job.LookupTaskGroup(alloc.TaskGroup).Tasks[0]
|
||||
|
||||
// run the connect native hook. use invalid consul address as it should not get hit
|
||||
h := newConnectNativeHook(newConnectNativeHookConfig(alloc, &config.ConsulConfig{
|
||||
Addr: "http://127.0.0.2:1",
|
||||
}, logger))
|
||||
|
||||
request := &interfaces.TaskPrestartRequest{
|
||||
Task: task,
|
||||
TaskDir: allocDir.NewTaskDir(task.Name),
|
||||
}
|
||||
require.NoError(t, request.TaskDir.Build(false, nil))
|
||||
|
||||
response := new(interfaces.TaskPrestartResponse)
|
||||
|
||||
// Run the hook
|
||||
require.NoError(t, h.Prestart(context.Background(), request, response))
|
||||
|
||||
// Assert the hook is Done
|
||||
require.True(t, response.Done)
|
||||
|
||||
// Assert secrets dir is empty (no TLS config set)
|
||||
checkFilesInDir(t, request.TaskDir.SecretsDir,
|
||||
nil,
|
||||
[]string{sidsTokenFile, secretCAFilename, secretCertfileFilename, secretKeyfileFilename},
|
||||
)
|
||||
}
|
||||
|
||||
func TestTaskRunner_ConnectNativeHook_Ok(t *testing.T) {
|
||||
t.Parallel()
|
||||
testutil.RequireConsul(t)
|
||||
|
||||
testConsul := getTestConsul(t)
|
||||
defer testConsul.Stop()
|
||||
|
||||
alloc := mock.Alloc()
|
||||
alloc.AllocatedResources.Shared.Networks = []*structs.NetworkResource{{Mode: "host", IP: "1.1.1.1"}}
|
||||
tg := alloc.Job.TaskGroups[0]
|
||||
tg.Services = []*structs.Service{{
|
||||
Name: "cn-service",
|
||||
TaskName: tg.Tasks[0].Name,
|
||||
Connect: &structs.ConsulConnect{
|
||||
Native: true,
|
||||
}},
|
||||
}
|
||||
tg.Tasks[0].Kind = structs.NewTaskKind("connect-native", "cn-service")
|
||||
|
||||
logger := testlog.HCLogger(t)
|
||||
|
||||
allocDir, cleanup := allocdir.TestAllocDir(t, logger, "ConnectNative")
|
||||
defer cleanup()
|
||||
|
||||
// register group services
|
||||
consulConfig := consulapi.DefaultConfig()
|
||||
consulConfig.Address = testConsul.HTTPAddr
|
||||
consulAPIClient, err := consulapi.NewClient(consulConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
consulClient := agentconsul.NewServiceClient(consulAPIClient.Agent(), logger, true)
|
||||
go consulClient.Run()
|
||||
defer consulClient.Shutdown()
|
||||
require.NoError(t, consulClient.RegisterWorkload(agentconsul.BuildAllocServices(mock.Node(), alloc, agentconsul.NoopRestarter())))
|
||||
|
||||
// Run Connect Native hook
|
||||
h := newConnectNativeHook(newConnectNativeHookConfig(alloc, &config.ConsulConfig{
|
||||
Addr: consulConfig.Address,
|
||||
}, logger))
|
||||
request := &interfaces.TaskPrestartRequest{
|
||||
Task: tg.Tasks[0],
|
||||
TaskDir: allocDir.NewTaskDir(tg.Tasks[0].Name),
|
||||
TaskEnv: taskenv.NewEmptyTaskEnv(),
|
||||
}
|
||||
require.NoError(t, request.TaskDir.Build(false, nil))
|
||||
|
||||
response := new(interfaces.TaskPrestartResponse)
|
||||
|
||||
// Run the Connect Native hook
|
||||
require.NoError(t, h.Prestart(context.Background(), request, response))
|
||||
|
||||
// Assert the hook is Done
|
||||
require.True(t, response.Done)
|
||||
|
||||
// Assert no environment variables configured to be set
|
||||
require.Empty(t, response.Env)
|
||||
|
||||
// Assert no secrets were written
|
||||
checkFilesInDir(t, request.TaskDir.SecretsDir,
|
||||
nil,
|
||||
[]string{sidsTokenFile, secretCAFilename, secretCertfileFilename, secretKeyfileFilename},
|
||||
)
|
||||
}
|
||||
|
||||
func TestTaskRunner_ConnectNativeHook_with_SI_token(t *testing.T) {
|
||||
t.Parallel()
|
||||
testutil.RequireConsul(t)
|
||||
|
||||
testConsul := getTestConsul(t)
|
||||
defer testConsul.Stop()
|
||||
|
||||
alloc := mock.Alloc()
|
||||
alloc.AllocatedResources.Shared.Networks = []*structs.NetworkResource{{Mode: "host", IP: "1.1.1.1"}}
|
||||
tg := alloc.Job.TaskGroups[0]
|
||||
tg.Services = []*structs.Service{{
|
||||
Name: "cn-service",
|
||||
TaskName: tg.Tasks[0].Name,
|
||||
Connect: &structs.ConsulConnect{
|
||||
Native: true,
|
||||
}},
|
||||
}
|
||||
tg.Tasks[0].Kind = structs.NewTaskKind("connect-native", "cn-service")
|
||||
|
||||
logger := testlog.HCLogger(t)
|
||||
|
||||
allocDir, cleanup := allocdir.TestAllocDir(t, logger, "ConnectNative")
|
||||
defer cleanup()
|
||||
|
||||
// register group services
|
||||
consulConfig := consulapi.DefaultConfig()
|
||||
consulConfig.Address = testConsul.HTTPAddr
|
||||
consulAPIClient, err := consulapi.NewClient(consulConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
consulClient := agentconsul.NewServiceClient(consulAPIClient.Agent(), logger, true)
|
||||
go consulClient.Run()
|
||||
defer consulClient.Shutdown()
|
||||
require.NoError(t, consulClient.RegisterWorkload(agentconsul.BuildAllocServices(mock.Node(), alloc, agentconsul.NoopRestarter())))
|
||||
|
||||
// Run Connect Native hook
|
||||
h := newConnectNativeHook(newConnectNativeHookConfig(alloc, &config.ConsulConfig{
|
||||
Addr: consulConfig.Address,
|
||||
}, logger))
|
||||
request := &interfaces.TaskPrestartRequest{
|
||||
Task: tg.Tasks[0],
|
||||
TaskDir: allocDir.NewTaskDir(tg.Tasks[0].Name),
|
||||
TaskEnv: taskenv.NewEmptyTaskEnv(),
|
||||
}
|
||||
require.NoError(t, request.TaskDir.Build(false, nil))
|
||||
|
||||
// Insert service identity token in the secrets directory
|
||||
token := uuid.Generate()
|
||||
siTokenFile := filepath.Join(request.TaskDir.SecretsDir, sidsTokenFile)
|
||||
err = ioutil.WriteFile(siTokenFile, []byte(token), 0440)
|
||||
require.NoError(t, err)
|
||||
|
||||
response := new(interfaces.TaskPrestartResponse)
|
||||
response.Env = make(map[string]string)
|
||||
|
||||
// Run the Connect Native hook
|
||||
require.NoError(t, h.Prestart(context.Background(), request, response))
|
||||
|
||||
// Assert the hook is Done
|
||||
require.True(t, response.Done)
|
||||
|
||||
// Assert environment variable for token is set
|
||||
require.NotEmpty(t, response.Env)
|
||||
require.Equal(t, token, response.Env["CONSUL_HTTP_TOKEN"])
|
||||
|
||||
// Assert no additional secrets were written
|
||||
checkFilesInDir(t, request.TaskDir.SecretsDir,
|
||||
[]string{sidsTokenFile},
|
||||
[]string{secretCAFilename, secretCertfileFilename, secretKeyfileFilename},
|
||||
)
|
||||
}
|
||||
|
||||
func TestTaskRunner_ConnectNativeHook_shareTLS(t *testing.T) {
|
||||
t.Parallel()
|
||||
testutil.RequireConsul(t)
|
||||
|
||||
try := func(t *testing.T, shareSSL *bool) {
|
||||
fakeCert, fakeCertDir := setupCertDirs(t)
|
||||
defer cleanupCertDirs(t, fakeCert, fakeCertDir)
|
||||
|
||||
testConsul := getTestConsul(t)
|
||||
defer testConsul.Stop()
|
||||
|
||||
alloc := mock.Alloc()
|
||||
alloc.AllocatedResources.Shared.Networks = []*structs.NetworkResource{{Mode: "host", IP: "1.1.1.1"}}
|
||||
tg := alloc.Job.TaskGroups[0]
|
||||
tg.Services = []*structs.Service{{
|
||||
Name: "cn-service",
|
||||
TaskName: tg.Tasks[0].Name,
|
||||
Connect: &structs.ConsulConnect{
|
||||
Native: true,
|
||||
}},
|
||||
}
|
||||
tg.Tasks[0].Kind = structs.NewTaskKind("connect-native", "cn-service")
|
||||
|
||||
logger := testlog.HCLogger(t)
|
||||
|
||||
allocDir, cleanup := allocdir.TestAllocDir(t, logger, "ConnectNative")
|
||||
defer cleanup()
|
||||
|
||||
// register group services
|
||||
consulConfig := consulapi.DefaultConfig()
|
||||
consulConfig.Address = testConsul.HTTPAddr
|
||||
consulAPIClient, err := consulapi.NewClient(consulConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
consulClient := agentconsul.NewServiceClient(consulAPIClient.Agent(), logger, true)
|
||||
go consulClient.Run()
|
||||
defer consulClient.Shutdown()
|
||||
require.NoError(t, consulClient.RegisterWorkload(agentconsul.BuildAllocServices(mock.Node(), alloc, agentconsul.NoopRestarter())))
|
||||
|
||||
// Run Connect Native hook
|
||||
h := newConnectNativeHook(newConnectNativeHookConfig(alloc, &config.ConsulConfig{
|
||||
Addr: consulConfig.Address,
|
||||
|
||||
// TLS config consumed by native application
|
||||
ShareSSL: shareSSL,
|
||||
EnableSSL: helper.BoolToPtr(true),
|
||||
VerifySSL: helper.BoolToPtr(true),
|
||||
CAFile: fakeCert,
|
||||
CertFile: fakeCert,
|
||||
KeyFile: fakeCert,
|
||||
Auth: "user:password",
|
||||
Token: uuid.Generate(),
|
||||
}, logger))
|
||||
request := &interfaces.TaskPrestartRequest{
|
||||
Task: tg.Tasks[0],
|
||||
TaskDir: allocDir.NewTaskDir(tg.Tasks[0].Name),
|
||||
TaskEnv: taskenv.NewEmptyTaskEnv(), // nothing set in env stanza
|
||||
}
|
||||
require.NoError(t, request.TaskDir.Build(false, nil))
|
||||
|
||||
response := new(interfaces.TaskPrestartResponse)
|
||||
response.Env = make(map[string]string)
|
||||
|
||||
// Run the Connect Native hook
|
||||
require.NoError(t, h.Prestart(context.Background(), request, response))
|
||||
|
||||
// Assert the hook is Done
|
||||
require.True(t, response.Done)
|
||||
|
||||
// Assert environment variable for token is set
|
||||
require.NotEmpty(t, response.Env)
|
||||
require.Equal(t, map[string]string{
|
||||
"CONSUL_CACERT": "/secrets/consul_ca_file.pem",
|
||||
"CONSUL_CLIENT_CERT": "/secrets/consul_cert_file.pem",
|
||||
"CONSUL_CLIENT_KEY": "/secrets/consul_key_file.pem",
|
||||
"CONSUL_HTTP_SSL": "true",
|
||||
"CONSUL_HTTP_SSL_VERIFY": "true",
|
||||
}, response.Env)
|
||||
require.NotContains(t, response.Env, "CONSUL_HTTP_AUTH") // explicitly not shared
|
||||
require.NotContains(t, response.Env, "CONSUL_HTTP_TOKEN") // explicitly not shared
|
||||
|
||||
// Assert 3 pem files were written
|
||||
checkFilesInDir(t, request.TaskDir.SecretsDir,
|
||||
[]string{secretCAFilename, secretCertfileFilename, secretKeyfileFilename},
|
||||
[]string{sidsTokenFile},
|
||||
)
|
||||
}
|
||||
|
||||
// The default behavior is that share_ssl is true (similar to allow_unauthenticated)
|
||||
// so make sure an unset value turns the feature on.
|
||||
|
||||
t.Run("share_ssl is true", func(t *testing.T) {
|
||||
try(t, helper.BoolToPtr(true))
|
||||
})
|
||||
|
||||
t.Run("share_ssl is nil", func(t *testing.T) {
|
||||
try(t, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func checkFilesInDir(t *testing.T, dir string, includes, excludes []string) {
|
||||
ls, err := ioutil.ReadDir(dir)
|
||||
require.NoError(t, err)
|
||||
|
||||
var present []string
|
||||
for _, fInfo := range ls {
|
||||
present = append(present, fInfo.Name())
|
||||
}
|
||||
|
||||
for _, filename := range includes {
|
||||
require.Contains(t, present, filename)
|
||||
}
|
||||
for _, filename := range excludes {
|
||||
require.NotContains(t, present, filename)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskRunner_ConnectNativeHook_shareTLS_override(t *testing.T) {
|
||||
t.Parallel()
|
||||
testutil.RequireConsul(t)
|
||||
|
||||
fakeCert, fakeCertDir := setupCertDirs(t)
|
||||
defer cleanupCertDirs(t, fakeCert, fakeCertDir)
|
||||
|
||||
testConsul := getTestConsul(t)
|
||||
defer testConsul.Stop()
|
||||
|
||||
alloc := mock.Alloc()
|
||||
alloc.AllocatedResources.Shared.Networks = []*structs.NetworkResource{{Mode: "host", IP: "1.1.1.1"}}
|
||||
tg := alloc.Job.TaskGroups[0]
|
||||
tg.Services = []*structs.Service{{
|
||||
Name: "cn-service",
|
||||
TaskName: tg.Tasks[0].Name,
|
||||
Connect: &structs.ConsulConnect{
|
||||
Native: true,
|
||||
}},
|
||||
}
|
||||
tg.Tasks[0].Kind = structs.NewTaskKind("connect-native", "cn-service")
|
||||
|
||||
logger := testlog.HCLogger(t)
|
||||
|
||||
allocDir, cleanup := allocdir.TestAllocDir(t, logger, "ConnectNative")
|
||||
defer cleanup()
|
||||
|
||||
// register group services
|
||||
consulConfig := consulapi.DefaultConfig()
|
||||
consulConfig.Address = testConsul.HTTPAddr
|
||||
consulAPIClient, err := consulapi.NewClient(consulConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
consulClient := agentconsul.NewServiceClient(consulAPIClient.Agent(), logger, true)
|
||||
go consulClient.Run()
|
||||
defer consulClient.Shutdown()
|
||||
require.NoError(t, consulClient.RegisterWorkload(agentconsul.BuildAllocServices(mock.Node(), alloc, agentconsul.NoopRestarter())))
|
||||
|
||||
// Run Connect Native hook
|
||||
h := newConnectNativeHook(newConnectNativeHookConfig(alloc, &config.ConsulConfig{
|
||||
Addr: consulConfig.Address,
|
||||
|
||||
// TLS config consumed by native application
|
||||
ShareSSL: helper.BoolToPtr(true),
|
||||
EnableSSL: helper.BoolToPtr(true),
|
||||
VerifySSL: helper.BoolToPtr(true),
|
||||
CAFile: fakeCert,
|
||||
CertFile: fakeCert,
|
||||
KeyFile: fakeCert,
|
||||
Auth: "user:password",
|
||||
}, logger))
|
||||
|
||||
taskEnv := taskenv.NewEmptyTaskEnv()
|
||||
taskEnv.EnvMap = map[string]string{
|
||||
"CONSUL_CACERT": "/foo/ca.pem",
|
||||
"CONSUL_CLIENT_CERT": "/foo/cert.pem",
|
||||
"CONSUL_CLIENT_KEY": "/foo/key.pem",
|
||||
"CONSUL_HTTP_AUTH": "foo:bar",
|
||||
"CONSUL_HTTP_SSL_VERIFY": "false",
|
||||
// CONSUL_HTTP_SSL (check the default value is assumed from client config)
|
||||
}
|
||||
|
||||
request := &interfaces.TaskPrestartRequest{
|
||||
Task: tg.Tasks[0],
|
||||
TaskDir: allocDir.NewTaskDir(tg.Tasks[0].Name),
|
||||
TaskEnv: taskEnv, // env stanza is configured w/ non-default tls configs
|
||||
}
|
||||
require.NoError(t, request.TaskDir.Build(false, nil))
|
||||
|
||||
response := new(interfaces.TaskPrestartResponse)
|
||||
response.Env = make(map[string]string)
|
||||
|
||||
// Run the Connect Native hook
|
||||
require.NoError(t, h.Prestart(context.Background(), request, response))
|
||||
|
||||
// Assert the hook is Done
|
||||
require.True(t, response.Done)
|
||||
|
||||
// Assert environment variable for CONSUL_HTTP_SSL is set, because it was
|
||||
// the only one not overridden by task env stanza config
|
||||
require.NotEmpty(t, response.Env)
|
||||
require.Equal(t, map[string]string{
|
||||
"CONSUL_HTTP_SSL": "true",
|
||||
}, response.Env)
|
||||
|
||||
// Assert 3 pem files were written (even though they will be ignored)
|
||||
// as this is gated by share_tls, not the presense of ca environment variables.
|
||||
checkFilesInDir(t, request.TaskDir.SecretsDir,
|
||||
[]string{secretCAFilename, secretCertfileFilename, secretKeyfileFilename},
|
||||
[]string{sidsTokenFile},
|
||||
)
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
|
||||
const envoyBootstrapHookName = "envoy_bootstrap"
|
||||
|
||||
type envoyBootstrapConsulConfig struct {
|
||||
type consulTransportConfig struct {
|
||||
HTTPAddr string // required
|
||||
Auth string // optional, env CONSUL_HTTP_AUTH
|
||||
SSL string // optional, env CONSUL_HTTP_SSL
|
||||
@@ -33,8 +33,20 @@ type envoyBootstrapConsulConfig struct {
|
||||
// CAPath (dir) not supported by Nomad's config object
|
||||
}
|
||||
|
||||
func newConsulTransportConfig(consul *config.ConsulConfig) consulTransportConfig {
|
||||
return consulTransportConfig{
|
||||
HTTPAddr: consul.Addr,
|
||||
Auth: consul.Auth,
|
||||
SSL: decodeTriState(consul.EnableSSL),
|
||||
VerifySSL: decodeTriState(consul.VerifySSL),
|
||||
CAFile: consul.CAFile,
|
||||
CertFile: consul.CertFile,
|
||||
KeyFile: consul.KeyFile,
|
||||
}
|
||||
}
|
||||
|
||||
type envoyBootstrapHookConfig struct {
|
||||
consul envoyBootstrapConsulConfig
|
||||
consul consulTransportConfig
|
||||
alloc *structs.Allocation
|
||||
logger hclog.Logger
|
||||
}
|
||||
@@ -54,15 +66,7 @@ func newEnvoyBootstrapHookConfig(alloc *structs.Allocation, consul *config.Consu
|
||||
return &envoyBootstrapHookConfig{
|
||||
alloc: alloc,
|
||||
logger: logger,
|
||||
consul: envoyBootstrapConsulConfig{
|
||||
HTTPAddr: consul.Addr,
|
||||
Auth: consul.Auth,
|
||||
SSL: decodeTriState(consul.EnableSSL),
|
||||
VerifySSL: decodeTriState(consul.VerifySSL),
|
||||
CAFile: consul.CAFile,
|
||||
CertFile: consul.CertFile,
|
||||
KeyFile: consul.KeyFile,
|
||||
},
|
||||
consul: newConsulTransportConfig(consul),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +85,7 @@ type envoyBootstrapHook struct {
|
||||
// the bootstrap.json config. Runtime Envoy configuration is done via
|
||||
// Consul's gRPC endpoint. There are many security parameters to configure
|
||||
// before contacting Consul.
|
||||
consulConfig envoyBootstrapConsulConfig
|
||||
consulConfig consulTransportConfig
|
||||
|
||||
// logger is used to log things
|
||||
logger hclog.Logger
|
||||
@@ -269,7 +273,7 @@ func (h *envoyBootstrapHook) execute(cmd *exec.Cmd) (string, error) {
|
||||
// along to the exec invocation of consul which will then generate the bootstrap
|
||||
// configuration file for envoy.
|
||||
type envoyBootstrapArgs struct {
|
||||
consulConfig envoyBootstrapConsulConfig
|
||||
consulConfig consulTransportConfig
|
||||
sidecarFor string
|
||||
grpcAddr string
|
||||
envoyAdminBind string
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"testing"
|
||||
|
||||
consulapi "github.com/hashicorp/consul/api"
|
||||
consultest "github.com/hashicorp/consul/sdk/testutil"
|
||||
"github.com/hashicorp/nomad/client/allocdir"
|
||||
"github.com/hashicorp/nomad/client/allocrunner/interfaces"
|
||||
"github.com/hashicorp/nomad/client/taskenv"
|
||||
@@ -93,11 +92,11 @@ func TestEnvoyBootstrapHook_decodeTriState(t *testing.T) {
|
||||
}
|
||||
|
||||
var (
|
||||
consulPlainConfig = envoyBootstrapConsulConfig{
|
||||
consulPlainConfig = consulTransportConfig{
|
||||
HTTPAddr: "2.2.2.2",
|
||||
}
|
||||
|
||||
consulTLSConfig = envoyBootstrapConsulConfig{
|
||||
consulTLSConfig = consulTransportConfig{
|
||||
HTTPAddr: "2.2.2.2", // arg
|
||||
Auth: "user:password", // env
|
||||
SSL: "true", // env
|
||||
@@ -220,16 +219,7 @@ func TestEnvoyBootstrapHook_with_SI_token(t *testing.T) {
|
||||
t.Parallel()
|
||||
testutil.RequireConsul(t)
|
||||
|
||||
testconsul, err := consultest.NewTestServerConfig(func(c *consultest.TestServerConfig) {
|
||||
// If -v wasn't specified squelch consul logging
|
||||
if !testing.Verbose() {
|
||||
c.Stdout = ioutil.Discard
|
||||
c.Stderr = ioutil.Discard
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("error starting test consul server: %v", err)
|
||||
}
|
||||
testconsul := getTestConsul(t)
|
||||
defer testconsul.Stop()
|
||||
|
||||
alloc := mock.Alloc()
|
||||
@@ -328,16 +318,7 @@ func TestTaskRunner_EnvoyBootstrapHook_Ok(t *testing.T) {
|
||||
t.Parallel()
|
||||
testutil.RequireConsul(t)
|
||||
|
||||
testconsul, err := consultest.NewTestServerConfig(func(c *consultest.TestServerConfig) {
|
||||
// If -v wasn't specified squelch consul logging
|
||||
if !testing.Verbose() {
|
||||
c.Stdout = ioutil.Discard
|
||||
c.Stderr = ioutil.Discard
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("error starting test consul server: %v", err)
|
||||
}
|
||||
testconsul := getTestConsul(t)
|
||||
defer testconsul.Stop()
|
||||
|
||||
alloc := mock.Alloc()
|
||||
@@ -470,16 +451,7 @@ func TestTaskRunner_EnvoyBootstrapHook_RecoverableError(t *testing.T) {
|
||||
t.Parallel()
|
||||
testutil.RequireConsul(t)
|
||||
|
||||
testconsul, err := consultest.NewTestServerConfig(func(c *consultest.TestServerConfig) {
|
||||
// If -v wasn't specified squelch consul logging
|
||||
if !testing.Verbose() {
|
||||
c.Stdout = ioutil.Discard
|
||||
c.Stderr = ioutil.Discard
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("error starting test consul server: %v", err)
|
||||
}
|
||||
testconsul := getTestConsul(t)
|
||||
defer testconsul.Stop()
|
||||
|
||||
alloc := mock.Alloc()
|
||||
@@ -534,7 +506,7 @@ func TestTaskRunner_EnvoyBootstrapHook_RecoverableError(t *testing.T) {
|
||||
resp := &interfaces.TaskPrestartResponse{}
|
||||
|
||||
// Run the hook
|
||||
err = h.Prestart(context.Background(), req, resp)
|
||||
err := h.Prestart(context.Background(), req, resp)
|
||||
require.EqualError(t, err, "error creating bootstrap configuration for Connect proxy sidecar: exit status 1")
|
||||
require.True(t, structs.IsRecoverable(err))
|
||||
|
||||
|
||||
@@ -127,10 +127,15 @@ func (tr *TaskRunner) initHooks() {
|
||||
}))
|
||||
}
|
||||
|
||||
// envoy bootstrap must execute after sidsHook maybe sets SI token
|
||||
tr.runnerHooks = append(tr.runnerHooks, newEnvoyBootstrapHook(
|
||||
newEnvoyBootstrapHookConfig(alloc, tr.clientConfig.ConsulConfig, hookLogger),
|
||||
))
|
||||
if task.Kind.IsConnectProxy() {
|
||||
tr.runnerHooks = append(tr.runnerHooks, newEnvoyBootstrapHook(
|
||||
newEnvoyBootstrapHookConfig(alloc, tr.clientConfig.ConsulConfig, hookLogger),
|
||||
))
|
||||
} else if task.Kind.IsConnectNative() {
|
||||
tr.runnerHooks = append(tr.runnerHooks, newConnectNativeHook(
|
||||
newConnectNativeHookConfig(alloc, tr.clientConfig.ConsulConfig, hookLogger),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// If there are any script checks, add the hook
|
||||
|
||||
@@ -17,7 +17,7 @@ func newConnect(serviceName string, nc *structs.ConsulConnect, networks structs.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if nc.Native {
|
||||
if nc.IsNative() {
|
||||
return &api.AgentServiceConnect{Native: true}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1177,6 +1177,7 @@ func ApiServicesToStructs(in []*api.Service) []*structs.Service {
|
||||
out[i] = &structs.Service{
|
||||
Name: s.Name,
|
||||
PortLabel: s.PortLabel,
|
||||
TaskName: s.TaskName,
|
||||
Tags: s.Tags,
|
||||
CanaryTags: s.CanaryTags,
|
||||
EnableTagOverride: s.EnableTagOverride,
|
||||
|
||||
@@ -2763,7 +2763,7 @@ func TestConversion_apiConnectSidecarServiceToStructs(t *testing.T) {
|
||||
}))
|
||||
}
|
||||
|
||||
func TestConversion_ApiConsulConnectToStructs(t *testing.T) {
|
||||
func TestConversion_ApiConsulConnectToStructs_legacy(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Nil(t, ApiConsulConnectToStructs(nil))
|
||||
require.Equal(t, &structs.ConsulConnect{
|
||||
@@ -2776,3 +2776,13 @@ func TestConversion_ApiConsulConnectToStructs(t *testing.T) {
|
||||
SidecarTask: &api.SidecarTask{Name: "task"},
|
||||
}))
|
||||
}
|
||||
|
||||
func TestConversion_ApiConsulConnectToStructs_native(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Nil(t, ApiConsulConnectToStructs(nil))
|
||||
require.Equal(t, &structs.ConsulConnect{
|
||||
Native: true,
|
||||
}, ApiConsulConnectToStructs(&api.ConsulConnect{
|
||||
Native: true,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ func parseService(o *ast.ObjectItem) (*api.Service, error) {
|
||||
"address_mode",
|
||||
"check_restart",
|
||||
"connect",
|
||||
"task",
|
||||
"meta",
|
||||
"canary_meta",
|
||||
}
|
||||
|
||||
@@ -1276,6 +1276,24 @@ func TestParse(t *testing.T) {
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"tg-service-connect-native.hcl",
|
||||
&api.Job{
|
||||
ID: helper.StringToPtr("connect_native_service"),
|
||||
Name: helper.StringToPtr("connect_native_service"),
|
||||
TaskGroups: []*api.TaskGroup{{
|
||||
Name: helper.StringToPtr("group"),
|
||||
Services: []*api.Service{{
|
||||
Name: "example",
|
||||
TaskName: "task1",
|
||||
Connect: &api.ConsulConnect{
|
||||
Native: true,
|
||||
},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"tg-service-enable-tag-override.hcl",
|
||||
&api.Job{
|
||||
|
||||
12
jobspec/test-fixtures/tg-service-connect-native.hcl
Normal file
12
jobspec/test-fixtures/tg-service-connect-native.hcl
Normal file
@@ -0,0 +1,12 @@
|
||||
job "connect_native_service" {
|
||||
group "group" {
|
||||
service {
|
||||
name = "example"
|
||||
task = "task1"
|
||||
|
||||
connect {
|
||||
native = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,15 @@ func isSidecarForService(t *structs.Task, svc string) bool {
|
||||
return t.Kind == structs.TaskKind(fmt.Sprintf("%s:%s", structs.ConnectProxyPrefix, svc))
|
||||
}
|
||||
|
||||
func getNamedTaskForNativeService(tg *structs.TaskGroup, taskName string) *structs.Task {
|
||||
for _, t := range tg.Tasks {
|
||||
if t.Name == taskName {
|
||||
return t
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// probably need to hack this up to look for checks on the service, and if they
|
||||
// qualify, configure a port for envoy to use to expose their paths.
|
||||
func groupConnectHook(job *structs.Job, g *structs.TaskGroup) error {
|
||||
@@ -145,10 +154,15 @@ func groupConnectHook(job *structs.Job, g *structs.TaskGroup) error {
|
||||
|
||||
// create a port for the sidecar task's proxy port
|
||||
makePort(fmt.Sprintf("%s-%s", structs.ConnectProxyPrefix, service.Name))
|
||||
// todo(shoenig) magic port for 'expose.checks'
|
||||
} else if service.Connect.IsNative() {
|
||||
nativeTaskName := service.TaskName
|
||||
if t := getNamedTaskForNativeService(g, nativeTaskName); t != nil {
|
||||
t.Kind = structs.NewTaskKind(structs.ConnectNativePrefix, service.Name)
|
||||
} else {
|
||||
return fmt.Errorf("native task %s named by %s->%s does not exist", nativeTaskName, g.Name, service.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -180,18 +194,42 @@ func newConnectTask(serviceName string) *structs.Task {
|
||||
func groupConnectValidate(g *structs.TaskGroup) (warnings []error, err error) {
|
||||
for _, s := range g.Services {
|
||||
if s.Connect.HasSidecar() {
|
||||
if n := len(g.Networks); n != 1 {
|
||||
return nil, fmt.Errorf("Consul Connect sidecars require exactly 1 network, found %d in group %q", n, g.Name)
|
||||
if err := groupConnectSidecarValidate(g); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if g.Networks[0].Mode != "bridge" {
|
||||
return nil, fmt.Errorf("Consul Connect sidecar requires bridge network, found %q in group %q", g.Networks[0].Mode, g.Name)
|
||||
} else if s.Connect.IsNative() {
|
||||
if err := groupConnectNativeValidate(g, s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Stopping loop, only need to do the validation once
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func groupConnectSidecarValidate(g *structs.TaskGroup) error {
|
||||
if n := len(g.Networks); n != 1 {
|
||||
return fmt.Errorf("Consul Connect sidecars require exactly 1 network, found %d in group %q", n, g.Name)
|
||||
}
|
||||
|
||||
if g.Networks[0].Mode != "bridge" {
|
||||
return fmt.Errorf("Consul Connect sidecar requires bridge network, found %q in group %q", g.Networks[0].Mode, g.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func groupConnectNativeValidate(g *structs.TaskGroup, s *structs.Service) error {
|
||||
// note that network mode is not enforced for connect native services
|
||||
|
||||
// a native service must have the task identified in the service definition.
|
||||
if len(s.TaskName) == 0 {
|
||||
return fmt.Errorf("Consul Connect Native service %q requires task name", s.Name)
|
||||
}
|
||||
|
||||
// also make sure that task actually exists
|
||||
for _, task := range g.Tasks {
|
||||
if s.TaskName == task.Name {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("Consul Connect Native service %q requires undefined task %q in group %q", s.Name, s.TaskName, g.Name)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_isSidecarForService(t *testing.T) {
|
||||
func TestJobEndpointConnect_isSidecarForService(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
t *structs.Task // task
|
||||
s string // service
|
||||
@@ -49,7 +51,9 @@ func Test_isSidecarForService(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_groupConnectHook(t *testing.T) {
|
||||
func TestJobEndpointConnect_groupConnectHook(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Test that connect-proxy task is inserted for backend service
|
||||
job := mock.Job()
|
||||
job.TaskGroups[0] = &structs.TaskGroup{
|
||||
@@ -110,7 +114,7 @@ func Test_groupConnectHook(t *testing.T) {
|
||||
// the service name is interpolated *before the task is created.
|
||||
//
|
||||
// See https://github.com/hashicorp/nomad/issues/6853
|
||||
func TestJobEndpoint_ConnectInterpolation(t *testing.T) {
|
||||
func TestJobEndpointConnect_ConnectInterpolation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
server := &Server{logger: testlog.HCLogger(t)}
|
||||
@@ -125,3 +129,60 @@ func TestJobEndpoint_ConnectInterpolation(t *testing.T) {
|
||||
require.Len(t, j.TaskGroups[0].Tasks, 2)
|
||||
require.Equal(t, "connect-proxy-my-job-api", j.TaskGroups[0].Tasks[1].Name)
|
||||
}
|
||||
|
||||
func TestJobEndpointConnect_groupConnectSidecarValidate(t *testing.T) {
|
||||
t.Run("sidecar 0 networks", func(t *testing.T) {
|
||||
require.EqualError(t, groupConnectSidecarValidate(&structs.TaskGroup{
|
||||
Name: "g1",
|
||||
Networks: nil,
|
||||
}), `Consul Connect sidecars require exactly 1 network, found 0 in group "g1"`)
|
||||
})
|
||||
|
||||
t.Run("sidecar non bridge", func(t *testing.T) {
|
||||
require.EqualError(t, groupConnectSidecarValidate(&structs.TaskGroup{
|
||||
Name: "g2",
|
||||
Networks: structs.Networks{{
|
||||
Mode: "host",
|
||||
}},
|
||||
}), `Consul Connect sidecar requires bridge network, found "host" in group "g2"`)
|
||||
})
|
||||
|
||||
t.Run("sidecar okay", func(t *testing.T) {
|
||||
require.NoError(t, groupConnectSidecarValidate(&structs.TaskGroup{
|
||||
Name: "g3",
|
||||
Networks: structs.Networks{{
|
||||
Mode: "bridge",
|
||||
}},
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
func TestJobEndpointConnect_groupConnectNativeValidate(t *testing.T) {
|
||||
t.Run("no task in service", func(t *testing.T) {
|
||||
require.EqualError(t, groupConnectNativeValidate(&structs.TaskGroup{
|
||||
Name: "g1",
|
||||
}, &structs.Service{
|
||||
Name: "s1",
|
||||
TaskName: "",
|
||||
}), `Consul Connect Native service "s1" requires task name`)
|
||||
})
|
||||
|
||||
t.Run("no task for service", func(t *testing.T) {
|
||||
require.EqualError(t, groupConnectNativeValidate(&structs.TaskGroup{
|
||||
Name: "g2",
|
||||
}, &structs.Service{
|
||||
Name: "s2",
|
||||
TaskName: "t1",
|
||||
}), `Consul Connect Native service "s2" requires undefined task "t1" in group "g2"`)
|
||||
})
|
||||
|
||||
t.Run("native okay", func(t *testing.T) {
|
||||
require.NoError(t, groupConnectNativeValidate(&structs.TaskGroup{
|
||||
Name: "g2",
|
||||
Tasks: []*structs.Task{{Name: "t0"}, {Name: "t1"}, {Name: "t3"}},
|
||||
}, &structs.Service{
|
||||
Name: "s2",
|
||||
TaskName: "t1",
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1889,8 +1889,7 @@ func taskUsesConnect(task *structs.Task) bool {
|
||||
// not even in the task group
|
||||
return false
|
||||
}
|
||||
|
||||
return task.Kind.IsConnectProxy() || task.Kind.IsConnectNative()
|
||||
return task.UsesConnect()
|
||||
}
|
||||
|
||||
func (n *Node) EmitEvents(args *structs.EmitNodeEventsRequest, reply *structs.EmitNodeEventsResponse) error {
|
||||
|
||||
@@ -85,6 +85,12 @@ type ConsulConfig struct {
|
||||
// Uses Consul's default and env var.
|
||||
EnableSSL *bool `hcl:"ssl"`
|
||||
|
||||
// ShareSSL enables Consul Connect Native applications to use the TLS
|
||||
// configuration of the Nomad Client for establishing connections to Consul.
|
||||
//
|
||||
// Does not include sharing of ACL tokens.
|
||||
ShareSSL *bool `hcl:"share_ssl"`
|
||||
|
||||
// VerifySSL enables or disables SSL verification when the transport scheme
|
||||
// for the consul api client is https
|
||||
//
|
||||
@@ -200,6 +206,9 @@ func (c *ConsulConfig) Merge(b *ConsulConfig) *ConsulConfig {
|
||||
if b.VerifySSL != nil {
|
||||
result.VerifySSL = helper.BoolToPtr(*b.VerifySSL)
|
||||
}
|
||||
if b.ShareSSL != nil {
|
||||
result.ShareSSL = helper.BoolToPtr(*b.ShareSSL)
|
||||
}
|
||||
if b.CAFile != "" {
|
||||
result.CAFile = b.CAFile
|
||||
}
|
||||
@@ -301,6 +310,9 @@ func (c *ConsulConfig) Copy() *ConsulConfig {
|
||||
if nc.VerifySSL != nil {
|
||||
nc.VerifySSL = helper.BoolToPtr(*nc.VerifySSL)
|
||||
}
|
||||
if nc.ShareSSL != nil {
|
||||
nc.ShareSSL = helper.BoolToPtr(*nc.ShareSSL)
|
||||
}
|
||||
if nc.ServerAutoJoin != nil {
|
||||
nc.ServerAutoJoin = helper.BoolToPtr(*nc.ServerAutoJoin)
|
||||
}
|
||||
|
||||
@@ -2558,6 +2558,7 @@ func TestTaskGroupDiff(t *testing.T) {
|
||||
Services: []*Service{
|
||||
{
|
||||
Name: "foo",
|
||||
TaskName: "task1",
|
||||
EnableTagOverride: false,
|
||||
Checks: []*ServiceCheck{
|
||||
{
|
||||
@@ -2573,6 +2574,7 @@ func TestTaskGroupDiff(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Connect: &ConsulConnect{
|
||||
Native: false,
|
||||
SidecarTask: &SidecarTask{
|
||||
Name: "sidecar",
|
||||
Driver: "docker",
|
||||
@@ -2592,6 +2594,7 @@ func TestTaskGroupDiff(t *testing.T) {
|
||||
Services: []*Service{
|
||||
{
|
||||
Name: "foo",
|
||||
TaskName: "task2",
|
||||
EnableTagOverride: true,
|
||||
Checks: []*ServiceCheck{
|
||||
{
|
||||
@@ -2609,6 +2612,7 @@ func TestTaskGroupDiff(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Connect: &ConsulConnect{
|
||||
Native: true,
|
||||
SidecarService: &ConsulSidecarService{
|
||||
Port: "http",
|
||||
Proxy: &ConsulProxy{
|
||||
@@ -2661,6 +2665,12 @@ func TestTaskGroupDiff(t *testing.T) {
|
||||
Old: "",
|
||||
New: "",
|
||||
},
|
||||
{
|
||||
Type: DiffTypeEdited,
|
||||
Name: "TaskName",
|
||||
Old: "task1",
|
||||
New: "task2",
|
||||
},
|
||||
},
|
||||
Objects: []*ObjectDiff{
|
||||
{
|
||||
@@ -2786,10 +2796,10 @@ func TestTaskGroupDiff(t *testing.T) {
|
||||
Name: "ConsulConnect",
|
||||
Fields: []*FieldDiff{
|
||||
{
|
||||
Type: DiffTypeNone,
|
||||
Type: DiffTypeEdited,
|
||||
Name: "Native",
|
||||
Old: "false",
|
||||
New: "false",
|
||||
New: "true",
|
||||
},
|
||||
},
|
||||
Objects: []*ObjectDiff{
|
||||
@@ -4726,6 +4736,7 @@ func TestTaskDiff(t *testing.T) {
|
||||
Name: "foo",
|
||||
PortLabel: "bar",
|
||||
AddressMode: "driver",
|
||||
TaskName: "task1",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -4760,6 +4771,12 @@ func TestTaskDiff(t *testing.T) {
|
||||
Old: "foo",
|
||||
New: "bar",
|
||||
},
|
||||
{
|
||||
Type: DiffTypeAdded,
|
||||
Name: "TaskName",
|
||||
Old: "",
|
||||
New: "task1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -4890,6 +4907,10 @@ func TestTaskDiff(t *testing.T) {
|
||||
Type: DiffTypeNone,
|
||||
Name: "PortLabel",
|
||||
},
|
||||
{
|
||||
Type: DiffTypeNone,
|
||||
Name: "TaskName",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -4946,6 +4967,59 @@ func TestTaskDiff(t *testing.T) {
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Name: "Service with Connect Native",
|
||||
Old: &Task{
|
||||
Services: []*Service{
|
||||
{
|
||||
Name: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
New: &Task{
|
||||
Services: []*Service{
|
||||
{
|
||||
Name: "foo",
|
||||
TaskName: "task1",
|
||||
Connect: &ConsulConnect{
|
||||
Native: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Expected: &TaskDiff{
|
||||
Type: DiffTypeEdited,
|
||||
Objects: []*ObjectDiff{
|
||||
{
|
||||
Type: DiffTypeEdited,
|
||||
Name: "Service",
|
||||
Fields: []*FieldDiff{
|
||||
{
|
||||
Type: DiffTypeAdded,
|
||||
Name: "TaskName",
|
||||
Old: "",
|
||||
New: "task1",
|
||||
},
|
||||
},
|
||||
Objects: []*ObjectDiff{
|
||||
{
|
||||
Type: DiffTypeAdded,
|
||||
Name: "ConsulConnect",
|
||||
Fields: []*FieldDiff{
|
||||
{
|
||||
Type: DiffTypeAdded,
|
||||
Name: "Native",
|
||||
Old: "",
|
||||
New: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Name: "Service Checks edited",
|
||||
Old: &Task{
|
||||
@@ -5297,6 +5371,12 @@ func TestTaskDiff(t *testing.T) {
|
||||
Old: "",
|
||||
New: "",
|
||||
},
|
||||
{
|
||||
Type: DiffTypeNone,
|
||||
Name: "TaskName",
|
||||
Old: "",
|
||||
New: "",
|
||||
},
|
||||
},
|
||||
Objects: []*ObjectDiff{
|
||||
{
|
||||
|
||||
@@ -351,6 +351,12 @@ type Service struct {
|
||||
// as one of the seed values when generating a Consul ServiceID.
|
||||
Name string
|
||||
|
||||
// Name of the Task associated with this service.
|
||||
//
|
||||
// Currently only used to identify the implementing task of a Consul
|
||||
// Connect Native enabled service.
|
||||
TaskName string
|
||||
|
||||
// PortLabel is either the numeric port number or the `host:port`.
|
||||
// To specify the port number using the host's Consul Advertise
|
||||
// address, specify an empty host in the PortLabel (e.g. `:port`).
|
||||
@@ -422,8 +428,7 @@ func (s *Service) Canonicalize(job string, taskGroup string, task string) {
|
||||
"TASKGROUP": taskGroup,
|
||||
"TASK": task,
|
||||
"BASE": fmt.Sprintf("%s-%s-%s", job, taskGroup, task),
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
for _, check := range s.Checks {
|
||||
check.Canonicalize(s.Name)
|
||||
@@ -450,6 +455,7 @@ func (s *Service) Validate() error {
|
||||
mErr.Errors = append(mErr.Errors, fmt.Errorf("Service address_mode must be %q, %q, or %q; not %q", AddressModeAuto, AddressModeHost, AddressModeDriver, s.AddressMode))
|
||||
}
|
||||
|
||||
// check checks
|
||||
for _, c := range s.Checks {
|
||||
if s.PortLabel == "" && c.PortLabel == "" && c.RequiresPort() {
|
||||
mErr.Errors = append(mErr.Errors, fmt.Errorf("Check %s invalid: check requires a port but neither check nor service %+q have a port", c.Name, s.Name))
|
||||
@@ -469,10 +475,16 @@ func (s *Service) Validate() error {
|
||||
}
|
||||
}
|
||||
|
||||
// check connect
|
||||
if s.Connect != nil {
|
||||
if err := s.Connect.Validate(); err != nil {
|
||||
mErr.Errors = append(mErr.Errors, err)
|
||||
}
|
||||
|
||||
// if service is connect native, service task must be set
|
||||
if s.Connect.IsNative() && len(s.TaskName) == 0 {
|
||||
mErr.Errors = append(mErr.Errors, fmt.Errorf("Service %s is Connect Native and requires setting the task", s.Name))
|
||||
}
|
||||
}
|
||||
|
||||
return mErr.ErrorOrNil()
|
||||
@@ -620,8 +632,7 @@ OUTER:
|
||||
|
||||
// ConsulConnect represents a Consul Connect jobspec stanza.
|
||||
type ConsulConnect struct {
|
||||
// Native is true if a service implements Connect directly and does not
|
||||
// need a sidecar.
|
||||
// Native indicates whether the service is Consul Connect Native enabled.
|
||||
Native bool
|
||||
|
||||
// SidecarService is non-nil if a service requires a sidecar.
|
||||
@@ -662,17 +673,21 @@ func (c *ConsulConnect) HasSidecar() bool {
|
||||
return c != nil && c.SidecarService != nil
|
||||
}
|
||||
|
||||
func (c *ConsulConnect) IsNative() bool {
|
||||
return c != nil && c.Native
|
||||
}
|
||||
|
||||
// Validate that the Connect stanza has exactly one of Native or sidecar.
|
||||
func (c *ConsulConnect) Validate() error {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.Native && c.SidecarService != nil {
|
||||
if c.IsNative() && c.HasSidecar() {
|
||||
return fmt.Errorf("Consul Connect must be native or use a sidecar service; not both")
|
||||
}
|
||||
|
||||
if !c.Native && c.SidecarService == nil {
|
||||
if !c.IsNative() && !c.HasSidecar() {
|
||||
return fmt.Errorf("Consul Connect must be native or use a sidecar service")
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,6 @@ func TestConsulConnect_Validate(t *testing.T) {
|
||||
// An empty Connect stanza is invalid
|
||||
require.Error(t, c.Validate())
|
||||
|
||||
// Native=true is valid
|
||||
c.Native = true
|
||||
require.NoError(t, c.Validate())
|
||||
|
||||
@@ -131,7 +130,6 @@ func TestConsulConnect_Validate(t *testing.T) {
|
||||
c.SidecarService = &ConsulSidecarService{}
|
||||
require.Error(t, c.Validate())
|
||||
|
||||
// Native=false + Sidecar!=nil is valid
|
||||
c.Native = false
|
||||
require.NoError(t, c.Validate())
|
||||
}
|
||||
|
||||
@@ -5979,7 +5979,7 @@ func (tg *TaskGroup) LookupTask(name string) *Task {
|
||||
func (tg *TaskGroup) UsesConnect() bool {
|
||||
for _, service := range tg.Services {
|
||||
if service.Connect != nil {
|
||||
if service.Connect.Native || service.Connect.SidecarService != nil {
|
||||
if service.Connect.IsNative() || service.Connect.HasSidecar() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -6179,18 +6179,10 @@ type Task struct {
|
||||
|
||||
// UsesConnect is for conveniently detecting if the Task is able to make use
|
||||
// of Consul Connect features. This will be indicated in the TaskKind of the
|
||||
// Task, which exports known types of Tasks.
|
||||
//
|
||||
// Currently only Consul Connect Proxy tasks are known.
|
||||
// (Consul Connect Native tasks will be supported soon).
|
||||
// Task, which exports known types of Tasks. UsesConnect will be true if the
|
||||
// task is a connect proxy, or if the task is connect native.
|
||||
func (t *Task) UsesConnect() bool {
|
||||
// todo(shoenig): native tasks
|
||||
switch {
|
||||
case t.Kind.IsConnectProxy():
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return t.Kind.IsConnectProxy() || t.Kind.IsConnectNative()
|
||||
}
|
||||
|
||||
func (t *Task) Copy() *Task {
|
||||
|
||||
@@ -814,13 +814,20 @@ func TestTask_UsesConnect(t *testing.T) {
|
||||
t.Run("sidecar proxy", func(t *testing.T) {
|
||||
task := &Task{
|
||||
Name: "connect-proxy-task1",
|
||||
Kind: "connect-proxy:task1",
|
||||
Kind: NewTaskKind(ConnectProxyPrefix, "task1"),
|
||||
}
|
||||
usesConnect := task.UsesConnect()
|
||||
require.True(t, usesConnect)
|
||||
})
|
||||
|
||||
// todo(shoenig): add native case
|
||||
t.Run("native task", func(t *testing.T) {
|
||||
task := &Task{
|
||||
Name: "task1",
|
||||
Kind: NewTaskKind(ConnectNativePrefix, "task1"),
|
||||
}
|
||||
usesConnect := task.UsesConnect()
|
||||
require.True(t, usesConnect)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTaskGroup_UsesConnect(t *testing.T) {
|
||||
@@ -2707,10 +2714,14 @@ func TestService_Validate(t *testing.T) {
|
||||
// Base service should be valid
|
||||
require.NoError(t, s.Validate())
|
||||
|
||||
// Native Connect should be valid
|
||||
// Native Connect requires task name on service
|
||||
s.Connect = &ConsulConnect{
|
||||
Native: true,
|
||||
}
|
||||
require.Error(t, s.Validate())
|
||||
|
||||
// Native Connect should work with task name on service set
|
||||
s.TaskName = "testtask"
|
||||
require.NoError(t, s.Validate())
|
||||
|
||||
// Native Connect + Sidecar should be invalid
|
||||
|
||||
1
vendor/github.com/hashicorp/nomad/api/services.go
generated
vendored
1
vendor/github.com/hashicorp/nomad/api/services.go
generated
vendored
@@ -110,6 +110,7 @@ type Service struct {
|
||||
Connect *ConsulConnect
|
||||
Meta map[string]string
|
||||
CanaryMeta map[string]string
|
||||
TaskName string `mapstructure:"task"`
|
||||
}
|
||||
|
||||
// Canonicalize the Service by ensuring its name and address mode are set. Task
|
||||
|
||||
@@ -104,6 +104,12 @@ configuring Nomad to talk to Consul via DNS such as consul.service.consul
|
||||
Consul service name defined in the `server_service_name` option. This search
|
||||
only happens if the server does not have a leader.
|
||||
|
||||
- `share_ssl` `(bool: true)` - Specifies whether the Nomad client should share
|
||||
its Consul SSL configuration with Connect Native applications. Includes values
|
||||
of `ca_file`, `cert_file`, `key_file`, `ssl`, and `verify_ssl`. Does not include
|
||||
the values for the ACL `token` or `auth`. This option should be disabled in
|
||||
environments where Consul ACLs are not enabled.
|
||||
|
||||
- `ssl` `(bool: false)` - Specifies if the transport scheme should use HTTPS to
|
||||
communicate with the Consul agent. Will default to the `CONSUL_HTTP_SSL`
|
||||
environment variable if set.
|
||||
|
||||
@@ -328,7 +328,7 @@ dashes (`-`) are converted to underscores (`_`) in environment variables so
|
||||
|
||||
- The `consul` binary must be present in Nomad's `$PATH` to run the Envoy
|
||||
proxy sidecar on client nodes.
|
||||
- Consul Connect Native is not yet supported ([#6083][gh6083]).
|
||||
- Consul Connect Native requires host networking.
|
||||
- Only the Docker, `exec`, `raw_exec`, and `java` drivers support network namespaces
|
||||
and Connect.
|
||||
- Changes to the `connect` stanza may not properly trigger a job update
|
||||
@@ -337,7 +337,6 @@ dashes (`-`) are converted to underscores (`_`) in environment variables so
|
||||
- Consul Connect and network namespaces are only supported on Linux.
|
||||
|
||||
[count-dashboard]: /img/count-dashboard.png
|
||||
[gh6083]: https://github.com/hashicorp/nomad/issues/6083
|
||||
[gh6120]: https://github.com/hashicorp/nomad/issues/6120
|
||||
[gh6701]: https://github.com/hashicorp/nomad/issues/6701
|
||||
[gh6459]: https://github.com/hashicorp/nomad/issues/6459
|
||||
|
||||
@@ -47,14 +47,22 @@ job "countdash" {
|
||||
|
||||
## `connect` Parameters
|
||||
|
||||
- `native` - `(bool: false)` - This is used to configure the service as supporting
|
||||
[Connect Native](https://www.consul.io/docs/connect/native) applications. If set,
|
||||
the service definition must provide the name of the implementing task in the
|
||||
[task][service_task] field.
|
||||
Incompatible with `sidecar_service` and `sidecar_task`.
|
||||
|
||||
- `sidecar_service` - <code>([sidecar_service][]: nil)</code> - This is used to configure the sidecar
|
||||
service injected by Nomad for Consul Connect.
|
||||
service injected by Nomad for Consul Connect. Incompatible with `native`.
|
||||
|
||||
- `sidecar_task` - <code>([sidecar_task][]:nil)</code> - This modifies the configuration of the Envoy
|
||||
proxy task.
|
||||
proxy task. Incompatible with `native`.
|
||||
|
||||
## `connect` Examples
|
||||
|
||||
### Using Sidecar Service
|
||||
|
||||
The following example is a minimal connect stanza with defaults and is
|
||||
sufficient to start an Envoy proxy sidecar for allowing incoming connections
|
||||
via Consul Connect.
|
||||
@@ -161,11 +169,29 @@ job "countdash" {
|
||||
}
|
||||
```
|
||||
|
||||
### Using Connect Native
|
||||
|
||||
The following example is a minimal service stanza for a
|
||||
[Consul Connect Native](https://www.consul.io/docs/connect/native)
|
||||
application implemented by a task named `generate`.
|
||||
|
||||
```hcl
|
||||
service {
|
||||
name = "uuid-api"
|
||||
port = "${NOMAD_PORT_api}"
|
||||
task = "generate"
|
||||
|
||||
connect {
|
||||
native = true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Limitations
|
||||
|
||||
[Consul Connect Native services][native] and [Nomad variable
|
||||
interpolation][interpolation] are _not_ yet supported.
|
||||
[Nomad variable interpolation][interpolation] is _not_ yet supported ([gh-7221]).
|
||||
|
||||
[gh-7221]: https://github.com/hashicorp/nomad/issues/7221
|
||||
[job]: /docs/job-specification/job 'Nomad job Job Specification'
|
||||
[group]: /docs/job-specification/group 'Nomad group Job Specification'
|
||||
[task]: /docs/job-specification/task 'Nomad task Job Specification'
|
||||
@@ -174,3 +200,4 @@ interpolation][interpolation] are _not_ yet supported.
|
||||
[sidecar_task]: /docs/job-specification/sidecar_task 'Nomad sidecar task config Specification'
|
||||
[upstreams]: /docs/job-specification/upstreams 'Nomad sidecar service upstreams Specification'
|
||||
[native]: https://www.consul.io/docs/connect/native.html
|
||||
[service_task]: /docs/job-specification/service#task-1 'Nomad service task'
|
||||
|
||||
@@ -147,6 +147,11 @@ Connect][connect] integration.
|
||||
|
||||
- `host` - Use the host IP and port.
|
||||
|
||||
- `task` `(string: "")` - Specifies the name of the Nomad Task associated with
|
||||
this service definition. Only available on group services. Must be set if this
|
||||
service definition represents a Consul Connect Native service. The Nomad Task
|
||||
must exist in the same Task Group.
|
||||
|
||||
- `meta` <code>([Meta][]: nil)</code> - Specifies a key-value map that annotates
|
||||
the Consul service with user-defined metadata.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user