Skip to content

Instantly share code, notes, and snippets.

@ValterAndrei
Last active April 23, 2024 01:03
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ValterAndrei/9f3ce4a0ccce2840b7c451302f26127e to your computer and use it in GitHub Desktop.
Save ValterAndrei/9f3ce4a0ccce2840b7c451302f26127e to your computer and use it in GitHub Desktop.
Automating Instance Start/Stop - AWS, Lambda, EC2, RDS and Ruby

Automating Instance Start/Stop with AWS Lambda

  • Ruby 3.3
  • EC2 Instances
  • RDS DB Instance

Create Policy

IAM > Policies > Create policy > JSON > [copy + paste] > Next > Policy name: start-stop-instance-policy

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:StartInstances",
        "ec2:StopInstances",
        "rds:StartDBInstance",
        "rds:StopDBInstance"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "*"
    }
  ]
}

Create Role

IAM > Roles > Create role > Trusted entity type: AWS service > Use case: Lambda > Next > Role name: start-stop-instance-role

default (not change)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "sts:AssumeRole"
      ],
      "Principal": {
        "Service": [
          "lambda.amazonaws.com"
        ]
      }
    }
  ]
}

Attach Policy to Role

IAM > Roles > start-stop-instance-role > Add permissions > Attach policies > start-stop-instance-policy


Create Lambda

Lambda > Create function > Function name: start_instances

Environment variables
BASTION_ID = "i-0a1b2c3d4e5f6g7h8",
DEV_APP_ID = "i-9j8h7g6f5e4d3c2b1a0",
RDS_INSTANCE_ID = "dev-instance",
REGION = "us-east-1",
SLACK_WEBHOOK_URL = "https://hooks.slack.com/services/..."
# ruby version: 3.3

require 'aws-sdk-ec2'
require 'aws-sdk-rds'


def lambda_handler(*)
  # Environment variables
  bastion_id        = ENV.fetch('BASTION_ID')
  dev_app_id        = ENV.fetch('DEV_APP_ID')
  rds_instance_id   = ENV.fetch('RDS_INSTANCE_ID')
  region            = ENV.fetch('REGION')
  slack_webhook_url = ENV.fetch('SLACK_WEBHOOK_URL')

  # Create AWS Clients
  ec2_client = Aws::EC2::Client.new(region:)
  rds_client = Aws::RDS::Client.new(region:)

  # Start instances
  ec2_client.start_instances(instance_ids: [bastion_id, dev_app_id])
  rds_client.start_db_instance(db_instance_identifier: rds_instance_id)

  # Create slack message
  message = {
    blocks: [
      {
        type: 'header',
        text: {
          type: 'plain_text',
          text: ':coffee: Instances successfully connected'
        }
      },
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: "*Detalhes:*\n" \
                "• *Bastion ID:* `#{bastion_id}`\n" \
                "• *Dev App ID:* `#{dev_app_id}`\n" \
                "• *RDS Instance ID:* `#{rds_instance_id}`"
        }
      },
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: 'The system will be available in 10 minutes.'
        },
        accessory: {
          type: 'button',
          text: {
            type: 'plain_text',
            text: ':computer: Google'
          },
          url: 'https://google.com',
          style: 'primary'
        }
      }
    ]
  }

  # Send slack message
  uri = URI(slack_webhook_url)
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true

  request = Net::HTTP::Post.new(uri.request_uri)
  request.content_type = 'application/json'
  request.body = message.to_json
  http.request(request)

  # Return success message from Lambda
  {
    status: 200,
    message: 'Instances successfully connected.'
  }
end

Create EventBridge

Lambda > Functions > start_instances > Add trigger > EventBridge > Create a new rule > Rule name: start_instances_event

# Start DEV instance at 05:30h, from Monday to Friday (GMT-3).

cron(30 8 ? * MON-FRI *)

Create Lambda

Lambda > Create function > Function name: stop_instances

Environment variables
BASTION_ID = "i-0a1b2c3d4e5f6g7h8",
DEV_APP_ID = "i-9j8h7g6f5e4d3c2b1a0",
RDS_INSTANCE_ID = "dev-instance",
REGION = "us-east-1",
SLACK_WEBHOOK_URL = "https://hooks.slack.com/services/..."
# ruby version: 3.3

require 'aws-sdk-ec2'
require 'aws-sdk-rds'


def lambda_handler(*)
  # Environment variables
  bastion_id        = ENV.fetch('BASTION_ID')
  dev_app_id        = ENV.fetch('DEV_APP_ID')
  rds_instance_id   = ENV.fetch('RDS_INSTANCE_ID')
  region            = ENV.fetch('REGION')
  slack_webhook_url = ENV.fetch('SLACK_WEBHOOK_URL')

  # Create AWS Clients
  ec2_client = Aws::EC2::Client.new(region:)
  rds_client = Aws::RDS::Client.new(region:)

  # Stop instances
  ec2_client.stop_instances(instance_ids: [bastion_id, dev_app_id])
  rds_client.stop_db_instance(db_instance_identifier: rds_instance_id)

  # Create slack message
  message = {
    blocks: [
      {
        type: 'header',
        text: {
          type: 'plain_text',
          text: ':crescent_moon: Instances successfully disconnected'
        }
      },
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: "*Detalhes:*\n" \
                "• *Bastion ID:* `#{bastion_id}`\n" \
                "• *Dev App ID:* `#{dev_app_id}`\n" \
                "• *RDS Instance ID:* `#{rds_instance_id}`"
        }
      }
    ]
  }

  # Send slack message
  uri = URI(slack_webhook_url)
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true

  request = Net::HTTP::Post.new(uri.request_uri)
  request.content_type = 'application/json'
  request.body = message.to_json
  http.request(request)

  # Return success message from Lambda
  {
    status: 200,
    message: 'Instances successfully disconnected.'
  }
end

Create EventBridge

Lambda > Functions > stop_instances > Add trigger > EventBridge > Create a new rule > Rule name: stop_instances_event

# Stop DEV instance at 20:00h, from Monday to Friday (GMT-3).

cron(00 23 ? * MON-FRI *)

References:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment