├── .gitignore ├── README.md └── sonarqube.tf /.gitignore: -------------------------------------------------------------------------------- 1 | *.tfstate 2 | *.tfstate.backup 3 | *~ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aws-ecs-terraform-sonarqube 2 | A small SonarQube stack running on Amazon's Elastic Container Service, built using Terraform 3 | 4 | Prerequisites: 5 | 6 | * Terraform 7 | * AWS command-line credentials for Terraform to read (eg., in .aws/credentials) 8 | 9 | Things: 10 | 11 | * This ends up with the application being open to the internet. This might not be what you want. 12 | * I built this using ECS because I hadn't done that before - ECS has some other requirements that mean that if I were doing this again, I would use Elastic Beanstalk or even just a plain AMI since I ended up building a lot of things that weren't really about the app. 13 | * Backed to a very small RDS instance - again, could be more efficiently done with a MySQL installation on the same machine as the app server. 14 | * The IAM roles are a kludge, and could be made tighter and more specific. 15 | -------------------------------------------------------------------------------- /sonarqube.tf: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SonarQube: the slightly unnecessary ECS version. 4 | 5 | */ 6 | 7 | 8 | provider "aws" { 9 | region = "${var.region}" 10 | } 11 | 12 | 13 | variable region { 14 | default = "eu-west-1" 15 | } 16 | 17 | 18 | variable "images" { 19 | type = "map" 20 | 21 | default = { 22 | us-east-1 = "ami-04351e12" 23 | eu-west-1 = "ami-809f84e6" 24 | } 25 | } 26 | 27 | variable "zones" { 28 | type = "map" 29 | 30 | default = { 31 | us-east-1 = ["us-east-1a", "us-east-1b", "us-east-1c", "us-east-1d", "us-east-1e"] 32 | eu-west-1 = ["eu-west-1a", "eu-west-1b", "eu-west-1c"] 33 | } 34 | } 35 | 36 | resource "aws_security_group" "sonarqube_elb_sg" { 37 | 38 | name = "sonarqube-elb-sg" 39 | 40 | ingress { 41 | from_port = 80 42 | to_port = 80 43 | protocol = "tcp" 44 | cidr_blocks = ["0.0.0.0/0"] 45 | } 46 | 47 | egress { 48 | from_port = 0 49 | to_port = 0 50 | protocol = "-1" 51 | cidr_blocks = ["0.0.0.0/0"] 52 | } 53 | 54 | } 55 | 56 | resource "aws_security_group" "sonarqube_db_sg" { 57 | 58 | name = "sonarqube-db-sg" 59 | 60 | ingress { 61 | from_port = 0 62 | to_port = 0 63 | protocol = "-1" # FIXME figure out what the correct port is 64 | security_groups = ["${aws_security_group.sonarqube_ecs_instance_sg.id}"] 65 | } 66 | 67 | } 68 | 69 | resource "aws_security_group" "sonarqube_ecs_instance_sg" { 70 | 71 | name = "sonarqube-ecs-instance-sg" 72 | ingress { 73 | from_port = 9000 74 | to_port = 9000 75 | protocol = "tcp" 76 | security_groups = ["${aws_security_group.sonarqube_elb_sg.id}"] 77 | } 78 | 79 | egress { 80 | from_port = 0 81 | to_port = 0 82 | protocol = "-1" 83 | cidr_blocks = ["0.0.0.0/0"] 84 | } 85 | 86 | } 87 | 88 | 89 | resource "aws_db_instance" "sonarqube" { 90 | 91 | allocated_storage = 64 92 | engine = "mysql" 93 | storage_type = "standard" 94 | instance_class = "db.t1.micro" 95 | skip_final_snapshot = "true" # FIXME 96 | 97 | vpc_security_group_ids = ["${aws_security_group.sonarqube_db_sg.id}"] 98 | 99 | name = "sonar" 100 | 101 | username = "sonarqube" 102 | password = "sonarqube" 103 | 104 | parameter_group_name = "sonarqube-db-params" 105 | depends_on = ["aws_db_parameter_group.sonarqube"] # not automatic 106 | 107 | tags { 108 | Name = "sonarqube-db" 109 | } 110 | 111 | } 112 | 113 | 114 | resource "aws_db_parameter_group" "sonarqube" { 115 | 116 | name = "sonarqube-db-params" 117 | family = "mysql5.6" 118 | 119 | parameter { 120 | name = "max_allowed_packet" 121 | value = 268435456 122 | } 123 | 124 | tags { 125 | Name = "sonarqube-db-params" 126 | } 127 | 128 | } 129 | 130 | ## ECS CLUSTER 131 | 132 | resource "aws_ecs_cluster" "sonarqube" { 133 | 134 | name = "sonarqube-ecs-cluster" 135 | 136 | # can't have tags 137 | } 138 | 139 | resource "aws_launch_configuration" "sonarqube_ecs_nodes" { 140 | name_prefix = "sonarqube-ecs-cluster-" 141 | image_id = "${var.images[var.region]}" 142 | instance_type = "t2.micro" 143 | 144 | lifecycle { 145 | create_before_destroy = true 146 | } 147 | 148 | iam_instance_profile = "${aws_iam_instance_profile.sonarqube_ecs_host.name}" 149 | 150 | security_groups = ["${aws_security_group.sonarqube_ecs_instance_sg.name}"] 151 | 152 | user_data = <> /etc/ecs/ecs.config 155 | EOF 156 | 157 | } 158 | 159 | resource "aws_autoscaling_group" "sonarqube_cluster" { 160 | 161 | name = "sonarqube-asg" 162 | 163 | availability_zones = "${var.zones[var.region]}" 164 | 165 | max_size = 1 166 | min_size = 1 167 | desired_capacity = 1 168 | 169 | launch_configuration = "${aws_launch_configuration.sonarqube_ecs_nodes.name}" 170 | 171 | health_check_type = "ELB" 172 | 173 | } 174 | 175 | 176 | resource "aws_iam_instance_profile" "sonarqube_ecs_host" { 177 | name = "sonarqube-ecs-host" 178 | role = "${aws_iam_role.sonarqube_ecs.name}" 179 | } 180 | 181 | 182 | resource "aws_ecs_task_definition" "sonarqube_web" { 183 | 184 | family = "sonar-web" 185 | network_mode = "bridge" 186 | 187 | container_definitions = <