ประหยัดงบ เปิด-ปิด EC2 อัตโนมัติ ด้วย AWS EventBridge และ Lambda

AWS, EC2, Cost Saving, Automation, Serverless

Main project image

TL;DR

กฎเกณฑ์การคิดค่าใช้จ่ายของ AWS EC2

AWS จะไม่เรียกเก็บค่าใช้จ่ายเมื่อ EC2 instance ถูกหยุดใช้งาน ดังนั้นคุณสามารถหยุดทรัพยากร EC2 ได้เมื่อไม่มีการใช้งาน โดยทั่วไปแล้วทรัพยากรในการพัฒนาและการทดสอบไม่จำเป็นต้องทำงานตลอด 24 ชั่วโมงทุกวัน หากคุณปรับใช้การเริ่มและหยุดในช่วงเวลาทำงาน จะช่วยประหยัดค่าใช้จ่ายได้มากทีเดียว ลองมาดูตารางเปรียบเทียบต้นทุนในแต่ละสถานการณ์การใช้งาน EC2 instance t2.micro ที่มีการกำหนดการเริ่ม/หยุดทำงานโดยอัตโนมัติกับการทำงานอย่างต่อเนื่องตลอด 24 ชั่วโมง/7 วันกันดู

Cost ComponentInstance Running 24/7Automated Start/Stop Configuration
EC2 Hourly Rate$0.0116/hour$0.0116/hour
Total Running Hours per Month720 hours160 hours
Monthly Cost for EC2 Instance$8.352$1.856
EBS Volume Size8 GiB8 GiB
EBS Monthly Cost$0.80$0.80
Total Monthly Cost$9.152$2.656
Savings Amount-$6.496
Percentage Savings-71%

ตอนนี้คุณสามารถประหยัดค่าใช้จ่ายได้ประมาณ 70% สำหรับสภาพแวดล้อมการพัฒนาและการทดสอบของคุณ

เลือก AWS Services เพื่อใช้ส่งคำสั่งเปิดปิด EC2 อัตโนมัติ

ผมเลือกใช้บริการเหล่านี้จาก AWS เพื่อให้การทำงานเป็นไปอย่างมีประสิทธิภาพ

การนำบริการเหล่านี้มาใช้จะช่วยให้การจัดการ EC2 instances ของคุณเป็นเรื่องง่ายและลดค่าใช้จ่ายลงได้อย่างมีนัยสำคัญ

Workflow

มาเริ่มกันเลย

ติดตั้ง EC2 ด้วย AWS CloudFormation

ก่อนอื่นเราจะสร้าง EC2 instance โดยใช้ประเภท t2.micro เพื่อสาธิต ผมจะสร้าง instance ใน AWS Thai Region

AWSTemplateFormatVersion: '2010-09-09'
Description: EC2 instance with auto start/stop during business hours

Parameters:
  InstanceType:
    Type: String
    Default: t2.micro
    Description: EC2 instance type
  KeyName:
    Type: String
    Description: Name of an existing EC2 KeyPair to enable SSH access to the instance.

Resources:
  # EC2 Instance
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      ImageId: ami-0c55b159cbfafe1f0
      KeyName: !Ref KeyName
      IamInstanceProfile: !Ref EC2InstanceProfile

Outputs:
  InstanceId:
    Description: The Instance ID of the EC2 instance.
    Value: !Ref MyEC2Instance  

รัน CloudFormation CLI เพื่อติดตั้ง จากนั้นคุณจะเห็น EC2 ที่สร้างขึ้นใน AWS Management Console

ตั้งค่า Lambda ฟังก์ชันสำหรับเริ่ม/หยุด EC2

เราสามารถใช้ AWS-SDK กับ AWS Lambda เพื่อเริ่ม/หยุด EC2 จากคอนโซลได้ ผมตัดสินใจใช้ NodeJS ก่อนอื่นเราจะสร้างโฟลเดอร์ชื่อว่า lambda_functions และรัน npm init จากนั้นสร้างไฟล์ 2 ไฟล์สำหรับแต่ละฟังก์ชันเริ่ม/หยุด

📁lambda_functions
└── package.json
└── startInstance.js
└── stopInstance.js

เริ่มจาก package.json

{
  "name": "dev-ec2-start-stop-lambda-function",
  "version": "1.0.0",
  "type": "module",
  "dependencies": {
    "@aws-sdk/client-ec2": "^3.0.0"
  }
}

