Skip to content

Commit a2e077b

Browse files
authored
feat: adding config option for SOCI installation on VM (runfinch#506)
*Description of changes:* Adding snapshotter option in config file and integrating SOCI with Finch **Setting Snapshotters** - Users can set the snapshotters they'd like to use by listing them in the config file as the ```snapshotters``` value. - All listed snapshotters will be installed if they are currently supported with Finch. As of this PR the two that are supported are OverlayFS (the default snapshotter) and SOCI. - The first snapshotter listed will be made the default snapshotter used. Other snapshotters can be used by specifying when running commands (i.e. ```finch --snapshotter={exampleSnapshotter} pull ...```) *Example ```snapshotters``` setting in ```finch.yaml```:* ```yaml snapshotters: -overlayfs -soci ``` This would install SOCI on the user's VM and allow for it to be used when specified in commands, but would keep OverlayFS as the default for commands **To Install SOCI** - SOCI can be setup with minimal configuration by adding ```"- soci"``` to the ```snapshotters``` list in ```finch.yaml``` . - Once the option has been set SOCI will be installed on either ```finch vm init``` or ```finch vm start```. The binary will be downloaded onto the user's VM and the needed settings for SOCI containerd configuration will be appended to ```etc/containerd/config.toml``` in the VM. If SOCI is the first snapshotter listed it will also be set as the default nerdctl snapshotter in the user's VM which would allow the user to pull images with SOCI simply by doing ```finch pull ...``` . **To Stop Using SOCI by default** - ```"- soci"``` should be removed or not be the first snapshotter of the ```snapshotters``` list in ```finch.yaml``` . The user can also make the first snapshotter listed ```"- overlayfs"``` to revert back to the original default used by nerdctl/containerd. **Note:** removing a snapshotter from ```snapshotters``` list will not uninstall the snapshotter from the user's VM. In order to fully uninstall the snapshotter the user must shell into the VM and remove the binaries from ```/usr/local/bin```. Testing done: [ x] I've reviewed the guidance in CONTRIBUTING.md License Acceptance By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. --------- Signed-off-by: Channing Gaddy <[email protected]>
1 parent fca1828 commit a2e077b

File tree

8 files changed

+485
-1
lines changed

8 files changed

+485
-1
lines changed

Diff for: README.md

+10
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,16 @@ An example `finch.yaml` looks like this:
9999
cpus: 4
100100
# memory: the amount of memory to dedicate to the virtual machine. (required)
101101
memory: 4GiB
102+
# snapshotters: the snapshotters a user wants to use (the first snapshotter will be set as the default snapshotter)
103+
# Supported Snapshotters List:
104+
# - soci https://github.com/awslabs/soci-snapshotter/tree/main
105+
# Once the option has been set the snapshotters will be installed on either finch vm init or finch vm start.
106+
# The snapshotters binary will be downloaded on the virtual machine and will be configured and ready for use.
107+
# To change your default snpahotter back to overlayfs, simply remove the snapshotters value from finch.yaml or set snapshotters to `overlayfs`
108+
# To completely remove the snapshotters' binaries, shell into your VM and remove /usr/local/bin/{snapshotter binary}
109+
# and remove the snapshotter configuration in the containerd config file found at /etc/containerd/config.toml
110+
snapshotters:
111+
- soci
102112
# creds_helpers: a list of credential helpers that will be installed and configured automatically.
103113
# Supported Credential Helpers List:
104114
# - ecr-login https://github.com/awslabs/amazon-ecr-credential-helper

Diff for: e2e/vm/soci_test.go

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package vm
5+
6+
import (
7+
"os"
8+
"path/filepath"
9+
"strings"
10+
11+
"github.com/onsi/ginkgo/v2"
12+
"github.com/onsi/gomega"
13+
"github.com/runfinch/common-tests/command"
14+
"github.com/runfinch/common-tests/option"
15+
)
16+
17+
const (
18+
ffmpegSociImage = "public.ecr.aws/soci-workshop-examples/ffmpeg:latest"
19+
sociMountString = "fuse.rawBridge"
20+
)
21+
22+
var testSoci = func(o *option.Option, installed bool) {
23+
ginkgo.Describe("SOCI", func() {
24+
var limactlO *option.Option
25+
var limaHomePathEnv string
26+
var wd string
27+
var err error
28+
29+
ginkgo.BeforeEach(func() {
30+
wd, err = os.Getwd()
31+
gomega.Expect(err).Should(gomega.BeNil())
32+
limaHomePathEnv = "LIMA_HOME=" + filepath.Join(wd, "../../_output/lima/data")
33+
limactlO, err = option.New([]string{filepath.Join(wd, "../../_output/lima/bin/limactl")},
34+
option.Env([]string{limaHomePathEnv}))
35+
gomega.Expect(err).Should(gomega.BeNil())
36+
})
37+
38+
ginkgo.It("finch pull should have same mounts as nerdctl pull with SOCI", func() {
39+
resetVM(o, installed)
40+
resetDisks(o, installed)
41+
writeFile(finchConfigFilePath, []byte("cpus: 6\nmemory: 4GiB\nsnapshotters:\n "+
42+
"- soci\nvmType: qemu\nrosetta: false"))
43+
command.New(o, virtualMachineRootCmd, "init").WithTimeoutInSeconds(600).Run()
44+
command.New(o, "pull", ffmpegSociImage).WithTimeoutInSeconds(30).Run()
45+
finchPullMounts := countMounts(limactlO)
46+
command.Run(o, "rmi", "-f", ffmpegSociImage)
47+
command.New(limactlO, "shell", "finch",
48+
"sudo", "nerdctl", "--snapshotter=soci", "pull", ffmpegSociImage).WithTimeoutInSeconds(30).Run()
49+
nerdctlPullMounts := countMounts(limactlO)
50+
command.Run(o, "rmi", "-f", ffmpegSociImage)
51+
gomega.Expect(finchPullMounts).Should(gomega.Equal(nerdctlPullMounts))
52+
})
53+
54+
ginkgo.It("finch run should have same mounts as nerdctl run with SOCI", func() {
55+
resetVM(o, installed)
56+
resetDisks(o, installed)
57+
writeFile(finchConfigFilePath, []byte("cpus: 6\nmemory: 4GiB\nsnapshotters:\n "+
58+
"- soci\nvmType: qemu\nrosetta: false"))
59+
command.New(o, virtualMachineRootCmd, "init").WithTimeoutInSeconds(600).Run()
60+
command.New(o, "run", ffmpegSociImage).WithTimeoutInSeconds(30).Run()
61+
finchPullMounts := countMounts(limactlO)
62+
command.Run(o, "rmi", "-f", ffmpegSociImage)
63+
command.New(limactlO, "shell", "finch",
64+
"sudo", "nerdctl", "--snapshotter=soci", "run", ffmpegSociImage).WithTimeoutInSeconds(30).Run()
65+
nerdctlPullMounts := countMounts(limactlO)
66+
command.Run(o, "rmi", "-f", ffmpegSociImage)
67+
gomega.Expect(finchPullMounts).Should(gomega.Equal(nerdctlPullMounts))
68+
})
69+
})
70+
}
71+
72+
// counts the mounts present in the VM after pulling an image.
73+
func countMounts(limactlO *option.Option) int {
74+
mountOutput := command.StdoutStr(limactlO, "shell", "finch", "mount")
75+
return strings.Count(mountOutput, sociMountString)
76+
}

Diff for: e2e/vm/vm_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ func TestVM(t *testing.T) {
5353
testVirtualizationFrameworkAndRosetta(o, *e2e.Installed)
5454
testSupportBundle(o)
5555
testCredHelper(o, *e2e.Installed, *e2e.Registry)
56+
testSoci(o, *e2e.Installed)
5657
})
5758

