In this post, you’ll learn how to provision, configure, and orchestrate a PHP application using the AWS OpsWorks application management service into a deployment pipeline using AWS CodePipeline that’s capable of deploying new infrastructure and code changes when developers commit changes to the AWS CodeCommit version-control repository. This way, team members can release new changes to users whenever they choose to do so: aka, Continuous Delivery.

Recently, AWS announced the integration of OpsWorks into AWS CodePipeline so I’ll be describing various components and services that support this solution including CodePipeline along with codifying the entire infrastructure in AWS CloudFormation. As part of the announcement, AWS provided a step-by-step tutorial of integrating OpsWorks with CodePipeline that I used as a reference in automating the entire infrastructure and workflow.

This post describes how to automate all the steps using CloudFormation so that you can click on a Launch Stack button to instantiate all of your infrastructure resources.

OpsWorks

“AWS OpsWorks is a configuration management service that helps you configure and operate applications of all shapes and sizes using Chef. You can define the application’s architecture and the specification of each component including package installation, software configuration and resources such as storage. Start from templates for common technologies like application servers and databases or build your own to perform any task that can be scripted. AWS OpsWorks includes automation to scale your application based on time or load and dynamic configuration to orchestrate changes as your environment scales.” [1] OpsWorks provides a structured way to automate the operations of your AWS infrastructure and deployments with lifecycle events and the Chef configuration management tool. OpsWorks provides more flexibility than Elastic Beanstalk and more structure and constraints than CloudFormation. There are several key constructs that compose OpsWorks. They are:

Stack – An OpsWorks stack is the logical container defining OpsWorks layers, instances, apps and deployments.

– An OpsWorks stack is the logical container defining OpsWorks layers, instances, apps and deployments. Layer – There are built-in layers provided by OpsWorks such as Static Web Servers, Rails, Node.js, etc. But, you can also define your own custom layers as well.

– There are built-in layers provided by OpsWorks such as Static Web Servers, Rails, Node.js, etc. But, you can also define your own custom layers as well. Instances – These are EC2 instances on which the OpsWorks agent has been installed. There are only certain Linux and Windows operating systems supported by OpsWorks instances.

– These are EC2 instances on which the OpsWorks agent has been installed. There are only certain Linux and Windows operating systems supported by OpsWorks instances. App – “Each application is represented by an app, which specifies the application type and contains the information that is needed to deploy the application from the repository to your instances.” [2]

– “Each application is represented by an app, which specifies the application type and contains the information that is needed to deploy the application from the repository to your instances.” [2] Deployment – Runs Chef recipes to deploy the application onto instances based on the defined layer in the stack.

There are also lifecycle events that get executed for each deployment. Lifecycle events are linked to one or more Chef recipes. The five lifecycle events are setup, configure, deploy, undeploy, shutdown. Events get triggered based upon certain conditions. Some events can be triggered multiple times. They are described in more detail below:

setup – When an instance finishes booting as part of the initial setup

– When an instance finishes booting as part of the initial setup configure – When this event is run, it executes on all instances in all layers whenever a new instance comes in service, or an EIP changes, or an ELB is attached

– When this event is run, it executes on all instances in all layers whenever a new instance comes in service, or an EIP changes, or an ELB is attached deploy – When running a deployment on an instance, this event is run

– When running a deployment on an instance, this event is run undeploy – When an app gets deleted, this event is run

– When an app gets deleted, this event is run shutdown – Before an instance is terminated, this event is run

Solution Architecture and Components

In Figure 2, you see the deployment pipeline and infrastructure architecture for the OpsWorks/CodePipeline integration.



OpsWorks – In this stack, code configures operations of your infrastructure using lifecycle events and Chef

– In this stack, code configures operations of your infrastructure using lifecycle events and Chef CodePipeline – Orchestrate all actions in your software delivery process. In this solution, I provision a CodePipeline pipeline with two stages and one action per stage in CloudFormation

– Orchestrate all actions in your software delivery process. In this solution, I provision a CodePipeline pipeline with two stages and one action per stage in CloudFormation CloudFormation – Automates the provisioning of all AWS resources. In this solution, I’m using CloudFormation to automate the provisioning for OpsWorks, CodePipeline, IAM, and S3

