About sharing data between units

Hi everybody,

There is an issue which I cannot figure out which is the best way to solve it. In fact I do not know if it’s an issue in my code (most likely), or it’s an issue/feature of juju/ops.

I am working on mysql-operator charm. The mysql image I’m using lets us configure the MYSQL_ROOT_PASSWORD through environment variables, so if I run:

$ juju deploy -n 3 ./mysql.charm --config MYSQL_ROOT_PASSWORD=SecretPass --resource mysql-image=ubuntu/mysql:latest

I will have 3 units running MySQL with root user having the same password: SecretPass. That’s make sense, and everything works.

In the charm code I have:

        if config.get("MYSQL_ROOT_PASSWORD"):
            self._stored.mysql_setup["MYSQL_ROOT_PASSWORD"] = config[
                "MYSQL_ROOT_PASSWORD"
            ]
            env_config["MYSQL_ROOT_PASSWORD"] = config["MYSQL_ROOT_PASSWORD"] # Password from env variable
        else:
            env_config["MYSQL_ROOT_PASSWORD"] = self.mysql_root_password      # Randomly generated password

and build the pod_spec using the env_config dictionary:

        pod_spec = {
            "version": 3,
            "containers": [
                {
                    "name": self.app.name,
                    "imageDetails": image_info,
                    "ports": [
                        {"containerPort": config["port"], "protocol": "TCP"}
                    ],
                    "envConfig": self.env_config,

...

But if I do not send the MYSQL_ROOT_PASSWORD and run:

$ juju deploy -n 3 ./mysql.charm --resource mysql-image=ubuntu/mysql:latest

the charm code will generate a random password for root user:

            password = "".join(
                random.choice(ascii_letters + digits) for x in range(20)
            )
            self._stored.mysql_setup["MYSQL_ROOT_PASSWORD"] = password

and set it to the pod_spec.
From what I understand, the first execution (leader unit) will generate a password, set it to the pod_spec and use this specs to generate the 3 units even though this code is executed once per unit.
In each execution the generated password is stored in the StoredState object so we can use it later in the readinessProbe and livenessProbe:

                        "readinessProbe": {
                            "exec": {
                                "command": [
                                    "mysqladmin",
                                    "ping",
                                    "-u",
                                    "root",
                                    "-p{}".format(self.mysql_root_password),
                                ]
                            },
                            "initialDelaySeconds": 20,
                            "periodSeconds": 5,
                            },

But these probes only works ok in the leader unit which stores the password that was used to build the pod_spec for the 3 units. AFAIK the Storedstate object stores data the charm needs persisted across invocations of the same unit.

So at this point I have these doubts to tackle the solution:

  • Is there a way to store data across invocations of different units? (so we can generate the password in the leader unit, and get it from the other units)
  • Is there a way to build the units using slightly different pod_specs? (so we can have different password for each unit)
  • May be the solution will be making the --config MYSQL_ROOT_PASSWORD=SecretPass parameter mandatory during the deployment… :man_shrugging:

Thanks!

Jose

one way you can make the leader share data with other units of the same charm, is through the peer relation. Each unit can then store a copy of the same password.

1 Like

Thank you very much @bthomas
Do we have an example of this in a charm?

Another approach here is to use leader data directly. We’re doing this in the postgresql k8s charm via a “leadership” library. I wouldn’t recommend it directly just yet as we’ve got a PR against the Operator Framework to integrate it more directly into the framework itself, but that’s still in review.

1 Like

@jose The mongodb-operator defines a peer relation in its metadata.yaml and uses it in its charm.py but it does not exchange data over the peer relation. However exchanging data over a peer relation is just like for any other relation. For example the prometheus-opeartor sets data for its relation with grafana.

1 Like

@mthaddon I like the approach you use in the postgresql k8s charm. Thank you very much!

Great @bthomas I’ll take a look at both!