5859
gomega.RegisterFailHandler(ginkgo.Fail)

Diff for: go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ require (
2020
github.com/stretchr/testify v1.8.4
2121
github.com/xorcare/pointer v1.2.2
2222
golang.org/x/crypto v0.11.0
23+
golang.org/x/exp v0.0.0-20230810033253-352e893a4cad
2324
golang.org/x/tools v0.11.1
2425
gopkg.in/yaml.v3 v3.0.1
2526
k8s.io/apimachinery v0.27.4

Diff for: go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
400400
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
401401
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
402402
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
403+
golang.org/x/exp v0.0.0-20230810033253-352e893a4cad h1:g0bG7Z4uG+OgH2QDODnjp6ggkk1bJDsINcuWmJN1iJU=
404+
golang.org/x/exp v0.0.0-20230810033253-352e893a4cad/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
403405
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
404406
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
405407
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

Diff for: pkg/config/config.go

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ type AdditionalDirectory struct {
3535
type Finch struct {
3636
CPUs *int `yaml:"cpus"`
3737
Memory *string `yaml:"memory"`
38+
// Snapshotters: the snapshotters that will be installed and configured automatically on vm init or on vm start.
39+
// Values: `soci` for SOCI snapshotter; `overlayfs` for default overlay snapshotter.
40+
Snapshotters []string `yaml:"snapshotters,omitempty"`
3841
// CredsHelper: the list of credential helpers that will be installed and configured automatically on vm init or on vm start
3942
CredsHelpers []string `yaml:"creds_helpers,omitempty"`
4043
// AdditionalDirectories are the work directories that are not supported by default. In macOS, only home directory is supported by default.

Diff for: pkg/config/lima_config_applier.go

+97-1
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,40 @@ import (
1010
"github.com/lima-vm/lima/pkg/limayaml"
1111
"github.com/spf13/afero"
1212
"github.com/xorcare/pointer"
13+
"golang.org/x/exp/slices"
1314
"gopkg.in/yaml.v3"
1415

1516
"github.com/runfinch/finch/pkg/command"
1617
"github.com/runfinch/finch/pkg/system"
1718
)
1819

19-
const userModeEmulationProvisioningScriptHeader = "# cross-arch tools"
20+
const (
21+
sociVersion = "0.3.0"
22+
sociInstallationProvisioningScriptHeader = "# soci installation and configuring"
23+
sociFileNameFormat = "soci-snapshotter-%s-linux-%s.tar.gz"
24+
sociDownloadURLFormat = "https://github.com/awslabs/soci-snapshotter/releases/download/v%s/%s"
25+
sociInstallationScriptFormat = `%s
26+
if [ ! -f /usr/local/bin/soci ]; then
27+
# download soci
28+
set -e
29+
curl --retry 2 --retry-max-time 120 -OL "%s"
30+
# move to usr/local/bin
31+
tar -C /usr/local/bin -xvf %s soci soci-snapshotter-grpc
32+
fi
33+
34+
# changing containerd config
35+
export config=etc/containerd/config.toml
36+
echo " [proxy_plugins.soci]
37+
type = \"snapshot\"
38+
address = \"/run/soci-snapshotter-grpc/soci-snapshotter-grpc.sock\" " >> $config
39+
40+
sudo systemctl restart containerd.service
41+
sudo soci-snapshotter-grpc &> ~/soci-snapshotter-logs &
42+
43+
`
44+
45+
userModeEmulationProvisioningScriptHeader = "# cross-arch tools"
46+
)
2047

2148
// LimaConfigApplierSystemDeps contains the system dependencies for LimaConfigApplier.
2249
//
@@ -96,6 +123,24 @@ func (lca *limaConfigApplier) Apply(isInit bool) error {
96123
limaCfg = *cfgAfterInit
97124
}
98125

126+
supportedSnapshotters := []string{"overlayfs", "soci"}
127+
snapshotters := make(map[string][2]bool)
128+
for i, snapshotter := range lca.cfg.Snapshotters {
129+
if !slices.Contains(supportedSnapshotters, snapshotter) {
130+
return fmt.Errorf("invalid snapshotter config value: %s", snapshotter)
131+
}
132+
133+
isDefaultSnapshotter := false
134+
if i == 0 {
135+
isDefaultSnapshotter = true
136+
}
137+
138+
isEnabled := true
139+
snapshotters[snapshotter] = [2]bool{isEnabled, isDefaultSnapshotter}
140+
}
141+
142+
toggleSnaphotters(&limaCfg, snapshotters)
143+
99144
limaCfgBytes, err := yaml.Marshal(limaCfg)
100145
if err != nil {
101146
return fmt.Errorf("failed to marshal the lima config file: %w", err)
@@ -188,3 +233,54 @@ func hasUserModeEmulationInstallationScript(limaCfg *limayaml.LimaYAML) (int, bo
188233

189234
return scriptIdx, hasCrossArchToolInstallationScript
190235
}
236+
237+
// toggles snapshotters and sets default snapshotter.
238+
func toggleSnaphotters(limaCfg *limayaml.LimaYAML, snapshotters map[string][2]bool) {
239+
toggleOverlayFs(limaCfg, snapshotters["overlayfs"][1])
240+
toggleSoci(limaCfg, snapshotters["soci"][0], snapshotters["soci"][1], sociVersion)
241+
}
242+
243+
// sets overlayfs as the default snapshotter.
244+
func toggleOverlayFs(limaCfg *limayaml.LimaYAML, isDefault bool) {
245+
if isDefault {
246+
limaCfg.Env = map[string]string{"CONTAINERD_SNAPSHOTTER": ""}
247+
}
248+
}
249+
250+
func toggleSoci(limaCfg *limayaml.LimaYAML, enabled bool, isDefault bool, sociVersion string) {
251+
idx, hasScript := findSociInstallationScript(limaCfg)
252+
sociFileName := fmt.Sprintf(sociFileNameFormat, sociVersion, system.NewStdLib().Arch())
253+
sociDownloadURL := fmt.Sprintf(sociDownloadURLFormat, sociVersion, sociFileName)
254+
sociInstallationScript := fmt.Sprintf(sociInstallationScriptFormat, sociInstallationProvisioningScriptHeader, sociDownloadURL, sociFileName)
255+
if !hasScript && enabled {
256+
limaCfg.Provision = append(limaCfg.Provision, limayaml.Provision{
257+
Mode: "system",
258+
Script: sociInstallationScript,
259+
})
260+
} else if hasScript && !enabled {
261+
if len(limaCfg.Provision) > 0 {
262+
limaCfg.Provision = append(limaCfg.Provision[:idx], limaCfg.Provision[idx+1:]...)
263+
}
264+
}
265+
266+
if isDefault {
267+
limaCfg.Env = map[string]string{"CONTAINERD_SNAPSHOTTER": "soci"}
268+
} else {
269+
limaCfg.Env = map[string]string{"CONTAINERD_SNAPSHOTTER": ""}
270+
}
271+
}
272+
273+
func findSociInstallationScript(limaCfg *limayaml.LimaYAML) (int, bool) {
274+
hasSociInstallationScript := false
275+
var scriptIdx int
276+
for idx, prov := range limaCfg.Provision {
277+
trimmed := strings.Trim(prov.Script, " ")
278+
if !hasSociInstallationScript && strings.HasPrefix(trimmed, sociInstallationProvisioningScriptHeader) {
279+
hasSociInstallationScript = true
280+
scriptIdx = idx
281+
break
282+
}
283+
}
284+
285+
return scriptIdx, hasSociInstallationScript
286+
}

0 commit comments

Comments
 (0)