- continuous integration
- continuous deployment
Continuous deployment works by either delivering or having the server update the content of you application based on a recent commit to your repo
We will setup the scripts that will handle starting and stopping your app server
For this tutorial we will be using this app I built https://github.com/amrdraz/aws-wercker.git fork it and use its url in the remainder of this lab
We want to automate what we do on the server when we update our project. To do that you must think of the commands you run whenever you update the server.
Updating a project consists of something as follows
- ssh into your server
- if this is the first time
- clone your project folder
- cd into it and npm install and build if you need too
- start the server on you preferred port default 80 for example
- if you are updating you
- cd into your project folder
- git pull you master branch
- restart the server
Bash scripts are files that run terminal commands (instead of typing them one by one), we will add a bash script to our server.
To understand what the following files are doing you know what you have to do, lookup
ssh into your deployed server and add update_server.sh
$ ssh YOUR_USER@SOME_IP_ADDRESS
$ vi update_server.sh
content of update_server.sh
note this setup assumes you are using nvm and have installed
pm2
if you're usingforever
then you would replace pm2 with forever in the following script though I encourage you tonpm install pm2 -g
to match this tutorial
#!/bin/bash
# load the nvm enviroment apparently it is not avalable by default
[[ -s $HOME/.nvm/nvm.sh ]] && . $HOME/.nvm/nvm.sh
# use node
nvm use 5.9
# check if the directory indicated by -d is not there (! -d "$APP")
# "$APP" is a variable and will be replaced in runtime
if [ ! -d "$APP" ]; then
# if the app directory is not there we clone (creating a new one) then cd into it
# the REPO_URL and APP will be supplied in runtime
# we will be basically saying
# git clone https://github.com/amrdraz/aws-wercker.git aws-test
git clone "$REPO_URL" "$APP"
cd "$APP"
else
# if the directory is there this means we cloned it before so we cd into it and pull
cd "$APP"
# here we're using -f to force pull the master branch
git pull origin master -f
fi
# now that we are in the updated directory we should rebuild our project in case there are any new packages
npm install
# if we used grunt or gulp to build angular this would be the point where we
# gulp build
# having completed rebuilding we now start running our app
cd ..
# stop all servers running any server in our case there's just one so this is just me being lazy
# the reason I'm using stop all is to avoid conflicting with your existing app if it's also running port 80
# after stopping I start our app this method is not optimal since the server would be down for a very small time
# it is better to do something like pm2 rebuild
pm2 stop all
pm2 start "$APP"
# this works in the case of my example app because I have the server called index.js otherwise
# you would probably not cd .. and instead pm2 start server.js while inside the server
# this is just terminating the bash script successfully
exit 0
Now test that the scripts are working
$ APP=aws-test REPO_URL=https://github.com/amrdraz/aws-wercker.git ./update_server.sh
You will get a permission error this is because you have to make the file executable
$ chmod u+x update_server.sh
$ APP=aws-test REPO_URL=https://github.com/amrdraz/aws-wercker.git ./update_server.sh
Once you try it out a couple of times and fail, you will remember that you need to make sure you are using what works with your system :). you can check that the app is running with pm2 list
and visiting your server
Some possible errors with regards to starting the server
Maybe that you did not open port 80
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --dport 80 -j ACCEPT
sudo apt-get install libcap2-bin
sudo setcap cap_net_bind_service=+ep /usr/local/bin/node
Another server is running on the existing port
- make sure you stop all servers if you where using forever and switched to pm2
forever stopall
- you could kill all node processes by typing
killall node
Remember you can debug the bash script by simply running in the terminal each command one by one.
Now that our server is ready to receive automated commands, we will need a service that will trigger these commands.
There are multiple options but for this course I decided the best practical choice is to use an up and coming CI/CD provider called wercker
wercker will require you do 3 things
- create an account for your repo and grant wercker permission to access it; in order to pull code from you
- add a wercker.yml file to your repo to tell wercker what to do with your repo when it gets it
- add the wercker app's public key to your amazon instance so that wercker can ssh into it and run your update_script.sh
You will find I have configured a wrecker.yml file already in the repo I asked you to use
The remaining instructions for how to setup wercker are on the repo
Here I will discuss the wercker.yml file and explain each command.
You will find in the repo a wercker.yml file
box: node
build:
steps:
- npm-install
- npm-test
deploy:
aws:
- add-ssh-key:
keyname: SSH_KEY
host: $HOST
- add-to-known_hosts:
hostname: $HOST
- script:
name: clone or pull then start
code: |
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $USER@$HOST APP=$APP REPO_URL=$REPO_URL ./update_server.sh
This file is used to tell wercker what to do with your repo on each commit
we tell wercker to use a node box, according to wercker a box is essentially a server image that's pre-configured, in this case it is a server that has node environment setup.
we then configure wercker's build settings, we tell it that on each build it should follow the following steps, first run npm install
then run npm test
The whole point of CI is to make sure our tests are passing, there for your repo must have tests (otherwise the CI is useless), you will notice that we included a trivial test in the repo (one that will always pass) for the sake of demonstration
so this build process should always work, if the build does not pass then the deploy process will not be triggered and you will be notified by email.
next in the .yml file we configrued the deploy command
the wercker deploy command can also have steps however we wanted some custom setup so we created a deploy target called aws (see the repo's readme) and told it it should use this deploy target variables when deploying
we then tell it that for the aws deploy target we should like you to add the SSH_KEY we created to you .shh folder as well as add our host to your known hosts. This is so that we can ssh into our server.
next we tell it to run in its terminal the following script
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $USER@$HOST APP=$APP REPO_URL=$REPO_URL
This command basically has us ssh into our server with USER@HOST and runs the same command we ran before in runtime this line is doing the following assuming USER, HOST, APP and REPO_URL
$ ssh [email protected]
$ APP=aws-test REPO_URL=https://github.com/amrdraz/aws-wercker.git ./update_server.sh
which pulls our server form the repo and restarts
- with this setup every-time we push a commit, tests will automatically run and you will know if this branch is passing.
- when you commit to master if the build passes your app will be automatically deployed on the server.
please note that if you stop your instance your IP address will change and so you'll need to update the wercker deploy target configuration
To get familiar with the work-flow
- create a branch other then master, lets call it test
- make a trivial failing test and add it to our test file
- add commit and push the branch
- checkout wercker you should see that the build is failing
- fix the test so that it passes and push
- you should see on wercker that the build passes but no deploy is triggered
git checkout master
thengit merge test
to merge your passing test then push your new master- you should see wercker passing the build then deploying your server
Integrate what you learned here in your upcoming listen as you build your back-end similar to the assignment you should create tests for your modules methods and your API calls using mocha, chai and supertest