Skip to content

Commit c7ac032

Browse files
frostebiteclaude
andcommitted
Add Load Balancing documentation page
Documents how to route builds across multiple providers using GitHub Actions scripting: platform-based routing, branch-based routing, runner availability fallback, weighted distribution, and async mode integration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b70b662 commit c7ac032

1 file changed

Lines changed: 257 additions & 0 deletions

File tree

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
# Load Balancing
2+
3+
Orchestrator supports multiple providers, and you can use standard GitHub Actions scripting to route
4+
builds across them. This lets you distribute builds based on platform, project size, runner
5+
availability, or any custom logic.
6+
7+
```
8+
┌──────────────────────┐
9+
│ Incoming Build │
10+
│ Request │
11+
└──────────┬─────────────┘
12+
13+
┌──────────▼─────────────┐
14+
│ Router Logic │
15+
│ (GitHub Actions / │
16+
│ script / matrix) │
17+
└──┬───────┬──────────┬───┘
18+
│ │ │
19+
┌──────────────▼┐ ┌───▼────────┐ ┌▼──────────────┐
20+
│ aws │ │ k8s │ │ local-docker │
21+
│ │ │ │ │ │
22+
│ Fargate │ │ Cluster │ │ Self-hosted │
23+
│ (scalable) │ │ (flexible) │ │ (no cost) │
24+
└───────────────┘ └────────────┘ └───────────────┘
25+
```
26+
27+
## Why Load Balance?
28+
29+
- **Cost control** — Route small or test builds to free self-hosted runners and reserve cloud
30+
providers for production builds.
31+
- **Availability** — Fall back to a cloud provider when your self-hosted runner is busy or offline.
32+
- **Platform routing** — Send Linux builds to AWS and Windows builds to a local runner.
33+
- **Parallel scaling** — Split a multi-platform matrix across providers to finish faster.
34+
35+
## Basic Provider Routing
36+
37+
Use a GitHub Actions matrix or conditional logic to route builds to different providers.
38+
39+
### Route by Platform
40+
41+
```yaml
42+
name: Platform-Based Routing
43+
44+
on:
45+
push:
46+
branches: [main]
47+
48+
jobs:
49+
build:
50+
name: Build ${{ matrix.targetPlatform }}
51+
runs-on: ubuntu-latest
52+
strategy:
53+
fail-fast: false
54+
matrix:
55+
include:
56+
# Linux builds go to AWS (fast, scalable)
57+
- targetPlatform: StandaloneLinux64
58+
provider: aws
59+
# Windows builds go to self-hosted runner
60+
- targetPlatform: StandaloneWindows64
61+
provider: local-docker
62+
# WebGL builds go to Kubernetes
63+
- targetPlatform: WebGL
64+
provider: k8s
65+
steps:
66+
- uses: actions/checkout@v4
67+
with:
68+
lfs: true
69+
70+
- uses: game-ci/unity-builder@v4
71+
with:
72+
providerStrategy: ${{ matrix.provider }}
73+
targetPlatform: ${{ matrix.targetPlatform }}
74+
gitPrivateToken: ${{ secrets.GITHUB_TOKEN }}
75+
```
76+
77+
### Route by Branch
78+
79+
Send production builds to a high-resource cloud provider and development builds to a cheaper option.
80+
81+
```yaml
82+
jobs:
83+
build:
84+
runs-on: ubuntu-latest
85+
steps:
86+
- uses: actions/checkout@v4
87+
with:
88+
lfs: true
89+
90+
- name: Select provider
91+
id: provider
92+
run: |
93+
if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
94+
echo "strategy=aws" >> "$GITHUB_OUTPUT"
95+
echo "cpu=4096" >> "$GITHUB_OUTPUT"
96+
echo "memory=16384" >> "$GITHUB_OUTPUT"
97+
else
98+
echo "strategy=local-docker" >> "$GITHUB_OUTPUT"
99+
echo "cpu=1024" >> "$GITHUB_OUTPUT"
100+
echo "memory=3072" >> "$GITHUB_OUTPUT"
101+
fi
102+
103+
- uses: game-ci/unity-builder@v4
104+
with:
105+
providerStrategy: ${{ steps.provider.outputs.strategy }}
106+
targetPlatform: StandaloneLinux64
107+
containerCpu: ${{ steps.provider.outputs.cpu }}
108+
containerMemory: ${{ steps.provider.outputs.memory }}
109+
gitPrivateToken: ${{ secrets.GITHUB_TOKEN }}
110+
```
111+
112+
## Fallback on Runner Availability
113+
114+
If your self-hosted runner is offline or busy, fall back to a cloud provider. Use a short initial
115+
job to check runner health, then pick the provider accordingly.
116+
117+
```yaml
118+
name: Fallback Routing
119+
120+
on:
121+
push:
122+
branches: [main]
123+
124+
jobs:
125+
check-runner:
126+
name: Check self-hosted availability
127+
runs-on: ubuntu-latest
128+
outputs:
129+
provider: ${{ steps.pick.outputs.provider }}
130+
steps:
131+
- name: Check if self-hosted runner is available
132+
id: pick
133+
env:
134+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
135+
run: |
136+
# Query runner status via the GitHub API
137+
RUNNERS=$(gh api repos/${{ github.repository }}/actions/runners --jq '[.runners[] | select(.status == "online")] | length')
138+
if [[ "$RUNNERS" -gt 0 ]]; then
139+
echo "provider=local-docker" >> "$GITHUB_OUTPUT"
140+
echo "Self-hosted runner available — using local-docker"
141+
else
142+
echo "provider=aws" >> "$GITHUB_OUTPUT"
143+
echo "No runners online — falling back to AWS"
144+
fi
145+
146+
build:
147+
name: Build
148+
needs: check-runner
149+
runs-on: ubuntu-latest
150+
steps:
151+
- uses: actions/checkout@v4
152+
with:
153+
lfs: true
154+
155+
- uses: game-ci/unity-builder@v4
156+
with:
157+
providerStrategy: ${{ needs.check-runner.outputs.provider }}
158+
targetPlatform: StandaloneLinux64
159+
gitPrivateToken: ${{ secrets.GITHUB_TOKEN }}
160+
```
161+
162+
## Async Mode for Load Balancing
163+
164+
The [`asyncOrchestrator`](../api-reference#github-integration) parameter works well with load
165+
balancing. When enabled, the GitHub Action dispatches the build and returns immediately without
166+
waiting for it to finish. This means your routing job completes in seconds regardless of which
167+
provider handles the actual build.
168+
169+
```
170+
Router Job (seconds) Provider A Provider B
171+
┌───────────────────┐ ┌──────────────┐ ┌──────────────┐
172+
│ 1. Check runners │ │ │ │ │
173+
│ 2. Pick provider │ │ │ │ │
174+
│ 3. Dispatch build ├──────►│ 3a. Building │ OR ─►│ 3b. Building │
175+
│ 4. Return (done) │ │ ... │ │ ... │
176+
└───────────────────┘ │ ... │ │ ... │
177+
Completes instantly │ 5. Complete │ │ 5. Complete │
178+
└──────┬───────┘ └──────┬───────┘
179+
│ │
180+
┌──────▼───────────────────────▼──────┐
181+
│ GitHub Check updated │
182+
│ (monitor from PR page) │
183+
└─────────────────────────────────────┘
184+
```
185+
186+
Benefits of combining async mode with load balancing:
187+
188+
- **No wasted runner minutes** — The router job finishes in seconds. You don't pay for a
189+
GitHub-hosted runner to sit idle while the build runs in the cloud.
190+
- **Parallel dispatch** — Launch multiple builds to different providers simultaneously. Each
191+
dispatches and returns immediately.
192+
- **Monitor everything from one place** — Enable `githubCheck: true` and all builds report status
193+
back to the pull request Checks tab, regardless of which provider ran them.
194+
195+
```yaml
196+
jobs:
197+
build:
198+
name: Build ${{ matrix.targetPlatform }}
199+
runs-on: ubuntu-latest
200+
strategy:
201+
fail-fast: false
202+
matrix:
203+
include:
204+
- targetPlatform: StandaloneLinux64
205+
provider: aws
206+
- targetPlatform: StandaloneWindows64
207+
provider: k8s
208+
steps:
209+
- uses: actions/checkout@v4
210+
with:
211+
lfs: true
212+
213+
- uses: game-ci/unity-builder@v4
214+
with:
215+
providerStrategy: ${{ matrix.provider }}
216+
targetPlatform: ${{ matrix.targetPlatform }}
217+
gitPrivateToken: ${{ secrets.GITHUB_TOKEN }}
218+
asyncOrchestrator: true
219+
githubCheck: true
220+
```
221+
222+
Each matrix job dispatches its build and exits immediately. The builds continue running on their
223+
respective providers and report progress via GitHub Checks.
224+
225+
## Weighted Distribution
226+
227+
For teams with both cloud and self-hosted capacity, distribute builds based on a ratio. This example
228+
sends 70% of builds to AWS and 30% to a local runner using a simple hash.
229+
230+
```yaml
231+
- name: Weighted provider selection
232+
id: provider
233+
run: |
234+
# Hash the run ID to get a stable pseudo-random number
235+
HASH=$(echo "${{ github.run_id }}" | md5sum | cut -c1-8)
236+
DECIMAL=$((16#$HASH % 100))
237+
if [[ $DECIMAL -lt 70 ]]; then
238+
echo "strategy=aws" >> "$GITHUB_OUTPUT"
239+
else
240+
echo "strategy=local-docker" >> "$GITHUB_OUTPUT"
241+
fi
242+
```
243+
244+
## Tips
245+
246+
- **Start simple** — A platform-based matrix with different providers per entry is the easiest
247+
starting point. Add dynamic routing only when you need it.
248+
- **Use async for long builds** — Combining `asyncOrchestrator: true` with `githubCheck: true` keeps
249+
your routing job fast and gives you build status on the PR page.
250+
- **Cache keys are provider-independent** — The [`cacheKey`](../api-reference#caching) parameter
251+
works the same across all providers, so builds routed to different providers can still share
252+
caches if they use the same storage backend.
253+
- **Test fallback logic** — Temporarily disable your self-hosted runner to verify that your fallback
254+
routing works before you need it in production.
255+
- **Custom providers** — The same routing patterns work with
256+
[custom providers](../providers/custom-providers). Set `providerStrategy` to a GitHub repo or NPM
257+
package and Orchestrator loads it dynamically.

0 commit comments

Comments
 (0)