Many applications require access to some persistent storage. The Juju OLM and Charmed Operator Framework provide a facility for working with and defining charms that require storage.
- Defining storage
- Storage events
- Charm and Container Accessing to Storage
- Scaling Storage
Charm storage is defined in
metadata.yaml (spec). Storage is defined as a top-level YAML map in
metadata.yaml, keys for the map represent the name of the storage volume.
storage map definition:
||required||Type of storage requested. Supported values are
If the charm specifies a
||Description of the storage requested|
(see table below)
||By default, stores are singletons; a charm will have exactly one of the specified stores. The
Unless a number is explicitly specified during deployment, units of the application will be allocated the minimum number of storage instances specified in the charm metadata. It is then possible to add instances (up to the maximum) by using the
||Size in the forms:
||Specifies the mount location for
||List of properties for the storage. Currently only
||True indicates that all units of the application share the storage.|
multiple map definition:
||nil||Value can be an
An example of a storage definition inside
# ... storage: # Name of this storage is 'data' data: type: filesystem description: junk storage minimum-size: 100M location: /srv/data # ...
Storage on Kubernetes
In addition to the above, there is some additional data required to define storage for Kubernetes charms. You will still need to define the top-level storage map (as above), but also specify which containers you would like the storage mounted into. Consider the following
# ... containers: # define a container named "important-app" important-app: # use the "app-image" oci resource resource: app-image # mount our 'logs' store at /var/log/important-app # in the workload container mounts: - storage: logs location: /var/log/important-app # This is another container with no storage supporting-app: resource: supporting-app-image storage: logs: type: filesystem # specifying location on the charm container is optional # when unspecified, defaults to /var/lib/juju/storage/<name>/<num> # ...
The above snippet will ensure that both the
important-app container and charm container inside each Pod has the
logs store mounted. Under the hood, the
storage map is translated into a series of
PersistentVolumes, mounted into Pods with
location attribute must be specified when mounting a storage into a workload container as shown above - this will dictate the mount point for the specific container.
Optionally, developers can specify the
location attribute on the storage itself, which will specify the mount point in the charm container. If left unset, the charm container will have the storage volume mounted at a predictable path at
<num> is the index of the storage. This defaults to
For the above
metadata.yaml, the charm container would have the storage available at:
There are two key events associated with storage:
|Event name||Event Type||Description|
||This event is triggered when new storage is available for the charm to use. Callback methods bound to this event allow the charm to run code when storage has been added.
Such methods will be run before the
||Callback methods bound to this event allow the charm to run code before storage is removed.
Such methods will be run before storage is detached, and always before the
The name prefix of the hook will depend on the storage key defined in the
Charm and Container Accessing to Storage
When you use storage mounts with juju, it will be automatically mounted into the charm container at either:
locationbased on the storage section of metadata.yaml or
the default location
numis zero for “normal”/singular storages or integer id for storages that support
The operator framework provides the
Model.storages dict-like member that maps storage names to a
list of storages mounted under that name. It is a list in order to handle the case of storage
configured for multiple instances. For the basic singular case, you will simply access the
first/only element of this list.
Charm developers should not directly assume a location/path for mounted storage. To access mounted storage resources, retrieve the desired storage’s mount location from within your charm code - e.g.:
def _my_hook_function(self, event): ... storage = self.model.storages['my-storage'] root = storage.location fname = 'foo.txt' fpath = os.path.join(root, fname) with open(fpath, 'w') as f: f.write('super important config info') ...
This example utilizes the framework’s representation of juju storage - i.e.
which returns a
objects, which exposes the
location of each storage to the charm developer,
id is the underlying storage provider ID.
If you have also mounted storage in a container, that storage will be located directly at the specified mount location. For example with the following content in your metadata.yaml:
containers: foo: resource: foo-image mounts: - storage: data location: /foo-data
storage for the “foo” container will be mounted directly at
/foo-data. There are no storage name
or integer-indexed subdirectories. Juju does not currently support multiple storage instances for
charms using “containers” functionality. If you are writing a container-based charm (e.g. for
kubernetes clouds) it is best to have your charm code communicate the storage location to the
workload rather than hard-coding the storage path in the container itself. This can be
accomplished by various means. One method is passing the mount path via a file using the
def _on_mystorage_storage_attached(self, event): # get the mount path from the charm metadata container_meta = self.framework.meta.containers['my-container'] storage_path = container_meta.mounts['my-storage'].location # push the path to the workload container c = self.model.unit.get_container('my-container') c.push('/my-app-config/storage-path.cfg', storage_path) ... # tell workload service to reload config/restart, etc.
While juju provides an
add-storage command, this does not “grow” existing storage
instances/mounts like you might expect. Rather it works by increasing the number of storage
instances available/mounted for storages configured with the
multiple parameter. For charm
development, handling storage scaling (add/detach) amounts to handling
<name_storage_detaching events. For example, with the following in your metadata.yaml file:
storage: my-storage: type: filesystem multiple: range: 1-10
juju will deploy the application with the minimum of the range (1 storage instance in the example
above). Storage with this type of
multiple:... configuration will have each instance residing
under an indexed subdirectory of that storage’s main directory - e.g.
/var/lib/juju/storage/my-storage/1 by default in charm container. Running
juju add-storage <unit> my-storage=32G,2 will add two additional instances to this storage - e.g.:
/var/lib/juju/storage/my-storage/3. “Adding” storage
does not modify or affect existing storage mounts. This would generate two separate
storage-attached events that should be handled.
In addition to juju client requests for adding storage, the
self.model.storages also exposes a
self.model.storages.request()) which provides an expedient method for the developer
to invoke the underlying
storage-add hook tool in
the charm to request additional storage. On success, this will fire a
Last updated 11 months ago.