10 Building an Azure Web App Chaos Extension with Node.JS
Gavin Bauman редактировал(а) эту страницу 2018-07-11 16:27:26 -04:00

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

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. manage screenshot

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!