tangled
alpha
login
or
join now
evan.jarrett.net
/
loom
10
fork
atom
Kubernetes Operator for Tangled Spindles
10
fork
atom
overview
issues
pulls
pipelines
build multiarch image fix runner binary
evan.jarrett.net
3 months ago
7e3fdced
ee82083d
verified
This commit was signed with the committer's
known signature
.
evan.jarrett.net
SSH Key Fingerprint:
SHA256:bznk0uVPp7XFOl67P0uTM1pCjf2A4ojeP/lsUE7uauQ=
2/2
workflow-amd64.yaml
success
10s
workflow-arm64.yaml
success
8m 59s
+64
-41
4 changed files
expand all
collapse all
unified
split
Dockerfile
Makefile
cmd
runner
main.go
internal
jobbuilder
job_template.go
+10
-1
Dockerfile
reviewed
···
1
1
# Build both binaries
2
2
+
# Use BUILDPLATFORM so Go runs natively, cross-compile for target arch
2
3
FROM --platform=$BUILDPLATFORM golang:1.24 AS builder
3
4
4
5
ARG TARGETOS
5
6
ARG TARGETARCH
7
7
+
ARG BUILDARCH
8
8
+
9
9
+
# Install C cross-compiler for CGO when building arm64 from amd64
10
10
+
RUN if [ "$BUILDARCH" = "amd64" ] && [ "$TARGETARCH" = "arm64" ]; then \
11
11
+
apt-get update && apt-get install -y gcc-aarch64-linux-gnu && rm -rf /var/lib/apt/lists/*; \
12
12
+
fi
6
13
7
14
WORKDIR /workspace
8
15
···
27
34
28
35
# Build controller (requires CGO for sqlite3)
29
36
# Use -s -w to strip debug symbols and reduce binary size
30
30
-
RUN CGO_ENABLED=1 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} \
37
37
+
# Use cross-compiler for arm64 when building from amd64
38
38
+
RUN CC=$(if [ "$TARGETARCH" = "arm64" ] && [ "$BUILDARCH" = "amd64" ]; then echo "aarch64-linux-gnu-gcc"; else echo "gcc"; fi) && \
39
39
+
CGO_ENABLED=1 CC=$CC GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} \
31
40
go build -a -ldflags='-s -w' -o manager ./cmd/controller
32
41
33
42
# Unified image with both binaries
+13
-39
Makefile
reviewed
···
163
163
run: manifests generate fmt vet ## Run a controller from your host.
164
164
go run ./cmd/controller/main.go
165
165
166
166
-
# If you wish to build the manager image targeting other platforms you can use the --platform flag.
167
167
-
# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.
166
166
+
# Build multi-arch docker image using buildx (native builds per arch due to CGO)
168
167
# More info: https://docs.docker.com/develop/develop-images/build_enhancements/
169
168
.PHONY: docker-build
170
170
-
docker-build: ## Build docker image with the manager.
171
171
-
cd .. && $(CONTAINER_TOOL) build -f loom/Dockerfile -t ${IMG} .
172
172
-
173
173
-
.PHONY: docker-push
174
174
-
docker-push: ## Push docker image with the manager.
175
175
-
$(CONTAINER_TOOL) push ${IMG}
169
169
+
docker-build: setup-buildx ## Build and push multi-arch docker image.
170
170
+
cd .. && $(CONTAINER_TOOL) buildx build \
171
171
+
--builder loom-builder \
172
172
+
--platform=linux/amd64,linux/arm64 \
173
173
+
--push \
174
174
+
--tag ${IMG} \
175
175
+
-f loom/Dockerfile .
176
176
177
177
-
# Runner image configuration
178
178
-
RUNNER_IMG ?= atcr.io/evan.jarrett.net/loom-runner:$(VERSION)
179
179
-
RUNNER_IMG_LATEST ?= atcr.io/evan.jarrett.net/loom-runner:latest
180
180
-
RUNNER_PLATFORMS ?= linux/amd64,linux/arm64
177
177
+
.PHONY: docker-build-local
178
178
+
docker-build-local: ## Build docker image for local arch only (no push).
179
179
+
cd .. && $(CONTAINER_TOOL) build -f loom/Dockerfile -t ${IMG} .
181
180
182
182
-
.PHONY: setup-buildx-runner
183
183
-
setup-buildx-runner: ## Set up buildx builder with credential access for runner builds
181
181
+
.PHONY: setup-buildx
182
182
+
setup-buildx: ## Set up buildx builder with credential access for multi-arch builds
184
183
@echo "Setting up loom-builder with credential access..."
185
184
# Remove existing builder if present
186
185
- $(CONTAINER_TOOL) buildx rm loom-builder 2>/dev/null || true
···
209
208
fi
210
209
@echo "✓ Builder setup complete!"
211
210
212
212
-
.PHONY: docker-build-runner
213
213
-
docker-build-runner: setup-buildx-runner ## Build multi-arch docker image for the loom runner binary.
214
214
-
@if [ -n "$(DOCKER_USERNAME)" ] && [ -n "$(DOCKER_PASSWORD)" ]; then \
215
215
-
echo "Authenticating to atcr.io with username/password..."; \
216
216
-
echo "$(DOCKER_PASSWORD)" | $(CONTAINER_TOOL) login atcr.io -u "$(DOCKER_USERNAME)" --password-stdin; \
217
217
-
echo "Copying updated credentials to builder..."; \
218
218
-
$(CONTAINER_TOOL) cp $(HOME)/.docker/config.json buildx_buildkit_loom-builder0:/root/.docker/config.json; \
219
219
-
else \
220
220
-
echo "⚠ No DOCKER_USERNAME/DOCKER_PASSWORD set. Will try credential helper (may not work in container)."; \
221
221
-
fi
222
222
-
cd .. && $(CONTAINER_TOOL) buildx build \
223
223
-
--builder loom-builder \
224
224
-
--platform=$(RUNNER_PLATFORMS) \
225
225
-
--tag $(RUNNER_IMG) \
226
226
-
--tag $(RUNNER_IMG_LATEST) \
227
227
-
--push \
228
228
-
--file Dockerfile.runner \
229
229
-
.
230
230
-
231
231
-
.PHONY: docker-build-runner-local
232
232
-
docker-build-runner-local: ## Build local runner image (single arch) for testing.
233
233
-
cd .. && $(CONTAINER_TOOL) build \
234
234
-
--tag $(RUNNER_IMG_LATEST) \
235
235
-
--file Dockerfile.runner \
236
236
-
.
237
211
238
212
.PHONY: test-registry-auth
239
213
test-registry-auth: ## Test registry authentication before building
+40
cmd/runner/main.go
reviewed
···
32
32
}
33
33
34
34
func main() {
35
35
+
// Handle --install flag for self-copying (used by init container in distroless image)
36
36
+
if len(os.Args) >= 3 && os.Args[1] == "--install" {
37
37
+
if err := installSelf(os.Args[2]); err != nil {
38
38
+
fmt.Fprintf(os.Stderr, "install failed: %v\n", err)
39
39
+
os.Exit(1)
40
40
+
}
41
41
+
os.Exit(0)
42
42
+
}
43
43
+
35
44
if err := run(); err != nil {
36
45
fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
37
46
os.Exit(1)
38
47
}
48
48
+
}
49
49
+
50
50
+
// installSelf copies this executable to the destination path
51
51
+
func installSelf(dst string) error {
52
52
+
src, err := os.Executable()
53
53
+
if err != nil {
54
54
+
return fmt.Errorf("failed to get executable path: %w", err)
55
55
+
}
56
56
+
57
57
+
srcFile, err := os.Open(src)
58
58
+
if err != nil {
59
59
+
return fmt.Errorf("failed to open source: %w", err)
60
60
+
}
61
61
+
defer srcFile.Close()
62
62
+
63
63
+
dstFile, err := os.Create(dst)
64
64
+
if err != nil {
65
65
+
return fmt.Errorf("failed to create destination: %w", err)
66
66
+
}
67
67
+
defer dstFile.Close()
68
68
+
69
69
+
if _, err := io.Copy(dstFile, srcFile); err != nil {
70
70
+
return fmt.Errorf("failed to copy: %w", err)
71
71
+
}
72
72
+
73
73
+
// Make executable
74
74
+
if err := os.Chmod(dst, 0755); err != nil {
75
75
+
return fmt.Errorf("failed to chmod: %w", err)
76
76
+
}
77
77
+
78
78
+
return nil
39
79
}
40
80
41
81
func run() error {
+1
-1
internal/jobbuilder/job_template.go
reviewed
···
268
268
{
269
269
Name: "install-runner",
270
270
Image: config.LoomImage,
271
271
-
Command: []string{"cp", "/loom-runner", "/runner-bin/loom-runner"},
271
271
+
Command: []string{"/loom-runner", "--install", "/runner-bin/loom-runner"},
272
272
SecurityContext: &corev1.SecurityContext{
273
273
AllowPrivilegeEscalation: &[]bool{false}[0],
274
274
RunAsNonRoot: &[]bool{true}[0],