-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add optional cpu limit to spawned action containers #5443
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3e142c9
3d26a20
c0f9496
ccc6a61
8fd0a6e
6a6da58
8e0cf4e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,7 +25,7 @@ import org.apache.openwhisk.spi.Spi | |
|
||
import scala.concurrent.Future | ||
import scala.concurrent.duration.FiniteDuration | ||
import scala.math.max | ||
import scala.math.{max, round} | ||
|
||
case class ContainerArgsConfig(network: String, | ||
dnsServers: Seq[String] = Seq.empty, | ||
|
@@ -55,13 +55,15 @@ case class ContainerPoolConfig(userMemory: ByteSize, | |
prewarmPromotion: Boolean, | ||
memorySyncInterval: FiniteDuration, | ||
batchDeletionSize: Int, | ||
userCpus: Option[Double] = None, | ||
prewarmContainerCreationConfig: Option[PrewarmContainerCreationConfig] = None) { | ||
require( | ||
concurrentPeekFactor > 0 && concurrentPeekFactor <= 1.0, | ||
s"concurrentPeekFactor must be > 0 and <= 1.0; was $concurrentPeekFactor") | ||
|
||
require(prewarmExpirationCheckInterval.toSeconds > 0, "prewarmExpirationCheckInterval must be > 0") | ||
require(batchDeletionSize > 0, "batch deletion size must be > 0") | ||
require(userCpus.forall(_ > 0), "userCpus must be > 0") | ||
|
||
/** | ||
* The shareFactor indicates the number of containers that would share a single core, on average. | ||
|
@@ -73,6 +75,16 @@ case class ContainerPoolConfig(userMemory: ByteSize, | |
// Grant more CPU to a container if it allocates more memory. | ||
def cpuShare(reservedMemory: ByteSize) = | ||
max((totalShare / (userMemory.toBytes / reservedMemory.toBytes)).toInt, 2) // The minimum allowed cpu-shares is 2 | ||
|
||
private val minContainerCpus = 0.01 // The minimum cpus allowed by docker is 0.01 | ||
private val roundingMultiplier = 100000 | ||
def cpuLimit(reservedMemory: ByteSize): Option[Double] = { | ||
userCpus.map(c => { | ||
val containerCpus = c / (userMemory.toBytes / reservedMemory.toBytes) | ||
val roundedContainerCpus = round(containerCpus * roundingMultiplier).toDouble / roundingMultiplier // Only use decimal precision of 5 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
max(roundedContainerCpus, minContainerCpus) | ||
quintenp01 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}) | ||
} | ||
} | ||
|
||
case class PrewarmContainerCreationConfig(maxConcurrent: Int, creationDelay: FiniteDuration) { | ||
|
@@ -116,16 +128,18 @@ trait ContainerFactory { | |
userProvidedImage: Boolean, | ||
memory: ByteSize, | ||
cpuShares: Int, | ||
cpuLimit: Option[Double], | ||
action: Option[ExecutableWhiskAction])(implicit config: WhiskConfig, logging: Logging): Future[Container] = { | ||
createContainer(tid, name, actionImage, userProvidedImage, memory, cpuShares) | ||
createContainer(tid, name, actionImage, userProvidedImage, memory, cpuShares, cpuLimit) | ||
} | ||
|
||
def createContainer(tid: TransactionId, | ||
name: String, | ||
actionImage: ExecManifest.ImageName, | ||
userProvidedImage: Boolean, | ||
memory: ByteSize, | ||
cpuShares: Int)(implicit config: WhiskConfig, logging: Logging): Future[Container] | ||
cpuShares: Int, | ||
cpuLimit: Option[Double])(implicit config: WhiskConfig, logging: Logging): Future[Container] | ||
|
||
/** perform any initialization */ | ||
def init(): Unit | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -71,6 +71,8 @@ whisk { | |
prewarm-promotion: false # if true, action can take prewarm container which has bigger memory | ||
memory-sync-interval: 1 second # period to sync memory info to etcd | ||
batch-deletion-size: 10 # batch size for removing containers when disable invoker, too big value may cause docker/k8s overload | ||
# optional setting to specify the total allocatable cpus for all action containers, each container will get a fraction of this proportional to its allocated memory to limit the cpu | ||
# user-cpus: 1 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BTW, could you add a configuration like this? It would be easier to configure it with ansible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @style95 I just pushed some changes to |
||
} | ||
|
||
kubernetes { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,6 +63,7 @@ object DockerContainer { | |
registryConfig: Option[RuntimesRegistryConfig] = None, | ||
memory: ByteSize = 256.MB, | ||
cpuShares: Int = 0, | ||
cpuLimit: Option[Double] = None, | ||
environment: Map[String, String] = Map.empty, | ||
network: String = "bridge", | ||
dnsServers: Seq[String] = Seq.empty, | ||
|
@@ -101,6 +102,7 @@ object DockerContainer { | |
dnsSearch.flatMap(d => Seq("--dns-search", d)) ++ | ||
dnsOptions.flatMap(d => Seq(dnsOptString, d)) ++ | ||
name.map(n => Seq("--name", n)).getOrElse(Seq.empty) ++ | ||
cpuLimit.map(c => Seq("--cpus", c.toString)).getOrElse(Seq.empty) ++ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i wasn't aware that you could do both There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
params | ||
|
||
val registryConfigUrl = registryConfig.map(_.url).getOrElse("") | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could deploy this branch w/ and w/o this configuration using ansible.
I confirmed that NanoCpus is properly configured in containers according to this config.