Dynamic deployment of website

Madhuri MK
8 min readJul 28, 2020

Scope:

This document outlines the process of deploying a website based on the developer code either html or php etc. To implement this solution dynamically we have considered integrating code repo (git) with CI/CD tool like Jenkins along with container service (docker) to deploy. Additionally we will also enable a self healing mechanism of restarting or re-building the image in case it is stopped along with notifying the user.

Pre-Requisites:

  • This solution runs on any system which has docker installed
  • jenkins user with which job runs or user with which the server or VM is added as slave needs to run docker commands and systemctl commands. Hence the user need to be added to docker and sudo groups
  • Internet connectivity (either directly or via Proxy) to read github repo

Solution Architecture:

Before we start understanding the solution, let us try to figure out different components:

Local host or base machine:

This is the base OS where we deploy containers (jenkins, html, php etc.) need to be linux distro. Validation checks can be implemented to see if there are any conflict of names or ports for the docker containers that we will be deploying. Let us understand the base setup now:

  • Verify if git and docker are installed on this base OS:
mkadmin@mk-home-vm01:~$ docker --version
Docker version 19.03.12, build 48a66213fe
mkadmin@mk-home-vm01:~$ git --version
git version 2.17.1
mkadmin@mk-home-vm01:~$
  • Create a docker file to deploy jenkins
FROM centos:7
RUN yum install wget -y
RUN wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat/jenkins.repo
RUN rpm --import https://pkg.jenkins.io/redhat/jenkins.io.key
RUN yum install java -y
RUN yum install jenkins -y
RUN yum install git -y
RUN yum install sudo -y
RUN echo -e "jenkins ALL=(ALL) NOPASSWD:ALL " >> /etc/sudoers
CMD java -jar /usr/lib/jenkins/jenkins.war
  • Make sure that you dont have any docker with the jenkins name, if so chose a different name to build the docker image. Also make sure that port that you are using to host jenkin inside the docker is not been used by any other services, if so please choose a different port
mkadmin@mk-home-vm01:~/task-2$ docker ps | grep jenkins
mkadmin@mk-home-vm01:~/task-2$
mkadmin@mk-home-vm01:~/task-2$ sudo netstat -tunlp | grep 9090
mkadmin@mk-home-vm01:~/task-2$
  • Now build the image and run the container. Output of this is very huge, hence truncating:
