From cba9f2aef097481e8c6095d4d7bac77d78074f67 Mon Sep 17 00:00:00 2001 From: Sibi Prabakaran Date: Thu, 18 Jun 2020 18:45:09 +0530 Subject: [PATCH 1/2] Add asg-simple-lifecycle --- examples/asg-simple-lifecycle-hooks/Makefile | 75 +++++++++++++ examples/asg-simple-lifecycle-hooks/README.md | 74 ++++++++++++ examples/asg-simple-lifecycle-hooks/asg.tf | 38 +++++++ .../cloud-config.yml | 43 +++++++ examples/asg-simple-lifecycle-hooks/data.tf | 106 ++++++++++++++++++ examples/asg-simple-lifecycle-hooks/locals.tf | 7 ++ examples/asg-simple-lifecycle-hooks/main.tf | 76 +++++++++++++ .../asg-simple-lifecycle-hooks/outputs.tf | 4 + .../asg-simple-lifecycle-hooks/variables.tf | 50 +++++++++ .../asg-simple-lifecycle-hooks/versions.tf | 4 + 10 files changed, 477 insertions(+) create mode 100644 examples/asg-simple-lifecycle-hooks/Makefile create mode 100644 examples/asg-simple-lifecycle-hooks/README.md create mode 100644 examples/asg-simple-lifecycle-hooks/asg.tf create mode 100644 examples/asg-simple-lifecycle-hooks/cloud-config.yml create mode 100644 examples/asg-simple-lifecycle-hooks/data.tf create mode 100644 examples/asg-simple-lifecycle-hooks/locals.tf create mode 100644 examples/asg-simple-lifecycle-hooks/main.tf create mode 100644 examples/asg-simple-lifecycle-hooks/outputs.tf create mode 100644 examples/asg-simple-lifecycle-hooks/variables.tf create mode 100644 examples/asg-simple-lifecycle-hooks/versions.tf diff --git a/examples/asg-simple-lifecycle-hooks/Makefile b/examples/asg-simple-lifecycle-hooks/Makefile new file mode 100644 index 00000000..07ab7adf --- /dev/null +++ b/examples/asg-simple-lifecycle-hooks/Makefile @@ -0,0 +1,75 @@ +.PHONY: init ssh-key plan-vpc plan-subnets plan-gateway plan apply destroy clean + +.DEFAULT_GOAL = help + +TERRAFORM = "aws-env terraform" + +# Hardcoding value of 3 minutes when we check if the plan file is stale +STALE_PLAN_FILE := `find "tf.out" -mmin -3 | grep -q tf.out` + +## Check if tf.out is stale (Older than 2 minutes) +check-plan-file: + @if ! ${STALE_PLAN_FILE} ; then \ + echo "ERROR: Stale tf.out plan file (older than 3 minutes)!"; \ + exit 1; \ + fi + +## Runs terraform get and terraform init for env +init: + $terraform init + +## Create ssh key +ssh-key: + @ssh-keygen -q -N "" -b 4096 -C "SSH key for vpc-scenario-1 example" -f ./id_rsa + +## use 'terraform plan' to 'target' the vpc in the vpc module +plan-vpc: + @terraform plan \ + -target="module.vpc.module.vpc" \ + -out=tf.out + +## use 'terraform plan' to 'target' the public subnets in the vpc module +plan-subnets: + @terraform plan \ + -target="module.vpc.module.public-subnets" \ + -out=tf.out + +## use 'terraform plan' to 'target' the public gateway in the vpc module +plan-gateway: + @terraform plan \ + -target="module.vpc.module.public-gateway" \ + -out=tf.out + +## use 'terraform plan' to map out updates to apply +plan: + @terraform plan -out=tf.out + +## use 'terraform apply' to apply updates in a 'tf.out' plan file +apply: check-plan-file + @terraform apply tf.out + +## use 'terraform destroy' to remove all resources from AWS +destroy: + @terraform destroy + +## rm -rf all files and state +clean: + @rm -f tf.out + @rm -f id_rsa + @rm -f id_rsa.pub + @rm -f terraform.tfvars + @rm -f terraform.*.backup + @rm -f terraform.tfstate + +## Show help screen. +help: + @echo "Please use \`make ' where is one of\n\n" + @awk '/^[a-zA-Z\-\_0-9]+:/ { \ + helpMessage = match(lastLine, /^## (.*)/); \ + if (helpMessage) { \ + helpCommand = substr($$1, 0, index($$1, ":")-1); \ + helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \ + printf "%-30s %s\n", helpCommand, helpMessage; \ + } \ + } \ + { lastLine = $$0 }' $(MAKEFILE_LIST) diff --git a/examples/asg-simple-lifecycle-hooks/README.md b/examples/asg-simple-lifecycle-hooks/README.md new file mode 100644 index 00000000..1f2476e1 --- /dev/null +++ b/examples/asg-simple-lifecycle-hooks/README.md @@ -0,0 +1,74 @@ +# Example of basic ASG integration with lifecycle hooks + +The difference between this and the one one is that we aren't using +any load balancers in this example. + +## Environment creation and deployment + +To use this example set up AWS credentials and then run the commands in the +following order: + +``` +make ssh-key +make init +make plan-vpc +make apply +make plan-subnets +make apply +make plan-gateway +make apply +make plan +make apply +``` + +## Testing + +Get the public IP address of the newly created ec2 web instance with the AWS console. + +SSH into the machine with the command: + +``` +ssh -i id_rsa ec2-user@ +``` + +You can see in the machine that `lifecycled` daemon would be +running. You can check the status of the service using + +``` +systemctl status lifecycled.service +``` + + +## Test the Notification + +To generate a notification for a launch event, update the Auto Scaling group by increasing the desired capacity of the Auto Scaling group by 1. You receive a notification within a few minutes after instance launch. + +To change the desired capacity using the console + + Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/. + + On the navigation pane, under Auto Scaling, choose Auto Scaling Groups. + + Select your Auto Scaling group. + + On the Details tab, choose Edit. + + For Desired, decrease the current value by 1. + + Choose Save. + + After a few minutes, you'll see that the lifecycle-handler.sh script will be executed and it's side effect operation will be performed. + + +## Destruction + +To destroy the test environment run the following commands: + +``` +make destroy +make clean +``` + +## Notes +- This example was last tested with `Terraform v0.11.11` +- This example assumes AWS credentials setup with access to the **us-east-2** region. diff --git a/examples/asg-simple-lifecycle-hooks/asg.tf b/examples/asg-simple-lifecycle-hooks/asg.tf new file mode 100644 index 00000000..d062293f --- /dev/null +++ b/examples/asg-simple-lifecycle-hooks/asg.tf @@ -0,0 +1,38 @@ +resource "aws_autoscaling_group" "cluster" { + name = "${var.name_prefix}-${aws_launch_configuration.cluster.name}" + launch_configuration = aws_launch_configuration.cluster.id + vpc_zone_identifier = module.vpc.public_subnet_ids + + min_size = 1 + desired_capacity = 2 + max_size = 4 + + lifecycle { + create_before_destroy = true + } + + initial_lifecycle_hook { + name = "${var.name_prefix}-lifecycle" + default_result = "CONTINUE" + heartbeat_timeout = 60 + lifecycle_transition = "autoscaling:EC2_INSTANCE_TERMINATING" + notification_target_arn = aws_sns_topic.main.arn + role_arn = aws_iam_role.lifecycle_hook.arn + } +} + +# Launch Config for the ASG +resource "aws_launch_configuration" "cluster" { + name_prefix = var.name_prefix + image_id = data.aws_ami.linux2.id + instance_type = "t2.nano" + key_name = aws_key_pair.main.key_name + iam_instance_profile = aws_iam_instance_profile.ec2.name + security_groups = [aws_security_group.main.id] + + user_data = data.template_file.main.rendered + + lifecycle { + create_before_destroy = true + } +} diff --git a/examples/asg-simple-lifecycle-hooks/cloud-config.yml b/examples/asg-simple-lifecycle-hooks/cloud-config.yml new file mode 100644 index 00000000..e1a91dd1 --- /dev/null +++ b/examples/asg-simple-lifecycle-hooks/cloud-config.yml @@ -0,0 +1,43 @@ +#cloud-config +write_files: + - path: "/etc/systemd/system/lifecycled.service" + permissions: "0644" + owner: "root" + content: | + [Unit] + Description=Autoscale Lifecycle Daemon + Requires=network-online.target + After=network-online.target + + [Service] + Type=simple + Restart=on-failure + RestartSec=30s + TimeoutStopSec=5m + + Environment="AWS_REGION=${region}" + ExecStart=/usr/local/bin/lifecycled --no-spot --sns-topic=${lifecycle_topic} --handler=/usr/local/scripts/lifecycle-handler.sh --json + + [Install] + WantedBy=multi-user.target + - path: "/usr/local/scripts/lifecycle-handler.sh" + permissions: "0744" + owner: "root" + content: | + #! /usr/bin/bash + + set -euo pipefail + + echo "hello from the handler" + curl http://3.22.77.147 + sleep 120 + echo "goodbye from the handler" +runcmd: + - | + wget https://github.com/buildkite/lifecycled/releases/download/v3.0.2/lifecycled-linux-amd64 + cp ./lifecycled-linux-amd64 /usr/local/bin/lifecycled + chmod +x /usr/local/bin/lifecycled + chown root:root /usr/local/bin/lifecycled + echo "lifecycled installed" + - | + systemctl enable lifecycled.service --now diff --git a/examples/asg-simple-lifecycle-hooks/data.tf b/examples/asg-simple-lifecycle-hooks/data.tf new file mode 100644 index 00000000..c9ce0a33 --- /dev/null +++ b/examples/asg-simple-lifecycle-hooks/data.tf @@ -0,0 +1,106 @@ +# Use the latest Amazon Linux 2 AMI +data "aws_ami" "linux2" { + owners = ["amazon"] + most_recent = true + + filter { + name = "virtualization-type" + values = ["hvm"] + } + + filter { + name = "architecture" + values = ["x86_64"] + } + + filter { + name = "root-device-type" + values = ["ebs"] + } + + filter { + name = "name" + values = ["amzn2-ami*gp2"] + } +} + +data "aws_availability_zones" "available" { +} + +data "aws_region" "current" { +} + +# Cloud init script for the autoscaling group +data "template_file" "main" { + template = file("${path.module}/cloud-config.yml") + + vars = { + region = data.aws_region.current.name + lifecycle_topic = aws_sns_topic.main.arn + } +} + +# Instance profile for the autoscaling group. +data "aws_iam_policy_document" "permissions" { + statement { + effect = "Allow" + + actions = [ + "sns:Subscribe", + "sns:Unsubscribe", + ] + + resources = [ + aws_sns_topic.main.arn, + ] + } + + statement { + effect = "Allow" + + actions = [ + "autoscaling:RecordLifecycleActionHeartbeat", + "autoscaling:CompleteLifecycleAction", + ] + + resources = ["*"] + } +} + +data "aws_iam_policy_document" "ec2_assume" { + statement { + effect = "Allow" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ec2.amazonaws.com"] + } + } +} + +data "aws_iam_policy_document" "asg_assume" { + statement { + effect = "Allow" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["autoscaling.amazonaws.com"] + } + } +} + +data "aws_iam_policy_document" "asg_permissions" { + statement { + effect = "Allow" + + resources = [ + aws_sns_topic.main.arn, + ] + + actions = [ + "sns:Publish", + ] + } +} diff --git a/examples/asg-simple-lifecycle-hooks/locals.tf b/examples/asg-simple-lifecycle-hooks/locals.tf new file mode 100644 index 00000000..aa59b1c2 --- /dev/null +++ b/examples/asg-simple-lifecycle-hooks/locals.tf @@ -0,0 +1,7 @@ +locals { + az_count = length(var.public_subnet_cidrs) + azs = slice( + data.aws_availability_zones.available.names, + 0, + local.az_count) +} diff --git a/examples/asg-simple-lifecycle-hooks/main.tf b/examples/asg-simple-lifecycle-hooks/main.tf new file mode 100644 index 00000000..4fe6e6e4 --- /dev/null +++ b/examples/asg-simple-lifecycle-hooks/main.tf @@ -0,0 +1,76 @@ +provider "aws" { + region = var.region +} + +module "vpc" { + source = "../../modules/vpc-scenario-1" + name_prefix = var.name + region = var.region + cidr = var.vpc_cidr + azs = local.azs + + extra_tags = var.extra_tags + + public_subnet_cidrs = var.public_subnet_cidrs +} + +resource "aws_key_pair" "main" { + key_name = var.name + public_key = file(var.ssh_pubkey) +} + +# SNS topic for the lifecycle hook +resource "aws_sns_topic" "main" { + name = "${var.name_prefix}-lifecycle" +} + +resource "aws_security_group" "main" { + name = "${var.lifecycle_name_prefix}-sg" + description = "Allow access to lifecycled instances" + vpc_id = module.vpc.vpc_id + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } +} + +# Allow SSH ingress if a EC2 key pair is specified. +resource "aws_security_group_rule" "ssh_ingress" { + security_group_id = aws_security_group.main.id + type = "ingress" + protocol = "tcp" + from_port = 22 + to_port = 22 + cidr_blocks = ["0.0.0.0/0"] +} + +resource "aws_iam_instance_profile" "ec2" { + name = "${var.lifecycle_name_prefix}-ec2-instance-profile" + role = aws_iam_role.ec2.name +} + +resource "aws_iam_role" "ec2" { + name = "${var.lifecycle_name_prefix}-ec2-role" + assume_role_policy = data.aws_iam_policy_document.ec2_assume.json +} + +resource "aws_iam_role_policy" "ec2" { + name = "${var.name_prefix}-ec2-permissions" + role = aws_iam_role.ec2.id + policy = data.aws_iam_policy_document.permissions.json +} + +# Execution role and policies for the lifecycle hook +resource "aws_iam_role" "lifecycle_hook" { + name = "${var.lifecycle_name_prefix}-lifecycle-role" + assume_role_policy = data.aws_iam_policy_document.asg_assume.json +} + +resource "aws_iam_role_policy" "lifecycle_hook" { + name = "${var.lifecycle_name_prefix}-lifecycle-asg-permissions" + role = aws_iam_role.lifecycle_hook.id + policy = data.aws_iam_policy_document.asg_permissions.json +} diff --git a/examples/asg-simple-lifecycle-hooks/outputs.tf b/examples/asg-simple-lifecycle-hooks/outputs.tf new file mode 100644 index 00000000..05f12337 --- /dev/null +++ b/examples/asg-simple-lifecycle-hooks/outputs.tf @@ -0,0 +1,4 @@ +output "asg_id" { + value = aws_autoscaling_group.cluster.id + description = "The autoscaling group id." +} diff --git a/examples/asg-simple-lifecycle-hooks/variables.tf b/examples/asg-simple-lifecycle-hooks/variables.tf new file mode 100644 index 00000000..0d0807ae --- /dev/null +++ b/examples/asg-simple-lifecycle-hooks/variables.tf @@ -0,0 +1,50 @@ +variable "extra_tags" { + description = "Extra tags that will be added to aws_subnet resources" + default = {} +} + +variable "lifecycle_name_prefix" { + description = "Prefix used for resource names." + default = "lcissue" +} + +variable "name_prefix" { + description = "Prefix used for resource names." + default = "sibi-issue163" +} + +variable "name" { + description = "name of the project, use as prefix to names of resources created" + default = "lifecycle" +} + +variable "region" { + description = "Region where the project will be deployed" + default = "ap-south-1" +} + +variable "vpc_cidr" { + description = "Top-level CIDR for the whole VPC network space" + default = "10.23.0.0/16" +} + +variable "ssh_pubkey" { + description = "File path to SSH public key" + default = "./id_rsa.pub" +} + +variable "ssh_key" { + description = "File path to SSH public key" + default = "./id_rsa" +} + +variable "public_subnet_cidrs" { + default = ["10.23.11.0/24", "10.23.12.0/24", "10.23.13.0/24"] + description = "A list of public subnet CIDRs to deploy inside the VPC" +} + +variable "tags" { + description = "A map of tags (key-value pairs) passed to resources." + type = map(string) + default = {} +} diff --git a/examples/asg-simple-lifecycle-hooks/versions.tf b/examples/asg-simple-lifecycle-hooks/versions.tf new file mode 100644 index 00000000..ac97c6ac --- /dev/null +++ b/examples/asg-simple-lifecycle-hooks/versions.tf @@ -0,0 +1,4 @@ + +terraform { + required_version = ">= 0.12" +} From 1a139383c20ac53f5ed408aa3da2867b0625b258 Mon Sep 17 00:00:00 2001 From: Sibi Prabakaran Date: Tue, 7 Jul 2020 19:54:32 +0530 Subject: [PATCH 2/2] Update --- examples/asg-lifecycle-hooks/README.md | 112 +++++--- examples/asg-lifecycle-hooks/cloud-config.yml | 17 +- examples/asg-lifecycle-hooks/main.tf | 262 +----------------- examples/asg-lifecycle-hooks/outputs.tf | 22 +- examples/asg-lifecycle-hooks/variables.tf | 10 + 5 files changed, 94 insertions(+), 329 deletions(-) diff --git a/examples/asg-lifecycle-hooks/README.md b/examples/asg-lifecycle-hooks/README.md index 5c3391be..91ba27c2 100644 --- a/examples/asg-lifecycle-hooks/README.md +++ b/examples/asg-lifecycle-hooks/README.md @@ -1,8 +1,17 @@ -# Example to test basic ASG integration with lifecycle hooks +# Example of basic ASG integration with lifecycle hooks -This example uses [lifecycled](https://github.com/buildkite/lifecycled) to process -lifecycle events. As of version 3.0.2 `lifecycled` supports only instance termination -events and reacts to a termination event for a node it is running on. +We use the [lifecycled](https://github.com/buildkite/lifecycled +"lifecycled") tool to run a script whenever an instance in an ASG gets +an scale-in event. + +## lifecycled + +lifecyled is an executable provided by buildkite. Note that the +executable will be run on the ASG's instance. Note that it +automatically creates an [SQS +queue](https://github.com/buildkite/lifecycled/commit/fa9f36f25a6ca6ceb3dae1814bacc26b3643392d +"SQS queue") and subscribes to the SNS topic you have created. So, +using this approaches makes you depend on two AWS services. ## Environment creation and deployment @@ -10,61 +19,82 @@ To use this example set up AWS credentials and then run the commands in the following order: ``` -make ssh-key -make init -make plan-vpc -make apply -make plan-subnets -make apply -make plan-gateway -make apply -make plan -make apply +$ make ssh-key +$ make init +$ make plan-vpc +$ make apply +$ make plan-subnets +$ make apply +$ make plan-gateway +$ make apply +$ make plan +$ make apply ``` ## Testing -Get the public IP address of the newly created ec2 web instance with the AWS console. +SSH into the ASG instance with the command: -SSH into the machine with the command: - -``` -ssh -i id_rsa ec2-user@ +``` shellsession +$ ssh -i id_rsa ec2-user@ ``` You can see in the machine that `lifecycled` daemon would be running. You can check the status of the service using +``` shellsession +$ systemctl status lifecycled.service +[ec2-user@ip-10-23-11-146 ~]$ systemctl status lifecycled.service +● lifecycled.service - Autoscale Lifecycle Daemon + Loaded: loaded (/etc/systemd/system/lifecycled.service; enabled; vendor preset: disabled) + Active: active (running) since Fri 2020-06-26 12:39:35 UTC; 3min 19s ago + Main PID: 3412 (lifecycled) + CGroup: /system.slice/lifecycled.service + └─3412 /usr/local/bin/lifecycled --no-spot --sns-topic=arn:aws:sns:ap-south-1:xxxx:sibi-issue163-lifecycle --handler=/usr/local/scripts/lifecycle-handler.sh --json + +Jun 26 12:39:35 ip-10-23-11-146.ap-south-1.compute.internal systemd[1]: Started Autoscale Lifecycle Daemon. +Jun 26 12:39:35 ip-10-23-11-146.ap-south-1.compute.internal systemd[1]: Starting Autoscale Lifecycle Daemon... +Jun 26 12:39:35 ip-10-23-11-146.ap-south-1.compute.internal lifecycled[3412]: {"level":"info","msg":"Looking up instance id from metadata service","time":"2020-06-26T12:39:35Z"} +Jun 26 12:39:35 ip-10-23-11-146.ap-south-1.compute.internal lifecycled[3412]: {"instanceId":"i-xxx","level":"info","listener":"autoscaling","msg":"Starting listener","time":"2020-06-26T12:39:35Z"} +Jun 26 12:39:35 ip-10-23-11-146.ap-south-1.compute.internal lifecycled[3412]: {"instanceId":"i-xxx","level":"info","msg":"Waiting for termination notices","time":"2020-06-26T12:39:35Z"} ``` -systemctl status lifecycled.service -``` - -Output from a handler could be seen in the service log e.g. by using - -``` -journalctl -f -u lifecycled.service -``` - - -## Test the Notification -To generate a notification for a termination event, update the Auto Scaling group by decreasing the desired capacity of the Auto Scaling group by 1. You receive a notification within a few minutes after instance termination. +## Note about lifecycle-handler.sh -To change the desired capacity using the console +For testing that the handler is working properly, you have to observe +that you are able to observe the side effect from the script. - Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/. +One easy way to validate is by spawning a new EC2 instance with a +sample website deployed in it (You could use busybox for it) and then +doing a SSH and watching to see if you are able to observe any logs on +it whenever it's being hit with an HTTP request. Example: - On the navigation pane, under Auto Scaling, choose Auto Scaling Groups. +``` shellsession +ubuntu@ip-10-23-11-56:~/test$ sudo busybox httpd -f -v -p 80 -h . +[::ffff:49.207.192.240]:38924: response:200 +``` - Select your Auto Scaling group. +You can modify your [script](./cloud-config.yml) to have curl hit it: - On the Details tab, choose Edit. +``` shellsession +$ curl http://x.x.x.x +``` - For Desired, decrease the current value by 1. +## Test the Notification - Choose Save. +To generate a notification for a launch event, update the Auto Scaling +group by decreasing the desired capacity of the Auto Scaling group +by 1. That will make the lifecycle handler to get triggered. These are +the steps to decreased the desired capacity using AWS console: - After a few minutes, you'll see that the lifecycle-handler.sh script will be executed and it's side effect operation will be performed: in the log of lifecycled.service you'll see a line with something like "hello from the handler, received autoscaling:EC2_INSTANCE_TERMINATING i-01234567890123456" +* Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/. +* On the navigation pane, under Auto Scaling, choose Auto Scaling Groups. +* Select your Auto Scaling group. +* On the Details tab, choose Edit. +* For Desired, decrease the current value by 1. +* Choose Save. +* After a few minutes, you'll see that the lifecycle-handler.sh script + will be executed and it's side effect operation will be performed. ## Destruction @@ -74,7 +104,3 @@ To destroy the test environment run the following commands: make destroy make clean ``` - -## Notes -- This example was last tested with `Terraform v0.12.4` -- This example assumes AWS credentials setup with access to the **us-east-2** region. diff --git a/examples/asg-lifecycle-hooks/cloud-config.yml b/examples/asg-lifecycle-hooks/cloud-config.yml index 0279a3df..4ff00da5 100644 --- a/examples/asg-lifecycle-hooks/cloud-config.yml +++ b/examples/asg-lifecycle-hooks/cloud-config.yml @@ -1,9 +1,5 @@ #cloud-config write_files: - - path: "/index.html" - permissions: "0644" - owner: "root" - content: "hello world" - path: "/etc/systemd/system/lifecycled.service" permissions: "0644" owner: "root" @@ -32,10 +28,10 @@ write_files: set -euo pipefail - echo "hello from the handler, received $${@-nothing}" - curl http://localhost:3000 - echo - sleep 10 + echo "hello from the handler" + # Have a side effect which you can observe + # Example side effect: curl http://x.x.x.x + sleep 120 echo "goodbye from the handler" runcmd: - | @@ -46,8 +42,3 @@ runcmd: echo "lifecycled installed" - | systemctl enable lifecycled.service --now - - | - wget https://www.busybox.net/downloads/binaries/1.28.1-defconfig-multiarch/busybox-x86_64 - chmod +x busybox-x86_64 - nohup ./busybox-x86_64 httpd -f -p 3000 & - curl http://localhost:3000 diff --git a/examples/asg-lifecycle-hooks/main.tf b/examples/asg-lifecycle-hooks/main.tf index 1a8f1737..4fe6e6e4 100644 --- a/examples/asg-lifecycle-hooks/main.tf +++ b/examples/asg-lifecycle-hooks/main.tf @@ -1,169 +1,31 @@ -/** - * ## Example to demonstrate basic ASG integration with lifecycle hooks - */ provider "aws" { region = var.region } -data "aws_availability_zones" "available" { -} - -data "aws_region" "current" { -} - -data "aws_caller_identity" "current" { -} - -# Cloud init script for the autoscaling group -data "template_file" "main" { - template = file("${path.module}/cloud-config.yml") - - vars = { - region = data.aws_region.current.name - stack_name = "${var.name_prefix}-asg" - lifecycle_topic = aws_sns_topic.main.arn - elb_name = aws_elb.web.name - } -} - module "vpc" { source = "../../modules/vpc-scenario-1" - name_prefix = var.name_prefix + name_prefix = var.name region = var.region cidr = var.vpc_cidr azs = local.azs - extra_tags = { - kali = "ma" - } + extra_tags = var.extra_tags public_subnet_cidrs = var.public_subnet_cidrs } -# Use the latest Amazon Linux 2 AMI -data "aws_ami" "linux2" { - owners = ["amazon"] - most_recent = true - - filter { - name = "virtualization-type" - values = ["hvm"] - } - - filter { - name = "architecture" - values = ["x86_64"] - } - - filter { - name = "root-device-type" - values = ["ebs"] - } - - filter { - name = "name" - values = ["amzn2-ami*gp2"] - } -} - resource "aws_key_pair" "main" { - key_name = var.name_prefix + key_name = var.name public_key = file(var.ssh_pubkey) } -# Security group for the elastic load balancer, web instance, only accessible from ELB -module "elb-sg" { - source = "../../modules/security-group-base" - description = "Allow public access to ELB in ${var.name_prefix}" - name = "${var.name_prefix}-elb" - vpc_id = module.vpc.vpc_id -} - -# shared security group for SSH -module "web-public-ssh-rule" { - source = "../../modules/ssh-sg" - security_group_id = module.elb-sg.id -} - -# security group rule for elb open inbound http -module "elb-http-rule" { - source = "../../modules/single-port-sg" - port = 80 - cidr_blocks = ["0.0.0.0/0"] - security_group_id = module.elb-sg.id - description = "open HTTP on the ELB to public access" -} - -# security group rule for elb open egress (outbound from nodes) -module "elb-open-egress-rule" { - source = "../../modules/open-egress-sg" - security_group_id = module.elb-sg.id -} - -# allow HTTP from ELB to web instances -module "web-http-elb-sg-rule" { - source = "../../modules/single-port-sg" - port = "3000" - description = "Allow ELB HTTP to web app on port 3000" - cidr_blocks = module.vpc.public_cidr_blocks - security_group_id = module.elb-sg.id -} - -# Load Balancer -resource "aws_elb" "web" { - name = "${var.name_prefix}-elb" - - health_check { - healthy_threshold = 2 - interval = 15 - target = "TCP:3000" - timeout = "5" - unhealthy_threshold = 10 - } - - # public, or private to VPC? - internal = false - - # route HTTPS to services app on port 3000 - listener { - instance_port = 3000 - instance_protocol = "http" - lb_port = 80 - lb_protocol = "http" - } - - # Ensure we allow incoming traffic to the ELB, HTTP/S - security_groups = [module.elb-sg.id] - - # ELBs in the public subnets, separate from the web ASG in private subnets - subnets = module.vpc.public_subnet_ids -} - # SNS topic for the lifecycle hook resource "aws_sns_topic" "main" { name = "${var.name_prefix}-lifecycle" } -module "asg" { - source = "../../modules/asg" - name_prefix = var.name_prefix - elb_names = [aws_elb.web.name] - subnet_ids = module.vpc.public_subnet_ids - min_nodes = 1 - max_nodes = 4 - ami = data.aws_ami.linux2.id - instance_type = "t2.nano" - key_name = aws_key_pair.main.key_name - user_data = data.template_file.main.rendered - enable_terminating_hook = true - lifecycle_sns_topic_arn = aws_sns_topic.main.arn - aws_role_arn = aws_iam_role.lifecycle_hook.arn - iam_profile = aws_iam_instance_profile.ec2.name - security_group_ids = [module.elb-sg.id, aws_security_group.main.id] -} - resource "aws_security_group" "main" { - name = "${var.name_prefix}-sg" + name = "${var.lifecycle_name_prefix}-sg" description = "Allow access to lifecycled instances" vpc_id = module.vpc.vpc_id @@ -185,74 +47,13 @@ resource "aws_security_group_rule" "ssh_ingress" { cidr_blocks = ["0.0.0.0/0"] } -# Instance profile for the autoscaling group. -data "aws_iam_policy_document" "permissions" { - statement { - effect = "Allow" - - actions = [ - "logs:DescribeLogStreams", - ] - - resources = [ - "*", - ] - } - - statement { - effect = "Allow" - - actions = [ - "sns:Subscribe", - "sns:Unsubscribe", - ] - - resources = [ - aws_sns_topic.main.arn, - ] - } - - statement { - effect = "Allow" - - actions = [ - "sqs:*", - ] - - resources = ["arn:aws:sqs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:lifecycled-*"] - } - - statement { - effect = "Allow" - - actions = [ - "autoscaling:RecordLifecycleActionHeartbeat", - "autoscaling:CompleteLifecycleAction", - ] - - resources = ["*"] - } - - statement { - effect = "Allow" - - actions = [ - "elasticloadbalancing:DeregisterInstancesFromLoadBalancer", - "ec2:DescribeClassicLinkInstances", - "ec2:DescribeInstances", - ] - - resources = [aws_elb.web.arn] - } -} - resource "aws_iam_instance_profile" "ec2" { - name = "${var.name_prefix}-ec2-instance-profile" + name = "${var.lifecycle_name_prefix}-ec2-instance-profile" role = aws_iam_role.ec2.name } resource "aws_iam_role" "ec2" { - name = "${var.name_prefix}-ec2-role" + name = "${var.lifecycle_name_prefix}-ec2-role" assume_role_policy = data.aws_iam_policy_document.ec2_assume.json } @@ -262,61 +63,14 @@ resource "aws_iam_role_policy" "ec2" { policy = data.aws_iam_policy_document.permissions.json } -data "aws_iam_policy_document" "ec2_assume" { - statement { - effect = "Allow" - actions = ["sts:AssumeRole"] - - principals { - type = "Service" - identifiers = ["ec2.amazonaws.com"] - } - } -} - # Execution role and policies for the lifecycle hook resource "aws_iam_role" "lifecycle_hook" { - name = "${var.name_prefix}-lifecycle-role" + name = "${var.lifecycle_name_prefix}-lifecycle-role" assume_role_policy = data.aws_iam_policy_document.asg_assume.json } resource "aws_iam_role_policy" "lifecycle_hook" { - name = "${var.name_prefix}-lifecycle-asg-permissions" + name = "${var.lifecycle_name_prefix}-lifecycle-asg-permissions" role = aws_iam_role.lifecycle_hook.id policy = data.aws_iam_policy_document.asg_permissions.json } - -data "aws_iam_policy_document" "asg_assume" { - statement { - effect = "Allow" - actions = ["sts:AssumeRole"] - - principals { - type = "Service" - identifiers = ["autoscaling.amazonaws.com"] - } - } -} - -data "aws_iam_policy_document" "asg_permissions" { - statement { - effect = "Allow" - - resources = [ - aws_sns_topic.main.arn, - ] - - actions = [ - "sns:Publish", - ] - } -} - -locals { - az_count = length(var.public_subnet_cidrs) - azs = slice( - data.aws_availability_zones.available.names, - 0, - local.az_count) -} - diff --git a/examples/asg-lifecycle-hooks/outputs.tf b/examples/asg-lifecycle-hooks/outputs.tf index 9631077d..05f12337 100644 --- a/examples/asg-lifecycle-hooks/outputs.tf +++ b/examples/asg-lifecycle-hooks/outputs.tf @@ -1,20 +1,4 @@ -output "elb_dns" { - value = "http://${aws_elb.web.dns_name}" - description = "DNS name of the ELB" +output "asg_id" { + value = aws_autoscaling_group.cluster.id + description = "The autoscaling group id." } - -output "elb_name" { - value = aws_elb.web.name - description = "ELB Name" -} - -output "elb_instances" { - value = aws_elb.web.instances - description = "ELB Instances" -} - -output "elb_arn" { - value = aws_elb.web.arn - description = "ELB Arn" -} - diff --git a/examples/asg-lifecycle-hooks/variables.tf b/examples/asg-lifecycle-hooks/variables.tf index d2f35674..b682dd56 100644 --- a/examples/asg-lifecycle-hooks/variables.tf +++ b/examples/asg-lifecycle-hooks/variables.tf @@ -3,11 +3,21 @@ variable "extra_tags" { default = {} } +variable "lifecycle_name_prefix" { + description = "Prefix used for resource names." + default = "lifecycled-eg" +} + variable "name_prefix" { description = "Prefix used for resource names." default = "asg-lc" } +variable "name" { + description = "name of the project, use as prefix to names of resources created" + default = "test-lifecycle-project" +} + variable "region" { description = "Region where the project will be deployed" default = "us-east-2"