Deploying your GoLang web server to AWS quickly and easily

Deploying a new code is always a bit scary. You need to keep track that everything is still going smooth after your upgrade has been finished, and of course make sure that your new code / features / bug fixes, is working as expected, as it worked in your dev environment.

In order to make things easier, we decided to automate every code deployment scenario we've got here at PushApps. From our main backend and mobile SDKs, to our admin console and our website. Our goal was:

No Manual Deployments.

In this blog post we'll show you the automated deployment process of our GoLang web server, which runs through supervisord and over nginx. Also important to mention that this will be deployed on Amazon Linux instance.

Pre Requirements:

  1. GoLang
  2. AWS CLI
  3. AWS S3Api
Handling input parameters

In order to have one script for both test and production environment, we'll ask for "env" input. Also, we'll ask for "desc" input, so we could see it later, in the AWS code deploy console:

# parsing the command line arguments and passing
# them to the correct script variables
while [[ $# > 1 ]]  
    do
    key="$1"
    case $key in
        -env|-environment)
        DEPLOYMENT_ENVIRONMENT="$2"
        shift # past argument
        ;;
        -desc|-description)
        DEPLOYMENT_DESCRIPTION="$2"
        shift # past argument
        ;;
        *)
           # unknown option
        ;;
    esac
    shift # past argument or value
done  

Don't forget to check your input and exit if something is missing or wrong:

# check required vars for deployment
checkInputArguments() {  
    if [ ! "$DEPLOYMENT_ENVIRONMENT" ]; then
        printf "Error : Please specify the requested environment using -env or -environment\n"
        return 999
    fi
    if [ "$DEPLOYMENT_ENVIRONMENT" != "test" ] && [ "$DEPLOYMENT_ENVIRONMENT" != "prod" ]; then
        printf "Error : Environment can be either 'test' or 'prod'\n"
        return 999
    fi
    if [ ! "$DEPLOYMENT_DESCRIPTION" ]; then
       printf "Error : Please specify the deployment description using -desc or -description\n"
       return 999
    fi
}
Verification question for production

Now, we'll want to add a verification question, if the user decide to deploy to production:

if [ "$DEPLOYMENT_ENVIRONMENT" != "test" ]; then  
    read -r -p "Are you sure you want to deploy this version to PRODUCTION? [y/N] " response
    if [[ $response =~ ^([yY][eE][sS]|[yY])$ ]]; then
        deploy
    else
        echo "Aborted"
    fi
else  
    deploy
fi  
Running commands in safe mode

After handling the input parameters, we would like to run each command in a safe mode - abort immediately if after one step failed and do not continue with the deployment (to avoid errors). I like the following method for this purpose:

# run every command in this script, using this method
# in order to avoid continuation after an error
safeRunCommand() {  
    typeset cmnd="$*"
    typeset ret_code
    # LOGGER_PREFIX is a var contains all our logs 
    # line prefix
    echo ${LOGGER_PREFIX} cmnd=${cmnd}
    eval ${cmnd}
    ret_code=$?
    if [ ${ret_code} != 0 ]; then
      printf "Error : [%d] when executing command: '$cmnd'. Exit now\n" ${ret_code}
      return ${ret_code}
    fi
}
Compiling the server

Notice that when compiling your Go program to an Amazon Linux we need to set the GOOS and GOARCH parameters.

compileServerAndConfigFile() {  
    GOOS=linux GOARCH=amd64 go build -o server/pushapps.linux server.go
}
Uploading to S3 and deploying the code

In order to use code deploy, you should have your code ready in S3 (or Github account, but we won't handle it in this post). We assume that you've already created an application and a deployment group. If not, please check this AWS tutorial.

deployToAWSServers() {  
    # upload to S3
    aws s3 cp tmp/${CURRENT_FILENAME} s3://code-deploy-bucket/$DEPLOYMENT_ENVIRONMENT/
    AWS_CODE_DEPLOY_OBJECT="$(aws s3api get-object --bucket code-deploy-bucket --key ${DEPLOYMENT_ENVIRONMENT}/${CURRENT_FILENAME} ${CURRENT_FILENAME})"
    AWS_ETAG="$(echo ${AWS_CODE_DEPLOY_OBJECT} | jsawk 'return this.ETag')"
    if [ ! "$AWS_ETAG" ]; then
       printf "Error : Could not get uploaded file ETAG\n"
       return 999
    fi
    AWS_CODE_DEPLOY_DEPLOYMENT_GROUP="Prodcution-Deployment-Group"
    if [ "$DEPLOYMENT_ENVIRONMENT" == "test" ]; then
        AWS_CODE_DEPLOY_DEPLOYMENT_GROUP="Test-Deployment-Group"
    fi
    aws deploy create-deployment --application-name PushApps-Application --deployment-group-name "$AWS_CODE_DEPLOY_DEPLOYMENT_GROUP" --description "$DEPLOYMENT_DESCRIPTION" --deployment-config-name CodeDeployDefault.OneAtATime --no-ignore-application-stop-failures --s3-location bucket=code-deploy-bucket,bundleType=zip,eTag=$AWS_ETAG,key=${DEPLOYMENT_ENVIRONMENT}/${CURRENT_FILENAME}
}

Now all you need is connect the parts that are relevant to you.
That's it for now. In my next post I'll show you guys how you can take this process and adjust it to any code deployment on AWS. You can also find the full scripts in our Github account.

Asaf Ron

Read more posts by this author.