mirror of
https://github.com/yeslayla/aws-billing-alerts.git
synced 2025-01-13 12:33:34 +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'
|
||||
Transform: 'AWS::Serverless-2016-10-31'
|
||||
Description: 'Top level stack for Command Relay API resources'
|
||||
AWSTemplateFormatVersion: 2010-09-09
|
||||
Description: Stack that sends out billing alerts
|
||||
Parameters:
|
||||
cloudToolsBucket:
|
||||
Type: String
|
||||
Description: 'S3 Bucket containing Cloud Tools'
|
||||
#------------------------
|
||||
# Deployment Information
|
||||
#------------------------
|
||||
environment:
|
||||
Type: String
|
||||
Description: 'Environment'
|
||||
Description: Name of the environment to use in naming.
|
||||
Default: production
|
||||
release:
|
||||
Type: String
|
||||
Description: 'Release'
|
||||
Default: 'develop'
|
||||
notificationDiscordId:
|
||||
Description: Name of the release name of the stack version to use.
|
||||
Default: production
|
||||
AllowedValues: ['develop', 'production']
|
||||
ConstraintDescription: "Must be a possible release version."
|
||||
|
||||
|
||||
#---------------
|
||||
# Alert Methods
|
||||
#---------------
|
||||
discordWebhook:
|
||||
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:
|
||||
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:
|
||||
|
||||
|
||||
NotifyDiscord:
|
||||
Type: 'AWS::Serverless::Function'
|
||||
AlertSnsTopic:
|
||||
Type: AWS::SNS::Topic
|
||||
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:
|
||||
Handler: lambda_function.lambda_handler
|
||||
Runtime: python3.6
|
||||
# CodeUri:
|
||||
# Bucket: !Ref CloudToolsBucket
|
||||
# Key: !Ref TicketsArchive
|
||||
FunctionName: !Sub "BillingBot-${Environment}-FnNotifyDiscord"
|
||||
Description: 'Lambda receives API Gateway requests and generates tickets in FreshDesk.'
|
||||
Code:
|
||||
S3Bucket: "sumu-billingalerts"
|
||||
S3Key: !Sub "${release}/lambda/alertHandler.zip"
|
||||
FunctionName: !Sub "FnAlert-${environment}"
|
||||
Description: Lambda receives CloudWatch events and alerts channels.
|
||||
MemorySize: 128
|
||||
Timeout: 10
|
||||
#Role: !GetAtt CommandRelayIAM.Outputs.TicketsRoleArn
|
||||
Role: !GetAtt AlertExecutionerRole.Arn
|
||||
Environment:
|
||||
Variables:
|
||||
region: !Ref 'AWS::Region'
|
||||
discordId: !Ref NotificationDiscordId
|
||||
discordWebhook: !Ref discordWebhook
|
||||
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