Shut Down GCP Instance Groups at Nights

I’ve built so many Infrastructures that don’t need to be up running all night or weekends. Because it’s a development environment, or because it’s a private application where the users are like you and me: they sleep at nights.

So, I have to imagine a way to reduce the costs of my infrastructure during those non-working hours. One of the services I use in GCP is the Instance Groups. So let’s write a Cloud Function to stop and start this service at nights and weekends.

Workflow

First, let’s imagine the workflow to flollow:

  • I need a Cron Job which is triggered every morning to start the Instance Group and every night to stop the Instance Group
  • The Cron Job will plugged into a Cloud Function. In the morning, it sends a payload action = start, and in the evening, it sends a payload action = stop.
  • And I must identify the Instance Groups I want to stop and start. Because I don’t want to stop my production environment on the row. For that, I’ll use labels.

A quick investigation shows me that the Instance Groups doesn’t accept labels. Instead, I have to put them into the Instance Templates used by Instance Groups.

Cloud Function

I won’t talk about how to configure the Cron Job nor to plug it to the Cloud Function. Let’s go directly to the Cloud Function code.

First, I have to look for the GCP Instance Group, but Instance Group doesn’t accept label. Instead, I have to use the labels in the Instance Templates. Then, look for the associated Instance Group.

def find_instance_groups_name(project):
    instance_groups_client = compute_v1.InstanceGroupManagersClient()
    request = compute_v1.AggregatedListInstanceGroupManagersRequest()
    request.project = project

    agg_list = instance_groups_client.aggregated_list(request=request)

    instance_templates = {}
    for region, response in agg_list:
        if response.instance_group_managers:
            for instance in response.instance_group_managers:
                instance_templates[instance.instance_template.split("/")[-1]] = {
                    "group": instance.name,
                    "region": region.split("/")[-1],
                }

    instance_template_client = compute_v1.InstanceTemplatesClient()
    request = compute_v1.AggregatedListInstanceTemplatesRequest()
    request.project = project

    agg_list = instance_template_client.aggregated_list(request=request)

    instance_groups = []
    for region, response in agg_list:
        if response.instance_templates:
            for instance in response.instance_templates:
                if instance.name in instance_templates.keys():
                    if "stop-and-start" in instance.properties.labels and instance.properties.labels["stop-and-start"] == "yes":
                        instance_groups.append(
                            (instance_templates[instance.name]["group"], instance_templates[instance.name]["region"])
                        )

    return instance_groups

Once, I’ve found the impacted Instance Groups, let’s update them reducing their size.

def start_or_stop_instance_groups(project, instance_groups_name, start):
    instance_group_manager_client = compute_v1.InstanceGroupManagersClient()
    for instance_group_name, region in instance_groups_name:

        if start:
            print(f"Starting Instance Group {instance_group_name} {region}...")
        else:
            print(f"Stopping Instance Group {instance_group_name} {region}...")

        response = instance_group_manager_client.resize(
            project=project, instance_group_manager=instance_group_name, zone=region, size=1 if start else 0
        )
        print(response)

I’ve decided to use all this into a Cloud Function. Then, connect the Cloud Function to a Cron Job with Eventarc. To use the Cloud Function from Eventarc (through Pub/Sub), I need to include the functions_framework library.

@functions_framework.cloud_event
def stop_and_start(cloud_event):
    encoded_data = cloud_event.data["message"]["data"]
    decoded_data = base64.b64decode(encoded_data).decode("utf-8")
    message = json.loads(decoded_data)

    if message["action"] == "stop":
        start = False
    elif message["action"] == "start":
        start = True
    else:
        print("No action specified")
        return

    project = os.getenv("PROJECT_ID")

    instance_groups_name = find_instance_groups_name(project)
    start_or_stop_instance_groups(project, instance_groups_name, start)

Conclusion

In many of my projects on GCP, I use Instance Groups. To reduce the costs on the development or staging environments, this script is a good fit. It won’t redirect all the saved money to my paycheck, but it’s a start.


Never Miss Another Tech Innovation

Concrete insights and actionable resources delivered straight to your inbox to boost your developer career.

My New ebook, Best Practices To Create A Backend With Spring Boot 3, is available now.

Best practices to create a backend with Spring Boot 3

One response to “Shut Down GCP Instance Groups at Nights”

  1. […] what I want. Since my backend servers are already tucked in for the night (explained in another article), keeping the database running is just burning money for no reason. So, let’s put it to sleep […]

    Like

Leave a comment

Discover more from The Dev World - Sergio Lema

Subscribe now to keep reading and get access to the full archive.

Continue reading