mkadmin@mk-home-vm01:~/task-2$  ls -l
total 4
-rw-rw-r-- 1 opsadmin opsadmin 401 Jul 24 12:36 Dockerfile
opsadmin@aws-dr-vm01:~/task-2$ docker build -t jenkins:v1 .
Sending build context to Docker daemon 2.048kB
Step 1/10 : FROM centos:7
7: Pulling from library/centos
524b0c1e57f8: Pull complete
Digest: sha256:e9ce0b76f29f942502facd849f3e468232492b259b9d9f076f71b392293f1582
Status: Downloaded newer image for centos:7
---> b5b4d78bc90c
Step 2/10 : RUN yum install wget -y
---> Running in 7ca806d95ef5
Loaded plugins: fastestmirror, ovl
Determining fastest mirrors
* base: mirror.shastacoe.net
* extras: mirror.siena.edu
* updates: mirror.dal.nexril.net
Resolving Dependencies
...................................Complete!
Removing intermediate container e1eeb36deb35
---> bc9858df2b8c
Step 9/10 : RUN echo -e "jenkins ALL=(ALL) NOPASSWD:ALL " >> /etc/sudoers
---> Running in c11cb1002d53
Removing intermediate container c11cb1002d53
---> 360f61f33255
Step 10/10 : CMD java -jar /usr/lib/jenkins/jenkins.war
---> Running in 14a47ec8c80b
Removing intermediate container 14a47ec8c80b
---> 8e3bc0864a9b
Successfully built 8e3bc0864a9b
Successfully tagged jenkins:v1
mkadmin@mk-home-vm01:~/task-2$
mkadmin@mk-home-vm01:~/task-2$ docker run -it --privileged -p 90:8080 --name mk-jenkinsV1 jenkins:v1
Running from: /usr/lib/jenkins/jenkins.war
webroot: $user.home/.jenkins
2020-07-24 20:14:43.606+0000 [id=1] INFO org.eclipse.jetty.util.log.Log#initialized: Logging initialized @325ms to org.eclipse.jetty.util.log.JavaUtilLog
2020-07-24 20:14:43.729+0000 [id=1] INFO winstone.Logger#logInternal: Beginning extraction from war file
2020-07-24 20:14:44.870+0000 [id=1] WARNING o.e.j.s.handler.ContextHandler#setContextPath: Empty contextPath
2020-07-24 20:14:44.927+0000 [id=1] INFO org.eclipse.jetty.server.Server#doStart: jetty-9.4.30.v20200611; built: 2020-06-11T12:34:51.929Z; git: 271836e4c1f4612f12b7bb13ef5a92a927634b0d; jvm 1.8.0_252-b09
2020-07-24 20:14:45.207+0000 [id=1] INFO o.e.j.w.StandardDescriptorProcessor#visitServlet: NO JSP Support for /, did not find org.eclipse.jetty.jsp.JettyJspServlet
2020-07-24 20:14:45.247+0000 [id=1] INFO o.e.j.s.s.DefaultSessionIdManager#doStart: DefaultSessionIdManager workerName=node0
2020-07-24 20:14:45.247+0000 [id=1] INFO o.e.j.s.s.DefaultSessionIdManager#doStart: No SessionScavenger set, using defaults
2020-07-24 20:14:45.250+0000 [id=1] INFO o.e.j.server.session.HouseKeeper#startScavenging: node0 Scavenging every 660000ms
2020-07-24 20:14:45.546+0000 [id=1] INFO hudson.WebAppMain#contextInitialized: Jenkins home directory: /root/.jenkins found at: $user.home/.jenkins
2020-07-24 20:14:45.672+0000 [id=1] INFO o.e.j.s.handler.ContextHandler#doStart: Started w.@1b9ea3e3{Jenkins v2.249,/,file:///root/.jenkins/war/,AVAILABLE}{/root/.jenkins/war}
2020-07-24 20:14:45.689+0000 [id=1] INFO o.e.j.server.AbstractConnector#doStart: Started ServerConnector@7eecb5b8{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}
2020-07-24 20:14:45.689+0000 [id=1] INFO org.eclipse.jetty.server.Server#doStart: Started @2409ms
2020-07-24 20:14:45.689+0000 [id=22] INFO winstone.Logger#logInternal: Winstone Servlet Engine running: controlPort=disabled
2020-07-24 20:14:46.953+0000 [id=29] INFO jenkins.InitReactorRunner$1#onAttained: Started initialization
2020-07-24 20:14:46.982+0000 [id=28] INFO jenkins.InitReactorRunner$1#onAttained: Listed all plugins
2020-07-24 20:14:47.999+0000 [id=28] INFO jenkins.InitReactorRunner$1#onAttained: Prepared all plugins
2020-07-24 20:14:48.004+0000 [id=36] INFO jenkins.InitReactorRunner$1#onAttained: Started all plugins
2020-07-24 20:14:48.015+0000 [id=40] INFO jenkins.InitReactorRunner$1#onAttained: Augmented all extensions
2020-07-24 20:14:48.591+0000 [id=40] INFO jenkins.InitReactorRunner$1#onAttained: System config loaded
2020-07-24 20:14:48.591+0000 [id=29] INFO jenkins.InitReactorRunner$1#onAttained: System config adapted
2020-07-24 20:14:48.592+0000 [id=29] INFO jenkins.InitReactorRunner$1#onAttained: Loaded all jobs
2020-07-24 20:14:48.592+0000 [id=35] INFO jenkins.InitReactorRunner$1#onAttained: Configuration for all jobs updated
2020-07-24 20:14:48.688+0000 [id=55] INFO hudson.model.AsyncPeriodicWork#lambda$doRun$0: Started Download metadata
2020-07-24 20:14:48.697+0000 [id=55] INFO hudson.util.Retrier#start: Attempt #1 to do the action check updates server
2020-07-24 20:14:49.359+0000 [id=33] INFO o.s.c.s.AbstractApplicationContext#prepareRefresh: Refreshing org.springframework.web.context.support.StaticWebApplicationContext@48e2b66d: display name [Root WebApplicationContext]; startup date [Fri Jul 24 20:14:49 UTC 2020]; root of context hierarchy
2020-07-24 20:14:49.360+0000 [id=33] INFO o.s.c.s.AbstractApplicationContext#obtainFreshBeanFactory: Bean factory for application context [org.springframework.web.context.support.StaticWebApplicationContext@48e2b66d]: org.springframework.beans.factory.support.DefaultListableBeanFactory@11d16ca9
2020-07-24 20:14:49.373+0000 [id=33] INFO o.s.b.f.s.DefaultListableBeanFactory#preInstantiateSingletons: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@11d16ca9: defining beans [authenticationManager]; root of factory hierarchy
2020-07-24 20:14:49.537+0000 [id=33] INFO o.s.c.s.AbstractApplicationContext#prepareRefresh: Refreshing org.springframework.web.context.support.StaticWebApplicationContext@1ccd1b64: display name [Root WebApplicationContext]; startup date [Fri Jul 24 20:14:49 UTC 2020]; root of context hierarchy
2020-07-24 20:14:49.538+0000 [id=33] INFO o.s.c.s.AbstractApplicationContext#obtainFreshBeanFactory: Bean factory for application context [org.springframework.web.context.support.StaticWebApplicationContext@1ccd1b64]: org.springframework.beans.factory.support.DefaultListableBeanFactory@23f7a728
2020-07-24 20:14:49.538+0000 [id=33] INFO o.s.b.f.s.DefaultListableBeanFactory#preInstantiateSingletons: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@23f7a728: defining beans [filter,legacy]; root of factory hierarchy
2020-07-24 20:14:49.708+0000 [id=33] INFO jenkins.install.SetupWizard#init:
*************************************************************
*************************************************************
*************************************************************
Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:
575f8402cba547fb9d4f77bd06919783This may also be found at: /root/.jenkins/secrets/initialAdminPassword*************************************************************
*************************************************************
*************************************************************
  • From the output you will see a password, now launch the jenkins URL from the browser at the address: http://ip_of_vm:90 and will ask for password and install the plugins:
  • And then create the user:
  • Now lets go to Mange Jenkins >> Manage Plugins >> Available and search for ssh and install first 4 plugins:
  • Click Install without restart:
  • Configure the ssh credentials in the global credentials section:

