Skip to content
This repository was archived by the owner on Dec 24, 2019. It is now read-only.

Commit 1906462

Browse files
authored
Merge pull request #22 from hjacobs/ignore-master-asg
#21 ignore master nodes by default
2 parents 7038627 + ad963ac commit 1906462

3 files changed

Lines changed: 39 additions & 8 deletions

File tree

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ The autoscaler consists of a simple main loop which calls the ``autoscale`` func
4949
The main loop keeps no state (like history), all input for the ``autoscale`` function comes from either static configuration or the Kubernetes API server.
5050
The ``autoscale`` function performs the following task:
5151

52-
* retrieve the list of all nodes from the Kubernetes API and group them by Auto Scaling Group (ASG) and Availability Zone (AZ)
52+
* retrieve the list of all (worker) nodes from the Kubernetes API and group them by Auto Scaling Group (ASG) and Availability Zone (AZ)
5353
* retrieve the list of all pods from the Kubernetes API
5454
* calculate the current resource "usage" for every ASG and AZ by summing up all pod resource requests (CPU, memory and number of pods)
5555
* calculates the currently required number of nodes per AWS Auto Scaling Group:

kube_aws_autoscaler/main.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def is_node_ready(node):
8080
return False
8181

8282

83-
def get_nodes(api) -> dict:
83+
def get_nodes(api, include_master_nodes: bool=False) -> dict:
8484
nodes = {}
8585
for node in pykube.Node.objects(api):
8686
region = node.labels['failure-domain.beta.kubernetes.io/region']
@@ -98,7 +98,8 @@ def get_nodes(api) -> dict:
9898
'ready': is_node_ready(node),
9999
'unschedulable': node.obj['spec'].get('unschedulable', False),
100100
'master': node.labels.get('master', 'false') == 'true'}
101-
nodes[node.name] = obj
101+
if include_master_nodes or not obj['master']:
102+
nodes[node.name] = obj
102103
return nodes
103104

104105

@@ -317,10 +318,10 @@ def get_ready_nodes_by_asg(nodes_by_asg_zone):
317318
return ready_nodes_by_asg
318319

319320

320-
def autoscale(buffer_percentage: dict, buffer_fixed: dict, buffer_spare_nodes: int=0, dry_run: bool=False):
321+
def autoscale(buffer_percentage: dict, buffer_fixed: dict, buffer_spare_nodes: int=0, include_master_nodes: bool=False, dry_run: bool=False):
321322
api = get_kube_api()
322323

323-
all_nodes = get_nodes(api)
324+
all_nodes = get_nodes(api, include_master_nodes)
324325
region = list(all_nodes.values())[0]['region']
325326

326327
autoscaling = boto3.client('autoscaling', region)
@@ -344,6 +345,8 @@ def main():
344345
parser.add_argument('--debug', '-d', help='Debug mode: print more information', action='store_true')
345346
parser.add_argument('--once', help='Run loop only once and exit', action='store_true')
346347
parser.add_argument('--interval', type=int, help='Loop interval (default: 60s)', default=60)
348+
parser.add_argument('--include-master-nodes', help='Do not ignore auto scaling group with master nodes',
349+
action='store_true')
347350
parser.add_argument('--buffer-spare-nodes', type=int,
348351
help='Number of extra "spare" nodes to provision per ASG/AZ (default: 1)', default=1)
349352
for resource in RESOURCES:
@@ -368,7 +371,7 @@ def main():
368371
while True:
369372
try:
370373
autoscale(buffer_percentage, buffer_fixed, buffer_spare_nodes=args.buffer_spare_nodes,
371-
dry_run=args.dry_run)
374+
include_master_nodes=args.include_master_nodes, dry_run=args.dry_run)
372375
except:
373376
logger.exception('Failed to autoscale')
374377
if args.once:

tests/test_autoscaler.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,21 @@ def test_get_nodes(monkeypatch):
251251
'spec': {'externalID': 'i-123'}
252252
}
253253

254+
master = MagicMock()
255+
master.name = 'master'
256+
master.labels = {
257+
'failure-domain.beta.kubernetes.io/region': 'eu-north-1',
258+
'failure-domain.beta.kubernetes.io/zone': 'eu-north-1a',
259+
'beta.kubernetes.io/instance-type': 'a1.small',
260+
'master': 'true'
261+
}
262+
master.obj = {
263+
'status': {'allocatable': {'cpu': '2', 'memory': '16Gi', 'pods': '10'}},
264+
'spec': {'externalID': 'i-456'}
265+
}
266+
254267
objects = MagicMock()
255-
objects.return_value = [node]
268+
objects.return_value = [node, master]
256269
monkeypatch.setattr('pykube.Node.objects', objects)
257270
api = MagicMock()
258271
assert get_nodes(api) == {'n1': {
@@ -263,6 +276,21 @@ def test_get_nodes(monkeypatch):
263276
'unschedulable': False,
264277
'master': False}}
265278

279+
assert get_nodes(api, include_master_nodes=True) == {'n1': {
280+
'name': 'n1',
281+
'region': 'eu-north-1', 'zone': 'eu-north-1a', 'instance_id': 'i-123', 'instance_type': 'x1.mega',
282+
'allocatable': {'cpu': 2, 'memory': 16*1024*1024*1024, 'pods': 10},
283+
'ready': False,
284+
'unschedulable': False,
285+
'master': False}, 'master': {
286+
'name': 'master',
287+
'region': 'eu-north-1', 'zone': 'eu-north-1a', 'instance_id': 'i-456', 'instance_type': 'a1.small',
288+
'allocatable': {'cpu': 2, 'memory': 16*1024*1024*1024, 'pods': 10},
289+
'ready': False,
290+
'unschedulable': False,
291+
'master': True
292+
}}
293+
266294

267295
def test_get_kube_api(monkeypatch):
268296
kube_config = MagicMock()
@@ -337,7 +365,7 @@ def test_main(monkeypatch):
337365
monkeypatch.setattr('kube_aws_autoscaler.main.autoscale', autoscale)
338366
monkeypatch.setattr('sys.argv', ['foo', '--once', '--dry-run'])
339367
main()
340-
autoscale.assert_called_once_with({'memory': 10, 'pods': 10, 'cpu': 10}, {'memory': 209715200, 'pods': 10, 'cpu': 0.2}, buffer_spare_nodes=1, dry_run=True)
368+
autoscale.assert_called_once_with({'memory': 10, 'pods': 10, 'cpu': 10}, {'memory': 209715200, 'pods': 10, 'cpu': 0.2}, buffer_spare_nodes=1, include_master_nodes=False, dry_run=True)
341369

342370
autoscale.side_effect = ValueError
343371

0 commit comments

Comments
 (0)