shm: Create shared /dev/shm

This commit checks the size of "/dev/shm" for the sandbox container
which is then used to create the shared memory inside the guest.
kata agent then uses this size to set up a sandbox level ephemeral
storage for shm. The containers then simply bind mount this sandbox level
shm.

With this, we will now be able to support docker --shm-size option
as well have a shared shm within containers in a pod, since they are
supposed to be in the same IPC namespace.

Fixes #356

Signed-off-by: Archana Shinde <archana.m.shinde@intel.com>
This commit is contained in:
Archana Shinde 2018-05-29 11:02:27 -07:00
parent dd2bf15ebc
commit 4d470e513b
6 changed files with 185 additions and 8 deletions

View File

@ -42,12 +42,15 @@ var (
kataHostSharedDir = "/run/kata-containers/shared/sandboxes/" kataHostSharedDir = "/run/kata-containers/shared/sandboxes/"
kataGuestSharedDir = "/run/kata-containers/shared/containers/" kataGuestSharedDir = "/run/kata-containers/shared/containers/"
mountGuest9pTag = "kataShared" mountGuest9pTag = "kataShared"
kataGuestSandboxDir = "/run/kata-containers/sandbox/"
type9pFs = "9p" type9pFs = "9p"
vsockSocketScheme = "vsock" vsockSocketScheme = "vsock"
kata9pDevType = "9p" kata9pDevType = "9p"
kataBlkDevType = "blk" kataBlkDevType = "blk"
kataSCSIDevType = "scsi" kataSCSIDevType = "scsi"
sharedDir9pOptions = []string{"trans=virtio,version=9p2000.L", "nodev"} sharedDir9pOptions = []string{"trans=virtio,version=9p2000.L", "nodev"}
shmDir = "shm"
kataEphemeralDevType = "ephemeral"
) )
// KataAgentConfig is a structure storing information needed // KataAgentConfig is a structure storing information needed
@ -505,9 +508,27 @@ func (k *kataAgent) startSandbox(sandbox *Sandbox) error {
Options: sharedDir9pOptions, Options: sharedDir9pOptions,
} }
storages := []*grpc.Storage{sharedVolume}
if sandbox.shmSize > 0 {
path := filepath.Join(kataGuestSandboxDir, shmDir)
shmSizeOption := fmt.Sprintf("size=%d", sandbox.shmSize)
shmStorage := &grpc.Storage{
Driver: kataEphemeralDevType,
MountPoint: path,
Source: "shm",
Fstype: "tmpfs",
Options: []string{"noexec", "nosuid", "nodev", "mode=1777", shmSizeOption},
}
storages = append(storages, shmStorage)
}
req := &grpc.CreateSandboxRequest{ req := &grpc.CreateSandboxRequest{
Hostname: hostname, Hostname: hostname,
Storages: []*grpc.Storage{sharedVolume}, Storages: storages,
SandboxPidns: false,
} }
_, err = k.sendReq(req) _, err = k.sendReq(req)
@ -611,13 +632,25 @@ func constraintGRPCSpec(grpcSpec *grpc.Spec) {
} }
} }
grpcSpec.Linux.Namespaces = tmpNamespaces grpcSpec.Linux.Namespaces = tmpNamespaces
}
// Handle /dev/shm mount func (k *kataAgent) handleShm(grpcSpec *grpc.Spec, sandbox *Sandbox) {
for idx, mnt := range grpcSpec.Mounts { for idx, mnt := range grpcSpec.Mounts {
if mnt.Destination == "/dev/shm" { if mnt.Destination != "/dev/shm" {
continue
}
if sandbox.shmSize > 0 {
grpcSpec.Mounts[idx].Type = "bind"
grpcSpec.Mounts[idx].Options = []string{"rbind"}
grpcSpec.Mounts[idx].Source = filepath.Join(kataGuestSandboxDir, shmDir)
k.Logger().WithField("shm-size", sandbox.shmSize).Info("Using sandbox shm")
} else {
sizeOption := fmt.Sprintf("size=%d", DefaultShmSize)
grpcSpec.Mounts[idx].Type = "tmpfs" grpcSpec.Mounts[idx].Type = "tmpfs"
grpcSpec.Mounts[idx].Source = "shm" grpcSpec.Mounts[idx].Source = "shm"
grpcSpec.Mounts[idx].Options = []string{"noexec", "nosuid", "nodev", "mode=1777", "size=65536k"} grpcSpec.Mounts[idx].Options = []string{"noexec", "nosuid", "nodev", "mode=1777", sizeOption}
k.Logger().WithField("shm-size", sizeOption).Info("Setting up a separate shm for container")
} }
} }
} }
@ -785,6 +818,8 @@ func (k *kataAgent) createContainer(sandbox *Sandbox, c *Container) (p *Process,
// irrelevant information to the agent. // irrelevant information to the agent.
constraintGRPCSpec(grpcSpec) constraintGRPCSpec(grpcSpec)
k.handleShm(grpcSpec, sandbox)
req := &grpc.CreateContainerRequest{ req := &grpc.CreateContainerRequest{
ContainerId: c.id, ContainerId: c.id,
ExecId: c.id, ExecId: c.id,

View File

@ -10,6 +10,7 @@ import (
"io/ioutil" "io/ioutil"
"net" "net"
"os" "os"
"path/filepath"
"reflect" "reflect"
"testing" "testing"
@ -458,10 +459,42 @@ func TestConstraintGRPCSpec(t *testing.T) {
// check mounts // check mounts
assert.Len(g.Mounts, 1) assert.Len(g.Mounts, 1)
}
func TestHandleShm(t *testing.T) {
assert := assert.New(t)
k := kataAgent{}
sandbox := &Sandbox{
shmSize: 8192,
}
g := &pb.Spec{
Hooks: &pb.Hooks{},
Mounts: []pb.Mount{
{Destination: "/dev/shm"},
},
}
k.handleShm(g, sandbox)
assert.Len(g.Mounts, 1)
assert.NotEmpty(g.Mounts[0].Destination) assert.NotEmpty(g.Mounts[0].Destination)
assert.NotEmpty(g.Mounts[0].Type) assert.Equal(g.Mounts[0].Destination, "/dev/shm")
assert.NotEmpty(g.Mounts[0].Source) assert.Equal(g.Mounts[0].Type, "bind")
assert.NotEmpty(g.Mounts[0].Options) assert.NotEmpty(g.Mounts[0].Source, filepath.Join(kataGuestSharedDir, shmDir))
assert.Equal(g.Mounts[0].Options, []string{"rbind"})
sandbox.shmSize = 0
k.handleShm(g, sandbox)
assert.Len(g.Mounts, 1)
assert.NotEmpty(g.Mounts[0].Destination)
assert.Equal(g.Mounts[0].Destination, "/dev/shm")
assert.Equal(g.Mounts[0].Type, "tmpfs")
assert.Equal(g.Mounts[0].Source, "shm")
sizeOption := fmt.Sprintf("size=%d", DefaultShmSize)
assert.Equal(g.Mounts[0].Options, []string{"noexec", "nosuid", "nodev", "mode=1777", sizeOption})
} }
func testIsPidNamespacePresent(grpcSpec *pb.Spec) bool { func testIsPidNamespacePresent(grpcSpec *pb.Spec) bool {

View File

@ -18,6 +18,10 @@ import (
"github.com/kata-containers/runtime/virtcontainers/device/drivers" "github.com/kata-containers/runtime/virtcontainers/device/drivers"
) )
// DefaultShmSize is the default shm size to be used in case host
// IPC is used.
const DefaultShmSize = 65536 * 1024
var rootfsDir = "rootfs" var rootfsDir = "rootfs"
var systemMountPrefixes = []string{"/proc", "/sys"} var systemMountPrefixes = []string{"/proc", "/sys"}

View File

@ -13,6 +13,7 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"syscall"
criContainerdAnnotations "github.com/containerd/cri-containerd/pkg/annotations" criContainerdAnnotations "github.com/containerd/cri-containerd/pkg/annotations"
crioAnnotations "github.com/kubernetes-incubator/cri-o/pkg/annotations" crioAnnotations "github.com/kubernetes-incubator/cri-o/pkg/annotations"
@ -481,6 +482,11 @@ func SandboxConfig(ocispec CompatOCISpec, runtime RuntimeConfig, bundlePath, cid
return vc.SandboxConfig{}, err return vc.SandboxConfig{}, err
} }
shmSize, err := getShmSize(containerConfig)
if err != nil {
return vc.SandboxConfig{}, err
}
networkConfig, err := networkConfig(ocispec, runtime) networkConfig, err := networkConfig(ocispec, runtime)
if err != nil { if err != nil {
return vc.SandboxConfig{}, err return vc.SandboxConfig{}, err
@ -526,6 +532,8 @@ func SandboxConfig(ocispec CompatOCISpec, runtime RuntimeConfig, bundlePath, cid
vcAnnotations.ConfigJSONKey: string(ociSpecJSON), vcAnnotations.ConfigJSONKey: string(ociSpecJSON),
vcAnnotations.BundlePathKey: bundlePath, vcAnnotations.BundlePathKey: bundlePath,
}, },
ShmSize: shmSize,
} }
addAssetAnnotations(ocispec, &sandboxConfig) addAssetAnnotations(ocispec, &sandboxConfig)
@ -610,6 +618,32 @@ func ContainerConfig(ocispec CompatOCISpec, bundlePath, cid, console string, det
return containerConfig, nil return containerConfig, nil
} }
func getShmSize(c vc.ContainerConfig) (uint64, error) {
var shmSize uint64
for _, m := range c.Mounts {
if m.Destination != "/dev/shm" {
continue
}
shmSize = vc.DefaultShmSize
if m.Type == "bind" && m.Source != "/dev/shm" {
var s syscall.Statfs_t
if err := syscall.Statfs(m.Source, &s); err != nil {
return 0, err
}
shmSize = uint64(s.Bsize) * s.Blocks
}
break
}
ociLog.Infof("shm-size detected: %d", shmSize)
return shmSize, nil
}
// StatusToOCIState translates a virtcontainers container status into an OCI state. // StatusToOCIState translates a virtcontainers container status into an OCI state.
func StatusToOCIState(status vc.ContainerStatus) spec.State { func StatusToOCIState(status vc.ContainerStatus) spec.State {
return spec.State{ return spec.State{

View File

@ -13,11 +13,13 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"reflect" "reflect"
"strconv"
"testing" "testing"
"github.com/kubernetes-incubator/cri-o/pkg/annotations" "github.com/kubernetes-incubator/cri-o/pkg/annotations"
specs "github.com/opencontainers/runtime-spec/specs-go" specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"golang.org/x/sys/unix"
vc "github.com/kata-containers/runtime/virtcontainers" vc "github.com/kata-containers/runtime/virtcontainers"
"github.com/kata-containers/runtime/virtcontainers/device/config" "github.com/kata-containers/runtime/virtcontainers/device/config"
@ -839,6 +841,70 @@ func TestCompatOCISpecWithStruct(t *testing.T) {
assert.Nil(t, err, "This test should not fail") assert.Nil(t, err, "This test should not fail")
} }
func TestGetShmSize(t *testing.T) {
containerConfig := vc.ContainerConfig{
Mounts: []vc.Mount{},
}
shmSize, err := getShmSize(containerConfig)
assert.Nil(t, err)
assert.Equal(t, shmSize, uint64(0))
m := vc.Mount{
Source: "/dev/shm",
Destination: "/dev/shm",
Type: "tmpfs",
Options: nil,
}
containerConfig.Mounts = append(containerConfig.Mounts, m)
shmSize, err = getShmSize(containerConfig)
assert.Nil(t, err)
assert.Equal(t, shmSize, uint64(vc.DefaultShmSize))
containerConfig.Mounts[0].Source = "/var/run/shared/shm"
containerConfig.Mounts[0].Type = "bind"
_, err = getShmSize(containerConfig)
assert.NotNil(t, err)
}
func TestGetShmSizeBindMounted(t *testing.T) {
if os.Geteuid() != 0 {
t.Skip("Test disabled as requires root privileges")
}
dir, err := ioutil.TempDir("", "")
assert.Nil(t, err)
defer os.RemoveAll(dir)
shmPath := filepath.Join(dir, "shm")
err = os.Mkdir(shmPath, 0700)
assert.Nil(t, err)
size := 8192
shmOptions := "mode=1777,size=" + strconv.Itoa(size)
err = unix.Mount("shm", shmPath, "tmpfs", unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV, shmOptions)
assert.Nil(t, err)
defer unix.Unmount(shmPath, 0)
containerConfig := vc.ContainerConfig{
Mounts: []vc.Mount{
{
Source: shmPath,
Destination: "/dev/shm",
Type: "bind",
Options: nil,
},
},
}
shmSize, err := getShmSize(containerConfig)
assert.Nil(t, err)
assert.Equal(t, shmSize, uint64(size))
}
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
/* Create temp bundle directory if necessary */ /* Create temp bundle directory if necessary */
err := os.MkdirAll(tempBundlePath, dirMode) err := os.MkdirAll(tempBundlePath, dirMode)

View File

@ -356,6 +356,8 @@ type SandboxConfig struct {
// Annotations keys must be unique strings and must be name-spaced // Annotations keys must be unique strings and must be name-spaced
// with e.g. reverse domain notation (org.clearlinux.key). // with e.g. reverse domain notation (org.clearlinux.key).
Annotations map[string]string Annotations map[string]string
ShmSize uint64
} }
// valid checks that the sandbox configuration is valid. // valid checks that the sandbox configuration is valid.
@ -459,6 +461,8 @@ type Sandbox struct {
annotationsLock *sync.RWMutex annotationsLock *sync.RWMutex
wg *sync.WaitGroup wg *sync.WaitGroup
shmSize uint64
} }
// ID returns the sandbox identifier string. // ID returns the sandbox identifier string.
@ -738,6 +742,7 @@ func newSandbox(sandboxConfig SandboxConfig) (*Sandbox, error) {
state: State{}, state: State{},
annotationsLock: &sync.RWMutex{}, annotationsLock: &sync.RWMutex{},
wg: &sync.WaitGroup{}, wg: &sync.WaitGroup{},
shmSize: sandboxConfig.ShmSize,
} }
if err = globalSandboxList.addSandbox(s); err != nil { if err = globalSandboxList.addSandbox(s); err != nil {