fn: add storage opt size support (#860)

Added env FN_MAX_FS_SIZE_MB, which if defined and non-zero
is passed to docker as storage opt size. We do not validate
if this option is supported by docker currently. This is
because it's difficult to actually validate this since it
not only depends on storage driver and its backing filesystem,
but also the mount options used to mount that fs.
This commit is contained in:
Tolga Ceylan
2018-03-14 15:47:34 -07:00
committed by GitHub
parent b74db6762b
commit cb61a678d9
5 changed files with 24 additions and 5 deletions

View File

@@ -597,6 +597,7 @@ func (a *agent) prepCold(ctx context.Context, call *call, tok ResourceToken, ch
env: map[string]string(call.Config), env: map[string]string(call.Config),
memory: call.Memory, memory: call.Memory,
cpus: uint64(call.CPUs), cpus: uint64(call.CPUs),
fsSize: a.cfg.MaxFsSize,
timeout: time.Duration(call.Timeout) * time.Second, // this is unnecessary, but in case removal fails... timeout: time.Duration(call.Timeout) * time.Second, // this is unnecessary, but in case removal fails...
stdin: call.req.Body, stdin: call.req.Body,
stdout: common.NewClampWriter(call.w, a.cfg.MaxResponseSize, models.ErrFunctionResponseTooBig), stdout: common.NewClampWriter(call.w, a.cfg.MaxResponseSize, models.ErrFunctionResponseTooBig),
@@ -629,10 +630,7 @@ func (a *agent) runHot(ctx context.Context, call *call, tok ResourceToken, state
state.UpdateState(ctx, ContainerStateStart, call.slots) state.UpdateState(ctx, ContainerStateStart, call.slots)
defer state.UpdateState(ctx, ContainerStateDone, call.slots) defer state.UpdateState(ctx, ContainerStateDone, call.slots)
// if freezer is enabled, be consistent with freezer behavior and container, closer := NewHotContainer(call, &a.cfg)
// block stdout and stderr between calls.
isBlockIdleIO := MaxDisabledMsecs != a.cfg.FreezeIdle
container, closer := NewHotContainer(call, isBlockIdleIO)
defer closer() defer closer()
logger := logrus.WithFields(logrus.Fields{"id": container.id, "app": call.AppName, "route": call.Path, "image": call.Image, "memory": call.Memory, "cpus": call.CPUs, "format": call.Format, "idle_timeout": call.IdleTimeout}) logger := logrus.WithFields(logrus.Fields{"id": container.id, "app": call.AppName, "route": call.Path, "image": call.Image, "memory": call.Memory, "cpus": call.CPUs, "format": call.Format, "idle_timeout": call.IdleTimeout})
@@ -795,6 +793,7 @@ type container struct {
env map[string]string env map[string]string
memory uint64 memory uint64
cpus uint64 cpus uint64
fsSize uint64
timeout time.Duration // cold only (superfluous, but in case) timeout time.Duration // cold only (superfluous, but in case)
stdin io.Reader stdin io.Reader
@@ -806,7 +805,11 @@ type container struct {
stats *drivers.Stats stats *drivers.Stats
} }
func NewHotContainer(call *call, isBlockIdleIO bool) (*container, func()) { func NewHotContainer(call *call, cfg *AgentConfig) (*container, func()) {
// if freezer is enabled, be consistent with freezer behavior and
// block stdout and stderr between calls.
isBlockIdleIO := MaxDisabledMsecs != cfg.FreezeIdle
id := id.New().String() id := id.New().String()
@@ -834,6 +837,7 @@ func NewHotContainer(call *call, isBlockIdleIO bool) (*container, func()) {
env: map[string]string(call.Config), env: map[string]string(call.Config),
memory: call.Memory, memory: call.Memory,
cpus: uint64(call.CPUs), cpus: uint64(call.CPUs),
fsSize: cfg.MaxFsSize,
stdin: stdin, stdin: stdin,
stdout: stdout, stdout: stdout,
stderr: stderr, stderr: stderr,
@@ -877,6 +881,7 @@ func (c *container) Timeout() time.Duration { return c.timeout }
func (c *container) EnvVars() map[string]string { return c.env } func (c *container) EnvVars() map[string]string { return c.env }
func (c *container) Memory() uint64 { return c.memory * 1024 * 1024 } // convert MB func (c *container) Memory() uint64 { return c.memory * 1024 * 1024 } // convert MB
func (c *container) CPUs() uint64 { return c.cpus } func (c *container) CPUs() uint64 { return c.cpus }
func (c *container) FsSize() uint64 { return c.fsSize }
// WriteStat publishes each metric in the specified Stats structure as a histogram metric // WriteStat publishes each metric in the specified Stats structure as a histogram metric
func (c *container) WriteStat(ctx context.Context, stat drivers.Stat) { func (c *container) WriteStat(ctx context.Context, stat drivers.Stat) {

View File

@@ -19,6 +19,7 @@ type AgentConfig struct {
MaxLogSize uint64 `json:"max_log_size_bytes"` MaxLogSize uint64 `json:"max_log_size_bytes"`
MaxTotalCPU uint64 `json:"max_total_cpu_mcpus"` MaxTotalCPU uint64 `json:"max_total_cpu_mcpus"`
MaxTotalMemory uint64 `json:"max_total_memory_bytes"` MaxTotalMemory uint64 `json:"max_total_memory_bytes"`
MaxFsSize uint64 `json:"max_fs_size_mb"`
} }
const ( const (
@@ -31,6 +32,7 @@ const (
EnvMaxLogSize = "FN_MAX_LOG_SIZE_BYTES" EnvMaxLogSize = "FN_MAX_LOG_SIZE_BYTES"
EnvMaxTotalCPU = "FN_MAX_TOTAL_CPU_MCPUS" EnvMaxTotalCPU = "FN_MAX_TOTAL_CPU_MCPUS"
EnvMaxTotalMemory = "FN_MAX_TOTAL_MEMORY_BYTES" EnvMaxTotalMemory = "FN_MAX_TOTAL_MEMORY_BYTES"
EnvMaxFsSize = "FN_MAX_FS_SIZE_MB"
MaxDisabledMsecs = time.Duration(math.MaxInt64) MaxDisabledMsecs = time.Duration(math.MaxInt64)
) )
@@ -53,6 +55,7 @@ func NewAgentConfig() (*AgentConfig, error) {
err = setEnvUint(err, EnvMaxLogSize, &cfg.MaxLogSize) err = setEnvUint(err, EnvMaxLogSize, &cfg.MaxLogSize)
err = setEnvUint(err, EnvMaxTotalCPU, &cfg.MaxTotalCPU) err = setEnvUint(err, EnvMaxTotalCPU, &cfg.MaxTotalCPU)
err = setEnvUint(err, EnvMaxTotalMemory, &cfg.MaxTotalMemory) err = setEnvUint(err, EnvMaxTotalMemory, &cfg.MaxTotalMemory)
err = setEnvUint(err, EnvMaxFsSize, &cfg.MaxFsSize)
if err != nil { if err != nil {
return cfg, err return cfg, err

View File

@@ -168,6 +168,13 @@ func (drv *DockerDriver) Prepare(ctx context.Context, task drivers.ContainerTask
container.HostConfig.CPUPeriod = 100000 container.HostConfig.CPUPeriod = 100000
} }
// If defined, impose file system size limit. In MB units.
if task.FsSize() != 0 {
container.HostConfig.StorageOpt = make(map[string]string)
sizeOption := fmt.Sprintf("%vM", task.FsSize())
container.HostConfig.StorageOpt["size"] = sizeOption
}
volumes := task.Volumes() volumes := task.Volumes()
for _, mapping := range volumes { for _, mapping := range volumes {
hostDir := mapping[0] hostDir := mapping[0]

View File

@@ -33,6 +33,7 @@ func (f *taskDockerTest) WriteStat(context.Context, drivers.Stat) { /* TODO */ }
func (f *taskDockerTest) Volumes() [][2]string { return [][2]string{} } func (f *taskDockerTest) Volumes() [][2]string { return [][2]string{} }
func (f *taskDockerTest) Memory() uint64 { return 256 * 1024 * 1024 } func (f *taskDockerTest) Memory() uint64 { return 256 * 1024 * 1024 }
func (f *taskDockerTest) CPUs() uint64 { return 0 } func (f *taskDockerTest) CPUs() uint64 { return 0 }
func (f *taskDockerTest) FsSize() uint64 { return 0 }
func (f *taskDockerTest) WorkDir() string { return "" } func (f *taskDockerTest) WorkDir() string { return "" }
func (f *taskDockerTest) Close() {} func (f *taskDockerTest) Close() {}
func (f *taskDockerTest) Input() io.Reader { return f.input } func (f *taskDockerTest) Input() io.Reader { return f.input }

View File

@@ -113,6 +113,9 @@ type ContainerTask interface {
// CPUs in milli CPU units // CPUs in milli CPU units
CPUs() uint64 CPUs() uint64
// Filesystem size limit for the container, in megabytes.
FsSize() uint64
// WorkDir returns the working directory to use for the task. Empty string // WorkDir returns the working directory to use for the task. Empty string
// leaves it unset. // leaves it unset.
WorkDir() string WorkDir() string