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 payloadaction = 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.



Leave a comment