Defining Chaos Extensions
Chaos extensions are meant to be small, modular components built to start and stop chaotic events. They're the pieces of logic that directly interact with targeted cloud resources.
Extensions as Azure Functions
Today, chaos extensions are designed to be lightweight Azure Functions that may be run via any of their offered triggers (http, timers, ad hoc, and various Azure-supported sources). Today platform-chaos-cli uses the http
trigger, but you could imagine a scenario where-in different tooling leverages different triggers.
Let's take a look at how to build such an extension, leveraging this technology stack.
Prerequisites
- Node.js and npm installed
- The Azure CLI 2.0 tool
- An Azure Subscription
- A running Web App Service
- An Azure Storage Account
Getting Started: Building the WebApp Chaos Extension Locally
We'll build a simple chaos extension that targets an Azure Web App. There will be two JavaScript files here: one that starts the chaotic event (shuts down a web app) and one that stops the chaotic event (restarts the web app).
To start, let's create two folders in your root project directory - one labeled start
and the other labeled stop
:
cd your/project/directory
mkdir start
mkdir stop
Please also run the npm init
command to set up your project's package.json
file.
Package Dependencies
In order to get this extension running locally as an Azure Function, we'll need to install the azure-functions-core-tools
npm package. The npm package can be found here. We'll install it globally, so we can use it's command line form, func
in the steps below.
npm i -g azure-functions-core-tools
Once that's completed, we can execute the following command to define the Azure Functions project and create a local git repository:
func init
This adds host.json
and local.settings.json
which are both required to define an Azure Functions project.
We also recommend installing the azure-chaos-fn
package. It offers helpers that allow extension authors to more quickly and easily build their own extensions without concerning themselves with the communication details with the orchestrator. The npm package page can be found here.
npm i --save azure-chaos-fn
Additionally, we'll need a means of interacting with the Azure Web App Service. This functionality has been wrapped up in a npm package as well:
npm i --save azure-arm-website
Finally, we'll need the tool to invoke the chaos extension we're going to build. This currently exists as a CLI tool that registers extensions, manages the Azure Subscription, and invokes the extension from the command line. We'll install it globally, so we can use it's command line form, chaos
in the steps below.
npm install --global platform-chaos-cli
Starting the Chaotic Event
In your start
folder, create a new index.js
file. This is where we'll build the part of the extension that induces chaos upon an Azure Web App. In the file, we'll need to reference the two dependencies we installed: the azure-chaos-fn
package to manage the token and the Azure resource and the azure-arm-website
package to interact with the specified web app.
const chaosFnUtility = require('azure-chaos-fn');
const webSiteManagementClient = require('azure-arm-website');
Next, we'll need a function that explicitly stops a web app in a given Azure subscription and resource group using the webSiteManagementClient
.
function stopWebSite(credential, subscriptionId, resourceGroupName, resourceName, logger) {
const client = new webSiteManagementClient(credential, subscriptionId);
logger(`Stopping web app ${resourceName} in resource group ${resourceGroupName}`);
return client.webApps.stop(
resourceGroupName,
resourceName
);
}
The parameters this function requires can be satisfied by using the chaosFnUtility
we declared earlier - learn more about these utilities in the module documentation.
Here, we'll use the accessTokenToCredentials
and resourcesToObjects
functions as helpers to give us objects containing the proper resource and credential information. Since resourcesToObjects
returns a collection of objects, we map those to the relevant parameters in the stopWebSite
function we created.
module.exports = function (context, req) {
context.log('Beginning start of chaos event');
context.log('Stopping websites');
const credential = chaosFnUtility.parsers.accessTokenToCredentials(req);
const resources = chaosFnUtility.parsers.resourcesToObjects(req);
Promise.all(resources.map(resource => stopWebSite(
credential,
resource.subscriptionId,
resource.resourceGroupName,
resource.resourceName,
context.log
)))
.then(() => {
context.log('Completed stopping websites');
context.done();
})
.catch(err => {
context.log('Error stopping websites');
context.log(err);
context.done();
});
};
Stopping the Chaotic Event
The process to build an extension to stop a chaotic event is virtually the same, but with one key change. In this case, we would create an index.js
file in the stop
folder and instead would call the webApps.start
function. It's a bit confusing to think calling a start
function actually stops an event, but in this case, we're restarting our web app, thus ending the chaotic event. Be sure to add another function.json
file (the same one above) in the stop
folder as well as it is another function. Find the stop
component of the extension below:
const chaosFnUtility = require('azure-chaos-fn');
const webSiteManagementClient = require('azure-arm-website');
function startWebSite(credential, subscriptionId, resourceGroupName, resourceName, logger) {
const client = new webSiteManagementClient(credential, subscriptionId);
logger(`Starting web app ${resourceName} in resource group ${resourceGroupName}`);
return client.webApps.start(
resourceGroupName,
resourceName
);
}
module.exports = function (context, req) {
context.log('Beginning stop of chaos event');
context.log('Starting websites');
const credential = chaosFnUtility.parsers.accessTokenToCredentials(req);
const resources = chaosFnUtility.parsers.resourcesToObjects(req);
Promise.all(resources.map(resource => startWebSite(
credential,
resource.subscriptionId,
resource.resourceGroupName,
resource.resourceName,
context.log
)))
.then(() => {
context.log('Completed starting websites');
context.done();
})
.catch(err => {
context.log('Error starting websites');
context.log(err);
context.done();
});
};
And we've done it! We've just written a chaos extension! Find the full source file here.
Running the Extension Locally
In order to run this extension on a local machine, we'll make use of the azure-functions-core-tools
to host our extension and the platform-chaos-cli
tool to invoke it.
Starting the Function Runtime
In order to run the extension locally, we need to configure the Azure Functions runtime to be able to do so.
We start by creating a function.json
file in the start
folder of our project. This file tells the Functions runtime that the contents represent a singular Azure Function as well as what sort of trigger will invoke it. We'll define ours as a httpTrigger
. Paste this in each function.json
files:
{
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
],
"disabled": false
}
We're now ready to host it! From here, run the func host start
command in your terminal. You should see a good bit of output, but what we're interested in is the final few lines that should look like the following:
Http Functions:
start: http://localhost:7071/api/start
Registering the Extension with the CLI Tool
We'll need to use the uri from the step above in order to register our chaos extension with the platform-chaos-cli
tool. In another terminal, we'll need to run the chaos register
command with the flags name
, uri
, and description
respectively with the uri
flag being the output from your Azure Function. Make sure the start
folder is not in your uri path. Your command should resemble the following:
chaos register ExtensionName http://localhost:7071/api "chaos extension for a web app"
We can confirm the extension has been registered by running chaos list
.
Invoking the Extension
Note: You'll need an Azure Subscription and a running Web App Service for this part.
Now for the fun part! The platform-chaos-cli
tool comes with 2 registered commands baked in, start
, and stop
. Running the start
command targets the start
folder in our Azure Function project. In order to give the CLI tool an Azure resource to target, we pass it in the following format:
chaos start extensionName --resources "subscriptionId/resGroupName/resName"
You may optionally add an --accessToken
flag as well, otherwise you will be interactively logged in.
And there we have it! If you visit the URL of the Azure Web App you targeted, you should be greeted with a lovely 403 error page. You can also view the log statements detailing the function's completion in your terminal.
Publishing Extensions to Azure
In order to publish the extension we just made, we'll have to create an Azure Function in Azure with the same name we intend to deploy with. While you can do this in the portal, this walkthrough will accomplish this with the Azure CLI 2.0 tool. A Function App requires a storage account and resource group in order to be made. A sample script that creates them as well as the Azure Function App can be found below:
#!/bin/bash
# Create resource group
az group create --name myResourceGroup --location westeurope
# Create an azure storage account
az storage account create \
--name myconsumptionstore \
--location westeurope \
--resource-group myResourceGroup \
--sku Standard_LRS
# Create Function App
az functionapp create \
--name myconsumptionfunc \
--storage-account myconsumptionstore \
--consumption-plan-location westeurope \
--resource-group myResourceGroup
Source: https://docs.microsoft.com/en-us/azure/azure-functions/scripts/functions-cli-create-serverless
Now that the Azure Function App has been created, we're ready to deploy our local function to it. Simply run the following command, making sure to match the name of the function with the name of the Function App Service in Azure.
func azure functionapp publish <FunctionAppName>
It may take a few minutes for the upload to complete, but once it does, we can confirm the function was successfully published by checking in the Azure portal. In order to access this app from the CLI tool, we'll need to update it with the uri of the newly published Function App. Don't forget to append "/api" in your uri - it's added by default to all hosted Azure Function Apps!
chaos register NewExtensionName http://YourSiteName.azurewebsites.net/api "Description of the extension"
There's one more step before we're able to access this Azure Function App. By default, you'll have to pass in the key of the app. You can find the key in the "Manage" section of your Function App in the portal.
When you have that, you can run the chaos start
command with the provided key
flag.
chaos start ExtensionName --resources "subscriptionId/resGroupName/resGroup" --key "keygoeshere"
Run that command and your targeted web app should shut down! Success!