Skip to content

Commit 9c571e5

Browse files
authored
feat: Add support for ARM64 runners (#102)
* Add support for ARM64 runners * Support Graviton (a1) and Graviton2 (*6g*) * Address TF format issues. * Address additional TF format error. * Add test case for arm64 release asset. * Add documentation changes for ARM64. * Make ARM/ICU patch conditional in user_data.sh. * Run pre-commit hooks.
1 parent 93ca5fd commit 9c571e5

File tree

15 files changed

+368
-170
lines changed

15 files changed

+368
-170
lines changed

README.md

Lines changed: 47 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ Permission are managed on several places. Below the most important ones. For det
5858

5959
Besides these permissions, the lambdas also need permission to CloudWatch (for logging and scheduling), SSM and S3. For more details about the required permissions see the [documentation](./modules/setup-iam-permissions/README.md) of the IAM module which uses permission boundaries.
6060

61+
### ARM64 support via Graviton/Graviton2 instance-types
62+
63+
When using the default example or top-level module, specifying an `instance_type` that matches a Graviton/Graviton 2 (ARM64) architecture (e.g. a1 or any 6th-gen `g` or `gd` type), the sub-modules will be automatically configured to provision with ARM64 AMIs and leverage GitHub's ARM64 action runner. See below for more details.
64+
6165
## Usages
6266

6367
Examples are provided in [the example directory](examples/). Please ensure you have installed the following tools.
@@ -151,6 +155,8 @@ module "github-runner" {
151155
}
152156
```
153157

158+
**ARM64** support: Specify an `a1` or `*6g*` (6th-gen Graviton2) instance type to stand up an ARM64 runner, otherwise the default is x86_64.
159+
154160
2. Run terraform by using the following commands
155161

156162
```bash
@@ -223,58 +229,59 @@ The following sub modules are optional and are provided as example or utility:
223229
- _[download-lambda](./modules/download-lambda/README.md)_ - Utility module to download lambda artifacts from GitHub Release
224230
- _[setup-iam-permissions](./modules/setup-iam-permissions/README.md)_ - Example module to setup permission boundaries
225231

226-
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
227-
## Requirements
232+
### ARM64 configuration for submodules
228233

229-
No requirements.
234+
When not using the top-level module and specifying an `a1` or `*6g*` (6th-gen Graviton2) `instance_type`, the `runner-binaries-syncer` and `runners` submodules need to be configured appropriately for pulling the ARM64 GitHub action runner binary and leveraging the arm64 AMI for the runners.
230235

231-
## Providers
236+
When configuring `runner-binaries-syncer`
232237

233-
| Name | Version |
234-
|------|---------|
235-
| aws | n/a |
236-
| random | n/a |
238+
- _runner_architecture_ - set to `arm64`, defaults to `x64`
237239

240+
When configuring `runners`
241+
242+
- _ami_filter_ - set to `["amzn2-ami-hvm-2*-arm64-gp2"]`, defaults to `["amzn2-ami-hvm-2.*-x86_64-ebs"]`
243+
244+
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
238245
## Inputs
239246

240247
| Name | Description | Type | Default | Required |
241-
|------|-------------|------|---------|:--------:|
242-
| aws\_region | AWS region. | `string` | n/a | yes |
243-
| enable\_organization\_runners | n/a | `bool` | n/a | yes |
244-
| encrypt\_secrets | Encrypt secret variables for lambda's such as secrets and private keys. | `bool` | `true` | no |
245-
| environment | A name that identifies the environment, used as prefix and for tagging. | `string` | n/a | yes |
246-
| github\_app | GitHub app parameters, see your github app. Ensure the key is base64 encoded. | <pre>object({<br> key_base64 = string<br> id = string<br> client_id = string<br> client_secret = string<br> webhook_secret = string<br> })</pre> | n/a | yes |
247-
| instance\_profile\_path | The path that will be added to the instance\_profile, if not set the environment name will be used. | `string` | `null` | no |
248-
| instance\_type | Instance type for the action runner. | `string` | `"m5.large"` | no |
249-
| kms\_key\_id | Custom KMS key to encrypted lambda secrets, if not provided and `encrypt_secrets` = `true` a KMS key will be created by the module. Secrets will be encrypted with a context `Environment = var.environment`. | `string` | `null` | no |
250-
| manage\_kms\_key | Let the module manage the KMS key. | `bool` | `true` | no |
251-
| minimum\_running\_time\_in\_minutes | The time an ec2 action runner should be running at minimum before terminated if non busy. | `number` | `5` | no |
252-
| role\_path | The path that will be added to role path for created roles, if not set the environment name will be used. | `string` | `null` | no |
253-
| role\_permissions\_boundary | Permissions boundary that will be added to the created roles. | `string` | `null` | no |
254-
| runner\_as\_root | Run the action runner under the root user. | `bool` | `false` | no |
255-
| runner\_binaries\_syncer\_lambda\_timeout | Time out of the binaries sync lambda in seconds. | `number` | `300` | no |
256-
| runner\_binaries\_syncer\_lambda\_zip | File location of the binaries sync lambda zip file. | `string` | `null` | no |
257-
| runner\_extra\_labels | Extra labels for the runners (GitHub). Separate each label by a comma | `string` | `""` | no |
258-
| runners\_lambda\_zip | File location of the lambda zip file for scaling runners. | `string` | `null` | no |
259-
| runners\_maximum\_count | The maximum number of runners that will be created. | `number` | `3` | no |
260-
| runners\_scale\_down\_lambda\_timeout | Time out for the scale up lambda in seconds. | `number` | `60` | no |
261-
| runners\_scale\_up\_lambda\_timeout | Time out for the scale down lambda in seconds. | `number` | `60` | no |
262-
| scale\_down\_schedule\_expression | Scheduler expression to check every x for scale down. | `string` | `"cron(*/5 * * * ? *)"` | no |
263-
| subnet\_ids | List of subnets in which the action runners will be launched, the subnets needs to be subnets in the `vpc_id`. | `list(string)` | n/a | yes |
264-
| tags | Map of tags that will be added to created resources. By default resources will be tagged with name and environment. | `map(string)` | `{}` | no |
265-
| userdata\_post\_install | Script to be ran after the GitHub Actions runner is installed on the EC2 instances | `string` | `""` | no |
266-
| userdata\_pre\_install | Script to be ran before the GitHub Actions runner is installed on the EC2 instances | `string` | `""` | no |
267-
| vpc\_id | The VPC for security groups of the action runners. | `string` | n/a | yes |
268-
| webhook\_lambda\_timeout | Time out of the webhook lambda in seconds. | `number` | `10` | no |
269-
| webhook\_lambda\_zip | File location of the webhook lambda zip file. | `string` | `null` | no |
248+
|------|-------------|:----:|:-----:|:-----:|
249+
| aws\_region | AWS region. | string | n/a | yes |
250+
| enable\_organization\_runners | | bool | n/a | yes |
251+
| encrypt\_secrets | Encrypt secret variables for lambda's such as secrets and private keys. | bool | `"true"` | no |
252+
| environment | A name that identifies the environment, used as prefix and for tagging. | string | n/a | yes |
253+
| github\_app | GitHub app parameters, see your github app. Ensure the key is base64 encoded. | object | n/a | yes |
254+
| instance\_profile\_path | The path that will be added to the instance\_profile, if not set the environment name will be used. | string | `"null"` | no |
255+
| instance\_type | Instance type for the action runner. | string | `"m5.large"` | no |
256+
| kms\_key\_id | Custom KMS key to encrypted lambda secrets, if not provided and `encrypt\_secrets` = `true` a KMS key will be created by the module. Secrets will be encrypted with a context `Environment = var.environment`. | string | `"null"` | no |
257+
| manage\_kms\_key | Let the module manage the KMS key. | bool | `"true"` | no |
258+
| minimum\_running\_time\_in\_minutes | The time an ec2 action runner should be running at minimum before terminated if non busy. | number | `"5"` | no |
259+
| role\_path | The path that will be added to role path for created roles, if not set the environment name will be used. | string | `"null"` | no |
260+
| role\_permissions\_boundary | Permissions boundary that will be added to the created roles. | string | `"null"` | no |
261+
| runner\_as\_root | Run the action runner under the root user. | bool | `"false"` | no |
262+
| runner\_binaries\_syncer\_lambda\_timeout | Time out of the binaries sync lambda in seconds. | number | `"300"` | no |
263+
| runner\_binaries\_syncer\_lambda\_zip | File location of the binaries sync lambda zip file. | string | `"null"` | no |
264+
| runner\_extra\_labels | Extra labels for the runners \(GitHub\). Separate each label by a comma | string | `""` | no |
265+
| runners\_lambda\_zip | File location of the lambda zip file for scaling runners. | string | `"null"` | no |
266+
| runners\_maximum\_count | The maximum number of runners that will be created. | number | `"3"` | no |
267+
| runners\_scale\_down\_lambda\_timeout | Time out for the scale up lambda in seconds. | number | `"60"` | no |
268+
| runners\_scale\_up\_lambda\_timeout | Time out for the scale down lambda in seconds. | number | `"60"` | no |
269+
| scale\_down\_schedule\_expression | Scheduler expression to check every x for scale down. | string | `"cron(*/5 * * * ? *)"` | no |
270+
| subnet\_ids | List of subnets in which the action runners will be launched, the subnets needs to be subnets in the `vpc\_id`. | list(string) | n/a | yes |
271+
| tags | Map of tags that will be added to created resources. By default resources will be tagged with name and environment. | map(string) | `{}` | no |
272+
| userdata\_post\_install | Script to be ran after the GitHub Actions runner is installed on the EC2 instances | string | `""` | no |
273+
| userdata\_pre\_install | Script to be ran before the GitHub Actions runner is installed on the EC2 instances | string | `""` | no |
274+
| vpc\_id | The VPC for security groups of the action runners. | string | n/a | yes |
275+
| webhook\_lambda\_timeout | Time out of the webhook lambda in seconds. | number | `"10"` | no |
276+
| webhook\_lambda\_zip | File location of the webhook lambda zip file. | string | `"null"` | no |
270277

271278
## Outputs
272279

273280
| Name | Description |
274281
|------|-------------|
275-
| binaries\_syncer | n/a |
276-
| runners | n/a |
277-
| webhook | n/a |
282+
| binaries\_syncer | |
283+
| runners | |
284+
| webhook | |
278285

279286
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
280287

main.tf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ locals {
44
})
55

