Skip to content

Add support for ARM64 runners #102

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 47 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ Permission are managed on several places. Below the most important ones. For det

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.

### ARM64 support via Graviton/Graviton2 instance-types

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.

## Usages

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

**ARM64** support: Specify an `a1` or `*6g*` (6th-gen Graviton2) instance type to stand up an ARM64 runner, otherwise the default is x86_64.

2. Run terraform by using the following commands

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

<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Requirements
### ARM64 configuration for submodules

No requirements.
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.

## Providers
When configuring `runner-binaries-syncer`

| Name | Version |
|------|---------|
| aws | n/a |
| random | n/a |
- _runner_architecture_ - set to `arm64`, defaults to `x64`

When configuring `runners`

- _ami_filter_ - set to `["amzn2-ami-hvm-2*-arm64-gp2"]`, defaults to `["amzn2-ami-hvm-2.*-x86_64-ebs"]`

<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| aws\_region | AWS region. | `string` | n/a | yes |
| enable\_organization\_runners | n/a | `bool` | n/a | yes |
| encrypt\_secrets | Encrypt secret variables for lambda's such as secrets and private keys. | `bool` | `true` | no |
| environment | A name that identifies the environment, used as prefix and for tagging. | `string` | n/a | yes |
| 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 |
| 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 |
| instance\_type | Instance type for the action runner. | `string` | `"m5.large"` | no |
| 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 |
| manage\_kms\_key | Let the module manage the KMS key. | `bool` | `true` | no |
| minimum\_running\_time\_in\_minutes | The time an ec2 action runner should be running at minimum before terminated if non busy. | `number` | `5` | no |
| 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 |
| role\_permissions\_boundary | Permissions boundary that will be added to the created roles. | `string` | `null` | no |
| runner\_as\_root | Run the action runner under the root user. | `bool` | `false` | no |
| runner\_binaries\_syncer\_lambda\_timeout | Time out of the binaries sync lambda in seconds. | `number` | `300` | no |
| runner\_binaries\_syncer\_lambda\_zip | File location of the binaries sync lambda zip file. | `string` | `null` | no |
| runner\_extra\_labels | Extra labels for the runners (GitHub). Separate each label by a comma | `string` | `""` | no |
| runners\_lambda\_zip | File location of the lambda zip file for scaling runners. | `string` | `null` | no |
| runners\_maximum\_count | The maximum number of runners that will be created. | `number` | `3` | no |
| runners\_scale\_down\_lambda\_timeout | Time out for the scale up lambda in seconds. | `number` | `60` | no |
| runners\_scale\_up\_lambda\_timeout | Time out for the scale down lambda in seconds. | `number` | `60` | no |
| scale\_down\_schedule\_expression | Scheduler expression to check every x for scale down. | `string` | `"cron(*/5 * * * ? *)"` | no |
| 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 |
| tags | Map of tags that will be added to created resources. By default resources will be tagged with name and environment. | `map(string)` | `{}` | no |
| userdata\_post\_install | Script to be ran after the GitHub Actions runner is installed on the EC2 instances | `string` | `""` | no |
| userdata\_pre\_install | Script to be ran before the GitHub Actions runner is installed on the EC2 instances | `string` | `""` | no |
| vpc\_id | The VPC for security groups of the action runners. | `string` | n/a | yes |
| webhook\_lambda\_timeout | Time out of the webhook lambda in seconds. | `number` | `10` | no |
| webhook\_lambda\_zip | File location of the webhook lambda zip file. | `string` | `null` | no |
|------|-------------|:----:|:-----:|:-----:|
| aws\_region | AWS region. | string | n/a | yes |
| enable\_organization\_runners | | bool | n/a | yes |
| encrypt\_secrets | Encrypt secret variables for lambda's such as secrets and private keys. | bool | `"true"` | no |
| environment | A name that identifies the environment, used as prefix and for tagging. | string | n/a | yes |
| github\_app | GitHub app parameters, see your github app. Ensure the key is base64 encoded. | object | n/a | yes |
| 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 |
| instance\_type | Instance type for the action runner. | string | `"m5.large"` | no |
| 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 |
| manage\_kms\_key | Let the module manage the KMS key. | bool | `"true"` | no |
| minimum\_running\_time\_in\_minutes | The time an ec2 action runner should be running at minimum before terminated if non busy. | number | `"5"` | no |
| 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 |
| role\_permissions\_boundary | Permissions boundary that will be added to the created roles. | string | `"null"` | no |
| runner\_as\_root | Run the action runner under the root user. | bool | `"false"` | no |
| runner\_binaries\_syncer\_lambda\_timeout | Time out of the binaries sync lambda in seconds. | number | `"300"` | no |
| runner\_binaries\_syncer\_lambda\_zip | File location of the binaries sync lambda zip file. | string | `"null"` | no |
| runner\_extra\_labels | Extra labels for the runners \(GitHub\). Separate each label by a comma | string | `""` | no |
| runners\_lambda\_zip | File location of the lambda zip file for scaling runners. | string | `"null"` | no |
| runners\_maximum\_count | The maximum number of runners that will be created. | number | `"3"` | no |
| runners\_scale\_down\_lambda\_timeout | Time out for the scale up lambda in seconds. | number | `"60"` | no |
| runners\_scale\_up\_lambda\_timeout | Time out for the scale down lambda in seconds. | number | `"60"` | no |
| scale\_down\_schedule\_expression | Scheduler expression to check every x for scale down. | string | `"cron(*/5 * * * ? *)"` | no |
| 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 |
| tags | Map of tags that will be added to created resources. By default resources will be tagged with name and environment. | map(string) | `{}` | no |
| userdata\_post\_install | Script to be ran after the GitHub Actions runner is installed on the EC2 instances | string | `""` | no |
| userdata\_pre\_install | Script to be ran before the GitHub Actions runner is installed on the EC2 instances | string | `""` | no |
| vpc\_id | The VPC for security groups of the action runners. | string | n/a | yes |
| webhook\_lambda\_timeout | Time out of the webhook lambda in seconds. | number | `"10"` | no |
| webhook\_lambda\_zip | File location of the webhook lambda zip file. | string | `"null"` | no |

