Skip to content

Commit f615383

Browse files
committed
internal/ci: switch to Namespace GitHub Actions runners
Namespace offers runners for GitHub actions with Ubuntu, MacOS, and Windows, much like GitHub. However, it has two main advantages: * Cost per minute; for the same CPU and memory, GitHub costs about four times as much. * Caching; Namespace offers volume mount caching, which is way faster than fetching and uploading compressed cache archives as GitHub does. * Advanced caching; Namespace also offer out-of-the-box caching for toolchains such as Go, as can be seen by `with: cache: "go"` below. They also offer transparent system-wide caching for Docker images, git checkouts, and toolchain downloads such as from actions/setup-go. The runner profiles used below were set up by me, to cost the same per minute as our old slow runners on GitHub, but have four times as much CPU power. Rather than about 2 CPUs per runner, we now have 8. Overall, we see roughly a 2.5x speed-up in CPU-intensive job steps, and the cost of setup and cleanup for the cache is now near-zero. A cache-less run before and after Namespace sees the following wins: * Go 1.24.x, Linux - 9m50s -> 3m46s * Go 1.24.x, MacOS - 2m10s -> 1m13s * Go 1.24.x, Windows - 4m36s -> 1m38s Take particular note of how much faster Windows is on Namespace; we were running into issues with slow Windows filesystems on GitHub. We will be able to see what performance looks like before and after Namespace on warm caches once this change lands in master. Given that CI runs on a warm cache spend at least 10-30s just fetching and extracting the cache, and they usually spend 2-3m running Go tests, I expect such CI runs with a warm cache to see a similar ~2x speed-up. Signed-off-by: Daniel Martí <[email protected]> Change-Id: I4f428d3382da7b414c31ae29f8f836e269fcf073 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1217532 Reviewed-by: Paul Jolly <[email protected]> TryBot-Result: CUEcueckoo <[email protected]> Unity-Result: CUE porcuepine <[email protected]>
1 parent 7e4dff2 commit f615383

File tree

10 files changed

+58
-158
lines changed

10 files changed

+58
-158
lines changed

.github/workflows/evict_caches.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
run:
1111
shell: bash --noprofile --norc -euo pipefail {0}
1212
if: ${{github.repository == 'cue-lang/cue'}}
13-
runs-on: ubuntu-24.04
13+
runs-on: namespace-profile-ubuntu-24-04-amd64-8x16
1414
steps:
1515
- name: Checkout code
1616
uses: actions/checkout@v4

.github/workflows/push_tip_to_trybot.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ jobs:
66
defaults:
77
run:
88
shell: bash --noprofile --norc -euo pipefail {0}
9-
runs-on: ubuntu-24.04
9+
runs-on: namespace-profile-ubuntu-24-04-amd64-8x16
1010
if: ${{github.repository == 'cue-lang/cue'}}
1111
steps:
1212
- name: Write netrc file for cueckoo Gerrithub

.github/workflows/release.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
defaults:
1717
run:
1818
shell: bash --noprofile --norc -euo pipefail {0}
19-
runs-on: ubuntu-24.04
19+
runs-on: namespace-profile-ubuntu-24-04-amd64-8x16
2020
if: ${{github.repository == 'cue-lang/cue'}}
2121
steps:
2222
- name: Checkout code

.github/workflows/tip_triggers.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
defaults:
1212
run:
1313
shell: bash --noprofile --norc -euo pipefail {0}
14-
runs-on: ubuntu-24.04
14+
runs-on: namespace-profile-ubuntu-24-04-amd64-8x16
1515
if: ${{github.repository == 'cue-lang/cue'}}
1616
steps:
1717
- name: Trigger unity build

.github/workflows/trybot.yaml

