Skip to content

Commit

Permalink
RFC: Refresh ARM64 guide (#1480)
Browse files Browse the repository at this point in the history
* RFC: Refresh ARM64 guide

Follows on from #1350.

* Do not imply vCurrent is ARM-exclusive

Co-authored-by: Aaron Moat <amoat@seek.com.au>

* Add a changeset

---------

Co-authored-by: Aaron Moat <amoat@seek.com.au>
  • Loading branch information
72636c and AaronMoat committed Mar 18, 2024
1 parent f137372 commit ef04794
Show file tree
Hide file tree
Showing 3 changed files with 294 additions and 24 deletions.
2 changes: 1 addition & 1 deletion .changeset/fifty-toys-destroy.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Previously, the built-in templates made use of [`BUILDPLATFORM`](https://docs.do
FROM --platform=${BUILDPLATFORM:-arm64} gcr.io/distroless/nodejs20-debian11
```

1. Choose the platform of the host machine running the Docker build. A vCurrent SEEK build agent or Apple Silicon laptop will build under `arm64`, while an Intel laptop will build under `amd64`.
1. Choose the platform of the host machine running the Docker build. An [AWS Graviton](https://aws.amazon.com/ec2/graviton/) Buildkite agent or Apple Silicon laptop will build under `arm64`, while an Intel laptop will build under `amd64`.
2. Fall back to `arm64` if the build platform is not available. This maintains compatibility with toolchains like Gantry that lack support for the `BUILDPLATFORM` argument.

This approach allowed you to quickly build images and run containers in a local environment without emulation. For example, you could `docker build` an `arm64` image on an Apple Silicon laptop for local troubleshooting, while your CI/CD solution employed `amd64` hardware across its build and runtime environments. The catch is that your local `arm64` image may exhibit different behaviour, and is unsuitable for use in your `amd64` runtime environment without cross-compilation.
Expand Down
7 changes: 7 additions & 0 deletions .changeset/soft-laws-double.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'skuba': patch
---

docs: Refresh ARM64 guide

This is not a change to skuba but rather a shameless plug for our deep-dive documentation. The [ARM64 guide](https://seek-oss.github.io/skuba/docs/deep-dives/arm64.html#migrating-an-existing-project) was just refreshed to prepare for upcoming migrations, and we also have [one for pnpm](https://seek-oss.github.io/skuba/docs/deep-dives/pnpm.html).
309 changes: 286 additions & 23 deletions docs/deep-dives/arm64.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ parent: Deep dives
**skuba** templates are configured to be built and run on ARM64 hardware in [CI/CD].

This topic provides some context around this decision,
and discusses how you can [get ready for ARM64](#getting-ready-for-arm64) or [revert to AMD64](#reverting-to-amd64).
then discusses how you can [get ready for ARM64](#getting-ready-for-arm64) and adopt it across [new](#creating-a-new-project) and [existing](#migrating-an-existing-project) projects.

---

Expand All @@ -27,45 +27,161 @@ On AWS' cloud platform, this means [Graviton-based] instances rather than Intel-
## Getting ready for ARM64

If you'd like to build and run your projects on ARM64 hardware,
specify a Buildkite cluster with a Graviton-based instance type.
This is now the default for `vCurrent` strategies:
create a Buildkite cluster with a Graviton-based instance type.
In a `vCurrent` strategy:

```yaml
```diff
schemaVersion: vCurrent
clusters:
- name: cicd # Existing cluster

instanceType: t3.large
rootVolumeSize: 8

# ...
+
+ - name: graviton # New cluster; choose a name you like
+
+ cpuArchitecture: arm64
+ instanceType: t4g.large # Required; g is for Graviton
+ rootVolumeSize: 8 # Optional
+
+ # ...
```

Repeat this process for all accounts and strategies under your remit,
taking care to right-size instances and volumes in the process.
If the existing cluster is already optimised,
simply map `instanceType` to the Graviton equivalent and reuse `rootVolumeSize`.

This approach allows you to gradually migrate existing projects over to the new clusters,
then delete the original clusters once complete:

```diff
schemaVersion: vCurrent
clusters:
- # Take care to right-size instances and disk size
instanceType: t4g.large # Optional; g is for Graviton
rootVolumeSize: 8 # Optional
- - name: cicd
-
- instanceType: t3.large
- rootVolumeSize: 8
-
- # ...
-
- name: graviton

cpuArchitecture: arm64
instanceType: t4g.large
rootVolumeSize: 8

# ...
```

See [Builds at SEEK] and the [Gantry ARM reference] for more information.

---

## Reverting to AMD64
## Creating a new project

### Defaulting to ARM64

Let's start by following the [`skuba init`] documentation:

```shell
skuba init
```

and reaching the starter questions:

```shell
? For starters, some project details:
⊙ Owner : SEEK-Jobs/my-team
⊙ Repo : my-repo
⊙ Platform : arm64
⊙ Default Branch : main

# ...
```

Note that new projects default to the `arm64` platform;
leave this as is.

Continue to follow the prompts.
Your Buildkite pipeline should point to the new cluster(s) configured [above](#getting-ready-for-arm64).
After initialising your project,
review the `agents.queue`s in `.buildkite/pipeline.yml`:

```yaml
agents:
queue: my-prod-account:graviton # Should be the new name you chose above

steps:
- label: Prod

- label: Dev
agents:
queue: my-dev-account:graviton # Should be the new name you chose above
```

At this point, your new project is ready for ARM64.

If you are not ready to build and run your projects on ARM64 hardware,
you can downgrade a templated project to be compatible with AMD64 hardware.
### Reverting to AMD64

Replace the relevant `--platform` values in your Dockerfile(s),
then ensure that you run your builds on AMD64 hardware:
If you later realise that you are not ready to build and run on ARM64 hardware,
you can revert your project to be compatible with AMD64 hardware.

Point your `agents.queue`s back to the original cluster(s) in `pipeline.yml`:

```diff
- FROM --platform=arm64 gcr.io/distroless/nodejs20-debian12 AS runtime
+ FROM --platform=amd64 gcr.io/distroless/nodejs20-debian12 AS runtime
agents:
- queue: my-prod-account:graviton
+ queue: my-prod-account:cicd

steps:
- label: Prod

- label: Dev
agents:
- queue: my-dev-account:graviton
+ queue: my-dev-account:cicd
```

Replace the relevant `--platform` flags in your Dockerfile(s):

```diff
- FROM --platform=arm64 node:20-alpine AS dev-deps
+ FROM --platform=amd64 node:20-alpine AS dev-deps

- FROM --platform=arm64 gcr.io/distroless/nodejs20-debian12 AS runtime
+ FROM --platform=amd64 gcr.io/distroless/nodejs20-debian12 AS runtime
```

For a [Gantry] service, modify the `cpuArchitecture` property in your `gantry.build.yml` and `gantry.apply.yml` resource files:
For a [Gantry] service,
modify `cpuArchitecture` property on the `ContainerImage` and `Service` resources in `gantry.build.yml` and `gantry.apply.yml`:

```diff
kind: ContainerImage

schemaVersion: v0.0

- cpuArchitecture: arm64
+ cpuArchitecture: amd64

...
```

For an [AWS CDK] worker, modify the `architecture` property in your `infra/appStack.ts` file:
```diff
kind: Service

schemaVersion: v0.0

- cpuArchitecture: arm64
+ cpuArchitecture: amd64

...
```

For an [AWS CDK] worker,
modify the `architecture` property on the Lambda function resource in `infra/appStack.ts`:

```diff
const worker = new aws_lambda.Function(this, 'worker', {
Expand All @@ -74,18 +190,165 @@ const worker = new aws_lambda.Function(this, 'worker', {
});
```

For a [Serverless] worker, modify the `provider.architecture` property in your `serverless.yml` file:
For a [Serverless] worker,
modify the `provider.architecture` property in `serverless.yml`:

```diff
provider:
- architecture: arm64
+ architecture: x86_64
```

[aws cdk]: https://docs.aws.amazon.com/cdk/latest/guide/work-with-cdk-typescript.html
[builds at seek]: https://backstage.myseek.xyz/docs/default/component/builds-cicd-seek/
[ci/cd]: ./buildkite.md
---

## Migrating an existing project

This guide is targeted at existing TypeScript projects that are looking to migrate from AMD64 to ARM64.

In your Buildkite pipeline(s),
point your `agents.queue`s to the new cluster(s) you configured [above](#getting-ready-for-arm64).
Your default pipeline should be defined in [`.buildkite/pipeline.yml`],
though you may have auxiliary pipelines under a similar or nested directory.

```diff
agents:
- queue: my-prod-account:cicd
+ queue: my-prod-account:graviton # Should be the new name you chose above

steps:
- label: Prod

- label: Dev
agents:
- queue: my-dev-account:cicd
+ queue: my-dev-account:graviton # Should be the new name you chose above
```

Set the `--platform=arm64` flag on each external [`FROM`] command in your Dockerfile(s):

```diff
- FROM node:20-alpine AS dev-deps
+ FROM --platform=arm64 node:20-alpine AS dev-deps

# ...

- FROM gcr.io/distroless/nodejs20-debian12 AS runtime
+ FROM --platform=arm64 gcr.io/distroless/nodejs20-debian12 AS runtime
```

Review and test the behaviour of your project dependencies that use platform-specific binaries.
Common examples include:

- Browser-adjacent packages like `puppeteer`
- Cryptographic packages like `bcrypt`
- Low-level tooling like `esbuild`

### Gantry

For a [Gantry] service, first locate your Gantry resource files.
As these have no set naming convention, you can look for:

- YAML files that match the following common patterns:

- `.gantry/**/*.{yaml,yml}`
- `gantry.{yaml,yml}`
- `gantry.apply.yml`
- `gantry.build.yml`
- `service.{yaml,yml}`

- `file`s supplied to the Gantry plugin in your Buildkite pipeline:

```yaml
steps:
- label: 📦 Build & Package
plugins:
- *aws-sm
- *private-npm
- *docker-ecr-cache
- seek-jobs/gantry#v3.0.0:
command: build
file: gantry.build.yml # <-- here
region: ap-southeast-2
values: .gantry/common.yml
```

Once you have located these files,
set the `cpuArchitecture` property on the `ContainerImage` and `Service` resources:

```diff
kind: ContainerImage

schemaVersion: v0.0

+ cpuArchitecture: arm64

...
```

```diff
kind: Service

schemaVersion: v0.0

+ cpuArchitecture: arm64

...
```

### AWS CDK

For an [AWS CDK] worker, first locate your application stack.
This is a TypeScript source file that may be named similar to `infra/appStack.ts` and contains a class that extends `Stack`:

```typescript
export class AppStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
// ...
}
}
```

Once you have located this file,
set the `architecture` property on the Lambda function resource:

```diff
const worker = new aws_lambda.Function(this, 'worker', {
+ architecture: aws_lambda.Architecture.ARM_64,
runtime: aws_lambda.Runtime.NODEJS_20_X,
// ...
});
```

```diff
const worker = new aws_lambda_nodejs.NodejsFunction(this, 'worker', {
+ architecture: aws_lambda.Architecture.ARM_64,
runtime: aws_lambda.Runtime.NODEJS_20_X,
// ...
});
```

### Serverless

For a [Serverless] worker, set the `provider.architecture` property in [`serverless.yml`]:

```diff
provider:
name: aws

+ architecture: arm64
runtime: nodejs20.x

...
```

[`.buildkite/pipeline.yml`]: https://buildkite.com/docs/pipelines/defining-steps#customizing-the-pipeline-upload-path
[`FROM`]: https://docs.docker.com/reference/dockerfile/#from
[`serverless.yml`]: https://www.serverless.com/framework/docs/providers/aws/guide/serverless.yml
[`skuba init`]: ../cli/init.md#interactive-walkthrough
[AWS CDK]: https://docs.aws.amazon.com/cdk/latest/guide/work-with-cdk-typescript.html
[Builds at SEEK]: https://backstage.myseek.xyz/docs/default/component/builds-cicd-seek/
[CI/CD]: ./buildkite.md
[Gantry ARM reference]: https://backstage.myseek.xyz/docs/default/component/gantry/v1/reference/using-arm/
[gantry]: https://backstage.myseek.xyz/docs/default/component/gantry/
[gantry arm reference]: https://backstage.myseek.xyz/docs/default/component/gantry/v1/reference/using-arm/
[graviton-based]: https://aws.amazon.com/ec2/graviton/
[serverless]: https://serverless.com/
[Graviton-based]: https://aws.amazon.com/ec2/graviton/
[Serverless]: https://serverless.com/

0 comments on commit ef04794

Please sign in to comment.