What is a Juju relation and what purpose do they serve?

Relations are Juju’s secret weapon. They’re the key to enabling “always optimal configuration”. Applications can automatically negotiate their configuration, by making use of information provided by their environment.

This tutorial is 1 of a series that I hope to write illustrating what relations are and how they behave.

Note: We’ll assume for now that you have a Juju controller available. If that’s not the case, then use juju bootstrap to create one, or juju login jaas to make use of the hosted JAAS controllers.

A relation enables application units to share information. Let’s ignore how Juju enables that sharing at this stage. Instead, we’ll work through some examples of why this feature is useful.

Scenario: automatically re-configure an application when it is scaled out into multiple units

When an application’s scale increases, configuration often needs to be updated. Suddenly, the pre-existing unit(s) need to be notified of their new peer.

Deploying a high-availability PostgreSQL cluster on Ubuntu is a very popular tutorial topic. Most tutorials include a dive deep into configuration files. With Juju, that’s not necessary. Relations enable applications to re-configure themselves.

To start, let’s create a stand-alone PostgreSQL server. Execute these commands from the command-line:

juju add-model postgres-demo
juju deploy postgresql

Juju will proceed to deploy PostgreSQL onto /etc/postgresql within an Ubuntu Server instance. After everything is installed, Juju will write the configuration settings files. One of the most important files is pg_hba.conf. The letters HBA in the file name stand for “host-based authentication”. Accordingly, pg_hba.conf controls access to the database server.

To demonstrate what Juju is able to do, we’ll take a local copy of the file before and after adding a second unit.

The following snippet uses juju exec to execute a command on the postgresql/0 unit. In this case, we’re executing cat. We then redirect the output of the cat command to a new file on our local machine.

juju exec --unit postgresql/0 \
          -- sudo cat /etc/postgresql/10/main/pg_hba.conf \
          > /tmp/hba_old.conf

And, to create a high-availability cluster, all we need to do is add a unit:

juju add-unit postgresql

Juju will take the time to ensure that the new unit is on machine that’s provisioned on a different availability zone.

If we monitor the juju status output, “(config changed)” will appear in the postgresql/0 unit’s line. The asterisk after postgresql/0 indicates that it is the leader unit.

Model          Controller  Cloud/Region     Version      SLA          Timestamp
postgres-demo  az          azure/centralus  2.8-beta1.1  unsupported  10:05:45+13:00

App         Version  Status   Scale  Charm       Store       Rev  OS      Notes
postgresql  10.10    waiting      2  postgresql  jujucharms  199  ubuntu  

Unit           Workload  Agent      Machine  Public address  Ports     Message
postgresql/0*  active    executing  0        13.86.127.19    5432/tcp  (config-changed) Live master (10.10)
postgresql/1   waiting   executing  1        52.185.67.131             agent initializing

Machine  State    DNS            Inst id    Series  AZ  Message
0        started  13.86.127.19   machine-0  bionic      
1        started  52.185.67.131  machine-1  bionic      

Once everything has settled down, we’ll be able to evaluate the changes that Juju has made. Let’s copy the new configuration file to a new location.

juju exec --unit postgresql/0 \
          -- sudo cat /etc/postgresql/10/main/pg_hba.conf \
          > /tmp/hba_new.conf

Let’s observe the difference between the two files. diff is a great command for this:

diff /tmp/hba_old.conf /tmp/hba_new.conf

Produces (on my model):

104a105,106
> host replication _juju_repl "192.168.0.5/32" md5 # replication:1 (postgresql/1)
> host postgres _juju_repl "192.168.0.5/32" md5 # replication:1 (postgresql/1)

Adding the second unit has caused Juju to insert 2 lines to be added. The _juju_repl user has been given access to the replication and postgres databases. Is this magic? No! The charm has written the code that makes these changes.

Relations are a mechanism for informing charms that the situation has changed. The charm code is then able to update its application to fit. They’re a surprisingly powerful abstraction that can enable you, as someone writing charms, to keep everything up-to-date.

To clean up, execute the juju destroy-model command:

juju destroy-model -y postgres-demo

@erik-lonroth Let me know if this makes sense. More parts will follow.

1 Like

This is well needed. I think you are addressing perhaps the single most important topic currently in the whole juju domain.

I still dont understand this topic fully and expect many more have a hard time here. My collegues without programming backgrounds are totally lost.

Code samples and templates perhaps could be needed? Not sure. But definetly this topic is what makes juju hard to learn to me at least.

+1, I think that relations got a lot less black magic when I stopped thinking of them as the two services talking to each other and focused more on the units passing dicts of data back and forth through the controller. A visual of the units using their hook-tool to set data and read it on the other side showing going through the controller might be helpful. In this light I think having an example that’s not where the services are doing direct communication might be useful as a second example.

This is a great start thanks for the write up!

Good stuff, @timClicks.

I might recommend adding the --relations flag to juju status output in your example status so that you can demonstrate the “peer” interface relation that postgres has with itself in the model status view and provide the example metadata.yaml section that defines the peer relationship that relates to that relation in the status output.

Adding --relations is a really good idea.

Yes.

But also what mechanisms signals when data is available. New vs updated data and how different units might get or need different data etc etc.

Iron out a simple scenario to show this, in code, would be welcome.

The current official documentation covers this topic today unfortunately without a single piece of code: Implementing relations | Juju

Not good for teaching this difficult area.

If @timClicks manage to create a good teaching material on this topic I will be mega happy.

Here is the best example I can find, although its implemented in bash. Charms and MySQL interfaces

I would love to see a consistent set of separate of examples for :

  • peer relations example (reactive + hooks version)

  • subordinate relations example (reactive + hooks version)

  • managing multiple unit relations (reactive + hooks version)

  • leadership (reactive + hooks version)

  • what is a boken relation? What am I expected to do if this event occurs?

Might be more versions of relations I dont know of…

1 Like

I just wanted to point out what my single biggest misconception about relations was as I tried to get started with them.

When I saw that you could relation-get and relation-set, I thought that relation-set sets a key-value pair on the “connecting line” of the relation so to speak. I thought that each relation essentially created a key-value store that was shared by the units on both sides of the relation. In truth, the units don’t share a key-value store, but they can set values on thier side of the relation that can be read from the other side of the relation.

My problem was trying to understand the pgsql relation from a bash script. I realized that I needed to relation-set the database name that I wanted Postgres to connect me to, but I didn’t realize that I had to relation-get the connection info from the remote unit. I thought I was supposed to relation-get the connection info from the local unit.

1 Like

Thanks for letting us know that this was an issue for you.

1 Like