|
| 1 | +# Launch EC2 Runner with Fallback |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +This action is used to launch an EC2 instance (EC2 runner) in AWS -- either as a spot instance or a dedicated instance. It essentially leverages the [machulav/ec2-github-runner](https://github.com/machulav/ec2-github-runner) GitHub action under the hood to launch EC2 instance in AWS, but implements "fallback logic" if one or more attempts to launch an EC2 instance fail. |
| 6 | + |
| 7 | +## When to Use it? |
| 8 | + |
| 9 | +### Insufficient Capacity: AWS Lacks Availablility for your Desired EC2 Instance Type in a Given Availability Zone |
| 10 | + |
| 11 | +If one of your CI workflows attempts to launch an EC2 instance in AWS but fails due to an `InsufficientInstanceCapacity` error (aka AWS doesn't have any instances available for that instance type), you can leverage this action to try other regions as a backup. |
| 12 | + |
| 13 | +### Cost Savings: You want to Try Launching Your EC2 Runner as a Spot Instance First |
| 14 | + |
| 15 | +Spot instances are generally cheaper and can be sufficient for certain sitautions. Just be mindful of the implications of a spot instance. The official AWS documentation emphasizes that spot instances ["can be interrupted by Amazon EC2 when EC2 needs the capacity back."](https://docs.aws.amazon.com/whitepapers/latest/cost-optimization-leveraging-ec2-spot-instances/how-spot-instances-work.html) Thus, if your instance type is "very popular" amongst other AWS users and you can't afford to have interruptions on your EC2 instances, you should actively avoid launching your EC2 instances as spot instances. |
| 16 | + |
| 17 | +## Which AWS Regions and Availability Zones are Supported? |
| 18 | + |
| 19 | +The pricing for EC2 instances depends on the region, with some regions charging more money for the same instance type compared to other regions. Given that information, the currently "supported" AWS regions and availability zones for launching an EC2 instance are: |
| 20 | + |
| 21 | +* US East 1 (Virginia) - `us-east-1` |
| 22 | + * `us-east-1a` |
| 23 | + * `us-east-1b` |
| 24 | + * `us-east-1c` |
| 25 | + * `us-east-1d` |
| 26 | + * `us-east-1e` |
| 27 | + * `us-east-1f` |
| 28 | +* US East 2 (Ohio) - `us-east-2` |
| 29 | + * `us-east-2a` |
| 30 | + * `us-east-2b` |
| 31 | + * `us-east-2c` |
| 32 | + |
| 33 | + |
| 34 | +## How to Call this Action from a Job within a GitHub Workflow |
| 35 | + |
| 36 | +Consider a simple job definition within a GitHub workflow file that is used to launch an EC2 instance in AWS. You would first call the GitHub `actions/checkout` action to "checkout" this action and store it locally. A list of supported inputs is provided in the next subsection, followed by an example in the following subsection. |
| 37 | + |
| 38 | +### Supported Inputs |
| 39 | + |
| 40 | +#### Required inputs: |
| 41 | + |
| 42 | +| Name | Description | Example Value | |
| 43 | +| --- | --- | --- | |
| 44 | +| `aws-access-key-id` | AWS access key ID that will be used to launch your desired instance. | `AKIAIOSFODNN7EXAMPLE` | |
| 45 | +| `aws-resource-tags` | Resource tags to apply to the desired AWS instance, upon successful launch. | `[{"Key": "Name", "Value": "my-runner"}]`| |
| 46 | +| `aws-secret-access-key` | AWS secret access key that will be used to launch your desired instance. | `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY` | |
| 47 | +| `ec2-instance-type` | The desired AWS instance type to use, regardless of region. (Note that some instance types are not available in certain regions.) | `g4dn.2xlarge` | |
| 48 | +| `github-token` | GitHub Personal Access Token with the `repo` scope assigned. | `ghp_xxxxxxxxx` | |
| 49 | +| `regions-config` | A JSON string that defines which regions and subnets to try, along with the AMIs and security groups to use within those regions. | See example in next sub-section | |
| 50 | + |
| 51 | +#### Optional inputs: |
| 52 | + |
| 53 | +| Name | Description | Example value | |
| 54 | +| --- | --- | --- | |
| 55 | +| `try-spot-instance-first` | If set to "true", then the EC2 instance will be launched as a spot instance rather than a dedicated EC2 instance. If a spot instance cannot be launched in any of the desired availability zones (e.g., due to insufficient capacity on AWS), then a dedicated instance will be tried next. (Note: This option is not always desirable for certain instance types.) | `true` | |
| 56 | + |
| 57 | +#### `regions-config` Formatting |
| 58 | + |
| 59 | +This input must be a valid JSON string. It is essentially a list of configuration items, where each configuration item corresponds to the configuration for launching an EC2 instance a specific AWS region. The required fields are: |
| 60 | + |
| 61 | +| Name | Description | Example Value | |
| 62 | +| --- | --- | --- | |
| 63 | +| `region` | the AWS region code. (You can view [the official "Available AWS Regions" table](https://docs.aws.amazon.com/global-infrastructure/latest/regions/aws-regions.html#available-regions) to see the supported, available codes.) | `us-east-2` | |
| 64 | +| `subnets` | a map which tells the GitHub action | - | |
| 65 | +| `ec2-ami` | the AMI ID to use in that specific region. (Note that AMI IDs are unique to each region.) | `ami-0123456789` | |
| 66 | +| `security-group-id` | security group ID to use when launching the EC2 instance. (Note that security group IDs are unique to each region.) | `sg-02ce123456e7893c7` | |
| 67 | + |
| 68 | +<details> |
| 69 | + |
| 70 | +<summary>Example individual configuration item for launching an EC2 instance in `us-east-1`</summary> |
| 71 | + |
| 72 | +```json |
| 73 | +{ |
| 74 | + "region": "us-east-1", |
| 75 | + "subnets": { |
| 76 | + "us-east-1a": "${{ secrets.SUBNET_US_EAST_1A }}", |
| 77 | + "us-east-1b": "${{ secrets.SUBNET_US_EAST_1B }}", |
| 78 | + "us-east-1c": "${{ secrets.SUBNET_US_EAST_1C }}", |
| 79 | + }, |
| 80 | + "ec2-ami": "${{ secrets.EC2_AMI_US_EAST_1 }}", |
| 81 | + "security-group-id": "${{ secrets.SECURITY_GROUP_ID_US_EAST_1 }}" |
| 82 | +} |
| 83 | +``` |
| 84 | +</details> |
| 85 | + |
| 86 | +Whether you have one or more region configurations, you will need to place them in a list. For example, if you only want to launch in `us-east-1`, your `regions-config` input would be formatted like so: |
| 87 | + |
| 88 | + |
| 89 | +<details> |
| 90 | + |
| 91 | +<summary>Example format for `regions-config` for only 1 region</summary> |
| 92 | + |
| 93 | +```json |
| 94 | +[ |
| 95 | + { |
| 96 | + "region": "us-east-1", |
| 97 | + "subnets": { |
| 98 | + "us-east-1a": "${{ secrets.SUBNET_US_EAST_1A }}", |
| 99 | + "us-east-1b": "${{ secrets.SUBNET_US_EAST_1B }}", |
| 100 | + "us-east-1c": "${{ secrets.SUBNET_US_EAST_1C }}", |
| 101 | + }, |
| 102 | + "ec2-ami": "${{ secrets.EC2_AMI_US_EAST_1 }}", |
| 103 | + "security-group-id": "${{ secrets.SECURITY_GROUP_ID_US_EAST_1 }}" |
| 104 | + } |
| 105 | +] |
| 106 | +``` |
| 107 | + |
| 108 | +</details> |
| 109 | + |
| 110 | +<details> |
| 111 | + |
| 112 | +<summary>Example format for `regions-config` for multiple regions</summary> |
| 113 | + |
| 114 | +```json |
| 115 | +[ |
| 116 | + { |
| 117 | + "region": "us-east-1", |
| 118 | + "subnets": { |
| 119 | + "us-east-1a": "${{ secrets.SUBNET_US_EAST_1A }}", |
| 120 | + "us-east-1b": "${{ secrets.SUBNET_US_EAST_1B }}", |
| 121 | + "us-east-1c": "${{ secrets.SUBNET_US_EAST_1C }}", |
| 122 | + }, |
| 123 | + "ec2-ami": "${{ secrets.EC2_AMI_US_EAST_1 }}", |
| 124 | + "security-group-id": "${{ secrets.SECURITY_GROUP_ID_US_EAST_1 }}" |
| 125 | + }, |
| 126 | + { |
| 127 | + "region": "us-east-2", |
| 128 | + "subnets": { |
| 129 | + "us-east-2a": "${{ secrets.SUBNET_US_EAST_2A }}", |
| 130 | + "us-east-2b": "${{ secrets.SUBNET_US_EAST_2B }}", |
| 131 | + "us-east-2c": "${{ secrets.SUBNET_US_EAST_2C }}", |
| 132 | + "us-east-2d": "${{ secrets.SUBNET_US_EAST_2D }}", |
| 133 | + "us-east-2e": "${{ secrets.SUBNET_US_EAST_2E }}", |
| 134 | + }, |
| 135 | + "ec2-ami": "${{ secrets.EC2_AMI_US_EAST_2 }}", |
| 136 | + "security-group-id": "${{ secrets.SECURITY_GROUP_ID_US_EAST_2 }}" |
| 137 | + } |
| 138 | +] |
| 139 | +``` |
| 140 | + |
| 141 | +</details> |
| 142 | + |
| 143 | +### Example Usage |
| 144 | + |
| 145 | +```yaml |
| 146 | +jobs: |
| 147 | + start-ec2-runner: |
| 148 | + runs-on: ubuntu-latest |
| 149 | + steps: |
| 150 | + |
| 151 | + - name: Checkout "launch-ec2-runner-with-fallback" in-house CI action |
| 152 | + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 |
| 153 | + with: |
| 154 | + # The action is stored in this repository, so we need to tell GitHub to pull from: {org}/{repo} |
| 155 | + repository: instructlab/ci-actions |
| 156 | + # clone the "ci-actions" repo to a local directory called "ci-actions", instead of overwriting the current WORKDIR contents |
| 157 | + path: ci-actions |
| 158 | + # Only checkout the relevant GitHub action |
| 159 | + sparse-checkout: | |
| 160 | + actions/launch-ec2-runner-with-fallback |
| 161 | +
|
| 162 | + - name: Launch EC2 Runner with Fallback |
| 163 | + # Make sure to provide the relative path to `launch-ec2-runner-with-fallback` |
| 164 | + uses: ./ci-actions/actions/launch-ec2-runner-with-fallback |
| 165 | + with: |
| 166 | + # (OPTIONAL) Cost-savings inputs |
| 167 | + try-spot-instance-first: "true" |
| 168 | + # (REQUIRED) Authentication inputs |
| 169 | + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} |
| 170 | + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} |
| 171 | + github-token: ${{ secrets.GITHUB_TOKEN }} |
| 172 | + # (REQUIRED) Common EC2 instance configuration inputs |
| 173 | + ec2-instance-type: g4dn.2xlarge |
| 174 | + aws-resource-tags: > |
| 175 | + [ |
| 176 | + {"Key": "Name", "Value": "instructlab-ci-github-xlarge-runner"}, |
| 177 | + {"Key": "GitHubRepository", "Value": "${{ github.repository }}"}, |
| 178 | + {"Key": "GitHubRef", "Value": "${{ github.ref }}"}, |
| 179 | + {"Key": "GitHubPR", "Value": "${{ github.event.number }}"} |
| 180 | + ] |
| 181 | + # (REQUIRED) AWS regions to try launching your EC2 instance in. (If desired, you can |
| 182 | + # omit one of the supported regions below. You can also omit specific |
| 183 | + # subnets within those regions.) |
| 184 | + regions-config: > |
| 185 | + [ |
| 186 | + { |
| 187 | + "region": "us-east-1", |
| 188 | + "subnets": { |
| 189 | + "us-east-1a": "${{ secrets.SUBNET_US_EAST_1A }}", |
| 190 | + "us-east-1b": "${{ secrets.SUBNET_US_EAST_1B }}", |
| 191 | + "us-east-1c": "${{ secrets.SUBNET_US_EAST_1C }}", |
| 192 | + }, |
| 193 | + "ec2-ami": "${{ secrets.EC2_AMI_US_EAST_1 }}", |
| 194 | + "security-group-id": "${{ secrets.SECURITY_GROUP_ID_US_EAST_1 }}" |
| 195 | + }, |
| 196 | + { |
| 197 | + "region": "us-east-2", |
| 198 | + "subnets": { |
| 199 | + "us-east-2a": "${{ secrets.SUBNET_US_EAST_2A }}", |
| 200 | + "us-east-2b": "${{ secrets.SUBNET_US_EAST_2B }}", |
| 201 | + "us-east-2c": "${{ secrets.SUBNET_US_EAST_2C }}", |
| 202 | + "us-east-2d": "${{ secrets.SUBNET_US_EAST_2D }}", |
| 203 | + "us-east-2e": "${{ secrets.SUBNET_US_EAST_2E }}", |
| 204 | + }, |
| 205 | + "ec2-ami": "${{ secrets.EC2_AMI_US_EAST_2 }}", |
| 206 | + "security-group-id": "${{ secrets.SECURITY_GROUP_ID_US_EAST_2 }}" |
| 207 | + } |
| 208 | + ] |
| 209 | +``` |
| 210 | +
|
0 commit comments