website: intro in progress
This commit is contained in:
parent
8517d0950b
commit
b8b5f15f3a
|
@ -1,125 +0,0 @@
|
|||
---
|
||||
layout: "intro"
|
||||
page_title: "Run the Agent"
|
||||
sidebar_current: "gettingstarted-agent"
|
||||
---
|
||||
|
||||
# Run the Terraform Agent
|
||||
|
||||
After Terraform is installed, the agent must be run. The agent can either run
|
||||
in a server or client mode. Each datacenter must have at least one server,
|
||||
although 3 or 5 is recommended. A single server deployment is _**highly**_ discouraged
|
||||
as data loss is inevitable in a failure scenario. [This guide](/docs/guides/bootstrapping.html)
|
||||
covers bootstrapping a new datacenter. All other agents run in client mode, which
|
||||
is a very lightweight process that registers services, runs health checks,
|
||||
and forwards queries to servers. The agent must be run for every node that
|
||||
will be part of the cluster.
|
||||
|
||||
## Starting the Agent
|
||||
|
||||
For simplicity, we'll run a single Terraform agent in server mode right now:
|
||||
|
||||
```
|
||||
$ terraform agent -server -bootstrap -data-dir /tmp/consul
|
||||
==> WARNING: Bootstrap mode enabled! Do not enable unless necessary
|
||||
==> WARNING: It is highly recommended to set GOMAXPROCS higher than 1
|
||||
==> Starting Terraform agent...
|
||||
==> Starting Terraform agent RPC...
|
||||
==> Terraform agent running!
|
||||
Node name: 'Armons-MacBook-Air'
|
||||
Datacenter: 'dc1'
|
||||
Server: true (bootstrap: true)
|
||||
Client Addr: 127.0.0.1 (HTTP: 8500, DNS: 8600, RPC: 8400)
|
||||
Cluster Addr: 10.1.10.38 (LAN: 8301, WAN: 8302)
|
||||
|
||||
==> Log data will now stream in as it occurs:
|
||||
|
||||
[INFO] serf: EventMemberJoin: Armons-MacBook-Air.local 10.1.10.38
|
||||
[INFO] raft: Node at 10.1.10.38:8300 [Follower] entering Follower state
|
||||
[INFO] terraform: adding server for datacenter: dc1, addr: 10.1.10.38:8300
|
||||
[ERR] agent: failed to sync remote state: rpc error: No cluster leader
|
||||
[WARN] raft: Heartbeat timeout reached, starting election
|
||||
[INFO] raft: Node at 10.1.10.38:8300 [Candidate] entering Candidate state
|
||||
[INFO] raft: Election won. Tally: 1
|
||||
[INFO] raft: Node at 10.1.10.38:8300 [Leader] entering Leader state
|
||||
[INFO] terraform: cluster leadership acquired
|
||||
[INFO] terraform: New leader elected: Armons-MacBook-Air
|
||||
[INFO] terraform: member 'Armons-MacBook-Air' joined, marking health alive
|
||||
```
|
||||
|
||||
As you can see, the Terraform agent has started and has output some log
|
||||
data. From the log data, you can see that our agent is running in server mode,
|
||||
and has claimed leadership of the cluster. Additionally, the local member has
|
||||
been marked as a healthy member of the cluster.
|
||||
|
||||
<div class="alert alert-block alert-warning">
|
||||
<strong>Note for OS X Users:</strong> Terraform uses your hostname as the
|
||||
default node name. If your hostname contains periods, DNS queries to
|
||||
that node will not work with Terraform. To avoid this, explicitly set
|
||||
the name of your node with the <code>-node</code> flag.
|
||||
</div>
|
||||
|
||||
## Cluster Members
|
||||
|
||||
If you run `terraform members` in another terminal, you can see the members of
|
||||
the Terraform cluster. You should only see one member (yourself). We'll cover
|
||||
joining clusters in the next section.
|
||||
|
||||
```
|
||||
$ terraform members
|
||||
Armons-MacBook-Air 10.1.10.38:8301 alive role=terraform,dc=dc1,vsn=1,vsn_min=1,vsn_max=1,port=8300,bootstrap=1
|
||||
```
|
||||
|
||||
The output shows our own node, the address it is running on, its
|
||||
health state, and some metadata associated with the node. Some important
|
||||
metadata keys to recognize are the `role` and `dc` keys. These tell you
|
||||
the service name and the datacenter that member is within. These can be
|
||||
used to lookup nodes and services using the DNS interface, which is covered
|
||||
shortly.
|
||||
|
||||
The output from the `members` command is generated based on the
|
||||
[gossip protocol](/docs/internals/gossip.html) and is eventually consistent.
|
||||
For a strongly consistent view of the world, use the
|
||||
[HTTP API](/docs/agent/http.html), which forwards the request to the
|
||||
Terraform servers:
|
||||
|
||||
```
|
||||
$ curl localhost:8500/v1/catalog/nodes
|
||||
[{"Node":"Armons-MacBook-Air","Address":"10.1.10.38"}]
|
||||
```
|
||||
|
||||
In addition to the HTTP API, the
|
||||
[DNS interface](/docs/agent/dns.html) can be used to query the node. Note
|
||||
that you have to make sure to point your DNS lookups to the Terraform agent's
|
||||
DNS server, which runs on port 8600 by default. The format of the DNS
|
||||
entries (such as "Armons-MacBook-Air.node.terraform") will be covered later.
|
||||
|
||||
```
|
||||
$ dig @127.0.0.1 -p 8600 Armons-MacBook-Air.node.terraform
|
||||
...
|
||||
|
||||
;; QUESTION SECTION:
|
||||
;Armons-MacBook-Air.node.terraform. IN A
|
||||
|
||||
;; ANSWER SECTION:
|
||||
Armons-MacBook-Air.node.terraform. 0 IN A 10.1.10.38
|
||||
```
|
||||
|
||||
## Stopping the Agent
|
||||
|
||||
You can use `Ctrl-C` (the interrupt signal) to gracefully halt the agent.
|
||||
After interrupting the agent, you should see it leave the cluster gracefully
|
||||
and shut down.
|
||||
|
||||
By gracefully leaving, Terraform notifies other cluster members that the
|
||||
node _left_. If you had forcibly killed the agent process, other members
|
||||
of the cluster would have detected that the node _failed_. When a member leaves,
|
||||
its services and checks are removed from the catalog. When a member fails,
|
||||
its health is simply marked as critical, but is not removed from the catalog.
|
||||
Terraform will automatically try to reconnect to _failed_ nodes, which allows it
|
||||
to recover from certain network conditions, while _left_ nodes are no longer contacted.
|
||||
|
||||
Additionally, if an agent is operating as a server, a graceful leave is important
|
||||
to avoid causing a potential availability outage affecting the [consensus protocol](/docs/internals/consensus.html).
|
||||
See the [guides section](/docs/guides/index.html) to safely add and remove servers.
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
---
|
||||
layout: "intro"
|
||||
page_title: "Build Infrastructure"
|
||||
sidebar_current: "gettingstarted-build"
|
||||
---
|
||||
|
||||
# Build Infrastructure
|
||||
|
||||
With Terraform installed, let's dive right into it and start creating
|
||||
some infrastructure.
|
||||
|
||||
We'll build infrastructure on
|
||||
[AWS](http://aws.amazon.com) for the getting started guide
|
||||
since it is popular and generally understood, but Terraform
|
||||
can [manage many providers](#),
|
||||
including multiple providers in a single configuration.
|
||||
Some examples of this are in the
|
||||
[use cases section](/intro/use-cases.html).
|
||||
|
||||
If you don't have an AWS account,
|
||||
[create one now](http://aws.amazon.com/free/).
|
||||
For the getting started guide, we'll only be using resources
|
||||
which qualify under the AWS
|
||||
[free-tier](http://aws.amazon.com/free/),
|
||||
meaning it will be free.
|
||||
If you already have an AWS account, you may be charged some
|
||||
amount of money, but it shouldn't be more than a few dollars
|
||||
at most.
|
||||
|
||||
<div class="alert alert-block alert-warning">
|
||||
<p>
|
||||
<strong>Note:</strong> If you're not using an account that qualifies
|
||||
under the AWS
|
||||
<a href="http://aws.amazon.com/free/">free-tier</a>,
|
||||
you may be charged to run these examples. The most you should
|
||||
be charged should only be a few dollars, but we're not responsible
|
||||
for any charges that may incur.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
## Configuration
|
||||
|
||||
The set of files used to describe infrastructure in Terraform is simply
|
||||
known as a Terraform _configuration_. We're going to write our first
|
||||
configuration now to launch a single AWS EC2 instance.
|
||||
|
||||
The format of the configuration files is
|
||||
[documented here](#).
|
||||
Configuration files can
|
||||
[also be JSON](#), but we recommend only using JSON when the
|
||||
configuration is generated by a machine.
|
||||
|
||||
The entire configuration is shown below. We'll go over each part
|
||||
after. Save the contents to a file named `example.tf`. Verify that
|
||||
there are no other `*.tf` files in your directory, since Terraform
|
||||
loads all of them.
|
||||
|
||||
```
|
||||
provider "aws" {
|
||||
access_key = "ACCESS_KEY_HERE"
|
||||
secret_key = "SECRET_KEY_HERE"
|
||||
region = "us-east-1"
|
||||
}
|
||||
|
||||
resource "aws_instance" "example" {
|
||||
ami = "ami-408c7f28"
|
||||
instance_type = "t1.micro"
|
||||
}
|
||||
```
|
||||
|
||||
Replace the `ACCESS_KEY_HERE` and `SECRET_KEY_HERE` with your
|
||||
AWS access key and secret key, available from
|
||||
[this page](https://console.aws.amazon.com/iam/home?#security_credential).
|
||||
We're hardcoding them for now, but will extract these into
|
||||
variables later in the getting started guide.
|
||||
|
||||
This is a complete configuration that Terraform is ready to apply.
|
||||
The general structure should be intuitive and straightforward.
|
||||
|
||||
The `provider` block is used to configure the named provider, in
|
||||
our case "aws." A provider is responsible for creating and
|
||||
managing resources. Multiple provider blocks can exist if a
|
||||
Terraform configuration is comprised of multiple providers,
|
||||
which is a common situation.
|
||||
|
||||
The `resource` block defines a resource that exists within
|
||||
the infrastructure. A resource might be a physical component such
|
||||
as an EC2 instance, or it can be a logical resource such as
|
||||
a Heroku applcation.
|
||||
|
||||
The resource block has two strings before opening the block:
|
||||
the resource type and the resource name. In our example, the
|
||||
resource type is "aws\_instance" and the name is "example."
|
||||
The prefix of the type maps to the provider. In our case
|
||||
"aws\_instance" automatically tells Terraform that it is
|
||||
managed by the "aws" provider.
|
||||
|
||||
Within the resource block itself is configuration for that
|
||||
resource. This is dependent on each resource provider and
|
||||
is fully documented within our
|
||||
[providers reference](#). For our EC2 instance, we specify
|
||||
an AMI for Ubuntu, and request a "t1.micro" instance so we
|
||||
qualify under the free tier.
|
||||
|
||||
## Execution Plan
|
||||
|
||||
Next, let's see what Terraform would do if we asked it to
|
||||
apply this configuration. In the same directory as the
|
||||
`example.tf` file you created, run `terraform plan`. You
|
||||
should see output similar to what is copied below. We've
|
||||
truncated some of the output to save space.
|
||||
|
||||
```
|
||||
$ terraform plan
|
||||
...
|
||||
|
||||
+ aws_instance.example
|
||||
ami: "" => "ami-408c7f28"
|
||||
availability_zone: "" => "<computed>"
|
||||
instance_type: "" => "t1.micro"
|
||||
key_name: "" => "<computed>"
|
||||
private_dns: "" => "<computed>"
|
||||
private_ip: "" => "<computed>"
|
||||
public_dns: "" => "<computed>"
|
||||
public_ip: "" => "<computed>"
|
||||
security_groups: "" => "<computed>"
|
||||
subnet_id: "" => "<computed>"
|
||||
```
|
||||
|
||||
`terraform plan` shows what changes Terraform will apply to
|
||||
your infrastructure given the current state of your infrastructure
|
||||
as well as the current contents of your configuration.
|
||||
|
||||
If `terraform plan` failed with an error, read the error message
|
||||
and fix the error that occurred. At this stage, it is probably a
|
||||
syntax error in the configuration.
|
||||
|
||||
The output format is similar to the diff format generated by tools
|
||||
such as Git. The output has a "+" next to "aws\_instance.example",
|
||||
meaning that Terraform will create this resource. Beneath that,
|
||||
it shows the attributes that will be set. When the value it is
|
||||
going to is `<computed>`, it means that the value won't be known
|
||||
until the resource is created.
|
||||
|
||||
## Apply
|
||||
|
||||
The plan looks good, our configuration appears valid, so its time to
|
||||
create real resources. Run `terraform apply` in the same directory
|
||||
as your `example.tf`, and watch it go! It will take a few minutes
|
||||
since Terraform waits for the EC2 instance to become available.
|
||||
|
||||
```
|
||||
$ terraform apply
|
||||
aws_instance.example: Creating...
|
||||
ami: "" => "ami-408c7f28"
|
||||
instance_type: "" => "t1.micro"
|
||||
|
||||
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Done! You can go to the AWS console to prove to yourself that the
|
||||
EC2 instance has been created.
|
||||
|
||||
Terraform also put some state into the `terraform.tfstate` file
|
||||
by default. This state file is extremely important; it maps various
|
||||
resource metadata to actual resource IDs so that Terraform knows
|
||||
what it is managing. This file must be saved and distributed
|
||||
to anyone who might run Terraform. We recommend simply putting it
|
||||
into version control, since it generally isn't too large.
|
||||
|
||||
You can inspect the state using `terraform show`:
|
||||
|
||||
```
|
||||
$ terraform show
|
||||
aws_instance.example:
|
||||
id = i-e60900cd
|
||||
ami = ami-408c7f28
|
||||
availability_zone = us-east-1c
|
||||
instance_type = t1.micro
|
||||
key_name =
|
||||
private_dns = domU-12-31-39-12-38-AB.compute-1.internal
|
||||
private_ip = 10.200.59.89
|
||||
public_dns = ec2-54-81-21-192.compute-1.amazonaws.com
|
||||
public_ip = 54.81.21.192
|
||||
security_groups.# = 1
|
||||
security_groups.0 = default
|
||||
subnet_id =
|
||||
```
|
||||
|
||||
You can see that by creating our resource, we've also gathered
|
||||
a lot more metadata about it. This metadata can actually be referenced
|
||||
for other resources or outputs, which will be covered later in
|
||||
the getting started guide.
|
||||
|
||||
## Next
|
||||
|
||||
Congratulations! You've built your first infrastructure with Terraform.
|
||||
You've seen the configuration syntax, an example of a basic execution
|
||||
plan, and understand the state file.
|
||||
|
||||
Next, we're going to move on to changing and destroying infrastructure.
|
|
@ -0,0 +1,95 @@
|
|||
---
|
||||
layout: "intro"
|
||||
page_title: "Change Infrastructure"
|
||||
sidebar_current: "gettingstarted-change"
|
||||
---
|
||||
|
||||
# Change Infrastructure
|
||||
|
||||
In the previous page, you created your first infrastructure with
|
||||
Terraform: a single EC2 instance. In this page, we're going to
|
||||
modify that resource, and see how Terraform handles change.
|
||||
|
||||
Infrastructure is continuously evolving, and Terraform was built
|
||||
to help manage and enact that change. As you change Terraform
|
||||
configurations, Terraform builds an execution plan that only
|
||||
modifies what is necessary to reach your desired state.
|
||||
|
||||
By using Terraform to change infrastructure, you can version
|
||||
control not only your configurations but also your state so you
|
||||
can see how the infrastructure evolved over time.
|
||||
|
||||
## Configuration
|
||||
|
||||
Let's modify the `ami` of our instance. Edit the "aws\_instance.web"
|
||||
resource in your configuration and change it to the following:
|
||||
|
||||
```
|
||||
resource "aws_instance" "example" {
|
||||
ami = "ami-aa7ab6c2"
|
||||
instance_type = "t1.micro"
|
||||
}
|
||||
```
|
||||
|
||||
We've changed the AMI from being an Ubuntu 14.04 AMI to being
|
||||
an Ubuntu 12.04 AMI. Terraform configurations are meant to be
|
||||
changed like this. You can also completely remove resources
|
||||
and Terraform will know to destroy the old one.
|
||||
|
||||
## Execution Plan
|
||||
|
||||
Let's see what Terraform will do with the change we made.
|
||||
|
||||
```
|
||||
$ terraform plan
|
||||
...
|
||||
|
||||
-/+ aws_instance.example
|
||||
ami: "ami-408c7f28" => "ami-aa7ab6c2" (forces new resource)
|
||||
availability_zone: "us-east-1c" => "<computed>"
|
||||
key_name: "" => "<computed>"
|
||||
private_dns: "domU-12-31-39-12-38-AB.compute-1.internal" => "<computed>"
|
||||
private_ip: "10.200.59.89" => "<computed>"
|
||||
public_dns: "ec2-54-81-21-192.compute-1.amazonaws.com" => "<computed>"
|
||||
public_ip: "54.81.21.192" => "<computed>"
|
||||
security_groups: "" => "<computed>"
|
||||
subnet_id: "" => "<computed>"
|
||||
```
|
||||
|
||||
The prefix "-/+" means that Terraform will destroy and recreate
|
||||
the resource, versus purely updating it in-place. While some attributes
|
||||
can do in-place updates (which are shown with a "~" prefix), AMI
|
||||
changing on EC2 instance requires a new resource. Terraform handles
|
||||
these details for you, and the execution plan makes it clear what
|
||||
Terraform will do.
|
||||
|
||||
Additionally, the plan output shows that the AMI change is what
|
||||
necessitated the creation of a new resource. Using this information,
|
||||
you can tweak your changes to possibly avoid destroy/create updates
|
||||
if you didn't want to do them at this time.
|
||||
|
||||
## Apply
|
||||
|
||||
From the plan, we know what will happen. Let's apply and enact
|
||||
the change.
|
||||
|
||||
```
|
||||
$ terraform apply
|
||||
aws_instance.example: Destroying...
|
||||
aws_instance.example: Modifying...
|
||||
ami: "ami-408c7f28" => "ami-aa7ab6c2"
|
||||
|
||||
Apply complete! Resources: 0 added, 1 changed, 1 destroyed.
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
As the plan predicted, Terraform started by destroying our old
|
||||
instance, then creating the new one. You can use `terraform show`
|
||||
again to see the new properties associated with this instance.
|
||||
|
||||
## Next
|
||||
|
||||
You've now seen how easy it is to modify infrastructure with
|
||||
Terraform. Feel free to play around with this more before continuing.
|
||||
In the next section we're going to destroy our infrastructure.
|
|
@ -1,94 +0,0 @@
|
|||
---
|
||||
layout: "intro"
|
||||
page_title: "Registering Health Checks"
|
||||
sidebar_current: "gettingstarted-checks"
|
||||
---
|
||||
|
||||
# Health Checks
|
||||
|
||||
We've now seen how simple it is to run Terraform, add nodes and services, and
|
||||
query those nodes and services. In this section we will continue by adding
|
||||
health checks to both nodes and services, a critical component of service
|
||||
discovery that prevents using services that are unhealthy.
|
||||
|
||||
This page will build upon the previous page and assumes you have a
|
||||
two node cluster running.
|
||||
|
||||
## Defining Checks
|
||||
|
||||
Similarly to a service, a check can be registered either by providing a
|
||||
[check definition](/docs/agent/checks.html)
|
||||
, or by making the appropriate calls to the
|
||||
[HTTP API](/docs/agent/http.html).
|
||||
|
||||
We will use the check definition, because just like services, definitions
|
||||
are the most common way to setup checks.
|
||||
|
||||
Create two definition files in the Terraform configuration directory of
|
||||
the second node.
|
||||
The first file will add a host-level check, and the second will modify the web
|
||||
service definition to add a service-level check.
|
||||
|
||||
```
|
||||
$ echo '{"check": {"name": "ping", "script": "ping -c1 google.com >/dev/null", "interval": "30s"}}' >/etc/terraform.d/ping.json
|
||||
|
||||
$ echo '{"service": {"name": "web", "tags": ["rails"], "port": 80,
|
||||
"check": {"script": "curl localhost:80 >/dev/null 2>&1", "interval": "10s"}}}' >/etc/terraform.d/web.json
|
||||
```
|
||||
|
||||
The first definition adds a host-level check named "ping". This check runs
|
||||
on a 30 second interval, invoking `ping -c1 google.com`. If the command
|
||||
exits with a non-zero exit code, then the node will be flagged unhealthy.
|
||||
|
||||
The second command modifies the web service and adds a check that uses
|
||||
curl every 10 seconds to verify that the web server is running.
|
||||
|
||||
Restart the second agent, or send a `SIGHUP` to it. We should now see the
|
||||
following log lines:
|
||||
|
||||
```
|
||||
==> Starting Terraform agent...
|
||||
...
|
||||
[INFO] agent: Synced service 'web'
|
||||
[INFO] agent: Synced check 'service:web'
|
||||
[INFO] agent: Synced check 'ping'
|
||||
[WARN] Check 'service:web' is now critical
|
||||
```
|
||||
|
||||
The first few log lines indicate that the agent has synced the new
|
||||
definitions. The last line indicates that the check we added for
|
||||
the `web` service is critical. This is because we're not actually running
|
||||
a web server and the curl test is failing!
|
||||
|
||||
## Checking Health Status
|
||||
|
||||
Now that we've added some simple checks, we can use the HTTP API to check
|
||||
them. First, we can look for any failing checks. You can run this curl
|
||||
on either node:
|
||||
|
||||
```
|
||||
$ curl http://localhost:8500/v1/health/state/critical
|
||||
[{"Node":"agent-two","CheckID":"service:web","Name":"Service 'web' check","Status":"critical","Notes":"","ServiceID":"web","ServiceName":"web"}]
|
||||
```
|
||||
|
||||
We can see that there is only a single check in the `critical` state, which is
|
||||
our `web` service check.
|
||||
|
||||
Additionally, we can attempt to query the web service using DNS. Terraform
|
||||
will not return any results, since the service is unhealthy:
|
||||
|
||||
```
|
||||
dig @127.0.0.1 -p 8600 web.service.terraform
|
||||
...
|
||||
|
||||
;; QUESTION SECTION:
|
||||
;web.service.terraform. IN A
|
||||
```
|
||||
|
||||
This section should have shown that checks can be easily added. Check definitions
|
||||
can be updated by changing configuration files and sending a `SIGHUP` to the agent.
|
||||
Alternatively the HTTP API can be used to add, remove and modify checks dynamically.
|
||||
The API allows for a "dead man's switch" or [TTL based check](/docs/agent/checks.html).
|
||||
TTL checks can be used to integrate an application more tightly with Terraform, enabling
|
||||
business logic to be evaluated as part of passing a check.
|
||||
|
|
@ -6,36 +6,25 @@ sidebar_current: "gettingstarted-install"
|
|||
|
||||
# Install Terraform
|
||||
|
||||
Terraform must first be installed on every node that will be a member of a
|
||||
Terraform cluster. To make installation easy, Terraform is distributed as a
|
||||
[binary package](/downloads.html) for all supported platforms and
|
||||
architectures. This page will not cover how to compile Terraform from
|
||||
Terraform must first be installed on your machine. Terraform is distributed
|
||||
as a [binary package](/downloads.html) for all supported platforms and
|
||||
architecture. This page will not cover how to compile Terraform from
|
||||
source.
|
||||
|
||||
## Installing Terraform
|
||||
|
||||
To install Terraform, find the [appropriate package](/downloads.html) for
|
||||
your system and download it. Terraform is packaged as a "zip" archive.
|
||||
your system and download it. Terraform is packaged as a zip archive.
|
||||
|
||||
After downloading Terraform, unzip the package. Copy the `terraform` binary to
|
||||
somewhere on the PATH so that it can be executed. On Unix systems,
|
||||
`~/bin` and `/usr/local/bin` are common installation directories,
|
||||
depending on if you want to restrict the install to a single user or
|
||||
expose it to the entire system. On Windows systems, you can put it wherever
|
||||
you would like.
|
||||
|
||||
### OS X
|
||||
|
||||
If you are using [homebrew](http://brew.sh/#install) as a package manager,
|
||||
than you can install terraform as simple as:
|
||||
```
|
||||
brew cask install terraform
|
||||
```
|
||||
|
||||
if you are missing the [cask plugin](http://caskroom.io/) you can install it with:
|
||||
```
|
||||
brew install caskroom/cask/brew-cask
|
||||
```
|
||||
After downloading Terraform, unzip the package into a directory where
|
||||
Terraform will be installed. The directory will contain a set of binary
|
||||
programs, such as `terraform`, `terraform-provider-aws`, etc. The final
|
||||
step is to make sure the directory you installed Terraform to is on the
|
||||
PATH. See
|
||||
[this page](http://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux)
|
||||
for instructions on setting the PATH on Linux and Mac.
|
||||
[This page](http://stackoverflow.com/questions/1618280/where-can-i-set-path-to-make-exe-on-windows)
|
||||
contains instructions for setting the PATH on Windows.
|
||||
|
||||
## Verifying the Installation
|
||||
|
||||
|
@ -48,15 +37,13 @@ $ terraform
|
|||
usage: terraform [--version] [--help] <command> [<args>]
|
||||
|
||||
Available commands are:
|
||||
agent Runs a Terraform agent
|
||||
force-leave Forces a member of the cluster to enter the "left" state
|
||||
info Provides debugging information for operators
|
||||
join Tell Terraform agent to join cluster
|
||||
keygen Generates a new encryption key
|
||||
leave Gracefully leaves the Terraform cluster and shuts down
|
||||
members Lists the members of a Terraform cluster
|
||||
monitor Stream logs from a Terraform agent
|
||||
version Prints the Terraform version
|
||||
apply Builds or changes infrastructure
|
||||
graph Create a visual graph of Terraform resources
|
||||
output Read an output from a state file
|
||||
plan Generate and show an execution plan
|
||||
refresh Update local state file against real resources
|
||||
show Inspect Terraform state or plan
|
||||
version Prints the Terraform version
|
||||
```
|
||||
|
||||
If you get an error that `terraform` could not be found, then your PATH
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
---
|
||||
layout: "intro"
|
||||
page_title: "Terraform Cluster"
|
||||
sidebar_current: "gettingstarted-join"
|
||||
---
|
||||
|
||||
# Terraform Cluster
|
||||
|
||||
By this point, we've started our first agent and registered and queried
|
||||
one or more services on that agent. This showed how easy it is to use
|
||||
Terraform, but didn't show how this could be extended to a scalable production
|
||||
service discovery infrastructure. On this page, we'll create our first
|
||||
real cluster with multiple members.
|
||||
|
||||
When starting a Terraform agent, it begins without knowledge of any other node, and is
|
||||
an isolated cluster of one. To learn about other cluster members, the agent must
|
||||
_join_ an existing cluster. To join an existing cluster, only needs to know
|
||||
about a _single_ existing member. After it joins, the agent will gossip with this
|
||||
member and quickly discover the other members in the cluster. A Terraform
|
||||
agent can join any other agent, it doesn't have to be an agent in server mode.
|
||||
|
||||
## Starting the Agents
|
||||
|
||||
To simulate a more realistic cluster, we are using a two node cluster in
|
||||
Vagrant. The Vagrantfile can be found in the demo section of the repo
|
||||
[here](https://github.com/hashicorp/terraform/tree/master/demo/vagrant-cluster).
|
||||
|
||||
We start the first agent on our first node and also specify a node name.
|
||||
The node name must be unique and is how a machine is uniquely identified.
|
||||
By default it is the hostname of the machine, but we'll manually override it.
|
||||
We are also providing a bind address. This is the address that Terraform listens on,
|
||||
and it *must* be accessible by all other nodes in the cluster. The first node
|
||||
will act as our server in this cluster. We're still not making a cluster
|
||||
of servers.
|
||||
|
||||
```
|
||||
$ terraform agent -server -bootstrap -data-dir /tmp/consul \
|
||||
-node=agent-one -bind=172.20.20.10
|
||||
...
|
||||
```
|
||||
|
||||
Then, in another terminal, start the second agent on the new node.
|
||||
This time, we set the bind address to match the IP of the second node
|
||||
as specified in the Vagrantfile. In production, you will generally want
|
||||
to provide a bind address or interface as well.
|
||||
|
||||
```
|
||||
$ terraform agent -data-dir /tmp/consul -node=agent-two -bind=172.20.20.11
|
||||
...
|
||||
```
|
||||
|
||||
At this point, you have two Terraform agents running, one server and one client.
|
||||
The two Terraform agents still don't know anything about each other, and are each part of their own
|
||||
clusters (of one member). You can verify this by running `terraform members`
|
||||
against each agent and noting that only one member is a part of each.
|
||||
|
||||
## Joining a Cluster
|
||||
|
||||
Now, let's tell the first agent to join the second agent by running
|
||||
the following command in a new terminal:
|
||||
|
||||
```
|
||||
$ terraform join 172.20.20.11
|
||||
Successfully joined cluster by contacting 1 nodes.
|
||||
```
|
||||
|
||||
You should see some log output in each of the agent logs. If you read
|
||||
carefully, you'll see that they received join information. If you
|
||||
run `terraform members` against each agent, you'll see that both agents now
|
||||
know about each other:
|
||||
|
||||
```
|
||||
$ terraform members
|
||||
agent-one 172.20.20.10:8301 alive role=terraform,dc=dc1,vsn=1,vsn_min=1,vsn_max=1,port=8300,bootstrap=1
|
||||
agent-two 172.20.20.11:8301 alive role=node,dc=dc1,vsn=1,vsn_min=1,vsn_max=1
|
||||
```
|
||||
|
||||
<div class="alert alert-block alert-info">
|
||||
<p><strong>Remember:</strong> To join a cluster, a Terraform agent needs to only
|
||||
learn about <em>one existing member</em>. After joining the cluster, the
|
||||
agents gossip with each other to propagate full membership information.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
In addition to using `terraform join` you can use the `-join` flag on
|
||||
`terraform agent` to join a cluster as part of starting up the agent.
|
||||
|
||||
## Querying Nodes
|
||||
|
||||
Just like querying services, Terraform has an API for querying the
|
||||
nodes themselves. You can do this via the DNS or HTTP API.
|
||||
|
||||
For the DNS API, the structure of the names is `NAME.node.terraform` or
|
||||
`NAME.DATACENTER.node.terraform`. If the datacenter is omitted, Terraform
|
||||
will only search the local datacenter.
|
||||
|
||||
From "agent-one", query "agent-two":
|
||||
|
||||
```
|
||||
$ dig @127.0.0.1 -p 8600 agent-two.node.terraform
|
||||
...
|
||||
|
||||
;; QUESTION SECTION:
|
||||
;agent-two.node.terraform. IN A
|
||||
|
||||
;; ANSWER SECTION:
|
||||
agent-two.node.terraform. 0 IN A 172.20.20.11
|
||||
```
|
||||
|
||||
The ability to look up nodes in addition to services is incredibly
|
||||
useful for system administration tasks. For example, knowing the address
|
||||
of the node to SSH into is as easy as making it part of the Terraform cluster
|
||||
and querying it.
|
||||
|
||||
## Leaving a Cluster
|
||||
|
||||
To leave the cluster, you can either gracefully quit an agent (using
|
||||
`Ctrl-C`) or force kill one of the agents. Gracefully leaving allows
|
||||
the node to transition into the _left_ state, otherwise other nodes
|
||||
will detect it as having _failed_. The difference is covered
|
||||
in more detail [here](/intro/getting-started/agent.html#toc_3).
|
|
@ -1,118 +0,0 @@
|
|||
---
|
||||
layout: "intro"
|
||||
page_title: "Key/Value Data"
|
||||
sidebar_current: "gettingstarted-kv"
|
||||
---
|
||||
|
||||
# Key/Value Data
|
||||
|
||||
In addition to providing service discovery and integrated health checking,
|
||||
Terraform provides an easy to use Key/Value store. This can be used to hold
|
||||
dynamic configuration, assist in service coordination, build leader election,
|
||||
and anything else a developer can think to build. The
|
||||
[HTTP API](/docs/agent/http.html) fully documents the features of the K/V store.
|
||||
|
||||
This page assumes you have at least one Terraform agent already running.
|
||||
|
||||
## Simple Usage
|
||||
|
||||
To demonstrate how simple it is to get started, we will manipulate a few keys
|
||||
in the K/V store.
|
||||
|
||||
Querying the agent we started in a prior page, we can first verify that
|
||||
there are no existing keys in the k/v store:
|
||||
|
||||
```
|
||||
$ curl -v http://localhost:8500/v1/kv/?recurse
|
||||
* About to connect() to localhost port 8500 (#0)
|
||||
* Trying 127.0.0.1... connected
|
||||
> GET /v1/kv/?recurse HTTP/1.1
|
||||
> User-Agent: curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3
|
||||
> Host: localhost:8500
|
||||
> Accept: */*
|
||||
>
|
||||
< HTTP/1.1 404 Not Found
|
||||
< X-Terraform-Index: 1
|
||||
< Date: Fri, 11 Apr 2014 02:10:28 GMT
|
||||
< Content-Length: 0
|
||||
< Content-Type: text/plain; charset=utf-8
|
||||
<
|
||||
* Connection #0 to host localhost left intact
|
||||
* Closing connection #0
|
||||
```
|
||||
|
||||
Since there are no keys, we get a 404 response back.
|
||||
Now, we can put a few example keys:
|
||||
|
||||
```
|
||||
$ curl -X PUT -d 'test' http://localhost:8500/v1/kv/web/key1
|
||||
true
|
||||
$ curl -X PUT -d 'test' http://localhost:8500/v1/kv/web/key2?flags=42
|
||||
true
|
||||
$ curl -X PUT -d 'test' http://localhost:8500/v1/kv/web/sub/key3
|
||||
true
|
||||
$ curl http://localhost:8500/v1/kv/?recurse
|
||||
[{"CreateIndex":97,"ModifyIndex":97,"Key":"web/key1","Flags":0,"Value":"dGVzdA=="},
|
||||
{"CreateIndex":98,"ModifyIndex":98,"Key":"web/key2","Flags":42,"Value":"dGVzdA=="},
|
||||
{"CreateIndex":99,"ModifyIndex":99,"Key":"web/sub/key3","Flags":0,"Value":"dGVzdA=="}]
|
||||
```
|
||||
|
||||
Here we have created 3 keys, each with the value of "test". Note that the
|
||||
`Value` field returned is base64 encoded to allow non-UTF8
|
||||
characters. For the "web/key2" key, we set a `flag` value of 42. All keys
|
||||
support setting a 64bit integer flag value. This is opaque to Terraform but can
|
||||
be used by clients for any purpose.
|
||||
|
||||
After setting the values, we then issued a GET request to retrieve multiple
|
||||
keys using the `?recurse` parameter.
|
||||
|
||||
You can also fetch a single key just as easily:
|
||||
|
||||
```
|
||||
$ curl http://localhost:8500/v1/kv/web/key1
|
||||
[{"CreateIndex":97,"ModifyIndex":97,"Key":"web/key1","Flags":0,"Value":"dGVzdA=="}]
|
||||
```
|
||||
|
||||
Deleting keys is simple as well. We can delete a single key by specifying the
|
||||
full path, or we can recursively delete all keys under a root using "?recurse":
|
||||
|
||||
```
|
||||
$ curl -X DELETE http://localhost:8500/v1/kv/web/sub?recurse
|
||||
$ curl http://localhost:8500/v1/kv/web?recurse
|
||||
[{"CreateIndex":97,"ModifyIndex":97,"Key":"web/key1","Flags":0,"Value":"dGVzdA=="},
|
||||
{"CreateIndex":98,"ModifyIndex":98,"Key":"web/key2","Flags":42,"Value":"dGVzdA=="}]
|
||||
```
|
||||
|
||||
A key can be updated by setting a new value by issuing the same PUT request.
|
||||
Additionally, Terraform provides a Check-And-Set operation, enabling atomic
|
||||
key updates. This is done by providing the `?cas=` paramter with the last
|
||||
`ModifyIndex` value from the GET request. For example, suppose we wanted
|
||||
to update "web/key1":
|
||||
|
||||
```
|
||||
$ curl -X PUT -d 'newval' http://localhost:8500/v1/kv/web/key1?cas=97
|
||||
true
|
||||
$ curl -X PUT -d 'newval' http://localhost:8500/v1/kv/web/key1?cas=97
|
||||
false
|
||||
```
|
||||
|
||||
In this case, the first CAS update succeeds because the last modify time is 97.
|
||||
However the second operation fails because the `ModifyIndex` is no longer 97.
|
||||
|
||||
We can also make use of the `ModifyIndex` to wait for a key's value to change.
|
||||
For example, suppose we wanted to wait for key2 to be modified:
|
||||
|
||||
```
|
||||
$ curl "http://localhost:8500/v1/kv/web/key2?index=101&wait=5s"
|
||||
[{"CreateIndex":98,"ModifyIndex":101,"Key":"web/key2","Flags":42,"Value":"dGVzdA=="}]
|
||||
```
|
||||
|
||||
By providing "?index=" we are asking to wait until the key has a `ModifyIndex` greater
|
||||
than 101. However the "?wait=5s" parameter restricts the query to at most 5 seconds,
|
||||
returning the current, unchanged value. This can be used to efficiently wait for
|
||||
key modifications. Additionally, this same technique can be used to wait for a list
|
||||
of keys, waiting only until any of the keys has a newer modification time.
|
||||
|
||||
This is only a few example of what the API supports. For full documentation, please
|
||||
reference the [HTTP API](/docs/agent/http.html).
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
---
|
||||
layout: "intro"
|
||||
page_title: "Registering Services"
|
||||
sidebar_current: "gettingstarted-services"
|
||||
---
|
||||
|
||||
# Registering Services
|
||||
|
||||
In the previous page, we ran our first agent, saw the cluster members, and
|
||||
queried that node. On this page, we'll register our first service and query
|
||||
that service. We're not yet running a cluster of Terraform agents.
|
||||
|
||||
## Defining a Service
|
||||
|
||||
A service can be registered either by providing a
|
||||
[service definition](/docs/agent/services.html),
|
||||
or by making the appropriate calls to the
|
||||
[HTTP API](/docs/agent/http.html).
|
||||
|
||||
We're going to start by registering a service using a service definition,
|
||||
since this is the most common way that services are registered. We'll be
|
||||
building on what we covered in the
|
||||
[previous page](/intro/getting-started/agent.html).
|
||||
|
||||
First, create a directory for Terraform configurations. A good directory
|
||||
is typically `/etc/terraform.d`. Terraform loads all configuration files in the
|
||||
configuration directory.
|
||||
|
||||
```
|
||||
$ sudo mkdir /etc/terraform.d
|
||||
```
|
||||
|
||||
Next, we'll write a service definition configuration file. We'll
|
||||
pretend we have a service named "web" running on port 80. Additionally,
|
||||
we'll give it some tags, which we can use as additional ways to query
|
||||
it later.
|
||||
|
||||
```
|
||||
$ echo '{"service": {"name": "web", "tags": ["rails"], "port": 80}}' \
|
||||
>/etc/terraform.d/web.json
|
||||
```
|
||||
|
||||
Now, restart the agent we're running, providing the configuration directory:
|
||||
|
||||
```
|
||||
$ terraform agent -server -bootstrap -data-dir /tmp/consul -config-dir /etc/consul.d
|
||||
==> Starting Terraform agent...
|
||||
...
|
||||
[INFO] agent: Synced service 'web'
|
||||
...
|
||||
```
|
||||
|
||||
You'll notice in the output that it "synced" the web service. This means
|
||||
that it loaded the information from the configuration.
|
||||
|
||||
If you wanted to register multiple services, you create multiple service
|
||||
definition files in the Terraform configuration directory.
|
||||
|
||||
## Querying Services
|
||||
|
||||
Once the agent is started and the service is synced, we can query that
|
||||
service using either the DNS or HTTP API.
|
||||
|
||||
### DNS API
|
||||
|
||||
Let's first query it using the DNS API. For the DNS API, the DNS name
|
||||
for services is `NAME.service.terraform`. All DNS names are always in the
|
||||
`terraform` namespace. The `service` subdomain on that tells Terraform we're
|
||||
querying services, and the `NAME` is the name of the service. For the
|
||||
web service we registered, that would be `web.service.terraform`:
|
||||
|
||||
```
|
||||
$ dig @127.0.0.1 -p 8600 web.service.terraform
|
||||
...
|
||||
|
||||
;; QUESTION SECTION:
|
||||
;web.service.terraform. IN A
|
||||
|
||||
;; ANSWER SECTION:
|
||||
web.service.terraform. 0 IN A 172.20.20.11
|
||||
```
|
||||
|
||||
As you can see, an A record was returned with the IP address of the node that
|
||||
the service is available on. A records can only hold IP addresses. You can
|
||||
also use the DNS API to retrieve the entire address/port pair using SRV
|
||||
records:
|
||||
|
||||
```
|
||||
$ dig @127.0.0.1 -p 8600 web.service.terraform SRV
|
||||
...
|
||||
|
||||
;; QUESTION SECTION:
|
||||
;web.service.terraform. IN SRV
|
||||
|
||||
;; ANSWER SECTION:
|
||||
web.service.terraform. 0 IN SRV 1 1 80 agent-one.node.dc1.consul.
|
||||
|
||||
;; ADDITIONAL SECTION:
|
||||
agent-one.node.dc1.terraform. 0 IN A 172.20.20.11
|
||||
```
|
||||
|
||||
The SRV record returned says that the web service is running on port 80
|
||||
and exists on the node `agent-one.node.dc1.terraform.`. An additional section
|
||||
is returned by the DNS with the A record for that node.
|
||||
|
||||
Finally, we can also use the DNS API to filter services by tags. The
|
||||
format for tag-based service queries is `TAG.NAME.service.terraform`. In
|
||||
the example below, we ask Terraform for all web services with the "rails"
|
||||
tag. We get a response since we registered our service with that tag.
|
||||
|
||||
```
|
||||
$ dig @127.0.0.1 -p 8600 rails.web.service.terraform
|
||||
...
|
||||
|
||||
;; QUESTION SECTION:
|
||||
;rails.web.service.terraform. IN A
|
||||
|
||||
;; ANSWER SECTION:
|
||||
rails.web.service.terraform. 0 IN A 172.20.20.11
|
||||
```
|
||||
|
||||
### HTTP API
|
||||
|
||||
In addition to the DNS API, the HTTP API can be used to query services:
|
||||
|
||||
```
|
||||
$ curl http://localhost:8500/v1/catalog/service/web
|
||||
[{"Node":"agent-one","Address":"172.20.20.11","ServiceID":"web","ServiceName":"web","ServiceTags":["rails"],"ServicePort":80}]
|
||||
```
|
||||
|
||||
## Updating Services
|
||||
|
||||
Service definitions can be updated by changing configuration files and
|
||||
sending a `SIGHUP` to the agent. This lets you update services without
|
||||
any downtime or unavailability to service queries.
|
||||
|
||||
Alternatively the HTTP API can be used to add, remove, and modify services
|
||||
dynamically.
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
---
|
||||
layout: "intro"
|
||||
page_title: "Web UI"
|
||||
sidebar_current: "gettingstarted-ui"
|
||||
---
|
||||
|
||||
# Terraform Web UI
|
||||
|
||||
Terraform comes with support for a
|
||||
[beautiful, functional web UI](http://demo.terraform.io) out of the box.
|
||||
This UI can be used for viewing all services and nodes, viewing all
|
||||
health checks and their current status, and for reading and setting
|
||||
key/value data. The UI automatically supports multi-datacenter.
|
||||
|
||||
For ease of deployment, the UI is
|
||||
[distributed](/downloads_web_ui.html)
|
||||
as static HTML and JavaScript.
|
||||
You don't need a separate web server to run the web UI. The Terraform
|
||||
agent itself can be configured to serve the UI.
|
||||
|
||||
## Screenshot and Demo
|
||||
|
||||
You can view a live demo of the Terraform Web UI
|
||||
[here](http://demo.terraform.io).
|
||||
|
||||
While the live demo is able to access data from all datacenters,
|
||||
we've also setup demo endpoints in the specific datacenters:
|
||||
[AMS2](http://ams2.demo.terraform.io) (Amsterdam),
|
||||
[SFO1](http://sfo1.demo.terraform.io) (San Francisco),
|
||||
and [NYC1](http://nyc1.demo.terraform.io) (New York).
|
||||
|
||||
A screenshot of one page of the demo is shown below so you can get an
|
||||
idea of what the web UI is like. Click the screenshot for the full size.
|
||||
|
||||
<div class="center">
|
||||
<a href="/images/terraform_web_ui.png">
|
||||
<img src="/images/terraform_web_ui.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## Set Up
|
||||
|
||||
To set up the web UI,
|
||||
[download the web UI package](/downloads_web_ui.html)
|
||||
and unzip it to a directory somewhere on the server where the Terraform agent
|
||||
is also being run. Then, just append the `-ui-dir` to the `terraform agent`
|
||||
command pointing to the directory where you unzipped the UI (the
|
||||
directory with the `index.html` file):
|
||||
|
||||
```
|
||||
$ terraform agent -ui-dir /path/to/ui
|
||||
...
|
||||
```
|
||||
|
||||
The UI is available at the `/ui` path on the same port as the HTTP API.
|
||||
By default this is `http://localhost:8500/ui`.
|
|
@ -13,29 +13,9 @@
|
|||
<li<%= sidebar_current("vs-other") %>>
|
||||
<a href="/intro/vs/index.html">Terraform vs. Other Software</a>
|
||||
<ul class="nav">
|
||||
<li<%= sidebar_current("vs-other-zk") %>>
|
||||
<a href="/intro/vs/zookeeper.html">ZooKeeper, doozerd, etcd</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("vs-other-chef") %>>
|
||||
<a href="/intro/vs/chef-puppet.html">Chef, Puppet, etc.</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("vs-other-nagios-sensu") %>>
|
||||
<a href="/intro/vs/nagios-sensu.html">Nagios, Sensu</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("vs-other-skydns") %>>
|
||||
<a href="/intro/vs/skydns.html">SkyDNS</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("vs-other-smartstack") %>>
|
||||
<a href="/intro/vs/smartstack.html">SmartStack</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("vs-other-serf") %>>
|
||||
<a href="/intro/vs/serf.html">Serf</a>
|
||||
</li>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("vs-other-custom") %>>
|
||||
<a href="/intro/vs/custom.html">Custom Solutions</a>
|
||||
|
@ -50,34 +30,37 @@
|
|||
<a href="/intro/getting-started/install.html">Install Terraform</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("gettingstarted-agent") %>>
|
||||
<a href="/intro/getting-started/agent.html">Run the Agent</a>
|
||||
<li<%= sidebar_current("gettingstarted-build") %>>
|
||||
<a href="/intro/getting-started/build.html">Build Infrastructure</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("gettingstarted-services") %>>
|
||||
<a href="/intro/getting-started/services.html">Services</a>
|
||||
<li<%= sidebar_current("gettingstarted-change") %>>
|
||||
<a href="/intro/getting-started/change.html">Change Infrastructure</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("gettingstarted-join") %>>
|
||||
<a href="/intro/getting-started/join.html">Terraform Cluster</a>
|
||||
<li<%= sidebar_current("gettingstarted-destroy") %>>
|
||||
<a href="/intro/getting-started/destroy.html">Destroy Infrastructure</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("gettingstarted-checks") %>>
|
||||
<a href="/intro/getting-started/checks.html">Health Checks</a>
|
||||
<li<%= sidebar_current("gettingstarted-outputs") %>>
|
||||
<a href="/intro/getting-started/outputs.html">Output Variables</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("gettingstarted-kv") %>>
|
||||
<a href="/intro/getting-started/kv.html">Key/Value Data</a>
|
||||
<li<%= sidebar_current("gettingstarted-deps") %>>
|
||||
<a href="/intro/getting-started/dependencies.html">Resource Dependencies</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("gettingstarted-ui") %>>
|
||||
<a href="/intro/getting-started/ui.html">Web UI</a>
|
||||
<li<%= sidebar_current("gettingstarted-variables") %>>
|
||||
<a href="/intro/getting-started/variables.html">Input Variables</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("gettingstarted-variables") %>>
|
||||
<a href="/intro/getting-started/provisioners.html">Provision</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("gettingstarted-nextsteps") %>>
|
||||
<a href="/intro/getting-started/next-steps.html">Next Steps</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
Loading…
Reference in New Issue