Jenkins Job Setup:

Now we shall discuss about the variuos jobs that need to be setup for completing this solution.

Job-1: repo_pull

This is a free style project created to automatically pull the git repository whenever there is a developer commit activity every 5 mins interval:

Output of the job:

Job-2:deploy_env

This job looks at the code and identifies if its html or php and deploys the respective development environment as a docker container. Also it validates if the previous docker persists, will stop and remove to make sure latest container is deployed. This job is down stream to the previous job. So that this job auto triggers on previous job successful execution:

Execute shell commands:

#!/bin/bash
#Check if the index.html has php reference or not
if grep "?php" ../repo_pull/website/index.html &>/dev/null
then
#Before we start php container, check if it already exists, then delete it and then start
if sudo docker ps -a | grep OSphp
then
sudo docker stop OSphp
sudo docker rm -f OSphp
echo "Existing docker for OSphp is now deleted"
echo "Starting the OSphp container"
sudo docker run -dit -p 777:80 -v website:/var/www/html --name OSphp vimal13/apache-webserver-php:latest
else
echo "Starting the OSphp container"
sudo docker run -dit -p 777:80 -v website:/var/www/html --name OSphp vimal13/apache-webserver-php:latest
fi
else
if sudo docker ps -a | grep OShtml
then
sudo docker stop OShtml
sudo docker rm -f OShtml
echo "Existing docker for OShtml is now deleted"
echo "Starting the OShtml container"
sudo docker run -dit -p 777:80 -v website:/usr/local/apache2/htdocs --name OShtml httpd:latest
else
echo "Starting the OShtml container"
sudo docker run -dit -p 777:80 -v website:/usr/local/apache2/htdocs --name OShtml httpd:latest
fi
fi

After successful deployment of the html page the URL looks like this:

and PHP page looks like this:

Job output looks like this:

for html:

for php:

Job-3: test_app

This job will verify that the deployed website/app is healthy or not by checking the http code.

Output of the job for successful:

In case of any issues:

Job-4: job_monitor

This job monitors all the above jobs and triggers an email in case anyone fails:

Final Build Pipeline looks like this:

Please feel free to review and let me know any inputs or feedback.

--

--