Lines changed: 18 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ jobs:
2323
- 1.23.x
2424
- 1.24.x
2525
runner:
26-
- ubuntu-24.04
27-
- macos-14
28-
- windows-2022
26+
- namespace-profile-ubuntu-24-04-amd64-8x16
27+
- namespace-profile-macos-15-arm64-6x14
28+
- namespace-profile-windows-2022-amd64-8x16
2929
runs-on: ${{ matrix.runner }}
3030
if: |-
3131
(contains(github.event.head_commit.message, '
@@ -77,51 +77,29 @@ jobs:
7777
7878
# Dump env for good measure
7979
go env
80-
- id: go-mod-cache-dir
81-
name: Get go mod cache directory
82-
run: echo "dir=$(go env GOMODCACHE)" >> ${GITHUB_OUTPUT}
83-
- id: go-cache-dir
84-
name: Get go build/test cache directory
85-
run: echo "dir=$(go env GOCACHE)" >> ${GITHUB_OUTPUT}
86-
- if: |-
87-
(((github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-branch.')) && (! (contains(github.event.head_commit.message, '
88-
Dispatch-Trailer: {"type":"')))) || (github.ref == 'refs/heads/ci/test'))
89-
uses: actions/cache@v4
90-
with:
91-
path: |-
92-
${{ steps.go-mod-cache-dir.outputs.dir }}/cache/download
93-
${{ steps.go-cache-dir.outputs.dir }}
94-
key: ${{ runner.os }}-${{ matrix.go-version }}-${{ github.run_id }}
95-
restore-keys: ${{ runner.os }}-${{ matrix.go-version }}
96-
- if: |-
97-
! (((github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-branch.')) && (! (contains(github.event.head_commit.message, '
98-
Dispatch-Trailer: {"type":"')))) || (github.ref == 'refs/heads/ci/test'))
99-
uses: actions/cache/restore@v4
80+
- if: matrix.runner != 'namespace-profile-windows-2022-amd64-8x16'
81+
uses: namespacelabs/nscloud-cache-action@v1
10082
with:
101-
path: |-
102-
${{ steps.go-mod-cache-dir.outputs.dir }}/cache/download
103-
${{ steps.go-cache-dir.outputs.dir }}
104-
key: ${{ runner.os }}-${{ matrix.go-version }}-${{ github.run_id }}
105-
restore-keys: ${{ runner.os }}-${{ matrix.go-version }}
83+
cache: go
10684
- if: |-
10785
github.repository == 'cue-lang/cue' && (((github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-branch.')) && (! (contains(github.event.head_commit.message, '
108-
Dispatch-Trailer: {"type":"')))) || github.ref == 'refs/heads/ci/test')
86+
Dispatch-Trailer: {"type":"')))) || (github.ref == 'refs/heads/ci/test'))
10987
run: go clean -testcache
11088
- run: go run cuelang.org/go/cmd/cue login --token=${{ secrets.NOTCUECKOO_CUE_TOKEN }}
111-
- if: (matrix.go-version == '1.24.x' && matrix.runner == 'ubuntu-24.04')
89+
- if: (matrix.go-version == '1.24.x' && matrix.runner == 'namespace-profile-ubuntu-24-04-amd64-8x16')
11290
name: Early git and code sanity checks
11391
run: go run ./internal/ci/checks
11492
- if: |-
11593
((github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-branch.')) && (! (contains(github.event.head_commit.message, '
116-
Dispatch-Trailer: {"type":"')))) || !(matrix.go-version == '1.24.x' && matrix.runner == 'ubuntu-24.04')
94+
Dispatch-Trailer: {"type":"')))) || !(matrix.go-version == '1.24.x' && matrix.runner == 'namespace-profile-ubuntu-24-04-amd64-8x16')
11795
name: Test
11896
run: go test ./...
119-
- if: (matrix.go-version == '1.24.x' && matrix.runner == 'ubuntu-24.04')
97+
- if: (matrix.go-version == '1.24.x' && matrix.runner == 'namespace-profile-ubuntu-24-04-amd64-8x16')
12098
name: Test with -race
12199
env:
122100
GORACE: atexit_sleep_ms=10
123101
run: go test -race ./...
124-
- if: (matrix.go-version == '1.24.x' && matrix.runner == 'ubuntu-24.04')
102+
- if: (matrix.go-version == '1.24.x' && matrix.runner == 'namespace-profile-ubuntu-24-04-amd64-8x16')
125103
name: Test on 32 bits
126104
env:
127105
GOARCH: "386"
@@ -131,38 +109,38 @@ jobs:
131109
- id: auth
132110
if: |-
133111
github.repository == 'cue-lang/cue' && (((github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-branch.')) && (! (contains(github.event.head_commit.message, '
134-
Dispatch-Trailer: {"type":"')))) || (github.ref == 'refs/heads/ci/test')) && (matrix.go-version == '1.24.x' && matrix.runner == 'ubuntu-24.04')
112+
Dispatch-Trailer: {"type":"')))) || (github.ref == 'refs/heads/ci/test')) && (matrix.go-version == '1.24.x' && matrix.runner == 'namespace-profile-ubuntu-24-04-amd64-8x16')
135113
name: gcloud auth for end-to-end tests
136114
uses: google-github-actions/auth@v2
137115
with:
138116
credentials_json: ${{ secrets.E2E_GCLOUD_KEY }}
139117
- if: |-
140118
github.repository == 'cue-lang/cue' && (((github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-branch.')) && (! (contains(github.event.head_commit.message, '
141-
Dispatch-Trailer: {"type":"')))) || (github.ref == 'refs/heads/ci/test')) && (matrix.go-version == '1.24.x' && matrix.runner == 'ubuntu-24.04')
119+
Dispatch-Trailer: {"type":"')))) || (github.ref == 'refs/heads/ci/test')) && (matrix.go-version == '1.24.x' && matrix.runner == 'namespace-profile-ubuntu-24-04-amd64-8x16')
142120
name: gcloud setup for end-to-end tests
143121
uses: google-github-actions/setup-gcloud@v2
144122
- if: |-
145123
github.repository == 'cue-lang/cue' && (((github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-branch.')) && (! (contains(github.event.head_commit.message, '
146-
Dispatch-Trailer: {"type":"')))) || (github.ref == 'refs/heads/ci/test')) && (matrix.go-version == '1.24.x' && matrix.runner == 'ubuntu-24.04')
124+
Dispatch-Trailer: {"type":"')))) || (github.ref == 'refs/heads/ci/test')) && (matrix.go-version == '1.24.x' && matrix.runner == 'namespace-profile-ubuntu-24-04-amd64-8x16')
147125
name: End-to-end test
148126
env:
149127
CUE_TEST_TOKEN: ${{ secrets.E2E_PORCUEPINE_CUE_TOKEN }}
150128
run: |-
151129
cd internal/_e2e
152130
go test -race
153-
- if: (matrix.go-version == '1.24.x' && matrix.runner == 'ubuntu-24.04')
131+
- if: (matrix.go-version == '1.24.x' && matrix.runner == 'namespace-profile-ubuntu-24-04-amd64-8x16')
154132
name: Go checks
155133
run: |-
156134
go vet ./...
157135
go mod tidy
158136
(cd internal/_e2e && go test -run=-)
159-
- if: (matrix.go-version == '1.24.x' && matrix.runner == 'ubuntu-24.04')
137+
- if: (matrix.go-version == '1.24.x' && matrix.runner == 'namespace-profile-ubuntu-24-04-amd64-8x16')
160138
name: staticcheck
161139
uses: dominikh/staticcheck-action@v1
162140
with:
163141
version: "2025.1"
164142
install-go: false
165-
- if: (matrix.go-version == '1.24.x' && matrix.runner == 'ubuntu-24.04')
143+
- if: (matrix.go-version == '1.24.x' && matrix.runner == 'namespace-profile-ubuntu-24-04-amd64-8x16')
166144
name: Check all git tags are available
167145
run: |-
168146
cd $(mktemp -d)
@@ -178,7 +156,7 @@ jobs:
178156
echo "Did you forget about refs/attic branches? https://github.com/cue-lang/cue/wiki/Notes-for-project-maintainers"
179157
exit 1
180158
fi
181-
- if: (matrix.go-version == '1.24.x' && matrix.runner == 'ubuntu-24.04')
159+
- if: (matrix.go-version == '1.24.x' && matrix.runner == 'namespace-profile-ubuntu-24-04-amd64-8x16')
182160
name: Generate
183161
run: go generate ./...
184162
- if: always()

.github/workflows/trybot_dispatch.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
defaults:
1212
run:
1313
shell: bash --noprofile --norc -euo pipefail {0}
14-
runs-on: ubuntu-24.04
14+
runs-on: namespace-profile-ubuntu-24-04-amd64-8x16
1515
if: ${{ ((github.ref == 'refs/heads/ci/test') && false) || github.event.client_payload.type == 'trybot' }}
1616
steps:
1717
- name: Write netrc file for cueckoo Gerrithub

internal/ci/base/gerrithub.cue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ evictCaches: bashWorkflow & {
244244
steps: [
245245
for v in checkoutCode {v},
246246

247+
// TODO(mvdan): remove once we've fully moved to Namespace runners.
247248
githubactions.#Step & {
248249
name: "Delete caches"
249250
run: """

internal/ci/base/github.cue

Lines changed: 28 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ package base
44

55
import (
66
"encoding/json"
7-
"list"
87
"strings"
98
"strconv"
9+
1010
"cue.dev/x/githubactions"
1111
)
1212

@@ -68,7 +68,7 @@ installGo: {
6868
checkoutCode: {
6969
#actionsCheckout: githubactions.#Step & {
7070
name: "Checkout code"
71-
uses: "actions/checkout@v4"
71+
uses: "actions/checkout@v4" // TODO(mvdan): switch to namespacelabs/nscloud-checkout-action@v1 once Windows supports caching
7272

7373
// "pull_request_target" builds will by default use a merge commit,
7474
// testing the PR's HEAD merged on top of the master branch.
@@ -151,105 +151,32 @@ curlGitHubAPI: {
151151
"""#
152152
}
153153

154-
setupGoActionsCaches: {
155-
// #readonly determines whether we ever want to write the cache back. The
156-
// writing of a cache back (for any given cache key) should only happen on a
157-
// protected branch. But running a workflow on a protected branch often
158-
// implies that we want to skip the cache to ensure we catch flakes early.
159-
// Hence the concept of clearing the testcache to ensure we catch flakes
160-
// early can be defaulted based on #readonly. In the general case the two
161-
// concepts are orthogonal, hence they are kept as two parameters, even
162-
// though in our case we could get away with a single parameter that
163-
// encapsulates our needs.
164-
#readonly: *false | bool
165-
#cleanTestCache: *!#readonly | bool
166-
#goVersion: string
167-
#additionalCacheDirs: [...string]
168-
#os: string
169-
170-
let goModCacheDirID = "go-mod-cache-dir"
171-
let goCacheDirID = "go-cache-dir"
172-
173-
// cacheDirs is a convenience variable that includes
174-
// GitHub expressions that represent the directories
175-
// that participate in Go caching.
176-
let cacheDirs = list.Concat([[
177-
"${{ steps.\(goModCacheDirID).outputs.dir }}/cache/download",
178-
"${{ steps.\(goCacheDirID).outputs.dir }}",
179-
], #additionalCacheDirs])
180-
181-
let cacheRestoreKeys = "\(#os)-\(#goVersion)"
182-
183-
let cacheStep = githubactions.#Step & {
184-
with: {
185-
path: strings.Join(cacheDirs, "\n")
186-
187-
// GitHub actions caches are immutable. Therefore, use a key which is
188-
// unique, but allow the restore to fallback to the most recent cache.
189-
// The result is then saved under the new key which will benefit the
190-
// next build. Restore keys are only set if the step is restore.
191-
key: "\(cacheRestoreKeys)-${{ github.run_id }}"
192-
"restore-keys": cacheRestoreKeys
193-
}
194-
}
195-
196-
let readWriteCacheExpr = "(\(isProtectedBranch) || \(isTestDefaultBranch))"
197-
198-
// pre is the list of steps required to establish and initialise the correct
199-
// caches for Go-based workflows.
200-
[
201-
// TODO: once https://github.com/actions/setup-go/issues/54 is fixed,
202-
// we could use `go env` outputs from the setup-go step.
203-
githubactions.#Step & {
204-
name: "Get go mod cache directory"
205-
id: goModCacheDirID
206-
run: #"echo "dir=$(go env GOMODCACHE)" >> ${GITHUB_OUTPUT}"#
207-
},
208-
githubactions.#Step & {
209-
name: "Get go build/test cache directory"
210-
id: goCacheDirID
211-
run: #"echo "dir=$(go env GOCACHE)" >> ${GITHUB_OUTPUT}"#
212-
},
213-
214-
// Only if we are not running in readonly mode do we want a step that
215-
// uses actions/cache (read and write). Even then, the use of the write
216-
// step should be predicated on us running on a protected branch. Because
217-
// it's impossible for anything else to write such a cache.
218-
if !#readonly {
219-
cacheStep & {
220-
if: readWriteCacheExpr
221-
uses: "actions/cache@v4"
222-
}
223-
},
224-
225-
cacheStep & {
226-
// If we are readonly, there is no condition on when we run this step.
227-
// It should always be run, becase there is no alternative. But if we
228-
// are not readonly, then we need to predicate this step on us not
229-
// being on a protected branch.
230-
if !#readonly {
231-
if: "! \(readWriteCacheExpr)"
232-
}
233-
234-
uses: "actions/cache/restore@v4"
235-
},
236-
237-
if #cleanTestCache {
238-
// All tests on protected branches should skip the test cache. The
239-
// canonical way to do this is with -count=1. However, we want the
240-
// resulting test cache to be valid and current so that subsequent CLs
241-
// in the trybot repo can leverage the updated cache. Therefore, we
242-
// instead perform a clean of the testcache.
243-
//
244-
// Critically we only want to do this in the main repo, not the trybot
245-
// repo.
246-
githubactions.#Step & {
247-
if: "github.repository == '\(githubRepositoryPath)' && (\(isProtectedBranch) || github.ref == 'refs/heads/\(testDefaultBranch)')"
248-
run: "go clean -testcache"
249-
}
250-
},
251-
]
252-
}
154+
setupGoActionsCaches: [
155+
// Our runner profiles on Namespace are already configured to only update
156+
// the cache when they run from one of the protected branches.
157+
githubactions.#Step & {
158+
uses: "namespacelabs/nscloud-cache-action@v1"
159+
with: cache: "go"
160+
},
161+
162+
// All tests on protected branches should skip the test cache. The
163+
// canonical way to do this is with -count=1. However, we want the
164+
// resulting test cache to be valid and current so that subsequent CLs
165+
// in the trybot repo can leverage the updated cache. Therefore, we
166+
// instead perform a clean of the testcache.
167+
//
168+
// Critically we only want to do this in the main repo, not the trybot
169+
// repo.
170+
//
171+
// TODO(mvdan): rethink for Namespace, where trimming the cache size is irrelevant.
172+
// A better approach there is likely to set GOFLAGS=-count=1 for "go test",
173+
// assuming that does not interfere with other commands like "go vet",
174+
// as -count=1 is the canonical way to disable test caching.
175+
githubactions.#Step & {
176+
if: "github.repository == '\(githubRepositoryPath)' && (\(isProtectedBranch) || \(isTestDefaultBranch))"
177+
run: "go clean -testcache"
178+
},
179+
]
253180

254181
// isProtectedBranch is an expression that evaluates to true if the
255182
// job is running as a result of pushing to one of protectedBranchPatterns.

internal/ci/github/trybot.cue

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,6 @@ workflows: trybot: _repo.bashWorkflow & {
4040
strategy: _testStrategy
4141
"runs-on": "${{ matrix.runner }}"
4242

43-
let _setupGoActionsCaches = _repo.setupGoActionsCaches & {
44-
#goVersion: goVersionVal
45-
#os: runnerOSVal
46-
_
47-
}
48-
4943
let installGo = _repo.installGo & {
5044
#setupGo: with: "go-version": goVersionVal
5145
_
@@ -63,7 +57,9 @@ workflows: trybot: _repo.bashWorkflow & {
6357

6458
// cachePre must come after installing Node and Go, because the cache locations
6559
// are established by running each tool.
66-
for v in _setupGoActionsCaches {v},
60+
for v in _repo.setupGoActionsCaches {v & {
61+
if: string | *"\(matrixRunner) != '\(_repo.windowsMachine)'" // TODO(mvdan): remove the condition once Windows supports caching
62+
}},
6763

6864
_repo.loginCentralRegistry,
6965

@@ -92,8 +88,6 @@ workflows: trybot: _repo.bashWorkflow & {
9288
}
9389
}
9490

95-
let runnerOS = "runner.os"
96-
let runnerOSVal = "${{ \(runnerOS) }}"
9791
let matrixRunner = "matrix.runner"
9892
let goVersion = "matrix.go-version"
9993
let goVersionVal = "${{ \(goVersion) }}"

internal/ci/repo/repo.cue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ protectedBranchPatterns: [defaultBranch, releaseBranchPattern]
2626
botGitHubUser: "cueckoo"
2727
botGitHubUserEmail: "[email protected]"
2828

29-
linuxMachine: "ubuntu-24.04"
30-
macosMachine: "macos-14"
31-
windowsMachine: "windows-2022"
29+
linuxMachine: "namespace-profile-ubuntu-24-04-amd64-8x16"
30+
macosMachine: "namespace-profile-macos-15-arm64-6x14"
31+
windowsMachine: "namespace-profile-windows-2022-amd64-8x16"
3232

3333
// Use the latest Go version for extra checks,
3434
// such as running tests with the data race detector.

0 commit comments

Comments
 (0)