README version 0.2.0
A highly-available key value store for shared configuration and service discovery. etcd is inspired by zookeeper and doozer, with a focus on:
- Simple: curl'able user facing API (HTTP+JSON)
- Secure: optional SSL client cert authentication
- Fast: benchmarked 1000s of writes/s per instance
- Reliable: Properly distributed using Raft
Etcd is written in Go and uses the raft consensus algorithm to manage a highly-available replicated log.
See etcdctl for a simple command line client. Or feel free to just use curl, as in the examples below.
The latest release is available as a binary at Github.
You can build etcd from source:
git clone https://github.com/coreos/etcd
cd etcd
./build
This will generate a binary in the base directory called ./etcd
.
NOTE: you need go 1.1+. Please check your installation with
go version
These examples will use a single node cluster to show you the basics of the etcd REST API. Lets start etcd:
./etcd -d node0 -n node0
This will bring up an etcd node listening on port 4001 for client communication and on port 7001 for server-to-server communication.
The -d node0
argument tells etcd to write node configuration, logs and snapshots to the ./node0/
directory.
The -n node0
tells the rest of the cluster that this node is named node0.
Let’s set the first key-value pair to the node. In this case the key is /message
and the value is Hello world
.
curl -L http://127.0.0.1:4001/v2/keys/message -XPUT -d value="Hello world"
{"action":"set","key":"/message","value":"Hello world","index":3,"term":0}
This response contains five fields. We will introduce three more fields as we try more commands.
-
The action of the request; we set the value via a PUT request, thus the action is
set
. -
The key of the request; we set
/message
toHello world!
, so the key field is/message
. Notice we use a file system like structure to represent the key-value pairs. So each key starts with/
. -
The current value of the key; we set the value to
Hello world
. -
Index is the unique internal log index of the set request. Requests that change the log index include
set
,delete
,update
,create
andcompareAndSwap
. Theget
andwatch
commands do not change state in the store and so they do not change the index. You may notice that in this example the index is 3, although it is the first request you sent to the server. This is because there are internal commands that also change the state like adding and syncing servers.
Get the value that we just set in /message
by issuing a GET:
curl -L http://127.0.0.1:4001/v2/keys/message
{"action":"get","key":"/message","value":"Hello world","index":3,"term":0}
Change the value of /message
from Hello world
to Hello etcd
with another PUT request to the key:
curl -L http://127.0.0.1:4001/v1/keys/message -XPUT -d value="Hello etcd"
{"action":"set","key":"/message","prevValue":"Hello world","value":"Hello etcd","index":4,"term":0}
Notice that the prevValue
is set to the previous value of the key - Hello world
. It is useful when you want to atomically set a value to a key and get its old value.
Remove the /message
key with a DELETE:
curl -L http://127.0.0.1:4001/v2/keys/message -XDELETE
{"action":"delete","key":"/message","prevValue":"Hello etcd","index":5,"term":0}
Keys in etcd can be set to expire after a specified number of seconds. That is done by setting a TTL (time to live) on the key when you POST:
curl -L http://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar -d ttl=5
{"action":"set","key":"/foo","value":"bar","expiration":"2013-10-19T18:44:04.528757176-07:00","ttl":5,"index":6,"term":0}
Note the last two new fields in response:
-
The expiration is the time that this key will expire and be deleted.
-
The ttl is the time to live of the key.
Now you can try to get the key by sending:
curl -L http://127.0.0.1:4001/v2/keys/foo
If the TTL has expired, the key will be deleted, and you will be returned a 100.
{"errorCode":100,"message":"Key Not Found","cause":"/foo","index":6,"term":0}
We can watch for a change and get a notification at a given path or any keys underneath it.
In one terminal, we send a get request with wait=true
:
curl -L http://127.0.0.1:4001/v2/keys/foo?wait=true
Now, we are waiting for any changes at path /foo
.
In another terminal, we set a key /foo
with value bar
:
curl -L http://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar
The first terminal should get the notification and return with the same response as the set request.
{"action":"set","key":"/foo","value":"bar","index":7,"term":0}
However, the watch command can do more than this. Using the the index we can watch for commands that has happened in the past. This is useful for ensuring you don't miss events between watch commands.
Let's try to watch for the set command of index 7 again:
curl -L http://127.0.0.1:4001/v2/keys/foo?wait=true\&waitIndex=7
The watch command returns immediately with the same response as previous.
Etcd can be used as a centralized coordination service in a cluster and CompareAndSwap
is the most basic operation to build distributed lock service.
This command will set the value to the key only if the client provided conditions are equal to the current conditions.
The current comparable conditions are:
-
prevValue
previous value of the key: -
prevIndex
previous index of the key -
prevExist
previous existence of the key: ifprevExist
is true, it is aupdate
request; if prevExist isfalse
, it is acreate
request.
Here is a simple example. Let's create a key-value pair first: foo=one
.
curl -L http://127.0.0.1:4001/v1/keys/foo -XPUT -d value=one
Let's try an invalid CompareAndSwap
command.
We can give another parameter prevValue to set command to make it a CompareAndSwap
command.
curl -L http://127.0.0.1:4001/v1/keys/foo?prevValue=two -XPUT -d value=three
This will try to compare the previous value of the key and the previous value we provided. If they are equal, the value of the key will change to three.
{"errorCode":101,"message":"Test Failed","cause":"[two != one] [0 != 8]","index":9,"term":0}
which means compareAndSwap
failed.
Let us try a valid one.
curl -L http://127.0.0.1:4001/v2/keys/foo?prevValue=one -XPUT -d value=two
The response should be
{"action":"compareAndSwap","key":"/foo","prevValue":"one","value":"two","index":10,"term":0}
We successfully changed the value from “one” to “two”, since we give the correct previous value.
Let us create some keys first.
We already have /foo=two
We create another one /foo_dir/foo=bar
curl -L http://127.0.0.1:4001/v2/keys/foo_dir/foo -XPUT -d value=bar
{"action":"set","key":"/foo_dir/foo","value":"bar","index":11,"term":0}
Now list the keys under root /
curl -L http://127.0.0.1:4001/v2/keys/
We should see the response as an array of items
{"action":"get","key":"/","dir":true,"kvs":[{"key":"/foo","value":"two"},{"key":"/foo_dir","dir":true}],"index":11,"term":0}
which meas /foo=two
is a key-value pair under / and
/foo_dir` is a directory.
Also we can recursively get all the content under a directory by add recursive=true
.
curl -L http://127.0.0.1:4001/v2/keys/?recursive=true
{"action":"get","key":"/","dir":true,"kvs":[{"key":"/foo","value":"two"},{"key":"/foo_dir","dir":true,"kvs":[{"key":"/foo_dir/foo","value":"bar"}]}],"index":11,"term":0}
Let try to delete the directory /foo_dir
.
To delete a directory, we must add recursive=true
.
curl -L http://127.0.0.1:4001/v2/keys/foo_dir?recursive=true -XDELETE
{"action":"delete","key":"/foo_dir","dir":true,"index":12,"term":0}
Creating a hidden node
We can create a hidden key-value pair or directory by add _
prefix. The hidden item will not be list when using get for a directory.
curl -L http://127.0.0.1:4001/v2/keys/_message -XPUT -d value="Hello hidden world"
{"action":"set","key":"/_message","value":"Hello hidden world","index":13,"term":0}
curl -L http://127.0.0.1:4001/v2/keys/message -XPUT -d value="Hello world"
{"action":"set","key":"/message","value":"Hello world","index":14,"term":0}
Let us try to get the root /
curl -L http://127.0.0.1:4001/v2/keys/
{"action":"get","key":"/","dir":true,"kvs":[{"key":"/foo","value":"two"},{"key":"/message","value":"Hello world"}],"index":15,"term":0}
We can only get /message
, but cannot get /_message
.
Etcd supports SSL/TLS and client cert authentication for clients to server, as well as server to server communication
First, you need to have a CA cert clientCA.crt
and signed key pair client.crt
, client.key
. This site has a good reference for how to generate self-signed key pairs:
http://www.g-loaded.eu/2005/11/10/be-your-own-ca/
For testing you can use the certificates in the fixtures/ca
directory.
Next, lets configure etcd to use this keypair:
./etcd -n node0 -d node0 -clientCert=./fixtures/ca/server.crt -clientKey=./fixtures/ca/server.key.insecure -f
-f
forces new node configuration if existing configuration is found (WARNING: data loss!)
-clientCert
and -clientKey
are the key and cert for transport layer security between client and server
You can now test the configuration using https:
curl --cacert fixtures/ca/ca.crt https://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar -v
You should be able to see the handshake succeed.
...
SSLv3, TLS handshake, Finished (20):
...
And also the response from the etcd server.
{"action":"set","key":"/foo","value":"bar","index":3, "term: 0"}
We can also do authentication using CA certs. The clients will provide their cert to the server and the server will check whether the cert is signed by the CA and decide whether to serve the request.
./etcd -n node0 -d node0 -clientCAFile=./fixtures/ca/ca.crt -clientCert=./fixtures/ca/server.crt -clientKey=./fixtures/ca/server.key.insecure -f
-clientCAFile
is the path to the CA cert.
Try the same request to this server:
curl --cacert fixtures/ca/ca.crt https://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar -v
The request should be rejected by the server.
...
routines:SSL3_READ_BYTES:sslv3 alert bad certificate
...
We need to give the CA signed cert to the server.
curl -L https://127.0.0.1:4001/v1/keys/foo -XPUT -d value=bar -v --key myclient.key --cert myclient.crt -cacert clientCA.crt
You should able to see
...
SSLv3, TLS handshake, CERT verify (15):
...
TLS handshake, Finished (20)
And also the response from the server:
{"action":"set","key":"/foo","value":"bar","index":3,"term:0"}
Let's explore the use of etcd clustering. We use go-raft as the underlying distributed protocol which provides consistency and persistence of the data across all of the etcd instances.
Let start by creating 3 new etcd instances.
We use -s to specify server port and -c to specify client port and -d to specify the directory to store the log and info of the node in the cluster
./etcd -s 127.0.0.1:7001 -c 127.0.0.1:4001 -d nodes/node1 -n node1
Note: If you want to run etcd on external IP address and still have access locally you need to add -cl 0.0.0.0
so that it will listen on both external and localhost addresses.
A similar argument -sl
is used to setup the listening address for the server port.
Let the join two more nodes to this cluster using the -C argument:
./etcd -c 127.0.0.1:4002 -s 127.0.0.1:7002 -C 127.0.0.1:7001 -d nodes/node2 -n node2
./etcd -c 127.0.0.1:4003 -s 127.0.0.1:7003 -C 127.0.0.1:7001 -d nodes/node3 -n node3
Get the machines in the cluster:
curl -L http://127.0.0.1:4001/v1/machines
We should see there are three nodes in the cluster
http://127.0.0.1:4001, http://127.0.0.1:4002, http://127.0.0.1:4003
The machine list is also available via this API:
curl -L http://127.0.0.1:4001/v1/keys/_etcd/machines
[{"action":"get","key":"/_etcd/machines/node1","value":"raft=http://127.0.0.1:7001&etcd=http://127.0.0.1:4001&raftVersion=v0.1.1-311-g91cad59","index":4},{"action":"get","key":"/_etcd/machines/node2","value":"raft=http://127.0.0.1:7002&etcd=http://127.0.0.1:4002&raftVersion=v0.1.1-311-g91cad59","index":4},{"action":"get","key":"/_etcd/machines/node3","value":"raft=http://127.0.0.1:7003&etcd=http://127.0.0.1:4003&raftVersion=v0.1.1-311-g91cad59","index":4}]
The key of the machine is based on the commit index
when it was added. The value of the machine is hostname
, raft port
and client port
.
Also try to get the current leader in the cluster
curl -L http://127.0.0.1:4001/v2/leader
The first server we set up should be the leader, if it has not died during these commands.
http://127.0.0.1:7001
Now we can do normal SET and GET operations on keys as we explored earlier.
curl -L http://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar
{"action":"set","key":"/foo","value":"bar","index":5,"term:0"}
Let's kill the leader of the cluster and get the value from the other machine:
curl -L http://127.0.0.1:4002/v1/keys/foo
A new leader should have been elected.
curl -L http://127.0.0.1:4001/v1/leader
http://127.0.0.1:7002
or
http://127.0.0.1:7003
You should be able to see this:
{"action":"get","key":"/foo","value":"bar","index":5,"term:1"}
It succeeded!
OK. Next let us kill all the nodes to test persistence. And restart all the nodes use the same command as before.
Your request for the foo
key will return the correct value:
curl -L http://127.0.0.1:4002/v1/keys/foo
{"action":"GET","key":"/foo","value":"bar","index":5}
In the previous example we showed how to use SSL client certs for client to server communication. Etcd can also do internal server to server communication using SSL client certs. To do this just change the -client*
flags to -server*
.
If you are using SSL for server to server communication, you must use it on all instances of etcd.
See CONTRIBUTING for details on submitting patches and contacting developers via IRC and mailing lists.
Tools
- etcdctl - A command line client for etcd
Go libraries
Java libraries
Python libraries
- transitorykris/etcd-py
- jplana/python-etcd
- russellhaering/txetcd - a Twisted Python library
Node libraries
Ruby libraries
C libraries
Chef Integration
Chef Cookbook
Projects using etcd
- binocarlos/yoda - etcd + ZeroMQ
- calavera/active-proxy - HTTP Proxy configured with etcd
- derekchiang/etcdplus - A set of distributed synchronization primitives built upon etcd
- gleicon/goreman - Branch of the Go Foreman clone with etcd support
- garethr/hiera-etcd - Puppet hiera backend using etcd
- mattn/etcd-vim - SET and GET keys from inside vim
- mattn/etcdenv - "env" shebang with etcd integration
Every command the client sends to the master is broadcast to all of the followers. But, the command is not committed until the majority of the cluster machines receive that command.
Because of this majority voting property the ideal cluster should be kept small to keep speed up and be made up of an odd number of machines.
Odd numbers are good because if you have 8 machines the majority will be 5 and if you have 9 machines the majority with be 5. The result is that an 8 machine cluster can tolerate 3 machine failures and a 9 machine cluster can tolerate 4 nodes failures. And in the best case when all 9 machines are responding the cluster will perform at the speed of the fastest 5 nodes.
etcd uses semantic versioning. When we release v1.0.0 of etcd we will promise not to break the "v1" REST API. New minor versions may add additional features to the API however.
You can get the version of etcd by issuing a request to /version:
curl -L http://127.0.0.1:4001/version
During the v0 series of releases we may break the API as we fix bugs and get feedback.
etcd is under the Apache 2.0 license. See the LICENSE file for details.