- First we need to launch an EC2 instance with Keypair
instanceType: t2.micro
AMI: Amazon Linux-2
Security Group:
22, SSH
8080, Custom TCP
- Once our server is running, connect to your server via SSH by using your keypair.
- Next we need to install
Java
,Jenkins
,Maven
to our server. - First switch to root user
sudo su -
and update the packages firstsudo yum update -y
sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
- Then we need to install Java
sudo amazon-linux-extras install java-openjdk11 -y
- After installing Java, we can now install Jenkins and start our Jenkins server
sudo yum install jenkins -y
sudo systemctl enable jenkins
sudo systemctl start jenkins
sudo systemctl status jenkins
- Connect to http://<your_server_public_DNS>:8080 from your browser. You can get
initialAdminPassword
for jenkins by running below command:
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
- We need to install git in our Jenkins server, run below command:
sudo yum install git
- Now we can run our first Jenkins job. Go to
Dashboard
->New Item
JobName: HelloWorldJob
Type: Free Style Project
Build Step: Execute shell
echo "Hello World!"
-
We need to go to
Manage Jenkins
->Manage Plugins
. Here we will installGithub Integration
andMaven Integration
plugins. -
Now we can run our Second job to check
Github integration
is working as expected. Create another FreeStyleJob as below:
SCM: Git
URL: https://github.com/rumeysakdogan/hello-world.git
Save -> Build Now
- You can check the Workspace, if your project successfully downloadded from GitHub. In Jenkins server,
/var/lib/jenkins/workspace/
you can see the jobs you have run so far.
- We need to install MAVEN in Jenkins server.
cd /opt
sudo wget https://dlcdn.apache.org/maven/maven-3/3.8.6/binaries/apache-maven-3.8.6-bin.tar.gz
tar -xvzf apache-maven-3.8.6-bin.tar.gz
mv apache-maven-3.8.6-bin maven
-
Next configure
M2_HOME
andM2
(binary directory) environment variables and add them to thePATH
so that we can runmaven
commands in any directory. You can search where is your JVM by using tfind / name java-11*
-
Now you need to edit .bash_profile to add these variables to path and save
M2_HOME=/opt/maven
M2=/opt/maven/bin
JAVA_HOME=/usr/lib/jvm/java-11-openjdk-11.0.16.0.8-1.amzn2.0.1.x86_64
PATH=$PATH:$HOME/bin:$M2_HOME:$M2:$JAVA_HOME
export PATH
- To apply the changes we have made to .bash_profile, either we can logout and log back in or run
source .bash_profile
command. It will upload the changes.
-
Now go to
Manage Jenkins
andGlobal Tool Configuration
to add path fofr Java and Maven. -
We can configure a
Maven Project
to build our Code, go toDashboard
->NewItem
Name = FirstMavenProject
Type: Maven Project
Root Pom: pom.xml
Goals and options: clean install
- now we can go to
/var/lib/jenkins/workspace/FirstMavenProject/webapp/target
to see our build artifact webapp.war file.
- First create an EC2 instance for Tomcat Server
instanceType: t2.micro
AMI: Amazon Linux-2
Security Group:
22, SSH
8080, Custom TCP
- Next install java-11 in Tomcat server, switch to root user
sudo su -
and run below command. Once installed, checkjava -version
amazon-linux-extras install java-openjdk11
- Next we will install Tomcat, switch to
/opt
directory
wget https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.68/bin/apache-tomcat-9.0.68.tar.gz
tar -xvzf apache-tomcat-9.0.68.tar.gz
mv apache-tomcat-9.0.68.tar.gz tomcat
- Now we can start our Tomcat server
cd tomcat/bin/
./startup.sh
-
Now we should be able to access our Tomcat server from browser. Go to
http:<public_ip_of_your_tomcat_server>:8080
-
When we click
Manager App
we should get403 Access Denied
error. To fix this issue we need to editcontext.xml
file.
By default the Manager is only accessible from a browser running on the same machine as Tomcat. If you wish to modify this restriction, you'll need to edit the Manager's context.xml file.
- Go to tomcat directory run
find / -name context.xml
to get the location ofcontext.xml
file
/opt/tomcat/webapps/examples/META-INF/context.xml
/opt/tomcat/webapps/host-manager/META-INF/context.xml
/opt/tomcat/webapps/manager/META-INF/context.xml
- We should update
context.xml
file underhost-manager
andmanager
directories. Currently it is only allowing access from localhost, we will comment out the part shown below in the xml.
<!-- <Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" /> -->
- Once we updated those files, we need to stop and restart our Tomcat server.
cd tomcat/bin/
./shutdown.sh
./startup.sh
- Now we updated the files, we will no longer get
403 Access Denied
error but it will ask username&password in Tomcat server. To find the credentials, go to under tomcat/conf directory
cd tomcat/conf/
vim tomcat-users.xml
- We will add below users to the file, and save the file.
<role rolename="manager-gui"/>
<role rolename="manager-script"/>
<role rolename="manager-jmx"/>
<role rolename="manager-status"/>
<user username="admin" password="admin" roles="manager-gui, manager-script, manager-jmx, manager-status"/>
<user username="deployer" password="deployer" roles="manager-script"/>
<user username="tomcat" password="s3cret" roles="manager-gui"/>
- Once we updated the file, we need to stop and restart our Tomcat server.
cd tomcat/bin/
./shutdown.sh
./startup.sh
-
Install
Deploy to Container
plugin in Jenkins. Go toManage Jenkins
->Manage Plugins
, find the plugin under Available and chooseinstall without restart
-
Configure Tomcat Server with credentials. Go to
Manage Jenkins
->Manage Credentials
. We will selectAdd credentials
. We will use the credential we have added totomcat-user.xml
file for this step. Since these credentials will be used for deploying app, we will adddeployer
credentials which hasmanager-script
role.
Kind: username with password
username: deployer
pwd: deployer
- Now we can create our next job with name of
BuildAndDeployJob
. After build step, the artifact will stored underwebapp/target/
directory aswebapp.war
.
Kind: Maven Project
SCM: https://github.com/rumeysakdogan/hello-world.git
Goal and options: clean install
Post Build Actions: Deploy war/ear to a container
WAR/EAR files: **/*.war
Container: Tomcat 8 (even though we have install v9, this plugin is giving some issues with v9, for that reason we will use v8)
Credentials: tomcat_deployer
Tomcat URL: http://<Public_IP_of_Tomcat_server>:8080/
Save
andBuild
the job. When go to Tomcat server underManager App
, you will be able to see our application underwebapp/
-
We can configure our job to be triggered with
Poll SCM
by scheduling a cron job. It will check the repository based on given schedule. If there is any change in repository, it will trigger job and deployed the new version of app to Tomcat server. -
We can also configure a webhook in our repository, whenever there is any
Git push
happens, job will trigger. To be able to setup Webhooks in Github, Go toSettings
->Webhook
->Add webhook
Payload URL: http://<dns_of_your_jenkins_server>:8080/github-webhook/
- First we will create an EC2 instance for Docker and name it as
Docker-Host
.
instanceType: t2.micro
AMI: Amazon Linux-2
Security Group:
22, SSH
8080, Custom TCP
- Login to
Docker-Host
server via SSH, switch to root usersudo su -
and install docker
yum install -y
- Go to
DockerHub
, search forTomcat
official image. We can pick a specific tag of the image, but if we don't specify it will pull the image with latest tag. For this project we will use latest Tomcat image.
docker pull tomcat
- Next we will run docker container from this image.
docker run -d --name tomcat-container -p 8081:8080 tomcat
-
To be able to reach this container from browser, we need to add port
8081
to our Security Group. We can add a range of port numbers8081-9000
to Ingress. -
Once we try to reach our container from browser it will give us
404 Not found
error. This is a known issue after Tomcat version 9. We will follow below steps to resolve this issue. -
First we need to go inside container by running below command:
docker exec -it tomcat-container /bin/bash
- Once we are inside the container, we need to move files under
webapps.dist/
towebapps/
mv webapps webapps2
mv webapps.dist webapps
exit
- However, this solution is temporary. Whenever we stop our container and restart the same error will be appeared. To overcome this issue, we will create a Dockerfile and create our own Tomcat image.
- We will create a Dockerfile which will fix the issue.
FROM tomcat
RUN cp -R /usr/local/tomcat/webapps.dist/* /usr/local/tomcat/webapps/
- Lets create an image from this Docker file.
docker build -t tomcat-fixed .
- Now we can create a container from this image
docker run -d --name tomcat-container-fixed -p 8085:8080 tomcat-fixed
- We can check our Tomcat server in browser now.
- We will create a new user/password called
dockeradmin
and add it todocker
group
useradd dockeradmin
passwd dockeradmin
usermod -aG docker dockeradmin
- Start docker service.
systemctl status docker
systemctl start docker
systemctl enable docker
systemctl status docker
- We can check our new user in
/etc/passwd
and groups/etc/group
.
cat /etc/passwd
cat /etc/group
- Next we will allow username/password authentication to login to our EC2 instance. By default, EC2s are only allowing connection with Keypair via SSH not username/password.
vim /etc/ssh/sshd_config
- We need to uncomment/comment below lines in
sshd_config
file and save it
PasswordAuthentication yes
#PasswordAuthentication no
- We need to restart sshd service
service sshd reload
- Go to Jenkins , install
Publish over SSH
plugin. next go toManage Jenkins
->Configure System
FindPublish over SSH
->SSH Server
.Apply
changes andSave
Name: dockerhost
Hostname: Private IP of Docker Host(since Jenkins and Docker host are in the same VPC, they would be able to communicate over same network)
Username: dockeradmin
click Advanced
Check use password based authentication
provide password
- We will create a Jenkins job with below properties:
Name: BuildAndDeployOnContainer
Type: Maven Project
SCM: https://github.com/rumeysakdogan/hello-world.git
POLL SCM: * * * * *
Build Goals: clean install
Post build actions: Send build artifacts over ssh
SSH server: dockerhost
TransferSet: webapp/target/*.war
Remove prefix: webapp/target
Remote directory: /home/dockeradmin
- Save and build, we can check under dockerhost server if webapp successfully send to /home/dockeradmin by using SSH.
- Currently artifacts created through Jenkins are sent to
/home/dockeradmin
. We would like to change it to home directory of root user, and give ownership of this new directory todockeradmin
sudo su -
cd /opt
mkdir docker
chown -R dockeradmin:dockeradmin docker
- We have our Dockerfile under
/home/root
, we will move Dockerfile underdocker
directory and change ownership as well
mv Dockerfile /opt/docker
chown -R dockeradmin:dockeradmin docker
- We will change our Dockerfile to copy the webapps.war file under webapps/ in Tomcat container.
FROM tomcat:latest
RUN cp -R /usr/local/tomcat/webapps.dist/* /usr/local/tomcat/webapps/
COPY ./*.war /usr/local/tomcat/webapps
- we build the image and create a container from the newly created image.
docker build -t tomcat:v1 .
docker run -d --name tomcatv1 -p 8086:8080 tomcat:v1
-
We can check our app from browser http://<public_ip_of_docker_host>:8086/webapp/
-
Now we can configure our
BuildAndDeployOnContainer
job to deploy our application. We will add below lines toExec Command
part. We also need to changeRemote directory
path as//opt//docker
cd /opt/docker;
docker build -t regapp:v1 .;
docker stop registerapp;
docker rm registerapp;
docker run -d --name registerapp -p 8089:8080 regapp:v1
- First we will create an EC2 instance for Ansible.
instanceType: t2.micro
AMI: Amazon Linux-2
Security Group: DevOps-Security-Group
- Login Ansible server via SSH and create new user named
ansadmin
and add it tosudoers
sudo su -
useradd ansadmin
passwd ansadmin
- Add below line to
/etc/sudoers
s file. to open file in edit mode entervisudo
.
## Same thing without a password
# %wheel ALL=(ALL) NOPASSWD: ALL
ansadmin ALL=(ALL) NOPASSWD: ALL
- Next we will allow username/password authentication to login to our EC2 instance. By default, EC2s are only allowing connection using KeyPair via SSH not username/password.
vim /etc/ssh/sshd_config
- We need to uncomment/comment below lines in
sshd_config
file and save it
PasswordAuthentication yes
#PasswordAuthentication no
- We need to restart sshd service
service sshd reload
- Next we need to switch to
ansadmin
user and create ssh key. Create key will be stored in/home/ansadmin/.ssh
directory.id_rsa
will be our private key,id_rsa.pub
will be our public key.
ssh-keygen
- Now we can install ansible as root user.
amazon-linux-extras install ansible2
- Ansible requires pyhon to be able to run, but Amazon Linux-2 already has python installed for this reason, we don't need to install python
- We need to follow below steps to steup connection between our Ansible node and Docker Host.
On Docker Host:
- Create ansadmin
- Add ansadmin to sudoers files
- Enable password based login
sudo su -
useradd ansadmin
passwd ansadmin
visudo
- Add
ansadmin
tosudoers
file
## Same thing without a password
# %wheel ALL=(ALL) NOPASSWD: ALL
ansadmin ALL=(ALL) NOPASSWD: ALL
- We need to uncomment/comment below lines in
sshd_config
file and save it
PasswordAuthentication yes
#PasswordAuthentication no
- We need to restart sshd service
service sshd reload
On Ansible Node:
- Add to hosts files
- Copy ssh public key
- Test Connection
-
We need to add dockerhost to
/etc/ansible/hosts
file -
We need to create ssh-key for
ansadmin
user and copy the public key to docker host
sudo su - ansadmin
ssh-keygen
ssh-copy-id <private-ip-of-docker-host>
- We can check connection by running below command in ansible server.
ansible all -m ping
- Go to Jenkins server,
Manage jenkins
->Configure System
. We need to add below information underPublish over SSH
:
Name: ansible-server
Hostname: Private-ip-of-Ansible-server
username: ansadmin
Enable user authentication
password
- Now we can create our Jenkins job:
Name: CopyArtifactsOntoAnsible
Copy from: BuildAndDeployOnContainer
Post build actions: ansiblehost
delete exec commands
- Go to ansible server, we need to create
/opt/docker
directory and give ownership toansadmin
cd /opt
mkdir docker
chown -R ansadmin:ansadmin docker
- Save and build the job.
webapp.war
file is successfully copied to ansible server.
- First we need to install docker in ansible server.And add
ansadmin
user to docker group, start docker service.
sudo yum install docker -y
sudo usermod -aG docker ansadmin
systemctl status docker
systemctl start docker
systemctl enable docker
systemctl status docker
- We will create same Dockerfile under
docker
directory in Ansible host. We can create image and run container from this image inansible
server.
docker build -t regapp:v1 .
docker run -t --name regapp-server -p 8081:8080 regapp:v1
- We will create a simple playbook to create an image and container.
---
- hosts: ansible
tasks:
- name: create docker image
command: docker build -t regapp:latest .
args:
chdir: /opt/docker
- Before running this playbook, since we want to run this in ansible server we need to add public key to authorized keys. we can use
ssh-copy-id
command
ssh-copy-id <private-ip-of-ansible-server>
- Next we need to add Ansible server to our
/etc/ansible/hosts
file.
[dockerhost]
172.31.29.5 ansible_user=ansadmin
[ansible]
172.31.84.3
- Now we are ready to run our playbook
ansible-playbook all <playbook-name>.yml
- For this step we need to have dockerhub username to push our images to dockerhub. It is easy to signup free from the website
https://hub.docker.com/
.
docker login
Username:
Password:
Login Succeeded
- If we want to push an image to our dockerhub it has to be tagged with our docker username. We can tag an existing image by using
docker tag
command like shown below.
docker tag <image-id> rumeysakdogan/regapp:tagname
docker push rumeysakdogan/regapp:tagname
- Now we can update our playbook to add new tasks.
---
- hosts: ansible
tasks:
- name: create docker image
command: docker build -t regapp:latest .
args:
chdir: /opt/docker
- name: create tag to push image onto dockerhub
command: docker tag regapp:latest rumeysakdogan/regapp:latest
- name: push docker image
command: docker push rumeysadogan/regapp:latest
- We can dry-run our playbook by giving
--check
flag.
ansible-playbook regapp.yml --check
- We will configure CopyArtifactOntoAnsible job to deploy image with ansible playbook
under SSH server:
host: ansiblehost
exec command: ansible-playbook /opt/docker/regapp.yml
- We can create another playbook, which can run container from the image that we pushed to our public repository on dockerhub.
---
- hosts: dockerhost
tasks:
- name: create container
command: docker run -d --name regapp-server -p 8082:8080 rumeysakdogan/regapp:latest
- But we have a problem in this playbook, when we try to run the same playbook again, it will give an error saying
regapp-server container already exists.
To fix this problem, we will add below tasks to our playbook.
- remove existing container
- remove existing image
- create a new container
- We will make the below updates in our
regapp-deploy.yml
file
---
- hosts: dockerhost
tasks:
- name: stop existing container
command: docker stop regapp-server
ignore_errors: yes
- name: remove the container
command: docker rm regapp-server
ignore_errors: yes
- name: remove the existing image
command: docker rmi rumeysakdogan/regapp:latest
ignore_errors: yes
- name: create container
command: docker run -d --name regapp-server -p 8082:8080 rumeysakdogan/regapp:latest
ignore_errors: yes
- It is time to configure our existing Jenkins job
CopyArtifactsOntoAnsible
, we will add below commands to exec command part under SSH Host.
ansible-playbook /opt/docker/regapp.yml;
sleep 10;
ansible-playbook /opt/docker/regapp-deploy.yml
-
First we will create an ec2-instance instance to use as EKSCTL bootstrap server.
-
As per official documentation https://docs.aws.amazon.com/eks/latest/userguide/getting-started-eksctl.html, we need to install prerequisites shown below as root user:
- Latest version of AWS CLI: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html
- kubectl latest version: https://docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html
- eksctl latest version: https://docs.aws.amazon.com/eks/latest/userguide/eksctl.html
- IAM Role with required priviledges
- Create cluster by using eksctl with below command:
eksctl create cluster --name rd-cluster \
--region us-east-1 \
--node-type t2.small
- To delete cluster, run below command:
eksctl delete cluster --name rumeysa-cluster
- Once our EKS cluster is ready we can run
kubectl
commands to check our cluster resources.
kubectl get all
kubectl get no
- Next we will create a deployment manifest for
regapp
.
apiVersion: apps/v1
kind: Deployment
metadata:
name: rumeysa-regapp
labels:
app: regapp
spec:
replicas: 2
selector:
matchLabels:
app: regapp
template:
metadata:
labels:
app: regapp
spec:
containers:
- name: regapp
image: rumeysakdogan/regapp
imagePullPolicy: Always
ports:
- containerPort: 8080
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
- We can create and check our deployment with below
kubectl
commands:
kubectl apply -f regapp-deploy.yml
kubectl get deploy
- Now we need to create a service to access our application from browser. We will create a
LoadBalancer
type service manifest for our app.
apiVersion: v1
kind: Service
metadata:
name: rumeysa-service
spec:
selector:
app: regapp
ports:
- port: 8080
targetPort: 8080
type: LoadBalancer
- We can create and check our service with below
kubectl
commands:
kubectl apply -f regapp-service.yml
kubectl get svc
- We need to follow below steps to steup connection between our Ansible node and K8s bootstrap server.
On K8s bootstrap server:
- Create ansadmin
- Add ansadmin to sudoers files
- Enable password based login
sudo su -
useradd ansadmin
passwd ansadmin
visudo
- Add
ansadmin
tosudoers
file
## Same thing without a password
# %wheel ALL=(ALL) NOPASSWD: ALL
ansadmin ALL=(ALL) NOPASSWD: ALL
- We need to uncomment/comment below lines in
sshd_config
file and save it
PasswordAuthentication yes
#PasswordAuthentication no
- We need to restart sshd service
service sshd reload
On Ansible Node:
- Add to hosts files
- Copy ssh public key
- Test Connection
- We will create a new host file to add
kubernetes bootstrap server
under/opt/docker
localhost
[kubernetes]
172.31.94.207
[ansible]
172.31.84.3
- We need to copy ansadmin public ssh key to
k8s bootstrap server
ssh-copy-id <private-ip-of-k8s-bootstrap-server>
- We can check connection by running below command in ansible server.
ansible -a uptime all
- First we need to create our
kube_deploy.yml
playbook in ansible server.
---
- hosts: kubernetes
user: root
tasks:
- name: deploy regapp on kubernetes
command: kubectl apply -f regapp-deployment.yml
- Next we will create our
kube_service.yml
playbook in ansible server.
---
- hosts: kubernetes
user: root
tasks:
- name: deploy regapp on kubernetes
command: kubectl apply -f regapp-service.yml
- Since we are running these files as root user we need to copy ssh public key uder home directory of root user for ansible to control. For this action, It will ask root user password. we can easily configure a password for rrot with
passwd root
command in kubernetes bootstrap server.
ssh-copy-id root@<private_ip_of_kubernetes_bootstrap_server>
- Now we are ready to run our playbooks:
ansible-playbook -i hosts kube_deploy.yml
ansible-playbook -i hosts kube_service.yml
- We can check if our deployment is successful via ansible control node by running below command in K8s bootstrap server:
kubectl get deploy,svc
- We will create a Freestyle job named
DeployOnKubernetes
in Jenkins.
Post-build Actions: Send over SSH
server: ansiblehost
Exec command:
ansible-playbook -i /opt/docker/hosts /opt/docker/kube_deploy.yml;
ansible-playbook -i /opt/docker/hosts /opt/docker/kube_service.yml
- Before running the job, lets delete existing deployments in our K8s server. Then we can run our job.
kubectl delete deploy rumeysa-regapp
kubectl delete service rumeysa-service
- We can combine playbooks in one by adding
kube_service.yml
as a new task underkube_deploy.yml
.
---
- hosts: kubernetes
user: root
tasks:
- name: deploy regapp on kubernetes
command: kubectl apply -f regapp-deployment.yml
- name: create loadbalancer service on kubernetes
command: kubectl apply -f regapp-service.yml
- Here we will just create a job named
RegApp_CI_Job
by using exieting jobCopyArtifactOntoDocker
. Last time we renamed our playbook, we will just update that in Exec Command section
ansible-playbook /opt/docker/create_image_regapp.yml
- To integrate our CI job with CD job, we will configure
PostBuild action
asBuild Other Projects
RegApp_CD_Job
Initialize only when build is stable
- We need to update one more thing in our
kube-deploy.yml
playbook. We need to specify the rollout whenever if new image is pushed to docker hub.
- name: update deployment with new pods if image updated in docker hub
command: kubectl rollout restart deployment.apps/rumeysa-regapp
- We can make an update to
index.jsp
in ourhello-world project
underhello-world/webapp/src/main/webapp/
directory and push our changes to Github. This will trigger both CI&CD jobs triggered successively.