Services are composed of one or more service units. A service unit runs the service's software and is the smallest entity managed by Juju. Service units are typically run in an isolated container on a machine with no knowledge or access to other services deployed onto the same machine. Subordinate services allows for units of different services to be deployed into the same container and to have knowledge of each other.
Services such as logging, monitoring, backups and some types of storage often require some access to the runtime of the service they wish to operate on. Under the current modeling of services it is only possible to relate services to other services with an explicit interface pairing. Requiring a specified relation implies that every charm author need be aware of any and all services a deployment might wish to depend on, even if the other service can operate without any explicit cooperation. For example a logging service may only require access to the container level logging directory to function.
The following changes are designed to address these issues and allow a class of charm that can execute in the context of an existing container while still taking advantage of the existing relationship machinery.
Principal service: A traditional service or charm in whose container subordinate services will execute.
Subordinate service/charm: A service designed for and deployed to the running container of another service unit.
Container relation: A scope:container relationship. While modeled identically to traditional, scope: global, relationships, juju only implements the relationship between the units belonging to the same container.
When a traditional relation is added between two services, all the service units for the first service will receive relation events about all service units for the second service. Subordinate services have a very tight relationship with their principal service, so it makes sense to be able to restrict that communication in some cases so that they only receive events about each other. That's precisely what happens when a relation is tagged as being a scoped to the container. See Relations lifecycle.
Container relations exist because they simplify responsibilities for the subordinate service charm author who would otherwise always have to filter units of their relation before finding the unit they can operate on.
If a subordinate service needs to communicate with all units of the principal service, it can still establish a traditional (non-container) relationship to it.
In order to deploy a subordinate service a scope: container relationship is required. Even when the principal services' charm author doesn't provide an explicit relationship for the subordinate to join, using an implicit relation with scope: container will satisfy this constraint.
No special changes are made for the purpose of naming or addressing subordinate units. If a subordinate logging service is deployed with a single unit of wordpress we would expect the logging unit to be addressable as logging/0, if this service were then related to a mysql service with a single unit we'd expect logging/1 to be deployed in its container. Subordinate units inherit the public/private address of the principal service. The container of the principal defines the network setup.
When a charm author wishes to indicate their charm should operate as a subordinate service only a small change to the subordinate charm's metadata is required. Adding subordinate: true as a top-level attribute indicates the charm is intended only to deploy in an existing container. Subordinate charms should then declare a required interface with scope: container in the relation definition of the charms metadata. Subordinate services may still declare traditional relations to any service. The deployment is delayed until a container relation is added.
subordinate: false charms (the default) may still declare relations as scope: container. Principal charms providing or requiring scope: container relations will only be able to form relations with subordinate: true charms.
The example below shows adding a container relation to a charm.
requires: logging-directory: interface: logging scope: container
Assume the following deployment:
juju deploy mysql juju deploy wordpress juju add-relation mysql wordpress
Now we'll request a subordinate rsyslog-forwarder application:
juju deploy rsyslog-forwarder juju add-relation rsyslog-forwarder mysql juju add-relation rsyslog-forwarder wordpress
This will create a rsyslog-forwarder application unit inside each of the containers holding the mysql and wordpress units. The rsyslog-forwarder application has a standard client-server relation to both wordpress and mysql but these new relationships are implemented only between the principal unit and the subordinate unit. A subordinate unit may still have standard relations established with any unit as usual.
The status output contains details about subordinate units under the status of the principal application unit that it is sharing the container with. The subordinate unit's output matches the formatting of existing unit entries but omits machine, public-address and subordinates (which are all the same as the principal unit).
Shown below are two partial excerpts from the output to command
juju status --format yaml after the 'wordpress' charm and the
'rsyslog-forwarder' subordinate charm were deployed and a relation added
between the two.
Firstly, the rsyslog-forwarder application will show to what application it is a subordinate of:
applications: rsyslog-forwarder: subordinate-to: - wordpress
Secondly, in the units sub-section to the wordpress application the subordinate unit will be listed:
applications: wordpress: units: subordinates: rsyslog-forwarder/0:
When a subordinate is related to a single principal application, the subordinate may be removed by removing the principal-subordinate relation. When related to multiple principal applications, the subordinate may only be removed by removing the principal unit.