Create and deploy Juju Charms

Terminology

A Juju Charm is a structured bundle of YAML configuration files and scripts for a software component that enables Juju to deploy and manage the software component as a service.

A Juju charm contains all the instructions necessary for deploying and configuring application units.

Charms make it easy to reliably and repeatedly deploy applications, then scale up (and down) as desired with minimal effort

Goal

Create, configure and deploy a wordpress environment consisting of two charms: wordpress charm and mysql charm.

Wordpress and mysql charms are choosed as an example.

However, any application charm can be created and deployed by following the steps reported in this documentation, althought the specific charm configuration will vary from application to application.

Language and pattern used

The Wordpress charm will be created and configured using the reactive paradigm and, specifically, using the reactive.charms python library.

Create Wordpress Charm

  1. In order to make writing Juju Charms easier, first install Juju Charm Tools:

    sudo snap install charm --classic
  2. Using the charm tools plugin, we create the directory structure we need for our charm quickly and easily:

    charm create wordpress

    This not only creates the directory structure, it also populates it with template files for you to edit.

Configure Wordpress Charm

  1. Find the wordpress directory just created and edit the metadata.yaml file located in it:

    name: wordpress
    summary: The wordpress CMS
    maintainer: Claudio Pisa <claudio.pisa@garr.it>
    description: |
    The
    wordpress
    cms
    tags:
    # Replace "misc" with one or more whitelisted tags from this list:
    # https://jujucharms.com/docs/stable/authors-charm-metadata
    - misc
    subordinate: false
    series: [trusty]
    provides:
    website:
        interface: http
    requires:
    database:
        interface: mysql

    The metadata.yaml is the file that Juju reads to find out what a charm is, what it does and what it needs to do it.

    The summary, maintainer and description fields can be edited with arbitrary descriptions.

    The provides and requires fields define which relations are actually provided or required by the wordpress application.

    Since the Wordpress charm is a web-based application, it provides a simple HTTP interface.

    Also, Wordpress charm needs a connection to a database to function properly, so in the metadata.yaml we require a mysql interface.

  2. Edit the layer.yaml file in the wordpress directory and include the basic and apt layer:

    includes: ['layer:basic', 'layer:apt']

    Depending on the charm you're building, you can add other layers but tipically the basic and apt layers are required by every charm.

    These two layers provide charm.reactive ,charm-helpers, charm.atp libraries and all of their dependencies for use by the charm.

  3. Edit the wordpress.py file in the wordpress/reactive directory:

    from charms.reactive import when, when_not, set_flag, hook, when_all
    from charmhelpers.core.hookenv import status_set, relation_get, log
    from charmhelpers.core.host import write_file, service_restart
    from charmhelpers.core.templating import render
    import charms.apt
    import subprocess
    
    @when_not('apt.installed.wordpress')
    def install_wordpress_apt():
        charms.apt.queue_install(['wordpress'])
        #sets the 'apt.installed.wordpress' flag when done
    
    @when('apt.installed.wordpress')
    @when_not('wordpress.ready')
    def install_wordpress():
        # Do your setup here.
        #
        # If your charm has other dependencies before it can install,
        # add those as @when() clauses above., or as additional @when()
        # decorated handlers below
        #
        # See the following for information about reactive charms:
        #
        #  * https://jujucharms.com/docs/devel/developer-getting-started
        #  * https://github.com/juju-solutions/layer-basic#overview
        #
        status_set('blocked', "wordpress installed, waiting for database")
        set_flag('wordpress.ready')
    
    @when_not('wordpress.ready')
    @when_not('apt.installed.wordpress')
    def waiting_for_wordpress():
        status_set('maintenance', "waiting for apt wordpress installation")
  4. Be sure to be logged into a juju controller.

  5. Position yourself in the wordpress directory and from the command line execute these commands:

    charm build
    juju deploy /tmp/charm-builds/wordpress
    watch -c juju status --color

    As you can see, juju will do the following:

    1. Interact with your cloud/openstack/localhost to request the provisioning of a machine on which deploy the charm

    2. Deploy the wordpress charm previously created

    The wordpress application and unit will halt on the status: 'blocked' because no other python function was specified to be executed in the wordpress.py file after the install_wordpress function.

    Also, the application status 'blocked' is exactly what we expect to see since we wrote the following instruction in wordpress.py:

    status_set('blocked', "wordpress installed, waiting for database")

    At this point, a charm that will install wordpress via apt and do nothing else it has been created and deployed.

  6. Continue the charm configuration, deploy mysql and add a relation between wordpress and mysql:

    1. Add a new function in the wordpress.py file:

      @hook('database-relation-joined')
      def database_is_ready():
          status_set('blocked', 'Database is ready (joined) but not configured')
          set_flag('wordpress.database_is_ready')
    2. Position yourself in the wordpress directory and from the command line execute these commands:

      charm build
      juju upgrade-charm wordpress --path /tmp/charm-builds/wordpress
      juju deploy mysql --series xenial
      juju add-relation wordpress mysql
      watch -c juju status --color

    As a result of these commands, the mysql charm will be deployed.

    A relation between mysql and wordpress will be added.

    The wordpress application and unit will halt again on the status: 'blocked' as we expect since we wrote the status_set instruction in the database_is_ready function.

    The database_is_ready function is executed when both wordpress and mysql units are deployed.

    Also, the function execution is triggered when the relation between wordpress and mysql is added as a result of the juju add-relation command previously issued.