– Automates the provisioning of all AWS resources. In this solution, I’m using CloudFormation to automate the provisioning for OpsWorks, CodePipeline, IAM, and S3 CodeCommit – A Git repo used to host the sample application code from this solution

– A Git repo used to host the sample application code from this solution PHP – In this solution, I leverage AWS’ OpsWorks sample application written in PHP.

– In this solution, I leverage AWS’ OpsWorks sample application written in PHP. IAM – The CloudFormation stack defines an IAM Instance Profile and Roles for controlled access to AWS resources

– The CloudFormation stack defines an IAM Instance Profile and Roles for controlled access to AWS resources EC2 – A single compute instance is launched as part of the configuration of the OpsWorks stack

– A single compute instance is launched as part of the configuration of the OpsWorks stack S3 – Hosts the deployment artifacts used by CodePipeline.

Create and Connect to a CodeCommit Repository

Both OpsWorks and CodePipeline are defined in a single CloudFormation stack, which is described in more detail later in this post. Here are the key services and tools that make up the solution:

While you can store your software code in any version-control repository, in this solution, I’ll be using the AWS CodeCommit Git repository. I’ll be integrating CodeCommit with CodePipeline. I’m basing the code off of the Amazon OpsWorks PHP Simple Demo App located at https://github.com/awslabs/opsworks-demo-php-simple-app.

To create your own CodeCommit repo, follow these instructions: Create and Connect to an AWS CodeCommit Repository. I called my CodeCommit repository opsworks-php-demo. You can call it the same but if you do name it something different, be sure to replace the samples with your repo name.

After you create your CodeCommit repo, copy the contents from the AWS PHP OpsWorks Demo app and commit all of the files.

Implementation

I created this sample solution by stitching together several available resources including the CloudFormation template provided by the Step-by-Step Tutorial from AWS on integrating OpsWorks with CodePipeline and existing templates we use at Stelligent for CodePipeline. Finally, I manually created the pipeline in CodePipeline using the same step-by-step tutorial and then obtained the configuration of the pipeline using the get-pipeline command as shown in the command snippet below.

aws codepipeline get-pipeline --name OpsWorksPipeline > pipeline.json

This section describes the various resources of the CloudFormation solution in greater detail including IAM Instance Profiles and Roles, the OpsWorks resources, and CodePipeline.

Security Group

Here, you see the CloudFormation definition for the security group that the OpsWorks instance uses. The definition restricts the ingress port to 80 so that only web traffic is accepted on the instance.

"CPOpsDeploySecGroup":{ "Type":"AWS::EC2::SecurityGroup", "Properties":{ "GroupDescription":"Lets you manage OpsWorks instances deployed to by CodePipeline" } }, "CPOpsDeploySecGroupIngressHTTP":{ "Type":"AWS::EC2::SecurityGroupIngress", "Properties":{ "IpProtocol":"tcp", "FromPort":"80", "ToPort":"80", "CidrIp":"0.0.0.0/0", "GroupId":{ "Fn::GetAtt":[ "CPOpsDeploySecGroup", "GroupId" ] } } },

IAM Role

Here, you see the CloudFormation definition for the OpsWorks instance role. In the same CloudFormation template, there’s a definition for an IAM service role and an instance profile. The instance profile refers to OpsWorksInstanceRole defined in the snippet below.

The roles, policies and profiles restrict the service and resources to the essential permissions it needs to perform its functions.

"OpsWorksInstanceRole":{ "Type":"AWS::IAM::Role", "Properties":{ "AssumeRolePolicyDocument":{ "Statement":[ { "Effect":"Allow", "Principal":{ "Service":[ { "Fn::FindInMap":[ "Region2Principal", { "Ref":"AWS::Region" }, "EC2Principal" ] } ] }, "Action":[ "sts:AssumeRole" ] } ] }, "Path":"/", "Policies":[ { "PolicyName":"s3-get", "PolicyDocument":{ "Version":"2012-10-17", "Statement":[ { "Effect":"Allow", "Action":[ "s3:GetObject" ], "Resource":"*" } ] } } ] } },

Stack

The snippet below shows the CloudFormation definition for the OpsWorks Stack. It makes references to the IAM service role and instance profile, using Chef 11.10 for its configuration, and using Amazon Linux 2016.03 for its operating system. This stack is used as the basis for defining the layer, app, instance, and deployment that are described later in this section.

