kelseyhightower / confd Goto Github PK
View Code? Open in Web Editor NEWManage local application configuration files using templates and data from etcd or consul
License: MIT License
Manage local application configuration files using templates and data from etcd or consul
License: MIT License
I have a situation where confd finds a different config file from what it expects:
2014-03-24T12:33:51Z ip-172-16-1-185 confd[8382]: NOTICE Starting confd
2014-03-24T12:33:51Z ip-172-16-1-185 confd[8382]: NOTICE etcd nodes set to http://127.0.0.1:5001
2014-03-24T12:33:51Z ip-172-16-1-185 confd[8382]: INFO /etc/nginx/sites-enabled/site.conf has md5sum f70137102cd936cecd5890f2b330da43 should be a434ed9390072b9bc9d0c2e8be60fa47
2014-03-24T12:33:51Z ip-172-16-1-185 confd[8382]: INFO Target config /etc/nginx/sites-enabled/site.conf out of sync
2014-03-24T12:33:51Z ip-172-16-1-185 confd[8382]: ERROR Config check failed: exit status 1
However there seems to be no way for me to tell it to force overwrite the config file, and I'm forced to remove the old file before it will write a new one.
To make following the examples easier, provide a short tutorial on setting up an etcd cluster and populating the initial set of keys.
If the prefix does not contain a trailing "/" we end up with a leading "_" when converting values from etcd paths.
If the configured prefix is "/env" and the key is "/nginx/port" we end up with _nginx_port
instead of nginx_port
.
It would be very helpful to show the output from reload_cmd and check_cmd in standard out. I've looked through the documentation and can't find an option.
I'm using confd with CoreOS, so sending all output to standard out logs it into journald. I can wrap the reload command and send output somewhere else, but sending it to standard out would simplify my setup. Thanks!
$ confd -debug -verbose -onetime -node 192.168.61.100:4001 -config-file /app/confd.toml
2014-01-16T20:39:22Z 436e246d8953 confd[17]: ERROR exit status 2
..running it a few more times eventually result in success.
An strace
suggests an EINPROGRESS
error on the connect() to etcd peers. It seems like the socket FD (3) is being closed and reused a few times. I suspect some kind of race condition. I can dig in more, but I wanted to report it now.
+ strace confd -debug -verbose -onetime -node 192.168.61.100:4001 -config-file /app/confd.toml
execve("/usr/local/bin/confd", ["confd", "-debug", "-verbose", "-onetime", "-node", "192.168.61.100:4001", "-config-file", "/app/confd.toml"], [/* 11 vars */]) = 0
arch_prctl(ARCH_SET_FS, 0x9fab10) = 0
sched_getaffinity(0, 128, {3, 0, 0, 0}) = 32
mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f539db8b000
mmap(0xc000000000, 65536, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xc000000000
munmap(0xc000000000, 65536) = 0
mmap(0xc210000000, 1048576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xc210000000
mmap(0xc20fff0000, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xc20fff0000
mmap(0xc000000000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xc000000000
mmap(NULL, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f539db7b000
mmap(NULL, 1439992, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f539da1b000
mmap(NULL, 131072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f539d9fb000
sigaltstack({ss_sp=0xc210002000, ss_flags=0, ss_size=32768}, NULL) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
rt_sigaction(SIGHUP, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGHUP, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGINT, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGINT, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGQUIT, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGILL, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGTRAP, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGABRT, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGBUS, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGFPE, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGUSR1, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGSEGV, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGUSR2, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGPIPE, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGALRM, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGTERM, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGSTKFLT, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGCHLD, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGURG, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGXCPU, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGXFSZ, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGVTALRM, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGPROF, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGWINCH, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGIO, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGPWR, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGSYS, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRTMIN, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_2, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_3, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_4, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_5, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_6, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_7, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_8, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_9, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_10, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_11, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_12, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_13, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_14, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_15, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_16, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_17, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_18, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_19, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_20, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_21, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_22, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_23, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_24, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_25, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_26, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_27, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_28, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_29, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_30, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_31, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigaction(SIGRT_32, {0x4255c0, ~[], SA_RESTORER|SA_STACK|SA_RESTART|SA_SIGINFO, 0x425630}, NULL, 8) = 0
rt_sigprocmask(SIG_SETMASK, ~[], [], 8) = 0
clone(child_stack=0x7f539da1afa0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD) = 25
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
mmap(NULL, 4080, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f539d9fa000
mmap(NULL, 1048576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f539d8fa000
futex(0x9facf8, FUTEX_WAKE, 1) = 1
rt_sigprocmask(SIG_SETMASK, ~[], [], 8) = 0
clone(child_stack=0x7f539da16fa0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD) = 26
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
futex(0xa014e8, FUTEX_WAIT, 0, NULL) = 0
rt_sigprocmask(SIG_SETMASK, ~[], [], 8) = 0
clone(child_stack=0x7f539da14fa0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD) = 28
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
futex(0xa014e8, FUTEX_WAIT, 0, NULL) = 0
open("/proc/sys/net/core/somaxconn", O_RDONLY|O_CLOEXEC) = 3
futex(0xa014e8, FUTEX_WAIT, 0, NULL) = 0
read(3, "128\n", 4096) = 4
read(3, "", 4092) = 0
close(3) = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
close(3) = 0
socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP) = 3
setsockopt(3, SOL_IPV6, IPV6_V6ONLY, [0], 4) = 0
bind(3, {sa_family=AF_INET6, sin6_port=htons(0), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = 0
socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP) = 4
setsockopt(4, SOL_IPV6, IPV6_V6ONLY, [0], 4) = 0
futex(0x9facf8, FUTEX_WAKE, 1) = 1
bind(4, {sa_family=AF_INET6, sin6_port=htons(0), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = 0
close(4) = 0
close(3) = 0
socket(PF_FILE, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
setsockopt(3, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
epoll_create1(O_CLOEXEC) = 4
epoll_ctl(4, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLOUT|EPOLLET|0x2000, {u32=2646200624, u64=139997105213744}}) = 0
connect(3, {sa_family=AF_FILE, path="/run/systemd/journal/socket"}, 30) = -1 ENOENT (No such file or directory)
epoll_ctl(4, EPOLL_CTL_DEL, 3, {0, {u32=4236712, u64=4236712}}) = 0
close(3) = 0
readlink("/proc/self/exe", "/usr/local/bin/confd", 128) = 20
readlink("/proc/self/exe", "/usr/local/bin/confd", 128) = 20
open("/app/confd.toml", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=154, ...}) = 0
read(3, "[confd]\nconfdir = \"/app\"\ninterv"..., 666) = 154
read(3, "", 512) = 0
close(3) = 0
open("/etc/localtime", O_RDONLY) = 3
read(3, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\1\0\0\0\0"..., 4096) = 118
read(3, "", 4096) = 0
close(3) = 0
open("/proc/sys/kernel/hostname", O_RDONLY|O_CLOEXEC) = 3
read(3, "c0cd58e62acc\n", 512) = 13
close(3) = 0
open("/proc/sys/kernel/hostname", O_RDONLY|O_CLOEXEC) = 3
read(3, "c0cd58e62acc\n", 512) = 13
close(3) = 0
socket(PF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 3
setsockopt(3, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
epoll_ctl(4, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLOUT|EPOLLET|0x2000, {u32=2646200624, u64=139997105213744}}) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(4001), sin_addr=inet_addr("192.168.61.100")}, 16) = -1 EINPROGRESS (Operation now in progress)
futex(0xc21002c8e8, FUTEX_WAKE, 1) = 1
futex(0x9faa58, FUTEX_WAIT, 0, {0, 998960735} <unfinished ... exit status 0>
It seems that every time confd checks into etcd, a new TCP connection is established, and old ones are never torn down.
Eventually, ulimit is hit:
2013/10/28 08:44:04 http: Accept error: accept tcp 127.0.0.1:4001: too many open files; retrying in 1s
2013/10/28 08:44:05 http: Accept error: accept tcp 127.0.0.1:4001: too many open files; retrying in 1s
@kelseyhightower do you have any ambitions to implement the etcd support to use etcd watching rather than polling?
Have you considered namespacing watched keys in template resources similar to the global prefix
option?
Something like an nginx site you may want to use a common template but have a discrete template resource watching separate keys. e.g.
example.com.toml
[template]
src = "example.conf.tmpl"
dest = "/etc/nginx/sites-enabled/example.com.conf"
owner = "root"
group = "root"
mode = "0644"
prefix = "site"
keys = [
"/nginx/examplecom"
]
check_cmd = "/usr/sbin/nginx -t -c {{ .src }}"
reload_cmd = "/usr/sbin/service nginx restart"
example.net.toml
[template]
src = "example.conf.tmpl"
dest = "/etc/nginx/sites-enabled/example.net.conf"
owner = "root"
group = "root"
mode = "0644"
prefix = "site"
keys = [
"/nginx/examplenet"
]
check_cmd = "/usr/sbin/nginx -t -c {{ .src }}"
reload_cmd = "/usr/sbin/service nginx restart"
example.conf.tmpl
server {
listen 80;
server_name www.{{ .site_domain }};
access_log /var/log/nginx/{{ .site_domain }}.access.log;
error_log /var/log/nginx/{{ .site_domain }}.log;
location / {
root {{ .site_root }};
index index.html index.htm;
}
}
$ etcdctl set /nginx/examplecom/domain "example.com"
$ etcdctl set /nginx/examplenet/domain "example.net"
$ etcdctl set /nginx/examplecom/root "/var/www/example.com"
$ etcdctl set /nginx/examplenet/root "/var/www/example.net"
example.com.conf
server {
listen 80;
server_name www.example.com
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.log;
location / {
root /var/www/example.com;
index index.html index.htm;
}
}
example.net.conf
server {
listen 80;
server_name www.example.net
access_log /var/log/nginx/example.net.access.log;
error_log /var/log/nginx/example.net.log;
location / {
root /var/www/example.net;
index index.html index.htm;
}
}
Theoretically this would allow for DRY templates.
I see this as being potential problematic if you wanted to have everything pointed to say /var/www
and use a common etcd
key for both sites, but different template (for say logging purposes).
Perhaps the prefix could be set per watch key/dir like so:
foobar.toml
[template]
src = "foobar.conf.tmpl"
dest = "/etc/nginx/sites-enabled/foobar.conf"
owner = "root"
group = "root"
mode = "0644"
keys = [
["/nginx/foobar","site"],
"/nginx/root"
]
check_cmd = "/usr/sbin/nginx -t -c {{ .src }}"
reload_cmd = "/usr/sbin/service nginx restart"
example.conf.tmpl
server {
listen 80;
server_name www.{{ .site_domain }};
access_log /var/log/nginx/{{ .site_domain }}.access.log;
error_log /var/log/nginx/{{ .site_domain }}.log;
location / {
root {{ .nginx_root }};
index index.html index.htm;
}
}
$ etcdctl set /nginx/foobar/domain "google.com"
$ etcdctl set /nginx/root "/var/www/"
foobar.conf
server {
listen 80;
server_name www.google.com
access_log /var/log/nginx/google.com.access.log;
error_log /var/log/nginx/google.com.log;
location / {
root /var/www/;
index index.html index.htm;
}
}
If I have a nginx template like so:
# required to run in a container
daemon off;
user www-data;
worker_processes 4;
pid /run/nginx.pid;
events {
worker_connections 768;
# multi_accept on;
}
http {
# basic settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_names_hash_bucket_size 64;
gzip on;
gzip_disable "msie6";
include /etc/nginx/mime.types;
default_type application/octet-stream;
# send logs to STDOUT so they can be seen using 'docker logs'
access_log /dev/stdout;
error_log /dev/stdout;
server {
listen 80 default_server;
server_name _; # will never match
server_name_in_redirect off;
}
# service definitions for each application
{{ range $service := .deis_services }}{{ if $service.Nodes }}
upstream {{ Base $service.Key }} {
{{ range $upstream := $service.Nodes }}server {{ $upstream.Value }};
{{ end }}
}
server {
server_name ~^{{ Base $service.Key }}\.(?<domain>.+)${{ range $domain := .deis_domains }}{{ $domain.Value }}{{ end }};
server_name_in_redirect off;
port_in_redirect off;
location / {
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
proxy_connect_timeout 10;
proxy_send_timeout 30;
proxy_read_timeout 30;
proxy_pass http://{{ Base $service.Key }};
}
}
{{ end }}{{ end }}
}
When I try to run confd, it fails with the following:
# confd -onetime -node 172.17.8.100:4001 -config-file /app/confd.toml
2014-05-12T22:34:28Z 907f2188b7f5 confd[19514]: ERROR template: nginx.conf:47:81: executing "nginx.conf" at <.deis_domains>: deis_domains is not a field of struct type etcd.Node
However, moving that logic out of the loop and putting it separately and works nicely (notice facebook.com
at the very bottom):
$ cat /etc/nginx/nginx.conf
[...]
server {
server_name ~^proper-newsreel\.(?<domain>.+)$;
server_name_in_redirect off;
port_in_redirect off;
location / {
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
proxy_connect_timeout 10;
proxy_send_timeout 30;
proxy_read_timeout 30;
proxy_pass http://proper-newsreel;
}
}
}
facebook.com
I'd be happy to contribute a fix. Any idea where I'd have to start looking?
While local logging is great confd should support a basic webhook to post events when config files changes take place.
Before release confd logging must be documented.
With both 0.3.0-beta1
and 0.3.0
, I get the issue described below. What am I doing wrong?
This works
etcd_nodes = [
"http://<node-1>:4001"
]
This, on the other hand...
etcd_nodes = [
"http://<node-1>:4001",
"http://<node-2>:4001",
"http://<node-3>:4001"
]
...produces this:
root@f9ce598c2cf0:~# confd -onetime
panic: reflect: slice index out of range
goroutine 1 [running]:
runtime.panic(0x616a20, 0xc210036a90)
/usr/local/go/src/pkg/runtime/panic.c:266 +0xb6
reflect.Value.Index(0x608a40, 0x9e1d88, 0x176, 0x1, 0xc2100368b0, ...)
/usr/local/go/src/pkg/reflect/value.go:916 +0x223
github.com/BurntSushi/toml.unifySlice(0x6081c0, 0xc21000a6a0, 0x608a40, 0x9e1d88, 0x176, ...)
/Users/kelseyhightower/go/src/github.com/BurntSushi/toml/decode.go:215 +0x26f
github.com/BurntSushi/toml.unify(0x6081c0, 0xc21000a6a0, 0x608a40, 0x9e1d88, 0x176, ...)
/Users/kelseyhightower/go/src/github.com/BurntSushi/toml/decode.go:129 +0x7c1
github.com/BurntSushi/toml.unifyStruct(0x615160, 0xc21001dc90, 0x6c02c0, 0x9e1d40, 0x196, ...)
/Users/kelseyhightower/go/src/github.com/BurntSushi/toml/decode.go:169 +0x2ed
github.com/BurntSushi/toml.unify(0x615160, 0xc21001dc90, 0x6c02c0, 0x9e1d40, 0x196, ...)
/Users/kelseyhightower/go/src/github.com/BurntSushi/toml/decode.go:125 +0x84f
github.com/BurntSushi/toml.unifyStruct(0x615160, 0xc21001dc00, 0x680e00, 0x9e1d40, 0x196, ...)
/Users/kelseyhightower/go/src/github.com/BurntSushi/toml/decode.go:169 +0x2ed
github.com/BurntSushi/toml.unify(0x615160, 0xc21001dc00, 0x680e00, 0x9e1d40, 0x196, ...)
/Users/kelseyhightower/go/src/github.com/BurntSushi/toml/decode.go:125 +0x84f
github.com/BurntSushi/toml.Decode(0xc210060000, 0xa7, 0x601480, 0x9e1d40, 0xa7, ...)
/Users/kelseyhightower/go/src/github.com/BurntSushi/toml/decode.go:69 +0x107
github.com/BurntSushi/toml.DecodeFile(0x702690, 0x15, 0x601480, 0x9e1d40, 0x15, ...)
/Users/kelseyhightower/go/src/github.com/BurntSushi/toml/decode.go:79 +0x100
github.com/kelseyhightower/confd/config.LoadConfig(0x702690, 0x15, 0x1, 0x9ea7f3)
/Users/kelseyhightower/go/src/github.com/kelseyhightower/confd/config/config.go:83 +0x344
main.main()
/Users/kelseyhightower/go/src/github.com/kelseyhightower/confd/confd.go:41 +0xae
confd should cache etcd data between runs and utilize the cache in cases where the etcd cluster is unreachable.
It's really hard to troubleshoot connectivity issues without this.
I'm not able to get template iteration to work via consul, although etcd is working fine.
test.conf.tmpl:
{{range $i := .servers_api}}
{{$i.Value}}
{{end}}
{{ .servers_api }}
test.toml
[template]
src = "test.conf.tmpl"
dest = "/tmp/test.conf"
keys = [
"servers/api",
]
etcd setup:
curl http://127.0.0.1:4001/v2/keys/servers/api -XPUT -d dir=true
curl http://127.0.0.1:4001/v2/keys/servers/api/app2 -XPUT -d value="10.0.1.101:80"
curl http://127.0.0.1:4001/v2/keys/servers/api/app1 -XPUT -d value="10.0.1.100:80"
confd -verbose -onetime -node 'http://127.0.0.1:4001' -confdir="/etc/confd"
consul setup:
curl -X PUT -d '10.0.1.101:80' http://localhost:8500/v1/kv/servers/api/app1
curl -X PUT -d '10.0.1.100:80' http://localhost:8500/v1/kv/servers/api/app2
confd -verbose -onetime -consul=true -consul-addr="127.0.0.1:8500" -confdir="/etc/confd"
etcd 'test.conf' output:
10.0.1.101:80
10.0.1.100:80
[0xc21000a900 0xc21000a960]
consul 'test.conf' output:
<no value>
e.g. : I have etcd keys like so:
└── webservers
├── web-001.mycompany.tld
│ ├── listen_ip
│ └── listen_port
└── web-002.mycompany.tld
├── listen_ip
└── listen_port
Can I use confd to write out a config from a dynamic list of etcd keys? The examples seem to assume that you know the path of all etcd keys you'll be referencing in a given template.
My use case for this is etcd + haproxy, with a dynamic set of backend servers for multiple haproxy "backends".
From the confd docs, and what I understood from text/template, I don't understand how I can accomplish this with confd.
If this isn't something you think confd should support, or my keystructure is silly, that's fine too!
Thanks!
INI configuration files are proving to limited, we should really switch to TOML so thing like configuring a list of etcd servers is much cleaner.
We can go from:
[etcd]
machines = node1,node2
To:
[etcd]
machines = ["node1", "node2"]
I am trying to run the pre-built confd from the 0.3.0 release tar.gz archive, and I keep getting the following error:
$ confd -debug -onetime
2014-04-08T13:41:01-04:00 twemproxy01 confd[3627]: NOTICE Starting confd
2014-04-08T13:41:01-04:00 twemproxy01 confd[3627]: NOTICE etcd nodes set to http://127.0.0.1:4001
2014-04-08T13:41:01-04:00 twemproxy01 confd[3627]: DEBUG Loading template resources from confdir /etc/confd
2014-04-08T13:41:01-04:00 twemproxy01 confd[3627]: DEBUG Processing template resource /etc/confd/conf.d/example.toml
2014-04-08T13:41:01-04:00 twemproxy01 confd[3627]: DEBUG Loading template resource from /etc/confd/conf.d/example.toml
2014-04-08T13:41:01-04:00 twemproxy01 confd[3627]: DEBUG Retrieving keys from etcd
2014-04-08T13:41:01-04:00 twemproxy01 confd[3627]: DEBUG Key prefix set to /
2014-04-08T13:41:01-04:00 twemproxy01 confd[3627]: DEBUG Using source template /etc/confd/templates
2014-04-08T13:41:01-04:00 twemproxy01 confd[3627]: DEBUG Compiling source template /etc/confd/templates
panic: read /etc/confd/templates: is a directory
goroutine 1 [running]:
runtime.panic(0x6751e0, 0xc210074870)
/usr/local/go/src/pkg/runtime/panic.c:266 +0xb6
text/template.Must(0x0, 0x7f363facfab8, 0xc210074870, 0x1)
/usr/local/go/src/pkg/text/template/helper.go:23 +0x4f
github.com/kelseyhightower/confd/resource/template.(*TemplateResource).createStageFile(0xc21000f820, 0x0, 0x0)
/Users/kelseyhightower/go/src/github.com/kelseyhightower/confd/resource/template/resource.go:95 +0x4dd
github.com/kelseyhightower/confd/resource/template.(*TemplateResource).process(0xc21000f820, 0x1e, 0x7f363facfa90)
/Users/kelseyhightower/go/src/github.com/kelseyhightower/confd/resource/template/resource.go:195 +0x81
github.com/kelseyhightower/confd/resource/template.ProcessTemplateResources(0x7f363facfa90, 0xc210061480, 0x9e07a8, 0x7f363facfa90, 0x0)
/Users/kelseyhightower/go/src/github.com/kelseyhightower/confd/resource/template/resource.go:254 +0x7ad
main.main()
/Users/kelseyhightower/go/src/github.com/kelseyhightower/confd/confd.go:58 +0x295
goroutine 5 [runnable]:
net/http.(*persistConn).readLoop(0xc210061600)
/usr/local/go/src/pkg/net/http/transport.go:778 +0x68f
created by net/http.(*Transport).dialConn
/usr/local/go/src/pkg/net/http/transport.go:528 +0x607
goroutine 6 [select]:
net/http.(*persistConn).writeLoop(0xc210061600)
/usr/local/go/src/pkg/net/http/transport.go:791 +0x271
created by net/http.(*Transport).dialConn
/usr/local/go/src/pkg/net/http/transport.go:529 +0x61e
My /etc/confd/confd.toml
file reads as follows:
[confd]
confdir = "/etc/confd"
interval = 60
prefix = "/"
verbose = true
debug = false
quiet = false
etcd_nodes = ["http://127.0.0.1:4001"]
etcd_scheme = "http"
srv_domain = ""
client_cert = ""
client_key = ""
noop = false
Why is confd attempting to read /etc/confd/templates
as though it is a template file?
We are past the prototype stage and need unit tests!
Bonjour,
I would like to control a config file that has a simple INI format (it's carbons storage-schemas.conf).
It is going to look like this:
[carbon]
pattern = ^carbon\.
retentions = 1s:1d
[diamond]
pattern = ^servers\.
retentions = 5s:1d
To start simple I though about storing it like...
carbon/storageschemas/statsd -d value="pattern = '^stats\.'\nretentions = 5s:1d"
carbon/storageschemas/default -d value="pattern = .*\nretentions = 60s:1d"
My template:
{{range $entry := .carbon_storageschemas}}
[{{ $entry.Key }}]
{{ $entry.Value }}
{{end}}
This results in...
$ cat /tmp/storage-schemas.conf
[/carbon/storageschemas/statsd]
pattern = '^stats\.'\nretentions = 5s:1d
[/carbon/storageschemas/default]
pattern = .*\nretentions = 60s:1d
So I am not that far away, spliting the key with ''.split("/")[-1]'' and I got the last part of the key.
But this ruby-magic has to be inserted somehow I could not get to wrap my head around (so far).
Now I have to split the value twice, maybe better:
carbon/storageschemas/statsd/pattern -d value="'^stats\.'"
carbon/storageschemas/statsd/retentions -d value="5s:1d"
Could someone help me out, plz... :) I guess INI files are not something strange to handle.
Thx
Christian
@kelseyhightower I put together a quick 'n dirty Chef cookbook while checking this project out.
I've pushed it up to GitHub incase it's useful for anyone: https://github.com/rjocoleman/confd-cookbook
I'm using v0.1.1beta3
.
It seems like templates resources are parsed in alphabetical order.
If a watched key is not present an error is thrown and processing is stopped. Subsequent template resources are not processed.
e.g.
a.toml
[template]
src = "a.conf.tmpl"
dest = "/tmp/test/a.conf"
owner = "root"
group = "root"
mode = "0644"
keys = [
"/a",
]
b.toml
[template]
src = "b.conf.tmpl"
dest = "/temp/test/b.conf"
owner = "root"
group = "root"
mode = "0644"
keys = [
"/b",
]
$ etcdctl set /b 'bar'
$ confd -i 5
2013-10-21T05:13:21Z vagrant confd[7153]: ERROR 100: Key Not Found (get: /a)
2013-10-21T05:13:26Z vagrant confd[7153]: ERROR 100: Key Not Found (get: /a)
$ ls -l /tmp/test
total 0
You can do some directory things with etcd
key/values e.g.
$ curl -L http://127.0.0.1:4001/v1/keys/foo
[{"action":"GET","key":"/foo/bar","value":"hello","index":8},{"action":"GET","key":"/foo/maa","value":"world","index":8},{"action":"GET","key":"/foo/pop","dir":true,"index":8}]
$ curl -L http://127.0.0.1:4001/v1/keys/foo/pop
[{"action":"GET","key":"/foo/pop/tar","value":"dfa","index":8}]
In this case I have /foo
watched for my template and confd
doesn't like this. In the template {{.}}
yields:
map[foo_bar:hello foo_maa:world foo_pop:]
Before release there MUST be a wiki entry on how to use SRV records to configure etcd nodes.
confd should have a status endpoint that allows external tools and clients to gather stats and check the health of the confd daemon.
Before release there MUST be a wiki entry on how to use noop mode.
Support DNS SRV records when searching for etcd cluster nodes.
It's pretty easy to add a dryrun or noop mode to preview changes. I'm proposing a new flag, -noop
, to enable this behavior.
The consul client returns the keys without the first /
(e.g: map[string]string{"prefix/key":value}
)
However, cleanKeys expects keys to have the first /
in order to trim the prefix.
This can easily be tested:
--- a/resource/template/resource_test.go
+++ b/resource/template/resource_test.go
@@ -433,6 +433,7 @@ func TestCleanKeys(t *testing.T) {
"/this_key": "foo",
"/prefix/key": "test",
"/my/new-cool-val/here": "bar",
+ "prefix/second_key": "ok",
}
config.SetPrefix("/prefix")
clean := cleanKeys(pre, "/prefix")
@@ -451,4 +452,7 @@ func TestCleanKeys(t *testing.T) {
if _, ok := clean["my_new_cool_val_here"]; !ok {
t.Fatalf("bad: %v", clean)
}
+ if _, ok := clean["second_key"]; !ok {
+ t.Fatalf("bad: %v", clean)
+ }
}
Quick and dirty solution would be to strip first /
from key and prefix before trimming prefix:
--- a/resource/template/resource.go
+++ b/resource/template/resource.go
@@ -108,6 +108,8 @@ func cleanKeys(vars map[string]interface{}, prefix string) map[string]interface{
// pathToKey translates etcd key paths into something more suitable for use
// in Golang templates. Turn /prefix/key/subkey into key_subkey.
func pathToKey(key, prefix string) string {
+ prefix = strings.TrimPrefix(prefix, "/")
+ key = strings.TrimPrefix(key, "/")
key = strings.TrimPrefix(key, prefix)
key = strings.TrimPrefix(key, "/")
return replacer.Replace(key)
Would you rather do this or fix it in the consul client?
/cc @armon since he did the consul implementation
confd is great, but we really need more docs with examples on how to use it well. Docs should cover both etcd and consul.
An error should be logged when confd cannot update the target config due to lack of permissions, or missing parent directories.
I noticed Consul has been added, when will a new release be made?
I tried compiling Go on my own but running into an issue.
go build
github.com/kelseyhightower/confd/etcd/etcdutil
etcd/etcdutil/client.go:70: cannot use &node (type **etcd.Node) as type *etcd.Node in function argument
The README file specifies that etcd should be available but the project also supports Consul.
An example of using Consul with confd should be added to the README.
Set a nested variable in consul:
export PREFIX=http://127.0.0.1:8500/v1/kv/foo/bar
curl -XPUT $PREFIX/role -d'master'
Create a template:
all = {{ . }}
role = {{ .role }}
Run consul:
$ confd -consul -prefix=/foo/bar ...
Expected result is:
all = map[role:master]
role = master
Actual result:
all = map[foo_bar_role:master]
role = <no value>
Say I am configuring an application that supports a hooks.d/ directory. The app will run 'hooks.d/foo" when the "foo" event occurs.
It would be nice if I can add the hook to etcd, and confd would have to place it in the proper directory. Essentially, this amounts to a etcd key whose content's are mirrored to an actual directory on the disk.
Is this something that confd might support?
confd should report what triggers the target config to be updated -- change in content, permissions, etc.
I'm trying to get the configuration into a different partition but confd fails in 'os.Rename'. The solution could be to copy first and then delete the original (temporal stage file).
Refer to:
https://groups.google.com/forum/#!topic/golang-dev/5w7Jmg_iCJQ
@kelseyhightower do you know of any public confd service implementations? Perhaps make a github community for them?
confd should support connecting to an etcd cluster via SSL and client auth.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.