Skip to content
This repository was archived by the owner on Mar 16, 2024. It is now read-only.

Commit 87d1e0f

Browse files
authored
add: volume preloading via ?preload=true directive in Acornfile (manager#1598) (#2351)
Signed-off-by: Thorsten Klein <tk@thklein.io>
1 parent 6cb4af4 commit 87d1e0f

File tree

13 files changed

+417
-28
lines changed

13 files changed

+417
-28
lines changed

Dockerfile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ RUN --mount=type=cache,target=/go/pkg --mount=type=cache,target=/root/.cache/go-
1414

1515
FROM ghcr.io/acorn-io/images-mirror/golang:1.21-alpine AS loglevel
1616
WORKDIR /usr/src
17-
RUN apk -U add curl
17+
RUN apk -U add curl && rm -rf /var/cache/apk/*
1818
RUN curl -sfL https://github.com/acorn-io/loglevel/archive/refs/tags/v0.1.6.tar.gz | tar xzf - --strip-components=1
1919
RUN --mount=type=cache,target=/go/pkg --mount=type=cache,target=/root/.cache/go-build CGO_ENABLED=0 go build -o /usr/local/bin/loglevel -ldflags "-s -w"
2020

@@ -25,14 +25,14 @@ COPY --from=sleep /sleep /src/pkg/controller/appdefinition/embed/acorn-sleep
2525
RUN --mount=type=cache,target=/go/pkg --mount=type=cache,target=/root/.cache/go-build GO_TAGS=netgo,image make build
2626

2727
FROM ghcr.io/acorn-io/images-mirror/nginx:1.23.2-alpine AS base
28-
RUN apk add --no-cache ca-certificates iptables ip6tables fuse3 git openssh pigz xz \
28+
RUN apk add --no-cache ca-certificates iptables ip6tables fuse3 git openssh pigz xz busybox-static \
2929
&& ln -s fusermount3 /usr/bin/fusermount
3030
RUN adduser -D acorn
3131
RUN mkdir apiserver.local.config && chown acorn apiserver.local.config
3232
RUN --mount=from=binfmt,src=/usr/bin,target=/usr/src for i in aarch64 x86_64; do if [ -e /usr/src/qemu-$i ]; then cp /usr/src/qemu-$i /usr/bin; fi; done
3333
RUN --mount=from=buildkit,src=/usr/bin,target=/usr/src for i in aarch64 x86_64; do if [ -e /usr/src/buildkit-qemu-$i ]; then cp /usr/src/buildkit-qemu-$i /usr/bin; fi; done
3434
COPY --from=binfmt /usr/bin/binfmt /usr/local/bin
35-
COPY --from=buildkit /usr/bin/buildkitd /usr/bin/buildctl /usr/bin/buildkit-runc /usr/local/bin
35+
COPY --from=buildkit /usr/bin/buildkitd /usr/bin/buildctl /usr/bin/buildkit-runc /usr/local/bin/
3636
COPY --from=registry /etc/docker/registry/config.yml /etc/docker/registry/config.yml
3737
COPY --from=registry /bin/registry /usr/local/bin
3838
COPY --from=klipper-lb /usr/bin/entry /usr/local/bin/klipper-lb
@@ -43,6 +43,7 @@ COPY --from=loglevel /usr/local/bin/loglevel /usr/local/bin/
4343
VOLUME /var/lib/buildkit
4444

4545
COPY /scripts/acorn-helper-init /usr/local/bin
46+
COPY /scripts/acorn-busybox-init /usr/local/bin
4647
COPY /scripts/acorn-job-helper-init /usr/local/bin
4748
COPY /scripts/acorn-job-helper-shutdown /usr/local/bin
4849
COPY /scripts/acorn-job-get-output /usr/local/bin

integration/client/imagerules/testdata/nested-perms/Acornfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
containers: {
22
"rootapp": {
3-
image: "nginx:latest"
3+
image: "ghcr.io/acorn-io/images-mirror/nginx:latest"
44
permissions: rules: [{
55
verbs: ["get"]
66
apiGroups: ["foo.bar.com"]

integration/client/imagerules/testdata/nested-perms/nested.acorn

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
containers: {
22
"awsapp": {
3-
image: "nginx:latest"
3+
image: "ghcr.io/acorn-io/images-mirror/nginx:latest"
44
permissions: {
55
rules: [{
66
verbs: ["get"]

pkg/apis/internal.acorn.io/v1/appspec.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ type VolumeMount struct {
165165
Volume string `json:"volume,omitempty"`
166166
SubPath string `json:"subPath,omitempty"`
167167
ContextDir string `json:"contextDir,omitempty"`
168+
Preload bool `json:"preload,omitempty"`
168169
Secret VolumeSecretMount `json:"secret,omitempty"`
169170
}
170171

pkg/apis/internal.acorn.io/v1/unmarshal.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -974,7 +974,7 @@ func (in *VolumeMount) UnmarshalJSON(data []byte) error {
974974
} else if strings.HasPrefix(s, "./") {
975975
in.ContextDir = s
976976
} else {
977-
in.Volume, in.SubPath, err = parseVolumeReference(s)
977+
in.Volume, in.SubPath, in.Preload, err = parseVolumeReference(s)
978978
if err != nil {
979979
return err
980980
}
@@ -1411,14 +1411,19 @@ func parseVolumeDefinition(anonName, s string) (VolumeBinding, error) {
14111411
return result, nil
14121412
}
14131413

1414-
func parseVolumeReference(s string) (string, string, error) {
1414+
// parseVolumeReference parses a volume reference string into its components, including query parameters
1415+
// @return string - volume reference (unmodified)
1416+
// @return string - subpath that should be mounted (query param)
1417+
// @return bool - preload: if the volume should be pre-populated with data from the container image (query param)
1418+
// @return error - parse error
1419+
func parseVolumeReference(s string) (string, string, bool, error) {
14151420
if !strings.HasPrefix(s, "volume://") && !strings.HasPrefix(s, "ephemeral://") {
1416-
return s, "", nil
1421+
return s, "", false, nil
14171422
}
14181423

14191424
u, err := url.Parse(s)
14201425
if err != nil {
1421-
return "", "", fmt.Errorf("parsing volume reference %s: %w", s, err)
1426+
return "", "", false, fmt.Errorf("parsing volume reference %s: %w", s, err)
14221427
}
14231428

14241429
subPath := u.Query().Get("subPath")
@@ -1429,7 +1434,16 @@ func parseVolumeReference(s string) (string, string, error) {
14291434
subPath = u.Query().Get("sub-path")
14301435
}
14311436

1432-
return s, subPath, nil
1437+
preload := false
1438+
preloadStr := u.Query().Get("preload")
1439+
if preloadStr != "" {
1440+
preload, err = strconv.ParseBool(preloadStr)
1441+
if err != nil {
1442+
return "", "", false, fmt.Errorf("malformed ?preload value %q: %v", preloadStr, err)
1443+
}
1444+
}
1445+
1446+
return s, subPath, preload, nil
14331447
}
14341448

14351449
func MustParseResourceQuantity(s Quantity) *resource.Quantity {

pkg/apis/internal.acorn.io/v1/unmarshal_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,29 @@ func FuzzUserContextUnmarshalJSON(f *testing.F) {
190190
}
191191
})
192192
}
193+
194+
func TestParseVolumeReference(t *testing.T) {
195+
s, subPath, preload, err := parseVolumeReference("name")
196+
require.NoError(t, err)
197+
require.Equal(t, "name", s)
198+
require.Empty(t, subPath)
199+
require.False(t, preload)
200+
201+
_, subPath, preload, err = parseVolumeReference("volume://foo?subPath=bar")
202+
require.NoError(t, err)
203+
require.False(t, preload)
204+
require.Equal(t, "bar", subPath)
205+
206+
_, subPath, preload, err = parseVolumeReference("volume://foo?preload=true&subPath=bar")
207+
require.NoError(t, err)
208+
require.Equal(t, "bar", subPath)
209+
require.True(t, preload)
210+
211+
_, subPath, preload, err = parseVolumeReference("volume://foo?preload=false")
212+
require.NoError(t, err)
213+
require.Empty(t, subPath)
214+
require.False(t, preload)
215+
216+
_, _, _, err = parseVolumeReference("volume://foo?preload=foo")
217+
require.Error(t, err)
218+
}

pkg/controller/appdefinition/deploy.go

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/acorn-io/z"
3232
"github.com/google/go-containerregistry/pkg/name"
3333
"github.com/rancher/wrangler/pkg/data/convert"
34+
wname "github.com/rancher/wrangler/pkg/name"
3435
appsv1 "k8s.io/api/apps/v1"
3536
corev1 "k8s.io/api/core/v1"
3637
apierror "k8s.io/apimachinery/pkg/api/errors"
@@ -213,7 +214,7 @@ func hasContextDir(container v1.Container) bool {
213214
return false
214215
}
215216

216-
func toContainers(app *v1.AppInstance, tag name.Reference, name string, container v1.Container, interpolator *secrets.Interpolator, addWait bool) ([]corev1.Container, []corev1.Container) {
217+
func toContainers(app *v1.AppInstance, tag name.Reference, name string, container v1.Container, interpolator *secrets.Interpolator, addWait, addBusybox bool) ([]corev1.Container, []corev1.Container) {
217218
var (
218219
containers []corev1.Container
219220
initContainers []corev1.Container
@@ -234,7 +235,49 @@ func toContainers(app *v1.AppInstance, tag name.Reference, name string, containe
234235
})
235236
}
236237

238+
if addBusybox {
239+
// Drop the static busybox binary into a shared volume so that we can use it in initContainers.
240+
initContainers = append(initContainers, corev1.Container{
241+
Name: wname.SafeConcatName("acorn-helper-busybox", string(app.UID)),
242+
Image: system.DefaultImage(),
243+
Command: []string{"acorn-busybox-init"},
244+
VolumeMounts: []corev1.VolumeMount{
245+
{
246+
Name: sanitizeVolumeName(AcornHelper),
247+
MountPath: AcornHelperPath,
248+
},
249+
},
250+
},
251+
)
252+
}
253+
237254
newContainer := toContainer(app, tag, name, container, interpolator, addWait && len(container.Ports) > 0)
255+
for src, dir := range container.Dirs {
256+
if dir.Preload {
257+
// If a directory is marked for preload, then add an initContainer that copies the directory into the shared volume.
258+
// Data will be copied to the data/ subdirectory and we'll drop a .preload-done file in the root to indicate that
259+
// the copy has been completed (and should not be repeated).
260+
initContainers = append(initContainers, corev1.Container{
261+
Name: wname.SafeConcatName("acorn-preload-dir", sanitizeVolumeName(src), string(app.UID)),
262+
Image: newContainer.Image,
263+
Command: []string{AcornHelperBusyboxPath, "sh", "-c"},
264+
ImagePullPolicy: corev1.PullIfNotPresent,
265+
// cp -aT == copy recursively, following symlinks and preserving file attributes, only copy the contents of the source directory
266+
Args: []string{fmt.Sprintf("if [ ! -f /dest/.preload-done ]; then mkdir -p /dest/data && cp -aT %s /dest/data && date > /dest/.preload-done; fi", src)},
267+
VolumeMounts: []corev1.VolumeMount{
268+
{
269+
Name: sanitizeVolumeName(dir.Volume),
270+
MountPath: "/dest",
271+
SubPath: dir.SubPath,
272+
},
273+
{
274+
Name: sanitizeVolumeName(AcornHelper),
275+
MountPath: AcornHelperPath,
276+
},
277+
},
278+
})
279+
}
280+
}
238281
containers = append(containers, newContainer)
239282
for _, entry := range typed.Sorted(container.Sidecars) {
240283
newContainer = toContainer(app, tag, entry.Key, entry.Value, interpolator, addWait && len(entry.Value.Ports) > 0)
@@ -293,11 +336,18 @@ func toMounts(app *v1.AppInstance, container v1.Container, interpolation *secret
293336
helperMounted = true
294337
}
295338
} else if mount.Secret.Name == "" {
296-
result = append(result, corev1.VolumeMount{
339+
vm := corev1.VolumeMount{
297340
Name: sanitizeVolumeName(mount.Volume),
298341
MountPath: path.Join("/", mountPath),
299342
SubPath: mount.SubPath,
300-
})
343+
}
344+
if mount.Preload {
345+
// Preloaded contents land in the data/ subdirectory, since we have to drop the .preload-done file
346+
// in the root of the volume to indicate that the copy has been completed
347+
// and that may cause issues with applications that dislike unknown files in their path.
348+
vm.SubPath = path.Join(vm.SubPath, "data")
349+
}
350+
result = append(result, vm)
301351
} else {
302352
result = append(result, corev1.VolumeMount{
303353
Name: secretPodVolName(mount.Secret.Name),
@@ -670,20 +720,27 @@ func getSecretAnnotations(req router.Request, appInstance *v1.AppInstance, conta
670720

671721
func toDeployment(req router.Request, appInstance *v1.AppInstance, tag name.Reference, name string, container v1.Container, pullSecrets *PullSecrets, interpolator *secrets.Interpolator) (*appsv1.Deployment, error) {
672722
var (
673-
stateful = isStateful(appInstance, container)
674-
addWait = !stateful && len(acornSleepBinary) > 0 && z.Dereference(container.Scale) > 1 && !appInstance.Status.GetDevMode()
723+
stateful = isStateful(appInstance, container)
724+
addWait = !stateful && len(acornSleepBinary) > 0 && z.Dereference(container.Scale) > 1 && !appInstance.Status.GetDevMode()
725+
addBusybox = false
675726
)
676727

728+
for _, v := range container.Dirs {
729+
if v.Preload {
730+
addBusybox = true
731+
}
732+
}
733+
677734
interpolator = interpolator.ForContainer(name)
678735

679-
containers, initContainers := toContainers(appInstance, tag, name, container, interpolator, addWait)
736+
containers, initContainers := toContainers(appInstance, tag, name, container, interpolator, addWait, addBusybox)
680737

681738
secretAnnotations, err := getSecretAnnotations(req, appInstance, container, interpolator)
682739
if err != nil {
683740
return nil, err
684741
}
685742

686-
volumes, err := toVolumes(appInstance, container, interpolator, addWait)
743+
volumes, err := toVolumes(appInstance, container, interpolator, addWait, addBusybox)
687744
if err != nil {
688745
return nil, err
689746
}

pkg/controller/appdefinition/jobs.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,13 @@ func toJobs(req router.Request, appInstance *v1.AppInstance, pullSecrets *PullSe
4040
if err != nil {
4141
return nil, err
4242
}
43-
job, err := toJob(req, appInstance, pullSecrets, tag, jobName, jobDef, interpolator)
43+
addBusybox := false
44+
for _, v := range jobDef.Dirs {
45+
if v.Preload {
46+
addBusybox = true
47+
}
48+
}
49+
job, err := toJob(req, appInstance, pullSecrets, tag, jobName, jobDef, interpolator, addBusybox)
4450
if err != nil {
4551
return nil, err
4652
}
@@ -85,7 +91,7 @@ func setSecretOutputVolume(containers []corev1.Container) (result []corev1.Conta
8591
return
8692
}
8793

88-
func toJob(req router.Request, appInstance *v1.AppInstance, pullSecrets *PullSecrets, tag name.Reference, name string, container v1.Container, interpolator *secrets.Interpolator) (kclient.Object, error) {
94+
func toJob(req router.Request, appInstance *v1.AppInstance, pullSecrets *PullSecrets, tag name.Reference, name string, container v1.Container, interpolator *secrets.Interpolator, addBusybox bool) (kclient.Object, error) {
8995
interpolator = interpolator.ForJob(name)
9096
jobEventName := jobs.GetEvent(name, appInstance)
9197

@@ -100,7 +106,7 @@ func toJob(req router.Request, appInstance *v1.AppInstance, pullSecrets *PullSec
100106
return nil, nil
101107
}
102108

103-
containers, initContainers := toContainers(appInstance, tag, name, container, interpolator, false)
109+
containers, initContainers := toContainers(appInstance, tag, name, container, interpolator, false, addBusybox)
104110

105111
containers = append(containers, corev1.Container{
106112
Name: jobs.Helper,
@@ -114,7 +120,7 @@ func toJob(req router.Request, appInstance *v1.AppInstance, pullSecrets *PullSec
114120
return nil, err
115121
}
116122

117-
volumes, err := toVolumes(appInstance, container, interpolator, false)
123+
volumes, err := toVolumes(appInstance, container, interpolator, false, addBusybox)
118124
if err != nil {
119125
return nil, err
120126
}

0 commit comments

Comments
 (0)