mirror of
https://github.com/yeslayla/aws-billing-alerts.git
synced 2025-01-14 13:03:33 +01:00
Basic stack and a decent readme
This commit is contained in:
parent
1cd6398be9
commit
2561d7569b
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
config
|
||||||
|
*.zip
|
40
ReadMe.md
40
ReadMe.md
@ -1 +1,39 @@
|
|||||||
# TBD
|
# Billing Alerts Stack
|
||||||
|
|
||||||
|
![Billing Alerts Diagram](https://static.cloudsumu.com/billingalerts/diagram.png)
|
||||||
|
|
||||||
|
Uses SNS, Lambda, and CloudWatch to send billing alerts via:
|
||||||
|
- Email
|
||||||
|
- Text
|
||||||
|
- Discord
|
||||||
|
- Slack
|
||||||
|
|
||||||
|
Easy to launch and configure!
|
||||||
|
|
||||||
|
## Launching Stack
|
||||||
|
|
||||||
|
### Option 1: Launch via Link
|
||||||
|
|
||||||
|
Make sure you are logged into the AWS Console and have permissions then click:
|
||||||
|
|
||||||
|
[![Launch in AWS Console](https://static.cloudsumu.com/billingalerts/launch_button.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/create/template?stackName=BillingAlerts&templateURL=https://sumu-billingalerts.s3.amazonaws.com/production/cloudformation/top.yaml)
|
||||||
|
|
||||||
|
Fill out the parameters and launch!
|
||||||
|
|
||||||
|
### Option 2: Manually input template into AWS Console
|
||||||
|
|
||||||
|
1. Download top template or copy URL for later.
|
||||||
|
|
||||||
|
*Development S3 URL:* [https://sumu-billingalerts.s3.amazonaws.com/develop/cloudformation/top.yaml](https://sumu-billingalerts.s3.amazonaws.com/production/cloudformation/top.yaml)
|
||||||
|
|
||||||
|
*Production S3 URL:* [https://sumu-billingalerts.s3.amazonaws.com/production/cloudformation/top.yaml](https://sumu-billingalerts.s3.amazonaws.com/production/cloudformation/top.yaml)
|
||||||
|
|
||||||
|
2. Go to [CloudFormation on the AWS Console](https://console.aws.amazon.com/cloudformation/home)
|
||||||
|
3. Click the "Create stack" button
|
||||||
|
4. Under "Prepare template" make sure that "Template is ready" is selected, it should be the default.
|
||||||
|
|
||||||
|
Then under "Template Source" either paste in the template URL or upload the downloaded template.
|
||||||
|
|
||||||
|
Then click "Next"
|
||||||
|
|
||||||
|
5. Fill out stack parameters then launch!
|
@ -1,44 +1,262 @@
|
|||||||
AWSTemplateFormatVersion: '2010-09-09'
|
AWSTemplateFormatVersion: 2010-09-09
|
||||||
Transform: 'AWS::Serverless-2016-10-31'
|
Description: Stack that sends out billing alerts
|
||||||
Description: 'Top level stack for Command Relay API resources'
|
|
||||||
Parameters:
|
Parameters:
|
||||||
cloudToolsBucket:
|
#------------------------
|
||||||
Type: String
|
# Deployment Information
|
||||||
Description: 'S3 Bucket containing Cloud Tools'
|
#------------------------
|
||||||
environment:
|
environment:
|
||||||
Type: String
|
Type: String
|
||||||
Description: 'Environment'
|
Description: Name of the environment to use in naming.
|
||||||
|
Default: production
|
||||||
release:
|
release:
|
||||||
Type: String
|
Type: String
|
||||||
Description: 'Release'
|
Description: Name of the release name of the stack version to use.
|
||||||
Default: 'develop'
|
Default: production
|
||||||
notificationDiscordId:
|
AllowedValues: ['develop', 'production']
|
||||||
|
ConstraintDescription: "Must be a possible release version."
|
||||||
|
|
||||||
|
|
||||||
|
#---------------
|
||||||
|
# Alert Methods
|
||||||
|
#---------------
|
||||||
|
discordWebhook:
|
||||||
Type: String
|
Type: String
|
||||||
Description: 'Username of discord user to notify'
|
Description: A webhook for notifications for Discord. (Leave empty for none)
|
||||||
|
Default: ""
|
||||||
|
slackWebhook:
|
||||||
|
Type: String
|
||||||
|
Description: An incoming webhook for notifications for Slack. (Leave empty for none)
|
||||||
|
Default: ""
|
||||||
notificationEmail:
|
notificationEmail:
|
||||||
Type: String
|
Type: String
|
||||||
Description: 'Email of to send SNS notifications to'
|
Description: An email address to subscribe to the alerting topic. (Leave empty for none)
|
||||||
|
Default: ""
|
||||||
|
notificationPhone:
|
||||||
|
Type: String
|
||||||
|
Description: A mobile number to subscribe to the alerting topic. Formatted as '+1XXXXXXXXXX' (Leave empty for none)
|
||||||
|
Default: ""
|
||||||
|
|
||||||
|
|
||||||
|
#-------------------
|
||||||
|
# Alarm Information
|
||||||
|
#-------------------
|
||||||
|
lowPriorityAlert:
|
||||||
|
Type: Number
|
||||||
|
Description: Estimated monthly cost in USD to send a low priority alert.
|
||||||
|
Default: 10
|
||||||
|
MinValue: 1
|
||||||
|
mediumPriorityAlert:
|
||||||
|
Type: Number
|
||||||
|
Description: Estimated monthly cost in USD to send a normal alert.
|
||||||
|
Default: 15
|
||||||
|
MinValue: 2
|
||||||
|
highPriorityAlert:
|
||||||
|
Type: Number
|
||||||
|
Description: Estimated monthly cost in USD to send a low priority alert.
|
||||||
|
Default: 20
|
||||||
|
MinValue: 3
|
||||||
|
|
||||||
|
updateInterval:
|
||||||
|
Type: Number
|
||||||
|
Description: Time in seconds alarms are updated.
|
||||||
|
Default: 21600 # 6 hours
|
||||||
|
ConstraintDescription: Minimum alarm time is a minute.
|
||||||
|
MinValue: 60
|
||||||
|
|
||||||
|
Metadata:
|
||||||
|
AWS::CloudFormation::Interface:
|
||||||
|
ParameterGroups:
|
||||||
|
- Label:
|
||||||
|
default: "Deployment Information"
|
||||||
|
Parameters:
|
||||||
|
- environment
|
||||||
|
- release
|
||||||
|
- Label:
|
||||||
|
default: "Alert Methods"
|
||||||
|
Parameters:
|
||||||
|
- notificationEmail
|
||||||
|
- notificationPhone
|
||||||
|
- discordWebhook
|
||||||
|
- slackWebhook
|
||||||
|
- Label:
|
||||||
|
default: "Alarm Settings"
|
||||||
|
Parameters:
|
||||||
|
- lowPriorityAlert
|
||||||
|
- mediumPriorityAlert
|
||||||
|
- highPriorityAlert
|
||||||
|
ParameterLabels:
|
||||||
|
environment:
|
||||||
|
default: "Environment"
|
||||||
|
release:
|
||||||
|
default: "Release"
|
||||||
|
notificationEmail:
|
||||||
|
default: "Email"
|
||||||
|
notificationPhone:
|
||||||
|
default: "SMS Number"
|
||||||
|
discordWebhook:
|
||||||
|
default: "Discord Webhook"
|
||||||
|
slackWebhook:
|
||||||
|
default: "Slack Webhook"
|
||||||
|
lowPriorityAlert:
|
||||||
|
Default: "Cost for Low Priority Alert"
|
||||||
|
mediumPriorityAlert:
|
||||||
|
Default: "Cost for Medium Priority Alert"
|
||||||
|
highPriorityAlert:
|
||||||
|
Default: "Cost for High Priority Alert"
|
||||||
|
|
||||||
|
Conditions:
|
||||||
|
SubscribeEmail: !Not [!Equals [!Ref "notificationEmail", ""]]
|
||||||
|
SubscribePhone: !Not [!Equals [!Ref "notificationPhone", ""]]
|
||||||
|
|
||||||
Resources:
|
Resources:
|
||||||
|
|
||||||
|
AlertSnsTopic:
|
||||||
NotifyDiscord:
|
Type: AWS::SNS::Topic
|
||||||
Type: 'AWS::Serverless::Function'
|
Properties:
|
||||||
|
TopicName: !Sub "BillingAlerts-${environment}"
|
||||||
|
|
||||||
|
EmailAlertSubscription:
|
||||||
|
Type: AWS::SNS::Subscription
|
||||||
|
Condition: SubscribeEmail
|
||||||
|
Properties:
|
||||||
|
Protocol: email
|
||||||
|
Endpoint: !Ref notificationEmail
|
||||||
|
TopicArn: !Ref AlertSnsTopic
|
||||||
|
|
||||||
|
PhoneAlertSubscription:
|
||||||
|
Type: AWS::SNS::Subscription
|
||||||
|
Condition: SubscribePhone
|
||||||
|
Properties:
|
||||||
|
Protocol: sms
|
||||||
|
Endpoint: !Ref notificationPhone
|
||||||
|
TopicArn: !Ref AlertSnsTopic
|
||||||
|
|
||||||
|
AlertExecutionerRole:
|
||||||
|
Type: AWS::IAM::Role
|
||||||
|
Properties:
|
||||||
|
AssumeRolePolicyDocument:
|
||||||
|
Version: 2012-10-17
|
||||||
|
Statement:
|
||||||
|
- Effect: Allow
|
||||||
|
Principal:
|
||||||
|
Service:
|
||||||
|
- lambda.amazonaws.com
|
||||||
|
Action:
|
||||||
|
- sts:AssumeRole
|
||||||
|
Policies:
|
||||||
|
- PolicyName: LambdaLogging
|
||||||
|
PolicyDocument:
|
||||||
|
Version: 2012-10-17
|
||||||
|
Statement:
|
||||||
|
- Effect: Allow
|
||||||
|
Action:
|
||||||
|
- logs:CreateLogGroup
|
||||||
|
- logs:CreateLogStream
|
||||||
|
- logs:PutLogEvents
|
||||||
|
Resource: "*"
|
||||||
|
- PolicyName: AlertSNS
|
||||||
|
PolicyDocument:
|
||||||
|
Version: 2012-10-17
|
||||||
|
Statement:
|
||||||
|
- Effect: Allow
|
||||||
|
Action:
|
||||||
|
- sns:Publish
|
||||||
|
Resource: !Ref AlertSnsTopic
|
||||||
|
|
||||||
|
|
||||||
|
AlertHandler:
|
||||||
|
Type: AWS::Lambda::Function
|
||||||
Properties:
|
Properties:
|
||||||
Handler: lambda_function.lambda_handler
|
Handler: lambda_function.lambda_handler
|
||||||
Runtime: python3.6
|
Runtime: python3.6
|
||||||
# CodeUri:
|
Code:
|
||||||
# Bucket: !Ref CloudToolsBucket
|
S3Bucket: "sumu-billingalerts"
|
||||||
# Key: !Ref TicketsArchive
|
S3Key: !Sub "${release}/lambda/alertHandler.zip"
|
||||||
FunctionName: !Sub "BillingBot-${Environment}-FnNotifyDiscord"
|
FunctionName: !Sub "FnAlert-${environment}"
|
||||||
Description: 'Lambda receives API Gateway requests and generates tickets in FreshDesk.'
|
Description: Lambda receives CloudWatch events and alerts channels.
|
||||||
MemorySize: 128
|
MemorySize: 128
|
||||||
Timeout: 10
|
Timeout: 10
|
||||||
#Role: !GetAtt CommandRelayIAM.Outputs.TicketsRoleArn
|
Role: !GetAtt AlertExecutionerRole.Arn
|
||||||
Environment:
|
Environment:
|
||||||
Variables:
|
Variables:
|
||||||
region: !Ref 'AWS::Region'
|
discordWebhook: !Ref discordWebhook
|
||||||
discordId: !Ref NotificationDiscordId
|
slackWebhook: !Ref slackWebhook
|
||||||
|
snsTopic: !Ref AlertSnsTopic
|
||||||
|
|
||||||
#CloudWatch CRON
|
|
||||||
#CloudWatch Billing Limit (3 Tiers (Low Priority, Medium Priority, High Priority))
|
CloudWatchReciever:
|
||||||
|
Type: AWS::SNS::Topic
|
||||||
|
Properties:
|
||||||
|
TopicName: !Sub "Billing-CloudWatch-${environment}"
|
||||||
|
|
||||||
|
CloudWatchAlertSubscription:
|
||||||
|
Type: AWS::SNS::Subscription
|
||||||
|
Properties:
|
||||||
|
Protocol: lambda
|
||||||
|
Endpoint: !GetAtt AlertHandler.Arn
|
||||||
|
TopicArn: !Ref CloudWatchReciever
|
||||||
|
|
||||||
|
SnsLambdaPermission:
|
||||||
|
Type: AWS::Lambda::Permission
|
||||||
|
Properties:
|
||||||
|
Action: lambda:InvokeFunction
|
||||||
|
Principal: sns.amazonaws.com
|
||||||
|
SourceArn: !Ref CloudWatchReciever
|
||||||
|
FunctionName: !GetAtt AlertHandler.Arn
|
||||||
|
|
||||||
|
LowPriorityAlarm:
|
||||||
|
Type: AWS::CloudWatch::Alarm
|
||||||
|
Properties:
|
||||||
|
ActionsEnabled: true
|
||||||
|
AlarmActions:
|
||||||
|
- !Ref CloudWatchReciever
|
||||||
|
ComparisonOperator: GreaterThanThreshold
|
||||||
|
Dimensions:
|
||||||
|
- Name: Currency
|
||||||
|
Value: USD
|
||||||
|
EvaluationPeriods: 1
|
||||||
|
MetricName: EstimatedCharges
|
||||||
|
Namespace: "AWS/Billing"
|
||||||
|
OKActions:
|
||||||
|
- !Ref CloudWatchReciever
|
||||||
|
Period: !Ref updateInterval
|
||||||
|
Statistic: Maximum
|
||||||
|
Threshold: !Ref lowPriorityAlert
|
||||||
|
|
||||||
|
MediumPriorityAlarm:
|
||||||
|
Type: AWS::CloudWatch::Alarm
|
||||||
|
Properties:
|
||||||
|
ActionsEnabled: true
|
||||||
|
AlarmActions:
|
||||||
|
- !Ref CloudWatchReciever
|
||||||
|
ComparisonOperator: GreaterThanThreshold
|
||||||
|
Dimensions:
|
||||||
|
- Name: Currency
|
||||||
|
Value: USD
|
||||||
|
EvaluationPeriods: 1
|
||||||
|
MetricName: EstimatedCharges
|
||||||
|
Namespace: "AWS/Billing"
|
||||||
|
OKActions:
|
||||||
|
- !Ref CloudWatchReciever
|
||||||
|
Period: !Ref updateInterval
|
||||||
|
Statistic: Maximum
|
||||||
|
Threshold: !Ref mediumPriorityAlert
|
||||||
|
|
||||||
|
HighPriorityAlarm:
|
||||||
|
Type: AWS::CloudWatch::Alarm
|
||||||
|
Properties:
|
||||||
|
ActionsEnabled: true
|
||||||
|
AlarmActions:
|
||||||
|
- !Ref CloudWatchReciever
|
||||||
|
ComparisonOperator: GreaterThanThreshold
|
||||||
|
Dimensions:
|
||||||
|
- Name: Currency
|
||||||
|
Value: USD
|
||||||
|
EvaluationPeriods: 1
|
||||||
|
MetricName: EstimatedCharges
|
||||||
|
Namespace: "AWS/Billing"
|
||||||
|
OKActions:
|
||||||
|
- !Ref CloudWatchReciever
|
||||||
|
Period: !Ref updateInterval
|
||||||
|
Statistic: Maximum
|
||||||
|
Threshold: !Ref highPriorityAlert
|
||||||
|
70
lambda/alertHandler/lambda_function.py
Normal file
70
lambda/alertHandler/lambda_function.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import json, requests, os, boto3
|
||||||
|
|
||||||
|
|
||||||
|
def lambda_handler(event, context):
|
||||||
|
if 'Records' in event:
|
||||||
|
for record in event['Records']:
|
||||||
|
if record['EventSource'] == "aws:sns":
|
||||||
|
if "Message" in record['Sns']:
|
||||||
|
message = json.loads(record['Sns']['Message'])
|
||||||
|
if "NewStateValue" in message:
|
||||||
|
print(message)
|
||||||
|
is_important = "HighPriority" in message["AlarmName"] and (message["NewStateValue"] != "OK")
|
||||||
|
send_message(message["NewStateReason"], "AWS Billing Alert", is_important)
|
||||||
|
else:
|
||||||
|
send_message(message, record['Sns']['Subject'])
|
||||||
|
|
||||||
|
#-------------------
|
||||||
|
# Messaging Methods
|
||||||
|
#-------------------
|
||||||
|
def send_discord_message(message, name = None, high_priority = False):
|
||||||
|
if high_priority:
|
||||||
|
send_discord_message("@everyone \n**HIGH PRIORITY:** " + message, name)
|
||||||
|
return
|
||||||
|
if "discordWebhook" in os.environ and os.environ["discordWebhook"] != "":
|
||||||
|
r = requests.post(os.environ['discordWebhook'],
|
||||||
|
data={'content': message, 'username': name})
|
||||||
|
print("Sent to Discord: " + message)
|
||||||
|
else:
|
||||||
|
print("Discord webhook not setup. Skipping...")
|
||||||
|
|
||||||
|
def send_sns_message(message, name = "Billing Alert", high_priority = False):
|
||||||
|
if high_priority:
|
||||||
|
send_sns_message(message, "HIGH PRIORITY: " + name)
|
||||||
|
return
|
||||||
|
if 'snsTopic' in os.environ and os.environ["snsTopic"] != "":
|
||||||
|
boto3.client('sns').publish(
|
||||||
|
TargetArn=os.environ['snsTopic'],
|
||||||
|
Message=json.dumps({'default':message,
|
||||||
|
'sms' : name +"\n---\n" + message}),
|
||||||
|
Subject=name,
|
||||||
|
MessageStructure='json'
|
||||||
|
)
|
||||||
|
print("Published to SNS: " + message)
|
||||||
|
else:
|
||||||
|
print("SNS not setup. Skipping...")
|
||||||
|
|
||||||
|
def send_slack_message(message, name = None, high_priority = False):
|
||||||
|
if high_priority:
|
||||||
|
send_slack_message("<!channel> \n*HIGH PRIORITY:* " + message, name)
|
||||||
|
return
|
||||||
|
if "slackWebhook" in os.environ and os.environ["slackWebhook"] != "":
|
||||||
|
r = requests.post(os.environ['slackWebhook'],
|
||||||
|
data=json.dumps({'text': message, 'username' : name}),
|
||||||
|
headers={'Content-Type' : 'application/json'})
|
||||||
|
print("Sent to Slack: " + message)
|
||||||
|
else:
|
||||||
|
print("Slack webhook not setup. Skipping...")
|
||||||
|
|
||||||
|
#Function sends message to every messaging method
|
||||||
|
def send_message(message, name = None, high_priority = False):
|
||||||
|
send_discord_message(message, name, high_priority)
|
||||||
|
send_slack_message(message, name, high_priority)
|
||||||
|
send_sns_message(message, name, high_priority)
|
||||||
|
|
||||||
|
|
||||||
|
# os.environ['snsTopic'] = "arn:aws:sns:us-east-1:959431236163:BillingDev"
|
||||||
|
# os.environ['slackWebhook'] = "https://hooks.slack.com/services/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"
|
||||||
|
# os.environ['discordWebhook'] = "https://discordapp.com/api/webhooks/597128213373648900/Ys4iB0MCeJiwne_KSJIE_Q8geZluKsFYzQQ02GJIMp2fGka1-tx47ZmH0aTxbGVz6fJ6"
|
||||||
|
|
||||||
|
# send_message("Test", "AWS Billing", True)
|
2
lambda/alertHandler/requirements.txt
Normal file
2
lambda/alertHandler/requirements.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
boto3>=1.9.57
|
||||||
|
requests>=2.21.0
|
Loading…
Reference in New Issue
Block a user