66
s3_action_runner_url = "s3://${module.runner_binaries.bucket.id}/${module.runner_binaries.runner_distribution_object_key}"
7+
runner_architecture = substr(var.instance_type, 0, 2) == "a1" || substr(var.instance_type, 1, 2) == "6g" ? "arm64" : "x64"
78
}
89

910
resource "random_string" "random" {
@@ -62,6 +63,9 @@ module "runners" {
6263

6364
instance_type = var.instance_type
6465

66+
runner_architecture = local.runner_architecture
67+
ami_filter = local.runner_architecture == "arm64" ? { name = ["amzn2-ami-hvm-2*-arm64-gp2"] } : { name = ["amzn2-ami-hvm-2.*-x86_64-ebs"] }
68+
6569
sqs_build_queue = aws_sqs_queue.queued_builds
6670
github_app = var.github_app
6771
enable_organization_runners = var.enable_organization_runners
@@ -92,9 +96,12 @@ module "runner_binaries" {
9296

9397
distribution_bucket_name = "${var.environment}-dist-${random_string.random.result}"
9498

99+
runner_architecture = substr(var.instance_type, 0, 2) == "a1" || substr(var.instance_type, 1, 2) == "6g" ? "arm64" : "x64"
100+
95101
lambda_zip = var.runner_binaries_syncer_lambda_zip
96102
lambda_timeout = var.runner_binaries_syncer_lambda_timeout
97103

104+
98105
role_path = var.role_path
99106
role_permissions_boundary = var.role_permissions_boundary
100107
}

modules/download-lambda/README.md

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,17 @@ module "lambdas" {
2525
```
2626

2727
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
28-
## Requirements
29-
30-
No requirements.
31-
32-
## Providers
33-
34-
| Name | Version |
35-
|------|---------|
36-
| null | n/a |
37-
3828
## Inputs
3929

4030
| Name | Description | Type | Default | Required |
41-
|------|-------------|------|---------|:--------:|
42-
| lambdas | Name and tag for lambdas to download. | <pre>list(object({<br> name = string<br> tag = string<br> }))</pre> | n/a | yes |
31+
|------|-------------|:----:|:-----:|:-----:|
32+
| lambdas | Name and tag for lambdas to download. | object | n/a | yes |
4333

4434
## Outputs
4535

4636
| Name | Description |
4737
|------|-------------|
48-
| files | n/a |
38+
| files | |
4939

5040
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
5141

modules/runner-binaries-syncer/README.md

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,38 +34,29 @@ yarn run dist
3434
```
3535

3636
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
37-
## Requirements
38-
39-
No requirements.
40-
41-
## Providers
42-
43-
| Name | Version |
44-
|------|---------|
45-
| aws | n/a |
46-
4737
## Inputs
4838

4939
| Name | Description | Type | Default | Required |
50-
|------|-------------|------|---------|:--------:|
51-
| aws\_region | AWS region. | `string` | n/a | yes |
52-
| distribution\_bucket\_name | Bucket for storing the action runner distribution. | `string` | n/a | yes |
53-
| environment | A name that identifies the environment, used as prefix and for tagging. | `string` | n/a | yes |
54-
| lambda\_schedule\_expression | Scheduler expression for action runner binary syncer. | `string` | `"cron(27 * * * ? *)"` | no |
55-
| lambda\_timeout | Time out of the lambda in seconds. | `number` | `300` | no |
56-
| lambda\_zip | File location of the lambda zip file. | `string` | `null` | no |
57-
| role\_path | The path that will be added to the role, if not set the environment name will be used. | `string` | `null` | no |
58-
| role\_permissions\_boundary | Permissions boundary that will be added to the created role for the lambda. | `string` | `null` | no |
59-
| tags | Map of tags that will be added to created resources. By default resources will be tagged with name and environment. | `map(string)` | `{}` | no |
40+
|------|-------------|:----:|:-----:|:-----:|
41+
| aws\_region | AWS region. | string | n/a | yes |
42+
| distribution\_bucket\_name | Bucket for storing the action runner distribution. | string | n/a | yes |
43+
| environment | A name that identifies the environment, used as prefix and for tagging. | string | n/a | yes |
44+
| lambda\_schedule\_expression | Scheduler expression for action runner binary syncer. | string | `"cron(27 * * * ? *)"` | no |
45+
| lambda\_timeout | Time out of the lambda in seconds. | number | `"300"` | no |
46+
| lambda\_zip | File location of the lambda zip file. | string | `"null"` | no |
47+
| role\_path | The path that will be added to the role, if not set the environment name will be used. | string | `"null"` | no |
48+
| role\_permissions\_boundary | Permissions boundary that will be added to the created role for the lambda. | string | `"null"` | no |
49+
| runner\_architecture | The platform architecture for the runner instance \(x64, arm64\), defaults to 'x64' | string | `"x64"` | no |
50+
| tags | Map of tags that will be added to created resources. By default resources will be tagged with name and environment. | map(string) | `{}` | no |
6051

6152
## Outputs
6253

6354
| Name | Description |
6455
|------|-------------|
65-
| bucket | n/a |
66-
| lambda | n/a |
67-
| lambda\_role | n/a |
68-
| runner\_distribution\_object\_key | n/a |
56+
| bucket | |
57+
| lambda | |
58+
| lambda\_role | |
59+
| runner\_distribution\_object\_key | |
6960

7061
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
7162

modules/runner-binaries-syncer/lambdas/runner-binaries-syncer/src/syncer/handler.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { handle } from './handler';
22
import latestReleases from '../../test/resources/github-latest-releases.json';
33
import latestReleasesEmpty from '../../test/resources/github-latest-releases-empty.json';
44
import latestReleasesNoLinux from '../../test/resources/github-latest-releases-no-linux.json';
5+
import latestReleasesNoArm64 from '../../test/resources/github-latest-releases-no-arm64.json';
56

67
const mockOctokit = {
78
repos: {
@@ -151,3 +152,20 @@ describe('Invalid config', () => {
151152
await expect(handle()).rejects.toThrow(errorMessage);
152153
});
153154
});
155+
156+
describe('Synchronize action distribution for arm64.', () => {
157+
const errorMessage = 'Cannot find GitHub release asset.';
158+
beforeEach(() => {
159+
process.env.S3_BUCKET_NAME = bucketName;
160+
process.env.S3_OBJECT_KEY = bucketObjectKey;
161+
process.env.GITHUB_RUNNER_ARCHITECTURE = 'arm64';
162+
});
163+
164+
it('No linux arm64 asset.', async () => {
165+
mockOctokit.repos.getLatestRelease.mockImplementation(() => ({
166+
data: latestReleasesNoArm64.data,
167+
}));
168+
169+
await expect(handle()).rejects.toThrow(errorMessage);
170+
});
171+
});

modules/runner-binaries-syncer/lambdas/runner-binaries-syncer/src/syncer/handler.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ interface ReleaseAsset {
3131
downloadUrl: string;
3232
}
3333

34-
async function getLinuxReleaseAsset(): Promise<ReleaseAsset | undefined> {
34+
async function getLinuxReleaseAsset(runnerArch = 'x64'): Promise<ReleaseAsset | undefined> {
3535
const githubClient = new Octokit();
3636
const assets = await githubClient.repos.getLatestRelease({
3737
owner: 'actions',
3838
repo: 'runner',
3939
});
40-
const linuxAssets = assets.data.assets?.filter((a) => a.name?.includes('actions-runner-linux-x64-'));
40+
const linuxAssets = assets.data.assets?.filter((a) => a.name?.includes(`actions-runner-linux-${runnerArch}-`));
4141

4242
return linuxAssets?.length === 1
4343
? { name: linuxAssets[0].name, downloadUrl: linuxAssets[0].browser_download_url }
@@ -73,6 +73,8 @@ async function uploadToS3(s3: S3, cacheObject: CacheObject, actionRunnerReleaseA
7373
export const handle = async (): Promise<void> => {
7474
const s3 = new AWS.S3();
7575

76+
const runnerArch = process.env.GITHUB_RUNNER_ARCHITECTURE || 'x64'
77+
7678
const cacheObject: CacheObject = {
7779
bucket: process.env.S3_BUCKET_NAME as string,
7880
key: process.env.S3_OBJECT_KEY as string,
@@ -81,7 +83,7 @@ export const handle = async (): Promise<void> => {
8183
throw Error('Please check all mandatory variables are set.');
8284
}
8385

84-
const actionRunnerReleaseAsset = await getLinuxReleaseAsset();
86+
const actionRunnerReleaseAsset = await getLinuxReleaseAsset(runnerArch);
8587
if (actionRunnerReleaseAsset === undefined) {
8688
throw Error('Cannot find GitHub release asset.');
8789
}

0 commit comments

Comments
 (0)