Skip to content

Commit 92eb031

Browse files
committed
feat: isolated volume resource; data backup via lifecycle manager policy
1 parent 4455f28 commit 92eb031

File tree

5 files changed

+165
-81
lines changed

5 files changed

+165
-81
lines changed

cloud-init/cloud-init.yml

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,29 @@ packages:
33
- htop
44
- docker
55

6+
bootcmd:
7+
# cloud-init runs before EBS volume mount (sometimes?), so we have to wait. See https://stackoverflow.com/a/77868589/11138267
8+
- echo "$(date --rfc-3339=ns) | Waiting for EBS volume device to be available"
9+
- echo "$(lsblk)"
10+
- timeout 30s sh -c 'while [ ! -e ${device_name} ]; do sleep 1; done'
11+
- echo "$(date --rfc-3339=ns) | Device found"
12+
- echo "$(lsblk)"
13+
614
timezone: ${timezone}
715

816
device_aliases: { "minecraft_data": "${device_name}" }
917
disk_setup:
1018
minecraft_data:
1119
table_type: gpt
1220
layout: true
21+
overwrite: true
1322

1423
fs_setup:
1524
- device: minecraft_data
1625
label: Minecraft
1726
filesystem: xfs
1827
partition: any
28+
overwrite: true
1929

2030
mounts:
2131
- [
@@ -27,32 +37,6 @@ mounts:
2737
"2",
2838
]
2939

30-
runcmd:
31-
- systemctl daemon-reload
32-
# Finish Duck DNS setup
33-
- systemctl enable duck.service
34-
- systemctl start duck.service
35-
# Manually install Docker Compose plugin
36-
- mkdir -p /usr/local/lib/docker/cli-plugins
37-
- [
38-
curl,
39-
-SL,
40-
"https://github.com/docker/compose/releases/download/v2.27.1/docker-compose-linux-aarch64",
41-
-o,
42-
/usr/local/lib/docker/cli-plugins/docker-compose,
43-
]
44-
- chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
45-
# Finish Docker setup
46-
- usermod -a -G docker ec2-user # Allow docker commands without sudo
47-
- systemctl enable docker
48-
- systemctl start docker
49-
# Initialize Minecraft server
50-
- chown ec2-user:ec2-user -R ${minecraft_data_path} # Fix permissions
51-
- systemctl enable minecraft
52-
- systemctl start minecraft
53-
- systemctl enable minecraft_shutdown.timer
54-
- systemctl start minecraft_shutdown.timer
55-
5640
write_files:
5741
# Duck DNS files
5842
- path: /home/ec2-user/duck.sh
@@ -92,3 +76,31 @@ write_files:
9276
defer: true
9377
encoding: base64
9478
content: ${minecraft_shutdown_timer_file_content_b64}
79+
80+
runcmd:
81+
- lsblk
82+
- cat /etc/fstab
83+
- systemctl daemon-reload
84+
# Finish Duck DNS setup
85+
- systemctl enable duck.service
86+
- systemctl start duck.service
87+
# Manually install Docker Compose plugin
88+
- mkdir -p /usr/local/lib/docker/cli-plugins
89+
- [
90+
curl,
91+
-SL,
92+
"https://github.com/docker/compose/releases/download/v2.27.1/docker-compose-linux-aarch64",
93+
-o,
94+
/usr/local/lib/docker/cli-plugins/docker-compose,
95+
]
96+
- chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
97+
# Finish Docker setup
98+
- usermod -a -G docker ec2-user # Allow docker commands without sudo
99+
- systemctl enable docker
100+
- systemctl start docker
101+
# Initialize Minecraft server
102+
- chown ec2-user:ec2-user -R ${minecraft_data_path} # Fix permissions
103+
- systemctl enable minecraft
104+
- systemctl start minecraft
105+
- systemctl enable minecraft_shutdown.timer
106+
- systemctl start minecraft_shutdown.timer