"MyStack":{ "Type":"AWS::OpsWorks::Stack", "Properties":{ "Name":{ "Ref":"AWS::StackName" }, "ServiceRoleArn":{ "Fn::GetAtt":[ "OpsWorksServiceRole", "Arn" ] }, "ConfigurationManager":{ "Name":"Chef", "Version":"11.10" }, "DefaultOs":"Amazon Linux 2016.03", "DefaultInstanceProfileArn":{ "Fn::GetAtt":[ "OpsWorksInstanceProfile", "Arn" ] } } },

Layer

The OpsWorks PHP layer is described in the CloudFormation definition below. It references the OpsWorks stack that was previously created in the same template. It also uses the php-app layer type. For a list of valid types, see CreateLayer in the AWS API documentation. This resource also enables auto healing, assigns public IPs and references the previously-created security group.

"MyLayer":{ "Type":"AWS::OpsWorks::Layer", "Properties":{ "StackId":{ "Ref":"MyStack" }, "Name":"MyLayer", "Type":"php-app", "Shortname":"mylayer", "EnableAutoHealing":"true", "AutoAssignElasticIps":"false", "AutoAssignPublicIps":"true", "CustomSecurityGroupIds":[ { "Fn::GetAtt":[ "CPOpsDeploySecGroup", "GroupId" ] } ] }, "DependsOn":[ "MyStack", "CPOpsDeploySecGroup" ] },

OpsWorks Instance

In the snippet below, you see the CloudFormation definition for the OpsWorks instance. It references the OpsWorks layer and stack that are created in the same template. It defines the instance type as c3.large and refers to the EC2 Key Pair that you will provide as an input parameter when launching the stack.

"MyInstance":{ "Type":"AWS::OpsWorks::Instance", "Properties":{ "LayerIds":[ { "Ref":"MyLayer" } ], "StackId":{ "Ref":"MyStack" }, "InstanceType":"c3.large", "SshKeyName":{ "Ref":"KeyName" } } },

OpsWorks App

In the snippet below, you see the CloudFormation definition for the OpsWorks app. It refers to the previously created OpsWorks stack and uses the current stack name for the app name – making it unique. In the OpsWorks type, I’m using php. For other supported types, see CreateApp.

I’m using other for the AppSource type (OpsWorks doesn’t seem to make the documentation obvious in terms of the types that AppSource supports, so I resorted to using the OpsWorks console to determine the possibilities). I’m using other because my source type is CodeCommit, which isn’t currently an option in OpsWorks.

"MyOpsWorksApp":{ "Type":"AWS::OpsWorks::App", "Properties":{ "StackId":{ "Ref":"MyStack" }, "Type":"php", "Shortname":"phptestapp", "Name":{ "Ref":"AWS::StackName" }, "AppSource":{ "Type":"other" } } },

CodePipeline

In the snippet below, you see the CodePipeline definition for the Deploy stage and the DeployPHPApp action in CloudFormation. It takes MyApp as an Input Artifact – which is an Output Artifact of the Source stage and action that obtains code assets from CodeCommit.

The action uses a Deploy category and OpsWorks as the Provider. It takes four inputs for the configuration: StackId, AppId, DeploymentType, LayerId. With the exception of DeploymentType, these values are obtained as references from previously created AWS resources in this CloudFormation template.

For more information, see CodePipeline Concepts.

{ "Name":"Deploy", "Actions":[ { "InputArtifacts":[ { "Name":"MyApp" } ], "Name":"DeployPHPApp", "ActionTypeId":{ "Category":"Deploy", "Owner":"AWS", "Version":"1", "Provider":"OpsWorks" }, "OutputArtifacts":[ ], "Configuration":{ "StackId":{ "Ref":"MyStack" }, "AppId":{ "Ref":"MyOpsWorksApp" }, "DeploymentType":"deploy_app", "LayerId":{ "Ref":"MyLayer" } }, "RunOrder":1 } ] }

Launch the Stack

Click the button below to launch a CloudFormation stack that provisions the OpsWorks environment including all the resources previously described such as CodePipeline, OpsWorks, IAM Roles, etc.

