├── 1- Create VPC ├── create_vpc.tf ├── provider.tf └── variables.tf ├── 2- Create AMI ├── apache-ami.json ├── config │ └── userdata.sh ├── info.txt ├── playbook │ ├── ansible.cfg │ ├── inventory.txt │ ├── main.yml │ ├── roles │ │ └── apache-tomcat │ │ │ ├── defaults │ │ │ └── main.yml │ │ │ ├── handlers │ │ │ └── main.yml │ │ │ ├── tasks │ │ │ └── main.yml │ │ │ └── templates │ │ │ ├── server.xml.j2 │ │ │ └── tomcat.service.j2 │ └── variables.yml └── vars.json ├── 3 - Create ASG ├── cloud-config.yml ├── create_autoscaling_group.tf ├── create_elb.tf ├── create_key.tf ├── create_security_groups.tf ├── fetch_id.tf ├── key.pub ├── outputs.tf ├── provider.tf └── vars.tf ├── Independent apache-tomcat Configuration ├── ansible.cfg ├── inventory.txt ├── main.yml ├── roles │ └── apache-tomcat │ │ ├── defaults │ │ └── main.yml │ │ ├── handlers │ │ └── main.yml │ │ ├── tasks │ │ └── main.yml │ │ └── templates │ │ ├── server.xml.j2 │ │ └── tomcat.service.j2 └── variables.yml └── README.md /1- Create VPC/create_vpc.tf: -------------------------------------------------------------------------------- 1 | /* 2 | This terraform script will create a VPC and perform the following actions 3 | 1) Create Internet Gateway for public subnets 4 | 2) Create Public Subnets in multiple AZs and update route rules to use Internet Gateway 5 | 3) Create a NAT gateway service in a single AZ to be associated with 6 | 4) Create Private Subnets and update route rules to use NAT Gateway service 7 | */ 8 | 9 | /* 10 | Create a VPC and setup an AWS Internet Gateway 11 | */ 12 | 13 | resource "aws_vpc" "default" { 14 | cidr_block = "${var.vpc_cidr}" 15 | enable_dns_hostnames = true 16 | tags { 17 | Name = "kris-test-vpc" 18 | } 19 | } 20 | 21 | data "aws_availability_zones" "apache" {} 22 | 23 | resource "aws_internet_gateway" "default" { 24 | vpc_id = "${aws_vpc.default.id}" 25 | } 26 | 27 | /* 28 | Setup public Subnets 29 | */ 30 | resource "aws_subnet" "zone-a-public" { 31 | vpc_id = "${aws_vpc.default.id}" 32 | 33 | cidr_block = "${var.public_subnet_cidr_a}" 34 | availability_zone = "${data.aws_availability_zones.apache.names[0]}" 35 | 36 | tags { 37 | Name = "public_subneta" 38 | } 39 | } 40 | 41 | resource "aws_subnet" "zone-b-public" { 42 | vpc_id = "${aws_vpc.default.id}" 43 | 44 | cidr_block = "${var.public_subnet_cidr_b}" 45 | availability_zone = "${data.aws_availability_zones.apache.names[1]}" 46 | 47 | tags { 48 | Name = "public_subnetb" 49 | } 50 | } 51 | 52 | resource "aws_subnet" "zone-c-public" { 53 | vpc_id = "${aws_vpc.default.id}" 54 | 55 | cidr_block = "${var.public_subnet_cidr_c}" 56 | availability_zone = "${data.aws_availability_zones.apache.names[2]}" 57 | 58 | tags { 59 | Name = "public_subnetc" 60 | } 61 | } 62 | 63 | /* Public Subnet Route Rules 64 | */ 65 | resource "aws_route_table" "zone-a-public" { 66 | vpc_id = "${aws_vpc.default.id}" 67 | 68 | route { 69 | cidr_block = "0.0.0.0/0" 70 | gateway_id = "${aws_internet_gateway.default.id}" 71 | } 72 | 73 | tags { 74 | Name = "route_public_subneta" 75 | } 76 | } 77 | 78 | resource "aws_route_table_association" "zone-a-public" { 79 | subnet_id = "${aws_subnet.zone-a-public.id}" 80 | route_table_id = "${aws_route_table.zone-a-public.id}" 81 | } 82 | 83 | resource "aws_route_table" "zone-b-public" { 84 | vpc_id = "${aws_vpc.default.id}" 85 | 86 | route { 87 | cidr_block = "0.0.0.0/0" 88 | gateway_id = "${aws_internet_gateway.default.id}" 89 | } 90 | 91 | tags { 92 | Name = "route_public_subnetb" 93 | } 94 | } 95 | 96 | resource "aws_route_table_association" "zone-b-public" { 97 | subnet_id = "${aws_subnet.zone-b-public.id}" 98 | route_table_id = "${aws_route_table.zone-b-public.id}" 99 | } 100 | 101 | resource "aws_route_table" "zone-c-public" { 102 | vpc_id = "${aws_vpc.default.id}" 103 | 104 | route { 105 | cidr_block = "0.0.0.0/0" 106 | gateway_id = "${aws_internet_gateway.default.id}" 107 | } 108 | 109 | tags { 110 | Name = "route_public_subnetc" 111 | } 112 | } 113 | 114 | resource "aws_route_table_association" "zone-c-public" { 115 | subnet_id = "${aws_subnet.zone-c-public.id}" 116 | route_table_id = "${aws_route_table.zone-c-public.id}" 117 | } 118 | 119 | /* 120 | AWS NAT Gateway Setup in a Public Subnet 121 | */ 122 | 123 | resource "aws_eip" "natEIP" { 124 | vpc = true 125 | } 126 | 127 | resource "aws_nat_gateway" "nat_gateway_public" { 128 | allocation_id = "${aws_eip.natEIP.id}" 129 | subnet_id = "${aws_subnet.zone-b-public.id}" 130 | depends_on = ["aws_internet_gateway.default"] 131 | } 132 | 133 | 134 | /* 135 | Private Subnet setup 136 | */ 137 | 138 | resource "aws_subnet" "zone-a-private" { 139 | vpc_id = "${aws_vpc.default.id}" 140 | 141 | cidr_block = "${var.private_subnet_cidr_a}" 142 | availability_zone = "${data.aws_availability_zones.apache.names[0]}" 143 | 144 | tags { 145 | Name = "Private Subnet Zone A" 146 | } 147 | } 148 | 149 | resource "aws_subnet" "zone-b-private" { 150 | vpc_id = "${aws_vpc.default.id}" 151 | 152 | cidr_block = "${var.private_subnet_cidr_b}" 153 | availability_zone = "${data.aws_availability_zones.apache.names[1]}" 154 | 155 | tags { 156 | Name = "Private Subnet Zone B" 157 | } 158 | } 159 | 160 | resource "aws_subnet" "zone-c-private" { 161 | vpc_id = "${aws_vpc.default.id}" 162 | 163 | cidr_block = "${var.private_subnet_cidr_c}" 164 | availability_zone = "${data.aws_availability_zones.apache.names[2]}" 165 | 166 | tags { 167 | Name = "Private Subnet Zone C" 168 | } 169 | } 170 | 171 | /* 172 | Private Subnet Route Rules to be routed to NAT gateway 173 | */ 174 | 175 | resource "aws_route_table" "zone-a-private" { 176 | vpc_id = "${aws_vpc.default.id}" 177 | 178 | route { 179 | cidr_block = "0.0.0.0/0" 180 | nat_gateway_id = "${aws_nat_gateway.nat_gateway_public.id}" 181 | } 182 | 183 | tags { 184 | Name = "Private Subnet A" 185 | } 186 | } 187 | 188 | resource "aws_route_table_association" "zone-a-private" { 189 | subnet_id = "${aws_subnet.zone-a-private.id}" 190 | route_table_id = "${aws_route_table.zone-a-private.id}" 191 | } 192 | 193 | 194 | resource "aws_route_table" "zone-b-private" { 195 | vpc_id = "${aws_vpc.default.id}" 196 | 197 | route { 198 | cidr_block = "0.0.0.0/0" 199 | nat_gateway_id = "${aws_nat_gateway.nat_gateway_public.id}" 200 | } 201 | 202 | tags { 203 | Name = "Private Subnet B" 204 | } 205 | } 206 | 207 | resource "aws_route_table_association" "zone-b-private" { 208 | subnet_id = "${aws_subnet.zone-b-private.id}" 209 | route_table_id = "${aws_route_table.zone-b-private.id}" 210 | } 211 | 212 | resource "aws_route_table" "zone-c-private" { 213 | vpc_id = "${aws_vpc.default.id}" 214 | 215 | route { 216 | cidr_block = "0.0.0.0/0" 217 | nat_gateway_id = "${aws_nat_gateway.nat_gateway_public.id}" 218 | } 219 | 220 | tags { 221 | Name = "Private Subnet C" 222 | } 223 | } 224 | 225 | resource "aws_route_table_association" "zone-c-private" { 226 | subnet_id = "${aws_subnet.zone-c-private.id}" 227 | route_table_id = "${aws_route_table.zone-c-private.id}" 228 | } -------------------------------------------------------------------------------- /1- Create VPC/provider.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "${var.aws_region}" 3 | access_key = "${var.access_key}" 4 | secret_key = "${var.secret_key}" 5 | } -------------------------------------------------------------------------------- /1- Create VPC/variables.tf: -------------------------------------------------------------------------------- 1 | variable "access_key" { 2 | default = "" 3 | } 4 | 5 | variable "secret_key" { 6 | default = "" 7 | } 8 | 9 | variable "aws_region" { 10 | description = "Region for VPC" 11 | default = "ap-southeast-2" 12 | } 13 | 14 | variable "vpc_cidr" { 15 | description = "CIDR for the whole VPC" 16 | default = "192.168.0.0/22" 17 | } 18 | 19 | /* 20 | Divide 192.168.0.0/25 into AZ specific public subnets. 21 | 3 for now for 3 AZs 22 | */ 23 | 24 | variable "public_subnet_cidr_a" { 25 | description = "CIDR for the Public Subnet" 26 | default = "192.168.0.0/27" 27 | } 28 | 29 | variable "public_subnet_cidr_b" { 30 | description = "CIDR for the Public Subnet" 31 | default = "192.168.0.32/27" 32 | } 33 | 34 | variable "public_subnet_cidr_c" { 35 | description = "CIDR for the Public Subnet" 36 | default = "192.168.0.64/27" 37 | } 38 | 39 | /* 40 | Divide 192.168.0.128/25 into AZ specific private subnets. 41 | 3 for now for 3 AZs 42 | */ 43 | 44 | variable "private_subnet_cidr_a" { 45 | description = "CIDR for the Private Subnet" 46 | default = "192.168.0.128/27" 47 | } 48 | 49 | variable "private_subnet_cidr_b" { 50 | description = "CIDR for the Private Subnet" 51 | default = "192.168.0.160/27" 52 | } 53 | 54 | variable "private_subnet_cidr_c" { 55 | description = "CIDR for the Private Subnet" 56 | default = "192.168.0.192/27" 57 | } -------------------------------------------------------------------------------- /2- Create AMI/apache-ami.json: -------------------------------------------------------------------------------- 1 | { 2 | "builders": [ 3 | { 4 | "access_key": "{{user `aws_access_key`}}", 5 | "ami_name": "apache-tomcat-{{user `image_version`}}", 6 | "instance_type": "{{user `aws_instance_type`}}", 7 | "region": "{{user `aws_region`}}", 8 | "secret_key": "{{user `aws_secret_key`}}", 9 | "source_ami": "{{user `aws_ami_image`}}", 10 | "ssh_username": "ubuntu", 11 | "tags": { 12 | "Name":"apache-tomcat-{{user `image_version`}}", 13 | "OS_Version": "Ubuntu 16.04", 14 | "Description": "Golden image for apache tomcat" 15 | }, 16 | "type": "amazon-ebs" 17 | 18 | } 19 | ], 20 | "provisioners": [ 21 | { 22 | "type": "shell", 23 | "inline": [ 24 | "sleep 20", 25 | "sudo apt-get update", 26 | "sudo apt-get install python-simplejson -yq", 27 | "sudo apt-get install python -yq" 28 | ] 29 | }, 30 | { 31 | "type": "ansible", 32 | "playbook_file": "playbook/main.yml" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /2- Create AMI/config/userdata.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo apt-get install python-simplejson -y -------------------------------------------------------------------------------- /2- Create AMI/info.txt: -------------------------------------------------------------------------------- 1 | 1. please use below for builder if you don't want to specify the source-ami yourself. 2 | 3 | Just replace the whole ["source_ami": "{{user `aws_ami_image`}}", ]part with below: 4 | 5 | 6 | "source_ami_filter": { 7 | "filters": { 8 | "virtualization-type": "hvm", 9 | "name": "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*", 10 | "root-device-type": "ebs" 11 | }, 12 | "owners": ["099720109477"], 13 | "most_recent": true 14 | }, 15 | 16 | 2. If you want to add user data: 17 | "user_data_file": "config/userdata.sh" 18 | 19 | -------------------------------------------------------------------------------- /2- Create AMI/playbook/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | inventory = inventory.txt 3 | host_key_checking = False 4 | log_path=/tmp/ansible.log 5 | deprecation_warnings = False 6 | command_warnings = False -------------------------------------------------------------------------------- /2- Create AMI/playbook/inventory.txt: -------------------------------------------------------------------------------- 1 | [hosts] -------------------------------------------------------------------------------- /2- Create AMI/playbook/main.yml: -------------------------------------------------------------------------------- 1 | - hosts: all 2 | become: yes 3 | vars_files: 4 | - variables.yml 5 | roles: 6 | - role: apache-tomcat -------------------------------------------------------------------------------- /2- Create AMI/playbook/roles/apache-tomcat/defaults/main.yml: -------------------------------------------------------------------------------- 1 | package_list: 2 | - apache2 3 | - mysql-server 4 | - default-jdk 5 | - authbind 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /2- Create AMI/playbook/roles/apache-tomcat/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: restart apache2 4 | service: 5 | name: apache2 6 | state: restarted 7 | 8 | - name: restart tomcat 9 | service: 10 | name: tomcat 11 | state: restarted -------------------------------------------------------------------------------- /2- Create AMI/playbook/roles/apache-tomcat/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: install packages - apache2, mysql, authbind, java 2 | apt: name={{ item }} update_cache=yes state=present 3 | with_items: 4 | - "{{package_list}}" 5 | 6 | - name: install mod_jk 7 | apt: name=libapache2-mod-jk state=present 8 | notify: 9 | - restart apache2 10 | 11 | - name: ensure mysql is running and starts on boot 12 | service: name=mysql state=started enabled=true 13 | 14 | - name: create tomcat group 15 | group: 16 | state: present 17 | name: tomcat 18 | 19 | - name: create tomcat user (nobody should login to tomcat account) 20 | user: 21 | name: tomcat 22 | group: tomcat 23 | shell: /bin/false 24 | home: /opt/tomcat 25 | 26 | - name: create directory for tomcat 27 | file: 28 | path: /opt/tomcat 29 | state: directory 30 | 31 | - name: download tomcat package 32 | get_url: url={{tomcat_url}} dest='/tmp/' 33 | 34 | - name: unarchive tomcat package 35 | shell: "tar xzvf /tmp/apache-tomcat-8*tar.gz -C /opt/tomcat --strip-components=1" 36 | 37 | - name: change tomcat directory permissions 38 | file: 39 | path: /opt/tomcat 40 | state: directory 41 | owner: tomcat 42 | group: tomcat 43 | recurse: yes 44 | 45 | - name: change mode for conf directory 46 | file: 47 | path: /opt/tomcat/conf 48 | state: directory 49 | owner: tomcat 50 | group: tomcat 51 | recurse: yes 52 | mode: "g+x" 53 | 54 | - name: create systemd service file for tomcat 55 | template: 56 | src: tomcat.service.j2 57 | dest: /etc/systemd/system/tomcat.service 58 | 59 | - name: Reload systemd daemon & start tomcat 60 | systemd: 61 | daemon_reload: yes 62 | name: tomcat 63 | enabled: yes 64 | state: started 65 | 66 | - name: replace tomcat server.xml 67 | template: 68 | src: server.xml.j2 69 | dest: /opt/tomcat/conf/server.xml 70 | notify: 71 | - restart tomcat 72 | 73 | - name: configure apache 74 | replace: dest=/etc/apache2/sites-enabled/000-default.conf regexp='^' replace="JkMount /* ajp13_worker \n " 75 | notify: 76 | - restart apache2 77 | 78 | - name: setup simple tomcat application 79 | get_url: url=https://community.jboss.org/servlet/JiveServlet/download/588259-27006/clusterjsp.war dest='/opt/tomcat/webapps' 80 | notify: 81 | - restart tomcat 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /2- Create AMI/playbook/roles/apache-tomcat/templates/server.xml.j2: -------------------------------------------------------------------------------- 1 | 2 | 18 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 37 | 38 | 41 | 46 | 47 | 48 | 53 | 54 | 55 | 56 | 60 | 61 | 62 | 69 | 72 | 73 | 79 | 87 | 96 | 102 | 114 | 115 | 116 | 117 | 118 | 119 | 124 | 125 | 128 | 129 | 130 | 133 | 136 | 137 | 139 | 140 | 144 | 146 | 147 | 148 | 150 | 151 | 153 | 156 | 157 | 160 | 163 | 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /2- Create AMI/playbook/roles/apache-tomcat/templates/tomcat.service.j2: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Apache Tomcat Web Application Container 3 | After=network.target 4 | 5 | [Service] 6 | Type=forking 7 | 8 | Environment=JAVA_HOME=/usr/lib/jvm/{{ java_home }}/jre 9 | Environment=CATALINA_PID=/opt/tomcat/temp/tomcat.pid 10 | Environment=CATALINA_HOME=/opt/tomcat 11 | Environment=CATALINA_BASE=/opt/tomcat 12 | Environment='CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC' 13 | Environment='JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom' 14 | 15 | ExecStart=/opt/tomcat/bin/startup.sh 16 | ExecStop=/opt/tomcat/bin/shutdown.sh 17 | 18 | User=tomcat 19 | Group=tomcat 20 | UMask=0007 21 | RestartSec=10 22 | Restart=always 23 | 24 | [Install] 25 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /2- Create AMI/playbook/variables.yml: -------------------------------------------------------------------------------- 1 | java_home: java-1.8.0-openjdk-amd64 2 | tomcat_url: http://apache.mirror.digitalpacific.com.au/tomcat/tomcat-8/v8.5.24/bin/apache-tomcat-8.5.24.tar.gz 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /2- Create AMI/vars.json: -------------------------------------------------------------------------------- 1 | { 2 | "aws_access_key": "", 3 | "aws_secret_key": "", 4 | "aws_region": "ap-southeast-2", 5 | "aws_ami_image": "ami-cab258a8", 6 | "aws_instance_type": "t2.small", 7 | "image_version" : "0.1" 8 | } 9 | -------------------------------------------------------------------------------- /3 - Create ASG/cloud-config.yml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | users: 3 | - name: username 4 | ssh-authorized-keys: 5 | - ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA3SDe0254+8pHwDn9oKfAla5wbM0izvp0wQ3lUUd2frnvLqpW91zGU0hIaHIrv4uukU64HpahpJxB1PXHTnr/VAZ01QEf96trzGWuDqKLVa/yCKRum//+DsVHdquM3TOwdZt3BXVBVe3dOWe7UqeTDzEZi8ncTgt224mDKGxIW1h53fhSu6H7g0nLXl7RnFFhfw4xuDKGAVFJ6CllQtlKWGxlTpZRsX5e/4HD2smqNl0sCSCNNOpzVp9W88RkPZF8p6cKK4uwDs6vNO62J11wkzFNjW85gZFHzzJwu2e/53swnxw== 6 | sudo: ['ALL=(ALL) NOPASSWD:ALL'] 7 | shell: /bin/bash 8 | home: /home/username 9 | groups: wheel,sudo 10 | -------------------------------------------------------------------------------- /3 - Create ASG/create_autoscaling_group.tf: -------------------------------------------------------------------------------- 1 | resource "aws_launch_configuration" "apache" { 2 | name = "apache-launch-configuration" 3 | image_id = "${data.aws_ami.apache.id}" 4 | instance_type = "${var.instance_type}" 5 | security_groups = ["${aws_security_group.apache.id}"] 6 | user_data = "${file("cloud-config.yml")}" 7 | key_name = "${aws_key_pair.kris.id}" 8 | } 9 | 10 | resource "aws_autoscaling_group" "apachescale" { 11 | name = "apacge-asg" 12 | launch_configuration = "${aws_launch_configuration.apache.id}" 13 | availability_zones = ["${data.aws_availability_zones.apache.names}"] 14 | max_size = "${var.max_instance_asg}" 15 | min_size = 3 16 | desired_capacity = 3 17 | wait_for_elb_capacity = 3 18 | health_check_type = "ELB" 19 | load_balancers = ["${aws_elb.apachelb.id}"] 20 | vpc_zone_identifier = ["${data.aws_subnet.public_a.id}","${data.aws_subnet.public_b.id}","${data.aws_subnet.public_c.id}"] 21 | tag { 22 | key = "Name" 23 | value = "apachetomcat" 24 | propagate_at_launch = "true" 25 | } 26 | } 27 | 28 | resource "aws_autoscaling_policy" "apache-memory-up" { 29 | adjustment_type = "ChangeInCapacity" 30 | autoscaling_group_name = "${aws_autoscaling_group.apachescale.id}" 31 | name = "apache-memory-up" 32 | scaling_adjustment = 1 33 | cooldown = 300 34 | } 35 | 36 | resource "aws_autoscaling_policy" "apache-cpu-up" { 37 | adjustment_type = "ChangeInCapacity" 38 | autoscaling_group_name = "${aws_autoscaling_group.apachescale.id}" 39 | name = "apache-cpu-up" 40 | scaling_adjustment = 1 41 | cooldown = 300 42 | } 43 | 44 | resource "aws_cloudwatch_metric_alarm" "memory-alarm-up" { 45 | alarm_name = "memory-alarm-up" 46 | comparison_operator = "GreaterThanOrEqualToThreshold" 47 | evaluation_periods = 2 48 | metric_name = "MemoryUtilization" 49 | namespace = "AWS/EC2" 50 | period = 120 51 | threshold = 80 52 | statistic = "Average" 53 | alarm_actions = ["${aws_autoscaling_policy.apache-memory-up.arn}"] 54 | dimensions {AutoScalingGroupName = "${aws_autoscaling_group.apachescale.id}"} 55 | 56 | } 57 | 58 | resource "aws_cloudwatch_metric_alarm" "cpu-alarm-up" { 59 | alarm_name = "cpu-alarm-up" 60 | comparison_operator = "GreaterThanOrEqualToThreshold" 61 | evaluation_periods = 2 62 | metric_name = "CPUUtilization" 63 | namespace = "AWS/EC2" 64 | period = 120 65 | threshold = 80 66 | statistic = "Average" 67 | alarm_actions = ["${aws_autoscaling_policy.apache-cpu-up.arn}"] 68 | dimensions {AutoScalingGroupName = "${aws_autoscaling_group.apachescale.id}"} 69 | 70 | } 71 | 72 | resource "aws_autoscaling_policy" "apache-memory-down" { 73 | adjustment_type = "ChangeInCapacity" 74 | autoscaling_group_name = "${aws_autoscaling_group.apachescale.id}" 75 | name = "apache-memory-down" 76 | scaling_adjustment = -1 77 | cooldown = 300 78 | } 79 | 80 | resource "aws_autoscaling_policy" "apache-cpu-down" { 81 | adjustment_type = "ChangeInCapacity" 82 | autoscaling_group_name = "${aws_autoscaling_group.apachescale.id}" 83 | name = "apache-cpu-down" 84 | scaling_adjustment = -1 85 | cooldown = 300 86 | } 87 | 88 | resource "aws_cloudwatch_metric_alarm" "memory-alarm-down" { 89 | alarm_name = "memory-alarm-down" 90 | comparison_operator = "LessThanOrEqualToThreshold" 91 | evaluation_periods = 2 92 | metric_name = "MemoryUtilization" 93 | namespace = "AWS/EC2" 94 | period = 120 95 | threshold = 20 96 | statistic = "Average" 97 | alarm_actions = ["${aws_autoscaling_policy.apache-memory-down.arn}"] 98 | dimensions {AutoScalingGroupName = "${aws_autoscaling_group.apachescale.id}"} 99 | } 100 | 101 | resource "aws_cloudwatch_metric_alarm" "cpu-alarm-down" { 102 | alarm_name = "cpu-alarm-down" 103 | comparison_operator = "LessThanOrEqualToThreshold" 104 | evaluation_periods = 2 105 | metric_name = "CPUUtilization" 106 | namespace = "AWS/EC2" 107 | period = 120 108 | threshold = 20 109 | statistic = "Average" 110 | alarm_actions = ["${aws_autoscaling_policy.apache-cpu-down.arn}"] 111 | dimensions {AutoScalingGroupName = "${aws_autoscaling_group.apachescale.id}"} 112 | 113 | } 114 | -------------------------------------------------------------------------------- /3 - Create ASG/create_elb.tf: -------------------------------------------------------------------------------- 1 | 2 | 3 | resource "aws_elb" "apachelb" { 4 | name = "apache-elb" 5 | 6 | subnets = ["${data.aws_subnet.public_a.id}","${data.aws_subnet.public_b.id}","${data.aws_subnet.public_c.id}"] 7 | security_groups = ["${aws_security_group.elb.id}"] 8 | listener { 9 | instance_port = 80 10 | instance_protocol = "http" 11 | lb_port = 80 12 | lb_protocol = "http" 13 | } 14 | health_check { 15 | healthy_threshold = 2 16 | interval = 10 17 | target = "TCP:80" 18 | timeout = 3 19 | unhealthy_threshold = 3 20 | } 21 | connection_draining = true 22 | } -------------------------------------------------------------------------------- /3 - Create ASG/create_key.tf: -------------------------------------------------------------------------------- 1 | resource "aws_key_pair" "kris" { 2 | key_name = "kris-key-pair" 3 | public_key = "${file("key.pub")}" 4 | } -------------------------------------------------------------------------------- /3 - Create ASG/create_security_groups.tf: -------------------------------------------------------------------------------- 1 | resource "aws_security_group" "apache" { 2 | name = "apache-security" 3 | description = "Allow incoming HTTP connections." 4 | 5 | ingress { 6 | from_port = 22 7 | to_port = 22 8 | protocol = "tcp" 9 | cidr_blocks = ["${var.port_22_cidr}"] 10 | } 11 | 12 | ingress { 13 | from_port = 80 14 | to_port = 80 15 | protocol = "tcp" 16 | security_groups = ["${aws_security_group.elb.id}"] 17 | } 18 | 19 | ingress { 20 | from_port = 8080 21 | to_port = 8080 22 | protocol = "tcp" 23 | security_groups = ["${aws_security_group.elb.id}"] 24 | } 25 | egress { 26 | from_port = 0 27 | to_port = 0 28 | protocol = "-1" 29 | cidr_blocks = ["0.0.0.0/0"] 30 | } 31 | 32 | vpc_id = "${data.aws_subnet.public_a.vpc_id}" 33 | 34 | tags { 35 | Name = "ApacheTomcat" 36 | } 37 | } 38 | 39 | 40 | resource "aws_security_group" "elb" { 41 | name = "elb-security" 42 | 43 | ingress { 44 | from_port = 80 45 | to_port = 80 46 | protocol = "tcp" 47 | cidr_blocks = ["0.0.0.0/0"] 48 | } 49 | 50 | ingress { 51 | from_port = 443 52 | to_port = 443 53 | protocol = "tcp" 54 | cidr_blocks = ["0.0.0.0/0"] 55 | } 56 | 57 | egress { 58 | from_port = 0 59 | to_port = 0 60 | protocol = "-1" 61 | cidr_blocks = ["0.0.0.0/0"] 62 | } 63 | 64 | tags { 65 | Name = "ELBApache" 66 | } 67 | 68 | vpc_id = "${data.aws_subnet.public_a.vpc_id}" 69 | 70 | } 71 | -------------------------------------------------------------------------------- /3 - Create ASG/fetch_id.tf: -------------------------------------------------------------------------------- 1 | data "aws_ami" "apache" { 2 | most_recent = true 3 | owners = ["self"] 4 | filter { 5 | name = "tag:Name" 6 | values = ["${var.filter_ami}"] 7 | } 8 | } 9 | 10 | data "aws_subnet" "public_a" { 11 | 12 | filter { 13 | name = "tag:${var.subnet_tag_name}" 14 | values = ["${var.subnet_tag_value}a"] 15 | } 16 | 17 | } 18 | 19 | data "aws_subnet" "public_b" { 20 | 21 | filter { 22 | name = "tag:${var.subnet_tag_name}" 23 | values = ["${var.subnet_tag_value}b"] 24 | } 25 | 26 | } 27 | 28 | data "aws_subnet" "public_c" { 29 | 30 | filter { 31 | name = "tag:${var.subnet_tag_name}" 32 | values = ["${var.subnet_tag_value}c"] 33 | } 34 | 35 | } 36 | 37 | data "aws_availability_zones" "apache" {} 38 | -------------------------------------------------------------------------------- /3 - Create ASG/key.pub: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /3 - Create ASG/outputs.tf: -------------------------------------------------------------------------------- 1 | // 2 | //output "ami_id" { 3 | // value = "${data.aws_ami.apache.id}" 4 | //} 5 | // 6 | // 7 | //output "subnet_ida" { 8 | // value = "${data.aws_subnet.public_a.id}" 9 | //} 10 | // 11 | // 12 | //output "subnet_idb" { 13 | // value = "${data.aws_subnet.public_b.id}" 14 | //} 15 | // 16 | // 17 | //output "subnet_idc" { 18 | // value = "${data.aws_subnet.public_c.id}" 19 | //} 20 | 21 | output "ELB address to hit on brower" {value = "${aws_elb.apachelb.dns_name}"} 22 | 23 | -------------------------------------------------------------------------------- /3 - Create ASG/provider.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "${var.aws_region}" 3 | access_key = "${var.access_key}" 4 | secret_key = "${var.secret_key}" 5 | } -------------------------------------------------------------------------------- /3 - Create ASG/vars.tf: -------------------------------------------------------------------------------- 1 | 2 | 3 | variable "access_key" { 4 | default = "" 5 | } 6 | 7 | variable "secret_key" { 8 | default = "" 9 | } 10 | 11 | variable "aws_region" { 12 | default = "ap-southeast-2" 13 | } 14 | 15 | variable "filter_ami" { 16 | default = "apache-tomcat-0.1" 17 | } 18 | 19 | variable "subnet_tag_name" { 20 | default = "Name" 21 | } 22 | 23 | variable "subnet_tag_value" { 24 | default = "public_subnet" 25 | } 26 | 27 | variable "instance_type" { 28 | default = "t2.small" 29 | } 30 | 31 | variable "port_22_cidr" { 32 | description = "CIDR for the whole VPC" 33 | default = "203.13.146.0/24" 34 | } 35 | 36 | variable "max_instance_asg" { 37 | default = "5" 38 | } 39 | 40 | -------------------------------------------------------------------------------- /Independent apache-tomcat Configuration/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | inventory = inventory.txt 3 | host_key_checking = False 4 | log_path=/tmp/ansible.log 5 | deprecation_warnings = False 6 | command_warnings = False -------------------------------------------------------------------------------- /Independent apache-tomcat Configuration/inventory.txt: -------------------------------------------------------------------------------- 1 | [hosts] 2 | -------------------------------------------------------------------------------- /Independent apache-tomcat Configuration/main.yml: -------------------------------------------------------------------------------- 1 | - hosts: all 2 | become: yes 3 | vars_files: 4 | - variables.yml 5 | roles: 6 | - role: apache-tomcat -------------------------------------------------------------------------------- /Independent apache-tomcat Configuration/roles/apache-tomcat/defaults/main.yml: -------------------------------------------------------------------------------- 1 | package_list: 2 | - apache2 3 | - mysql-server 4 | - default-jdk 5 | - authbind 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Independent apache-tomcat Configuration/roles/apache-tomcat/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: restart apache2 4 | service: 5 | name: apache2 6 | state: restarted 7 | 8 | - name: restart tomcat 9 | service: 10 | name: tomcat 11 | state: restarted -------------------------------------------------------------------------------- /Independent apache-tomcat Configuration/roles/apache-tomcat/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: install packages - apache2, mysql, authbind, java 2 | apt: name={{ item }} update_cache=yes state=present 3 | with_items: 4 | - "{{package_list}}" 5 | 6 | - name: install mod_jk 7 | apt: name=libapache2-mod-jk state=present 8 | notify: 9 | - restart apache2 10 | 11 | - name: ensure mysql is running and starts on boot 12 | service: name=mysql state=started enabled=true 13 | 14 | - name: create tomcat group 15 | group: 16 | state: present 17 | name: tomcat 18 | 19 | - name: create tomcat user (nobody should login to tomcat account) 20 | user: 21 | name: tomcat 22 | group: tomcat 23 | shell: /bin/false 24 | home: /opt/tomcat 25 | 26 | - name: create directory for tomcat 27 | file: 28 | path: /opt/tomcat 29 | state: directory 30 | 31 | - name: download tomcat package 32 | get_url: url={{tomcat_url}} dest='/tmp/' 33 | 34 | - name: unarchive tomcat package 35 | shell: "tar xzvf /tmp/apache-tomcat-8*tar.gz -C /opt/tomcat --strip-components=1" 36 | 37 | - name: change tomcat directory permissions 38 | file: 39 | path: /opt/tomcat 40 | state: directory 41 | owner: tomcat 42 | group: tomcat 43 | recurse: yes 44 | 45 | - name: change mode for conf directory 46 | file: 47 | path: /opt/tomcat/conf 48 | state: directory 49 | owner: tomcat 50 | group: tomcat 51 | recurse: yes 52 | mode: "g+x" 53 | 54 | - name: create systemd service file for tomcat 55 | template: 56 | src: tomcat.service.j2 57 | dest: /etc/systemd/system/tomcat.service 58 | 59 | - name: Reload systemd daemon & start tomcat 60 | systemd: 61 | daemon_reload: yes 62 | name: tomcat 63 | enabled: yes 64 | state: started 65 | 66 | - name: replace tomcat server.xml 67 | template: 68 | src: server.xml.j2 69 | dest: /opt/tomcat/conf/server.xml 70 | notify: 71 | - restart tomcat 72 | 73 | - name: configure apache 74 | replace: dest=/etc/apache2/sites-enabled/000-default.conf regexp='^' replace="JkMount /* ajp13_worker \n " 75 | notify: 76 | - restart apache2 77 | 78 | - name: setup simple tomcat application 79 | get_url: url=https://community.jboss.org/servlet/JiveServlet/download/588259-27006/clusterjsp.war dest='/opt/tomcat/webapps' 80 | notify: 81 | - restart tomcat 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /Independent apache-tomcat Configuration/roles/apache-tomcat/templates/server.xml.j2: -------------------------------------------------------------------------------- 1 | 2 | 18 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 37 | 38 | 41 | 46 | 47 | 48 | 53 | 54 | 55 | 56 | 60 | 61 | 62 | 69 | 72 | 73 | 79 | 87 | 96 | 102 | 114 | 115 | 116 | 117 | 118 | 119 | 124 | 125 | 128 | 129 | 130 | 133 | 136 | 137 | 139 | 140 | 144 | 146 | 147 | 148 | 150 | 151 | 153 | 156 | 157 | 160 | 163 | 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /Independent apache-tomcat Configuration/roles/apache-tomcat/templates/tomcat.service.j2: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Apache Tomcat Web Application Container 3 | After=network.target 4 | 5 | [Service] 6 | Type=forking 7 | 8 | Environment=JAVA_HOME=/usr/lib/jvm/{{ java_home }}/jre 9 | Environment=CATALINA_PID=/opt/tomcat/temp/tomcat.pid 10 | Environment=CATALINA_HOME=/opt/tomcat 11 | Environment=CATALINA_BASE=/opt/tomcat 12 | Environment='CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC' 13 | Environment='JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom' 14 | 15 | ExecStart=/opt/tomcat/bin/startup.sh 16 | ExecStop=/opt/tomcat/bin/shutdown.sh 17 | 18 | User=tomcat 19 | Group=tomcat 20 | UMask=0007 21 | RestartSec=10 22 | Restart=always 23 | 24 | [Install] 25 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /Independent apache-tomcat Configuration/variables.yml: -------------------------------------------------------------------------------- 1 | java_home: java-1.8.0-openjdk-amd64 2 | 3 | tomcat_url: http://apache.mirror.digitalpacific.com.au/tomcat/tomcat-8/v8.5.24/bin/apache-tomcat-8.5.24.tar.gz 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # apache-tomcat-automation 2 | 3 | ## Overview 4 | 5 | We aim to create a Highly Available apache-tomcat cluster with ansible, packer and terraform. This automation is supported currently for **Ubuntu** only. 6 | 7 | There are 3 parts of this automation: 8 | 1. Using **Terraform** creating a VPC with public and private subnets. 9 | 2. Create an ami which has apache, tomcat & one sample application configured using **Packer** and **Ansible**. The AJP connection is used between apache & tomcat. 10 | 3. Create ELB, security groups and autoscaling group with scaling policies using **Terraform** 11 | 12 | - The **Ansible** playbook to install and configure Apache-Tomcat is included independently as well in case user does not want to use packer. 13 | - I've used Amazon Linux box to setup the automation instance where I've configured Terraform, Ansible & Packer. 14 | ## Prerequisites 15 | 16 | - ##### We are tagging the resources getting created through terraform and packer. These resources are called later during Autoscaling Group Creating using terraform **data resources**. 17 | 18 | - ##### Use of tags reduces the human errors and simplify the code. Hence, in case VPC is not created using the given Terraform code then the subnets must be tagged as below: 19 | Name: `tag_name`a\b\c 20 | ```YAML 21 | Example for tag named "public_subnet": 22 | For subnet of Public AZ 1: 23 | Name: public_subneta 24 | 25 | For subnet of Public AZ 2: 26 | Name: public_subnetb 27 | 28 | For subnet of Public AZ 3: 29 | Name: public_subnetc 30 | ``` 31 | - ##### For packer automation to create AMI, we are using Ubuntu 16.04 OS. User need to get this AMI ID and enter in Packer variables file manually. In case user want to avoid doing it then Packer and auto fetch the AMI ID, the code for that is given in **info.txt** file inside **Create AMI** directory. 32 | 33 | ### Creating VPC Using Terraform 34 | 35 | The code given in **1-Create VPC** is use to setup a new VPC. It creates: 36 | 1. A VPC 37 | 2. Three Public Subnets. They are created with tag `Name = public_subneta \ public_subnetb \ public_subnetc` 38 | 3. Three Private Subnets. 39 | 40 | To setup Terraform: 41 | 1. Download Terraform (linux) from: https://www.terraform.io/downloads.html 42 | 2. Extract the zip and move the terraform binary in /usr/sbin directory 43 | #### Terraform variables 44 | The following variables can be modified by the user. User need to edit **1-Create VPC/variables.tf** file 45 | 46 | - `access_key`: AWS access key for user's project 47 | - `secret_key`: AWS secret Key for User's project 48 | - `aws_region`: Region to use Default is ap-southeast-2 49 | - `vpc_cidr`: CIDR for whole VPC, default is 192.168.0.0/22 50 | - `public_subnet_cidr_a\b\c`: CIDRs for the public subnets. The default values are given in the variables.tf file. 51 | - `private_subnet_cidr_a\b\c`: CIDRs for the private subnets. The default values are given in the variables.tf file. 52 | 53 | #### Running Terraform 54 | 55 | Once all the info is correctly entered in variables.tf file user need to run below from **1-Create VPC**: 56 | - To initialize terraform: `terraform init` 57 | - To check the different resources being created: `terraform plan` 58 | - To create the resources: `terraform apply` 59 | 60 | 61 | ### Creating AMI Using Packer 62 | 63 | Creating AMI part consist of Packer and Ansible which are integrated together. This part focuses on Packer configs. 64 | The default AMI is created with Tag `Name = apache-tomcat-0.1` 65 | 66 | To setup Packer: 67 | 1. Download Packer (linux) from: https://www.packer.io/downloads.html 68 | 2. Extract the zip and move the packer binary in /usr/sbin directory 69 | #### Packer Variables 70 | 71 | The following variables can be modified by the user. User need to edit **2-Create AMI/vars.json** file 72 | - `aws_access_key`: AWS access key for user's project 73 | - `aws_secret_key`: AWS secret Key for User's project 74 | - `aws_region`: Region to use Default is ap-southeast-2 75 | - `aws_ami_image`: Ubuntu ami Id for the specific region. 76 | - `aws_instance_type`: The instance type with which the ami will be created by packer. 77 | - `image_version`: In case more images are created using the same automation this can be used to add versions. Default value is **0.1**. AMI created is **Name = apache-tomcat-0.1** 78 | 79 | ```JSON 80 | Below is the example config available: 81 | { 82 | "aws_access_key": "", 83 | "aws_secret_key": "", 84 | "aws_region": "ap-southeast-2", 85 | "aws_ami_image": "ami-cab258a8", 86 | "aws_instance_type": "t2.small", 87 | "image_version" : "0.1" 88 | } 89 | ``` 90 | 91 | #### Running Packer 92 | 93 | Go to the directory 2-Create AMI and run: 94 | - packer build -var-file=vars.json apache-ami.json 95 | 96 | #### Integration with Ansible 97 | 98 | To Integrate packer with Ansible: 99 | 1. The ansible code is copied ino the playbook directory inside Create AMI directory 100 | 2. The ansible provisioner is then used to integrate ansible with packer. 101 | 3. Once a system is created with AMI the apache & tomcat services will be up and running. 102 | 103 | 104 | ### Ansible Playbook to install and configure apache & tomcat 105 | 106 | 1. The ansible playbook is integrated with Packer. As well same playbook is provided separately "Independent apache-tomcat Configuration. 107 | 2. The ansible version tested is 2.3.1 but playbook should also support 2.4 108 | 3. The playbook does below: 109 | - Install and configures apache 110 | - Install and configure tomcat (version 8) and Create tomcat service file. 111 | - Ensure that only AJP connector is used between Apache & Tomcat. 112 | - Deploy a sample application "clusterjsp" in the end of automation. 113 | 114 | #### Ansible variables 115 | The following variables can be modified by the user. User need to edit **2-Create AMI/playbook/variables.yml** file 116 | - `java_home`: The java home being used to create tomcat service file. (Default: java-1.8.0-openjdk-amd64 ) 117 | - `tomcat_url`: The url to download the tomcat .tar.gz file. (Default: http://apache.mirror.digitalpacific.com.au/tomcat/tomcat-8/v8.5.24/bin/apache-tomcat-8.5.24.tar.gz) 118 | 119 | #### Running ansible playbook independently 120 | 1. All the instances where apache should be installed should have their IP's mentioned in "inventory.txt" file. 121 | 2. Run below command from "Independent apache-tomcat Configuration": 122 | - ansible-playbook main.yml 123 | 3. The ansible logs will be created in **/tmp/ansible.log** file 124 | 4. The ansible configurations are mentioned in "ansible.cfg" file. User can modify them as per need basis. 125 | 126 | 127 | ### Creating Autoscaling Group using Terraform 128 | 129 | 1. Here we are using Terraform to create: 130 | - Creates Security Groups 131 | - Creates public key for user to use with launch configuration. 132 | - Creates Elastic Load balancer 133 | - Creates Launch Configuration & Autoscaling Group 134 | - Creates policies for Autoscaling group for Increase & Decrease of number of instance as per CPU and Memory usage 135 | - Creates CPU & Memory based Cloud Watch Metrics to be used with policy of autoscaling group. 136 | 137 | 2. Info about Autoscaling Group Created: 138 | 139 | - The Desired and Minimum number of instances are 3. 140 | - The maximum number of instances is 5. 141 | - One additional instance is created when the CPU or Memory threshold crosses 80% with cool down of 300 seconds. 142 | - One instance is removed when the CPU or Memory threshold drops below 20%. 143 | 144 | 3. In the end of terraform run, the ELB DNS is generated as output. That can be used to test the ASG created. 145 | #### Terraform variables & Files 146 | 147 | The following variables can be modified by the user. User need to edit **3- Create ASG/vars.tf** file 148 | - `access_key`: AWS access key for user's project 149 | - `secret_key`: AWS secret Key for User's project 150 | - `aws_region`: Region to use Default is ap-southeast-2 151 | - `filter_ami`: The tag name for AMI being used. The default is **apache-tomcat-0.1** (This is also the default tag created by packer) 152 | - `subnet_tag_name`: This is the name of Tag's **Key** being used. The default is **Name**. 153 | - `subnet_tag_value`: This is the tag's **value** being used. The default is **public_subnet** (This is also the default subnet name created by VPC code) 154 | - `instance_type`: The size of instance to be launched with autoscaling group. 155 | - `port_22_cidr`: The cidr for the Ip range which will be used for ssh to this instances. (E.g. 103.11.226.0/24) 156 | - `max_instance_asg`: The max limit for the instances in autoscaling group. (Default = 5) 157 | 158 | For creation of key part: 159 | 1. Enter the public key in **key.pub** file. 160 | 2. If user does not want to create new key.pub then update the "key_name" part of "aws_launch_configuration" part in **3-Create ASG/create_autoscaling_group.tf** file : 161 | ```YAML 162 | resource "aws_launch_configuration" "apache" { 163 | key_name = "enter_your_key_name" 164 | } 165 | ``` 166 | 167 | #### Running Terraform 168 | 169 | Once all the info is correctly entered in vars.tf file user need to run below from **2-Create ASG** directory: 170 | - To initialize terraform: `terraform init` 171 | - To check the different resources being created: `terraform plan` 172 | - To create the resources: `terraform apply` 173 | 174 | 175 | ## Testing the final setup 176 | 177 | Take the output of ELB DNS created in the last terrafrom run, append clusterjsp to it and hit the url in brouwer: 178 | http://ELB_DNS_GENERATED/clusterjsp 179 | 180 | example: http://apache-elb-993864931.ap-southeast-2.elb.amazonaws.com/clusterjsp/ 181 | 182 | --------------------------------------------------------------------------------