dlm.tf

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
data "aws_iam_policy_document" "assume_role_dlm" {
2+
statement {
3+
effect = "Allow"
4+
5+
principals {
6+
type = "Service"
7+
identifiers = ["dlm.amazonaws.com"]
8+
}
9+
10+
actions = ["sts:AssumeRole"]
11+
}
12+
}
13+
14+
data "aws_iam_policy_document" "dlm_lifecycle" {
15+
statement {
16+
effect = "Allow"
17+
18+
actions = [
19+
"ec2:CreateSnapshot",
20+
"ec2:CreateSnapshots",
21+
"ec2:DeleteSnapshot",
22+
"ec2:DescribeInstances",
23+
"ec2:DescribeVolumes",
24+
"ec2:DescribeSnapshots",
25+
]
26+
27+
resources = ["*"]
28+
}
29+
30+
statement {
31+
effect = "Allow"
32+
actions = ["ec2:CreateTags"]
33+
resources = ["arn:aws:ec2:*::snapshot/*"]
34+
}
35+
}
36+
37+
// ---
38+
39+
resource "aws_iam_role" "dlm_lifecycle_role" {
40+
name = "${local.title_PascalCase}-AWSDataLifecycleManagerServiceRole"
41+
assume_role_policy = data.aws_iam_policy_document.assume_role_dlm.json
42+
managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AWSDataLifecycleManagerServiceRole"]
43+
}
44+
45+
resource "aws_iam_role_policy" "dlm_lifecycle" {
46+
name = "${local.title_PascalCase}-LifecycleRole"
47+
role = aws_iam_role.dlm_lifecycle_role.id
48+
policy = data.aws_iam_policy_document.dlm_lifecycle.json
49+
}
50+
51+
resource "aws_dlm_lifecycle_policy" "backup_minecraft_data" {
52+
description = "Takes daily snapshots of Minecraft data EBS volumes and retains them for two weeks"
53+
execution_role_arn = aws_iam_role.dlm_lifecycle_role.arn
54+
state = "ENABLED"
55+
56+
policy_details {
57+
resource_types = ["VOLUME"]
58+
59+
schedule {
60+
name = "Last 7 days"
61+
62+
create_rule {
63+
interval = 24
64+
interval_unit = "HOURS"
65+
times = ["07:12"]
66+
}
67+
68+
retain_rule {
69+
count = 7
70+
}
71+
72+
tags_to_add = {
73+
SnapshotCreator = "DLM"
74+
}
75+
76+
copy_tags = true
77+
}
78+
79+
target_tags = {
80+
"minecraft-spot-discord:data-volume" = true
81+
}
82+
}
83+
84+
tags = {
85+
Name = "${local.title} Backup Lifecycle Policy"
86+
}
87+
}

ec2.tf

Lines changed: 29 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -79,30 +79,28 @@ resource "aws_key_pair" "ec2_spot_instance" {
7979
public_key = var.instance_key_pair_public_key
8080
}
8181

82-
# resource "aws_ebs_volume" "minecraft" {
83-
# availability_zone = var.subnet_az
84-
# type = "gp3"
85-
# size = var.minecraft_data_volume_size
86-
# iops = 3000
87-
# throughput = 125
88-
89-
# tags = {
90-
# Name = "${local.title} Minecraft Data Volume"
91-
# "minecraft-spot-discord:created-at" : time_static.ebs_creation_date.rfc3339
92-
# }
93-
94-
# # lifecycle {
95-
# # prevent_destroy = true
96-
# # }
97-
# }
98-
99-
# resource "aws_volume_attachment" "attach_minecraft_data_to_instance" {
100-
# device_name = local.device_name
101-
# volume_id = aws_ebs_volume.minecraft.id
102-
# instance_id = module.ec2_spot_instance.spot_instance_id
103-
104-
# stop_instance_before_detaching = true
105-
# }
82+
resource "aws_ebs_volume" "minecraft" {
83+
availability_zone = var.subnet_az
84+
type = "gp3"
85+
size = var.minecraft_data_volume_size
86+
iops = 3000
87+
throughput = 125
88+
89+
final_snapshot = true
90+
91+
tags = {
92+
Name = "${local.title} Minecraft Data Volume"
93+
"minecraft-spot-discord:data-volume" : true
94+
}
95+
}
96+
97+
resource "aws_volume_attachment" "attach_minecraft_data_to_instance" {
98+
device_name = local.device_name
99+
volume_id = aws_ebs_volume.minecraft.id
100+
instance_id = module.ec2_spot_instance.spot_instance_id
101+
102+
stop_instance_before_detaching = true
103+
}
106104