When launching a stack, you’ll enter a value the KeyName parameter from the drop down. Optionally, you can enter values for your CodeCommit repository name and branch if they are different than the default values.





aws cloudformation create-stack --stack-name OpsWorksPipelineStack --template-url https://s3.amazonaws.com/stelligent-training-public/public/codepipeline/codepipeline-opsworks.json --region us-east-1 --disable-rollback --capabilities="CAPABILITY_IAM" --parameters ParameterKey=KeyName,ParameterValue=YOURKEYNAME

Outputs

You will charged for your AWS usage – particularly EC2, CodePipeline and S3.To launch the same stack from your AWS CLI, type the following (while modifying the same parameter values described above):

Once the CloudFormation stack successfully launches, there’s an output for the CodePipelineURL. You can click on this value to launch the pipeline that’s running that’s getting the source assets from CodeCommit and launch an OpsWorks stack and associated resources. See the screenshot below.



Commit Changes to CodeCommit

Once the pipeline is complete, you can access the OpsWorks stack and click on thelink for one of the instances to launch the PHP application that was deployed using OpsWorks as shown in Figures 5 and 6 below.

Make some visual changes to the code (e.g. your local CodeCommit version of index.php) and commit these changes to your CodeCommit repository to see these software get deployed through your pipeline. You perform these actions from the directory where you cloned a local version of your CodeCommit repo (in the directory created by your git clone command). Some example command-line operations are shown below.

git commit -am "change color to rust orange" git push

Once these changes have been committed, CodePipeline will discover the changes made to your CodeCommit repo and initiate a new pipeline. After the pipeline is successfully completed, follow the same instructions for launching the application from your browser – as shown in Figure 7.



Sample Code

The code for the examples demonstrated in this post are located at https://github.com/stelligent/cloudformation_templates/tree/master/labs/opsworks. Let us know if you have any comments or questions @stelligent or @paulduvall.

Stelligent is hiring! Do you enjoy working on complex problems like figuring out ways to automate all the things as part of a deployment pipeline? Do you believe in the “everything-as-code” mantra? If your skills and interests lie at the intersection of DevOps automation and the AWS cloud, check out the careers page on our website.

Useful Resources and References

OpsWorks Reference

Below, I’ve documented some additional information that might be useful on the OpsWorks service itself including its available integrations, supported versions and features.

OpsWorks supports three application source types: GitHub, S3, and HTTP.

You can store up to five versions of an OpsWorks application: the current revision plus four more for rollbacks.

When using the create-deployment method, you can target the OpsWorks stack, app, or instance

OpsWorks require internet access for the OpsWorks endpoint instance

Chef supports Windows in version 12

You cannot mix Windows and Linux instances in an OpsWorks stack

To change the default OS in OpsWorks, you need to change the OS and reprovision the instances

You cannot change the VPC for an OpsWorks instance

You can add ELB, EIPs, Volumes and RDS to an OpsWorks stack

OpsWorks autoheals at the layer level

You can assign multiple Chef recipes to an OpsWorks layer event

The three instance types in OpsWorks are: 24/7, time-based, load-based

To initiate a rollback in OpsWorks, you use create-deployment command

The following commands are available when using OpsWorks create-deployment along with possible use cases: install_dependencies update_dependencies – Patches to the Operating System. Not available after Chef 12. update_custom_cookbooks – pulling down changes in your Chef cookbooks execute_recipes – manually run specific Chef recipes that are defined in your layers configure – service discovery or whenever endpoints change setup deploy rollback start stop restart undeploy

To enable the use of multiple custom cookbook repositories in OpsWorks, you can enable custom cookbook at the stack and then create a cookbook that has a Berkshelf file with multiple sources. Before Chef 11.10, you couldn’t use multiple cookbook repositories.

You can define Chef databags in OpsWorks Users, Stacks, Layers, Apps and Instances

OpsWorks Auto Healing is triggered when an OpsWorks Agent detects loss of communication and stops, then restarts the instances. If it fails, it goes into manual intervention

OpsWorks will not auto heal an upgrade to the OS

OpsWorks does not auto heal by monitoring performance, only failures.

Acknowledgements

My colleague Casey Lee provided some of the background information on OpsWorks features. I also used several resources from AWS including the PHP sample app and the step-by-step tutorial on the OpsWorks/CodePipeline integration.





