What is a Juju relation and what purpose do they serve? [Part 2]

This tutorial follows on from part 1:

Here, we go into more detail about what relations actually are. It also covers the mechanisms that you have available to introspect Juju.

Scenario: informing two applications of each other’s presence

The most visible use of relations is when they’re linking two applications. The terminology is confusing here. The link provided by a relation is conceptual. No networking rules are established when the relation is added.

A relation is:

  • A unit to unit mechanism. We’ll cover the exceptions in a later post.
  • A signal that another unit has joined (or left) the model
  • How units share data

We saw in the earlier turorial that relations are unit to unit. This post explains the last two bullet points.

Let’s start with an example: adding HTTPS to a webservice via Let’s Encrypt.

juju deploy ghost
juju deploy cs:~tengu-team/ssl-termination-proxy

At this point, both charms will begin the deployment process. That involves ensuring a host machine is present and agents for the machine and the unit are active. The unit agent takes responsibility for installing the software defined by each charm.

Before moving on, agree to the terms of the Let’s Encrypt service:

juju agree isrg-lets-encrypt/2

After a few minutes, the ghost charm will be ready. Use the juju status command to find its IP address:

juju status --format=oneline

Output:

- ghost/0: 10.110.147.29 (agent:idle, workload:active) 2368/tcp
- ssl-termination-proxy/0: 10.110.147.129 (agent:idle, workload:blocked) 80/tcp, 443/tcp

Ghost can receive HTTP requests:

curl --head 10.110.147.29:2368

Output:

HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Wed, 27 Nov 2019 20:09:47 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 4557
Connection: keep-alive
X-Powered-By: Express
Cache-Control: public, max-age=0
ETag: W/"11cd-sWYHXlO9+Ren81NIJDNffpjLGp8"
Vary: Accept-Encoding

You may have noticed that the ssl-termination-proxy/0 line includes workload:blocked. The full status output for the ssl-termination-proxy/0 unit provides a hint:

juju status

Which includes the line:

ssl-termination-proxy/0*  blocked   idle   3        10.110.147.129  80/tcp,443/tcp  waiting for fqdn subordinates

The unit is waiting for web services to protect. The process for supporting multiple applications is more complex, but for a single one, adding a single HTTPS support is a 2 step process.

  1. Tell ssl-termination-proxy the domain name
  2. Relate the applications

Step 1 requires you to use a domain name registrar to update DNS records. Add an A record to point to the IP address of the ssl-termination-proxy/0 unit.

Step 2 is easier. A single Juju command.

juju relate ghost ssl-termination-proxy

If we check the output from juju status again, when adding the --relations option, we’ll receive an extra line:

juju status --relations

Includes the output:

Relation provider       Requirer                            Interface    Type     Message
ghost:website           ssl-termination-proxy:reverseproxy  http         regular  

Now, you should be able to visit Ghost over HTTPS via the domain name that you’ve set up with the DNS record.

Things now work. The relation has allowed both sides to update their configuration according to the new situation. That’s great, but it can feel a little magical. Let’s focus on explaining what relations do to enable this sort of change.

How do relations work?

Like juju deploy, juju relate instructs the unit agents to invoke commands provided by the charm. When a relation is added, the code within the charm is responsible for making any necessary changes.

Juju will only perform actions that are dictated by charms. It won’t update any configuration values by itself. Charm authors retain full control over what happens when two applications are related.

The juju show-status-log command provides a complete history of the hooks that a unit agent:

juju show-status-log --type juju-unit ghost/0

Output:

Time                        Type       Status      Message
28 Nov 2019 10:00:30+13:00  juju-unit  allocating  
28 Nov 2019 10:01:51+13:00  juju-unit  executing   running app-storage-attached hook
28 Nov 2019 10:05:07+13:00  juju-unit  executing   running install hook
28 Nov 2019 10:05:08+13:00  juju-unit  executing   running leader-elected hook
28 Nov 2019 10:05:08+13:00  juju-unit  executing   running config-changed hook
28 Nov 2019 10:05:09+13:00  juju-unit  executing   running start hook
28 Nov 2019 10:05:09+13:00  juju-unit  executing   running website-relation-joined hook
28 Nov 2019 10:05:10+13:00  juju-unit  executing   running website-relation-changed hook
28 Nov 2019 10:05:11+13:00  juju-unit  idle

The important lines, from the point of view of relations, are what happens after the start hook. All of the configuration changes for the ghost must have happened within the website-relation-joined and website-relation-changed hooks.

On the ssl-termination-proxy side, a similar pattern occurs. Here though, the unit agent was able to enter the idle state before executing its relation hooks.

juju show-status-log --type juju-unit ssl-termination-proxy/0

Output:

Time                        Type       Status      Message
28 Nov 2019 10:01:14+13:00  juju-unit  allocating  
28 Nov 2019 10:02:59+13:00  juju-unit  executing   running install hook
28 Nov 2019 10:05:02+13:00  juju-unit  executing   running leader-elected hook
28 Nov 2019 10:05:02+13:00  juju-unit  executing   running config-changed hook
28 Nov 2019 10:05:03+13:00  juju-unit  executing   running start hook
28 Nov 2019 10:05:04+13:00  juju-unit  idle        
28 Nov 2019 10:05:10+13:00  juju-unit  executing   running reverseproxy-relation-joined hook
28 Nov 2019 10:05:10+13:00  juju-unit  executing   running reverseproxy-relation-changed hook
28 Nov 2019 10:05:11+13:00  juju-unit  idle     

If you’re wondering why the hooks have different names in each case, it’s because hook names are somewhat arbitrary. They’re defined as endpoints within a charm’s metadata.yaml file that both speak one end the “http” interface. A future post will explain interfaces and endpoints.

The relation-joined hook signals to units that another unit is available. But we still haven’t explained the process by which they share data.

Juju offers charm writers two hook tools, relation-get and relation-set. They’re used by unit agents to send key value pairs. The controller relays the data between agents (there is no peer to peer communication system available to unit agents). It’s possible to simulate what’s happening from the client side, but the process is somewhat convoluted.

Using the juju exec command in the context of the ghost/0 unit, we’ll run the relation-ids hook tool. Once we have the internal relation ID, we’ll use that to inspect the data sent over the wire.

First, let’s check what the ssl-termination-proxy is making available to any ghost units:

$ juju exec --unit ghost/0 "relation-ids website"
website:0

$ juju exec --unit ghost/0 "relation-get -r website:0 - ssl-termination-proxy/0"
egress-subnets: 10.110.147.57/32
ingress-address: 10.110.147.57
private-address: 10.110.147.57

ghost/0 is able to access IP addresses, but not much else. What is happening on the other side?

$ juju exec --unit ssl-termination-proxy/0 "relation-ids reverseproxy"
reverseproxy:0

$ juju exec --unit ssl-termination-proxy/0 "relation-get -r reverseproxy:0 - ghost/0"
egress-subnets: 10.110.147.53/32
hostname: 10.110.147.53
ingress-address: 10.110.147.53
port: "2368"
private-address: 10.110.147.53

And, that’s how things work. The relation hooks signal to a unit that another unit has appeared and the hook tools are how units share data.

To get a fuller understanding, we need to detangle the terms endpoint, interface and relation. That’s next!

1 Like