podman: add support for splitting imagestore

Add support for `--imagestore` in podman which allows users to split the filesystem of containers vs image store, imagestore if configured will pull images in image storage instead of the graphRoot while keeping the other parts still in the originally configured graphRoot.

This is an implementation of
https://github.com/containers/storage/pull/1549 in podman.

Signed-off-by: Aditya R <arajan@redhat.com>
This commit is contained in:
Aditya R
2023-06-13 11:11:42 +05:30
parent 719e3228b1
commit 3829fbd35a
7 changed files with 97 additions and 0 deletions

View File

@ -507,6 +507,10 @@ func rootFlags(cmd *cobra.Command, podmanConfig *entities.PodmanConfig) {
pFlags.StringVar(&podmanConfig.Runroot, runrootFlagName, "", "Path to the 'run directory' where all state information is stored") pFlags.StringVar(&podmanConfig.Runroot, runrootFlagName, "", "Path to the 'run directory' where all state information is stored")
_ = cmd.RegisterFlagCompletionFunc(runrootFlagName, completion.AutocompleteDefault) _ = cmd.RegisterFlagCompletionFunc(runrootFlagName, completion.AutocompleteDefault)
imageStoreFlagName := "imagestore"
pFlags.StringVar(&podmanConfig.ImageStore, imageStoreFlagName, "", "Path to the `image store`, different from `graph root`, use this to split storing the image into a separate `image store`, see `man containers-storage.conf` for details")
_ = cmd.RegisterFlagCompletionFunc(imageStoreFlagName, completion.AutocompleteDefault)
pFlags.BoolVar(&podmanConfig.TransientStore, "transient-store", false, "Enable transient container storage") pFlags.BoolVar(&podmanConfig.TransientStore, "transient-store", false, "Enable transient container storage")
runtimeFlagName := "runtime" runtimeFlagName := "runtime"

View File

@ -78,6 +78,12 @@ Identity value resolution precedence:
- `containers.conf` - `containers.conf`
Remote connections use local containers.conf for default. Remote connections use local containers.conf for default.
#### **--imagestore**=*path*
Path of the imagestore where images are stored. By default, the storage library stores all the images in the graphroot but if an imagestore is provided, then the storage library will store newly pulled images in the provided imagestore and keep using the graphroot for everything else. If the user is using the overlay driver, then the images which were already part of the graphroot will still be accessible.
This will override *imagestore* option in `containers-storage.conf(5)`, refer to `containers-storage.conf(5)` for more details.
#### **--log-level**=*level* #### **--log-level**=*level*
Log messages at and above specified level: debug, info, warn, error, fatal or panic (default: "warn") Log messages at and above specified level: debug, info, warn, error, fatal or panic (default: "warn")

View File

@ -126,6 +126,18 @@ func WithTransientStore(transientStore bool) RuntimeOption {
} }
} }
func WithImageStore(imageStore string) RuntimeOption {
return func(rt *Runtime) error {
if rt.valid {
return define.ErrRuntimeFinalized
}
rt.storageConfig.ImageStore = imageStore
return nil
}
}
// WithSignaturePolicy specifies the path of a file which decides how trust is // WithSignaturePolicy specifies the path of a file which decides how trust is
// managed for images we've pulled. // managed for images we've pulled.
// If this is not specified, the system default configuration will be used // If this is not specified, the system default configuration will be used

View File

@ -52,6 +52,7 @@ type PodmanConfig struct {
URI string // URI to RESTful API Service URI string // URI to RESTful API Service
Runroot string Runroot string
ImageStore string
StorageDriver string StorageDriver string
StorageOpts []string StorageOpts []string
SSHMode string SSHMode string

View File

@ -153,6 +153,10 @@ func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpo
storageSet = true storageSet = true
storageOpts.RunRoot = cfg.Runroot storageOpts.RunRoot = cfg.Runroot
} }
if fs.Changed("imagestore") {
storageOpts.ImageStore = cfg.ImageStore
options = append(options, libpod.WithImageStore(cfg.ImageStore))
}
if len(storageOpts.RunRoot) > 50 { if len(storageOpts.RunRoot) > 50 {
return nil, errors.New("the specified runroot is longer than 50 characters") return nil, errors.New("the specified runroot is longer than 50 characters")
} }

