One of the main benefits of using Serverless is automation due to it’s event-driven and pricing models. In this post, we will walk through how to use AWS Lambda to cleanup unused EBS volumes to reduce the infrastructure’s monthly bill.
The figure below illustrates the architecture we will setup in order to remove orphaned volumes:
To get started, create a main.go file with the following content:
func handler() error {
cfg, err := external.LoadDefaultAWSConfig()
if err != nil {
return err
}
volumes, err := listAvailableVolumes(cfg)
if err != nil {
return err
}
for _, volume := range volumes {
log.Println("Removing volume: ", volume)
if err != deleteMovie(cfg, volume) {
return err
}
}
return nil
}
func main() {
lambda.Start(handler)
}
The code above simply registers a new handler, which will be executed every time our Lambda function is invoked. The entrypoint uses the AWS Go SDK to list all available EBS volumes:
func listAvailableVolumes(cfg aws.Config) ([]string, error) {
volumes := make([]string, 0)
svc := ec2.New(cfg)
req := svc.DescribeVolumesRequest(&ec2.DescribeVolumesInput{
Filters: []ec2.Filter{
ec2.Filter{
Name: aws.String("status"),
Values: []string{"available"},
},
},
})
res, err := req.Send()
if err != nil {
return volumes, err
}
for _, volume := range res.Volumes {
volumes = append(volumes, *volume.VolumeId)
}
return volumes, nil
}
Then, it calls the DeleteVolume operation on each volume returned by the previous function:
func deleteMovie(cfg aws.Config, volumeId string) error {
svc := ec2.New(cfg)
req := svc.DeleteVolumeRequest(&ec2.DeleteVolumeInput{
VolumeId: &volumeId,
})
_, err := req.Send()
if err != nil {
return err
}
return nil
}
Once the Lambda handler is defined, create a new IAM role with the following policy to grant read and delete permissions to the function:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "1",
"Effect": "Allow",
"Action": [
"ec2:DeleteVolume",
"ec2:DescribeVolumes"
],
"Resource": "*"
}
]
}
Next, build a deployment package and create a new Lambda function based on .zip file with the IAM role created earlier:
#!/bin/bash
echo "Building binary"
GOOS=linux GOARCH=amd64 go build -o main main.go
echo "Preparing deployment package"
zip deployment.zip main
echo "Deploying"
aws lambda create-function --function-name RemoveUnusedEBS \
--runtime go1.x --handler main \
--role arn:aws:iam::ACCOUNT_ID:role/RemoveUnusedEBS \
--zip-file fileb://./deployment.zip
echo "Cleaning up "
rm deployment.zip main
In order to invoke our function automatically, we need to create a CloudWatch Rule that will be executed everyday at 8am to invoke the function as follows:
Head back to Lambda Console; your function should be configured as follows:
To test it out, create a few EBS volumes with the following AWS CLI:
aws ec2 create-volume --availability-zone us-east-1a --size 5 --volume-type standard
You can test your function by invoking it manually from the function configuration page or by using the AWS CLI invoke command:
As expected, the function deleted all EBS volumes that are not used by any EC2 instance.
EBS constitutes a significant portion of overall infrastructure cost. Therefore, periodically checking whether all the EBS volumes are being attached to an EC2 instance and deleting orphaned volumes can reduce your operational cost.
The full code can be found on my GitHub@mlabouardy.
Click here to learn about all the serverless trends you need to know!
About the author
Mohamed Labouardy is a Software Engineer/DevOps Engineer, 5x AWS Certified and Scrum Master Certified. He is interested in Serverless Architecture, Containers, Distributed Systems, Go & NLP. A Contributor to numerous Open Source projects such as DialogFlow, Jenkins, Docker, Nexus and Telegraf. He authored some Open Source projects related to DevOps as well. Check out his website here.