Skip to content
This repository was archived by the owner on Jul 11, 2023. It is now read-only.

Support multiple EBS volumes attached and persisted to single node ASG. #319

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
40 changes: 40 additions & 0 deletions examples/single-node-asg-tester/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.PHONY: init ssh-key apply destroy clean

.DEFAULT_GOAL = help

## Runs terraform get and terraform init for env
init:
@terraform get
@terraform init

## Create ssh key
ssh-key:
@ssh-keygen -q -N "" -C "SSH key for vpc-scenario-1 example" -f ./id_rsa

## use 'terraform apply' to apply the setup.
apply:
@terraform apply

## use 'terraform destroy' to remove all resources from AWS
destroy:
@terraform destroy

## rm -rf all files and state
clean:
@rm -f id_rsa
@rm -f id_rsa.pub
@rm -f terraform.*.backup
@rm -f terraform.tfstate

## Show help screen.
help:
@echo "Please use \`make <target>' where <target> 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)
5 changes: 5 additions & 0 deletions examples/single-node-asg-tester/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# single node asg tester

This example shows the basic usage of `single-node-asg` module, especially the multiple EBS attachments.

The module keeps one and only one instance up at all time. And the EBS volumes are reattached when a new instance is up. Hence they are always accessible.
55 changes: 55 additions & 0 deletions examples/single-node-asg-tester/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
locals {
cidr = "192.168.0.0/16"
private_subnet_cidrs = ["192.168.100.0/24", "192.168.101.0/24"]
public_subnet_cidrs = ["192.168.0.0/24", "192.168.1.0/24"]
region = "ap-northeast-1"
}

data "aws_availability_zones" "azs" { }

module "vpc" {
source = "fpco/foundation/aws//modules/vpc-scenario-2"
cidr = local.cidr
public_subnet_cidrs = local.public_subnet_cidrs
private_subnet_cidrs = local.private_subnet_cidrs
azs = data.aws_availability_zones.azs.names
name_prefix = "test"
region = local.region
}

module "ubuntu" {
source = "fpco/foundation/aws//modules/ami-ubuntu"
}

resource "aws_key_pair" "main" {
public_key = file("./id_rsa.pub")
}

resource "aws_security_group" "ssh" {
vpc_id = module.vpc.vpc_id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}

module "tester" {
source = "../../modules/single-node-asg"
name_prefix = "ebs"
name_suffix = "test"
key_name = aws_key_pair.main.key_name
ami = module.ubuntu.id
instance_type = "t2.micro"
subnet_id = module.vpc.public_subnet_ids[0]
security_group_ids = [aws_security_group.ssh.id]
region = local.region
data_volumes = [{ name = "a", device = "/dev/xvdm", size = 50 }, { name = "b", device = "/dev/xvdn" }]
}
1 change: 0 additions & 1 deletion modules/alb-default-forward/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ resource "aws_lb_listener" "lb-listener" {
}

