Skip to content

Commit be4e9d0

Browse files
authored
Merge pull request #3213 from HirazawaUi/kinder-support-arm64
Let kinder supports arm64
2 parents 144fc7a + 7e0a2d4 commit be4e9d0

File tree

3 files changed

+181
-7
lines changed

3 files changed

+181
-7
lines changed

kinder/pkg/cri/nodes/containerd/alterhelper.go

Lines changed: 138 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
"os"
2222
"os/exec"
2323
"path/filepath"
24+
"regexp"
25+
"strings"
2426
"time"
2527

2628
"github.com/pkg/errors"
@@ -138,13 +140,143 @@ func ImportImage(bc *bits.BuildContext, tar string) error {
138140
return nil
139141
}
140142

141-
// PreLoadInitImages preload images required by kubeadm-init into the containerd runtime that exists inside a kind(er) node
143+
func getImageNamesFromTarInContainer(bc *bits.BuildContext, tarPath string) ([]string, error) {
144+
cmd := fmt.Sprintf("tar -xf '%s' manifest.json -O | jq -r '.[].RepoTags[]'", tarPath)
145+
imageNames, err := bc.CombinedOutputLinesInContainer("bash", "-c", cmd)
146+
if err != nil {
147+
return nil, fmt.Errorf("failed to get image names from tar %s: %w", tarPath, err)
148+
}
149+
return imageNames, nil
150+
}
151+
152+
// PreLoadInitImages preloads images from .tar files in srcFolder into containerd,
153+
// renaming images to remove architecture suffixes matching the host architecture.
142154
func PreLoadInitImages(bc *bits.BuildContext, srcFolder string) error {
143-
// NB. this code is an extract from "sigs.k8s.io/kind/pkg/build/node"
144-
return bc.RunInContainer(
145-
"bash", "-c",
146-
`find `+srcFolder+` -name *.tar -print0 | xargs -0 -n 1 -P $(nproc) ctr --namespace=k8s.io images import --all-platforms --no-unpack && rm -rf `+srcFolder+`/*.tar`,
147-
)
155+
arch, err := getHostArch(bc)
156+
if err != nil {
157+
return err
158+
}
159+
160+
tarFiles, err := listTarFiles(bc, srcFolder)
161+
if err != nil {
162+
return err
163+
}
164+
if len(tarFiles) == 0 {
165+
log.Infof("No .tar files found in %s", srcFolder)
166+
return nil
167+
}
168+
169+
var importErrors []string
170+
for _, tarFilePath := range tarFiles {
171+
log.Infof("Importing image from: %s", tarFilePath)
172+
imageNames, err := getImageNamesFromTarInContainer(bc, tarFilePath)
173+
if err != nil {
174+
errMsg := fmt.Sprintf("failed to read image name from tar %s: %v", tarFilePath, err)
175+
log.Errorf("Error: %s", errMsg)
176+
importErrors = append(importErrors, errMsg)
177+
continue
178+
}
179+
if len(imageNames) == 0 {
180+
errMsg := fmt.Sprintf("no image name found in tar %s", tarFilePath)
181+
log.Errorf("Error: %s", errMsg)
182+
importErrors = append(importErrors, errMsg)
183+
continue
184+
}
185+
186+
importCmd := fmt.Sprintf("ctr --namespace=k8s.io images import --all-platforms '%s'", tarFilePath)
187+
_, err = bc.CombinedOutputLinesInContainer("bash", "-c", importCmd)
188+
if err != nil {
189+
errMsg := fmt.Sprintf("failed to import image from %s: %v", tarFilePath, err)
190+
log.Errorf("Error: %s", errMsg)
191+
importErrors = append(importErrors, errMsg)
192+
continue
193+
}
194+
195+
for _, imageName := range imageNames {
196+
log.Infof("Successfully imported image: %s", imageName)
197+
base, archSuffix, tag := parseImageArchSuffix(imageName)
198+
if archSuffix != "" && archSuffix == arch {
199+
newImageName := fmt.Sprintf("%s:%s", base, tag)
200+
tagCmd := fmt.Sprintf("ctr --namespace=k8s.io images tag '%s' '%s'", imageName, newImageName)
201+
_, err := bc.CombinedOutputLinesInContainer("bash", "-c", tagCmd)
202+
if err != nil {
203+
log.Warnf("Warning: failed to tag image %s to %s: %v", imageName, newImageName, err)
204+
} else {
205+
log.Infof("Tagged image %s as %s", imageName, newImageName)
206+
}
207+
}
208+
}
209+
210+
rmCmd := fmt.Sprintf("rm -f '%s'", tarFilePath)
211+
_, err = bc.CombinedOutputLinesInContainer("bash", "-c", rmCmd)
212+
if err != nil {
213+
log.Infof("Warning: failed to remove %s: %v", tarFilePath, err)
214+
}
215+
}
216+
217+
if len(importErrors) > 0 {
218+
return fmt.Errorf("some images failed to import: %s", strings.Join(importErrors, "; "))
219+
}
220+
log.Infof("Successfully imported all images from %s", srcFolder)
221+
return nil
222+
}
223+
224+
// getHostArch returns the normalized host architecture string (e.g. amd64, arm64).
225+
func getHostArch(bc *bits.BuildContext) (string, error) {
226+
archLines, err := bc.CombinedOutputLinesInContainer("uname", "-m")
227+
if err != nil {
228+
return "", fmt.Errorf("failed to get host architecture: %w", err)
229+
}
230+
if len(archLines) == 0 {
231+
return "", fmt.Errorf("failed to get host architecture: empty output")
232+
}
233+
arch := strings.TrimSpace(archLines[0])
234+
if arch == "" {
235+
return "", fmt.Errorf("failed to get host architecture: empty architecture string")
236+
}
237+
archMap := map[string]string{
238+
"x86_64": "amd64",
239+
"amd64": "amd64",
240+
"aarch64": "arm64",
241+
"arm64": "arm64",
242+
}
243+
if stdArch, ok := archMap[arch]; ok {
244+
return stdArch, nil
245+
}
246+
log.Warnf("Warning: unsupported architecture %s, proceeding with original name", arch)
247+
return arch, nil
248+
}
249+
250+
// listTarFiles returns a list of .tar file paths in the given srcFolder inside the container.
251+
func listTarFiles(bc *bits.BuildContext, srcFolder string) ([]string, error) {
252+
tarFilesLines, err := bc.CombinedOutputLinesInContainer("find", srcFolder, "-name", "*.tar")
253+
if err != nil {
254+
return nil, fmt.Errorf("failed to list .tar files: %w", err)
255+
}
256+
var validTarFiles []string
257+
for _, tarFilePath := range tarFilesLines {
258+
tarFilePath = strings.TrimSpace(tarFilePath)
259+
if tarFilePath != "" {
260+
validTarFiles = append(validTarFiles, tarFilePath)
261+
}
262+
}
263+
return validTarFiles, nil
264+
}
265+
266+
// parseImageArchSuffix parses image name and returns base, archSuffix, tag.
267+
// e.g. kube-apiserver-arm64:v1.34 => (kube-apiserver, arm64, v1.34)
268+
func parseImageArchSuffix(imageName string) (base, archSuffix, tag string) {
269+
re := regexp.MustCompile(`^(.+)-(amd64|arm64):(.+)$`)
270+
matches := re.FindStringSubmatch(imageName)
271+
if len(matches) == 4 {
272+
return matches[1], matches[2], matches[3]
273+
}
274+
275+
parts := strings.SplitN(imageName, ":", 2)
276+
if len(parts) == 2 {
277+
return parts[0], "", parts[1]
278+
}
279+
return imageName, "", ""
148280
}
149281

