Skip to content

Commit

Permalink
feat: adding config option for SOCI installation on VM (#506)
Browse files Browse the repository at this point in the history
*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 <chxgaddy@amazon.com>
  • Loading branch information
CodeChanning authored Aug 11, 2023
1 parent fca1828 commit a2e077b
Show file tree
Hide file tree
Showing 8 changed files with 485 additions and 1 deletion.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ An example `finch.yaml` looks like this:
cpus: 4
# memory: the amount of memory to dedicate to the virtual machine. (required)
memory: 4GiB
# snapshotters: the snapshotters a user wants to use (the first snapshotter will be set as the default snapshotter)
# Supported Snapshotters List:
# - soci https://github.com/awslabs/soci-snapshotter/tree/main
# Once the option has been set the snapshotters will be installed on either finch vm init or finch vm start.
# The snapshotters binary will be downloaded on the virtual machine and will be configured and ready for use.
# To change your default snpahotter back to overlayfs, simply remove the snapshotters value from finch.yaml or set snapshotters to `overlayfs`
# To completely remove the snapshotters' binaries, shell into your VM and remove /usr/local/bin/{snapshotter binary}
# and remove the snapshotter configuration in the containerd config file found at /etc/containerd/config.toml
snapshotters:
- soci
# creds_helpers: a list of credential helpers that will be installed and configured automatically.
# Supported Credential Helpers List:
# - ecr-login https://github.com/awslabs/amazon-ecr-credential-helper
Expand Down
76 changes: 76 additions & 0 deletions e2e/vm/soci_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package vm

import (
"os"
"path/filepath"
"strings"

"github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
"github.com/runfinch/common-tests/command"
"github.com/runfinch/common-tests/option"
)

const (
ffmpegSociImage = "public.ecr.aws/soci-workshop-examples/ffmpeg:latest"
sociMountString = "fuse.rawBridge"
)

var testSoci = func(o *option.Option, installed bool) {
ginkgo.Describe("SOCI", func() {
var limactlO *option.Option
var limaHomePathEnv string
var wd string
var err error

ginkgo.BeforeEach(func() {
wd, err = os.Getwd()
gomega.Expect(err).Should(gomega.BeNil())
limaHomePathEnv = "LIMA_HOME=" + filepath.Join(wd, "../../_output/lima/data")
limactlO, err = option.New([]string{filepath.Join(wd, "../../_output/lima/bin/limactl")},
option.Env([]string{limaHomePathEnv}))
gomega.Expect(err).Should(gomega.BeNil())
})

ginkgo.It("finch pull should have same mounts as nerdctl pull with SOCI", func() {
resetVM(o, installed)
resetDisks(o, installed)
writeFile(finchConfigFilePath, []byte("cpus: 6\nmemory: 4GiB\nsnapshotters:\n "+
"- soci\nvmType: qemu\nrosetta: false"))
command.New(o, virtualMachineRootCmd, "init").WithTimeoutInSeconds(600).Run()
command.New(o, "pull", ffmpegSociImage).WithTimeoutInSeconds(30).Run()
finchPullMounts := countMounts(limactlO)
command.Run(o, "rmi", "-f", ffmpegSociImage)
command.New(limactlO, "shell", "finch",
"sudo", "nerdctl", "--snapshotter=soci", "pull", ffmpegSociImage).WithTimeoutInSeconds(30).Run()
nerdctlPullMounts := countMounts(limactlO)
command.Run(o, "rmi", "-f", ffmpegSociImage)
gomega.Expect(finchPullMounts).Should(gomega.Equal(nerdctlPullMounts))
})

ginkgo.It("finch run should have same mounts as nerdctl run with SOCI", func() {
resetVM(o, installed)
resetDisks(o, installed)
writeFile(finchConfigFilePath, []byte("cpus: 6\nmemory: 4GiB\nsnapshotters:\n "+
"- soci\nvmType: qemu\nrosetta: false"))
command.New(o, virtualMachineRootCmd, "init").WithTimeoutInSeconds(600).Run()
command.New(o, "run", ffmpegSociImage).WithTimeoutInSeconds(30).Run()
finchPullMounts := countMounts(limactlO)
command.Run(o, "rmi", "-f", ffmpegSociImage)
command.New(limactlO, "shell", "finch",
"sudo", "nerdctl", "--snapshotter=soci", "run", ffmpegSociImage).WithTimeoutInSeconds(30).Run()
nerdctlPullMounts := countMounts(limactlO)
command.Run(o, "rmi", "-f", ffmpegSociImage)
gomega.Expect(finchPullMounts).Should(gomega.Equal(nerdctlPullMounts))
})
})
}

// counts the mounts present in the VM after pulling an image.
func countMounts(limactlO *option.Option) int {
mountOutput := command.StdoutStr(limactlO, "shell", "finch", "mount")
return strings.Count(mountOutput, sociMountString)
}
1 change: 1 addition & 0 deletions e2e/vm/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func TestVM(t *testing.T) {
testVirtualizationFrameworkAndRosetta(o, *e2e.Installed)
testSupportBundle(o)
testCredHelper(o, *e2e.Installed, *e2e.Registry)
testSoci(o, *e2e.Installed)
})