resource "aws_lb_target_group" "lb-tg" {
name = "${var.name_prefix}-tg"
port = var.service_port
protocol = "HTTP"
vpc_id = var.vpc_id
Expand Down
1 change: 0 additions & 1 deletion modules/alb/main.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
resource "aws_lb" "alb" {
name = "${var.name_prefix}-alb"
internal = var.internal
load_balancer_type = "application"
security_groups = [aws_security_group.alb_sg.id]
Expand Down
7 changes: 7 additions & 0 deletions modules/asg/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
locals {
name_prefix_with_suffix = "${var.name_prefix}-${var.name_suffix}"
name_prefix_without_suffix = "${var.name_prefix}"

name_prefix = "${var.name_suffix != "" ? local.name_prefix_without_suffix : local.name_prefix_with_suffix}"

}
4 changes: 2 additions & 2 deletions modules/asg/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ resource "aws_autoscaling_group" "cluster" {
load_balancers = var.elb_names
max_size = var.max_nodes
min_size = var.min_nodes
name_prefix = "${var.name_prefix}-${var.name_suffix}"
name_prefix = local.name_prefix
placement_group = var.placement_group
termination_policies = var.termination_policies
protect_from_scale_in = var.protect_from_scale_in
Expand Down Expand Up @@ -71,7 +71,7 @@ resource "aws_autoscaling_group" "cluster" {
[
{
"key" = "Name"
"value" = "${var.name_prefix}-${var.name_suffix}"
"value" = local.name_prefix
"propagate_at_launch" = true
},
],
Expand Down
10 changes: 10 additions & 0 deletions modules/init-snippet-attach-ebs-volume/instance_id.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
if which wget; then
INSTANCE_ID="$(wget -O- http://169.254.169.254/latest/meta-data/instance-id)"
elif which curl; then
INSTANCE_ID="$(curl http://169.254.169.254/latest/meta-data/instance-id)"
fi

if [ "x$${INSTANCE_ID}" == "x" ]; then
echo 'There is no wget or curl tool installed. Hence bootstrap cannot get instance ID.'
exit 1
fi
66 changes: 14 additions & 52 deletions modules/init-snippet-attach-ebs-volume/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -9,70 +9,32 @@
*
*/

# variables used by this snippet of init shellcode
variable "device_path" {
default = "/dev/xvdf"
description = "path, to the device's path in /dev/"
type = string
}

variable "init_prefix" {
default = ""
description = "initial init (shellcode) to prefix this snippet with"
type = string
}

variable "init_suffix" {
default = ""
description = "init (shellcode) to append to the end of this snippet"
type = string
}

variable "log_level" {
default = "info"
description = "default log level verbosity for apps that support it"
type = string
}

variable "log_prefix" {
default = "OPS: "
description = "string to prefix log messages with"
type = string
}

variable "region" {
description = "AWS region the volume is in"
type = string
}

variable "wait_interval" {
default = "5"
description = "time (in seconds) to wait when looping to find the device"
type = number
}

variable "volume_id" {
description = "ID of the EBS volume to attach"
type = string
}

# render init script for a cluster using our generic template
data "template_file" "init_snippet" {
count = length(var.volume_ids)

template = file("${path.module}/snippet.tpl")

vars = {
device_path = var.device_path
init_prefix = var.init_prefix
init_suffix = var.init_suffix
device_path = var.device_paths[count.index]
log_prefix = var.log_prefix
log_level = var.log_level
region = var.region
volume_id = var.volume_id
volume_id = var.volume_ids[count.index]
wait_interval = var.wait_interval
}
}

data "template_file" "instance_id" {
template = file("${path.module}/instance_id.tpl")
}

output "init_snippet" {
value = data.template_file.init_snippet.rendered
value = <<EOF
${var.init_prefix}
${data.template_file.instance_id.rendered}
${join("\n", data.template_file.init_snippet.*.rendered)}
${var.init_suffix}
EOF
}

37 changes: 11 additions & 26 deletions modules/init-snippet-attach-ebs-volume/snippet.tpl
Original file line number Diff line number Diff line change
@@ -1,34 +1,19 @@
# start snippet - attach EBS volume
${init_prefix}
export AWS_DEFAULT_REGION=${region}
VOLUME_ID=${volume_id}
if which wget; then
INSTANCE_ID="$(wget -O- http://169.254.169.254/latest/meta-data/instance-id)"
elif which curl; then
INSTANCE_ID="$(curl http://169.254.169.254/latest/meta-data/instance-id)"
fi
echo "${log_prefix} will attach $${VOLUME_ID} via the AWS API in ${region}"
while ! aws ec2 attach-volume \
--volume-id "$${VOLUME_ID}" \
--instance-id "$${INSTANCE_ID}" \
--device '${device_path}'; do
echo "Attaching command failed to run. Retrying."
sleep '${wait_interval}'
done
echo "${log_prefix} $${VOLUME_ID} attached."
sleep '${wait_interval}' # Wait for device up

if [ "x$${INSTANCE_ID}" == "x" ]; then
echo 'OS not functioning'
else
echo "${log_prefix} will attach $${VOLUME_ID} via the AWS API in ${region}"
while ! aws ec2 attach-volume \
--volume-id "$${VOLUME_ID}" \
--instance-id "$${INSTANCE_ID}" \
--device '${device_path}'; do
echo "Attaching command failed to run. Retrying."
sleep '${wait_interval}'
done
echo "${log_prefix} $${VOLUME_ID} attached."

if [ ! -e ${device_path} ]; then
vol_id="$(echo "$${VOLUME_ID}" | tr -d '-')"
while [ ! -e /dev/disk/by-id/*-Amazon_Elastic_Block_Store_$${vol_id} ]; do
sleep '${wait_interval}'
done

dev_id="$(ls /dev/disk/by-id/*-Amazon_Elastic_Block_Store_$${vol_id} | head -1)"
dev_name="/dev/$(readlink "$${dev_id}" | tr / '\n' | tail -1)"
[ "$${dev_name}" == "${device_path}" ] || ln -s "$${dev_name}" "${device_path}"
fi

${init_suffix}
46 changes: 46 additions & 0 deletions modules/init-snippet-attach-ebs-volume/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
variable "device_paths" {
type = list(string)
description = "paths, to the device's path in /dev/"
default = []
}

variable "init_prefix" {
default = ""
description = "initial init (shellcode) to prefix this snippet with"
type = string
}

variable "init_suffix" {
default = ""
description = "init (shellcode) to append to the end of this snippet"
type = string
}

variable "log_level" {
default = "info"
description = "default log level verbosity for apps that support it"
type = string
}

variable "log_prefix" {
default = "OPS: "
description = "string to prefix log messages with"
type = string
}

variable "region" {
description = "AWS region the volume is in"
type = string
}

variable "wait_interval" {
default = "5"
description = "time (in seconds) to wait when looping to find the device"
type = number
}

variable "volume_ids" {
description = "IDs of the EBS volumes to attach"
type = list(string)
default = []
}
6 changes: 2 additions & 4 deletions modules/persistent-ebs/data.tf
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ data "aws_iam_policy_document" "attach_ebs_policy_doc" {
"ec2:DetachVolume",
]

resources = [
"arn:${data.aws_partition.current.partition}:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:volume/${aws_ebs_volume.main.id}",
"arn:${data.aws_partition.current.partition}:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:instance/*",
]
resources = concat(["arn:${data.aws_partition.current.partition}:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:instance/*"],
[for x in aws_ebs_volume.main.*.id : "arn:${data.aws_partition.current.partition}:ec2:${var.region}:${data.aws_caller_identity.current.account_id}:volume/${x}"])
}
}
11 changes: 11 additions & 0 deletions modules/persistent-ebs/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
locals {
volume_default = {
type = "gp2"
iops = 0
size = 15
encrypted = true
kms_key_id = ""
snapshot_id = ""
}
volumes_default = [for x in var.volumes : merge(local.volume_default, x)]
}
16 changes: 9 additions & 7 deletions modules/persistent-ebs/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,27 @@
*/

resource "aws_ebs_volume" "main" {
count = length(local.volumes_default)

availability_zone = var.az
size = var.size
type = var.volume_type
encrypted = var.encrypted
kms_key_id = var.kms_key_id
snapshot_id = var.snapshot_id
size = local.volumes_default[count.index].size
type = local.volumes_default[count.index].type
encrypted = local.volumes_default[count.index].encrypted
kms_key_id = local.volumes_default[count.index].kms_key_id
snapshot_id = local.volumes_default[count.index].snapshot_id

# merge Name w/ extra_tags
tags = merge(
{
"Name" = "${var.name_prefix}-${var.az}"
"Name" = "${var.name_prefix}-${var.az}-${local.volumes_default[count.index].name}"
},
var.extra_tags,
)
}

# IAM policy that allows attaching this EBS volume to an EC2 instance
resource "aws_iam_policy" "attach_ebs" {
name = "${var.name_prefix}-attach-ebs-${aws_ebs_volume.main.id}"
name = "${var.name_prefix}-attach-ebs"
policy = data.aws_iam_policy_document.attach_ebs_policy_doc.json
}

Expand Down
4 changes: 2 additions & 2 deletions modules/persistent-ebs/outputs.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
output "volume_id" {
value = aws_ebs_volume.main.id
output "volume_ids" {
value = aws_ebs_volume.main.*.id
description = "`id` exported from the `aws_ebs_volume`"
}

Expand Down
Loading