{"id":19195924,"url":"https://github.com/mercari/spanner-autoscaler","last_synced_at":"2025-04-15T14:27:04.183Z","repository":{"id":38575141,"uuid":"269546161","full_name":"mercari/spanner-autoscaler","owner":"mercari","description":"Kubernetes Operator for Cloud Spanner autoscaling","archived":false,"fork":false,"pushed_at":"2024-06-10T05:18:02.000Z","size":884,"stargazers_count":77,"open_issues_count":10,"forks_count":14,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-05T20:49:47.417Z","etag":null,"topics":["autoscaling","gcp-cloud-spanner","go","k8s-operator","spanner"],"latest_commit_sha":null,"homepage":"https://hub.docker.com/r/mercari/spanner-autoscaler","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mercari.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-06-05T06:16:50.000Z","updated_at":"2025-03-14T13:12:17.000Z","dependencies_parsed_at":"2023-12-25T08:25:34.045Z","dependency_job_id":"4ed490ea-21ae-43cd-80c1-4123e88cac8b","html_url":"https://github.com/mercari/spanner-autoscaler","commit_stats":null,"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mercari%2Fspanner-autoscaler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mercari%2Fspanner-autoscaler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mercari%2Fspanner-autoscaler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mercari%2Fspanner-autoscaler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mercari","download_url":"https://codeload.github.com/mercari/spanner-autoscaler/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249087625,"owners_count":21210671,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["autoscaling","gcp-cloud-spanner","go","k8s-operator","spanner"],"created_at":"2024-11-09T12:11:58.960Z","updated_at":"2025-04-15T14:27:04.155Z","avatar_url":"https://github.com/mercari.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Spanner Autoscaler\n\n[![actions-workflow-test][actions-workflow-test-badge]][actions-workflow-test]\n[![release][release-badge]][release]\n[![license][license-badge]][license]\n\nSpanner Autoscaler is a [Kubernetes Operator](https://coreos.com/operators/) to scale [Google Cloud Spanner](https://cloud.google.com/spanner/) automatically based on Cloud Spanner Instance CPU utilization like [Horizontal Pod Autoscaler](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/).\n\n## Overview\n\n[Cloud Spanner](https://cloud.google.com/spanner) is scalable.\nWhen CPU utilization becomes high, we can [reduce it by increasing compute capacity](https://cloud.google.com/spanner/docs/cpu-utilization?hl=en#add-compute-capacity).\n\nSpanner Autoscaler is created to reconcile Cloud Spanner compute capacity like [Horizontal Pod Autoscaler](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) by configuring a compute capacity range and `targetCPUUtilization`.\n\n\u003cimg src=\"./docs/assets/overview.jpg\" width=\"450\" height=\"300\"\u003e\n\nWhen CPU Utilization(High Priority) is above (or below) `targetCPUUtilization`, Spanner Autoscaler tries to bring it back to the threshold by calculating desired compute capacity and then increasing (or decreasing) compute capacity.\n\n\u003cimg src=\"./docs/assets/cpu_utilization.png\" width=\"400\" height=\"200\"\u003e \u003cimg src=\"./docs/assets/node_scaleup.png\" width=\"400\" height=\"200\"\u003e\n\nThe [pricing of Cloud Spanner](https://cloud.google.com/spanner/pricing) states that any compute capacity which is provisioned will be billed for a minimum of one hour, so Spanner Autoscaler maintains the increased compute capacity for about an hour. Spanner Autoscaler has `--scale-down-interval` flag (default: 55min) for achieving this.\n\nWhile scaling down, removing large amounts of compute capacity at once (like 10000 PU -\u003e 1000 PU) can cause a latency increase. Therefore, Spanner Autoscaler decreases the compute capacity in steps to avoid such large disruptions. This step size can be provided with the `scaledownStepSize` parameter (default: 2000 PU).\n\u003cimg src=\"./docs/assets/node_scaledown.png\" width=\"400\" height=\"200\"\u003e\n\n### Scheduled scaling feature\n\nIf there are some batch jobs or any other compute intensive tasks which are run periodically on the Cloud Spanner, it is now possible to bump up the scaling range only for a specified duration. For example, the following `SpannerAutoscaleSchedule` will add an extra compute capacity of 600 Processing Units to the spanner instance every day at 2 o'clock, just for 3 hours:\n```yaml\napiVersion: spanner.mercari.com/v1beta1\nkind: SpannerAutoscaleSchedule\nmetadata:\n  name: spannerautoscaleschedule-sample\n  namespace: your-namespace\nspec:\n  targetResource: spannerautoscaler-sample\n  additionalProcessingUnits: 600\n  schedule:\n    cron: \"0 2 * * *\"\n    duration: 3h\n```\n\n## Installation\n\nSpanner Autoscaler can be installed using [KPT](https://kpt.dev/installation/) by following 2 steps:\n\n1. Deploy the operator through `kpt`\n\n   ```console\n   $ kpt pkg get https://github.com/mercari/spanner-autoscaler/config spanner-autoscaler-pkg\n   $ kpt live init spanner-autoscaler-pkg/kpt\n   $ kpt live install-resource-group\n\n   ## Append '--dry-run' to the below line to just\n   ## check the resources which will be created\n   $ kustomize build spanner-autoscaler-pkg/kpt | kpt live apply -\n\n   ## To uninstall, use the following\n   $ kustomize build spanner-autoscaler-pkg/kpt | kpt live destroy -\n   ```\n   \u003e :information_source: **TIP:** Instead of `kpt`, you can also use `kubectl` directly to install the resources (use `?ref=master` for latest version) as follows:\n   \u003e   ```console\n   \u003e   $ kustomize build \"https://github.com/mercari/spanner-autoscaler.git/config/default?ref=v0.4.1\" | kubectl apply -f -\n   \u003e   ```\n   \u003e These resources can then be adopted by `kpt` by using the `--inventory-policy=adopt` flag while using `kpt live apply` command. [More info](https://kpt.dev/reference/cli/live/apply/?id=flags).\n\n1. Create a Custom Resource for managing a spanner instance\n\n   ```console\n   $ kubectl apply -f spanner-autoscaler-pkg/samples\n   ```\n   Examples of CustomResources can be found [below](#examples).\\\n   For authentication using a GCP service account JSON key, follow [these steps](#gcp-setup) to create a k8s secret with credentials.\n\n\n## CRD reference\n\n- [`SpannerAutoscaler` CRD reference](docs/crd-reference.md#spannerautoscaler)\n- [`SpannerAutoscaleSchedule` CRD reference](docs/crd-reference.md#spannerautoscaleschedule)\n\n\n## Examples\n\n#### Single Service Account using Workload Identity:\n\n```yaml\napiVersion: spanner.mercari.com/v1beta1\nkind: SpannerAutoscaler\nmetadata:\n  name: spannerautoscaler-sample\n  namespace: your-namespace\nspec:\n  targetInstance:\n    projectId: your-gcp-project-id\n    instanceId: your-spanner-instance-id\n  scaleConfig:\n    processingUnits:\n      min: 1000\n      max: 4000\n    scaledownStepSize: 1000\n    targetCPUUtilization:\n      highPriority: 60\n```\n\n#### Using Service Account JSON key for each `SpannerAutoscaler`:\n\n```diff\n  apiVersion: spanner.mercari.com/v1beta1\n  kind: SpannerAutoscaler\n  metadata:\n    name: spannerautoscaler-sample\n    namespace: your-namespace\n  spec:\n    targetInstance:\n      projectId: your-gcp-project-id\n      instanceId: your-spanner-instance-id\n+   authentication:\n+     iamKeySecret:\n+       namespace: your-namespace\n+       name: spanner-autoscaler-gcp-sa\n+       key: service-account\n    scaleConfig:\n      processingUnits:\n        min: 1000\n        max: 4000\n      scaledownStepSize: 1000\n      targetCPUUtilization:\n        highPriority: 60\n```\n\n#### Using Service Accounts with Workload Identity and impersonation:\n\n```diff\n  apiVersion: spanner.mercari.com/v1beta1\n  kind: SpannerAutoscaler\n  metadata:\n    name: spannerautoscaler-sample\n    namespace: your-namespace\n  spec:\n    targetInstance:\n      projectId: your-gcp-project-id\n      instanceId: your-spanner-instance-id\n+   authentication:\n+     impersonateConfig:\n+       targetServiceAccount: GSA_SPANNER@TENANT_PROJECT.iam.gserviceaccount.com\n    scaleConfig:\n      processingUnits:\n        min: 1000\n        max: 4000\n      scaledownStepSize: 1000\n      targetCPUUtilization:\n        highPriority: 60\n```\n\n\n## GCP Setup\n\nOn your GCP project, you will need to enable `spanner.googleapis.com` and `monitoring.googleapis.com` APIs.\n\n### Create service account\n\nYou will need to create at least one GCP service account, which will be used by the spanner-autoscaler controller to authenticate with GCP for modifying compute capacity of a Spanner instance. This service account should have the following roles:\n  - `roles/spanner.admin` (on the Spanner instances)\n  - `roles/monitoring.viewer` (on the project)\n\nFor fine grained access control, you should create one GCP service account per Spanner instance. This way, you will be able to specify a different service account in each of `SpannerAutoscaler` CRD resources you create later.\n\n### Authenticate with service account JSON key\n\nGenerate a JSON key for the GCP service account (created [above](#create-service-account)) and put it in a Kubernetes Secret:\n```sh\n$ kubectl create secret generic spanner-autoscaler-gcp-sa --from-file=service-account=./service-account-key.json -n your-namespace\n```\n\u003e :information_source: By default, `spanner-autoscaler` will have read access to `secret`s named `spanner-autoscaler-gcp-sa` in any namespace. If you wish to use a different name for your secret, then you need to explicitly create a `Role` and a `RoleBinding` ([example](/config/samples/rbac/role.yaml)) in your namespace. This will provide `spanner-autoscaler` with read access to any secret of your choice.\n\nYou can then refer to this secret in your `SpannerAutoscaler` CRD resource with `serviceAccountSecretRef` field [[example](#using-service-account-json-key-for-each-spannerautoscaler)].\n\n\n### [Optional] Advanced methods for GCP authentication\n\n\nFollowing are some other advanced methods which can also be used for GCP authentication:\n\u003cdetails\u003e \u003csummary\u003eDetails\u003c/summary\u003e\n\u003cul\u003e\n\n  #### Enable Workload Identity\n\n  \u003cdetails\u003e \u003csummary\u003eDetails\u003c/summary\u003e\n\n  You can configure the controller (`spanner-autoscaler-controller-manager`) to use [GKE Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity) feature for key-less GCP access. Steps to do this:\n  1. Enable Workload Identity on the GKE cluster - [Ref](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity?hl=en#enable_on_cluster).\n  1. Let's call the Kubernetes service account of the controller (`spanner-autoscaler/spanner-autoscaler-controller-manager`) as `KSA_CONTROLLER` and the GCP service account created [above](#create-service-account) as `GSA_CONTROLLER`.\\\n     Now configure Workload Identity between `KSA_CONTROLLER` and `GSA_CONTROLLER` with the following steps:\n     1. Allow `KSA_CONTROLLER` to impersonate `GSA_CONTROLLER` by creating an IAM Policy binding:\n        ```console\n        $ gcloud iam service-accounts add-iam-policy-binding --role roles/iam.workloadIdentityUser --member \"serviceAccount:PROJECT_ID.svc.id.goog[spanner-autoscaler/spanner-autoscaler-controller-manager]\" GSA_CONTROLLER@PROJECT_ID.iam.gserviceaccount.com`\n        ```\n     1. Add annotation\n        ```sh\n        $ kubectl annotate serviceaccount  --namespace spanner-autoscaler spanner-autoscaler-controller-manager iam.gke.io/gcp-service-account=GSA_CONTROLLER@PROJECT_ID.iam.gserviceaccount.com`\n        ```\n  \u003c/details\u003e\n\u003c/ul\u003e\n\n\u003cul\u003e\n\n  #### Single service account with Workload Identity\n\n  \u003cdetails\u003e \u003csummary\u003eDetails\u003c/summary\u003e\n\n  The Kubernetes service account which is used for running the spanner-autoscaler controller can be bound to the GCP service account (created [above](#create-service-account)) through Workload Identity. If this is done, there is no need to provide `serviceAccountSecretRef` or `impersonateConfig` authentication parameters in the `spec` section of the `SpannerAutoscaler` CRD resources.\n\n  An example for this is shown [here](#single-service-account-using-workload-identity).\n\n  \u003c/details\u003e\n\u003c/ul\u003e\n\n\u003cul\u003e\n\n  #### Using service accounts with Workload Identity and Impersonation\n\n  \u003cdetails\u003e \u003csummary\u003eDetails\u003c/summary\u003e\n\n  In this method there are 3 service accounts involved (2 GCP service accounts and 1 Kubernetes service account):\n  - `GSA_SPANNER`: The GCP Service Account (created [above](#create-service-account)) which has the correct permissions for modifying Spanner compute capacity\n  - `GSA_CONTROLLER`: The GCP Service Account which is used for Workload Identity with the GKE cluster\n  - `KSA_CONTROLLER`: The Kubernetes Service Account which is used for running the spanner-autoscaler controller pod in the GKE\n\n  After enabling Workload Identity between `GSA_CONTROLLER` and `KSA_CONTROLLER`, you can configure `GSA_CONTROLLER` as `roles/iam.serviceAccountTokenCreator` of the `GSA_SPANNER` service account as follows:\n\n  ```sh\n  $ gcloud iam service-accounts add-iam-policy-binding $GSA_SPANNER --member=serviceAccount:$GSA_CONTROLLER --role=roles/iam.serviceAccountTokenCreator\n  ```\n  This will allow `KSA_CONTROLLER` to use `GSA_CONTROLLER` and impersonate (act as) `GSA_SPANNER` for a short period of time (by using a short-lived token). An example for this can be found [here](#using-service-accounts-with-workload-identity-and-impersonation).\n\n  \u003c/details\u003e\n\u003c/ul\u003e\n\n\u003cul\u003e\n\n  **TIP:** Custom role with minimum permissions\n\n  \u003cdetails\u003e \u003csummary\u003eDetails\u003c/summary\u003e\n\n  Instead of predefined roles, you can define and use a [custom role](https://cloud.google.com/iam/docs/creating-custom-roles/?hl=en) with lesser privileges for Spanner Autoscaler. To scale the target Cloud Spanner instance, the weakest predefined role is [`roles/spanner.admin`](https://cloud.google.com/spanner/docs/iam?hl=en#roles). To observe the CPU usage metric of the project of the Spanner instance, the weakest predefined role is [`roles/monitoring.viewer`](https://cloud.google.com/monitoring/access-control?hl=en#monitoring_2).\\\n  The custom role can be created with just the following permissions:\n  - `spanner.instances.get`\n  - `spanner.instances.update`\n  - `monitoring.timeSeries.list`\n\n  \u003c/details\u003e\n\u003c/ul\u003e\n\n\u003c/details\u003e\n\n\n## Development and Contribution\n\nSee [docs/development.md](docs/development.md) and [CONTRIBUTING.md](.github/CONTRIBUTING.md) respectively.\n\n### :information_source: Migration from `0.3.0` to `0.4.0`:\n\nThe older version `0.3.0` (with `apiVersion: spanner.mercari.com/v1alpha1`) is now deprecated in favor of `0.4.0` (with `apiVersion: spanner.mercari.com/v1beta1`).\n\nVersion `0.4.0` is backward compatible with `0.3.0`, but there is a restructuring of the `SpannerAutoscaler` resource definition and names of many fields have changed. Thus it is recommended to go through the [`SpannerAutoscaler` CRD reference](docs/crd-reference.md#spannerautoscaler) and replace `v1alpha1` resources with `v1beta1` spec definition.\n\n## License\n\nSpanner Autoscaler is released under the [Apache License 2.0](./LICENSE).\n\n:warning: **NOTE:**\n\n1. This project is currently in active development phase and there might be some backward incompatible changes in future versions.\n1. Spanner Autoscaler watches `High Priority` CPU utilization only. It doesn't watch `Low Priority` CPU utilization and Rolling average 24 hour utilization.\n1. It doesn't check [the storage size and the number of databases](https://cloud.google.com/spanner/quotas?hl=en#database_limits) as well. You must take care of these metrics by yourself.\n\n\n:information_source: More information and background of spanner-autoscaler is available on [this blog](https://engineering.mercari.com/en/blog/entry/20211222-kubernetes-based-spanner-autoscaler)!\n\n\u003c!-- badge links --\u003e\n\n[actions-workflow-test]: https://github.com/mercari/spanner-autoscaler/actions?query=workflow%3ATest\n[actions-workflow-test-badge]: https://img.shields.io/github/workflow/status/mercari/spanner-autoscaler/Test?label=Test\u0026style=for-the-badge\u0026logo=github\n\n[release]: https://github.com/mercari/spanner-autoscaler/releases\n[release-badge]: https://img.shields.io/github/v/release/mercari/spanner-autoscaler?style=for-the-badge\u0026logo=github\n\n[license]: LICENSE\n[license-badge]: https://img.shields.io/github/license/mercari/spanner-autoscaler?style=for-the-badge\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmercari%2Fspanner-autoscaler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmercari%2Fspanner-autoscaler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmercari%2Fspanner-autoscaler/lists"}