AWS DevOps Blog

Using AWS OpsWorks to Customize and Automate App Deployment on Windows

Using OpsWorks and Chef on Windows helps you optimize your use of Windows by reliably automating configuration tasks to enforce instance compliance. Automating instance configuration enables software engineering best practices, like code-reviews and continuous integration, and allows smaller, faster delivery than with manual configuration. With automated instance configuration, you depend less on golden images or manual changes. OpsWorks also ships with features that ease operational tasks, like user management or scaling your infrastructure by booting additional machines.

In this post, I show how you can use OpsWorks to customize your instances and deploy your apps on Windows. To show how easy application management is when using OpsWorks, we will deploy a Node.JS app to Microsoft Internet Information Server (IIS). You can find both the cookbooks and the app source code in the Amazon Web Services – Labs repository on GitHub.

To follow this example, you need to understand how OpsWorks uses recipes and lifecycle events. For more information, see What is AWS OpsWorks?.

Create the Stack and Instance

First, let’s create a stack in the OpsWorks console. Navigate to the Add Stack page at https://console.aws.amazon.com/opsworks/home?#/stack/new.

For the name, type Node.JS Windows Demo. For the Default operating system, choose Microsoft Windows Server 2012 R2 Base.

Next, configure the cookbook source. Choose Advanced to display the Configuration Management section. Enable Use custom Chef cookbooks, choose the Git version control system as the Repository type, and enter https://github.com/awslabs/opsworks-windows-demo-cookbooks.git as the Repository URL. The cookbooks that we just configured describe how a Node.JS app is installed.

Choose Add Stack to create the stack.

In the Layers section, choose Add a Layer. Choose any name and short name you like. For Security Groups, choose AWS-OpsWorks-Web-Server as an additional security group. This ensures that HTTP traffic is allowed and you can connect to the demo app with your browser. For this example, you don’t need RDP access, so you can safely ignore the warning. Choose Add Layer.

Before we add instances to this layer, we have to wire up the Chef code with the lifecycle events. To do that, edit the layer recipes. On the Layers page, under more layers, choose Recipes. On the Layer more layers page, choose Edit. For the Setup lifecycle event, choose webserver_nodejs::setup, and for the Deploy event, choose webserver_nodejs::deploy.

Confirm your changes by choosing Save.

Now we are ready to add an instance. Switch to the Instances page by choosing Instances, and then choose Add an Instance. OpsWorks suggests a Hostname based on the layer name; for this example, webserver-node1. Choose Add Instance to confirm.

Don’t start the instance yet. We need to tell OpsWorks about the app that we want to deploy first. This ensures that the app is deployed when the instance starts. (When an instance executes the Setup event during boot, OpsWorks deploys apps automatically. Later, you can deploy new and existing apps to any running instances.)

Create the App

In the left pane, choose Apps to switch to the Apps page, and then choose Add an app. Give the app a name, choose Git as the Repository type, and enter https://github.com/awslabs/opsworks-windows-demo-nodejs.git as the Repository URL. The demo app supports environment variables, so you can use the APP_ADMIN_EMAIL key to set the mail address displayed on the demo app’s front page. Use any value you like.

To save the app, choose Add App. Return to the Instances page. Now start the instance.

OpsWorks reports setup progress, switching from “requested” to “pending,” and then to “booting.” When the instance is “booting,” it is running on Amazon EC2. After the OpsWorks agent is installed and has picked up its first lifecycle event, the status changes to “running_setup.” After OpsWorks processes the Setup lifecycle event, it shows the instance as “online.” When an instance reaches the online state, OpsWorks fires a Configure lifecycle event to inform all instances in the stack about the new instance.

Booting a new instance can take a few minutes, the total time depending on the instance size and your Chef recipes. While you wait, get a cup of coffee or tea. Then, let’s take a look at the code that Chef will execute and the general structure of the opsworks-windows-example-cookbooks.

Cookbook Deep Dive – Setup Event

Let’s take a look at the webserver_nodejs::setup recipe, which we wired to the Setup event when we chose layer settings:

# Recipes to install software on initial boot

include_recipe "opsworks_iis"
include_recipe "opsworks_nodejs"
include_recipe "opsworks_iisnode"

This recipe simply includes other recipes. As you can see, Chef installs the IIS, Node.JS, and the IIS-to-Node.JS bridge iisnode. By taking a closer look at the opsworks_nodejs cookbook, we can learn how to install apps. The general folder structure of a Chef cookbook is:

opsworks_nodejs
├── README.md
├── attributes
│   └── default.rb
├── definitions
│   └── opsworks_nodejs_npm.rb
├── metadata.rb
└── recipes
    └── default.rb

The opsworks_nodejs cookbook uses attributes to define settings that you can override when using the cookbook. This makes it easy to update to a new version of Node.JS or npm, the node package manager, by just setting the attribute to another value.

