agent: add RetryJoin support for Azure
Pull #2978 from leowmjw/develop Resolves #2978
This commit is contained in:
parent
37e159df63
commit
ec81c18006
|
@ -148,6 +148,10 @@ func (c *Command) readConfig() *Config {
|
|||
"Google Compute Engine tag value to filter on for server discovery.")
|
||||
f.StringVar(&cmdConfig.RetryJoinGCE.CredentialsFile, "retry-join-gce-credentials-file", "",
|
||||
"Path to credentials JSON file to use with Google Compute Engine.")
|
||||
f.StringVar(&cmdConfig.RetryJoinAzure.TagName, "retry-join-azure-tag-name", "",
|
||||
"Azure tag name to filter on for server discovery.")
|
||||
f.StringVar(&cmdConfig.RetryJoinAzure.TagValue, "retry-join-azure-tag-value", "",
|
||||
"Azure tag value to filter on for server discovery.")
|
||||
f.Var((*AppendSliceValue)(&cmdConfig.RetryJoinWan), "retry-join-wan",
|
||||
"Address of an agent to join -wan at start time with retries enabled. "+
|
||||
"Can be specified multiple times.")
|
||||
|
@ -570,8 +574,10 @@ func (c *Command) startupJoinWan(config *Config) error {
|
|||
// retries are exhausted.
|
||||
func (c *Command) retryJoin(config *Config, errCh chan<- struct{}) {
|
||||
ec2Enabled := config.RetryJoinEC2.TagKey != "" && config.RetryJoinEC2.TagValue != ""
|
||||
gceEnabled := config.RetryJoinGCE.TagValue != ""
|
||||
azureEnabled := config.RetryJoinAzure.TagName != "" && config.RetryJoinAzure.TagValue != ""
|
||||
|
||||
if len(config.RetryJoin) == 0 && !ec2Enabled && config.RetryJoinGCE.TagValue == "" {
|
||||
if len(config.RetryJoin) == 0 && !ec2Enabled && !gceEnabled && !azureEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -589,12 +595,18 @@ func (c *Command) retryJoin(config *Config, errCh chan<- struct{}) {
|
|||
logger.Printf("[ERROR] agent: Unable to query EC2 instances: %s", err)
|
||||
}
|
||||
logger.Printf("[INFO] agent: Discovered %d servers from EC2", len(servers))
|
||||
case config.RetryJoinGCE.TagValue != "":
|
||||
case gceEnabled:
|
||||
servers, err = config.discoverGCEHosts(logger)
|
||||
if err != nil {
|
||||
logger.Printf("[ERROR] agent: Unable to query GCE insances: %s", err)
|
||||
logger.Printf("[ERROR] agent: Unable to query GCE instances: %s", err)
|
||||
}
|
||||
logger.Printf("[INFO] agent: Discovered %d servers from GCE", len(servers))
|
||||
case azureEnabled:
|
||||
servers, err = config.discoverAzureHosts(logger)
|
||||
if err != nil {
|
||||
logger.Printf("[ERROR] agent: Unable to query Azure instances: %s", err)
|
||||
}
|
||||
logger.Printf("[INFO] agent: Discovered %d servers from Azure", len(servers))
|
||||
}
|
||||
|
||||
servers = append(servers, config.RetryJoin...)
|
||||
|
|
|
@ -400,6 +400,42 @@ func TestDiscoverGCEHosts(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDiscoverAzureHosts(t *testing.T) {
|
||||
subscriptionID := os.Getenv("ARM_SUBSCRIPTION_ID")
|
||||
tenantID := os.Getenv("ARM_TENANT_ID")
|
||||
clientID := os.Getenv("ARM_CLIENT_ID")
|
||||
clientSecret := os.Getenv("ARM_CLIENT_SECRET")
|
||||
environment := os.Getenv("ARM_ENVIRONMENT")
|
||||
|
||||
if subscriptionID == "" || clientID == "" || clientSecret == "" || tenantID == "" {
|
||||
t.Skip("ARM_SUBSCRIPTION_ID, ARM_CLIENT_ID, ARM_CLIENT_SECRET and ARM_TENANT_ID " +
|
||||
"must be set to test Discover Azure Hosts")
|
||||
}
|
||||
|
||||
if environment == "" {
|
||||
t.Log("Environments other than Public not supported at the moment")
|
||||
}
|
||||
|
||||
c := &Config{
|
||||
RetryJoinAzure: RetryJoinAzure{
|
||||
SubscriptionID: subscriptionID,
|
||||
ClientID: clientID,
|
||||
SecretAccessKey: clientSecret,
|
||||
TenantID: tenantID,
|
||||
TagName: "type",
|
||||
TagValue: "Foundation",
|
||||
},
|
||||
}
|
||||
|
||||
servers, err := c.discoverAzureHosts(log.New(os.Stderr, "", log.LstdFlags))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(servers) != 3 {
|
||||
t.Fatalf("bad: %v", servers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProtectDataDir(t *testing.T) {
|
||||
dir := testutil.TempDir(t, "consul")
|
||||
defer os.RemoveAll(dir)
|
||||
|
|
|
@ -165,6 +165,19 @@ type RetryJoinGCE struct {
|
|||
CredentialsFile string `mapstructure:"credentials_file"`
|
||||
}
|
||||
|
||||
// RetryJoinAzure is used to configure discovery of instances via AzureRM API
|
||||
type RetryJoinAzure struct {
|
||||
// The tag name and value to use when filtering instances
|
||||
TagName string `mapstructure:"tag_name"`
|
||||
TagValue string `mapstructure:"tag_value"`
|
||||
|
||||
// The Azure credentials to use for making requests to AzureRM
|
||||
SubscriptionID string `mapstructure:"subscription_id" json:"-"`
|
||||
TenantID string `mapstructure:"tenant_id" json:"-"`
|
||||
ClientID string `mapstructure:"client_id" json:"-"`
|
||||
SecretAccessKey string `mapstructure:"secret_access_key" json:"-"`
|
||||
}
|
||||
|
||||
// Performance is used to tune the performance of Consul's subsystems.
|
||||
type Performance struct {
|
||||
// RaftMultiplier is an integer multiplier used to scale Raft timing
|
||||
|
@ -537,6 +550,8 @@ type Config struct {
|
|||
// The config struct for the GCE tag server discovery feature.
|
||||
RetryJoinGCE RetryJoinGCE `mapstructure:"retry_join_gce"`
|
||||
|
||||
RetryJoinAzure RetryJoinAzure `mapstructure:"retry_join_azure"`
|
||||
|
||||
// RetryJoinWan is a list of addresses to join -wan with retry enabled.
|
||||
RetryJoinWan []string `mapstructure:"retry_join_wan"`
|
||||
|
||||
|
@ -1728,6 +1743,24 @@ func MergeConfig(a, b *Config) *Config {
|
|||
if b.RetryJoinGCE.CredentialsFile != "" {
|
||||
result.RetryJoinGCE.CredentialsFile = b.RetryJoinGCE.CredentialsFile
|
||||
}
|
||||
if b.RetryJoinAzure.TagName != "" {
|
||||
result.RetryJoinAzure.TagName = b.RetryJoinAzure.TagName
|
||||
}
|
||||
if b.RetryJoinAzure.TagValue != "" {
|
||||
result.RetryJoinAzure.TagValue = b.RetryJoinAzure.TagValue
|
||||
}
|
||||
if b.RetryJoinAzure.SubscriptionID != "" {
|
||||
result.RetryJoinAzure.SubscriptionID = b.RetryJoinAzure.SubscriptionID
|
||||
}
|
||||
if b.RetryJoinAzure.TenantID != "" {
|
||||
result.RetryJoinAzure.TenantID = b.RetryJoinAzure.TenantID
|
||||
}
|
||||
if b.RetryJoinAzure.ClientID != "" {
|
||||
result.RetryJoinAzure.ClientID = b.RetryJoinAzure.ClientID
|
||||
}
|
||||
if b.RetryJoinAzure.SecretAccessKey != "" {
|
||||
result.RetryJoinAzure.SecretAccessKey = b.RetryJoinAzure.SecretAccessKey
|
||||
}
|
||||
if b.RetryMaxAttemptsWan != 0 {
|
||||
result.RetryMaxAttemptsWan = b.RetryMaxAttemptsWan
|
||||
}
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
package agent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/network"
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
)
|
||||
|
||||
// discoverAzureHosts searches an Azure Subscription, returning a list of instance ips
|
||||
// where AzureTag_Name = AzureTag_Value
|
||||
func (c *Config) discoverAzureHosts(logger *log.Logger) ([]string, error) {
|
||||
var servers []string
|
||||
// Only works for the Azure PublicCLoud for now; no ability to test other Environment
|
||||
oauthConfig, err := azure.PublicCloud.OAuthConfigForTenant(c.RetryJoinAzure.TenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Get the ServicePrincipalToken for use searching the NetworkInterfaces
|
||||
sbt, tokerr := azure.NewServicePrincipalToken(*oauthConfig,
|
||||
c.RetryJoinAzure.ClientID,
|
||||
c.RetryJoinAzure.SecretAccessKey,
|
||||
azure.PublicCloud.ResourceManagerEndpoint,
|
||||
)
|
||||
if tokerr != nil {
|
||||
return nil, tokerr
|
||||
}
|
||||
// Setup the client using autorest; followed the structure from Terraform
|
||||
vmnet := network.NewInterfacesClient(c.RetryJoinAzure.SubscriptionID)
|
||||
vmnet.Client.UserAgent = fmt.Sprint("Hashicorp-Consul")
|
||||
vmnet.Authorizer = sbt
|
||||
vmnet.Sender = autorest.CreateSender(autorest.WithLogging(logger))
|
||||
// Get all Network interfaces across ResourceGroups unless there is a compelling reason to restrict
|
||||
netres, neterr := vmnet.ListAll()
|
||||
if neterr != nil {
|
||||
return nil, neterr
|
||||
}
|
||||
// For now, ignore Primary interfaces, choose any PrivateIPAddress with the matching tags
|
||||
for _, oneint := range *netres.Value {
|
||||
// Make it a little more robust just in case there is actually no Tags
|
||||
if oneint.Tags != nil {
|
||||
if *(*oneint.Tags)[c.RetryJoinAzure.TagName] == c.RetryJoinAzure.TagValue {
|
||||
// Make it a little more robust just in case IPConfigurations nil
|
||||
if oneint.IPConfigurations != nil {
|
||||
for _, onecfg := range *oneint.IPConfigurations {
|
||||
// fmt.Println("Internal FQDN: ", *onecfg.Name, " IP: ", *onecfg.PrivateIPAddress)
|
||||
// Only get the address if there is private IP address
|
||||
if onecfg.PrivateIPAddress != nil {
|
||||
servers = append(servers, *onecfg.PrivateIPAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return servers, nil
|
||||
}
|
|
@ -1124,6 +1124,47 @@ func TestRetryJoinGCE(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRetryJoinAzure(t *testing.T) {
|
||||
input := `{
|
||||
"retry_join_azure": {
|
||||
"tag_name": "type",
|
||||
"tag_value": "Foundation",
|
||||
"subscription_id": "klm-no",
|
||||
"tenant_id": "fgh-ij",
|
||||
"client_id": "abc-de",
|
||||
"secret_access_key": "qwerty"
|
||||
}}`
|
||||
|
||||
config, err := DecodeConfig(bytes.NewReader([]byte(input)))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if config.RetryJoinAzure.TagName != "type" {
|
||||
t.Fatalf("bad: %#v", config)
|
||||
}
|
||||
|
||||
if config.RetryJoinAzure.TagValue != "Foundation" {
|
||||
t.Fatalf("bad: %#v", config)
|
||||
}
|
||||
|
||||
if config.RetryJoinAzure.SubscriptionID != "klm-no" {
|
||||
t.Fatalf("bad: %#v", config)
|
||||
}
|
||||
|
||||
if config.RetryJoinAzure.TenantID != "fgh-ij" {
|
||||
t.Fatalf("bad: %#v", config)
|
||||
}
|
||||
|
||||
if config.RetryJoinAzure.ClientID != "abc-de" {
|
||||
t.Fatalf("bad: %#v", config)
|
||||
}
|
||||
|
||||
if config.RetryJoinAzure.SecretAccessKey != "qwerty" {
|
||||
t.Fatalf("bad: %#v", config)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeConfig_Performance(t *testing.T) {
|
||||
input := `{"performance": { "raft_multiplier": 3 }}`
|
||||
config, err := DecodeConfig(bytes.NewReader([]byte(input)))
|
||||
|
|
|
@ -238,6 +238,18 @@ will exit with an error at startup.
|
|||
- If none of these exist and discovery is being run from a GCE instance, the
|
||||
instance's configured service account will be used.
|
||||
|
||||
* <a name="_retry_join_azure_tag_name"></a><a href="#_retry_join_azure_tag_name">`-retry-join-azure-tag-name`
|
||||
</a> - The Azure instance tag name to filter on. When used with
|
||||
[`-retry-join-azure-tag-value`](#_retry_join_azure_tag_value), Consul will attempt to join Azure
|
||||
instances with the given tag name and value on startup.
|
||||
</br></br>For Azure authentication the following methods are supported, in order:
|
||||
- Static credentials (from the config file)
|
||||
|
||||
The only permission needed is the ListAll method for NetworkInterfaces. It is recommended you make a dedicated key used only for auto-joining.
|
||||
|
||||
* <a name="_retry_join_azure_tag_value"></a><a href="#_retry_join_azure_tag_value">`-retry-join-azure-tag-value`
|
||||
</a> - The Azure instance tag value to filter on.
|
||||
|
||||
* <a name="_retry_interval"></a><a href="#_retry_interval">`-retry-interval`</a> - Time
|
||||
to wait between join attempts. Defaults to 30s.
|
||||
|
||||
|
@ -850,6 +862,19 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
|
|||
[`-retry-join-gce-credentials-file` command-line
|
||||
flag](#_retry_join_gce_credentials_file).
|
||||
|
||||
* <a name="retry_join_azure"></a><a href="#retry_join_azure">`retry_join_azure`</a> - This is a nested object
|
||||
that allows the setting of Azure-related [`-retry-join`](#_retry_join) options.
|
||||
<br><br>
|
||||
The following keys are valid:
|
||||
* `tag_name` - The Azure instance tag name to filter on. Equivalent to the</br>
|
||||
[`-retry-join-azure-tag-name` command-line flag](#_retry_join_azure_tag_name).
|
||||
* `tag_value` - The Azure instance tag value to filter on. Equivalent to the</br>
|
||||
[`-retry-join-azure-tag-value` command-line flag](#_retry_join_azure_tag_value).
|
||||
* `subscription_id` - The Azure Subscription ID to use for authentication.
|
||||
* `tenant_id` - The Azure Tenant ID to use for authentication.
|
||||
* `client_id` - The Azure Client ID to use for authentication.
|
||||
* `secret_access_key` - The Azure secret access key to use for authentication.
|
||||
|
||||
* <a name="retry_interval"></a><a href="#retry_interval">`retry_interval`</a> Equivalent to the
|
||||
[`-retry-interval` command-line flag](#_retry_interval).
|
||||
|
||||
|
|
|
@ -29,7 +29,9 @@ For users on AWS the [-retry-join-ec2 configuration options](/docs/agent/options
|
|||
|
||||
For users on GCE the [-retry-join-gce configuration options](/docs/agent/options.html#_retry_join_gce_tag_value) allow bootstrapping by automatically discovering instances on Google Compute Engine by tag value at startup.
|
||||
|
||||
For users not on AWS or GCE the native [-join and retry-join functionality](/docs/agent/options.html#_join) can be used.
|
||||
For users on Azure the [-retry-join-azure configuration options](/docs/agent/options.html#_retry_join_azure_tag_name) allow bootstrapping by automatically discovering Azure instances with a given tag name/value at startup.
|
||||
|
||||
For users not on AWS, GCE or Azure the native [-join and retry-join functionality](/docs/agent/options.html#_join) can be used.
|
||||
|
||||
Other features of Consul Enterprise, such as the UI and Alerts also have suitable open source alternatives.
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ To trigger leader election, we must join these machines together and create a cl
|
|||
- Manually specified list of machines with [-retry-join](https://www.consul.io/docs/agent/options.html#_retry_join) option
|
||||
- Automatic AWS EC2 instance joining with the [-retry-join-ec2-*](https://www.consul.io/docs/agent/options.html#_retry_join_ec2_tag_key) options
|
||||
- Automatic GCE instance joining with the [-retry-join-gce-*](https://www.consul.io/docs/agent/options.html#_retry_join_gce_tag_value) options
|
||||
- Automatic Azure instance joining with the [-retry-join-azure-*](https://www.consul.io/docs/agent/options.html#_retry_join_azure_tag_name) options
|
||||
|
||||
Choose the method which best suits your environment and specific use case.
|
||||
|
||||
|
|
|
@ -141,7 +141,7 @@ learn about <em>one existing member</em>. After joining the cluster, the
|
|||
agents gossip with each other to propagate full membership information.
|
||||
|
||||
## Auto-joining a Cluster on Start
|
||||
Ideally, whenever a new node is brought up in your datacenter, it should automatically join the Consul cluster without human intervention. Consul facilitates auto-join by enabling the auto-discovery of instances in AWS or Google Cloud with a given tag key/value. To use the integration, add the [`retry_join_ec2`](/docs/agent/options.html?#retry_join_ec2) or the [`retry_join_gce`](/docs/agent/options.html?#retry_join_gce) nested object to your Consul configuration file. This will allow a new node to join the cluster without any hardcoded configuration. Alternatively, you can join a cluster at startup using the [`-join` flag](/docs/agent/options.html#_join) or [`start_join` setting](/docs/agent/options.html#start_join) with hardcoded addresses of other known Consul agents.
|
||||
Ideally, whenever a new node is brought up in your datacenter, it should automatically join the Consul cluster without human intervention. Consul facilitates auto-join by enabling the auto-discovery of instances in AWS, Google Cloud or Azure with a given tag key/value. To use the integration, add the [`retry_join_ec2`](/docs/agent/options.html?#retry_join_ec2), [`retry_join_gce`](/docs/agent/options.html?#retry_join_gce) or the [`retry_join_azure`](/docs/agent/options.html?#retry_join_azure) nested object to your Consul configuration file. This will allow a new node to join the cluster without any hardcoded configuration. Alternatively, you can join a cluster at startup using the [`-join` flag](/docs/agent/options.html#_join) or [`start_join` setting](/docs/agent/options.html#start_join) with hardcoded addresses of other known Consul agents.
|
||||
|
||||
## Querying Nodes
|
||||
|
||||
|
|
Loading…
Reference in New Issue