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

Do you know if any of these examples ever appeared?

I’m building my first application charm, which is now deploying locally in a docker container, but needs connecting to a deployed database. I really could do with an up to date reactive implementation of a database-relation-joined example as shown using bash in the link you kindly posted.

I can plough through the charms.reactive API – although it seems relations are replaced by endpoints? Surely there must somewhere be an equivalent example in python for getting hold of the database connection parameters in my application layer. Any guidance appreciated

2 Likes

I’m working on a more detailed example actually right now and I will happily help you to get through here. I’m however out for the weekend but next week I could spend some time with you on this topic.

@jamesbeedy is also a great help if he can find some time with you.

It highlights the need… Right?

1 Like

Many thanks Erik – Look forward to catching up next week.

1 Like

So, I’m about half way through my work on the new tutorial which intend to cover implementing “relations” with hooks and juju.

Its been very good for me personally as I’m building up more in depth knowledge on this topic and I am totally up for helping you also getting more knowledge here.

How far in your own development experience have you come with juju so far? Are you up to date with how juju works generally or what would you say your level is currently? It helps to know so I can point you in the right direction if you need to prepare…

1 Like

I have used juju for deployment of k8 and other applications on our on-premise maas cloud and am familiar with what it does and how to use it. I would say I am a competent user of juju for deployment of pre-built charms. This is the first time I have developed a juju charm and I am about 3 days in to this. Using tutorials ( mostly by following the nginx docker example ) I have built a charm that pulls an image of our docker application from our private repo and then deploys a machine and installs the docker container correctly. The container runs but then fails waiting to connect to an arangodb database (previously deployed by juju) – as would be expected. Hence the request for help.So I am at the early stage in charm development, a beginner, and am dependent on tutorials and examples to get a working installation. If I can get past the connection parameter issues I think that putting a bundle together (2 of our apps plus arangodb) should be ok. At a later date we’d look to extend deployment to public clouds, and I would hope that I can extend the charm to do that, but that’s not yet a priority. Hope that helps.

Really, really appreciate your help on this!

@deleahy - I’ll send you a PM with link to the work-in-progress for you to have a look at. It will likely help you somewhat to get started and perhaps you can give me feedback and see how you like it?

I’ve yet to write the text, but I’m working on the code-example to get that material top notch.

Hey @deleahy, not to derail your current direction or the help you are getting from @erik-lonroth ( thanks for working on examples and docs @erik-lonroth! ), the Lucky charming framework getting started guide has an in-depth example of creating a Docker-based deployment of an application with both a database and a web relation.

This is not a reactive framework guide, though, as Lucky is a completely different charming framework where you write charms as simple bash scripts, but it is specially tailored to deploying and managing Docker containers, if that is what you are wanting to do.

You may not want to switch frameworks or what-not so this may not be useful, but I figured I’d throw it out there because it sounds similar to what you are trying to accomplish.

1 Like

I’ll just fill in here that there are a number of different frameworks for producing charms @deleahy.

  • The “Hooks-only” framework (which is the most core/basic)
  • The “Reactive” framework - which is the python-based framework using “reactive” programming.
  • The “Operator” framework - which is a new python based framework developed by Canonical.
  • The “Lucky” framework which @zicklag and his skilled team has developed.

My own approach to juju charming has been to try to understand the core juju primitives. From that I believe that I can move easily between all these frameworks. But I guess everyone learns differently so I wouldn’t be able to say what works best for you.

1 Like

Thanks for this. I will look through the example you give

2 Likes