-
Notifications
You must be signed in to change notification settings - Fork 161
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
Allocate resources automatically to containers in a container app #856
Open
isaacabraham
wants to merge
3
commits into
master
Choose a base branch
from
allocate-resources
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+336
−46
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
#r "nuget:FsCheck" | ||
|
||
open FsCheck | ||
open System | ||
|
||
type [<Measure>] Gb | ||
type [<Measure>] VCores | ||
|
||
let resourceMinimums = | ||
[ | ||
0.25<VCores>, 0.5<Gb> | ||
0.5<VCores>, 1.0<Gb> | ||
0.75<VCores>, 1.5<Gb> | ||
1.0<VCores>, 2.0<Gb> | ||
1.25<VCores>, 2.5<Gb> | ||
1.5<VCores>, 3.0<Gb> | ||
1.75<VCores>, 3.5<Gb> | ||
2.0<VCores>, 4.<Gb> | ||
] | ||
|
||
|
||
let cores = 1.75 | ||
let containers = 5. | ||
|
||
|
||
let optimise containers (cores:float<VCores>, memory:float<Gb>) = | ||
let containers = float containers | ||
let minCores = resourceMinimums |> List.tryFind (fun (cores, _) -> float cores > containers * 0.05) |> Option.map fst | ||
let minRam = resourceMinimums |> List.tryFind (fun (_, ram) -> float ram > containers * 0.01) |> Option.map snd | ||
match minCores, minRam with | ||
| Some minCores, Some minRam -> | ||
if minCores > cores then Error $"Insufficient cores (minimum is {minCores}VCores)." | ||
elif minRam > memory then Error $"Insufficient memory (minimum is {minRam}Gb)." | ||
else | ||
let cores = float cores | ||
let memory = float memory | ||
|
||
let vcoresPerContainer = Math.Truncate ((cores / containers) * 20.) / 20. | ||
let remainingCores = cores - (vcoresPerContainer * containers) | ||
|
||
let gbPerContainer = Math.Truncate ((memory / containers) * 100.) / 100. | ||
let remainingGb = memory - (gbPerContainer * containers) | ||
|
||
Ok [ | ||
for container in 1. .. containers do | ||
if container = 1. then | ||
(vcoresPerContainer + remainingCores) * 1.<VCores>, (gbPerContainer + remainingGb) * 1.<Gb> | ||
else | ||
vcoresPerContainer * 1.<VCores>, gbPerContainer * 1.<Gb> | ||
] | ||
| None, _ -> | ||
Error "Insufficient cores" | ||
| _, None -> | ||
Error "Insufficient memory" | ||
|
||
// Usage | ||
optimise 4 (1.0<VCores>, 4.<Gb>) | ||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
type Inputs = PositiveInt * float<VCores> * float<Gb> | ||
type ValidInput = ValidInput of Inputs | ||
type InvalidInput = InvalidInput of Inputs | ||
|
||
type Tests = | ||
static member totalsAlwaysEqualInput (ValidInput(PositiveInt containers, cores, memory)) = | ||
let split = optimise containers (cores, memory) | ||
match split with | ||
| Ok split -> | ||
let correctCores = split |> List.sumBy fst |> decimal = decimal cores | ||
let correctRam = split |> List.sumBy snd |> decimal = decimal memory | ||
correctCores && correctRam | ||
| Error msg -> | ||
failwith msg | ||
|
||
static member givesBackCorrectNumberOfConfigs (ValidInput(PositiveInt containers, cores, memory)) = | ||
let split = optimise containers (cores, memory) | ||
match split with | ||
| Ok split -> split.Length = containers | ||
| Error msg -> failwith msg | ||
|
||
static member neverReturnsLessThanMinimum (ValidInput(PositiveInt containers, cores, memory)) = | ||
let split = optimise containers (cores, memory) | ||
match split with | ||
| Ok split -> split |> List.forall(fun (c, m) -> c >= 0.05<VCores> && m >= 0.01<Gb>) | ||
| Error msg -> failwith msg | ||
|
||
static member failsIfInputsInvalid (InvalidInput(PositiveInt containers, cores, memory)) = | ||
let split = optimise containers (cores, memory) | ||
match split with | ||
| Ok _ -> failwith "Should have failed." | ||
| Error _ -> true | ||
|
||
let basicGen = gen { | ||
let! cores, gb = Gen.elements resourceMinimums | ||
let! containers = Arb.Default.PositiveInt () |> Arb.filter(fun (PositiveInt s) -> s < 20) |> Arb.toGen | ||
return containers, cores, gb | ||
} | ||
|
||
let shrinker checker (con:PositiveInt, cor, mem) = | ||
[ | ||
if con.Get > 1 then PositiveInt (con.Get - 1), cor, mem | ||
if cor > 0.25<VCores> then con, cor - 0.25<VCores>, mem - 0.5<Gb> | ||
] | ||
|> List.filter checker | ||
|
||
type ResourceArb = | ||
static member IsValid (PositiveInt con, cor, mem) = | ||
let cores = resourceMinimums |> Seq.find(fun (cores, _) -> float cores > float con * 0.05) |> fst <= cor | ||
let memory = resourceMinimums |> Seq.find(fun (_, mem) -> float mem > float con * 0.01) |> snd <= mem | ||
cores && memory | ||
|
||
static member ValidInputs () = | ||
{ new Arbitrary<ValidInput> () with | ||
override _.Generator = basicGen |> Gen.filter ResourceArb.IsValid |> Gen.map ValidInput | ||
override _.Shrinker (ValidInput inputs) = inputs |> shrinker ResourceArb.IsValid |> Seq.map ValidInput | ||
} | ||
static member InvalidInputs () = | ||
{ new Arbitrary<InvalidInput> () with | ||
override _.Generator = basicGen |> Gen.filter (ResourceArb.IsValid >> not) |> Gen.map InvalidInput | ||
override _.Shrinker (InvalidInput inputs) = inputs |> shrinker (ResourceArb.IsValid >> not) |> Seq.map InvalidInput | ||
} | ||
|
||
let config = { Config.Default with Arbitrary = [ typeof<ResourceArb> ] } | ||
|
||
Check.All<Tests> config |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 think in most cases, this can't really be evenly distributed. We probably need some different strategies where containers are weighted to prioritize them or where some of the containers have a fixed size and any extra resources are allocated to certain containers that would need more elasticity.
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.
To clarify a bit - you might have some sidecars to handle things like logging that have low, fixed resource usage. And then an HTTP frontend might need more CPU, but low memory needs. And then maybe one or two containers that do most of the application logic and need all the rest of the capacity. Does that make sense? I think it's difficult to come up with a "one size fits all" solution and should design it around multiple user-defined strategies.
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.
Yes, that's true. One idea I had was either to set a percentage, or a simple T-Shirt size e.g. S/M/L, and distribute it that way. Once we get out of that world and into the world of more complex scenario, this is something that I suspect will then require manual creation - there are so many permutations.
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'd also like to loop in @matthewcrews to see if he has any thoughts on this kind of problem (although might be overkill!). Ideally I really don't want to bring in any external library but it sounds like a kind of constraint solving problem:
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'd need to unpack this more but this is sounding like a straightforward allocation/assignment problem. I don't think you need to go full optimization model on this.
There are some known heuristics that should be able to do this for you and only take a few lines of F#. I'd be happy to jump on a call if you wanted to discuss this in more detail.