Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
45 changes: 45 additions & 0 deletions lib/steps/eks_aws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,51 @@ func TestAWSEKSDeployTigeraAliasChain(t *testing.T) {
"urn:pulumi:"+stack+"::"+proj+"::ptd:AWSWorkloadEKS$ptd:TigeraOperator$kubernetes:core/v1:Namespace::wl01-staging-20250101-tigera-ns")
}

// TestAWSEKSDeployTigeraResourceRequests asserts the tigera-operator helm
// release carries the Calico resource requests/limits following PTD's
// memory-bounded, CPU-unbounded policy: memory request == limit and no CPU limit.
func TestAWSEKSDeployTigeraResourceRequests(t *testing.T) {
mocks := &eksStepMocks{}
err := pulumi.RunErr(func(ctx *pulumi.Context) error {
return awsEKSDeploy(ctx, mockAWSWorkloadTarget("wl01-staging"), newTestEKSParams())
}, pulumi.WithMocks("ptd-aws-workload-eks", "wl01-staging", mocks))
require.NoError(t, err)

rel := mocks.findResource("wl01-staging-20250101-tigera-operator")
require.NotNil(t, rel, "tigera-operator helm release not found")
values := rel.Inputs["values"].ObjectValue()

// containerResources digs out the resources block for the single container
// override under a component-deployment map (calicoNodeDaemonSet etc.).
containerResources := func(component resource.PropertyMap, key string) resource.PropertyMap {
containers := component[resource.PropertyKey(key)].ObjectValue()["spec"].ObjectValue()["template"].
ObjectValue()["spec"].ObjectValue()["containers"].ArrayValue()
require.Len(t, containers, 1)
return containers[0].ObjectValue()["resources"].ObjectValue()
}
// assertMemoryBounded checks CPU request set with no CPU limit, and memory
// request == limit at the expected value.
assertMemoryBounded := func(res resource.PropertyMap, wantCPU, wantMem string) {
requests := res["requests"].ObjectValue()
limits := res["limits"].ObjectValue()
assert.Equal(t, wantCPU, requests["cpu"].StringValue())
assert.Equal(t, wantMem, requests["memory"].StringValue())
assert.Equal(t, wantMem, limits["memory"].StringValue())
assert.NotContains(t, limits, resource.PropertyKey("cpu"), "CPU must be unbounded (no limit)")
}

installation := values["installation"].ObjectValue()
assertMemoryBounded(containerResources(installation, "calicoNodeDaemonSet"), "250m", "512Mi")
assertMemoryBounded(containerResources(installation, "typhaDeployment"), "100m", "256Mi")
assertMemoryBounded(containerResources(installation, "calicoKubeControllersDeployment"), "50m", "128Mi")

apiServer := values["apiServer"].ObjectValue()
assertMemoryBounded(containerResources(apiServer, "apiServerDeployment"), "100m", "256Mi")

// Operator pod itself.
assertMemoryBounded(values["resources"].ObjectValue(), "100m", "256Mi")
}

func TestAWSEKSDeployEfsEnabled(t *testing.T) {
params := newTestEKSParams()
params.clusters["20250101"] = types.AWSWorkloadClusterConfig{
Expand Down
68 changes: 68 additions & 0 deletions lib/steps/eks_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,55 @@ import (
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

// calicoResources builds a Kubernetes resources block following PTD's
// "memory-bounded, CPU-unbounded" policy for Calico components:
// - Memory: request == limit. Memory is non-compressible, so equal request
// and limit gives the pod a guaranteed, bounded allocation and prevents a
// runaway component from exhausting node memory (which would crash the node).
// - CPU: request only, no limit. Omitting the CPU limit avoids Linux CFS
// throttling of the dataplane (a throttled calico-node degrades networking
// cluster-wide).
//
// cpuRequest and memory are Kubernetes quantity strings (e.g. "250m", "512Mi").
func calicoResources(cpuRequest, memory string) pulumi.Map {
return pulumi.Map{
"requests": pulumi.Map{
"cpu": pulumi.String(cpuRequest),
"memory": pulumi.String(memory),
},
"limits": pulumi.Map{
"memory": pulumi.String(memory),
},
}
}

// calicoComponentOverride builds the operator component-deployment override that
// patches resources onto a single named container. The operator strategically
// merges this by container name into the rendered DaemonSet/Deployment, so only
// the name and resources need to be specified. Used under installation's
// calicoNodeDaemonSet / typhaDeployment / calicoKubeControllersDeployment and
// apiServer's apiServerDeployment.
//
// Only the main container is patched, not initContainers (e.g. calico-node's
// install-cni). Init containers are ephemeral — they exit after CNI setup and
// don't contribute to steady-state resource pressure — so they're left alone.
func calicoComponentOverride(containerName, cpuRequest, memory string) pulumi.Map {
return pulumi.Map{
"spec": pulumi.Map{
"template": pulumi.Map{
"spec": pulumi.Map{
"containers": pulumi.Array{
pulumi.Map{
"name": pulumi.String(containerName),
"resources": calicoResources(cpuRequest, memory),
},
},
},
},
},
}
}

// deployTigeraOperator ports python-pulumi/src/ptd/pulumi_resources/tigera_operator.py
// (the ptd:TigeraOperator nested ComponentResource created by
// aws_workload_eks.py:_define_tigera_operator). It installs the Calico/Tigera CNI:
Expand Down Expand Up @@ -112,6 +161,25 @@ func deployTigeraOperator(
"ipam": pulumi.Map{"type": pulumi.String("Calico")},
"type": pulumi.String("Calico"),
},
// Resource overrides for the operator-managed dataplane
// components (memory-bounded, CPU-unbounded — see calicoResources).
// calico-node runs Felix, whose memory scales with the number of
// endpoints and policies, so it gets the largest memory bound and
// keeps the operator's built-in 250m CPU request floor.
"calicoNodeDaemonSet": calicoComponentOverride("calico-node", "250m", "512Mi"),
"typhaDeployment": calicoComponentOverride("calico-typha", "100m", "256Mi"),
"calicoKubeControllersDeployment": calicoComponentOverride("calico-kube-controllers", "50m", "128Mi"),
},
// Resources for the tigera/operator pod itself. Same
// memory-bounded/CPU-unbounded policy as the dataplane components.
"resources": calicoResources("100m", "256Mi"),
// calico-apiserver lives outside "installation": its resources map to
// the APIServer CR (apiServer.apiServerDeployment), a separate CR from
// the Installation, so the chart exposes it under a top-level apiServer
// key rather than under installation. apiServer.enabled defaults to true
// in the chart and PTD does not disable it, so this override is live.
"apiServer": pulumi.Map{
"apiServerDeployment": calicoComponentOverride("calico-apiserver", "100m", "256Mi"),
},
"goldmane": pulumi.Map{"enabled": pulumi.Bool(false)},
"whisker": pulumi.Map{"enabled": pulumi.Bool(false)},
Expand Down
Loading