Containers, Kubernetes, Ephemeral Injectors, and Infrastructure Automation: Core Concepts and Mental Model
Chapter 26 proved that an injector is part of the experiment: CPU, heap/GC, sockets, disk and network headroom determine whether configured load becomes achieved load. Containers solve a different problem. They package a known Java/JMeter/userspace environment and make injectors disposable, but they also add image provenance, mount, namespace, resource-quota, virtual-network, DNS, filesystem and exit-state boundaries that can invalidate a test if they are implicit.
Learning objectives
- Separate container/orchestrator lifecycle from JMeter thread lifecycle.
- Explain pinned Java + JMeter + plugins + JMX/data → image/runtime → target → persistent evidence.
- Define image tag/digest, layer, bind mount, writable artifact volume, container network, resource limit and exit state.
- Inspect image/runtime/mount/network/resource/artifact state before execution.
-
Recognize that container
localhostbelongs to that container's network namespace. - Preserve Chapter 26 generator-capacity and Chapter 25 load-math contracts inside ephemeral infrastructure.
1. The practical problem: “works in my shell” is not an injector specification
A workstation has JMeter 5.6.3, one developer plugin, an untracked
Java update, a CSV beside the JMX and a writable home directory. The
same test is started in CI from an image called
jmeter:latest. It cannot find the CSV, writes JTL into
the container layer, has half the CPU quota and resolves
localhost to itself rather than the target.
Containerization did not create reproducibility because the
important state was never specified.
127.0.0.1:8027.
Each required run is 5 threads ×20 loops = 100 samples, 100 ms
pacing, 2 KiB synthetic response, ≤15 seconds. No real credentials,
cloud cluster, public target, privileged container, host Docker
socket mount, RMI service, or OS/JVM global tuning is required.
Never substitute a public/shared/production endpoint.
2. Mental model: package, runtime, execution and evidence are separate
Containerization changes how the injector is packaged, started, networked, limited, and cleaned up; it does not change JMeter thread semantics. The image/runtime lifecycle and the test-plan lifecycle must therefore be measured and evidenced separately.
flowchart TD A[Pinned Java base + verified JMeter binary + optional plugins] --> I[Injector image] J[JMX + properties + synthetic data] --> M[Read-only mounted inputs] I --> C[Ephemeral container / Job Pod] M --> C Q[CPU / memory / PID / filesystem / network limits] --> C C --> E[JMeter CLI engine] E --> H[HTTP protocol/session] H --> T[Private target service] E --> R[JTL + jmeter.log] E --> X[Process exit status] R --> P[Persistent host volume / collected artifact] X --> P T --> S[Target telemetry/events] S --> P K[Container/Kubernetes lifecycle] --> C
The image packages Java/JMeter binaries and any deliberately baked
plugins. Versioned JMX/data/config are separate inputs mounted into
the running injector. Docker/Kubernetes creates an ephemeral process
environment with CPU/memory/filesystem/network constraints. JMeter
still creates the same Thread Groups and protocol sessions as
outside a container. The target sees traffic from the container/pod
network. JTL/jmeter.log and exit state must leave the
ephemeral filesystem before cleanup. Deleting a container/Pod is an
infrastructure lifecycle event; it does not redefine what a JMeter
thread, loop or sample means.
3. New infrastructure terms
| Term | Meaning in this chapter |
|---|---|
| Image | Immutable layered filesystem/config used to create a container. An image ID/digest identifies content more strongly than a human tag. |
| Tag |
Mutable human-readable reference such as
5.6.3-p27; never assume
latest stays the same.
|
| Registry digest / image ID | Content-addressed identity recorded for provenance. A local build has an image ID; pushed/pulled images can have RepoDigests. |
| Bind mount | Host path exposed inside a container. Inputs are mounted read-only; results get a separate writable mount. |
| Read-only root filesystem |
Container image filesystem cannot be modified at runtime;
explicit writable locations such as /tmp//artifacts
are provided.
|
| User-defined Docker network |
Private virtual network with container-name DNS, e.g.
p27-fixture.
|
| Resource request/limit | Kubernetes scheduling request and enforced container CPU/memory ceiling; Docker has analogous runtime limits. |
| Job | Kubernetes run-to-completion controller that creates one or more Pods and records success/failure. |
| Ephemeral injector | Short-lived generator instance created for one test/run and then removed after evidence collection. |
4. Apache JMeter distribution versus a container image
Apache publishes JMeter binaries and integrity material. The mandatory path builds a custom container from that verified Apache tarball; it does not claim any community JMeter image is maintained by Apache. The Java base is the Docker Official Image for Eclipse Temurin at a version-specific Java 17.0.20+8 tag. Record its resolved digest before building.
The resulting local JMeter image has two provenance anchors: the base image's resolved registry digest and the Apache JMeter tarball's published SHA-512.
5. Container lifetime is not thread lifetime
One container can run one JMeter process containing 5 threads; two Kubernetes Pods each running that plan configure 10 total threads, not five. A Job retry can run the program again and create additional target work. Therefore:
- container count/Job parallelism belongs in configured-load math;
- JMeter Thread Group threads/loops still belong inside each process;
- orchestrator retries/deadlines/evictions belong in infrastructure validity;
- target counts and per-injector labels prove achieved work.
6. Mount JMX/data/config read-only
Docker bind mounts are read-write by default, so use the
readonly/ro option for plans/data/config.
This prevents a compromised/mistaken sampler/script from modifying
the source assets through the container. Artifacts get a separate
write mount.
7. Ephemeral filesystems and evidence
If JMeter writes /work/results.jtl only inside the
container, deleting the container can delete the experiment. The
required pattern is:
- inputs: immutable/read-only;
/tmp: disposable scratch;-
/artifacts: host bind mount/PVC/explicit artifact collection; -
JTL and matching
jmeter.log: always outside the ephemeral layer before cleanup; - target events/telemetry and container exit state: retained with the run manifest.
8. Container localhost is not host/sibling localhost
Inside the JMeter container, 127.0.0.1 points to the
JMeter container itself. A sibling fixture is reached by its
service/container name on the private network, e.g.
p27-fixture:8027. Kubernetes uses Service/DNS names
similarly. This is namespace semantics, not a JMeter DNS bug.
9. Resource limits are part of generator sizing
Chapter 26 measured usable generator capacity. A Docker
--cpus/--memory or Kubernetes CPU/memory
limit can move that capacity ceiling even when the physical host is
idle. Record the container limit and keep Java heap below the memory
limit with enough native/metaspace/thread/socket headroom.
10. State checklist before an ephemeral run
| State | Read-only question |
|---|---|
| Image/runtime | Exact base tag + resolved digest, JMeter SHA/version, final image ID, Docker/kind/kubectl versions? |
| JMeter/thread | Threads/loops/timers/CLI/properties and total count across containers/Pods? |
| Components/plugins | Stock Apache tarball or exact added plugin/JAR hashes? |
| Inputs | Which JMX/data/config are mounted; read-only; hashes match native run? |
| Protocol/session | Target hostname inside namespace, keep-alive/TLS/DNS behavior, service identity? |
| Resources | CPU/memory/PID/read-only root/tmp limits and Chapter 26 generator headroom? |
| Artifacts | Where JTL/log/target events go; are they persistent before deletion? |
| Credentials/trust | No secret in Dockerfile/layer/tag/log; runtime secret source and scope if needed? |
| Exit state | Container/Job exit code, OOMKilled/restarts/deadline, achieved target count? |
| Validity | Configured load equals sum of injectors and evidence proves achieved work without generator/container saturation? |
11. Read-only inspection first
PowerShell / portable Docker commands:
docker version
docker info --format "{{json .SecurityOptions}}"
docker pull eclipse-temurin:17.0.20_8-jre-jammy
docker image inspect eclipse-temurin:17.0.20_8-jre-jammy `
--format "{{json .RepoDigests}}"
docker image ls --digests
docker network ls
docker ps -a
kind version
kubectl version --client
kubectl config current-context
If the current Kubernetes context is not the disposable local cluster you intended, stop. Do not apply a teaching Job to a shared/production cluster.
12. DevOps connection
Ephemeral injectors improve repeatability only when the artifact has a supply chain and the runtime has a contract. Image provenance, immutable inputs, network target identity, resource quotas, secrets, exit status and evidence retention are as important as the JMX itself.
Knowledge check
Does two Pods running a 5-thread JMX mean five or ten configured threads?
Ten. Each Pod/JMeter JVM runs the full plan; container orchestration does not divide Thread Group threads automatically.
Why mount JMX/data read-only?
It protects versioned test inputs from runtime modification and makes the executed artifact easier to reproduce.
Why is a tag weaker provenance than a digest/image ID?
Tags are mutable names; content-addressed identities refer to specific image content.
Why can a container memory limit invalidate a JMeter test?
The injector may hit cgroup/container memory pressure or OOM even when the physical host has free RAM.
Why does 127.0.0.1 fail for a sibling container target?
Each container has its own network namespace; localhost points back to the JMeter container, not the sibling target.
Official references and version notes
- Apache JMeter downloads — JMeter 5.6.3, Java requirement, SHA-512/PGP integrity verification.
- Apache-published JMeter 5.6.3 SHA-512 — checksum used by the lab Dockerfile.
- Eclipse Temurin Docker Official Image — version-specific Java 17.0.20+8 JRE base used by the custom injector image.
- Docker bind mounts — read-only input mounts and writable host artifact mounts.
- Docker run reference — read-only root filesystem, CPU/memory limits, mounts, networks and exit-state inspection.
-
Kubernetes Jobs
— run-to-completion semantics, parallelism/completions,
restartPolicy, backoff and active deadlines. - kind Quick Start — local cluster lifecycle and loading locally built images.
Version-sensitive statements were rechecked against current
primary documentation on 2026-09-05. The course baseline remains
Apache JMeter 5.6.3, requiring Java 8+; this
chapter uses
Eclipse Temurin 17.0.20+8 JRE (Jammy) as the
version-specific Java base and records its resolved registry
digest locally. The JMeter layer is built from Apache's binary
tarball and verifies Apache's published SHA-512:
5978a1a35edb5a7d428e270564ff49d2b1b257a65e17a759d259a9283fc17093e522fe46f474a043864aea6910683486340706d745fcdf3db1505fd71e689083. Apache JMeter does not need a third-party plugin for the
mandatory lab. Docker bind mounts are writable by default, so the
JMX/data/config inputs are explicitly readonly; only
the artifact mount is writable. The optional Kubernetes path uses
current batch/v1 Job semantics with
restartPolicy: Never, backoffLimit: 0,
activeDeadlineSeconds, resource requests/limits, and
no cloud requirement. The local cluster example assumes
kind v0.32.0; a current minikube installation is
an optional equivalent.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.