In development environments and during integration tests it could be handy to be able to curl
workloads deployed by juju, via their topology. For example:
curl leader.prometheus.cos-lite.juju.internal:9090/api/v1/targets | jq '...'
One way of achieving this:
- Periodically update a
hosts
-like file with all the addresses fromjuju status
. - Local dns server that reads the custom hosts file.
- Plug the local dns server into systemd-resolved.
A hosts
-like file with all the addresses from juju status
This can be accomplished by:
- Get all combinations of controller-name and model-name from
~/.local/share/juju/models.yaml
. - Run
juju status
for each pair, extracting the app address and unit address. - Render it in a
/etc/hosts
-like format. Also add aleader.
subdomain for leader units.
Code for this can be found here: https://gist.github.com/sed-i/63f92c5f3e55d6688db79276e852e736.
Sample output:
$ ./juju-network.py | sudo tee /etc/hosts.juju
# --- THIS STDERR ---
{('microk8s', 'admin/controller'), ('lxd', 'admin/welcome-lxd'), ('lxd', 'admin/controller'), ('j34', 'admin/controller'), ('microk8s', 'admin/pebnote')}
Obtaining status for microk8s:admin/controller
No addresses for app 'controller'
Obtaining status for lxd:admin/welcome-lxd
Failed
Obtaining status for lxd:admin/controller
Failed
Obtaining status for j34:admin/controller
No addresses for app 'controller'
Obtaining status for microk8s:admin/pebnote
# --- THIS IS STDOUT ---
10.1.166.91 unit-0.loki2.pebnote.juju.internal
10.1.166.91 leader.loki2.pebnote.juju.internal
10.1.166.90 leader.prom.pebnote.juju.internal
10.1.166.93 leader.trfk.pebnote.juju.internal
10.1.166.90 unit-0.prom.pebnote.juju.internal
10.1.166.84 unit-0.loki.pebnote.juju.internal
10.152.183.140 prom.pebnote.juju.internal
10.43.8.188 trfk.pebnote.juju.internal
10.1.166.84 leader.loki.pebnote.juju.internal
10.152.183.241 loki.pebnote.juju.internal
10.1.166.93 unit-0.trfk.pebnote.juju.internal
10.152.183.83 loki2.pebnote.juju.internal
Now we have a hosts-like file, /etc/hosts.juju
, that we can use with a lightweight dns server.
Local dns server that reads the custom hosts file
dnsmasq
is lightweight and good enough for a first try.
sudo apt install dnsmasq
Then update the config file:
$ cat /etc/dnsmasq.conf | grep '^[^# ]'
port=5353
no-resolv
listen-address=::1,127.0.0.1
no-hosts
addn-hosts=/etc/hosts.juju
Note that changes made to the hosts.juju
file won’t be picked up by dnsmasq automatically, and I haven’t found a config option to do so. A documented method to re-read the file is to sudo killall -SIGHUP dnsmasq
.
Plug the local dns server into systemd-resolved
Add a .network
file like this:
$ cat /etc/systemd/network/juju.network
[Match]
Name=*
[Network]
DNS=127.0.0.1:5353
Domains=~juju.internal
and reload services:
sudo systemctl daemon-reload
sudo systemctl restart systemd-networkd
sudo systemctl restart systemd-resolved
Shortcomings
- Need to manually (or scheduled periodically) update the juju hosts file AND force dnsmasq to reload (
sudo killall -SIGHUP dnsmasq
). In integration tests this can be done onceactive/idle
after deploy/upgrade. - Ever since I
systemctl restart
, thejuju status
command hangs for lxd model. Haven’t figured out yet why.
A simpler, one-liner alternative
We can use jq
to render the hosts file for the current model only:
$ juju status --format json
| jq -r '.model.name as $model | .applications[].units | to_entries | map("\(.value.address) \(.key | gsub("/"; "-")).\($model).juju.internal") | join("\n")'
10.1.166.84 loki-0.pebnote.juju.internal
10.1.166.91 loki2-0.pebnote.juju.internal
10.1.166.90 prom-0.pebnote.juju.internal
10.1.166.93 trfk-0.pebnote.juju.internal
To generalize to all models,
#!/usr/bin/env bash
for ctrl_mdl in $(cat ~/.local/share/juju/models.yaml | yq -o json | jq -r '.controllers | to_entries[] | .key as $controller | .value.models | to_entries[] | "\($controller):\(.key)"')
do
timeout 5s bash <<EOT
juju status -m "$ctrl_mdl" --format json \
| jq -r '.model.name as \$model | .applications[].units | to_entries | map("\(.value.address) \(.key | gsub("/"; "-")).\(\$model).juju.internal") | join("\n")'
EOT
done
Looking forward
- Perhaps use some lib to have a super simple all-in-one juju dns server app that runs in the background, so we won’t need dnsmasq and won’t need to schedule periodic tasks.
- Have this functionality integrated in juju itself.