gomega.RegisterFailHandler(ginkgo.Fail)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ require (
github.com/stretchr/testify v1.8.4
github.com/xorcare/pointer v1.2.2
golang.org/x/crypto v0.11.0
golang.org/x/exp v0.0.0-20230810033253-352e893a4cad
golang.org/x/tools v0.11.1
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.27.4
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20230810033253-352e893a4cad h1:g0bG7Z4uG+OgH2QDODnjp6ggkk1bJDsINcuWmJN1iJU=
golang.org/x/exp v0.0.0-20230810033253-352e893a4cad/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
Expand Down
3 changes: 3 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ type AdditionalDirectory struct {
type Finch struct {
CPUs *int `yaml:"cpus"`
Memory *string `yaml:"memory"`
// Snapshotters: the snapshotters that will be installed and configured automatically on vm init or on vm start.
// Values: `soci` for SOCI snapshotter; `overlayfs` for default overlay snapshotter.
Snapshotters []string `yaml:"snapshotters,omitempty"`
// CredsHelper: the list of credential helpers that will be installed and configured automatically on vm init or on vm start
CredsHelpers []string `yaml:"creds_helpers,omitempty"`
// AdditionalDirectories are the work directories that are not supported by default. In macOS, only home directory is supported by default.
Expand Down
98 changes: 97 additions & 1 deletion pkg/config/lima_config_applier.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,40 @@ import (
"github.com/lima-vm/lima/pkg/limayaml"
"github.com/spf13/afero"
"github.com/xorcare/pointer"
"golang.org/x/exp/slices"
"gopkg.in/yaml.v3"

"github.com/runfinch/finch/pkg/command"
"github.com/runfinch/finch/pkg/system"
)

const userModeEmulationProvisioningScriptHeader = "# cross-arch tools"
const (
sociVersion = "0.3.0"
sociInstallationProvisioningScriptHeader = "# soci installation and configuring"
sociFileNameFormat = "soci-snapshotter-%s-linux-%s.tar.gz"
sociDownloadURLFormat = "https://github.com/awslabs/soci-snapshotter/releases/download/v%s/%s"
sociInstallationScriptFormat = `%s
if [ ! -f /usr/local/bin/soci ]; then
# download soci
set -e
curl --retry 2 --retry-max-time 120 -OL "%s"
# move to usr/local/bin
tar -C /usr/local/bin -xvf %s soci soci-snapshotter-grpc
fi
# changing containerd config
export config=etc/containerd/config.toml
echo " [proxy_plugins.soci]
type = \"snapshot\"
address = \"/run/soci-snapshotter-grpc/soci-snapshotter-grpc.sock\" " >> $config
sudo systemctl restart containerd.service
sudo soci-snapshotter-grpc &> ~/soci-snapshotter-logs &
`

userModeEmulationProvisioningScriptHeader = "# cross-arch tools"
)

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

supportedSnapshotters := []string{"overlayfs", "soci"}
snapshotters := make(map[string][2]bool)
for i, snapshotter := range lca.cfg.Snapshotters {
if !slices.Contains(supportedSnapshotters, snapshotter) {
return fmt.Errorf("invalid snapshotter config value: %s", snapshotter)
}

isDefaultSnapshotter := false
if i == 0 {
isDefaultSnapshotter = true
}

isEnabled := true
snapshotters[snapshotter] = [2]bool{isEnabled, isDefaultSnapshotter}
}

toggleSnaphotters(&limaCfg, snapshotters)

limaCfgBytes, err := yaml.Marshal(limaCfg)
if err != nil {
return fmt.Errorf("failed to marshal the lima config file: %w", err)
Expand Down Expand Up @@ -188,3 +233,54 @@ func hasUserModeEmulationInstallationScript(limaCfg *limayaml.LimaYAML) (int, bo

return scriptIdx, hasCrossArchToolInstallationScript
}

// toggles snapshotters and sets default snapshotter.
func toggleSnaphotters(limaCfg *limayaml.LimaYAML, snapshotters map[string][2]bool) {
toggleOverlayFs(limaCfg, snapshotters["overlayfs"][1])
toggleSoci(limaCfg, snapshotters["soci"][0], snapshotters["soci"][1], sociVersion)
}

// sets overlayfs as the default snapshotter.
func toggleOverlayFs(limaCfg *limayaml.LimaYAML, isDefault bool) {
if isDefault {
limaCfg.Env = map[string]string{"CONTAINERD_SNAPSHOTTER": ""}
}
}

func toggleSoci(limaCfg *limayaml.LimaYAML, enabled bool, isDefault bool, sociVersion string) {
idx, hasScript := findSociInstallationScript(limaCfg)
sociFileName := fmt.Sprintf(sociFileNameFormat, sociVersion, system.NewStdLib().Arch())
sociDownloadURL := fmt.Sprintf(sociDownloadURLFormat, sociVersion, sociFileName)
sociInstallationScript := fmt.Sprintf(sociInstallationScriptFormat, sociInstallationProvisioningScriptHeader, sociDownloadURL, sociFileName)
if !hasScript && enabled {
limaCfg.Provision = append(limaCfg.Provision, limayaml.Provision{
Mode: "system",
Script: sociInstallationScript,
})
} else if hasScript && !enabled {
if len(limaCfg.Provision) > 0 {
limaCfg.Provision = append(limaCfg.Provision[:idx], limaCfg.Provision[idx+1:]...)
}
}

if isDefault {
limaCfg.Env = map[string]string{"CONTAINERD_SNAPSHOTTER": "soci"}
} else {
limaCfg.Env = map[string]string{"CONTAINERD_SNAPSHOTTER": ""}
}
}

func findSociInstallationScript(limaCfg *limayaml.LimaYAML) (int, bool) {
hasSociInstallationScript := false
var scriptIdx int
for idx, prov := range limaCfg.Provision {
trimmed := strings.Trim(prov.Script, " ")
if !hasSociInstallationScript && strings.HasPrefix(trimmed, sociInstallationProvisioningScriptHeader) {
hasSociInstallationScript = true
scriptIdx = idx
break
}
}

return scriptIdx, hasSociInstallationScript
}
Loading

0 comments on commit a2e077b

Please sign in to comment.