From 2ac422441b770f652c97be7df7f8028a7cdce5bb Mon Sep 17 00:00:00 2001 From: xpk Date: Mon, 26 Feb 2024 11:05:53 +0800 Subject: [PATCH] NEW: ec2 instance scheduler --- .../ec2-instance-scheduler/Ec2Scheduler.py | 16 ++ .../compute/ec2-instance-scheduler/README.md | 52 ++++++ .../compute/ec2-instance-scheduler/main.tf | 171 ++++++++++++++++++ .../ec2-instance-scheduler/variables.tf | 19 ++ .../ec2-instance-scheduler/versions.tf | 13 ++ 5 files changed, 271 insertions(+) create mode 100644 modules/compute/ec2-instance-scheduler/Ec2Scheduler.py create mode 100644 modules/compute/ec2-instance-scheduler/README.md create mode 100644 modules/compute/ec2-instance-scheduler/main.tf create mode 100644 modules/compute/ec2-instance-scheduler/variables.tf create mode 100644 modules/compute/ec2-instance-scheduler/versions.tf diff --git a/modules/compute/ec2-instance-scheduler/Ec2Scheduler.py b/modules/compute/ec2-instance-scheduler/Ec2Scheduler.py new file mode 100644 index 0000000..a1d98c0 --- /dev/null +++ b/modules/compute/ec2-instance-scheduler/Ec2Scheduler.py @@ -0,0 +1,16 @@ +import boto3 +import os +import json + +# reference: https://aws.amazon.com/premiumsupport/knowledge-center/start-stop-lambda-eventbridge/ + +ec2 = boto3.client('ec2', region_name=os.environ['region_name']) + +def lambda_handler(event, context): + if (event['action'] == 'start'): + resp = ec2.start_instances(InstanceIds=json.loads(os.environ['instances'])) + elif (event['action'] == 'stop'): + resp = ec2.stop_instances(InstanceIds=json.loads(os.environ['instances'])) + else: + resp = "Event action not provided" + return resp \ No newline at end of file diff --git a/modules/compute/ec2-instance-scheduler/README.md b/modules/compute/ec2-instance-scheduler/README.md new file mode 100644 index 0000000..5e8b733 --- /dev/null +++ b/modules/compute/ec2-instance-scheduler/README.md @@ -0,0 +1,52 @@ + +## Requirements + +| Name | Version | +|------|---------| +| terraform | >= 1.3.0 | +| aws | >= 5.0 | + +## Providers + +| Name | Version | +|------|---------| +| archive | n/a | +| aws | >= 5.0 | +| random | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_role.eventscheduler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_iam_role_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_lambda_function.ec2-start-stop](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource | +| [aws_lambda_permission.lambda_permission](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource | +| [aws_scheduler_schedule.start](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/scheduler_schedule) | resource | +| [aws_scheduler_schedule.stop](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/scheduler_schedule) | resource | +| [random_id.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | +| [archive_file.lambda-package](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source | +| [aws_caller_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| description | A description of instances to be started/stopped on schedule | `string` | n/a | yes | +| instance-ids | Instances to be automatically started/stopped on schedule | `list(string)` | n/a | yes | +| instance-start-cron-expression | Cron expression for instance start schedule | `string` | n/a | yes | +| instance-stop-cron-expression | Cron expression for instance stop schedule | `string` | n/a | yes | + +## Outputs + +No outputs. + +--- +## Authorship +This module was developed by UPDATE_THIS. \ No newline at end of file diff --git a/modules/compute/ec2-instance-scheduler/main.tf b/modules/compute/ec2-instance-scheduler/main.tf new file mode 100644 index 0000000..4144a9d --- /dev/null +++ b/modules/compute/ec2-instance-scheduler/main.tf @@ -0,0 +1,171 @@ +data "aws_caller_identity" "this" {} + +resource "random_id" "this" { + byte_length = 4 +} + +resource "aws_iam_role" "eventscheduler" { + name = "EventSchedulerRole-${random_id.this.dec}" + assume_role_policy = jsonencode( + { + "Version" : "2012-10-17", + "Statement" : [ + { + "Effect" : "Allow", + "Principal" : { + "Service" : "scheduler.amazonaws.com" + }, + "Action" : "sts:AssumeRole" + } + ] + } + ) +} + +resource "aws_iam_role_policy_attachment" "this" { + policy_arn = "arn:aws:iam::aws:policy/AmazonEventBridgeSchedulerFullAccess" + role = aws_iam_role.eventscheduler.name +} + +resource "aws_iam_role" "this" { + name = "lambda-startstop-ec2-${var.description}" + + assume_role_policy = jsonencode( + { + "Version" : "2012-10-17", + "Statement" : [ + { + "Effect" : "Allow", + "Principal" : { + "Service" : "lambda.amazonaws.com" + }, + "Action" : "sts:AssumeRole" + } + ] + } + ) +} + +resource "aws_iam_role_policy" "this" { + policy = jsonencode( + { + "Version" : "2012-10-17", + "Statement" : [ + { + "Sid" : "AllowCreationOfCloudwatchLogGroup", + "Effect" : "Allow", + "Action" : "logs:CreateLogGroup", + "Resource" : "arn:aws:logs:ap-east-1:${data.aws_caller_identity.this.account_id}:*" + }, + { + "Sid" : "AllowWritingToCloudwatchLogGroup", + "Effect" : "Allow", + "Action" : [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Resource" : [ + "arn:aws:logs:ap-east-1:${data.aws_caller_identity.this.account_id}:log-group:/aws/lambda/*" + ] + }, + { + "Sid" : "AllowStartingStoppingOfEc2Instance", + "Action" : [ + "ec2:StopInstances", + "ec2:StartInstances", + "kms:CreateGrant" + ], + "Effect" : "Allow", + "Resource" : "*" + } + ] + } + ) + role = aws_iam_role.this.id + name = "LambdaExecutionPolicy" +} + +resource "aws_scheduler_schedule" "start" { + name = "scheduled-start-of-${var.description}-instances" + description = "Starts ${var.description} ec2 instance" + flexible_time_window { + mode = "OFF" + } + + schedule_expression = var.instance-start-cron-expression + + target { + arn = aws_lambda_function.ec2-start-stop.arn + role_arn = aws_iam_role.eventscheduler.arn + input = jsonencode({ "action" : "start" }) + } +} + +resource "aws_scheduler_schedule" "stop" { + name = "scheduled-stop-of-${var.description}-instances" + description = "Stops ${var.description} ec2 instance" + flexible_time_window { + mode = "OFF" + } + + schedule_expression = var.instance-stop-cron-expression + + target { + arn = aws_lambda_function.ec2-start-stop.arn + role_arn = aws_iam_role.eventscheduler.arn + input = jsonencode({ "action" : "stop" }) + } +} +# +#resource "aws_cloudwatch_event_rule" "start" { +# name = "scheduled-start-of-${var.description}-instances" +# description = "Starts automation ec2 instance" +# schedule_expression = var.instance-start-cron-expression +#} +# +#resource "aws_cloudwatch_event_rule" "stop" { +# name = "scheduled-stop-of-${var.description}-instances" +# description = "Stops automation ec2 instance" +# schedule_expression = var.instance-stop-cron-expression +#} +# +#resource "aws_cloudwatch_event_target" "start" { +# rule = aws_cloudwatch_event_rule.start.name +# arn = aws_lambda_function.ec2-start-stop.arn +# input = "{\"action\": \"start\"}" +#} +# +#resource "aws_cloudwatch_event_target" "stop" { +# rule = aws_cloudwatch_event_rule.stop.name +# arn = aws_lambda_function.ec2-start-stop.arn +# input = "{\"action\": \"stop\"}" +#} + +# Lambda function for instance scheduler +data "archive_file" "lambda-package" { + type = "zip" + source_file = "${path.module}/Ec2Scheduler.py" + output_path = "lambda-package.zip" +} + +resource "aws_lambda_function" "ec2-start-stop" { + function_name = "${var.description}-ec2-start-stop" + filename = data.archive_file.lambda-package.output_path + source_code_hash = data.archive_file.lambda-package.output_base64sha256 + handler = "Ec2Scheduler.lambda_handler" + runtime = "python3.12" + role = aws_iam_role.this.arn + timeout = 30 + environment { + variables = { + instances = jsonencode(var.instance-ids) + } + } +} + +resource "aws_lambda_permission" "lambda_permission" { + statement_id = "AllowCloudWatchToInvokeLambda" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.ec2-start-stop.function_name + principal = "events.amazonaws.com" +} \ No newline at end of file diff --git a/modules/compute/ec2-instance-scheduler/variables.tf b/modules/compute/ec2-instance-scheduler/variables.tf new file mode 100644 index 0000000..121110e --- /dev/null +++ b/modules/compute/ec2-instance-scheduler/variables.tf @@ -0,0 +1,19 @@ +variable instance-start-cron-expression { + type = string + description = "Cron expression for instance start schedule" +} + +variable instance-stop-cron-expression { + type = string + description = "Cron expression for instance stop schedule" +} + +variable instance-ids { + type = list(string) + description = "Instances to be automatically started/stopped on schedule" +} + +variable description { + type = string + description = "A description of instances to be started/stopped on schedule" +} \ No newline at end of file diff --git a/modules/compute/ec2-instance-scheduler/versions.tf b/modules/compute/ec2-instance-scheduler/versions.tf new file mode 100644 index 0000000..cd63239 --- /dev/null +++ b/modules/compute/ec2-instance-scheduler/versions.tf @@ -0,0 +1,13 @@ +terraform { + required_version = ">= 1.3.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.0" + } + + archive = { + source = "hashicorp/archive" + } + } +} \ No newline at end of file