Creating a GitHub Actions Workflow to Start/Stop a CloudSQL Instance Manually and Automatically Stop at Night

Introduction

In environments such as staging or testing, where services only need to be active when necessary, keeping a CloudSQL instance running constantly results in unnecessary costs. Since we don’t have unlimited funds, reducing costs wherever possible is desirable. However, manually going into the console to start the instance each time it’s needed, and stopping it after use, is a hassle.

To solve this, I created a workflow that allows starting and stopping the instance manually from GitHub, and automatically stops it every night to prevent forgetting.

Workflow Overview

Here’s the workflow I created:

name: Manage Staging Cloud SQL

on:
  workflow_dispatch:
    inputs:
      action:
        description: "Action"
        required: true
        default: "start"
        type: choice
        options:
          - start
          - stop
  schedule:
    - cron: "0 12 * * *" # JST 21:00 (UTC+9)

jobs:
  manage-cloudsql:
    permissions:
      contents: "read"
      id-token: "write"
    runs-on: ubuntu-latest
    steps:
      - name: Google Auth
        uses: google-github-actions/auth@v2
        with:
          token_format: access_token
          workload_identity_provider: "${{ secrets.WORKLOAD_IDENTITY_PROVIDER }}"
          service_account: "${{ secrets.SERVICE_ACCOUNT }}"
      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@v2
      - name: Manage CloudSQL Instance
        run: |
          INSTANCE_NAME=${{ secrets.CLOUDSQL_INSTANCE_NAME_STAGING }}
          STATUS=$(gcloud sql instances describe "$INSTANCE_NAME" --format="value(state)")

          echo "Requested action: $ACTION"
          echo "Current status of the instance: $STATUS"

          if [ "$ACTION" = "start" ] && [ "$STATUS" = "RUNNABLE" ]; then
            echo "Instance is already running."
            exit 0
          elif [ "$ACTION" = "stop" ] && [ "$STATUS" != "RUNNABLE" ]; then
            echo "Instance is already stopped."
            exit 0
          fi

          if [ "$ACTION" = "start" ]; then
            gcloud sql instances patch "$INSTANCE_NAME" \
              --activation-policy=ALWAYS \
              --async
          elif [ "$ACTION" = "stop" ]; then
            gcloud sql instances patch "$INSTANCE_NAME" \
              --activation-policy=NEVER \
              --async
          else
            echo "Invalid action: $ACTION"
            exit 1
          fi
        env:
          ACTION: ${{ github.event.inputs.action || 'stop' }}

Workflow Details

Trigger

The workflow is triggered manually using workflow_dispatch, allowing the user to choose between starting and stopping the instance using a choice input.

workflow_dispatch

Additionally, it is scheduled to run automatically every night at 9 PM JST via cron. In scheduled runs, inputs.action from workflow_dispatch isn’t available, so the workflow defaults to stopping the instance.

Authentication and gcloud Setup

To start and stop the CloudSQL instance, the workflow uses the gcloud command. For authentication, it uses google-github-actions/auth, and for setting up the gcloud CLI, it uses google-github-actions/setup-gcloud. The service account used for execution must have the roles/cloudsql.admin role.

I won’t go into detail about Workload Identity Federation here, but Google’s official documentation is well-written and worth checking out:

Starting and Stopping CloudSQL Instances

Before sending the command, the current status of the instance is checked. If the instance is already in the desired state, the operation is skipped. This is because sending a stop command to an already stopped Cloud SQL instance results in an HTTP 400 error, which causes the GitHub Actions job to be marked as failed. While the instance itself remains unaffected, having failed jobs in the logs can be mentally unpleasant. To avoid that, the workflow handles this case gracefully.

The instance is started or stopped using the gcloud sql instances patch command. Use the --activation-policy=ALWAYS option to start, and --activation-policy=NEVER to stop.

Since starting or stopping an instance can take several minutes, this might lead to the error ERROR: (gcloud.sql.instances.patch) Operation is taking longer than expected. While the operation usually completes successfully despite the error, it unnecessarily increases GitHub Actions execution time. To avoid this, I use the --async option to end the job without waiting for completion.

For more details on the patch command options, refer to:

Finally, by setting ACTION to ${{ github.event.inputs.action || 'stop' }}, the workflow supports using the selected action during manual runs and defaults to stop during scheduled runs.

Conclusion

That’s an overview of a GitHub Actions workflow to start and stop a CloudSQL instance. Most examples online use Cloud Functions and Cloud Scheduler, but I wanted to leverage GitHub Actions, so I went with this approach.

I hope this helps someone else trying to reduce their cloud costs!

Next

Build with a Specific Xcode Version in GitHub Actions macOS Image

PR

Related Posts