DEV Community

Cover image for Step-by-Step Guide to Setting Up and Deploying SonarQube on an AWS EC2 Instance Using AWS Cloud Development Kit (CDK)
Kevin Lactio Kemta
Kevin Lactio Kemta

Posted on

Step-by-Step Guide to Setting Up and Deploying SonarQube on an AWS EC2 Instance Using AWS Cloud Development Kit (CDK)

Day 015 - 100DaysAWSIaCDevopsChallenge

Today in my serie of 100 days of code challenge, I am going to show you step by step how to deploy SonarQube Server into an AWS EC2 Instance using AWS Cloud Development Kit (CDK).

What is SonarQube ?

SonarQube is an open-source application that automates the process of code quality analysis. It scans your codebase to detect issues such as bugs, security vulnerabilities, code smells (maintainability issues), and technical debt. It provides comprehensive reports and metrics on your code quality, helping teams identify and fix problems early in the development process.

Why do you need to use it in your application ?

SonarQube helps developers write clean, secure, and maintainable code by offering real-time insights and automated feedback during the software development lifecycle. Using SonarQube in your application development provides several critical benefits:

  1. Code Quality Checks: Detects issues related to code quality, including bugs and inefficient code practices.
  2. Security Vulnerabilities: Identifies potential security risks such as SQL injections and cross-site scripting (XSS).
  3. Reduces Technical Debt: Measures code complexity, duplication, and other factors that affect the maintainability of your project.
  4. Supports Continuous Integration: Can integrate with CI/CD pipelines (e.g., Jenkins, GitLab, Github-Actions) for automatic code scanning during development.

how to install SonarQube on an AWS EC2 Instance?

Image post

To achieve this, we will use AWS CDK to create the infrastructure required. The infrastructure consists of the following components:

  • VPC and a Public Subnetto manage network traffic within the server.
  • Internet Gatewayto allow internet communication between our secured cloud environment and external networks.
  • Security group to control access to the server based on IP addresses and ports connections.
  • EC2 Instance to host the SonarQube server (including the Web Server, ElasticSearch, and Postgres Database).
  • Key Pair the private key that allows external hosts to connect securely to the instance.

Diagram

Create the CDK Stack
VPC, Subnet and Internet Gateway
interface CustomProps extends StackProps {
  cidr: string
}

export class SonarServerInfrastructureStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: CustomProps) {
    super(scope, id, props)
    const vpc = new ec2.Vpc(this, 'PublicVpcResource', {
      vpcName: 'sonarqube-server-vpc',
      enableDnsHostnames: true,
      enableDnsSupport: true,
      ipAddresses: ec2.IpAddresses.cidr(props.cidr),
      createInternetGateway: true,
      subnetConfiguration: [{
        subnetType: ec2.SubnetType.PUBLIC,
        name: 'sq-public-subnet',
        mapPublicIpOnLaunch: true
      }]
    })
  }
}
Enter fullscreen mode Exit fullscreen mode
  • enableDnsHostnames/enableDnsSupport - These two options ensure that instances launched in the VPC will have DNS resolution capabilities.
  • ipAddresses - The VPC's CIDR block. It is provided as props.cidr, making it configurable.
  • subnetConfiguration - Configures a public subnet. The configuration creates a public subnet ('sq-public-subnet'), with the option to map public IP addresses to instances on launch
  • createInternetGateway - Automatically creates an Internet Gateway for external communication
Security group
const sonarQubeSG = new ec2.SecurityGroup(this, 'SonarQubeSecuGroupResource', {
    securityGroupName: 'SonarSecurityGroup',
    allowAllIpv6Outbound: false,
    allowAllOutbound: true,
    description: 'Inbound and outbound traffic to sonarqube server',
    disableInlineRules: false,
    vpc: vpc,
})
sonarQubeSG.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.SSH, 'Allow ssh traffic')
sonarQubeSG.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.HTTPS, 'Allow https traffic')
sonarQubeSG.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(9000), 'Allow custom port traffic')
Enter fullscreen mode Exit fullscreen mode

To allow any IP address (i.e., 0.0.0.0/0) to connect to the SonarQube server on port 9000, we have defined an ingress rule in the Security Group that is attached to your EC2 instance. This allows traffic to the SonarQube application, which runs on port 9000.