107105
module "ec2_spot_instance" {
108106
source = "terraform-aws-modules/ec2-instance/aws"
@@ -120,36 +118,23 @@ module "ec2_spot_instance" {
120118
subnet_id = module.vpc.public_subnets[0]
121119
associate_public_ip_address = true
122120

123-
monitoring = true
124-
key_name = aws_key_pair.ec2_spot_instance.key_name
121+
# monitoring = true
122+
key_name = aws_key_pair.ec2_spot_instance.key_name
125123

126124
spot_instance_interruption_behavior = "stop"
127125

128126
user_data = local.ec2_user_data
129127

130128
enable_volume_tags = false
131-
ebs_block_device = [{
132-
volume_type = "gp3"
133-
volume_size = var.minecraft_data_volume_size
134-
iops = 3000
135-
throughput = 125
136-
137-
device_name = local.device_name
138-
delete_on_termination = false
139-
140-
tags = {
141-
Name = "${local.title} Minecraft Data Volume"
142-
}
143-
}]
144129

145130
// Due to bug in the provider, spot instances and its root volumes are not being tagged automatically
146131
# instance_tags = { Name = "${local.title} Spot Instance" }
147132
# enable_volume_tags = false
148-
# root_block_device = [{
149-
# tags = {
150-
# Name = "${local.title} Root Volume"
151-
# }
152-
# }]
133+
root_block_device = [{
134+
tags = {
135+
Name = "${local.title} Root Volume"
136+
}
137+
}]
153138
tags = {
154139
Name = "${local.title} Spot Instance Request"
155140
}

lambda/manage-instance/index.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,8 @@ import {
77
} from '@aws-sdk/client-ec2';
88
import type { Handler, SNSEvent } from 'aws-lambda';
99
import { captureAWSv3Client } from 'aws-xray-sdk-core';
10-
import {
11-
InteractionResponseType,
12-
RESTPatchAPIInteractionOriginalResponseJSONBody,
13-
type RESTPostAPIInteractionCallbackJSONBody,
14-
} from 'discord-api-types/v10';
1510
import { captureFetchGlobal } from 'aws-xray-sdk-fetch';
11+
import { RESTPatchAPIInteractionOriginalResponseJSONBody } from 'discord-api-types/v10';
1612

1713
// comment for debugging bundle
1814
//!
@@ -73,9 +69,13 @@ const sendEC2Command = async (instanceId: string, command: string) => {
7369

7470
return (
7571
`Addresses:\n` +
76-
`- **\`${PublicIpAddress}:${MINECRAFT_PORT}\`**\n` +
77-
`- \`${PublicDnsName}:${MINECRAFT_PORT}\`\n` +
78-
`- \`${DUCKDNS_DOMAIN}.duckdns.org:${MINECRAFT_PORT}\``
72+
(PublicIpAddress
73+
? `- **\`${PublicIpAddress}:${MINECRAFT_PORT}\`**\n`
74+
: '') +
75+
(PublicDnsName
76+
? `- \`${PublicDnsName}:${MINECRAFT_PORT}\`\n`
77+
: '') +
78+
`- \`${DUCKDNS_DOMAIN}.duckdns.org:${MINECRAFT_PORT}\` (Dynamic)`
7979
);
8080
});
8181
default:

variables.tf

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,14 @@ variable "minecraft_compose_ports" {
117117
variable "minecraft_compose_environment" {
118118
type = map(string)
119119
default = {
120-
"INIT_MEMORY" : "6000M"
121-
"MAX_MEMORY" : "6000M"
120+
"INIT_MEMORY" : "5900M"
121+
"MAX_MEMORY" : "5900M"
122122
}
123123
}
124124

125125
variable "minecraft_compose_limits" {
126126
type = map(string)
127127
default = {
128-
memory : "7500mb"
128+
memory : "7400mb"
129129
}
130130
}

0 commit comments

Comments
 (0)