Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
7c5bb64
docs: add design spec for CLIEN-781 memory & CPU limits
fedemaleh May 21, 2026
feb746a
docs: add implementation plan for CLIEN-781 memory & CPU limits
fedemaleh May 21, 2026
3916ce8
feat: add cpu_millicores_limit and ram_memory_limit properties to k8s…
fedemaleh May 21, 2026
957debc
feat: rename Processor tab to Resources and surface CPU/RAM limit con…
fedemaleh May 21, 2026
8bc5dac
feat: normalize cpu/ram limit capabilities to request value when unset
fedemaleh May 21, 2026
f50b59c
feat: render application container limits from normalized capability …
fedemaleh May 21, 2026
a40f54a
refactor: tighten normalize_capability_limits jq + bats here-string i…
fedemaleh May 21, 2026
3eff675
fix: mark cpu_millicores_limit and ram_memory_limit as required for U…
fedemaleh May 21, 2026
6856c9c
refactor: make cpu_millicores_limit a dropdown with 'Same as request'…
fedemaleh May 21, 2026
d08ca79
docs: align ram_memory_limit description with cpu_millicores_limit ph…
fedemaleh May 22, 2026
88f65ee
chore: move design spec and plan to .claude (untracked working notes)
fedemaleh May 22, 2026
26aae22
docs: add changelog entry for configurable CPU and memory limits
fedemaleh May 22, 2026
d9cf93f
Merge branch 'beta' into feature/clien-781-memory-cpu-limits
fedemaleh May 22, 2026
81726e1
refactor: drop ticket id and noise from normalize_capability_limits c…
fedemaleh May 22, 2026
0a3ab0f
test: exercise normalize via full build_context instead of private fu…
fedemaleh May 22, 2026
811b607
test: remove deployment template shape tests in favor of integration …
fedemaleh May 22, 2026
44776c7
feat: clamp limit to request when below it as defense-in-depth
fedemaleh May 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Public and private scopes now register DNS records in their correct Route53 hosted zone when using `DNS_TYPE=external_dns`, preventing cross-zone record leakage
- Add configurable main HTTP port for k8s scopes (default 8080) and HTTP support for additional ports
- Improve **wait deployment active** failure logging: consolidate repeated `Unhealthy` probe events per pod into a single human-readable line, emit a progress heartbeat every 10% of timeout, and surface a targeted suggested fix based on the probe failure mode (port not open / HTTP non-2xx / probe timeout)
- Add configurable memory and CPU limits, independent from requests, for k8s scope containers
- Improve **k8s/diagnose** evidence: every check now emits structured evidence following a documented schema (`summary`, `severity`, `affected`, `details`, `suggested_actions`), failure findings embed the relevant pod log slice (current or previous depending on the failure mode), and a new **Application Logs** category surfaces the user-owned `application` container's log tail directly in the UI

