In this post I'll demonstrate a simple CD system, that based on a merge to the Master branch for a project on Github will package, push and deploy a small RESTfull application.
The setup consists of a single Jenkins server, a load balancer node running HAProxy and two API servers running Apache with the libapache2-mod-wsgi module to drive the Python Bottle microframework.
First let's create the local git repository:
File: gistfile1.txt
-------------------
[workstation]$ ssh -T git@github.com
[workstation]$ git ls-remote -h git@github.com:someuser/simple_rest_ci_pipline.git
[workstation]$ git add *
[workstation]$ git commit -m "Initializing the repository"
[workstation]$ git push -u origin master
Create a new project on Github, and deploy your public key to it:
Next, test that you can connect using the deployed SSH key and push to the Master branch:
File: gistfile1.txt
-------------------
[workstation]$ ssh -T git@github.com
[workstation]$ git ls-remote -h git@github.com:someuser/simple_rest_ci_pipline.git
[workstation]$ git add *
[workstation]$ git commit -m "Initializing the repository"
[workstation]$ git push -u origin master
Let's build the Jenkins server:
File: gistfile1.txt
-------------------
[jenkins-n01]$ wget -q -O - https://jenkins-ci.org/debian/jenkins-ci.org.key | sudo apt-key add -
[jenkins-n01]$ sh -c 'echo deb http://pkg.jenkins-ci.org/debian binary/ > /etc/apt/sources.list.d/jenkins.list'
[jenkins-n01]$ apt-get update && apt-get -y install jenkins git
[jenkins-n01]$ /etc/init.d/jenkins start
[jenkins-n01]$ iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
Once you login to Jenkins create a new project:
Or you can copy the following config file to /var/lib/jenkins/jobs/RESTfull-CI-Pipeline:
File: gistfile1.txt
-------------------
[jenkins-n01]$ cd ~/jobs/RESTfull-CI-Pipeline
[jenkins-n01]$ cat config.xml
false
https://github.com/kaivanov/simple_rest_ci_pipline/
2
https://github.com/kaivanov/simple_rest_ci_pipline.git
*/master
false
true
false
false
false
false
PACKAGE_NAME="simple-restfull-api.deb"
PACKAGE_VERSION=$BUILD_NUMBER
API1="XXX.XXX.XXX.XXX"
API2="XXX.XXX.XXX>XXX"
# Create the packe hierarchy and control file
mkdir -p PACKAGE/DEBIAN
mkdir -p PACKAGE/var/www/restfullapi
cat > PACKAGE/DEBIAN/control <<EOF
Package: simple-restfull-api
Version: $PACKAGE_VERSION
Priority: optional
Architecture: all
Maintainer: Konstantin Ivanov
Description: Simple RESTfull API
EOF
cp $WORKSPACE/project/* $WORKSPACE/PACKAGE/var/www/restfullapi/
# Build the package
dpkg-deb --build PACKAGE
mv PACKAGE.deb $PACKAGE_NAME
# Deploy the package.
rsync --rsync-path="sudo rsync" -vaz $PACKAGE_NAME $API1:/tmp
ssh -o stricthostkeychecking=no -o UserKnownHostsFile=/dev/null $API1 "sudo dpkg --install /tmp/$PACKAGE_NAME && sudo service apache2 reload"
if [ "$?" -eq 0 ]
then
rsync --rsync-path="sudo rsync" -vaz $PACKAGE_NAME $API2:/tmp
ssh -o stricthostkeychecking=no -o UserKnownHostsFile=/dev/null $API2 "sudo dpkg --install /tmp/$PACKAGE_NAME && sudo service apache2 reload"
if [ "$?" -ne 0 ]
then
echo "Error deploying to node2, aborting ..."
exit 1
fi
else
echo "Error deploying to node1, aborting ..."
exit 1
fi
false
Make sure you have the github plugin installed on Jenkins for this to work.
Lastly on the Github side, add a service that will call the Jenkins API when an event is triggered e.g. merge to Master:
Time to setup the API nodes:
File: gistfile1.txt
-------------------
[api-n01/02]$ useradd -m -s /bin/bash jenkins
[api-n01/02]$ sh -c 'echo jenkins ALL=(ALL:ALL) NOPASSWD:ALL > /etc/sudoers.d/100-ci-users'
[api-n01/02]$ apt-get update
[api-n01/02]$ apt-get install apache2
[api-n01/02]$ apt-get install python-pip
[api-n01/02]$ pip install bottle
[api-n01/02]$ apt-get install libapache2-mod-wsgi
[api-n01/02]$ cat << EOF > /etc/apache2/sites-available/000-default.conf
ServerName restfullapi-n01.example.net
WSGIDaemonProcess restfullapi user=www-data group=www-data processes=1 threads=1
WSGIScriptAlias / /var/www/restfullapi/restfullapi.wsgi
WSGIProcessGroup restfullapi
WSGIApplicationGroup %{GLOBAL}
Order deny,allow
Allow from all
EOF
And finally the load balancer node:
File: gistfile1.txt
-------------------
[lb-n01]$ apt-get update && apt-get install haproxy
[lb-n01]$ cat /etc/haproxy/haproxy.cfg
global
log /dev/log local1
log /dev/log local1 notice
user haproxy
group haproxy
daemon
defaults
log global
mode http
option httplog
option dontlognull
contimeout 5000
clitimeout 50000
srvtimeout 50000
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
frontend http
bind :80
reqadd X-Forwarded-Proto:\ http
default_backend http_nodes
backend http_nodes
mode http
balance leastconn
option httpclose
option forwardfor
option redispatch
option httpchk GET /
cookie JSESSIONID prefix
server api-n01 restfullapi-n01.example.net:80 check inter 1000
server api-n02 restfullapi-n02.example.net:80 check inter 1000
Now every time you merge a commit to Master, Github will POST to the Jenkins API, and Jenkins will execute the job, building a debian package, and deploying it to the API nodes.
To test the simple RESTfull API run:
File: gistfile1.txt
-------------------
[workstation]$ curl -X GET http://restfullapi.example.net/ping
pong!