cdk-github-runners icon indicating copy to clipboard operation
cdk-github-runners copied to clipboard

feat: Support fallback and weighted distribution for runner providers

Open kichik opened this issue 2 weeks ago • 0 comments

This PR introduces Composite Providers, a powerful new feature that allows you to combine multiple runner providers with different strategies. Composite providers enable advanced runner management patterns like fallback chains and weighted distribution.

What It Does

Composite providers wrap multiple runner providers and implement coordination strategies. They implement the ICompositeProvider interface, which is similar to IRunnerProvider but designed to work with multiple sub-providers. The main GitHubRunners construct now accepts both regular providers and composite providers in its providers array.

What It's Good For

Fallback Strategy

Use fallback providers when you want to try providers in order until one succeeds. This is perfect for:

  • Cost optimization: Try spot instances first, fall back to on-demand if spot capacity is unavailable
  • Instance type preferences: Try preferred instance types first, fall back to alternatives if unavailable
  • Availability zone resilience: Try primary AZs first, fall back to secondary AZs

Weighted Distribution Strategy

Use weighted distribution when you want to randomly select providers based on weights. This is ideal for:

  • Load balancing: Distribute traffic across multiple availability zones
  • Instance type distribution: Run some jobs on cheaper instances, some on more powerful ones
  • Multi-region distribution: Split workloads across regions based on capacity or cost

Code Examples

Fallback Provider Example

Try spot instances first, fall back to on-demand if spot capacity is unavailable:

import { CompositeProvider, EcsRunnerProvider, GitHubRunners } from '@cloudsnorkel/cdk-github-runners';

// Try spot instances first, fall back to on-demand if spot is unavailable
const ecsFallback = CompositeProvider.fallback(this, 'ECS Fallback', [
  new EcsRunnerProvider(this, 'ECS Spot', {
    labels: ['ecs', 'linux', 'x64'],
    spot: true,
    // ... other config
  }),
  new EcsRunnerProvider(this, 'ECS On-Demand', {
    labels: ['ecs', 'linux', 'x64'],
    spot: false,
    // ... other config
  }),
]);

new GitHubRunners(this, 'runners', {
  providers: [ecsFallback],
});

Weighted Distribution Example

Distribute 60% of traffic to us-east-1a, 40% to us-east-1b:

import { CompositeProvider, FargateRunnerProvider, GitHubRunners } from '@cloudsnorkel/cdk-github-runners';
import { SubnetSelection } from 'aws-cdk-lib/aws-ec2';

// Distribute 60% of traffic to AZ-1, 40% to AZ-2
const distributedProvider = CompositeProvider.distribute(this, 'Fargate Distribution', [
  {
    weight: 3, // 3/(3+2) = 60%
    provider: new FargateRunnerProvider(this, 'Fargate AZ-1', {
      labels: ['fargate', 'linux', 'x64'],
      subnetSelection: vpc.selectSubnets({
        availabilityZones: ['us-east-1a'],
      }),
    }),
  },
  {
    weight: 2, // 2/(3+2) = 40%
    provider: new FargateRunnerProvider(this, 'Fargate AZ-2', {
      labels: ['fargate', 'linux', 'x64'],
      subnetSelection: vpc.selectSubnets({
        availabilityZones: ['us-east-1b'],
      }),
    }),
  },
]);

new GitHubRunners(this, 'runners', {
  providers: [distributedProvider],
});

Requirements

  • All providers in a composite must have the exact same labels. This ensures any provisioned runner can match the labels requested by the GitHub workflow job.
  • At least two providers must be specified for both fallback and distribution strategies.
  • All weights in weighted distribution must be positive numbers.

Links

Resolves #394 Resolves #193 Resolves #335 Replaces #518

kichik avatar Dec 14 '25 14:12 kichik