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:
- Code Quality Checks: Detects issues related to code quality, including bugs and inefficient code practices.
- Security Vulnerabilities: Identifies potential security risks such as SQL injections and cross-site scripting (XSS).
- Reduces Technical Debt: Measures code complexity, duplication, and other factors that affect the maintainability of your project.
- 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?
To achieve this, we will use AWS CDK to create the infrastructure required. The infrastructure consists of the following components:
-
VPC
and aPublic Subnet
to manage network traffic within the server. -
Internet Gateway
to 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.
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
}]
})
}
}
-
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 asprops.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')
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'
})
-
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 inAWS 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 namedSonarqube-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 ist2.medium
. The instance has a block device configured with a30Go
GP3 EBS volume that will be deleted upon termination. -
User Data Configuration
- The instance is initialized with a user data script fromassets/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 asHashiCorp 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()
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
After the deployment, open the browser and type this url http://PUBLIC_IP_or_DNS:9000
🥳✨
We have reached the end of the article.
Thank you so much 🙂
Your can find the full source code on GitHub Repo↗
Top comments (4)
💪💪💪
Nice article!
Thanks for sharing with the community, keep up the good work
Thank you @respect17 for the support 😇