Skip to content

Commit c3eeb0f

Browse files
authored
feat(pool): Allow to optionally include busy runners into the pool (github-aws-runners#5078)
## Description 'pool_config' is useful when one wants to ensure that a certain number of runners is running within given time periods. However, when 'pool_config' is used in a combination with persistent runners, the pool lambda tends to spin up new runner instance when it sees that some existing runners are online, but busy. As a result, with frequent pool lambda checks, the total size of the runners pool tends to grow more than desired (without respecting 'runners_maximum_count', which is only considered by the 'scale_up' lambda). These changes introduce a new 'pool_include_busy_runners' module variable to make it possbile to include all online runners (both idle and busy) into the pool, so that the pool's lambda only tops up the pool if not enough runners are online. <!-- Briefly describe the changes in this PR. --> ## Test Plan Tested on a module instance with persistent runners, also added a few unit tests to the pool lambda itself. <!-- Please describe how you tested these changes. This helps reviewers understand the scope and confidence level of the change. Examples: - Ran `terraform plan` against an existing deployment - Added/updated unit tests (link to test file or describe coverage) - Deployed to a test environment and triggered a workflow run - Validated with `terraform validate` and `tflint` --> ## Related Issues <!-- Link any related issues, e.g. Fixes github-aws-runners#123, Closes github-aws-runners#456 -->
1 parent 28b90f4 commit c3eeb0f

10 files changed

Lines changed: 50 additions & 3 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ Join our discord community via [this invite link](https://discord.gg/bxgXW8jJGh)
170170
| <a name="input_minimum_running_time_in_minutes"></a> [minimum\_running\_time\_in\_minutes](#input\_minimum\_running\_time\_in\_minutes) | The time an ec2 action runner should be running at minimum before terminated, if not busy. | `number` | `null` | no |
171171
| <a name="input_parameter_store_tags"></a> [parameter\_store\_tags](#input\_parameter\_store\_tags) | Map of tags that will be added to all the SSM Parameter Store parameters created by the Lambda function. | `map(string)` | `{}` | no |
172172
| <a name="input_pool_config"></a> [pool\_config](#input\_pool\_config) | The configuration for updating the pool. The `pool_size` to adjust to by the events triggered by the `schedule_expression`. For example you can configure a cron expression for weekdays to adjust the pool to 10 and another expression for the weekend to adjust the pool to 1. Use `schedule_expression_timezone` to override the schedule time zone (defaults to UTC). | <pre>list(object({<br/> schedule_expression = string<br/> schedule_expression_timezone = optional(string)<br/> size = number<br/> }))</pre> | `[]` | no |
173+
| <a name="input_pool_include_busy_runners"></a> [pool\_include\_busy\_runners](#input\_pool\_include\_busy\_runners) | Include busy runners in the pool calculation. By default busy runners are not included in the pool. | `bool` | `false` | no |
173174
| <a name="input_pool_lambda_memory_size"></a> [pool\_lambda\_memory\_size](#input\_pool\_lambda\_memory\_size) | Memory size limit for scale-up lambda. | `number` | `512` | no |
174175
| <a name="input_pool_lambda_reserved_concurrent_executions"></a> [pool\_lambda\_reserved\_concurrent\_executions](#input\_pool\_lambda\_reserved\_concurrent\_executions) | Amount of reserved concurrent executions for the scale-up lambda function. A value of 0 disables lambda from being triggered and -1 removes any concurrency limitations. | `number` | `1` | no |
175176
| <a name="input_pool_lambda_timeout"></a> [pool\_lambda\_timeout](#input\_pool\_lambda\_timeout) | Time out for the pool lambda in seconds. | `number` | `60` | no |

lambdas/functions/control-plane/src/pool/pool.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,4 +424,28 @@ describe('Test simple pool.', () => {
424424
);
425425
});
426426
});
427+
428+
describe('With INCLUDE_BUSY_RUNNERS enabled', () => {
429+
beforeEach(() => {
430+
process.env.INCLUDE_BUSY_RUNNERS = 'true';
431+
});
432+
433+
it('Should not top up when pool size matches runners including busy online runners.', async () => {
434+
// Without INCLUDE_BUSY_RUNNERS: 2 in pool (i-1-idle, i-4-idle-older). With it: 3 (adds i-2-busy).
435+
await adjust({ poolSize: 3 });
436+
expect(createRunners).not.toHaveBeenCalled();
437+
});
438+
439+
it('Should top up by two runners when pool size is 5 and busy runners count toward the pool.', async () => {
440+
await adjust({ poolSize: 5 });
441+
// 3 in pool (idle, busy, older idle); need 2 more
442+
expect(createRunners).toHaveBeenCalledWith(
443+
expect.anything(),
444+
expect.anything(),
445+
2,
446+
expect.anything(),
447+
'pool-lambda',
448+
);
449+
});
450+
});
427451
});

lambdas/functions/control-plane/src/pool/pool.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export async function adjust(event: PoolEvent): Promise<void> {
5353
// -1 disables the maximum check, matching the scale-up lambda's semantics. Defaults to unlimited
5454
// when unset so the pool keeps its previous behavior on stacks that do not provide the variable.
5555
const maximumRunners = parseInt(process.env.RUNNERS_MAXIMUM_COUNT || '-1');
56+
const includeBusyRunners = yn(process.env.INCLUDE_BUSY_RUNNERS, { default: false });
5657

5758
const { ghesApiUrl, ghesBaseUrl } = getGitHubEnterpriseApiUrl();
5859

@@ -75,7 +76,7 @@ export async function adjust(event: PoolEvent): Promise<void> {
7576
statuses: ['running'],
7677
});
7778

78-
const numberOfRunnersInPool = calculatePooSize(ec2runners, runnerStatusses);
79+
const numberOfRunnersInPool = calculatePooSize(ec2runners, runnerStatusses, includeBusyRunners);
7980
let topUp = event.poolSize - numberOfRunnersInPool;
8081

8182
// The pool must never push the total number of runners (busy + idle) past the configured maximum.
@@ -146,12 +147,16 @@ async function getInstallationId(ghesApiUrl: string, org: string): Promise<numbe
146147
).data.id;
147148
}
148149

149-
function calculatePooSize(ec2runners: RunnerList[], runnerStatus: Map<string, RunnerStatus>): number {
150+
function calculatePooSize(
151+
ec2runners: RunnerList[],
152+
runnerStatus: Map<string, RunnerStatus>,
153+
includeBusyRunners: boolean,
154+
): number {
150155
// Runner should be considered idle if it is still booting, or is idle in GitHub
151156
let numberOfRunnersInPool = 0;
152157
for (const ec2Instance of ec2runners) {
153158
if (
154-
runnerStatus.get(ec2Instance.instanceId)?.busy === false &&
159+
(runnerStatus.get(ec2Instance.instanceId)?.busy === false || includeBusyRunners) &&
155160
runnerStatus.get(ec2Instance.instanceId)?.status === 'online'
156161
) {
157162
numberOfRunnersInPool++;

main.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ module "runners" {
280280
pool_lambda_timeout = var.pool_lambda_timeout
281281
pool_runner_owner = var.pool_runner_owner
282282
pool_lambda_reserved_concurrent_executions = var.pool_lambda_reserved_concurrent_executions
283+
pool_include_busy_runners = var.pool_include_busy_runners
283284

284285
ssm_housekeeper = var.runners_ssm_housekeeper
285286
ebs_optimized = var.runners_ebs_optimized

modules/runners/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ yarn run dist
198198
| <a name="input_parameter_store_tags"></a> [parameter\_store\_tags](#input\_parameter\_store\_tags) | Map of tags that will be added to all the SSM Parameter Store parameters created by the Lambda function. | `map(string)` | `{}` | no |
199199
| <a name="input_placement"></a> [placement](#input\_placement) | The placement options for the instance. See https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_template#placement for details. | <pre>object({<br/> affinity = optional(string)<br/> availability_zone = optional(string)<br/> group_id = optional(string)<br/> group_name = optional(string)<br/> host_id = optional(string)<br/> host_resource_group_arn = optional(string)<br/> spread_domain = optional(string)<br/> tenancy = optional(string)<br/> partition_number = optional(number)<br/> })</pre> | `null` | no |
200200
| <a name="input_pool_config"></a> [pool\_config](#input\_pool\_config) | The configuration for updating the pool. The `pool_size` to adjust to by the events triggered by the `schedule_expression`. For example you can configure a cron expression for week days to adjust the pool to 10 and another expression for the weekend to adjust the pool to 1. Use `schedule_expression_timezone ` to override the schedule time zone (defaults to UTC). | <pre>list(object({<br/> schedule_expression = string<br/> schedule_expression_timezone = optional(string)<br/> size = number<br/> }))</pre> | `[]` | no |
201+
| <a name="input_pool_include_busy_runners"></a> [pool\_include\_busy\_runners](#input\_pool\_include\_busy\_runners) | Include busy runners in the pool calculation. By default busy runners are not included in the pool. | `bool` | `false` | no |
201202
| <a name="input_pool_lambda_memory_size"></a> [pool\_lambda\_memory\_size](#input\_pool\_lambda\_memory\_size) | Lambda Memory size limit in MB for pool lambda | `number` | `512` | no |
202203
| <a name="input_pool_lambda_reserved_concurrent_executions"></a> [pool\_lambda\_reserved\_concurrent\_executions](#input\_pool\_lambda\_reserved\_concurrent\_executions) | Amount of reserved concurrent executions for the scale-up lambda function. A value of 0 disables lambda from being triggered and -1 removes any concurrency limitations. | `number` | `1` | no |
203204
| <a name="input_pool_lambda_timeout"></a> [pool\_lambda\_timeout](#input\_pool\_lambda\_timeout) | Time out for the pool lambda in seconds. | `number` | `60` | no |

modules/runners/pool.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ module "pool" {
3939
parameter_store_tags = local.parameter_store_tags
4040
}
4141
pool = var.pool_config
42+
include_busy_runners = var.pool_include_busy_runners
4243
role_path = local.role_path
4344
role_permissions_boundary = var.role_permissions_boundary
4445
runner = {

modules/runners/pool/main.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ resource "aws_lambda_function" "pool" {
6060
SSM_PARAMETER_STORE_TAGS = var.config.lambda.parameter_store_tags
6161
SCALE_ERRORS = jsonencode(var.config.runner.scale_errors)
6262
USE_DEDICATED_HOST = var.config.runner.use_dedicated_host
63+
INCLUDE_BUSY_RUNNERS = var.config.include_busy_runners
6364
}
6465
}
6566

modules/runners/pool/variables.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ variable "config" {
6060
schedule_expression_timezone = string
6161
size = number
6262
}))
63+
include_busy_runners = bool
6364
role_permissions_boundary = string
6465
kms_key_arn = string
6566
ami_kms_key_arn = string

modules/runners/variables.tf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,12 @@ variable "pool_config" {
606606
default = []
607607
}
608608

609+
variable "pool_include_busy_runners" {
610+
description = "Include busy runners in the pool calculation. By default busy runners are not included in the pool."
611+
type = bool
612+
default = false
613+
}
614+
609615
variable "disable_runner_autoupdate" {
610616
description = "Disable the auto update of the github runner agent. Be aware there is a grace period of 30 days, see also the [GitHub article](https://github.blog/changelog/2022-02-01-github-actions-self-hosted-runners-can-now-disable-automatic-updates/)"
611617
type = bool

variables.tf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,12 @@ variable "pool_config" {
847847
default = []
848848
}
849849

850+
variable "pool_include_busy_runners" {
851+
description = "Include busy runners in the pool calculation. By default busy runners are not included in the pool."
852+
type = bool
853+
default = false
854+
}
855+
850856
variable "aws_partition" {
851857
description = "(optiona) partition in the arn namespace to use if not 'aws'"
852858
type = string

0 commit comments

Comments
 (0)