The file opsworks_nodejs/attributes/default.rb defines these version attributes:

default["opsworks_nodejs"]["node_version"] = "0.12.7"
default["opsworks_nodejs"]["npm_version"] = "2.13.0"

The default recipe in the opsworks_nodejs cookbook uses the node_version and npm_version. It uses node_version as part of the download URL construction and npm_version in the batch code.

version = node["opsworks_nodejs"]["node_version"]

download_file = "node-v#{version}-x86.msi"
download_path = ::File.join(Chef::Config["file_cache_path"], download_file)

remote_file download_path do
  source "https://nodejs.org/dist/v#{version}/#{download_file}"
  retries 2
end

windows_package "nodejs" do
  source download_path
end

batch "install npm version #{node['opsworks_nodejs']['npm_version']}" do
  code ""%programfiles(x86)%\nodejs\npm" -g install npm@#{node['opsworks_nodejs']['npm_version']}"
end

The cookbook installs Node.JS in two steps. First, it uses the Chef remote_file resource to download the installation package from the official Node.JS website and save it to the local disk. The cookbook also sets the retries attribute to enable retries, so the code is more resilient to short-term networking issues.

After the cookbook saves the file, the windows_package resource installs the MSI. Then, the cookbook installs the requested npm version using the batch resource.

Chef resources provide many attributes for fine-tuning their behavior. For more information, see the Chef Resources Reference.

Cookbook Deep Dive – Deploy Event

As I mentioned, OpsWorks doesn’t prepopulate your Chef run with recipes or resources. This gives you fine-grained control and complete flexibility over how to deploy your app. However, there are some common tasks, like checking your app out of Git or downloading it from Amazon S3. To make performing common tasks easier, the example cookbooks ship with a custom Chef resource that handles these steps for you, opsworks_scm_checkout.

As it does with the Setup recipe, OpsWorks uses the webserver_nodejs::deploy recipe only to include other recipes. The opsworks_app_nodejs cookbook’s default recipe does the heavy lifting.

The slimmed-down version of the recipe looks like the following.

apps = search(:aws_opsworks_app, "deploy:true")

apps.each do |app|
  opsworks_scm_checkout app["shortname"] do
    ...
  end

  directory app_deploy do
     ...
  end

  # Copy app to deployment directory
  batch "copy #{app["shortname"]}" do
    code "Robocopy.exe ..."
  end

  # Run 'npm install'
  opsworks_nodejs_npm app["shortname"] do
    cwd app_deploy
  end

  template "#{app['shortname']}/web.config" do
    ...
    variables({ :environment => app["environment"]})
  end

  powershell_script "register #{app["shortname"]}" do
    ...
  end
end

Reviewing the code will help you understand how to write your own deployment cookbooks, so let’s walk through it. First, we use Chef search to fetch information about the apps to be deployed. The AWS OpsWorks User Guide lists all exposed attributes.

For each app, Chef then executes the following steps:

·      It checks out the app to the local file system using the opsworks_scm_checkout resource.

·      It creates the target directory and copies the app to that directory. To transfer only the files that have changed, it uses Robocopy.

·      Running npm install, it downloads all required third-party libraries. It does this by using the opsworks_nodejs_npm custom resource, which is defined in the opsworks_nodejs cookbook.

·      The web.config file is generated using a template, taking OpsWorks environment variables into account. The file configures the IIS-to-Node.JS integration.

·      A Windows PowerShell run registers the IIS site and brings the app online.

Check Out the Node.JS Demo App

Your booted instance should be online now:

To access the demo app, under Public IP, click the IP address. The app allows you to leave comments that other visitors can see.

Notice that the page is not static, but includes information about the hostname, the browser you used to request it, the system time, and the Node.JS version used. At the bottom of the page, you can see that the APP_ADMIN_EMAIL environment variable you configured in the app was picked up.

Leave a comment on the app, and choose Send. Because this is a minimal app for demo purposes only, the comment is saved on the instance in a plain JSON file.

To see the details of the customizations OpsWorks just applied to your instance, choose the hostname on the Layer overview page. On the very bottom of the Instance detail page, you will find the last 30 commands the instance received. To see the corresponding Chef log, choose show.

Conclusion

Using OpsWorks with Chef 12 on Windows gives you a reliable framework for customizing your Windows Server 2012 R2 instances and managing your apps. By attaching Chef recipes to lifecycle events, you can customize your instances. You can use open source community cookbooks or create your own cookbooks. Chef’s support for PowerShell allows you to reuse automation for Windows. OpsWorks’ built-in operational tools, like user management, permissions, and temporary RDP access help you manage day-to-day operations.

The example showed how to deploy a Node.JS app using IIS. By referring to typical cookbooks that were included in the example like opsworks_nodejs, which is used to install Node.JS, or opsworks_app_nodejs, which is used to deploy the app, you can learn how to write your own cookbooks.