This folder contains a Terraform module to deploy a
Consul cluster in AWS on top of an Auto Scaling Group. This module
is designed to deploy an Amazon Machine Image (AMI)
that has Consul installed via the install-consul module in this Module.
How do you use this module?
This folder defines a Terraform module, which you can use in your
code by adding a module configuration and setting its source parameter to URL of this folder:
module"consul_cluster" {
# TODO: update this to the final URL# Use version v0.0.5 of the consul-cluster module
source = "github.com/hashicorp/terraform-aws-consul//modules/consul-cluster?ref=v0.0.5"# Specify the ID of the Consul AMI. You should build this using the scripts in the install-consul module.
ami_id = "ami-abcd1234"# Add this tag to each node in the cluster
cluster_tag_key = "consul-cluster"
cluster_tag_value = "consul-cluster-example"# Configure and start Consul during boot. It will automatically form a cluster with all nodes that have that same tag.
user_data = <<-EOF
#!/bin/bash
/opt/consul/bin/run-consul --server --cluster-tag-key consul-cluster --cluster-tag-value consul-cluster-example
EOF
# ... See variables.tf for the other parameters you must define for the consul-cluster module
}
Note the following parameters:
source: Use this parameter to specify the URL of the consul-cluster module. The double slash (//) is intentional
and required. Terraform uses it to specify subfolders within a Git repo (see module
sources). The ref parameter specifies a specific Git tag in
this repo. That way, instead of using the latest version of this module from the master branch, which
will change every time you run Terraform, you're using a fixed version of the repo.
ami_id: Use this parameter to specify the ID of a Consul Amazon Machine Image
(AMI) to deploy on each server in the cluster. You
should install Consul in this AMI using the scripts in the install-consul module.
user_data: Use this parameter to specify a User
Data script that each
server will run during boot. This is where you can use the run-consul script to configure and
run Consul. The run-consul script is one of the scripts installed by the install-consul
module.
You can find the other parameters in variables.tf.
If you want to connect to the cluster from your own computer, the easiest way is to use the HTTP
API. Note that this only works if the Consul cluster is running in public
subnets and/or your default VPC (as in the consul-cluster example), which is OK for testing
and experimentation, but NOT recommended for production usage.
To use the HTTP API, you first need to get the public IP address of one of the Consul Servers. You can find Consul
servers by using AWS tags. If you're running the consul-cluster example, the
consul-examples-helper.sh script will do the tag lookup
for you automatically (note, you must have the AWS CLI,
jq, and the Consul agent installed locally):
> ../consul-examples-helper/consul-examples-helper.sh
Your Consul servers are running at the following IP addresses:
34.200.218.12334.205.127.13834.201.165.11
You can use one of these IP addresses with the members command to see a list of cluster nodes:
> consul members -http-addr=11.22.33.44:8500
Node Address Status Type Build Protocol DC
i-0051c3ea00e9691a0 172.31.35.148:8301 alive client 0.8.0 2 us-east-1
i-00aea529cce1761d4 172.31.47.236:8301 alive client 0.8.0 2 us-east-1
i-01bc94ccfa032d82d 172.31.27.193:8301 alive client 0.8.0 2 us-east-1
i-04271e97808f15d63 172.31.25.174:8301 alive server 0.8.0 2 us-east-1
i-0483b07abe49ea7ff 172.31.5.42:8301 alive client 0.8.0 2 us-east-1
i-098fb1ebd5ca443bf 172.31.55.203:8301 alive client 0.8.0 2 us-east-1
i-0eb961b6825f7871c 172.31.65.9:8301 alive client 0.8.0 2 us-east-1
i-0ee6dcf715adbff5f 172.31.67.235:8301 alive server 0.8.0 2 us-east-1
i-0fd0e63682a94b245 172.31.54.84:8301 alive server 0.8.0 2 us-east-1
You can also try inserting a value:
> consul kv put -http-addr=11.22.33.44:8500 foo bar
Success! Data written to: foo
And reading that value back:
> consul kv get -http-addr=11.22.33.44:8500 foo
bar
Finally, you can try opening up the Consul UI in your browser at the URL http://11.22.33.44:8500/ui/.
Using the Consul agent on another EC2 Instance
The easiest way to run Consul agent and have it connect to the Consul
cluster is to use the same EC2 tags the Consul servers use to discover each other during bootstrapping.
For example, imagine you deployed a Consul cluster in us-east-1 as follows:
module"consul_cluster" {
source = "github.com/hashicorp/terraform-aws-consul//modules/consul-cluster?ref=v0.0.5"# Add this tag to each node in the cluster
cluster_tag_key = "consul-cluster"
cluster_tag_value = "consul-cluster-example"# ... Other params omitted ...
}
Using the retry-join-ec2-xxx params, you can connect run a Consul agent on an EC2 Instance as follows:
consul agent -retry-join-ec2-tag-key=consul-cluster -retry-join-ec2-tag-value=consul-cluster-example -data-dir=/tmp/consul
Two important notes about this command:
By default, the Consul cluster nodes advertise their private IP addresses, so the command above only works from
EC2 Instances inside the same VPC (or any VPC with proper peering connections and route table entries).
In order to look up the EC2 tags, the EC2 Instance where you're running this command must have an IAM role with
the ec2:DescribeInstances permission.
How do you connect load balancers to the Auto Scaling Group (ASG)?
This module runs Consul on top of an Auto Scaling Group (ASG). Typically, you
should run the ASG with 3 or 5 EC2 Instances spread across multiple Availability
Zones. Each of the EC2
Instances should be running an AMI that has Consul installed via the install-consul
module. You pass in the ID of the AMI to run using the ami_id input parameter.
EC2 Instance Tags
This module allows you to specify a tag to add to each EC2 instance in the ASG. We recommend using this tag with the
retry_join_ec2 configuration to allow the EC2
Instances to find each other and automatically form a cluster.
Security Group
Each EC2 Instance in the ASG has a Security Group that allows:
Each EC2 Instance in the ASG has an IAM Role attached.
We give this IAM role a small set of IAM permissions that each EC2 Instance can use to automatically discover the other
Instances in its ASG and form a cluster with them. See the run-consul required permissions
docs for details.
The IAM Role ARN is exported as an output variable if you need to add additional permissions.
You can disable the creation of the IAM role and policies if needed by setting enable_iam_setup variable to false. This allows you to create the role seperately from this module and supply the external role arn via the iam_instance_profile_name variable.
How do you roll out updates?
If you want to deploy a new version of Consul across the cluster, the best way to do that is to:
Build a new AMI.
Set the ami_id parameter to the ID of the new AMI.
Run terraform apply.
This updates the Launch Configuration of the ASG, so any new Instances in the ASG will have your new AMI, but it does
NOT actually deploy those new instances. To make that happen, you should do the following:
Issue an API call to one of the old Instances in the ASG to have it leave gracefully. E.g.:
curl -XPUT <OLD_INSTANCE_IP>:8500/v1/agent/leave
Once the instance has left the cluster, terminate it:
After a minute or two, the ASG should automatically launch a new Instance, with the new AMI, to replace the old one.
Wait for the new Instance to boot and join the cluster.
Repeat these steps for each of the other old Instances in the ASG.
We will add a script in the future to automate this process (PRs are welcome!).
What happens if a node crashes?
There are two ways a Consul node may go down:
The Consul process may crash. In that case, systemd should restart it automatically.
The EC2 Instance running Consul dies. In that case, the Auto Scaling Group should launch a replacement automatically.
Note that in this case, since the Consul agent did not exit gracefully, and the replacement will have a different ID,
you may have to manually clean out the old nodes using the force-leave
command. We may add a script to do this
automatically in the future. For more info, see the Consul Outage
documentation.
Security
Here are some of the main security considerations to keep in mind when using this module:
The EC2 Instances in the cluster store all their data on the root EBS Volume. To enable encryption for the data at
rest, you must enable encryption in your Consul AMI. If you're creating the AMI using Packer (e.g. as shown in
the consul-ami example), you need to set the encrypt_boot
parameter to true.
Dedicated instances
If you wish to use dedicated instances, you can set the tenancy parameter to "dedicated" in this module.
Security groups
This module attaches a security group to each EC2 Instance that allows inbound requests as follows:
Consul: For all the ports used by Consul, you can
use the allowed_inbound_cidr_blocks parameter to control the list of
CIDR blocks that will be allowed access and the allowed_inbound_security_group_ids parameter to control the security groups that will be allowed access.
SSH: For the SSH port (default: 22), you can use the allowed_ssh_cidr_blocks parameter to control the list of
CIDR blocks that will be allowed access. You can use the allowed_inbound_ssh_security_group_ids parameter to control the list of source Security Groups that will be allowed access.
Note that all the ports mentioned above are configurable via the xxx_port variables (e.g. server_rpc_port). See
variables.tf for the full list.
SSH access
You can associate an EC2 Key Pair with each
of the EC2 Instances in this cluster by specifying the Key Pair's name in the ssh_key_name variable. If you don't
want to associate a Key Pair with these servers, set ssh_key_name to an empty string.
What's NOT included in this module?
This module does NOT handle the following items, which you may want to provide on your own:
This module does not include anything for monitoring, alerting, or log aggregation. All ASGs and EC2 Instances come
with limited CloudWatch metrics built-in, but beyond that, you will have to
provide your own solutions.
VPCs, subnets, route tables
This module assumes you've already created your network topology (VPC, subnets, route tables, etc). You will need to
pass in the the relevant info about your network topology (e.g. vpc_id, subnet_ids) as input variables to this
module.
DNS entries
This module does not create any DNS entries for Consul (e.g. in Route 53).
Questions? Ask away.
We're here to talk about our services, answer any questions, give advice, or just to chat.
{"treedata":{"name":"root","toggled":true,"children":[{"name":".circleci","children":[{"name":"config.yml","path":".circleci/config.yml","sha":"7cd21b1bd9fdbf5101b175a71ed1f83656945145"}]},{"name":".gitignore","path":".gitignore","sha":"866fba08567d4c22683017421abdb60f985833f2"},{"name":"CODEOWNERS","path":"CODEOWNERS","sha":"5949dbc0fa6d4dd6610575e3c878c353d92da44a"},{"name":"CONTRIBUTING.md","path":"CONTRIBUTING.md","sha":"bdfb309d7ff6bb05ffc1ea9453604805c022d13b"},{"name":"LICENSE","path":"LICENSE","sha":"7a4a3ea2424c09fbe48d455aed1eaa94d9124835"},{"name":"NOTICE","path":"NOTICE","sha":"9f6aed2a3cc04e4ef63c90448b2f0ac07378b270"},{"name":"README.md","path":"README.md","sha":"2f70405c336d19685c495170b103691ed604b300"},{"name":"_ci","children":[{"name":"publish-amis-in-new-account.md","path":"_ci/publish-amis-in-new-account.md","sha":"5b5daa55c3e36c1c4739471be92ccb53995f9783"},{"name":"publish-amis.sh","path":"_ci/publish-amis.sh","sha":"e575763ccd2158e49f3b5c53aad2b8f197992260"}]},{"name":"_docs","children":[{"name":"amazon-linux-ami-list.md","path":"_docs/amazon-linux-ami-list.md","sha":"fc523855ab81d4958d77179a64e3bb7d10782664"},{"name":"architecture.png","path":"_docs/architecture.png","sha":"539fece6e8a9fd7a56245e2b63e6640a1e0591ef"},{"name":"consul-ui-screenshot.png","path":"_docs/consul-ui-screenshot.png","sha":"622c7e70d3ab805b1bb8e27e474ff8243d4bc994"},{"name":"package-managers.md","path":"_docs/package-managers.md","sha":"f382b6997245e03adba61c02346519a70ee016d4"},{"name":"ubuntu16-ami-list.md","path":"_docs/ubuntu16-ami-list.md","sha":"706e3fd289f3aa1ddd4ef9c8555ab36d3b550ed9"}]},{"name":"examples","children":[{"name":"README.md","path":"examples/README.md","sha":"8eca7399ca356c90a307206147362bc35588179c"},{"name":"consul-ami","children":[{"name":"README.md","path":"examples/consul-ami/README.md","sha":"2b71fb3d7a4cd4176ece97a50a859e8106ebcd5a"},{"name":"consul.json","path":"examples/consul-ami/consul.json","sha":"cdfc5de67e013670d765c1ee3a3935e4cebac631"}]},{"name":"consul-examples-helper","children":[{"name":"README.md","path":"examples/consul-examples-helper/README.md","sha":"296f6878fd49edb27d6a6b62fa4119b6a407ba26"},{"name":"consul-examples-helper.sh","path":"examples/consul-examples-helper/consul-examples-helper.sh","sha":"fa9beba847bff2270703632a526ffbeb2070974c"}]},{"name":"example-with-custom-asg-role","children":[{"name":"README.md","path":"examples/example-with-custom-asg-role/README.md","sha":"fa1fbb9d7416fa96f13afb6b82664bbe1638ea3f"},{"name":"main.tf","path":"examples/example-with-custom-asg-role/main.tf","sha":"ed53539e6440f8b9a0b951c78b90270409daf07b"},{"name":"outputs.tf","path":"examples/example-with-custom-asg-role/outputs.tf","sha":"347e121901e654df4aaabe434af02c7ced60c051"},{"name":"user-data-client.sh","path":"examples/example-with-custom-asg-role/user-data-client.sh","sha":"fd0158b0287de6bf2a6956718c7aa802076fe489"},{"name":"user-data-server.sh","path":"examples/example-with-custom-asg-role/user-data-server.sh","sha":"c78ebc05584513fa04f44d44ef2b9d17c98e2ea6"},{"name":"variables.tf","path":"examples/example-with-custom-asg-role/variables.tf","sha":"a7f5025a21c9252bdb592a852bf485a426bc9347"}]},{"name":"example-with-encryption","children":[{"name":"README.md","path":"examples/example-with-encryption/README.md","sha":"0f2de7374cf63ee0b18cae4b960b85ec88403866"},{"name":"main.tf","path":"examples/example-with-encryption/main.tf","sha":"90b3095339a0ac6234c4165412534025ffe5e403"},{"name":"outputs.tf","path":"examples/example-with-encryption/outputs.tf","sha":"347e121901e654df4aaabe434af02c7ced60c051"},{"name":"packer","children":[{"name":"README.md","path":"examples/example-with-encryption/packer/README.md","sha":"b7b8ca11120c5f69565dab81eeeed659223538e9"},{"name":"ca.crt.pem","path":"examples/example-with-encryption/packer/ca.crt.pem","sha":"c41779f19be6d742d10be51d80c29aa5d41a660c"},{"name":"consul-with-certs.json","path":"examples/example-with-encryption/packer/consul-with-certs.json","sha":"fc2340056320869cd4ca98a10157e8d5ece038f7"},{"name":"consul.crt.pem","path":"examples/example-with-encryption/packer/consul.crt.pem","sha":"4a4ea11b89006f41fe2cb8b707d62463bb739184"},{"name":"consul.key.pem","path":"examples/example-with-encryption/packer/consul.key.pem","sha":"b7de428c63382b087007a236602797cc0a0a45f5"}]},{"name":"user-data-client.sh","path":"examples/example-with-encryption/user-data-client.sh","sha":"2ab3735d96777ce9324bc55f7be29c338ef0c1bb"},{"name":"user-data-server.sh","path":"examples/example-with-encryption/user-data-server.sh","sha":"5e97fc02da696b348ee26a2c1803bf7e3e018c79"},{"name":"variables.tf","path":"examples/example-with-encryption/variables.tf","sha":"2f741e848bd95f3d7798f4fd52021ddfdb30d3bd"}]},{"name":"root-example","children":[{"name":"README.md","path":"examples/root-example/README.md","sha":"6c562cb22c28cc5e35b5a21a94b66d6af9101081"},{"name":"user-data-client.sh","path":"examples/root-example/user-data-client.sh","sha":"fd0158b0287de6bf2a6956718c7aa802076fe489"},{"name":"user-data-server.sh","path":"examples/root-example/user-data-server.sh","sha":"c78ebc05584513fa04f44d44ef2b9d17c98e2ea6"}]}]},{"name":"main.tf","path":"main.tf","sha":"ce92b8206ed5e2be29d5e877235dca412bfdb47a"},{"name":"modules","children":[{"name":"README.md","path":"modules/README.md","sha":"8eca7399ca356c90a307206147362bc35588179c"},{"name":"consul-client-security-group-rules","children":[{"name":"README.md","path":"modules/consul-client-security-group-rules/README.md","sha":"777b679a73df3d5c96fb584176e91449c3a72615"},{"name":"main.tf","path":"modules/consul-client-security-group-rules/main.tf","sha":"a90c7a072a8d939094a0171f03520b22986a72d2"},{"name":"variables.tf","path":"modules/consul-client-security-group-rules/variables.tf","sha":"c9c25d00fdc683748570ba5e2ee718c9af8e6697"}]},{"name":"consul-cluster","children":[{"name":"README.md","path":"modules/consul-cluster/README.md","sha":"b9a3c55973ef27b0022f5eda05840f7cc65cea03","toggled":true},{"name":"main.tf","path":"modules/consul-cluster/main.tf","sha":"139789c8b623fd4523b9eb3ab15a57b48246d1e4"},{"name":"outputs.tf","path":"modules/consul-cluster/outputs.tf","sha":"a980d658e1be410a013a7e7febcc38a49899ffdc"},{"name":"variables.tf","path":"modules/consul-cluster/variables.tf","sha":"762180c029cf615583e163eaf5f7e91b00dc67c3"}],"toggled":true},{"name":"consul-iam-policies","children":[{"name":"README.md","path":"modules/consul-iam-policies/README.md","sha":"7725295fbdbf3d2fe881796e63074e3215854f5f"},{"name":"main.tf","path":"modules/consul-iam-policies/main.tf","sha":"815931541fb65a51755fd053e0e5f264eaf14111"},{"name":"variables.tf","path":"modules/consul-iam-policies/variables.tf","sha":"1bda7959f19ea4ab20108ec9fb8802036bfd22fd"}]},{"name":"consul-security-group-rules","children":[{"name":"README.md","path":"modules/consul-security-group-rules/README.md","sha":"315155181f5b42ff0fc6f16b86eaa930811366fe"},{"name":"main.tf","path":"modules/consul-security-group-rules/main.tf","sha":"2874ce4405e19fdb2d334911a2dc8098a8482b21"},{"name":"variables.tf","path":"modules/consul-security-group-rules/variables.tf","sha":"b9f5fe2bd11994d2d8eeb932db8650b43c47add1"}]},{"name":"install-consul","children":[{"name":"README.md","path":"modules/install-consul/README.md","sha":"1d451e16e08a4f6259cb3ee643e39d6171d5a4d9"},{"name":"install-consul","path":"modules/install-consul/install-consul","sha":"d1505e023bf0b9f4c779fa1f1b089879d6d864ba"}]},{"name":"install-dnsmasq","children":[{"name":"README.md","path":"modules/install-dnsmasq/README.md","sha":"80f0d44b7c7e916821ec8fd624d624f16eb2cb20"},{"name":"install-dnsmasq","path":"modules/install-dnsmasq/install-dnsmasq","sha":"b74b5307696bf4fbaa94192ba0e18ceccb0d947f"}]},{"name":"run-consul","children":[{"name":"README.md","path":"modules/run-consul/README.md","sha":"0b990a784db0354a8bd0401c8a64033765e01c05"},{"name":"run-consul","path":"modules/run-consul/run-consul","sha":"56c5054fe13c47a92fce0b8cfac4dcc510c52a34"}]},{"name":"setup-systemd-resolved","children":[{"name":"README.md","path":"modules/setup-systemd-resolved/README.md","sha":"92a5fb39c0eabefcfc84f20ebe7a994156774fc0"},{"name":"setup-systemd-resolved","path":"modules/setup-systemd-resolved/setup-systemd-resolved","sha":"d81c2bf0966dd1d1a1a97fc24c5862048b05b2cd"}]}],"toggled":true},{"name":"outputs.tf","path":"outputs.tf","sha":"347e121901e654df4aaabe434af02c7ced60c051"},{"name":"test","children":[{"name":"Gopkg.lock","path":"test/Gopkg.lock","sha":"880e78457fe502e2150412e5f511aaf0daa32d54"},{"name":"Gopkg.toml","path":"test/Gopkg.toml","sha":"9f0c0396c0addc41d8dcd195de8d9b4e63c81b18"},{"name":"README.md","path":"test/README.md","sha":"874818e6da7a9c0c9338edde0c27fa3f8a3b3d05"},{"name":"aws_helpers.go","path":"test/aws_helpers.go","sha":"867034abaf29d9c6e641daa9c4a51a8286ffb0ed"},{"name":"consul_cluster_test.go","path":"test/consul_cluster_test.go","sha":"179f09e027f87ee8b94debdd0a01a610708cc4b4"},{"name":"consul_cluster_with_custom_asg_role_test.go","path":"test/consul_cluster_with_custom_asg_role_test.go","sha":"96595f03771d0bb27d07ac9739284a9190be3530"},{"name":"consul_cluster_with_encryption_test.go","path":"test/consul_cluster_with_encryption_test.go","sha":"2149dd3a2eb3a0fbab52b17db8d0c18970df5d72"},{"name":"consul_enterprise_test.go","path":"test/consul_enterprise_test.go","sha":"6632f0529b899a314093b489335233108af86d21"},{"name":"consul_helpers.go","path":"test/consul_helpers.go","sha":"6c3cf0f8615f34d032114161d1fb0f580e930c9f"},{"name":"terratest_helpers.go","path":"test/terratest_helpers.go","sha":"cfe73dfea67dc40877e7df715c73dda501e614fd"}]},{"name":"variables.tf","path":"variables.tf","sha":"a2ab5424fac8bd42c8e48a9073ec2e1a403d5db3"}]},"detailsContent":"<h1 class=\"preview__body--title\" id=\"consul-cluster\">Consul Cluster</h1><div class=\"preview__body--border\"></div><p>This folder contains a <a href=\"https://www.terraform.io/\" class=\"preview__body--description--blue\" target=\"_blank\">Terraform</a> module to deploy a\n<a href=\"https://www.consul.io/\" class=\"preview__body--description--blue\" target=\"_blank\">Consul</a> cluster in <a href=\"https://aws.amazon.com/\" class=\"preview__body--description--blue\" target=\"_blank\">AWS</a> on top of an Auto Scaling Group. This module\nis designed to deploy an <a href=\"http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html\" class=\"preview__body--description--blue\" target=\"_blank\">Amazon Machine Image (AMI)</a>\nthat has Consul installed via the <a href=\"/repos/v0.9.2/terraform-aws-consul/modules/install-consul\" class=\"preview__body--description--blue\">install-consul</a> module in this Module.</p>\n<h2 class=\"preview__body--subtitle\" id=\"how-do-you-use-this-module\">How do you use this module?</h2>\n<p>This folder defines a <a href=\"https://www.terraform.io/docs/modules/usage.html\" class=\"preview__body--description--blue\" target=\"_blank\">Terraform module</a>, which you can use in your\ncode by adding a <code>module</code> configuration and setting its <code>source</code> parameter to URL of this folder:</p>\n<pre><span class=\"hljs-keyword\">module</span> <span class=\"hljs-string\">\"consul_cluster\"</span> {\n <span class=\"hljs-comment\"># <span class=\"hljs-doctag\">TODO:</span> update this to the final URL</span>\n <span class=\"hljs-comment\"># Use version v0.0.5 of the consul-cluster module</span>\n source = <span class=\"hljs-string\">\"github.com/hashicorp/terraform-aws-consul//modules/consul-cluster?ref=v0.0.5\"</span>\n\n <span class=\"hljs-comment\"># Specify the ID of the Consul AMI. You should build this using the scripts in the install-consul module.</span>\n ami_id = <span class=\"hljs-string\">\"ami-abcd1234\"</span>\n\n <span class=\"hljs-comment\"># Add this tag to each node in the cluster</span>\n cluster_tag_key = <span class=\"hljs-string\">\"consul-cluster\"</span>\n cluster_tag_value = <span class=\"hljs-string\">\"consul-cluster-example\"</span>\n\n <span class=\"hljs-comment\"># Configure and start Consul during boot. It will automatically form a cluster with all nodes that have that same tag.</span>\n user_data = <<-EOF\n <span class=\"hljs-comment\">#!/bin/bash</span>\n /opt/consul/bin/run-consul --server --cluster-tag-key consul-cluster --cluster-tag-value consul-cluster-example\n EOF\n\n <span class=\"hljs-comment\"># ... See variables.tf for the other parameters you must define for the consul-cluster module</span>\n}\n</pre>\n<p>Note the following parameters:</p>\n<ul>\n<li>\n<p><code>source</code>: Use this parameter to specify the URL of the consul-cluster module. The double slash (<code>//</code>) is intentional\nand required. Terraform uses it to specify subfolders within a Git repo (see <a href=\"https://www.terraform.io/docs/modules/sources.html\" class=\"preview__body--description--blue\" target=\"_blank\">module\nsources</a>). The <code>ref</code> parameter specifies a specific Git tag in\nthis repo. That way, instead of using the latest version of this module from the <code>master</code> branch, which\nwill change every time you run Terraform, you're using a fixed version of the repo.</p>\n</li>\n<li>\n<p><code>ami_id</code>: Use this parameter to specify the ID of a Consul <a href=\"http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html\" class=\"preview__body--description--blue\" target=\"_blank\">Amazon Machine Image\n(AMI)</a> to deploy on each server in the cluster. You\nshould install Consul in this AMI using the scripts in the <a href=\"/repos/v0.9.2/terraform-aws-consul/modules/install-consul\" class=\"preview__body--description--blue\">install-consul</a> module.</p>\n</li>\n<li>\n<p><code>user_data</code>: Use this parameter to specify a <a href=\"http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html#user-data-shell-scripts\" class=\"preview__body--description--blue\" target=\"_blank\">User\nData</a> script that each\nserver will run during boot. This is where you can use the <a href=\"/repos/v0.9.2/terraform-aws-consul/modules/run-consul\" class=\"preview__body--description--blue\">run-consul script</a> to configure and\nrun Consul. The <code>run-consul</code> script is one of the scripts installed by the <a href=\"/repos/v0.9.2/terraform-aws-consul/modules/install-consul\" class=\"preview__body--description--blue\">install-consul</a>\nmodule.</p>\n</li>\n</ul>\n<p>You can find the other parameters in <a href=\"/repos/v0.9.2/terraform-aws-consul/modules/consul-cluster/variables.tf\" class=\"preview__body--description--blue\">variables.tf</a>.</p>\n<p>Check out the <a href=\"/repos/v0.9.2/terraform-aws-consul/examples/root-example\" class=\"preview__body--description--blue\">consul-cluster example</a> for fully-working sample code.</p>\n<h2 class=\"preview__body--subtitle\" id=\"how-do-you-connect-to-the-consul-cluster\">How do you connect to the Consul cluster?</h2>\n<h3 class=\"preview__body--subtitle\" id=\"using-the-http-api-from-your-own-computer\">Using the HTTP API from your own computer</h3>\n<p>If you want to connect to the cluster from your own computer, the easiest way is to use the <a href=\"https://www.consul.io/docs/agent/http.html\" class=\"preview__body--description--blue\" target=\"_blank\">HTTP\nAPI</a>. Note that this only works if the Consul cluster is running in public\nsubnets and/or your default VPC (as in the <a href=\"/repos/v0.9.2/terraform-aws-consul/examples/root-example\" class=\"preview__body--description--blue\">consul-cluster example</a>), which is OK for testing\nand experimentation, but NOT recommended for production usage.</p>\n<p>To use the HTTP API, you first need to get the public IP address of one of the Consul Servers. You can find Consul\nservers by using AWS tags. If you're running the <a href=\"/repos/v0.9.2/terraform-aws-consul/examples/root-example\" class=\"preview__body--description--blue\">consul-cluster example</a>, the\n<a href=\"/repos/v0.9.2/terraform-aws-consul/examples/consul-examples-helper/consul-examples-helper.sh\" class=\"preview__body--description--blue\">consul-examples-helper.sh script</a> will do the tag lookup\nfor you automatically (note, you must have the <a href=\"https://aws.amazon.com/cli/\" class=\"preview__body--description--blue\" target=\"_blank\">AWS CLI</a>,\n<a href=\"https://stedolan.github.io/jq/\" class=\"preview__body--description--blue\" target=\"_blank\">jq</a>, and the <a href=\"https://www.consul.io/\" class=\"preview__body--description--blue\" target=\"_blank\">Consul agent</a> installed locally):</p>\n<pre>> ../consul-examples-helper/consul-examples-helper.sh\n\nYour Consul servers are running at the following IP addresses:\n\n<span class=\"hljs-number\">34.200.218.123</span>\n<span class=\"hljs-number\">34.205.127.138</span>\n<span class=\"hljs-number\">34.201.165.11</span>\n</pre>\n<p>You can use one of these IP addresses with the <code>members</code> command to see a list of cluster nodes:</p>\n<pre>> consul members <span class=\"hljs-attribute\">-http-addr</span>=11.22.33.44:8500\n\nNode <span class=\"hljs-built_in\"> Address </span> Status <span class=\"hljs-built_in\"> Type </span> Build Protocol DC\ni-0051c3ea00e9691a0 172.31.35.148:8301 alive <span class=\"hljs-built_in\"> client </span> 0.8.0 2 us-east-1\ni-00aea529cce1761d4 172.31.47.236:8301 alive <span class=\"hljs-built_in\"> client </span> 0.8.0 2 us-east-1\ni-01bc94ccfa032d82d 172.31.27.193:8301 alive <span class=\"hljs-built_in\"> client </span> 0.8.0 2 us-east-1\ni-04271e97808f15d63 172.31.25.174:8301 alive <span class=\"hljs-built_in\"> server </span> 0.8.0 2 us-east-1\ni-0483b07abe49ea7ff 172.31.5.42:8301 alive <span class=\"hljs-built_in\"> client </span> 0.8.0 2 us-east-1\ni-098fb1ebd5ca443bf 172.31.55.203:8301 alive <span class=\"hljs-built_in\"> client </span> 0.8.0 2 us-east-1\ni-0eb961b6825f7871c 172.31.65.9:8301 alive <span class=\"hljs-built_in\"> client </span> 0.8.0 2 us-east-1\ni-0ee6dcf715adbff5f 172.31.67.235:8301 alive <span class=\"hljs-built_in\"> server </span> 0.8.0 2 us-east-1\ni-0fd0e63682a94b245 172.31.54.84:8301 alive <span class=\"hljs-built_in\"> server </span> 0.8.0 2 us-east-1\n</pre>\n<p>You can also try inserting a value:</p>\n<pre>> consul kv put -http-addr=<span class=\"hljs-number\">11.22</span><span class=\"hljs-number\">.33</span><span class=\"hljs-number\">.44</span>:<span class=\"hljs-number\">8500</span> foo bar\n\nSuccess! Data written to: foo\n</pre>\n<p>And reading that value back:</p>\n<pre>> consul kv <span class=\"hljs-keyword\">get</span> -http-addr=<span class=\"hljs-number\">11.22</span><span class=\"hljs-number\">.33</span><span class=\"hljs-number\">.44</span>:<span class=\"hljs-number\">8500</span> foo\n\nbar\n</pre>\n<p>Finally, you can try opening up the Consul UI in your browser at the URL <code>http://11.22.33.44:8500/ui/</code>.</p>\n<p><img src=\"/repos/images/v0.9.2/terraform-aws-consul/_docs/consul-ui-screenshot.png?raw=true\" alt=\"Consul UI\" class=\"preview__body--diagram\"></p>\n<h3 class=\"preview__body--subtitle\" id=\"using-the-consul-agent-on-another-ec-2-instance\">Using the Consul agent on another EC2 Instance</h3>\n<p>The easiest way to run <a href=\"https://www.consul.io/docs/agent/basics.html\" class=\"preview__body--description--blue\" target=\"_blank\">Consul agent</a> and have it connect to the Consul\ncluster is to use the same EC2 tags the Consul servers use to discover each other during bootstrapping.</p>\n<p>For example, imagine you deployed a Consul cluster in <code>us-east-1</code> as follows:</p>\n<p></p>\n<pre><span class=\"hljs-keyword\">module</span> <span class=\"hljs-string\">\"consul_cluster\"</span> {\n source = <span class=\"hljs-string\">\"github.com/hashicorp/terraform-aws-consul//modules/consul-cluster?ref=v0.0.5\"</span>\n\n <span class=\"hljs-comment\"># Add this tag to each node in the cluster</span>\n cluster_tag_key = <span class=\"hljs-string\">\"consul-cluster\"</span>\n cluster_tag_value = <span class=\"hljs-string\">\"consul-cluster-example\"</span>\n\n <span class=\"hljs-comment\"># ... Other params omitted ...</span>\n}\n</pre>\n<p>Using the <code>retry-join-ec2-xxx</code> params, you can connect run a Consul agent on an EC2 Instance as follows:</p>\n<pre>consul agent <span class=\"hljs-attribute\">-retry-join-ec2-tag-key</span>=consul-cluster <span class=\"hljs-attribute\">-retry-join-ec2-tag-value</span>=consul-cluster-example <span class=\"hljs-attribute\">-data-dir</span>=/tmp/consul\n</pre>\n<p>Two important notes about this command:</p>\n<ol>\n<li>By default, the Consul cluster nodes advertise their <em>private</em> IP addresses, so the command above only works from\nEC2 Instances inside the same VPC (or any VPC with proper peering connections and route table entries).</li>\n<li>In order to look up the EC2 tags, the EC2 Instance where you're running this command must have an IAM role with\nthe <code>ec2:DescribeInstances</code> permission.</li>\n</ol>\n<h2 class=\"preview__body--subtitle\" id=\"how-do-you-connect-load-balancers-to-the-auto-scaling-group-asg\">How do you connect load balancers to the Auto Scaling Group (ASG)?</h2>\n<p>You can use the <a href=\"https://www.terraform.io/docs/providers/aws/r/autoscaling_attachment.html\" class=\"preview__body--description--blue\" target=\"_blank\"><code>aws_autoscaling_attachment</code></a> resource.</p>\n<p>For example, if you are using the new application or network load balancers:</p>\n<pre><span class=\"hljs-keyword\">resource</span> <span class=\"hljs-string\">\"aws_lb_target_group\"</span> <span class=\"hljs-string\">\"test\"</span> {\n // ...\n}\n\n<span class=\"hljs-comment\"># Create a new Consul Cluster</span>\n<span class=\"hljs-keyword\">module</span> <span class=\"hljs-string\">\"consul\"</span> {\n source =<span class=\"hljs-string\">\"...\"</span>\n // ...\n}\n\n<span class=\"hljs-comment\"># Create a new load balancer attachment</span>\n<span class=\"hljs-keyword\">resource</span> <span class=\"hljs-string\">\"aws_autoscaling_attachment\"</span> <span class=\"hljs-string\">\"asg_attachment_bar\"</span> {\n autoscaling_group_name = <span class=\"hljs-string\">\"<span class=\"hljs-variable\">${module.consul.asg_name}</span>\"</span>\n alb_target_group_arn = <span class=\"hljs-string\">\"<span class=\"hljs-variable\">${aws_alb_target_group.test.arn}</span>\"</span>\n}\n</pre>\n<p>If you are using a "classic" load balancer:</p>\n<pre><span class=\"hljs-comment\"># Create a new load balancer</span>\n<span class=\"hljs-keyword\">resource</span> <span class=\"hljs-string\">\"aws_elb\"</span> <span class=\"hljs-string\">\"bar\"</span> {\n // ...\n}\n\n<span class=\"hljs-comment\"># Create a new Consul Cluster</span>\n<span class=\"hljs-keyword\">module</span> <span class=\"hljs-string\">\"consul\"</span> {\n source =<span class=\"hljs-string\">\"...\"</span>\n // ...\n}\n\n<span class=\"hljs-comment\"># Create a new load balancer attachment</span>\n<span class=\"hljs-keyword\">resource</span> <span class=\"hljs-string\">\"aws_autoscaling_attachment\"</span> <span class=\"hljs-string\">\"asg_attachment_bar\"</span> {\n autoscaling_group_name = <span class=\"hljs-string\">\"<span class=\"hljs-variable\">${module.consul.asg_name}</span>\"</span>\n elb = <span class=\"hljs-string\">\"<span class=\"hljs-variable\">${aws_elb.bar.id}</span>\"</span>\n}\n</pre>\n<h2 class=\"preview__body--subtitle\" id=\"whats-included-in-this-module\">What's included in this module?</h2>\n<p>This module creates the following architecture:</p>\n<p><img src=\"/repos/images/v0.9.2/terraform-aws-consul/_docs/architecture.png?raw=true\" alt=\"Consul architecture\" class=\"preview__body--diagram\"></p>\n<p>This architecture consists of the following resources:</p>\n<ul>\n<li><a href=\"#auto-scaling-group\" class=\"preview__body--description--blue\">Auto Scaling Group</a></li>\n<li><a href=\"#ec2-instance-tags\" class=\"preview__body--description--blue\">EC2 Instance Tags</a></li>\n<li><a href=\"#security-group\" class=\"preview__body--description--blue\">Security Group</a></li>\n<li><a href=\"#iam-role-and-permissions\" class=\"preview__body--description--blue\">IAM Role and Permissions</a></li>\n</ul>\n<h3 class=\"preview__body--subtitle\" id=\"auto-scaling-group\">Auto Scaling Group</h3>\n<p>This module runs Consul on top of an <a href=\"https://aws.amazon.com/autoscaling/\" class=\"preview__body--description--blue\" target=\"_blank\">Auto Scaling Group (ASG)</a>. Typically, you\nshould run the ASG with 3 or 5 EC2 Instances spread across multiple <a href=\"http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html\" class=\"preview__body--description--blue\" target=\"_blank\">Availability\nZones</a>. Each of the EC2\nInstances should be running an AMI that has Consul installed via the <a href=\"/repos/v0.9.2/terraform-aws-consul/modules/install-consul\" class=\"preview__body--description--blue\">install-consul</a>\nmodule. You pass in the ID of the AMI to run using the <code>ami_id</code> input parameter.</p>\n<h3 class=\"preview__body--subtitle\" id=\"ec-2-instance-tags\">EC2 Instance Tags</h3>\n<p>This module allows you to specify a tag to add to each EC2 instance in the ASG. We recommend using this tag with the\n<a href=\"https://www.consul.io/docs/agent/options.html?#retry_join_ec2\" class=\"preview__body--description--blue\" target=\"_blank\">retry_join_ec2</a> configuration to allow the EC2\nInstances to find each other and automatically form a cluster.</p>\n<h3 class=\"preview__body--subtitle\" id=\"security-group\">Security Group</h3>\n<p>Each EC2 Instance in the ASG has a Security Group that allows:</p>\n<ul>\n<li>All outbound requests</li>\n<li>All the inbound ports specified in the <a href=\"https://www.consul.io/docs/agent/options.html?#ports-used\" class=\"preview__body--description--blue\" target=\"_blank\">Consul documentation</a></li>\n</ul>\n<p>The Security Group ID is exported as an output variable if you need to add additional rules.</p>\n<p>Check out the <a href=\"#security\" class=\"preview__body--description--blue\">Security section</a> for more details.</p>\n<h3 class=\"preview__body--subtitle\" id=\"iam-role-and-permissions\">IAM Role and Permissions</h3>\n<p>Each EC2 Instance in the ASG has an <a href=\"http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html\" class=\"preview__body--description--blue\" target=\"_blank\">IAM Role</a> attached.\nWe give this IAM role a small set of IAM permissions that each EC2 Instance can use to automatically discover the other\nInstances in its ASG and form a cluster with them. See the <a href=\"/repos/v0.9.2/terraform-aws-consul/modules/run-consul#required-permissions\" class=\"preview__body--description--blue\">run-consul required permissions\ndocs</a> for details.</p>\n<p>The IAM Role ARN is exported as an output variable if you need to add additional permissions.</p>\n<p>You can disable the creation of the IAM role and policies if needed by setting <code>enable_iam_setup</code> variable to false. This allows you to create the role seperately from this module and supply the external role arn via the <code>iam_instance_profile_name</code> variable.</p>\n<h2 class=\"preview__body--subtitle\" id=\"how-do-you-roll-out-updates\">How do you roll out updates?</h2>\n<p>If you want to deploy a new version of Consul across the cluster, the best way to do that is to:</p>\n<ol>\n<li>Build a new AMI.</li>\n<li>Set the <code>ami_id</code> parameter to the ID of the new AMI.</li>\n<li>Run <code>terraform apply</code>.</li>\n</ol>\n<p>This updates the Launch Configuration of the ASG, so any new Instances in the ASG will have your new AMI, but it does\nNOT actually deploy those new instances. To make that happen, you should do the following:</p>\n<ol>\n<li>\n<p>Issue an API call to one of the old Instances in the ASG to have it leave gracefully. E.g.:</p>\n<pre>curl -<span class=\"hljs-meta\">X</span> <span class=\"hljs-meta\">PUT</span> <OLD_INSTANCE_IP>:8500/v1/agent/<span class=\"hljs-meta\">leave</span>\n</pre>\n</li>\n<li>\n<p>Once the instance has left the cluster, terminate it:</p>\n<pre>aws ec2 <span class=\"hljs-keyword\">terminate</span>-instances <span class=\"hljs-comment\">--instance-ids <OLD_INSTANCE_ID></span>\n</pre>\n</li>\n<li>\n<p>After a minute or two, the ASG should automatically launch a new Instance, with the new AMI, to replace the old one.</p>\n</li>\n<li>\n<p>Wait for the new Instance to boot and join the cluster.</p>\n</li>\n<li>\n<p>Repeat these steps for each of the other old Instances in the ASG.</p>\n</li>\n</ol>\n<p>We will add a script in the future to automate this process (PRs are welcome!).</p>\n<h2 class=\"preview__body--subtitle\" id=\"what-happens-if-a-node-crashes\">What happens if a node crashes?</h2>\n<p>There are two ways a Consul node may go down:</p>\n<ol>\n<li>The Consul process may crash. In that case, <code>systemd</code> should restart it automatically.</li>\n<li>The EC2 Instance running Consul dies. In that case, the Auto Scaling Group should launch a replacement automatically.\nNote that in this case, since the Consul agent did not exit gracefully, and the replacement will have a different ID,\nyou may have to manually clean out the old nodes using the <a href=\"https://www.consul.io/docs/commands/force-leave.html\" class=\"preview__body--description--blue\" target=\"_blank\">force-leave\ncommand</a>. We may add a script to do this\nautomatically in the future. For more info, see the <a href=\"https://www.consul.io/docs/guides/outage.html\" class=\"preview__body--description--blue\" target=\"_blank\">Consul Outage\ndocumentation</a>.</li>\n</ol>\n<h2 class=\"preview__body--subtitle\" id=\"security\">Security</h2>\n<p>Here are some of the main security considerations to keep in mind when using this module:</p>\n<ol>\n<li><a href=\"#encryption-in-transit\" class=\"preview__body--description--blue\">Encryption in transit</a></li>\n<li><a href=\"#encryption-at-rest\" class=\"preview__body--description--blue\">Encryption at rest</a></li>\n<li><a href=\"#dedicated-instances\" class=\"preview__body--description--blue\">Dedicated instances</a></li>\n<li><a href=\"#security-groups\" class=\"preview__body--description--blue\">Security groups</a></li>\n<li><a href=\"#ssh-access\" class=\"preview__body--description--blue\">SSH access</a></li>\n</ol>\n<h3 class=\"preview__body--subtitle\" id=\"encryption-in-transit\">Encryption in transit</h3>\n<p>Consul can encrypt all of its network traffic. For instructions on enabling network encryption, have a look at the\n<a href=\"/repos/v0.9.2/terraform-aws-consul/modules/run-consul#how-do-you-handle-encryption\" class=\"preview__body--description--blue\">How do you handle encryption documentation</a>.</p>\n<h3 class=\"preview__body--subtitle\" id=\"encryption-at-rest\">Encryption at rest</h3>\n<p>The EC2 Instances in the cluster store all their data on the root EBS Volume. To enable encryption for the data at\nrest, you must enable encryption in your Consul AMI. If you're creating the AMI using Packer (e.g. as shown in\nthe <a href=\"/repos/v0.9.2/terraform-aws-consul/examples/consul-ami\" class=\"preview__body--description--blue\">consul-ami example</a>), you need to set the <a href=\"https://www.packer.io/docs/builders/amazon-ebs.html#encrypt_boot\" class=\"preview__body--description--blue\" target=\"_blank\">encrypt_boot\nparameter</a> to <code>true</code>.</p>\n<h3 class=\"preview__body--subtitle\" id=\"dedicated-instances\">Dedicated instances</h3>\n<p>If you wish to use dedicated instances, you can set the <code>tenancy</code> parameter to <code>"dedicated"</code> in this module.</p>\n<h3 class=\"preview__body--subtitle\" id=\"security-groups\">Security groups</h3>\n<p>This module attaches a security group to each EC2 Instance that allows inbound requests as follows:</p>\n<ul>\n<li>\n<p><strong>Consul</strong>: For all the <a href=\"https://www.consul.io/docs/agent/options.html#ports\" class=\"preview__body--description--blue\" target=\"_blank\">ports used by Consul</a>, you can\nuse the <code>allowed_inbound_cidr_blocks</code> parameter to control the list of\n<a href=\"https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing\" class=\"preview__body--description--blue\" target=\"_blank\">CIDR blocks</a> that will be allowed access and the <code>allowed_inbound_security_group_ids</code> parameter to control the security groups that will be allowed access.</p>\n</li>\n<li>\n<p><strong>SSH</strong>: For the SSH port (default: 22), you can use the <code>allowed_ssh_cidr_blocks</code> parameter to control the list of\n<a href=\"https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing\" class=\"preview__body--description--blue\" target=\"_blank\">CIDR blocks</a> that will be allowed access. You can use the <code>allowed_inbound_ssh_security_group_ids</code> parameter to control the list of source Security Groups that will be allowed access.</p>\n</li>\n</ul>\n<p>Note that all the ports mentioned above are configurable via the <code>xxx_port</code> variables (e.g. <code>server_rpc_port</code>). See\n<a href=\"/repos/v0.9.2/terraform-aws-consul/modules/consul-cluster/variables.tf\" class=\"preview__body--description--blue\">variables.tf</a> for the full list.</p>\n<h3 class=\"preview__body--subtitle\" id=\"ssh-access\">SSH access</h3>\n<p>You can associate an <a href=\"http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html\" class=\"preview__body--description--blue\" target=\"_blank\">EC2 Key Pair</a> with each\nof the EC2 Instances in this cluster by specifying the Key Pair's name in the <code>ssh_key_name</code> variable. If you don't\nwant to associate a Key Pair with these servers, set <code>ssh_key_name</code> to an empty string.</p>\n<h2 class=\"preview__body--subtitle\" id=\"whats-not-included-in-this-module\">What's NOT included in this module?</h2>\n<p>This module does NOT handle the following items, which you may want to provide on your own:</p>\n<ul>\n<li><a href=\"#monitoring-alerting-log-aggregation\" class=\"preview__body--description--blue\">Monitoring, alerting, log aggregation</a></li>\n<li><a href=\"#vpcs-subnets-route-tables\" class=\"preview__body--description--blue\">VPCs, subnets, route tables</a></li>\n<li><a href=\"#dns-entries\" class=\"preview__body--description--blue\">DNS entries</a></li>\n</ul>\n<h3 class=\"preview__body--subtitle\" id=\"monitoring-alerting-log-aggregation\">Monitoring, alerting, log aggregation</h3>\n<p>This module does not include anything for monitoring, alerting, or log aggregation. All ASGs and EC2 Instances come\nwith limited <a href=\"https://aws.amazon.com/cloudwatch/\" class=\"preview__body--description--blue\" target=\"_blank\">CloudWatch</a> metrics built-in, but beyond that, you will have to\nprovide your own solutions.</p>\n<h3 class=\"preview__body--subtitle\" id=\"vp-cs-subnets-route-tables\">VPCs, subnets, route tables</h3>\n<p>This module assumes you've already created your network topology (VPC, subnets, route tables, etc). You will need to\npass in the the relevant info about your network topology (e.g. <code>vpc_id</code>, <code>subnet_ids</code>) as input variables to this\nmodule.</p>\n<h3 class=\"preview__body--subtitle\" id=\"dns-entries\">DNS entries</h3>\n<p>This module does not create any DNS entries for Consul (e.g. in Route 53).</p>\n","repoName":"terraform-aws-consul","repoRef":"v0.7.4","serviceDescriptor":{"serviceName":"HashiCorp Consul","serviceRepoName":"terraform-aws-consul","serviceRepoOrg":"hashicorp","cloudProviders":["aws"],"description":"Deploy a Consul cluster. Supports automatic bootstrapping, DNS, Consul UI, and auto healing.","imageUrl":"consul.png","licenseType":"open-source","technologies":["Terraform","Bash"],"compliance":[],"tags":[""]},"serviceCategoryName":"Service Mesh","fileName":"README.md","filePath":"/modules/consul-cluster","title":"Repo Browser: HashiCorp Consul","description":"Browse the repos in the Gruntwork Infrastructure as Code Library."}