Configure Wordpress application inside Wordpress Charm

Configure Wordpress application according to https://help.ubuntu.com/lts/serverguide/wordpress.html

1 Edit the install_wordpress function in the wordpress.py file:

def install_wordpress():
# Do your setup here.
#
# If your charm has other dependencies before it can install,
# add those as @when() clauses above., or as additional @when()
# decorated handlers below
#
# See the following for information about reactive charms:
#
#  * https://jujucharms.com/docs/devel/developer-getting-started
#  * https://github.com/juju-solutions/layer-basic#overview
#

status_set('maintenance', "configuring wordpress")
# see https://help.ubuntu.com/lts/serverguide/wordpress.html
wpconf = """
    Alias /blog /usr/share/wordpress
    <Directory /usr/share/wordpress>
        Options FollowSymLinks
        AllowOverride Limit Options FileInfo
        DirectoryIndex index.php
        Order allow,deny
        Allow from all
    </Directory>
    <Directory /usr/share/wordpress/wp-content>
        Options FollowSymLinks
        Order allow,deny
        Allow from all
    </Directory>
"""
write_file('/etc/apache2/sites-available/wordpress.conf', wpconf)
subprocess.call(['a2ensite', 'wordpress'])
service_restart('apache2')

status_set('blocked', "wordpress installed, waiting for database")
set_flag('wordpress.installed')

The modified function does the following:

  1. writes into etc/apache2/sites-available/wordpress.conf

  2. executes the sudo a2ensite wordpress command

  3. restarts apache service

  1. Create a directory named templates into the wordpress directory

  2. In templates create and edit a file named config-localhost-php.tmpl:

    <?php
    define('DB_NAME', '{{ my_database.database }}');
    define('DB_USER', '{{ my_database.user }}');
    define('DB_PASSWORD', '{{ my_database.password }}');
    define('DB_HOST', '{{ my_database.host }}');
    define('WP_CONTENT_DIR', '/usr/share/wordpress/wp-content');
    ?>
  3. Write the following function in wordpress.py:

    @when_all('wordpress.installed', 'wordpress.database_is_ready')
    def config_php():
        status_set('maintenance', 'configuring wordpress')
        mysql = relation_get()
        log(str(mysql),'INFO')
        render(
                source='config-localhost-php.tmpl',
                target='/etc/wordpress/config-default.php',
                context={
                    'my_database': mysql
                })
        status_set('active', 'Ready')

    This function retrieves informations from the mysql unit regarding the account and database that was created when the juju add-relation mysql wordpress command was executed.

    Then, writes these informations into the /etc/wordpress/config-default.php file.

  4. Build and deploy the wordpress charm with the last modifications:

    charm build
    juju upgrade-charm wordpress --path /tmp/charm-builds/wordpress
    watch -c juju status --color

    The wordpress application and unit status will become Ready.

    The wordpress charm is correctly configured and ready to be used.

Expose Wordpress

From the command line execute this command:

juju expose wordpress

Use Wordpress

From the command line execute the juju status command:

images/status.png

You can notice that the wordpress application is publicy exposed.

Take the public address of the wordpress unit, open a browser and enter the address into the address bar.

Enjoy Wordpress!