SonarQube Web Server (EC2 Instance) + KeyPair
const keypair = new ec2.KeyPair(this, 'KeyPairResource', {
    keyPairName: 'sonarqube-server-keypair',
    type: ec2.KeyPairType.RSA,
    format: ec2.KeyPairFormat.PEM
})
const sonarQubeServer = new ec2.Instance(this, 'SonarQubeServerInstanceResource', {
    instanceName: 'Sonarqube-server',
    vpc,
    securityGroup: sonarQubeSG,
    instanceType: ec2.InstanceType.of(
    ec2.InstanceClass.T2, ec2.InstanceSize.MEDIUM),
    keyPair: keypair,
    associatePublicIpAddress: true,
    userDataCausesReplacement: true,
    machineImage: ec2.MachineImage.lookup({
        name: '*ubuntu*',
        filters: {
            'image-id': ['ami-0e86e20dae9224db8'],
            'architecture': ['x86_64']
        },
        windows: false
    }),
    vpcSubnets: vpc.selectSubnets({ subnetType: ec2.SubnetType.PUBLIC }),
    blockDevices: [
        {
            deviceName: '/dev/sda1',
            mappingEnabled: true,// to override the default Volume mounted automatically on creation
            volume: ec2.BlockDeviceVolume.ebs(30, {
            volumeType: ec2.EbsDeviceVolumeType.GP3,
            deleteOnTermination: true
            })
        }
    ]
})

const userDataEncoded = ec2.UserData.forLinux()
userDataEncoded.addCommands(fs.readFileSync('./assets/sonarqube-server.sh', 'utf-8'))
sonarQubeServer.addUserData(userDataEncoded.render())

// display the ARN of private key in AWS Parameter Store
new CfnOutput(this, 'SonarqubeKeyPairParamArn', {
    value: keypair.privateKey.parameterArn
})
// Optionally store the private key locally
fs.writeFileSync('./assets/private_key.pem', keypair.privateKey.stringValue, {
    flag: 'w', encoding: 'utf-8'
})
Enter fullscreen mode Exit fullscreen mode
  • Key Pair Creation - We create an RSA key pair that will be used to securely connect to the EC2 instance. The private key is stored either in AWS Parameter Store, retrieved using keypair.privateKey.parameterArn or locally as a PEM file private_key.pem.
  • EC2 Instance Configuration - we create a new EC2 instance named Sonarqube-server. The instance is provisioned in the public subnet of the VPC and is attached to the security group that allows access to port 9000. The AMI is filtered using its ID and architecture (ami-0e86e20dae9224db8), and the instance type is t2.medium. The instance has a block device configured with a 30Go GP3 EBS volume that will be deleted upon termination.
  • User Data Configuration - The instance is initialized with a user data script from assets/sonarqube-server.sh, which is executed upon instance launch to configure SonarQube on the server. The bolow code is the content of user data bootstrap source:

    # Update package index
    sudo apt update
    
    # Install OpenJDK 17, zip and unzip
    sudo apt install -y openjdk-17-jdk zip unzip
    
    # Set up the JAVA_HOME environment variable
    echo "export JAVA_HOME=$(dirname $(dirname $(readlink -f $(which java))))" | sudo tee /etc/profile.d/jdk.sh
    source /etc/profile.d/jdk.sh
    
    # Installing Postgres
    sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" /etc/apt/sources.list.d/pgdg.list'
    wget -q https://www.postgresql.org/media/keys/ACCC4CF8.asc -O - | sudo apt-key add -
    sudo apt install postgresql postgresql-contrib -y
    
    # Initiate the database server to begin operations
    sudo systemctl enable postgresql
    sudo systemctl start postgresql
    
    # Create database and its owner
    sudo -u postgres psql -c "CREATE DATABASE sonarqubedb;"
    sudo -u postgres psql -c "CREATE USER sonar WITH ENCRYPTED PASSWORD 'sonar';"
    sudo -u postgres psql -c "GRANT ALL ON SCHEMA public TO sonar;"
    sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE sonarqubedb TO sonar;"
    sudo -u postgres psql -c "ALTER DATABASE sonarqubedb OWNER TO sonar;"
    
    # download sonarqube server - community version
    # shellcheck disable=SC2317
    wget https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-9.9.6.92038.zip
    
    # unzip content
    # shellcheck disable=SC2317
    unzip sonarqube-*.zip
    sudo mv sonarqube-9.9.6.92038 sonarqube
    sudo mv sonarqube /opt/
    
    # Create sonarqube user and its group
    sudo groupadd sonarqube
    sudo useradd -d /opt/sonarqube -g sonarqube sonarqube
    sudo chown -R sonarqube:sonarqube /opt/sonarqube
    
    # Insert user runner after `APP_NAME="SonarQube"` line
    sudo sed -i 's/APP_NAME="SonarQube"/APP_NAME="SonarQube"\n\nRUN_AS_USER=sonarqube/' /opt/sonarqube/bin/linux-x86-64/sonar.sh
    
    # create sonarqube launcher service
    sudo tee /etc/systemd/system/sonarqube.service <<EOF
    [Unit]
    Description=SonarQube service
    After=syslog.target network.target
    [Service]
    Type=forking
    User=sonarqube
    Group=sonarqube
    PermissionsStartOnly=true
    ExecStart=/opt/sonarqube/bin/linux-x86-64/sonar.sh start
    ExecStop=/opt/sonarqube/bin/linux-x86-64/sonar.sh stop
    StandardOutput=journal
    LimitNOFILE=131072
    LimitNPROC=8192
    TimeoutStartSec=5
    Restart=always
    SuccessExitStatus=143
    
    [Install]
    WantedBy=multi-user.target
    EOF
    
    # Configuring the database
    sudo tee /opt/sonarqube/conf/sonar.properties <<EOF
    sonar.web.port=9000
    sonar.jdbc.username=sonar
    sonar.jdbc.password=sonar
    sonar.jdbc.url=jdbc:postgresql://localhost:5432/sonarqubedb
    EOF
    
    # Start SonarQube Server
    sudo systemctl enable sonarqube.service
    sudo systemctl start sonarqube.service
    

    For better security, it is recommended not to hard-code sensitive data such as database usernames and passwords. Instead, use services like AWS Systems Manager Parameter Store, AWS Secrets Manager, or alternatives such as HashiCorp Vault to securely manage and access sensitive information.

