|
| 1 | +# Copyright 2017 Google Inc. |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | + |
| 15 | +"""Sample that demonstrates how to use Stackdriver Monitoring metrics to |
| 16 | +programmatically scale a Google Cloud Bigtable cluster.""" |
| 17 | + |
| 18 | +import argparse |
| 19 | +import time |
| 20 | + |
| 21 | +from google.cloud import bigtable |
| 22 | +from google.cloud import monitoring |
| 23 | + |
| 24 | + |
| 25 | + |
| 26 | +def get_cpu_load(): |
| 27 | + """Returns the most recent Cloud Bigtable CPU load measurement. |
| 28 | +
|
| 29 | + Returns: |
| 30 | + float: The most recent Cloud Bigtable CPU usage metric |
| 31 | + """ |
| 32 | + # [START bigtable_cpu] |
| 33 | + client = monitoring.Client() |
| 34 | + query = client.query('bigtable.googleapis.com/cluster/cpu_load', minutes=5) |
| 35 | + time_series = list(query) |
| 36 | + recent_time_series = time_series[0] |
| 37 | + return recent_time_series.points[0].value |
| 38 | + # [END bigtable_cpu] |
| 39 | + |
| 40 | + |
| 41 | +def scale_bigtable(bigtable_instance, bigtable_cluster, scale_up): |
| 42 | + """Scales the number of Cloud Bigtable nodes up or down. |
| 43 | +
|
| 44 | + Edits the number of nodes in the Cloud Bigtable cluster to be increased |
| 45 | + or decreased, depending on the `scale_up` boolean argument. Currently |
| 46 | + the `incremental` strategy from `strategies.py` is used. |
| 47 | +
|
| 48 | +
|
| 49 | + Args: |
| 50 | + bigtable_instance (str): Cloud Bigtable instance ID to scale |
| 51 | + bigtable_cluster (str): Cloud Bigtable cluster ID to scale |
| 52 | + scale_up (bool): If true, scale up, otherwise scale down |
| 53 | + """ |
| 54 | + _MIN_NODE_COUNT = 3 |
| 55 | + """ |
| 56 | + The minimum number of nodes to use. The default minimum is 3. If you have a |
| 57 | + lot of data, the rule of thumb is to not go below 2.5 TB per node for SSD |
| 58 | + clusters, and 8 TB for HDD. The bigtable.googleapis.com/disk/bytes_used |
| 59 | + metric is useful in figuring out the minimum number of nodes. |
| 60 | + """ |
| 61 | + |
| 62 | + _MAX_NODE_COUNT = 30 |
| 63 | + """ |
| 64 | + The maximum number of nodes to use. The default maximum is 30 nodes per zone. |
| 65 | + If you need more quota, you can request more by following the instructions |
| 66 | + <a href="https://cloud.google.com/bigtable/quota">here</a>. |
| 67 | + """ |
| 68 | + |
| 69 | + _SIZE_CHANGE_STEP = 3 |
| 70 | + """The number of nodes to change the cluster by.""" |
| 71 | + # [START bigtable_scale] |
| 72 | + bigtable_client = bigtable.Client(admin=True) |
| 73 | + instance = bigtable_client.instance(bigtable_instance) |
| 74 | + instance.reload() |
| 75 | + |
| 76 | + cluster = instance.cluster(bigtable_cluster) |
| 77 | + cluster.reload() |
| 78 | + |
| 79 | + current_node_count = cluster.serve_nodes |
| 80 | + |
| 81 | + if scale_up: |
| 82 | + if current_node_count < _MAX_NODE_COUNT: |
| 83 | + new_node_count = min(current_node_count + 3, _MAX_NODE_COUNT) |
| 84 | + cluster.serve_nodes = new_node_count |
| 85 | + cluster.update() |
| 86 | + print('Scaled up from {} to {} nodes.'.format( |
| 87 | + current_node_count, new_node_count)) |
| 88 | + else: |
| 89 | + if current_node_count > _MIN_NODE_COUNT: |
| 90 | + new_node_count = max( |
| 91 | + current_node_count - _SIZE_CHANGE_STEP, _MIN_NODE_COUNT) |
| 92 | + cluster.serve_nodes = new_node_count |
| 93 | + cluster.update() |
| 94 | + print('Scaled down from {} to {} nodes.'.format( |
| 95 | + current_node_count, new_node_count)) |
| 96 | + # [END bigtable_scale] |
| 97 | + |
| 98 | + |
| 99 | +def main( |
| 100 | + bigtable_instance, |
| 101 | + bigtable_cluster, |
| 102 | + high_cpu_threshold, |
| 103 | + low_cpu_threshold, |
| 104 | + short_sleep, |
| 105 | + long_sleep): |
| 106 | + """Main loop runner that autoscales Cloud Bigtable. |
| 107 | +
|
| 108 | + Args: |
| 109 | + bigtable_instance (str): Cloud Bigtable instance ID to autoscale |
| 110 | + high_cpu_threshold (float): If CPU is higher than this, scale up. |
| 111 | + low_cpu_threshold (float): If CPU is lower than this, scale down. |
| 112 | + short_sleep (int): How long to sleep after no operation |
| 113 | + long_sleep (int): How long to sleep after the number of nodes is |
| 114 | + changed |
| 115 | + """ |
| 116 | + cluster_cpu = get_cpu_load() |
| 117 | + print('Detected cpu of {}'.format(cluster_cpu)) |
| 118 | + if cluster_cpu > high_cpu_threshold: |
| 119 | + scale_bigtable(bigtable_instance, bigtable_cluster, True) |
| 120 | + time.sleep(long_sleep) |
| 121 | + elif cluster_cpu < low_cpu_threshold: |
| 122 | + scale_bigtable(bigtable_instance, bigtable_cluster, False) |
| 123 | + time.sleep(long_sleep) |
| 124 | + else: |
| 125 | + print('CPU within threshold, sleeping.') |
| 126 | + time.sleep(short_sleep) |
| 127 | + |
| 128 | + |
| 129 | +if __name__ == '__main__': |
| 130 | + parser = argparse.ArgumentParser( |
| 131 | + description='Scales Cloud Bigtable clusters based on CPU usage.') |
| 132 | + parser.add_argument( |
| 133 | + 'bigtable_instance', |
| 134 | + help='ID of the Cloud Bigtable instance to connect to.') |
| 135 | + parser.add_argument( |
| 136 | + 'bigtable_cluster', |
| 137 | + help='ID of the Cloud Bigtable cluster to connect to.') |
| 138 | + parser.add_argument( |
| 139 | + '--high_cpu_threshold', |
| 140 | + help='If Cloud Bigtable CPU usage is above this threshold, scale up', |
| 141 | + default=0.6) |
| 142 | + parser.add_argument( |
| 143 | + '--low_cpu_threshold', |
| 144 | + help='If Cloud Bigtable CPU usage is below this threshold, scale down', |
| 145 | + default=0.2) |
| 146 | + parser.add_argument( |
| 147 | + '--short_sleep', |
| 148 | + help='How long to sleep in seconds between checking metrics after no ' |
| 149 | + 'scale operation', |
| 150 | + default=60) |
| 151 | + parser.add_argument( |
| 152 | + '--long_sleep', |
| 153 | + help='How long to sleep in seconds between checking metrics after a ' |
| 154 | + 'scaling operation', |
| 155 | + default=60 * 10) |
| 156 | + args = parser.parse_args() |
| 157 | + |
| 158 | + while True: |
| 159 | + main( |
| 160 | + args.bigtable_instance, |
| 161 | + args.bigtable_cluster, |
| 162 | + float(args.high_cpu_threshold), |
| 163 | + float(args.low_cpu_threshold), |
| 164 | + int(args.short_sleep), |
| 165 | + int(args.long_sleep)) |
0 commit comments