- Published on
Automate your deployment with AWS CodePipeline - Part 1
- Authors
- Name
- Devin Reeks
Lets configure the repo side of the pipeline
Title: repo configuration for codepipeline
Author: Devin Reeks
Language: English
Introduction
Tired of manually deploying your code? Want to automate your deployment process? What does that mean? It means that you can push your code to a repository and have it run through a pipeline and automatically deployed to your server.
A few weekends ago I decided to attend the Tanda Hackathon, here in Brisbane. It was an excellent opportunity to network, showcase ideas while having a few beers with like-minded people. It is here that I met an AWS solutions architect that inspired me to build a deployment pipeline. He was excellent and helped me get some of the way.
The project we will deploy is a golang backend with a mongo database. The mongo database was chosen due to the anticipated need for fast read/writes at scale. You can of course use whatever you want :) this is just an example implementation with hopefully a few gotcha's along the way.
There are two main parts to the dance. 1. adding the necessary build files to your repo and 2. setting up the AWS side:
Setting up the AWS side:
This part of the article will focus on the repository side. Important files you will need to add to your project: buildspec.yml this can be appropriately named (I called mine buildspec_release.yml) appspec.yml - this taps into the lifecycle hooks of the pipeline and allows use to define scripts a folder with the necessary scripts to run on each lifecycle event (more on this later)
Here is my buildspec.yml file. In here we can define how we would like our project to be built. This was inspired by sample buildspec
version: 0.2
phases:
install:
commands:
- echo CODEBUILD_SRC_DIR - $CODEBUILD_SRC_DIR
- echo GOPATH - $GOPATH
- echo GOROOT - $GOROOT
build:
commands:
- echo Build started on `date`
- echo Getting packages
- go get ./...
- echo Compiling the Go code...
- env GOOS=linux GOARCH=arm64 go build -o server main.go
- echo "$(date) $CODEBUILD_RESOLVED_SOURCE_VERSION" > commit_hash.txt
post_build:
commands:
- echo Build completed on `date`
artifacts:
files:
- server
- appspec.yml
- scripts/*
- commit_hash.txt
Here is a base repo you can use if you like, feel free to use as a template for your next go project:
Golang CodePipeline base project template
In this buildspec.yml file we define our install, build and post build commands.
The install phase:
In the install command we set the codebuild_src_directory using the available CodeBuild environment variable. This is mandatory, so that the codedeploy agent running in your EC2 will know where to find your build. For more information on CodeDeploy environment variables: codedeploy environment variables
I set the GOPATH and GOROOT because in the next phase I will build the binary. Feel free to skip that part if you’re doing a different sort of deployment.
The build phase:
This runs the build. In here you may run npm run build (if you’re doing a javascript deployment) you could potentially put your lint and typescript checks here, or have them as a separate part of the build pipeline:- depending on your requirements.
Artifacts:
One crucial aspect of the buildspec file is artifacting. In my buildspec, I include the binary that was built in the previous phase, the appspec.yml file from my repository, and all the necessary script files (which I'll discuss later). It's important to consider what your server will require to operate efficiently, and artifacting those elements is essential. For instance, static assets can be another example of artifacts that need to be included.
This part may give you the most grief if not included. It’s a bit like writing an email and forgetting to attach the attachment — or more visually:
The appspec.yml file
version: 0.0
os: linux
files:
- source: /
destination: /home
hooks:
ApplicationStop:
- location: scripts/application_stop.sh
timeout: 300
ApplicationStart:
- location: scripts/application_start.sh
timeout: 300
Within the appspec.yml file, I specify both the source and destination of files. The source refers to my artifact files, which are stored in an Amazon S3 bucket, while the destination indicates the desired folder within the EC2 instance where I want these files to be copied. The appspec file enables us to access and utilize the different lifecycle events that take place during deployment.
Scripts
It is here where we can tap into the lifecycle events of the deployment process to start and stop our server. We could also tap into the lifecycle events to seed our database, health check external resources, etc. The start script:
#!/bin/sh
echo "application start"
sudo nohup /home/server > /dev/null 2>&1 &
In here I am daemonizing my server and throwing away the output, I recommend logging your output more effectively.
#!/bin/sh
echo "Stopping the application..."
# Find the process IDs (PIDs) of the application
app_pids=$(pgrep -f "/home/server")
# Check if any application process is running
if [ -n "$app_pids" ]; then
# Terminate each application process individually
for app_pid in $app_pids; do
sudo kill "$app_pid"
done
echo "Application stopped successfully."
else
echo "Application is not running."
fi
Endpoints
Don't forget to add a health check endpoint, Amazon CodePipeline will need this for a successful pipeline run this is mine:
package controllers
import (
"github.com/julienschmidt/httprouter"
"net/http"
"fmt"
)
func HealthCheck() httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "Service is healthy")
}
}
You can optionally add a verification endpoint to return the commit hash (I have included in the github template)
http.HandleFunc("/verify", func(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadFile("/home/commit_hash.txt")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("unable to read file: " + err.Error()))
return
}
w.WriteHeader(http.StatusOK)
w.Write(body)
})
for more info on the appspec file: