A great use case for AWS Lambda is running Cron Jobs. Instead of setting up a special one-off Cron server to run a simple task, we can just run the task on serverless nowadays. In this blog post, we’ll go over how to build a simple Cron Job with Jets, the Ruby Serverless Framework. As a useful practical example, we’ll create a job that backs up route53 records. This is quite handy when if you ever need it!

Jets New Job Mode

The jets new command has a few different modes: html, api, and job. We’ll use the handy job mode for this tutorial to create a minimal starter project designed to run jobs.

jets new backer --mode job
cd backer

We’ve successfully generated a very minimal Jets project. It is interesting to note that in job mode, there’s is no database configured and prewarming is disabled. Feel that these are good default settings for job mode. We are ready to now add some job code.

Initial Test Job Code

Let’s create a simple stub Job class.

app/jobs/backup_job.rb:

class BackupJob < ApplicationJob
  rate "1 minute" # fast rate for initial testing
  def route53_records
    puts "Backing up route53 records"
  end
end

To do a quick test let’s use jets console:

$ jets console
>> BackupJob.perform_now(:route53_records)
Backing up route53 records
=> nil

The local sanity check looks good. Let’s deploy it now.

$ jets deploy
Deploying to Lambda backer-dev environment...
...
Stack success status: UPDATE_COMPLETE
Time took for stack deployment: 1m 41s.
Prewarming application.

You can check out the deployed function in the Lambda console:

Since we’ve used a rate of 1 minute we can also see it running pretty quickly in the CloudWatch logs. It looks something like this:

Backup Route53 Code

Okay, now we can add the actual code that backs up the route53 records to s3.

We need to add the aws-sdk-route53 to the project’s Gemfile. The aws-sdk-s3 dependency is already included as part of Jets, so we don’t need to add that.

Gemfile:

gem "aws-sdk-route53"

For explanatory purposes, I’m going to focus on the code snippets that help to understand how the backup script works. The full source code is also on GitHub: jobs/backup_job.rb

class BackupJob < ApplicationJob
  iam_policy "s3"
  managed_iam_policy "AmazonRoute53ReadOnlyAccess"
  rate "1 day" # changed it so it runs once a day now
  def route53_records
    puts "Backing up route53 records"
    hosted_zones = route53.list_hosted_zones.hosted_zones
    hosted_zones.each do |zone|
      # ...
        save_to_s3("backups/route53/#{domain}-#{page}.json", resp)
    end
  end

  # ...
private
  def bucket_name
    ENV['S3_BUCKET']
  end
end

The code essentially loops through all the hosted zones and takes a backup of all the route53 records for each hosted zone, saving them in JSON format.

We also added an iam_policy declaration. For the sake of this tutorial, we’re using a simple s3 policy to give us write access to s3. There is also a managed_iam_policy declaration to get route53 read-only access. Note that these iam policies apply for the deployed function on AWS Lambda. Locally, you’re using the IAM permissions of your IAM user.

There is also an environment variable S3_BUCKET to tell the script where to save the backup records. Let’s create a s3 bucket and also add S3_BUCKET to our .env file.

aws s3 mb s3://jets-demo-backups # use your own unique bucket name
echo "S3_BUCKET=jets-demo-backups" > .env

The backups are saved to an easily identifiable structure:

s3://jets-demo-backups/backups/route53/example.com-1.json
s3://jets-demo-backups/backups/route53/example.com-2.json

The number at the end is a page number in case the number of routes in a zone exceeds a single page of results. The full source code is available here again: tongueroo/jets-backup-route53

It is useful to first to test things locally.

$ bundle
$ jets console
>> BackupJob.perform_now(:route53_records)
Backing up route53 records
Saving to s3://jets-demo-backups/backups/route53/demo.boltops.com-1.json
Saving to s3://jets-demo-backups/backups/route53/demo.rubyonjets.com-1.json
=> {:success=>true}
>>  

Once the function completes successfully, let’s check our s3 bucket:

$ aws s3 ls s3://jets-demo-backups/backups/route53/
2019-01-04 06:49:31        968 demo.boltops.com-1.json
2019-01-04 06:49:31       2135 demo.rubyonjets.com-1.json
$ 

We’re ready to deploy it again:

$ jets deploy

After it deploys, let’s manually trigger the function in the Lambda console this time. You can use anything for the test event payload because the backup function doesn’t care. Click on the Test button to run the function.

We should see something like this after running the function:

That’s it! We have successfully created a Ruby Serverless Cron Job with Jets! To boot, it backs up route53 records, which is pretty darn useful in case we ever need it. Usually, I enable versioning and a lifecycle policy on the s3 bucket also. This allows us to go back in history and get records from the past. This also makes the script simpler.

Hope you’ve enjoyed this article. If you find Ruby on Jets interesting, please give it ⭐️ on GitHub. I’d appreciate it. 👍

More info