View File

@ -285,6 +285,9 @@ func CreateExitCommandArgs(storageConfig storageTypes.StoreOptions, config *conf
"--db-backend", config.Engine.DBBackend, "--db-backend", config.Engine.DBBackend,
fmt.Sprintf("--transient-store=%t", storageConfig.TransientStore), fmt.Sprintf("--transient-store=%t", storageConfig.TransientStore),
} }
if storageConfig.ImageStore != "" {
command = append(command, []string{"--imagestore", storageConfig.ImageStore}...)
}
if config.Engine.OCIRuntime != "" { if config.Engine.OCIRuntime != "" {
command = append(command, []string{"--runtime", config.Engine.OCIRuntime}...) command = append(command, []string{"--runtime", config.Engine.OCIRuntime}...)
} }

View File

@ -62,6 +62,73 @@ var _ = Describe("Podman pull", func() {
Expect(session).Should(Exit(0)) Expect(session).Should(Exit(0))
}) })
It("podman pull and run on split imagestore", func() {
SkipIfRemote("podman-remote does not support setting external imagestore")
imgName := "splitstoretest"
// Make alpine write-able
session := podmanTest.Podman([]string{"build", "--pull=never", "--tag", imgName, "build/basicalpine"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
tmpDir := filepath.Join(podmanTest.TempDir, "splitstore")
outfile := filepath.Join(podmanTest.TempDir, "image.tar")
save := podmanTest.Podman([]string{"save", "-o", outfile, "--format", "oci-archive", imgName})
save.WaitWithDefaultTimeout()
Expect(save).Should(Exit(0))
rmi := podmanTest.Podman([]string{"rmi", imgName})
rmi.WaitWithDefaultTimeout()
Expect(rmi).Should(Exit(0))
// load to splitstore
result := podmanTest.Podman([]string{"load", "--imagestore", tmpDir, "-q", "-i", outfile})
result.WaitWithDefaultTimeout()
Expect(result).Should(Exit(0))
// tag busybox to busybox-test in graphroot since we can delete readonly busybox
session = podmanTest.Podman([]string{"tag", "quay.io/libpod/busybox:latest", "busybox-test"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
session = podmanTest.Podman([]string{"images", "--imagestore", tmpDir})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring(imgName))
Expect(session.OutputToString()).To(ContainSubstring("busybox-test"))
// Test deleting image in graphroot even when `--imagestore` is set
session = podmanTest.Podman([]string{"rmi", "--imagestore", tmpDir, "busybox-test"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
// Images without --imagestore should not contain alpine
session = podmanTest.Podman([]string{"images"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(Not(ContainSubstring(imgName)))
// Set `imagestore` in `storage.conf` and container should run.
configPath := filepath.Join(podmanTest.TempDir, ".config", "containers", "storage.conf")
os.Setenv("CONTAINERS_STORAGE_CONF", configPath)
defer func() {
os.Unsetenv("CONTAINERS_STORAGE_CONF")
}()
err = os.MkdirAll(filepath.Dir(configPath), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
storageConf := []byte(fmt.Sprintf("[storage]\nimagestore=\"%s\"", tmpDir))
err = os.WriteFile(configPath, storageConf, os.ModePerm)
Expect(err).ToNot(HaveOccurred())
session = podmanTest.Podman([]string{"run", "--name", "test", "--rm",
imgName, "echo", "helloworld"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring("helloworld"))
})
It("podman pull by digest", func() { It("podman pull by digest", func() {
session := podmanTest.Podman([]string{"pull", "quay.io/libpod/testdigest_v2s2@sha256:755f4d90b3716e2bf57060d249e2cd61c9ac089b1233465c5c2cb2d7ee550fdb"}) session := podmanTest.Podman([]string{"pull", "quay.io/libpod/testdigest_v2s2@sha256:755f4d90b3716e2bf57060d249e2cd61c9ac089b1233465c5c2cb2d7ee550fdb"})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()