Skip to content
This repository has been archived by the owner on Oct 23, 2023. It is now read-only.

Commit

Permalink
Leverage ephemeral runners by default (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
jpalomaki authored Sep 26, 2021
1 parent c21dad8 commit 8bb2976
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 27 deletions.
88 changes: 72 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,32 @@ Inspired by <https://github.com/machulav/ec2-github-runner>
## Requirements

- AWS account and VPC network
- A default VPC works fine, too
- AWS credentials with EC2 permissions
- VPC subnet with Internet access (either assigning public IP or via NAT gateway)
- You can use either a plain IAM user, or assume a role
- VPC subnet with Internet access
- Public subnet (public IP) **or**
- Private subnet with NAT gateway
- Linux runner AMI (amd64 or arm64), with the following things pre-configured:
- [Actions-runner](https://github.com/actions/runner) and its [dependencies](https://github.com/actions/runner/blob/main/docs/start/envlinux.md)
- Non-root user to run actions-runner service as
- `git`, `docker`, `curl` and optionally `at` (if using the auto-shutdown feature)
- See e.g. <https://github.com/superblk/ec2-actions-runner-ami-linux-arm64>
- EC2 launch template (AMI, instance type, VPC, security group, spot options etc)
- [Actions-runner](https://github.com/actions/runner) v2.283.1+ and required [dependencies](https://github.com/actions/runner/blob/main/docs/start/envlinux.md)
- `git`, `docker`, `curl` and optionally `at` (if using the `auto-shutdown-at` feature)
- See e.g. <https://github.com/superblk/ec2-actions-runner-ami-linux-arm64> for an example AMI build
- EC2 launch template (AMI, instance type, VPC subnet, security groups, spot options etc)
- See example [Cloudformation template](https://gist.github.com/jpalomaki/003c4d173a856cf64c6d35f8869a2de8) that sets up a launch template
- GitHub personal access token (PAT) with `repo` scope

## Example workflow

See [start/action.yml](start/action.yml) and [stop/action.yml](stop/action.yml) for all available input parameters.

:warning: do not copy this example verbatim, but adjust action version, AWS region, launch template etc to match your config
## Example workflows

💡 EC2 instance ID is automatically assigned as a unique, self-hosted runner label

⚠️ Do not simply copy these examples verbatim, but adjust action version, AWS region, launch template name etc to match your config

### Simple

Simple default. Leverages ephemeral runners that are automatically deregistered from GitHub after the `main` job has run.

```yaml
jobs:
Expand All @@ -38,27 +48,73 @@ jobs:
aws-region: eu-north-1
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-launch-template: LaunchTemplateName=my-arm64-runner
aws-launch-template: LaunchTemplateName=my-special-runner
github-token: ${{ secrets.GH_PAT }}
runner-labels: ubuntu-18.04-arm64
outputs:
runner-id: ${{ steps.runner.outputs.runner-id }}
instance-id: ${{ steps.runner.outputs.instance-id }}

complex-build:
main:
needs: start-runner
runs-on: ${{ needs.start-runner.outputs.instance-id }}
steps:
- run: uname -a

stop-runner:
if: always()
needs: [start-runner, main]
runs-on: ubuntu-20.04
steps:
- name: Stop runner
uses: superblk/ec2-actions-runner/stop@<release>
with:
aws-region: eu-north-1
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
instance-id: ${{ needs.start-runner.outputs.instance-id }}
```
### Advanced
A more fail-safe alternative. Deregisters GitHub runner explicitly (not relying on ephemeral runner auto-deregistration behavior alone). Also leverages EC2 [instance-initiated shutdown](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/terminating-instances.html#Using_ChangingInstanceInitiatedShutdownBehavior) **terminate** behavior for ensuring the EC2 instance is terminated, even if the `stop-runner` job fails to run.

⚠️ For the automatic dead-man's switch termination to work, the AMI must include the `at` tool, and the EC2 launch template must specify instance-initiated shutdown behavior as **terminate**.

💡 This example also illustrates the use of extra runner labels and a matrix `main` job, that uses both GitHub-hosted and self-hosted runners.

```yaml
jobs:
start-runner:
runs-on: ubuntu-20.04
steps:
- id: runner
name: Start runner
uses: superblk/ec2-actions-runner/start@<release>
with:
aws-region: eu-north-1
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-launch-template: LaunchTemplateName=my-special-runner
runner-labels: ubuntu-18.04-arm64-${{ github.run_id }}
github-token: ${{ secrets.GH_PAT }}
auto-shutdown-at: 'now + 3 hours'
outputs:
instance-id: ${{ steps.runner.outputs.instance-id }}
runner-id: ${{ steps.runner.outputs.runner-id }}
main:
needs: start-runner
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- runner: ubuntu-18.04
- runner: ubuntu-18.04-arm64
- runner: ubuntu-18.04-arm64-${{ github.run_id }}
steps:
- run: uname -a
stop-runner:
if: always()
needs: [start-runner, complex-build]
needs: [start-runner, main]
runs-on: ubuntu-20.04
steps:
- name: Stop runner
Expand All @@ -67,7 +123,7 @@ jobs:
aws-region: eu-north-1
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
github-token: ${{ secrets.GH_PAT }}
runner-id: ${{ needs.start-runner.outputs.runner-id }}
instance-id: ${{ needs.start-runner.outputs.instance-id }}
runner-id: ${{ needs.start-runner.outputs.runner-id }}
github-token: ${{ secrets.GH_PAT }}
```
17 changes: 15 additions & 2 deletions start/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ inputs:
default: ${{ github.repository }}
required: false
runner-labels:
description: Extra runner labels (comma-separated)
description: Extra runner labels (comma-separated). Can be referenced in job 'runs-on'
required: false
runner-home:
description: Directory that contains actions-runner software and scripts
Expand All @@ -39,6 +39,10 @@ inputs:
auto-shutdown-at:
description: Automatically shutdown instance at the specified time?. E.g. 'now + 72 hours'. Requires https://linux.die.net/man/1/at
required: false
ephemeral:
description: Flag the runner as ephemeral? An ephemeral runner is automatically de-registered after running _one_ workflow job
required: false
default: true

outputs:
runner-id:
Expand Down Expand Up @@ -67,11 +71,19 @@ runs:
run: |
runner_token="$(gh api -X POST -H 'Accept: application/vnd.github.v3+json' "repos/$GITHUB_REPO/actions/runners/registration-token" | jq -r .token)"
if [ "$EPHEMERAL" = "true" ]; then
extra_flags="--ephemeral"
fi
if [ ! -z "$RUNNER_LABELS" ]; then
extra_labels=",$RUNNER_LABELS"
fi
user_data=$(cat <<HERE
#!/bin/bash
cd "$RUNNER_HOME"
instance_id="\$(curl -s http://169.254.169.254/latest/meta-data/instance-id)"
sudo -u "$RUNNER_USER" ./config.sh --unattended --name "\$instance_id" --url "https://github.com/$GITHUB_REPO" --token "$runner_token" --labels "$RUNNER_LABELS"
sudo -u "$RUNNER_USER" ./config.sh --unattended $extra_flags --name "\$instance_id" --url "https://github.com/$GITHUB_REPO" --token "$runner_token" --labels "\$instance_id$extra_labels"
./svc.sh install "$RUNNER_USER"
./svc.sh start
test -z "$SHUTDOWN_AT" || echo 'shutdown -P now' | at -M "$SHUTDOWN_AT"
Expand Down Expand Up @@ -115,3 +127,4 @@ runs:
RUNNER_USER: ${{ inputs.runner-user }}
RUNNER_LABELS: ${{ inputs.runner-labels }}
SHUTDOWN_AT: ${{ inputs.auto-shutdown-at }}
EPHEMERAL: ${{ inputs.ephemeral }}
15 changes: 6 additions & 9 deletions stop/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ inputs:
description: AWS IAM role to assume (optional)
required: false
github-token:
description: GitHub auth token (PAT with repo scope, pass via GitHub secret)
required: true
description: GitHub auth token (PAT with repo scope, pass via GitHub secret). Optional if using ephemeral runners
required: false
github-repo:
description: Github repository, e.g. ghost/example. Defaults to current repository
description: GitHub repository, e.g. ghost/example. Optional, defaults to the repository where the workflow is run
default: ${{ github.repository }}
required: false
runner-id:
description: GitHub repository runner id, e.g. 53
required: true
description: GitHub repository runner ID, e.g. 53. Optional if using ephemeral runners
required: false
instance-id:
description: EC2 instance id, e.g. i-0ab3a789090bd84c7
required: true
Expand All @@ -46,10 +46,7 @@ runs:
- id: main
shell: bash
run: |
# Offline runners are automatically deleted after 30 days, so we emit a warning for runner deregistration failure
if [ -z "$RUNNER_ID" ]; then
echo "WARN: Unable to deregister GitHub repository runner (runner ID not available)"
else
if [ ! -z "$RUNNER_ID" ]; then
echo "Deregistering GitHub repository runner: $RUNNER_ID ..."
gh api -X DELETE -H 'Accept: application/vnd.github.v3+json' \
"repos/$GITHUB_REPO/actions/runners/$RUNNER_ID" || echo "WARN: Failed to deregister GitHub repository runner (GitHub API call failed)"
Expand Down

0 comments on commit 8bb2976

Please sign in to comment.