และ startInstance.js

import { EC2Client, StartInstancesCommand } from '@aws-sdk/client-ec2';
const ec2Client = new EC2Client({ region: 'ap-southeast-7' });

export const handler = async (event) => {
    const command = new StartInstancesCommand({ InstanceIds: [event.instance_id] });
    await ec2Client.send(command);
    return `Started your instance: ${event.instance_id}`;
};

และ stopInstance.js

import { EC2Client, StopInstancesCommand } from '@aws-sdk/client-ec2';
const ec2Client = new EC2Client({ region: 'ap-southeast-7' });

export const handler = async (event) => {
    const command = new StopInstancesCommand({ InstanceIds: [event.instance_id] });
    await ec2Client.send(command);
    return `Stopped your instance: ${event.instance_id}`;
};

จากนั้นบีบอัดโฟลเดอร์และอัปโหลดไปยัง S3 Bucket เราจะใช้มันใน CloudFormation Template ถัดไป

ตั้งค่า Event Bridge ด้วย CloudFormation

ตอนนี้เราจะปรับเปลี่ยน CloudFormation template ของเราเพื่อเพิ่ม EventBridge ที่จะเรียกใช้ฟังก์ชัน Lambda เพื่อเริ่ม/หยุดภายในช่วงเวลาทำงาน

AWSTemplateFormatVersion: '2010-09-09'
Description: EC2 instance with auto start/stop during business hours in Thailand timezone

Parameters:
  InstanceType:
    Type: String
    Default: t4g.nano
    Description: EC2 instance type
  KeyName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: Name of an existing EC2 KeyPair to enable SSH access to the instance.
  S3Bucket:
    Type: String
    Description: S3 bucket where Lambda code is stored
  S3Key:
    Type: String
    Description: S3 key for the Lambda code zip file

Resources:
  DevEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      ImageId: ami-08f3e969e66cd963a
      KeyName: !Ref KeyName

  DevSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Enable SSH access
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '22'
          ToPort: '22'
          CidrIp: 0.0.0.0/0

  LambdaStartFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: './lambda_functions/startInstance.handler'
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        S3Bucket: !Ref S3Bucket
        S3Key: !Ref S3Key
      Runtime: nodejs18.x
      Timeout: 30

  LambdaStopFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: './lambda_functions/stopInstance.handler'
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        S3Bucket: !Ref S3Bucket
        S3Key: !Ref S3Key
      Runtime: nodejs18.x
      Timeout: 30

  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: EC2StartStopPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ec2:StartInstances
                  - ec2:StopInstances
                Resource: '*'

  StartInstanceRule:
    Type: AWS::Events::Rule
    Properties:
      ScheduleExpression: 'cron(0 1 * * ? *)'  # 08:00 UTC (Thailand Time)
      Targets:
        - Arn: !GetAtt LambdaStartFunction.Arn
          Id: "StartEC2Instance"
          Input: !Sub '{"instance_id": "${DevEC2Instance}"}'

  StopInstanceRule:
    Type: AWS::Events::Rule
    Properties:
      ScheduleExpression: 'cron(0 11 * * ? *)'  # 18:00 UTC (Thailand Time)
      Targets:
        - Arn: !GetAtt LambdaStopFunction.Arn
          Id: "StopEC2Instance"
          Input: !Sub '{"instance_id": "${DevEC2Instance}"}'

  EventRulePermissionStart:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref LambdaStartFunction
      Principal: events.amazonaws.com
      SourceArn: !GetAtt StartInstanceRule.Arn

  EventRulePermissionStop:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref LambdaStopFunction
      Principal: events.amazonaws.com
      SourceArn: !GetAtt StopInstanceRule.Arn

Outputs:
  InstanceId:
    Description: "Instance ID of the newly created EC2 instance"
    Value: !Ref DevEC2Instance

สรุป

หลังจากที่เราใช้ CloudFormation Template แล้ว EC2 Instance ของเราจะเริ่ม/หยุดอัตโนมัติในช่วงเวลาทำงานที่เรากำหนดใน Amazon EventBridge ซึ่งสามารถช่วยประหยัดค่าใช้จ่ายได้ประมาณ 70% สำหรับทรัพยากรที่ไม่จำเป็นต้องทำงานตลอด 24 ชั่วโมง 7 วัน เช่นเครื่อง Dev/Staging

References