# CI/CD with Github Actions, Sonarqube and Ansible

# **Objectives**

* Target the code at: [https://github.com/melvincv/java-basic-practice-app](https://github.com/melvincv/java-basic-practice-app)
    
* Switch to main-v2 branch.
    
* Build the code using GitHub Actions.
    
* Deploy the code on two AWS EC2 servers. (independently)
    
    * Implement approval process for deployment on EC2 servers.
        
    * Once the application is stable, try implementing the blue-green.
        
        * Make a change in the codebase and deploy the updated code to ONLY one of the EC2 instances.
            
        * Upon deployment, your load balancer should serve from both the EC2 instances and show your updated page when refreshed (randomly)
            

---

# **Architecture**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1714368624336/759fd6e0-0f19-49e5-a1a4-71f87b17db48.png align="center")

# **Workflow**

Complete Github Workflow: `.github/workflows/main-pipeline.yml`

[https://github.com/melvincv/java-basic-practice-app/blob/main-v2/.github/workflows/main-pipeline.yml](https://github.com/melvincv/java-basic-practice-app/blob/main-v2/.github/workflows/main-pipeline.yml)

## Jobs

1. **build job**
    

* set up JDK 8 Temurin
    
* build using maven
    
* cache dependencies using the same action
    
* upload WAR file as an artifact
    

2. **code-quality job**
    

* set up JDK 11 Temurin
    
* cache sonarqube packages
    
* build and analyze using Maven and sonar-maven-plugin
    

3. **test job**
    

* runs maven test
    
* uses JDK 8
    
* requires build and code-quality jobs
    

4. **deploy-a** job
    

* is triggered on the main-v2 branch only + manual input
    
* is part of the production  environment
    
* requires the test job
    
* downloads the artifact uploaded by the build job
    
* renames WAR file to ROOT.war
    
* scp action copies it to /tmp
    
* ssh action deploys it to the tomcat webapps dir
    

5. **deploy-b job**
    

* similar to deploy-a
    

# **Sonarqube Setup**

Start an EC2 instance with the following parameters:

* name: sonarqube
    
* ami: Ubuntu 22.04 LTS 64 bit
    
* type: t3.medium
    
* security group: open ports 80, 443, 22
    
* spot instance: true
    
* user data: (check and modify shell script variables first)
    
* [https://github.com/melvincv/java-basic-practice-app/blob/main/scripts/sonarqube/prereq.sh](https://github.com/melvincv/java-basic-practice-app/blob/main/scripts/sonarqube/prereq.sh)
    
    The script configures kernel settings, installs Docker and Caddy reverse proxy.
    

Be sure to set the DNS A record to the IP address of your Sonarqube instance asap.

SSH to the sonarqube instance

Check for instance init progress

```bash
tail -f /var/log/cloud-init-output.log
Cloud-init v. 23.4.4-0ubuntu0~22.04.1 finished at Sat, 20 Apr 2024 09:33:31
```

and lookup your domain name. Confirm that it is pointing to your instance before starting the Caddy service.

```bash
nslookup jenkins.aws.melvincv.com
sudo systemctl start caddy
sudo systemctl status caddy
```

Check for the certificate issue in the logs of the status command.

But Sonarqube is not ready yet and you will get an HTTP error 502 (Bad gateway). This is because the upstream server is not available.

## **Install Sonarqube using Docker Compose**

Create a folder for the Docker files

```bash
mkdir sonar; cd sonar
```

Create a .env file with a random, strong PostgreSQL Password.

```bash
cat > .env
POSTGRES_PASSWORD=xxxxxxxxxxxxxxxxxxxx
```

Create a compose.yml file

```bash
cat > compose.yml
```

My compose file: [https://github.com/melvincv/java-basic-practice-app/blob/main/scripts/sonarqube/compose.yml](https://github.com/melvincv/java-basic-practice-app/blob/main/scripts/sonarqube/compose.yml)

Start Sonarqube using Compose:

```bash
docker compose pull
docker compose up -d
```

The Compose file starts 3 containers:

* sonarqube lts version
    
* postgres 12
    
* adminer (optional, for DB management)
    

```bash
$ docker compose ps
NAME                IMAGE                     COMMAND                  SERVICE     CREATED              STATUS              PORTS
sonar-adminer-1     adminer                   "entrypoint.sh php -..."   adminer     About a minute ago   Up About a minute   0.0.0.0:10000->8080/tcp, :::10000->8080/tcp
sonar-db-1          postgres:12               "docker-entrypoint.s..."   db          About a minute ago   Up About a minute   5432/tcp
sonar-sonarqube-1   sonarqube:lts-community   "/opt/sonarqube/dock..."   sonarqube   About a minute ago   Up About a minute   0.0.0.0:9000->9000/tcp, :::9000->9000/tcp
```

Wait for a minute or two...

... and Sonarqube is ready

Eg: [https://sonar.aws.melvincv.com/](https://sonar.aws.melvincv.com/)

Login with the default credentials and change your password asap.

user: admin

pass: admin

### **Create a Project**

Sonarqube &gt; Projects &gt; Manually

Project display name = Java Practice App v2

Project key = Java-Practice-App-v2

Main branch name = main-v2

![](https://lh7-us.googleusercontent.com/VOMnXCb_wOMXukk9-P4mmeHmfbbhrnOrrfKxkvIvmI2L_Y2IWaaIoyT02__9rlxzJHZvp0xLU4JAQn4I2uMHQ1OzxzkTv8uDimQCn2vRwZrHUxb8CqFOrQQwyG5MWZGMKa8VMNchH_ItBzkvF4_1JpY align="left")

![](https://lh7-us.googleusercontent.com/l7ItM1kxvZHsctxc73j-BWsTrEC0avZ01YJkc0BhG67QUkSv1XkuwYiPg0oCY01fSIj8N3InTlJNgcJW4QFTnwl82LhrATtaaLVsrIwcPiY1S1zxPTKEJztuSKDMzqes1o139qYsxNW0a8CvdB-1peI align="left")

Follow the given instructions:

1. Create 2 GitHub Repo Secrets- SONAR\_TOKEN and SONAR\_HOST\_URL
    
    ![](https://lh7-us.googleusercontent.com/lPLypUt0zHNkBlP7obNlCLM24kHBhaj-4fFsrECp8PzzQx1SPeDYmh5sWHyPdq3D4iQcQP-sWlp_F1vP3kNvkwOy69GYT9nohyG3qxlm_P2MhuP6tOgG7Vim0RXqKJv7jpb1j7WULvS-hk5erLnG2P4 align="left")
    

Update workflow. Sample code will be shown. (already updated)

# CD Target Servers

Start 2 target ec2 instance - server-a and server-b

* ami: Ubuntu 22.04 LTS 64 bit
    
* type: t3.micro
    
* security group: open ports 8080, 22
    
* spot instance: true
    

## **Ansible Playbook**

Write and execute an Ansible playbook to install Tomcat 9 on the Target servers.

Code: [https://github.com/melvincv/java-basic-practice-app/tree/main-v2/ansible](https://github.com/melvincv/java-basic-practice-app/tree/main-v2/ansible)

Test connectivity to the instances using the ping module:

![](https://lh7-us.googleusercontent.com/1kaoxl3TVq89CtyFmK3KPe3hK4zEcV7wSvAmR1qw2uwbOiiaNTxtkcYd3LsesF2c-3auMfYsNlpVTZUxpzhS6l7zm7ciguiRJyoSJQQCj7BJdhftgvE2sDo7-8sa8EwzDWwjCQ0A15vTyagYvboqMWs align="left")

Run the playbook:

![](https://lh7-us.googleusercontent.com/0TDPNv5T7ofRaHukRsayPeczgll2YfBz0UiOQXYLtwsA7VEoYqx4k4myMvzdN-aNfoVg7zWgUIjltP6DhZ6Ce71QFKiG323ZYIjFAekUDIKiFoa5LKxVOL1YjHnUBLCghhWB2sE2Ds62CdzY9_t_OLQ align="left")

# **Add Github Variables and Secrets**

Settings &gt; Secrets and Variables &gt; Actions &gt; Variables

Add HOST\_A and HOST\_B as repo variables. In the value box, add the IP of the target server A / B.

![](https://lh7-us.googleusercontent.com/Dl2s2A6JHKCQqGDR1EkIxp0Bg9ayLPVYmvvsW9X61MZLkbGMlxkDmsV1wOiOk4_ifL6HSMzg715TKnbC4mnFqgRzhEoX1Bum_n4i5g9nGwXPnha6oXMdviFRMD_ic3t5anPsXLdFT6HLyrMyax_imGU align="left")

![](https://lh7-us.googleusercontent.com/nCoeofCM5Lw8b9r5N5iAdAZ6z78FSXFlKJ6rSNEsa529euWOFAc13dLrRwV93dF4yf4oKDBcrVqO5vCfSMwe-lVWmL68lokXCXkeX9PjLbofUewpqsGRoVlI0_rVqNmItMJGbyGT16m30seuYxVx9j4 align="left")

Settings &gt; Secrets and Variables &gt; Actions &gt; Secrets

Add repo secrets: 

* SONAR\_HOST\_URL  (URL of the Sonarqube instance)
    
* SONAR\_TOKEN  (Token obtained from the sonarqube project)
    
* USERNAME  (ubuntu)
    
* PRIVATE\_KEY  (private key for the target servers)
    

![](https://lh7-us.googleusercontent.com/UCxedw8w58PJuFJpnnDzboSAq4V-qYmpJjyUPT4z43vWcYUtd3PpDegqTPEU5XWTuryEQKGpwQ9m1DSNTS6xlWqDwF0Q8s7rpOiXwvzhF6-aZ75LwHzSRrzCcQoWWsYR9d5KvlHJe20EWIGmTcEPmBA align="left")

![](https://lh7-us.googleusercontent.com/nh1B-30-TgzsCa2MegLHlZXwHZHSSw-CPWhbu74-2AG8fKtYZHElmAX8GkKvMT6vnRnUGc0_-VKu0vIuOcyvLkFjdiL7PtXmxAwU0djzx9DevlXNSeRj7yPVCOWo7F9nALrS4_bPOAcGL57johMm5zk align="left")

Add Branch Protection for the main and main-v2 branches

![](https://lh7-us.googleusercontent.com/6T3svojUDs-nTG1SGvFWL0H1PAKvg39XE1hMoRnCMqmMO-6dWZPkSYfurABs5r5z0OwhQywA7YFkbfgQuldhjBB46eZ8_sNELBgS2vUR_hIwt4MUHilBofdAMIo8tkA1RkJvtdsLPvruTv5zXugxoN8 align="left")

Add a production environment for the workflow

Add a reviewer to check and confirm the deployment to production. You may create a new Github account to try this out...

![](https://lh7-us.googleusercontent.com/PxYrULpwgc9ufjFB1esjFf7h0N_PSsw-sokQQtpULIFWeA9aesvVHgRMr5BiY2b0kHO8xpmBLKmXPRsFK99KhVYPRBazHmHRC9wS_CJQCNZp9tCYnqAEtpHYe-CxNcMlpy4-u7a9cdJMlleLm3rKoho align="left")

# **Approval Process**

1. I make a commit to the devel branch
    
2. Raise a PR to the main-v2 branch
    
3. PR workflow runs
    
4. Reviewer approves the PR and code is merged from **devel** to **main-v2**
    
5. the workflow runs for main-v2
    

![](https://lh7-us.googleusercontent.com/mHiYESJ1ZkWnEzttW36Ttq4-6xyfPWhtQVFWTE7tDHPPspIS5cLLIYl-YeP2puTpnwU1EeSo0qLc_gNMAutelaoaBLO8QoEB9RTYcna6SRGrcAAVfZdZAyqLeoqbnpZ9SgyItoEsXfYBVRujK5RroW4 align="left")

![](https://lh7-us.googleusercontent.com/7VWNJ9z7STU3DD2J0LRzlm9RO9UJLBxlRftqHCQwCchEhI_0ceAoO6U-3aC8kLjeYQDaWSh8KQs_olPMAVcQYroQnV32YMfjSU_-1qcEiZo0V7MsqlLEZ6UcwRyfsTLUfqRBjh6GmS-VcKP--Gw89nY align="left")

![](https://lh7-us.googleusercontent.com/gJ04kHFMwpkuo7UlBHI2lDDu-m1JjLQL0EbUTdA-hMKdk5nX28J96Z8V5_sE1RYGA25Mk1bM07tsqrwrQbRQOC0-aBxeApqN0Jc9_5d1grfaOCZiphNGQlDmee8Ti8y-Q9_AQy9c9qoHs82Z1yeJ5tE align="left")

On the reviewer's account, approve the PR:

![](https://lh7-us.googleusercontent.com/jWV4xPQy6oRaZqgTjWys6orWlWKIwxBSBChYlq6fOyYJIzCDWDFvNa0aj-CerrcwH3WB-O7AE_nGCXE6ZlTu6EEwaE8LKDB5hhf_yaT5n-2mfcZaFEWWN67QeY4HwPelPFAOdmJJ5ulenXo80RJERJ8 align="left")

![](https://lh7-us.googleusercontent.com/fwJZ6Uqf4_xdYBa12N8fiRQ0TKaCtF5tBRToYYFeQWp_iuLJnDUW6NiFRtoNiRxq1M3qAAMyUiFCnT6KxugYJeJv4UaoGJvkm7hThN37o14jvE8YPkxuG-pVufeaM5hu5HekRYNDPYlcj8j0SHbaqDw align="left")

![](https://lh7-us.googleusercontent.com/3RKFOh47Y0EeXRiEXReBLlS5pXVzL6miS_WRqZggZWk2N1i0VKURuyFHPRGlICpmbHAU_NxDbQAiOfsD2kppRUlOXimMJjBcQQTl5C2NYbixqqZadFwB-kkL823BQs6Gi7HfrBKU2fAytenAVWZEZ6s align="left")

Production environment deployment approval prompt will be displayed when the workflow reaches the deploy jobs:

![](https://lh7-us.googleusercontent.com/FwAeD9rlZudMTMvxzfI7Bfy5T4ggJ0Hg7MliThF4kcFV1DB3R7I2u9b0P54SAjZReu6TZVIiqM_ElUF4hpjsNuJIl8ersCKbgL9mSgkuWK8xgOk_hk8K6nn5BB4iPU1p-ZhM-MBXIiD9VDrdAsgmJe0 align="left")

The reviewer approves the PR from his account:

![](https://lh7-us.googleusercontent.com/mnNOuiRhOw2gUB708qGOZVer7dbJrRaZ1OUlhM2sGZWDDjODN33yabxUbE8J5D7u-6A9inaM_yie6_9WS-dsfHqPHn-8ivEJ9Yg-Z5bnogbxXwdqiSsEfwidB2cwgIk1-TyN0qAuN-FCgxIN-BgHnUY align="left")

![](https://lh7-us.googleusercontent.com/hTaohwYOUe8d7M7Obg8DsEMPeZo0mTcQYGm4LyOgImsifgo5YMKz05jp80d4J2EPMTTBqBmmTM0GYB1mJSMPBKGuEsGktSwdpPuOBnh4N86odoQ1PJffdazO2Zw5NJoxIB_3dJjDoHcDKReUWV26VWY align="left")

The deploy jobs start running and the app is deployed to production.

# **AWS Setup**

## **Instances**

These instances have already been set up:

![](https://lh7-us.googleusercontent.com/St7hkypjTKg8ohJopwkctovla95tDNoxk4Qv_z4f0jOsNzkcbzW5hgvkoNc3iwUHSE_2jlttvhTFDTrtc3Stq1zBCJLqE-0e85tUYzPHOupP4ispxlIBqJpOJSqHwmHgQT2AoLTp4UqbQKuPn2Gxyd0 align="left")

## **Create a target group**

![](https://lh7-us.googleusercontent.com/bcVUExsyxqrTcFicAifuM5ZElGF-MmHRBjaptA0m4QE1NoVVlwWqK_Zd0CdNrNj-XGUIrxS6zXDsbTOaYHjv-p_owCVoWzPsyQzcFALxQZUn4W5g1zEOrNZwTTZ7gJ7PY4W4L6bT-vQ9WeQ15YCrRAA align="left")

![](https://lh7-us.googleusercontent.com/818GoRjL0b3299OHKjGnb_eGqa6HG5b_4o3Gig9xd9rG9ao60n5Zp93hpe7x7gAyu_Ad8S2veKyNyY8pvvtZB-HkB61LUaY7i33bV42MPeWoOFc5Yn81dP-JDkqe4XW7ach_08hrks_Z-au5xsdtbi8 align="left")

## **Create App Load Balancer**

Name: akhil-p2-alb

Internet facing, IPv4, Select all subnets

Select security group (akhil-project-1-alb-sg)

Add listeners:

![](https://lh7-us.googleusercontent.com/D16cTfqGfadY1VZ6Roe2d6Pw4sCy-mEOkrt4TiQSyqD9yR9ZvudUXDsq-fX1Q9dAjZ9-TTTrjSoikX_eoABPQ8nAPL77r8YARFY7UNcnLBEeJWwRd5ln599lNSpD2cA-1DWDD2bPJguX2o5o02R1krg align="left")

Selecting the wildcard SSL certificate created in the previous project

![](https://lh7-us.googleusercontent.com/CkxhuwvBo8oRieapdYvgJkJn7HM4LaggUApxB9rBh70jTeR0-GOUd2C40zuw262dlUTX3knIR6SeZyasPY9_LfZusYyfjDxuH37XbpXDV1ZKPoACVQzrEO18vYcSHrEO8RGQjfOJ0QJM2mfXN2Z1yaI align="left")

Create Load Balancer

  
DNS Name will be like this: [akhil-p2-alb-1468339209.ap-northeast-1.elb.amazonaws.com](http://akhil-p2-alb-1468339209.ap-northeast-1.elb.amazonaws.com)

### **Add Alias record to Route 53**

![](https://lh7-us.googleusercontent.com/A4JhQqyyXGudvt6RBo2qENOArXMji3DBuATO6b05NJ32jNClNkiD6HtLzC2jyAR6WQt7FIruXbKTpAthCY--mecO-d7b8Pd7rzpXjOpS46GkW6N4Mx8kRkq_MC1g64Zz0iCp4-1tY4pBnEcGBJ1kTns align="left")

### **Redirect HTTP to HTTPS**

![](https://lh7-us.googleusercontent.com/WR7HUdM3Y5dynG_ik7xnXJjhtLi9bNvU76nop5dXB_7xlV8O9wWRkaOvrYDFp3UmPAu1kWsafnNhaym9oOPNDNAX5mMHGDgVBrR-4EEA0IVtsfox01NSoocUTc43w7d0TbnA-tO9SVIGLk4wMGiQbiE align="left")

![](https://lh7-us.googleusercontent.com/ejc8d2-96GZxdZ66ASkbC6WzSSzWRbJvf1BtsKNDjC-SkNtTGLjHdgXP6nHttKAhgneMFP0pM_1Fpin3d1mDvIKon84N8lbD4Cy5z-zfqKZ0hxoI1-1sxEYbTXE2p2FSBySk8M9qXuk_4UDwgIrYQN0 align="left")

![](https://lh7-us.googleusercontent.com/QTEEt_e8HGppt1e18DeWWYg-59Q9zOIficqnwj00IUTdDrwX4kr33ul-Dm5kIaLQiTiKGJixPm_4Et9dLSBYMcPJOzdULmpL3NaBKFBQs4gi7xDI_aPKR7ooeRdB92TMpEZKRi4Sp5YRz3px24JrLRI align="left")

![](https://lh7-us.googleusercontent.com/Wnw9cxz6DdScPCurcrZLkNVO1P8oveEnjH6x_nRlWji_qvxSUyp4qfLHDJCpwEUKy3RRqg5VaS05-LtootJLYAu7gc2uZ5bBHnGubxxZjvRqCZv4STg5RKp16YCoXkLFT66bYjbpBGpi6vTIp3TxSDk align="left")

**Edit Health check settings**

Since the health checks are reporting that the instances are Unhealthy, 

To troubleshoot, check the HTTP status code in your browser's dev tools.

It is giving a 302 redirect. So, change the health check path to /login to get a 200 OK.

![](https://lh7-us.googleusercontent.com/hWmnTtyFIXsxSRtLcd2BCo7PguPot7Z3e0srDxvWDbRVvthmrm1uipIzv4w1O8OBbszOT-hp-EtWq0G6PsCjTf2o8mEKN3-QbQVDO4nYP8EODL54GzZElJdUH6sYR6aQcBTXt20SnvSwbGF2y5mi2GY align="left")

# **Blue Green Deployment**

Note: The workflow has to be present in the default branch for Manual triggering to work via workflow\_dispatch. I changed the default branch to main-v2 for this project.

![](https://lh7-us.googleusercontent.com/XUviO_zINwL7wvwxRnG3tKQ1CB8EXeuKGzcvgOHY8vUm0THM-6d9KTlnWEZKlMqGr1MrCnsu1Fr-cyrX2xQwxfyDKJUmj0TD6Zil15dfoZr58pmSxbHbOsAS6K05ViF5aRnFxMsB-kBKULwe0ocvQdw align="left")

Added boolean inputs to the workflow for selective deployment...

```bash
on:
  workflow_dispatch:
    inputs:
      deploy-a:
        type: boolean
        description: 'Deploy to Server A?'
        required: true
        default: true
      deploy-b:
        type: boolean
        description: 'Deploy to Server B?'
        required: true
        default: true
```

Raised a PR to the main-v2 branch and merged the PR.

Now, there is an option to trigger the workflow manually with the inputs.

![](https://lh7-us.googleusercontent.com/GMfj_kuxpKJxyndNX1t4FssVw4N1HtsCCpE8TnaUzPt6Wx-qV4IFwo0oXqWSqZpUZKZvXEUVI6RxyOPts_vfP-4i16swl_KXj1S0BE8XWEfxes3o2TNQd8bPgaKtALpjxXxW3vk2vxG3Uu20b4rwdZs align="left")

Check one of the servers (B) &gt; Run

The workflow will run the deploy-b job only:

![](https://lh7-us.googleusercontent.com/olGudXeP4NJBvMP9736m2oaz1UArtBR31ASG3TRdv_1n6FJ5Yh4mM1-UfNe8kNQqBeQEgyocCXl50PHAhmeziMU5RMHdczmfLyMIAPCIBuuWUDj0JzurT8c8nkCfJ2xbwIW8U4qPTw6ec5nKXe8Xsjk align="left")

# **Results**

After the Blue/Green deployment, the app is available at the below URL:

[https://javaappv2.aws.melvincv.com/login](https://javaappv2.aws.melvincv.com/login)

![](https://lh7-us.googleusercontent.com/oDI49BCTVivjcl-vTMiGemc9ANfu2RT2Q66ldT2XvFhr1W7oPJZHdjlWWoE_wRWbSeSH4dYix0AbXgLGr74U9-3rrH5W97pcjMwhh_mbUycHMTeAOPAguxYfG-IY-0SFv6QLsAouTv1xC5W8LwtrZ7o align="left")