Deploy the stack

const app = new cdk.App()
new SonarServerInfrastructureStack(app, 'Day015Stack', {
  cidr: '10.0.0.1/16',
  env: {
    region: process.env.CDK_DEFAULT_REGION,
    account: process.env.CDK_DEFAULT_ACCOUNT
  }
})
app.synth()
Enter fullscreen mode Exit fullscreen mode
git clone https://github.com/nivekalara237/100DaysTerraformAWSDevops.git

cd 100DaysTerraformAWSDevops/day_015
export PERSONAL_ACCESS_SECRET_ARN="arn:aws:secretsmanager:<REGION>:<ACCOUNT>:secret:<SECRET_ID>"

cdk deploy --profile cdk-user Day015Stack
Enter fullscreen mode Exit fullscreen mode

Below are the results after deployment

Deploy the stack

const app = new cdk.App()
new SonarServerInfrastructureStack(app, 'Day015Stack', {
  cidr: '10.0.0.1/16',
  env: {
    region: process.env.CDK_DEFAULT_REGION,
    account: process.env.CDK_DEFAULT_ACCOUNT
  }
})
app.synth()
Enter fullscreen mode Exit fullscreen mode

Open the terminal anywhere and run the following commande:

git clone https://github.com/nivekalara237/100DaysTerraformAWSDevops.git
cd 100DaysTerraformAWSDevops/day_015
cdk deploy --profile cdk-user Day015Stack
Enter fullscreen mode Exit fullscreen mode

After the deployment, open the browser and type this url http://PUBLIC_IP_or_DNS:9000

SonarQube Web Server Result

__

πŸ₯³βœ¨
We have reached the end of the article.
Thank you so much πŸ™‚


Your can find the full source code on GitHub Repo↗

Top comments (1)

Collapse
 
respect17 profile image
Kudzai Murimi

Thanks for sharing with the community, keep up the good work