-
Notifications
You must be signed in to change notification settings - Fork 0
Implementing environment providers
This document describes how to implement an environment provider for Juju. For the remainder of this document we will use the term "provider" to mean "environment provider", but be aware that there are additional types of providers (e.g. storage providers).
Providers are the bridge between Juju and the cloud environment in which Juju operates, and provides:
- allocation, deallocation, and querying of machines
- cloud-integrated network management
A provider is made up of several different parts:
- Configuration
- Creation (bootstrapping) and destruction of the environment
- Instance (machine) creation
- Instance querying
- Firewalling
These are encompassed by several interfaces in the Juju codebase:
The first thing you should do when creating a new provider is identify the core configuration required, and then implement the EnvironProvider interface. The EnvironProvider methods are described below.
Environment configuration is encapsulated in environs/config.Config, providing accessors for common configuration attributes. Provider-specific configuration can be obtained through the Config.UnknownAttrs
method.
These methods "prepare" an environment, which essentially means adding attributes to the provided configuration as required. For most providers, PrepareForCreateEnvironment will be a no-op, simply returning the provided configuration unmodified. Similarly, for most providers the PrepareForBootstrap method will be a call to PrepareForCreateEnvironment followed by a call to Open.
Validate takes two configurations, and reports whether or not they are valid. There are two use-cases for this method:
- "Is this configuration valid in isolation?" In this case, the first argument will be a non-nil configuration, and the second will be nil.
- "Is this configuration valid, given the existing configuration?" In this case, the first argument will be the new configuration, and the second argument the old. You must check for invalid changes to configuration here.
It is also possible for the Validate method to return a modified configuration, which is often used to implement configuration upgrades (i.e. upgrading configuration for older versions of a provider in a newer version). This exists only because we do not have machinery dedicated to upgrading configuration, and should not be used lightly.
Open returns an Environ for a given configuration.
SecretAttrs identifies which configuration attributes are secrets, which may only be shared with servers in the Juju environment, and will be stripped from the environment configuration that is stored in the database. These are typically only the credentials required to connect to the cloud provider.
There is work ongoing to support multiple environments (i.e. separate sets of machines, services, units, etc.) within the same cloud provider. RestrictedConfigAttributes identifies which attributes should not change between multiple environments for the same Juju server, such as the cloud provider credentials.
BoilerplateConfig returns the provider-specific YAML boilerplate that is added to environments.yaml
when running juju init
. The output should describe the possible configurations and default values.
Before Juju can be useful, it needs to create the initial environment which contains a single Juju server, known as "machine 0". The procedure by which this is created is known as "bootstrapping". Some of this is provider-independent, and some is provider-specific; the latter is implemented via the Environ.Bootstrap
method.
The bootstrap procedure is somewhat complicated, but many providers can simply defer to the provider/common.Bootstrap function. Most of the hard work is in creating machine instances.
Destroying an environment is just a matter of destroying any resources created in the cloud provider. You must make sure that any resources you create can later be identified in order to be destroyed. The environment destruction procedure is implemented via the Environ.Destroy
method. There exists a common function, provider/common.Destroy, which providers may use to destroy an environment. This will simply destroy each of the instances and storage in the environment; you must handle handle any other resource cleanup separately.
Easily the most involved part of a provider is dealing with how machine instances are created. There are many facets to this, including:
- constraint validation and matching
- OS image selection
- tagging
- distribution over availability zones
- network configuration
- storage configuration
Machine instances are created via the Environ.StartInstances
method, whose basic goal is to create a machine that:
- matches the requested constraints (e.g. have at least X amount of memory, Y number of CPU cores, Z amount of root disk space);
- runs an OS matching the specified "series" (i.e. an OS release, such as Ubuntu 14.04, or Windows Server 2012);
- can be identified later as being part of the environment;
- can be identified later as being a Juju server or not; and
- can communicate with other machines in the environment
Tagging is typically the preferred mechanism for identifying machines, and Juju provides a set of tags that should be applied to machines when they are created, including tags to identify the environment that the machine is a part of (using the environment's UUID); the Juju ID of the machine; and a special tag for Juju servers.
When a machine instance is created, StartInstance must report back the hardware characteristics to Juju. This enables Juju to properly describe resources in the environment, both for reporting purposes, and also to enable assigning services to machines based on their constraints, i.e. "assign this memory-hungry workload to a machine with a minimum of X amount of memory".
One very important part of machine creation is customising the behaviour of the machine through "user data". Generally we rely on cloud-init to configure machines; Juju provides a base cloud-init configuration to StartInstance; StartInstance can make any additional modifications to it as necessary, and then render it to the appropriate format and include it in the machine creation parameters to the cloud provider. How to do this is very specific to cloud providers.
There are several additional, advanced features which are optional, which we will not describe here:
- placement (provider-specific directives on how/where to allocate machines, e.g. "in zone us-east-1a")
- distribution (distribution of machines across availability zones, for highly available services)
- networking (for managing multiple networks within the environment)
- storage (for requesting additional disks/volumes when allocating a machine)
Destroying instances is achieved via the Environ.StopInstances
method. Note that while the name says "stop", it really means terminate.
A provider must be able to identify machine instances that it has created for an environment (Environ.Instances
and Environ.AllInstances
), and it must be able to identify just the machines that are Juju servers (Environ.StateServerInstances
). In either case, the provider will yield provider-specific implementations of the instance.Instance
interface.
Implementations of instance.Instance
are typically immutable snapshots with accessors for reporting the cloud-provider specific ID (Instance.Id
), the cloud-provider specific machine status (Instance.Status
), and the machine's current set of addresses (Instance.Addresses
). In addition to these, Instance
implementations must provide the OpenPorts
, ClosePorts
, and Ports
methods in order to support the "instance" firewall mode, which is described in the Firewalling section below.
Juju supports two modes of firewalling: global, and instance. Instance-level firewalling is preferable, as this provides the greatest level of granularity; ports are opened and closed at a machine level. The "global" firewalling mode exists for environments where instance-level firewalling is not possible, and applies to all machines in the environment.
To implement instance-level firewalling, the Instance.OpenPorts
, Instance.ClosePorts
and Instance.Ports
methods must be implemented. Otherwise, to support global firewalling, the Environ
methods of the same names must be implemented.
Testing
Releases
Documentation
Development
- READ BEFORE CODING
- Blocking bugs process
- Bug fixes and patching
- Contributing
- Code Review Checklists
- Creating New Repos
-
MongoDB and Consistency
- [mgo/txn Example] (https://github.com/juju/juju/wiki/mgo-txn-example)
- Scripts
- Update Launchpad Dependency
- Writing workers
- Reviewboard Tips
Debugging and QA
- Debugging Juju
- [Faster LXD] (https://github.com/juju/juju/wiki/Faster-LXD)