diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..271822f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/.envrc.example b/.envrc.example index 00fa3f6..c983c85 100644 --- a/.envrc.example +++ b/.envrc.example @@ -1,13 +1,9 @@ -#!/bin/sh +#!/usr/bin/env bash -PATH_add scripts - -export AWS_ACCESS_KEY_ID= -export AWS_SECRET_ACCESS_KEY= -export AWS_DEFAULT_REGION= +export CLOUDFLARE_EMAIL= +export CLOUDFLARE_TOKEN= -export TF_VAR_aws_account_num= -export TF_VAR_aws_region=${AWS_DEFAULT_REGION} +export ROLE_ARN_stg= +export ROLE_ARN_prod= -export CLOUDFLARE_EMAIL= -export CLOUDFLARE_TOKEN= \ No newline at end of file +export TF_VAR_newrelic_license_key= diff --git a/.gitignore b/.gitignore index c482008..28de410 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_Store .envrc +.bundle # IDE junk .idea/* @@ -8,3 +9,7 @@ # tfstate is saved on remote .terraform +terraform.tfstate.d +terraform.log + +functions/*.zip diff --git a/.travis.yml b/.travis.yml index 923b43c..03d0a32 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,41 +1,38 @@ --- sudo: required dist: trusty -language: ruby -rvm: - - 2.2.3 -cache: - directories: - - vendor/bundle - -before_install: - - gem install travis -v 1.8.2 --no-rdoc --no-ri - - wget https://releases.hashicorp.com/terraform/0.6.14/terraform_0.6.14_linux_amd64.zip -O /tmp/terraform.zip +language: python +cache: pip +python: +- '2.7' + +install: + # Install AWS CLI + - pip install awscli + - aws --version + # Install Terraform + - TERRAFORM_VERSION="0.10.2" + - wget https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip -O /tmp/terraform.zip - sudo unzip -d /usr/local/bin/ /tmp/terraform.zip before_script: - - export TF_VAR_aws_account_num=${AWS_ACCOUNT_NUMBER} - - export TF_VAR_aws_region=${AWS_DEFAULT_REGION} -script: - # If branch name was matched with deploy/xxx, continue to apply. + # Determine environment - export ENV=$(echo "${TRAVIS_BRANCH}" | perl -ne "print $& if /(?<=deploy\/).*/") - - if [ -z "${ENV}" ]; then exit ; fi - - # Enable Terraform Remote - - scripts/terraform-enable-remote.sh - - # Save current output to compare later - - terraform output | grep endpoint | tee out_before - - # Execute Terraform and create tfstate on remote - - scripts/terraform-wrapper.sh plan - - scripts/terraform-wrapper.sh apply - - # Save current output to compare later - - terraform output | grep endpoint | tee out_after - - # Compare output and take over building if endpoints were changed. - - diff out_before out_after || scripts/build-provisionings.sh - - # Deploy app when initial deployment - - if [ ! -s out_before ]; then scripts/build-app.sh ; fi +script: + - | + if [ -z "${ENV}" ]; then + echo "${TRAVIS_BRANCH} is not a branch to deploy." + else + source ./scripts/switch-role.sh + terraform init + terraform get + terraform workspace select ${ENV} + terraform plan + fi + +deploy: + - provider: script + script: terraform apply + skip_cleanup: true + on: + branch: deploy/* diff --git a/README.md b/README.md new file mode 100644 index 0000000..24afa34 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# micropost-formation + +## Core components + +* AWS VPC +* AWS ECS with ALB and AutoScaling +* AWS RDS +* Cloudflare DNS and CDN + +## Dependencies + +* Terraform +* AWS CLI +* direnv + +## Getting started + +Configure settings. + +``` +$ cp .envrc.example .envrc +$ vi .envrc +$ direnv allow +``` + +Init +``` +$ export ENV=stg +$ source ./scripts/switch-role.sh +$ terraform init +$ terraform get +$ terraform workspace select ${ENV} +``` + +Plan and Apply + +``` +$ ./terraform-env.sh stg plan +$ ./terraform-env.sh stg apply +``` + +## Related Projects + +* [Angular 2 app](https://github.com/springboot-angular2-tutorial/angular2-app) +* [Spring Boot app](https://github.com/springboot-angular2-tutorial/boot-app) +* [Android app](https://github.com/springboot-angular2-tutorial/android-app) +* [Lambda functions by Serverless](https://github.com/springboot-angular2-tutorial/micropost-functions) diff --git a/autoscaling.tf b/autoscaling.tf deleted file mode 100644 index 0b467fe..0000000 --- a/autoscaling.tf +++ /dev/null @@ -1,77 +0,0 @@ -resource "aws_launch_configuration" "web" { - name_prefix = "web-${var.env}-" - image_id = "${var.ami_web}" - instance_type = "t2.micro" - security_groups = [ - "${aws_security_group.internal.id}", - "${aws_security_group.ssh.id}", - ] - key_name = "akirasosa" - associate_public_ip_address = true - iam_instance_profile = "${aws_iam_instance_profile.web.id}" - lifecycle { - create_before_destroy = true - } -} - -resource "aws_autoscaling_group" "web" { - name = "web-${var.env}" - max_size = 4 - min_size = 0 - health_check_grace_period = 300 - health_check_type = "ELB" - desired_capacity = 0 - force_delete = true - launch_configuration = "${aws_launch_configuration.web.id}" - vpc_zone_identifier = [ - "${aws_subnet.public-a.id}", - "${aws_subnet.public-b.id}", - ] - load_balancers = ["${aws_elb.web.name}"] -} - -resource "aws_autoscaling_policy" "web-scale-out" { - name = "Instance-ScaleOut-CPU-High" - scaling_adjustment = 1 - adjustment_type = "ChangeInCapacity" - cooldown = 300 - autoscaling_group_name = "${aws_autoscaling_group.web.name}" -} - -resource "aws_cloudwatch_metric_alarm" "web-gte-threshold" { - alarm_name = "web-${var.env}-CPU-Utilization-High-30" - comparison_operator = "GreaterThanOrEqualToThreshold" - evaluation_periods = "1" - metric_name = "CPUUtilization" - namespace = "AWS/EC2" - period = "120" - statistic = "Average" - threshold = "80" - dimensions { - AutoScalingGroupName = "${aws_autoscaling_group.web.name}" - } - alarm_actions = ["${aws_autoscaling_policy.web-scale-out.arn}"] -} - -resource "aws_autoscaling_policy" "web-scale-in" { - name = "Instance-ScaleIn-CPU-Low" - scaling_adjustment = -1 - adjustment_type = "ChangeInCapacity" - cooldown = 300 - autoscaling_group_name = "${aws_autoscaling_group.web.name}" -} - -resource "aws_cloudwatch_metric_alarm" "web-lt-threshold" { - alarm_name = "web-${var.env}-CPU-Utilization-Low-5" - comparison_operator = "LessThanThreshold" - evaluation_periods = "1" - metric_name = "CPUUtilization" - namespace = "AWS/EC2" - period = "120" - statistic = "Average" - threshold = "5" - dimensions { - AutoScalingGroupName = "${aws_autoscaling_group.web.name}" - } - alarm_actions = ["${aws_autoscaling_policy.web-scale-in.arn}"] -} diff --git a/bastion/main.tf b/bastion/main.tf new file mode 100644 index 0000000..a92f840 --- /dev/null +++ b/bastion/main.tf @@ -0,0 +1,51 @@ +data "aws_ami" "ubuntu" { + most_recent = true + filter { + name = "name" + values = [ + "*ubuntu-xenial-16.04*" + ] + } + filter { + name = "architecture" + values = [ + "x86_64" + ] + } + filter { + name = "root-device-type" + values = [ + "ebs" + ] + } + filter { + name = "virtualization-type" + values = [ + "hvm" + ] + } + filter { + name = "block-device-mapping.volume-type" + values = [ + "gp2" + ] + } + owners = [ + # Canonical + "099720109477" + ] +} + +resource "aws_instance" "bastion" { + ami = "${data.aws_ami.ubuntu.id}" + instance_type = "t2.micro" + subnet_id = "${var.subnet_id}" + vpc_security_group_ids = [ + "${var.security_groups}" + ] + key_name = "${var.key_name}" + associate_public_ip_address = true + tags { + Name = "bastion" + } +} \ No newline at end of file diff --git a/bastion/variables.tf b/bastion/variables.tf new file mode 100644 index 0000000..cd8f0ab --- /dev/null +++ b/bastion/variables.tf @@ -0,0 +1,13 @@ +variable "subnet_id" { + description = "Public subnet for bastion server" +} + +variable "security_groups" { + type = "list" + description = "Security groups for bastion server" + default = [] +} + +variable "key_name" { + description = "SSH key to login to bastion server" +} \ No newline at end of file diff --git a/codedeploy.tf b/codedeploy.tf deleted file mode 100644 index 30138a7..0000000 --- a/codedeploy.tf +++ /dev/null @@ -1,11 +0,0 @@ -resource "aws_codedeploy_app" "micropost" { - name = "micropost-${var.env}" -} - -resource "aws_codedeploy_deployment_group" "web" { - app_name = "${aws_codedeploy_app.micropost.name}" - deployment_group_name = "web" - service_role_arn = "${aws_iam_role.codedeploy-service.arn}" - autoscaling_groups = ["${aws_autoscaling_group.web.id}"] - deployment_config_name = "CodeDeployDefault.OneAtATime" -} diff --git a/dbservers/main.tf b/dbservers/main.tf new file mode 100644 index 0000000..4f0de4d --- /dev/null +++ b/dbservers/main.tf @@ -0,0 +1,27 @@ +resource "aws_db_instance" "main" { + snapshot_identifier = "${var.snapshot_identifier}" + skip_final_snapshot = true + allocated_storage = 5 + engine = "mysql" + engine_version = "5.7.17" + instance_class = "db.t2.micro" + parameter_group_name = "${aws_db_parameter_group.main.name}" + vpc_security_group_ids = [ + "${var.security_groups}", + ] + db_subnet_group_name = "${aws_db_subnet_group.main.name}" +} + +resource "aws_db_subnet_group" "main" { + name = "main" + description = "main group of subnets" + subnet_ids = [ + "${var.subnets}" + ] +} + +# It's not used. +resource "aws_db_parameter_group" "main" { + name = "main" + family = "mysql5.7" +} diff --git a/dbservers/outputs.tf b/dbservers/outputs.tf new file mode 100644 index 0000000..c385baa --- /dev/null +++ b/dbservers/outputs.tf @@ -0,0 +1,3 @@ +output "endpoint" { + value = "${aws_db_instance.main.endpoint}" +} \ No newline at end of file diff --git a/dbservers/variables.tf b/dbservers/variables.tf new file mode 100644 index 0000000..bb13128 --- /dev/null +++ b/dbservers/variables.tf @@ -0,0 +1,15 @@ +variable "security_groups" { + type = "list" + description = "Security groups for db servers" + default = [] +} + +variable "subnets" { + type = "list" + description = "Subnets for db servers" + default = [] +} + +variable "snapshot_identifier" { + description = "Initial snapshot to restore" +} diff --git a/dns.tf b/dns.tf index 4635e11..f88b62c 100644 --- a/dns.tf +++ b/dns.tf @@ -1,6 +1,23 @@ -resource "cloudflare_record" "micropost" { - domain = "hana053.com" - name = "micropost-${var.env}" - value = "${aws_elb.web.dns_name}" +resource "cloudflare_record" "main" { + domain = "${var.domain}" + name = "${var.web_host_name}-${terraform.workspace}" + value = "${module.webservers.dns_name}" type = "CNAME" -} \ No newline at end of file +} + +resource "cloudflare_record" "main_prod" { + count = "${terraform.workspace == "prod" ? 1 : 0}" + domain = "${var.domain}" + name = "${var.web_host_name}" + value = "${module.webservers.dns_name}" + type = "CNAME" +} + +resource "cloudflare_record" "cdn" { + domain = "${var.domain}" + name = "cdn-${terraform.workspace}" + value = "${cloudflare_record.main.name}.hana053.com" + proxied = true + type = "CNAME" +} + diff --git a/elasticache.tf b/elasticache.tf deleted file mode 100644 index b92c6a9..0000000 --- a/elasticache.tf +++ /dev/null @@ -1,22 +0,0 @@ -resource "aws_elasticache_cluster" "micropost" { - cluster_id = "micropost-${var.env}" - engine = "redis" - engine_version = "2.8.24" - node_type = "cache.t2.micro" - port = 6379 - num_cache_nodes = 1 - parameter_group_name = "default.redis2.8" - security_group_ids = [ - "${aws_security_group.internal.id}", - ] - subnet_group_name = "${aws_elasticache_subnet_group.micropost.name}" -} - -resource "aws_elasticache_subnet_group" "micropost" { - name = "micropost-${var.env}" - description = "main subnet group" - subnet_ids = [ - "${aws_subnet.private-a.id}", - "${aws_subnet.private-b.id}", - ] -} \ No newline at end of file diff --git a/elasticsearch.tf b/elasticsearch.tf deleted file mode 100644 index 38b87f4..0000000 --- a/elasticsearch.tf +++ /dev/null @@ -1,40 +0,0 @@ -resource "aws_elasticsearch_domain" "logserver" { - domain_name = "micropost-${var.env}" - access_policies = <> /etc/ecs/ecs.config diff --git a/webservers/variables.tf b/webservers/variables.tf new file mode 100644 index 0000000..6257660 --- /dev/null +++ b/webservers/variables.tf @@ -0,0 +1,47 @@ +variable "env" { + description = "Application environment like stg, prod eg..." +} + +variable "dbserver_endpoint" { + description = "MySQL endpoint" +} + +variable "newrelic_license_key" { + description = "New Relic licence key" +} + +variable "key_name" { + description = "SSH key name for web servers" +} + +variable "web_subnets" { + type = "list" + description = "Subnets for web servers" + default = [] +} + +variable "web_security_groups" { + type = "list" + description = "Security groups for web servers" + default = [] +} + +variable "alb_subnets" { + type = "list" + description = "Subnets for alb" + default = [] +} + +variable "alb_security_groups" { + type = "list" + description = "Security groups for alb" + default = [] +} + +variable "vpc_id" { + description = "vpc id for alb target group" +} + +variable "log_bucket" { + description = "s3 bucket to save log" +}