Hey all!
This is a post from the Deployment Templates series, you must have seen the amazing posts that @rohan and @lukehertert did about Google Cloud Run and Google Cloud Functions, and today I will be walking you all through a AWS Elastic Beanstalk Deployment in Harness
In this post you can learn how to create Deployment Templates step by step, but if you want to go fast, in my Github repo you can find everything ready to import to your Harness Account using our Config As Code via Git Sync and you can find a script that uploads the templates automatically for you as well
Yes, Deployment Template is a cool feature that allow you to build your own deployment types that works as native, leveraging all the amazing features from Harness, such as automated rollbacks, reporting, security, verification and many others.
Let’s go!
Pre-Requisites
- Permissions to the Template Library
- Permissions to perform Workflow, Service, and Environment Creation
- Permission to Manage Delegate Profiles
- Deployment Templates need to be enabled (the feature is in Beta)
- EC2 Delegate with AWS CLI and jq installed
1. Creating the Template
Navigate to the Harness Template Library, and Select “Add Template” and click and click on Deployment Template Type
2. Configure the Deployment Template Type Form
- We will be configuring how Harness will map to the user’s infrastructure and query for deployed instances on the configured infrastructure. In this case we are fetching the EC2 instances for the environment we are going to deploy.
- User should configure the Display Name
- Any variables needed for Infrastructure, here we have the EB EnvironmentName, Region, and the STSRole if you want to assume other IAM Role to deploy to other account for example.
- In the Fetch Instance field, we will provide the script to collect the instances deployed by Harness on the target infrastructure
- The user will need to provide a Host Array path, this is the path in the JSON to the host object we are looking for.
- Here we are fetching each EC2 instance active for the Beanstalk Environment. This helps not only to use it in the workflow, but also on the Service Instance reporting.
export AWS_DEFAULT_REGION=${infra.custom.vars.Region}
AWS_STS_ROLE=${infra.custom.vars.stsRole}
NAME="Harness-Assume-Role"
if [ ! -z "$AWS_STS_ROLE" ]; then
echo "Assuming STS Role..."
#Cleanup current sessions
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_SECURITY_TOKEN
unset AWS_ACCESS_KEY AWS_SECRET_KEY AWS_DELEGATION_TOKEN
KST=(`aws sts assume-role --role-arn "$AWS_STS_ROLE" \
--role-session-name "$NAME" \
--query '[Credentials.AccessKeyId,Credentials.SecretAccessKey,Credentials.SessionToken]' \
--output text`)
export AWS_ACCESS_KEY_ID=${KST[0]}
export AWS_SECRET_ACCESS_KEY=${KST[1]}
export AWS_SESSION_TOKEN=${KST[2]}
else
echo "Skipping STS AssumeRole..."
fi
OUTPUT=`aws elasticbeanstalk describe-instances-health --environment-name=${infra.custom.vars.EnvironmentName}`
echo ${OUTPUT}
cat > $INSTANCE_OUTPUT_PATH <<EOF
${OUTPUT}
EOF
This is how the template spec looks like:
3. Configure the Service Commands
Now that we have the Deployment Specification, we need to define how we deploy the services in AWS Beanstalk. Here we have a template with a few commands:
- Prepare (Just a few validation commands)
- Create Version (Create a new version in your EB Environment)
- Update environment (Activate your new version)
- Steady State Check (Check if your environment
Here are the step contents:
Prepare
export AWS_DEFAULT_REGION=${infra.custom.vars.Region}
AWS_STS_ROLE=${infra.custom.vars.stsRole}
NAME="Harness-Assume-Role"
export VERSION_LABEL=${VERSION}
if [ ! -z "$AWS_STS_ROLE" ]; then
echo "Assuming STS Role..."
# Unset existing AWS session keys
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_SECURITY_TOKEN
unset AWS_ACCESS_KEY AWS_SECRET_KEY AWS_DELEGATION_TOKEN
KST=(`aws sts assume-role --role-arn "$AWS_STS_ROLE" \
--role-session-name "$NAME" \
--query '[Credentials.AccessKeyId,Credentials.SecretAccessKey,Credentials.SessionToken]' \
--output text`)
export AWS_ACCESS_KEY_ID=${KST[0]}
export AWS_SECRET_ACCESS_KEY=${KST[1]}
export AWS_SESSION_TOKEN=${KST[2]}
else
echo "Skipping STS AssumeRole..."
fi
Create Version
export AWS_DEFAULT_REGION=${infra.custom.vars.Region}
AWS_STS_ROLE=${infra.custom.vars.stsRole}
NAME="Harness-Assume-Role"
export VERSION_LABEL=${VERSION}
if [ ! -z "$AWS_STS_ROLE" ]; then
echo "Assuming STS Role..."
# Unset existing AWS session keys
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_SECURITY_TOKEN
unset AWS_ACCESS_KEY AWS_SECRET_KEY AWS_DELEGATION_TOKEN
KST=(`aws sts assume-role --role-arn "$AWS_STS_ROLE" \
--role-session-name "$NAME" \
--query '[Credentials.AccessKeyId,Credentials.SecretAccessKey,Credentials.SessionToken]' \
--output text`)
export AWS_ACCESS_KEY_ID=${KST[0]}
export AWS_SECRET_ACCESS_KEY=${KST[1]}
export AWS_SESSION_TOKEN=${KST[2]}
else
echo "Skipping STS AssumeRole... Will use the current IAM role."
fi
VERSION_EXISTS=`aws elasticbeanstalk describe-application-versions --application-name=${service.name} --version-labels=${VERSION_LABEL} | jq -r '.ApplicationVersions' | jq length`
if [ $VERSION_EXISTS -gt 0 ]; then
echo "Version already exists, Harness skipping this step..."
else
echo "Creating EB app version ${VERSION_LABEL} in EB app \"${service.name}\" on region ${AWS_DEFAULT_REGION}"
aws elasticbeanstalk create-application-version --application-name "${service.name}" --description "Version created by ${deploymentTriggeredBy} on Harness." \
--version-label "${VERSION_LABEL}" --source-bundle S3Bucket=${artifact.bucketName},S3Key=${artifact.artifactPath}
fi
Update Environment
export AWS_DEFAULT_REGION=${infra.custom.vars.Region}
AWS_STS_ROLE=${infra.custom.vars.stsRole}
NAME="Harness-Assume-Role"
export VERSION_LABEL=${VERSION}
if [ ! -z "$AWS_STS_ROLE" ]; then
echo "Assuming STS Role..."
# Unset existing AWS session keys
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_SECURITY_TOKEN
unset AWS_ACCESS_KEY AWS_SECRET_KEY AWS_DELEGATION_TOKEN
KST=(`aws sts assume-role --role-arn "$AWS_STS_ROLE" \
--role-session-name "$NAME" \
--query '[Credentials.AccessKeyId,Credentials.SecretAccessKey,Credentials.SessionToken]' \
--output text`)
export AWS_ACCESS_KEY_ID=${KST[0]}
export AWS_SECRET_ACCESS_KEY=${KST[1]}
export AWS_SESSION_TOKEN=${KST[2]}
else
echo "Skipping STS AssumeRole..."
fi
# See if EB_APP_VERSION is in the EB app
NB_VERS=`aws elasticbeanstalk describe-applications --application-name "${service.name}" | jq '.Applications[] | .Versions[]' | grep -c "\"${VERSION_LABEL}\""`
if [ ${NB_VERS} = 0 ];then
echo "No app version called \"${VERSION_LABEL}\" in EB application \"${EB_APP}\"."
exit 4
fi
aws elasticbeanstalk update-environment --environment-name ${infra.custom.vars.EnvironmentName} --version-label ${VERSION_LABEL}
Steady State Check
export AWS_DEFAULT_REGION=${infra.custom.vars.Region}
AWS_STS_ROLE=${infra.custom.vars.stsRole}
NAME="Harness-Assume-Role"
export VERSION_LABEL=${VERSION}
if [ ! -z "$AWS_STS_ROLE" ]; then
echo "Assuming STS Role..."
# Unset existing AWS session keys
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_SECURITY_TOKEN
unset AWS_ACCESS_KEY AWS_SECRET_KEY AWS_DELEGATION_TOKEN
KST=(`aws sts assume-role --role-arn "$AWS_STS_ROLE" \
--role-session-name "$NAME" \
--query '[Credentials.AccessKeyId,Credentials.SecretAccessKey,Credentials.SessionToken]' \
--output text`)
export AWS_ACCESS_KEY_ID=${KST[0]}
export AWS_SECRET_ACCESS_KEY=${KST[1]}
export AWS_SESSION_TOKEN=${KST[2]}
else
echo "Skipping STS AssumeRole..."
fi
#######
echo "Checking for Steady State..."
APP_INFO=`aws elasticbeanstalk describe-environments --environment-name ${infra.custom.vars.EnvironmentName}`
APP_STATUS=`echo ${APP_INFO} | jq '.Environments[] | .Status' | sed -e 's/^"//' -e 's/"$//'`
APP_HEALTHSTATUS=`echo ${APP_INFO} | jq '.Environments[] | .HealthStatus' | sed -e 's/^"//' -e 's/"$//'`
APP_HEALTH=`echo ${APP_INFO} | jq '.Environments[] | .Health' | sed -e 's/^"//' -e 's/"$//'`
echo "Current APP Status: " ${APP_STATUS}
echo "Current APP Health Status" ${APP_HEALTHSTATUS}
echo "Current APP Health" ${APP_HEALTH}
while [ "$APP_STATUS" != "Ready" ] || [ "$APP_HEALTHSTATUS" != "Ok" ] || [ "$APP_HEALTH" != "Green" ]; do
APP_INFO=`aws elasticbeanstalk describe-environments --environment-name ${infra.custom.vars.EnvironmentName}`
APP_STATUS=`echo ${APP_INFO} | jq '.Environments[] | .Status' | sed -e 's/^"//' -e 's/"$//'`
APP_HEALTHSTATUS=`echo ${APP_INFO} | jq '.Environments[] | .HealthStatus' | sed -e 's/^"//' -e 's/"$//'`
APP_HEALTH=`echo ${APP_INFO} | jq '.Environments[] | .Health' | sed -e 's/^"//' -e 's/"$//'`
echo "---"
echo "Checking for Steady State..."
echo "Current APP Status: " ${APP_STATUS} " - Desired: Ready "
echo "Current APP Health Status" ${APP_HEALTHSTATUS} " - Desired: Ok"
echo "Current APP Health" ${APP_HEALTH} " - Desired: Green"
sleep 2
done
echo "------"
echo ${APP_INFO}
We also need to define a ShellScript template to our rollback step:
export VERSION_LABEL=${VERSION}
export AWS_DEFAULT_REGION=${infra.custom.vars.Region}
AWS_STS_ROLE=${infra.custom.vars.stsRole}
NAME="Harness-Assume-Role"
if [ ! -z "$AWS_STS_ROLE" ]; then
echo "Assuming STS Role..."
# Clean existing sessions
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_SECURITY_TOKEN
unset AWS_ACCESS_KEY AWS_SECRET_KEY AWS_DELEGATION_TOKEN
KST=(`aws sts assume-role --role-arn "$AWS_STS_ROLE" \
--role-session-name "$NAME" \
--query '[Credentials.AccessKeyId,Credentials.SecretAccessKey,Credentials.SessionToken]' \
--output text`)
export AWS_ACCESS_KEY_ID=${KST[0]}
export AWS_SECRET_ACCESS_KEY=${KST[1]}
export AWS_SESSION_TOKEN=${KST[2]}
else
echo "Skipping STS AssumeRole..."
fi
APP_STATUS=`echo ${APP_INFO} | jq '.Environments[] | .Status' | sed -e 's/^"//' -e 's/"$//'`
APP_HEALTHSTATUS=`echo ${APP_INFO} | jq '.Environments[] | .HealthStatus' | sed -e 's/^"//' -e 's/"$//'`
APP_HEALTH=`echo ${APP_INFO} | jq '.Environments[] | .Health' | sed -e 's/^"//' -e 's/"$//'`
echo "Current APP Status: " ${APP_STATUS}
echo "Current APP Health Status" ${APP_HEALTHSTATUS}
echo "Current APP Health" ${APP_HEALTH}
echo "---"
echo "Latest environment events ..."
aws elasticbeanstalk describe-events --environment-name ${infra.custom.vars.EnvironmentName} --output text --query 'Events[*].[EventDate,Severity,Message]' | head -20
while [ "$APP_STATUS" != "Ready" ] ; do
APP_INFO=`aws elasticbeanstalk describe-environments --environment-name ${infra.custom.vars.EnvironmentName}`
APP_STATUS=`echo ${APP_INFO} | jq '.Environments[] | .Status' | sed -e 's/^"//' -e 's/"$//'`
echo "---"
echo "Checking for Steady State..."
echo "Current APP Status: " ${APP_STATUS} " - Desired: Ready "
sleep 2
done
# See if EB_APP_VERSION is in the EB app
NB_VERS=`aws elasticbeanstalk describe-applications --application-name "${service.name}" | jq '.Applications[] | .Versions[]' | grep -c "\"${VERSION_LABEL}\""`
if [ ${NB_VERS} = 0 ];then
echo "No app version called \"${VERSION_LABEL}\" in EB application \"${app.name}\"."
exit 4
fi
aws elasticbeanstalk update-environment --environment-name ${infra.custom.vars.EnvironmentName} --version-label ${VERSION_LABEL}
4. Configure the Service
- User will navigate to their Harness Application and create a new service. This service will be of the type of Deployment Template. So since we called the template AWS Elastic Beanstalk, the Deployment type should appear like this:
AWS Elastic Beanstalk (Custom)
- User should add the artifact in the service from a S3 source. This is the artifact that will be deployed to your Beanstalk Env.
If necessary you can customize the templates and leverage all Harness features like variables, secrets, etc.
5. Configure the Environment
- Where are we going to deploy this service? You should navigate to your environment section and configure an Environment and an Infrastructure Definition.
- The Infrastructure definition takes in the Infrastructure variables you configured in the Deployment Template in step 1.
6. Configure the Workflow
-
We are going to deploy this new service to the target infrastructure we defined. In order to do so, we will create a basic workflow.
-
This workflow will already come with Fetch Instances, which allows the user to track and manage the deployed instance. It also lets the user perform CV on their deployed instances as well.
-
Add a new step and link the “Deploy AWS Beanstalk” service command from Template Library. If you have many delegates, pick your delegate selector and make sure that the Delegate have the AWS CLI and JQ installed.
-
In the rollback steps, link the “Rollback Elastic Beanstalk” template.
7. Time to Deploy
- Finally, after the configuration, we can deploy our Elastic Beanstalk Service!
- Hit Deploy and you should see something like this!
Conclusion
The new Harness Deployment Templates Type allows our users to bring in their own deployment styles and types and leverage Harness to manage the workloads and track the instances that are deployed. Users can now define how they want to deploy and how they want to monitor and verify it!
Check out the docs on Deployment Templates: Create a Custom Deployment using Deployment Templates - Harness.io Docs