We will cover how to make our git-based deployment into a production-ready CI solution, with staged deployment and support for multiple environments. Staging deployment to production was covered earlier. This is going to address how to deploy to several environments in a CI pipeline, and promote a package from one environment to the next from a build server.
Our environments’ instances are each gonna be a slot of a web-app. We’ll create a local git repository for each of the environments, then promote code from one to the next.
The advantages of this method are:
- It’s generic enough that anything with a shell and a git client can run it
- It’s promoting from environment to environment using binaries, increasing confidence of what’s on each environment. Bonus point: binaries are marked with the commit hash that generated them, so you can easily trace back to the code.
- It maintains history of deployments. You can rollback to a given version by re-assigning master to the commit you want, then push to the environment.
The biggest drawback being that all your environments are hosted on the same webapp, which is not applicable to all cases.
Setting the pipeline up
You need to create something that ressembles this:
You can copy slot configuration from the staging slot, but make sure you deactivate auto swap for environments other than staging.
Then configure each slot to be deployed from local git repository, the same way that’s described here. The trick here is that each environment’s repository is named
SERVICENAME-slotname.azurewebsites.net, and being on the same resource-group, they all use the same credentials. To deploy from one environment to the next, we really just need to add environments as remotes, and push the master branch from one to the next. We’re thus going to build and deploy to the first environment (e.g. dev), then push to the next remotes.
Finally, set the pipeline using the scripts that you can find here, you need to do the following:
- set the environment variables
prepare.shto point the build directory to your seed environment (e.g. dev).
- deploy to the seed environment using
- repeat the following process:
- run required tests and operations
- promote to the next environment with
An example of the whole orchestration looks like this:
#!/bin/bash export DEPLOYMENT_USER=user-you-configured-in-azure export DEPLOYMENT_PASSWORD=some-password-that-is-cool export DEPLOYMENT_URL='my-app-service-$ENVIRONMENT.scm.azurewebsites.net:443/my-app-service.git' export DEPLOYMENT_FOLDER=bin git config --global user.email "firstname.lastname@example.org" git config --global user.name "Mr. Build Service" chmod +x clean.sh chmod +x build.sh chmod +x deploy.sh chmod +x prepare.sh chmod +x promote.sh ./prepare.sh dev ./build.sh ./deploy.sh dev
Then run your unit tests. If they succeed, we promote to CI:
#!/bin/bash ./promote.sh dev ci
The run your integration tests. If they succeed, we promote to prod (through staging and autoswap):
# Run some other tests ./promote.sh ci staging
How it works
First off, we changed the way
prepare.sh works from what was described here. It now takes an environment name, then:
- checks if the build folder exists, else creates it and initializes git.
- checks if the remote exists, adds it else - observe that the url is generated using the
DEPLOYMENT_URLshould be set accordingly (e.g. set to
- fetches the latest from the initial git repo1.
#!/bin/bash ENVIRONMENT=$1 echo "Preparing environment for $ENVIRONMENT" if [ ! -d "$DEPLOYMENT_FOLDER/.git" ]; then if [ ! -d "$DEPLOYMENT_FOLDER" ]; then mkdir "$DEPLOYMENT_FOLDER" fi cd "$DEPLOYMENT_FOLDER" git init else cd "$DEPLOYMENT_FOLDER" fi if [ ! `git remote | grep $ENVIRONMENT` ]; then URL=`eval echo -n $DEPLOYMENT_URL` git remote add $ENVIRONMENT https://$DEPLOYMENT_USER:$DEPLOYMENT_PASSWORD@$URL fi git fetch $ENVIRONMENT git clean -xdf git checkout $ENVIRONMENT/master --force git branch -f master $ENVIRONMENT/master git checkout master cd -
Then we build. This part is on you.
Then we deploy to the “seed” environment (e.g. dev), this is done through the
deploy.sh script, which really just commits everything and push to the remote:
#!/bin/bash ENVIRONMENT=$1 VERSION=`git describe --always` echo "Deploying version $VERSION to $ENVIRONMENT" cd $DEPLOYMENT_FOLDER git add -A git commit -m "Commit $VERSION - Build as of `date`" git push $ENVIRONMENT master cd -
Finally, we want to promote from one environment to the next based on your process (test results, manual process, etc.). This is done using
promote.sh. This script is basically checking out the source environment, and pushing its master to the target environment:
#!/bin/sh FROM=$1 TO=$2 echo "Promoting $FROM to $TO" ./prepare.sh $FROM ./deploy.sh $TO
And that is it!
- You need some form of cleanup to remove deleted files in your build. Drastically, you could just delete everything (except directory .git) as a first step of your build.
- You need to prevent the situation where a merge conflict would happen. This means that you need to always build to the same environment (say dev)
If you’re changing the initial build environment (say, go from staging to dev), you need to synchronize your staging repository to the dev repository before you do the first build to prevent merge conflicts. ↩