## [1.11.0] - 2026-04-16
Expand Down
16 changes: 16 additions & 0 deletions k8s/deployment/build_context
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ MIN_REPLICAS=$(echo "$MIN_REPLICAS" | awk '{printf "%d", ($1 == int($1) ? $1 : i

DEPLOYMENT_STATUS=$(echo "$CONTEXT" | jq -r ".deployment.status")

# Fill in *_limit capability fields with the corresponding request value when
# the limit is missing or explicitly null, then clamp any limit below its
# request up to the request value. The schema rejects limit < request at save
# time; this is defense-in-depth so the script can never produce an invalid
# resources block, regardless of how the context was built.
normalize_capability_limits() {
echo "$1" | jq '
.scope.capabilities.cpu_millicores_limit //= .scope.capabilities.cpu_millicores
| .scope.capabilities.ram_memory_limit //= .scope.capabilities.ram_memory
| .scope.capabilities.cpu_millicores_limit = ([.scope.capabilities.cpu_millicores, .scope.capabilities.cpu_millicores_limit] | max)
| .scope.capabilities.ram_memory_limit = ([.scope.capabilities.ram_memory, .scope.capabilities.ram_memory_limit] | max)
'
}

validate_status() {
local action="$1"
local status="$2"
Expand Down Expand Up @@ -313,6 +327,8 @@ CONTEXT=$(echo "$CONTEXT" | jq \
main_http_port: ($main_http_port | tonumber)
}')

CONTEXT=$(normalize_capability_limits "$CONTEXT")

DEPLOYMENT_ID=$(echo "$CONTEXT" | jq -r '.deployment.id')

OUTPUT_DIR="$SERVICE_PATH/output/$SCOPE_ID-$DEPLOYMENT_ID"
Expand Down
4 changes: 2 additions & 2 deletions k8s/deployment/templates/deployment.yaml.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,8 @@ spec:
{{ end }}
resources:
limits:
cpu: {{ .scope.capabilities.cpu_millicores }}m
memory: {{ .scope.capabilities.ram_memory }}Mi
cpu: {{ .scope.capabilities.cpu_millicores_limit }}m
memory: {{ .scope.capabilities.ram_memory_limit }}Mi
requests:
cpu: {{ .scope.capabilities.cpu_millicores }}m
memory: {{ .scope.capabilities.ram_memory }}Mi
Expand Down
92 changes: 92 additions & 0 deletions k8s/deployment/tests/build_context.bats
Original file line number Diff line number Diff line change
Expand Up @@ -946,3 +946,95 @@ set_additional_ports() {

assert_equal "$(echo "$CONTEXT" | jq -c '.scope.capabilities.additional_ports')" "[]"
}

# =============================================================================
# Capability limits normalization
# These tests source the real deployment/build_context and assert on the
# resulting CONTEXT, exercising the full pipeline. Limits default to their
# corresponding request value when missing or explicitly null; explicit values
# pass through.
# =============================================================================

# Patches CONTEXT.scope.capabilities with the given JSON fragment (merged into
# the existing capabilities object).
set_capabilities() {
CONTEXT=$(echo "$CONTEXT" | jq --argjson v "$1" '.scope.capabilities = (.scope.capabilities + $v)')
}

@test "capability limits: cpu limit defaults to cpu_millicores when absent" {
setup_full_build_context
set_capabilities '{"cpu_millicores":500,"ram_memory":1024,"ram_memory_limit":2048}'

source "$SCRIPT"

assert_equal "$(echo "$CONTEXT" | jq -r '.scope.capabilities.cpu_millicores_limit')" "500"
}

@test "capability limits: ram limit defaults to ram_memory when absent" {
setup_full_build_context
set_capabilities '{"cpu_millicores":500,"cpu_millicores_limit":1000,"ram_memory":1024}'

source "$SCRIPT"

assert_equal "$(echo "$CONTEXT" | jq -r '.scope.capabilities.ram_memory_limit')" "1024"
}

@test "capability limits: both limits default to their requests when both absent" {
setup_full_build_context
set_capabilities '{"cpu_millicores":500,"ram_memory":1024}'

source "$SCRIPT"

assert_equal "$(echo "$CONTEXT" | jq -r '.scope.capabilities.cpu_millicores_limit')" "500"
assert_equal "$(echo "$CONTEXT" | jq -r '.scope.capabilities.ram_memory_limit')" "1024"
}

@test "capability limits: explicit null limits fall back to their requests" {
setup_full_build_context
set_capabilities '{"cpu_millicores":500,"cpu_millicores_limit":null,"ram_memory":1024,"ram_memory_limit":null}'

source "$SCRIPT"

assert_equal "$(echo "$CONTEXT" | jq -r '.scope.capabilities.cpu_millicores_limit')" "500"
assert_equal "$(echo "$CONTEXT" | jq -r '.scope.capabilities.ram_memory_limit')" "1024"
}

@test "capability limits: explicit non-null limits pass through unchanged" {
setup_full_build_context
set_capabilities '{"cpu_millicores":500,"cpu_millicores_limit":2000,"ram_memory":1024,"ram_memory_limit":4096}'

source "$SCRIPT"

assert_equal "$(echo "$CONTEXT" | jq -r '.scope.capabilities.cpu_millicores_limit')" "2000"
assert_equal "$(echo "$CONTEXT" | jq -r '.scope.capabilities.ram_memory_limit')" "4096"
}

@test "capability limits: cpu limit below request is clamped up to request" {
setup_full_build_context
set_capabilities '{"cpu_millicores":500,"cpu_millicores_limit":100,"ram_memory":1024,"ram_memory_limit":2048}'

source "$SCRIPT"

assert_equal "$(echo "$CONTEXT" | jq -r '.scope.capabilities.cpu_millicores_limit')" "500"
assert_equal "$(echo "$CONTEXT" | jq -r '.scope.capabilities.ram_memory_limit')" "2048"
}

@test "capability limits: ram limit below request is clamped up to request" {
setup_full_build_context
set_capabilities '{"cpu_millicores":500,"cpu_millicores_limit":1000,"ram_memory":1024,"ram_memory_limit":64}'

source "$SCRIPT"

assert_equal "$(echo "$CONTEXT" | jq -r '.scope.capabilities.cpu_millicores_limit')" "1000"
assert_equal "$(echo "$CONTEXT" | jq -r '.scope.capabilities.ram_memory_limit')" "1024"
}

@test "capability limits: both limits below their requests are clamped up" {
setup_full_build_context
set_capabilities '{"cpu_millicores":500,"cpu_millicores_limit":100,"ram_memory":1024,"ram_memory_limit":64}'

source "$SCRIPT"

assert_equal "$(echo "$CONTEXT" | jq -r '.scope.capabilities.cpu_millicores_limit')" "500"
assert_equal "$(echo "$CONTEXT" | jq -r '.scope.capabilities.ram_memory_limit')" "1024"
}
53 changes: 52 additions & 1 deletion k8s/specs/service-spec.json.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
"type":"object",
"required":[
"ram_memory",
"ram_memory_limit",
"visibility",
"autoscaling",
"health_check",
"scaling_type",
"cpu_millicores",
"cpu_millicores_limit",
"fixed_instances",
"scheduled_stop",
"additional_ports",
Expand Down Expand Up @@ -44,12 +46,22 @@
"elements":[
{
"type":"Category",
"label":"Processor",
"label":"Resources",
"elements":[
{
"type":"Control",
"label":"CPU Millicores",
"scope":"#/properties/cpu_millicores"
},
{
"type":"Control",
"label":"CPU Millicores Limit",
"scope":"#/properties/cpu_millicores_limit"
},
{
"type":"Control",
"label":"RAM Memory Limit",
"scope":"#/properties/ram_memory_limit"
}
]
},
Expand Down Expand Up @@ -356,6 +368,27 @@
"default":128,
"description":"Amount of RAM memory to allocate to the container (in MB)"
},
"ram_memory_limit":{
"type":["integer","null"],
"oneOf":[
{"const":null, "title":"Same as request"},
{"const":64, "title":"64 MB"},
{"const":128, "title":"128 MB"},
{"const":256, "title":"256 MB"},
{"const":512, "title":"512 MB"},
{"const":1024, "title":"1 GB"},
{"const":2048, "title":"2 GB"},
{"const":4096, "title":"4 GB"},
{"const":8192, "title":"8 GB"},
{"const":16384, "title":"16 GB"}
],
"title":"RAM Memory Limit",
"default":null,
"minimum":{
"$data":"1/ram_memory"
},
"description":"Maximum memory the container can use (in MB). Pick 'Same as request' to leave it equal to the request value."
},
"visibility":{
"type":"string",
"oneOf":[
Expand Down Expand Up @@ -490,6 +523,24 @@
"minimum":100,
"description":"Amount of CPU to allocate (in millicores, 1000m = 1 CPU core)"
},
"cpu_millicores_limit":{
"type":["integer","null"],
"oneOf":[
{"const":null, "title":"Same as request"},
{"const":100, "title":"100 m"},
{"const":250, "title":"250 m"},
{"const":500, "title":"500 m"},
{"const":1000, "title":"1000 m"},
{"const":2000, "title":"2000 m"},
{"const":4000, "title":"4000 m"}
],
"title":"CPU Millicores Limit",
"default":null,
"minimum":{
"$data":"1/cpu_millicores"
},
"description":"Maximum CPU the container can use (in millicores). Pick 'Same as request' to leave it equal to the request value."
},
"scheduled_stop":{
"type":"object",
"title":"Scheduled Stop",
Expand Down
Loading