## Outputs

| Name | Description |
|------|-------------|
| binaries\_syncer | n/a |
| runners | n/a |
| webhook | n/a |
| binaries\_syncer | |
| runners | |
| webhook | |

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

Expand Down
7 changes: 7 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ locals {
})

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

resource "random_string" "random" {
Expand Down Expand Up @@ -62,6 +63,9 @@ module "runners" {

instance_type = var.instance_type

runner_architecture = local.runner_architecture
ami_filter = local.runner_architecture == "arm64" ? { name = ["amzn2-ami-hvm-2*-arm64-gp2"] } : { name = ["amzn2-ami-hvm-2.*-x86_64-ebs"] }

sqs_build_queue = aws_sqs_queue.queued_builds
github_app = var.github_app
enable_organization_runners = var.enable_organization_runners
Expand Down Expand Up @@ -92,9 +96,12 @@ module "runner_binaries" {

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

runner_architecture = substr(var.instance_type, 0, 2) == "a1" || substr(var.instance_type, 1, 2) == "6g" ? "arm64" : "x64"

lambda_zip = var.runner_binaries_syncer_lambda_zip
lambda_timeout = var.runner_binaries_syncer_lambda_timeout


role_path = var.role_path
role_permissions_boundary = var.role_permissions_boundary
}
Expand Down
16 changes: 3 additions & 13 deletions modules/download-lambda/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,17 @@ module "lambdas" {
```

<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Requirements

No requirements.

## Providers

| Name | Version |
|------|---------|
| null | n/a |

## Inputs

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

## Outputs

| Name | Description |
|------|-------------|
| files | n/a |
| files | |

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

Expand Down
39 changes: 15 additions & 24 deletions modules/runner-binaries-syncer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,38 +34,29 @@ yarn run dist
```

<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Requirements

No requirements.

## Providers

| Name | Version |
|------|---------|
| aws | n/a |

## Inputs

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

## Outputs

| Name | Description |
|------|-------------|
| bucket | n/a |
| lambda | n/a |
| lambda\_role | n/a |
| runner\_distribution\_object\_key | n/a |
| bucket | |
| lambda | |
| lambda\_role | |
| runner\_distribution\_object\_key | |

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { handle } from './handler';
import latestReleases from '../../test/resources/github-latest-releases.json';
import latestReleasesEmpty from '../../test/resources/github-latest-releases-empty.json';
import latestReleasesNoLinux from '../../test/resources/github-latest-releases-no-linux.json';
import latestReleasesNoArm64 from '../../test/resources/github-latest-releases-no-arm64.json';

const mockOctokit = {
repos: {
Expand Down Expand Up @@ -151,3 +152,20 @@ describe('Invalid config', () => {
await expect(handle()).rejects.toThrow(errorMessage);
});
});

describe('Synchronize action distribution for arm64.', () => {
const errorMessage = 'Cannot find GitHub release asset.';
beforeEach(() => {
process.env.S3_BUCKET_NAME = bucketName;
process.env.S3_OBJECT_KEY = bucketObjectKey;
process.env.GITHUB_RUNNER_ARCHITECTURE = 'arm64';
});

it('No linux arm64 asset.', async () => {
mockOctokit.repos.getLatestRelease.mockImplementation(() => ({
data: latestReleasesNoArm64.data,
}));

await expect(handle()).rejects.toThrow(errorMessage);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ interface ReleaseAsset {
downloadUrl: string;
}

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

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

const runnerArch = process.env.GITHUB_RUNNER_ARCHITECTURE || 'x64'

const cacheObject: CacheObject = {
bucket: process.env.S3_BUCKET_NAME as string,
key: process.env.S3_OBJECT_KEY as string,
Expand All @@ -81,7 +83,7 @@ export const handle = async (): Promise<void> => {
throw Error('Please check all mandatory variables are set.');
}

const actionRunnerReleaseAsset = await getLinuxReleaseAsset();
const actionRunnerReleaseAsset = await getLinuxReleaseAsset(runnerArch);
if (actionRunnerReleaseAsset === undefined) {
throw Error('Cannot find GitHub release asset.');
}
Expand Down
Loading