150282
// Commit a kind(er) node image that uses the containerd runtime internally
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package containerd
18+
19+
import (
20+
"testing"
21+
)
22+
23+
func TestParseImageArchSuffix(t *testing.T) {
24+
cases := []struct {
25+
input string
26+
base string
27+
arch string
28+
tag string
29+
}{
30+
{"registry.k8s.io/kube-apiserver-arm64:v1.34", "registry.k8s.io/kube-apiserver", "arm64", "v1.34"},
31+
{"registry.k8s.io/kube-apiserver-amd64:v1.34", "registry.k8s.io/kube-apiserver", "amd64", "v1.34"},
32+
{"registry.k8s.io/kube-apiserver:v1.34", "registry.k8s.io/kube-apiserver", "", "v1.34"},
33+
{"registry.k8s.io/kube-apiserver:dev", "registry.k8s.io/kube-apiserver", "", "dev"},
34+
}
35+
for _, c := range cases {
36+
base, arch, tag := parseImageArchSuffix(c.input)
37+
if base != c.base || arch != c.arch || tag != c.tag {
38+
t.Errorf("parseImageArchSuffix(%q) = (%q, %q, %q), want (%q, %q, %q)", c.input, base, arch, tag, c.base, c.arch, c.tag)
39+
}
40+
}
41+
}

kinder/pkg/extract/extract.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"os"
3333
"path"
3434
"path/filepath"
35+
"runtime"
3536
"strings"
3637
"time"
3738

@@ -313,7 +314,7 @@ func extractFromHTTP(src string, files []string, dst string, m fileNameMutator,
313314

314315
// in case the source is a Kubernetes build, add bin/OS/ARCH to the src uri
315316
if strings.HasPrefix(src, releaseBuildURepository) || strings.HasPrefix(src, ciBuildRepository) {
316-
src = fmt.Sprintf("%s/bin/linux/amd64", src)
317+
src = fmt.Sprintf("%s/bin/linux/%s", src, runtime.GOARCH)
317318
}
318319

319320
// Download the files.

0 commit comments

Comments
 (0)