Skip to content

Commit 2501a26

Browse files
p3rf Teamcopybara-github
authored andcommitted
Add Swap Encryption capabilities
PiperOrigin-RevId: 951250894
1 parent ae04448 commit 2501a26

20 files changed

Lines changed: 5805 additions & 10 deletions

perfkitbenchmarker/configs/container_spec.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from perfkitbenchmarker import virtual_machine_spec
2828
from perfkitbenchmarker.configs import option_decoders
2929
from perfkitbenchmarker.configs import spec
30+
from perfkitbenchmarker.configs import swap_config_spec
3031
from perfkitbenchmarker.resources import kubernetes_inference_server_spec
3132

3233

@@ -246,6 +247,7 @@ def __init__(
246247
# For GCP TPUs:
247248
self.tpu_topology: str | None
248249
self.tpu_count: int | None
250+
self.swap_config: swap_config_spec.SwapConfigSpec | None
249251

250252
@classmethod
251253
def _GetOptionDecoderConstructions(cls):
@@ -278,6 +280,10 @@ def _GetOptionDecoderConstructions(cls):
278280
'sandbox_config': (_SandboxDecoder, {'default': None}),
279281
'tpu_topology': (option_decoders.StringDecoder, {'default': None}),
280282
'tpu_count': (option_decoders.IntDecoder, {'default': None}),
283+
'swap_config': (
284+
swap_config_spec.SwapConfigDecoder,
285+
{'default': None},
286+
),
281287
})
282288
return result
283289

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Copyright 2026 PerfKitBenchmarker Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""SwapConfigSpec and decoder for BENCHMARK_CONFIG swap_config declarations.
15+
16+
Declares cloud-agnostic swap options for a GKE/EKS nodepool.
17+
Cloud-specific implementation classes (GkeSwapConfig, EksSwapConfig) live in
18+
their respective provider directories:
19+
providers/gcp/gcp_swap_config.py (CLOUD = 'GCP')
20+
providers/aws/aws_swap_config.py (CLOUD = 'AWS', deferred)
21+
22+
Use swap_config.GetSwapConfigClass(cloud) to obtain the implementation class.
23+
"""
24+
25+
from perfkitbenchmarker.configs import option_decoders
26+
from perfkitbenchmarker.configs import spec
27+
28+
29+
class SwapConfigSpec(spec.BaseSpec):
30+
"""Cloud-agnostic swap options for a nodepool.
31+
32+
Declared in BENCHMARK_CONFIG under nodepools.<name>.swap_config.
33+
Consumed by the cloud provider's _AddNodeParamsToCmd() to apply
34+
cloud-specific swap configuration during nodepool creation.
35+
36+
Common attributes apply to all clouds. GCP-specific attributes (lssd,
37+
boot_disk_iops, boot_disk_throughput) are ignored by non-GCP providers.
38+
Supported: GCP. AWS deferred.
39+
40+
Attributes:
41+
enabled: Whether to enable swap on the nodepool (default True).
42+
swappiness: vm.swappiness sysctl value (0-200, default 100).
43+
min_free_kbytes: vm.min_free_kbytes sysctl (default 200).
44+
watermark_scale_factor: vm.watermark_scale_factor sysctl (default 500).
45+
lssd: True if the nodepool uses local NVMe SSDs for the swap device (GCP).
46+
lssd_count: Number of local NVMe SSDs — GKE dedicatedLocalSsdProfile (GCP).
47+
boot_disk_iops: Provisioned IOPS for hyperdisk-balanced (GCP, 0 = not set).
48+
boot_disk_throughput: Provisioned throughput MiB/s for hyperdisk-balanced
49+
(GCP).
50+
"""
51+
52+
def __init__(self, *args, **kwargs):
53+
self.enabled: bool = True
54+
self.swappiness: int = 100
55+
self.min_free_kbytes: int = 200
56+
self.watermark_scale_factor: int = 500
57+
self.lssd: bool = False
58+
self.lssd_count: int = 0
59+
self.boot_disk_iops: int = 0
60+
self.boot_disk_throughput: int = 0
61+
super().__init__(*args, **kwargs)
62+
63+
@classmethod
64+
def _GetOptionDecoderConstructions(cls):
65+
result = super()._GetOptionDecoderConstructions()
66+
result.update({
67+
'enabled': (
68+
option_decoders.BooleanDecoder,
69+
{'default': True},
70+
),
71+
'swappiness': (
72+
option_decoders.IntDecoder,
73+
{'default': 100, 'min': 0, 'max': 200},
74+
),
75+
'min_free_kbytes': (
76+
option_decoders.IntDecoder,
77+
{'default': 200, 'min': 0},
78+
),
79+
'watermark_scale_factor': (
80+
option_decoders.IntDecoder,
81+
{'default': 500, 'min': 0},
82+
),
83+
'lssd': (
84+
option_decoders.BooleanDecoder,
85+
{'default': False},
86+
),
87+
'lssd_count': (
88+
option_decoders.IntDecoder,
89+
{'default': 0, 'min': 0},
90+
),
91+
'boot_disk_iops': (
92+
option_decoders.IntDecoder,
93+
{'default': 0, 'min': 0},
94+
),
95+
'boot_disk_throughput': (
96+
option_decoders.IntDecoder,
97+
{'default': 0, 'min': 0},
98+
),
99+
})
100+
return result
101+
102+
103+
class SwapConfigDecoder(option_decoders.TypeVerifier):
104+
"""Decodes the swap_config option of a NodepoolSpec."""
105+
106+
def Decode(self, value, component_full_name, flag_values):
107+
"""Decodes the swap_config dictionary into a SwapConfigSpec.
108+
109+
Args:
110+
value: dict. Keys match SwapConfigSpec._GetOptionDecoderConstructions.
111+
component_full_name: str. Fully qualified name of the parent component.
112+
flag_values: flags.FlagValues. Runtime flags propagated to BaseSpec.
113+
114+
Returns:
115+
SwapConfigSpec instance.
116+
117+
Raises:
118+
errors.Config.InvalidValue upon invalid input value.
119+
"""
120+
super().Decode(value, component_full_name, flag_values)
121+
return SwapConfigSpec(
122+
self._GetOptionFullName(component_full_name),
123+
flag_values=flag_values,
124+
**value,
125+
)
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
---
2+
# ConfigMap holds the setup script so it can be reviewed and modified
3+
# independently of the DaemonSet manifest structure.
4+
apiVersion: v1
5+
kind: ConfigMap
6+
metadata:
7+
name: {{ ds_name }}-setup
8+
namespace: {{ ds_namespace }}
9+
data:
10+
setup.sh: |
11+
#!/usr/bin/env bash
12+
# swap_encryption_setup.sh — privileged DaemonSet init for PKB swap benchmarks.
13+
# Installs measurement tools, verifies the swap device, then writes /tmp/pkb_ready.
14+
set -euo pipefail
15+
16+
echo "[pkb] Installing benchmark measurement tools..."
17+
# Phase 1 tools: fio (raw-device I/O), cryptsetup/mdadm (dm-crypt inspection),
18+
# sysstat (vmstat/pidstat), nvme-cli (NVMe telemetry).
19+
# Phase 2 tools: stress-ng (CPU/I/O overhead), cgroup-tools (cgroup v1 guard),
20+
# util-linux (taskset/ionice).
21+
# Phase 3b tools: gcc/make/bc/flex/bison/libelf-dev/libssl-dev (kernel build).
22+
# Workload benchmarks (redis, opensearch) run in separate PKB benchmark pods.
23+
PKB_APT_OK=0
24+
for _attempt in 1 2 3; do
25+
DEBIAN_FRONTEND=noninteractive apt-get update -qq 2>&1 || true
26+
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
27+
fio \
28+
cryptsetup \
29+
mdadm \
30+
sysstat \
31+
nvme-cli \
32+
stress-ng \
33+
cgroup-tools \
34+
util-linux \
35+
gcc \
36+
make \
37+
bc \
38+
flex \
39+
bison \
40+
libelf-dev \
41+
libssl-dev \
42+
2>&1 && PKB_APT_OK=1 && break
43+
echo "[pkb] apt-get attempt $_attempt failed, retrying in 15s..." >&2
44+
sleep 15
45+
done
46+
if [ "$PKB_APT_OK" != "1" ] || \
47+
! command -v fio >/dev/null 2>&1 || \
48+
! command -v stress-ng >/dev/null 2>&1 || \
49+
! command -v make >/dev/null 2>&1; then
50+
echo "[pkb] FATAL: critical tools (fio, stress-ng, make) not installed after 3 attempts" >&2
51+
exit 1
52+
fi
53+
echo "[pkb] fio: $(fio --version 2>&1 | head -1)"
54+
echo "[pkb] stress-ng: $(stress-ng --version 2>&1 | head -1)"
55+
echo "[pkb] gcc: $(gcc --version 2>&1 | head -1)"
56+
57+
echo "[pkb] Verifying swap device is active..."
58+
PKB_SWAP_FOUND=0
59+
for _attempt in $(seq 1 30); do
60+
if awk 'NR>1{found=1} END{exit !found}' /proc/swaps 2>/dev/null; then
61+
PKB_SWAP_DEV=$(awk 'NR==2{print $1}' /proc/swaps)
62+
echo "[pkb] Swap device active: $PKB_SWAP_DEV"
63+
PKB_SWAP_FOUND=1
64+
break
65+
fi
66+
echo "[pkb] Waiting for swap device (attempt $_attempt/30)..." >&2
67+
sleep 5
68+
done
69+
if [ "$PKB_SWAP_FOUND" != "1" ]; then
70+
echo "[pkb] FATAL: no active swap device after 150s." \
71+
"Check linuxConfig.swapConfig / kubelet swap config." >&2
72+
exit 1
73+
fi
74+
75+
echo "[pkb] Benchmark tools ready. Writing ready sentinel."
76+
touch /tmp/pkb_ready
77+
exec sleep infinity
78+
---
79+
apiVersion: apps/v1
80+
kind: DaemonSet
81+
metadata:
82+
name: {{ ds_name }}
83+
namespace: {{ ds_namespace }}
84+
labels:
85+
app: {{ ds_label }}
86+
spec:
87+
selector:
88+
matchLabels:
89+
app: {{ ds_label }}
90+
template:
91+
metadata:
92+
labels:
93+
app: {{ ds_label }}
94+
spec:
95+
hostPID: true
96+
hostNetwork: true
97+
# Pin to the benchmark nodepool — never schedule on the dummy default pool.
98+
nodeSelector:
99+
pkb_nodepool: {{ benchmark_nodepool }}
100+
tolerations:
101+
- operator: Exists
102+
containers:
103+
- name: benchmark
104+
image: {{ image }}
105+
command:
106+
- bash
107+
- /scripts/setup.sh
108+
securityContext:
109+
privileged: true
110+
capabilities:
111+
add: ["SYS_ADMIN", "IPC_LOCK"]
112+
resources:
113+
requests:
114+
memory: "512Mi"
115+
env:
116+
- name: NODE_NAME
117+
valueFrom:
118+
fieldRef:
119+
fieldPath: spec.nodeName
120+
volumeMounts:
121+
- name: scripts
122+
mountPath: /scripts
123+
readOnly: true
124+
- name: dev
125+
mountPath: /dev
126+
- name: sys
127+
mountPath: /sys
128+
- name: run
129+
mountPath: /run
130+
- name: proc-host
131+
mountPath: /proc-host
132+
readOnly: true
133+
- name: stateful-partition
134+
mountPath: /mnt/stateful_partition
135+
- name: lib-modules
136+
mountPath: /lib/modules
137+
readOnly: true
138+
volumes:
139+
- name: scripts
140+
configMap:
141+
name: {{ ds_name }}-setup
142+
defaultMode: 0755
143+
- name: dev
144+
hostPath:
145+
path: /dev
146+
- name: sys
147+
hostPath:
148+
path: /sys
149+
- name: run
150+
hostPath:
151+
path: /run
152+
- name: proc-host
153+
hostPath:
154+
path: /proc
155+
- name: stateful-partition
156+
hostPath:
157+
path: /mnt/stateful_partition
158+
type: DirectoryOrCreate
159+
- name: lib-modules
160+
hostPath:
161+
path: /lib/modules
162+
type: Directory

perfkitbenchmarker/linux_benchmarks/kubernetes/swap_encryption/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)