Flink¶
dev.rightsize.modules.FlinkContainer — an Apache Flink JobManager, optionally
paired with a companion TaskManager via withTaskManager() for a real session
cluster that can actually run jobs (a bare JobManager has zero task slots and can
only accept/reject submissions, never execute them).
Defaults¶
| Default image | flink:1.20.5 |
| Exposed ports | 8081 (REST), 6123 (RPC — only meaningful once a TaskManager joins) |
| Command | jobmanager |
| Memory limit | withMemoryLimit(1024) — see below |
| Wait strategy | Wait.forHttp("/overview").forPort(8081).withStartupTimeout(Duration.ofSeconds(120)) |
Helpers¶
| Member | Returns |
|---|---|
restUrl: String |
The JobManager REST base URI (/overview, /taskmanagers, job submission, etc.) |
withTaskManager(): FlinkContainer |
Adds a companion TaskManager on a shared network for a real session cluster with task slots — docker only, see below |
Call withTaskManager() before start().
Example¶
package dev.rightsize.modules
import dev.rightsize.modules.FlinkContainer
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
class FlinkContainerTest {
private val http = HttpClient.newHttpClient()
@Test
fun `bare JobManager answers REST overview`() {
val flink = FlinkContainer()
flink.start()
try {
val resp = http.send(
HttpRequest.newBuilder(URI("${flink.restUrl}/overview")).GET().build(),
HttpResponse.BodyHandlers.ofString(),
)
assertEquals(200, resp.statusCode(), "overview failed: ${resp.body()}")
assertTrue(resp.body().contains("\"taskmanagers\""), "unexpected overview body: ${resp.body()}")
} finally {
flink.stop()
}
}
}
A full session-cluster example (docker only):
package dev.rightsize.modules
import dev.rightsize.modules.FlinkContainer
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
class FlinkSessionClusterTest {
private val http = HttpClient.newHttpClient()
@Test
fun `withTaskManager registers a slot-bearing TaskManager`() {
val flink = FlinkContainer().withTaskManager()
flink.start()
try {
val deadline = System.currentTimeMillis() + 60_000
var body = ""
while (System.currentTimeMillis() < deadline) {
val resp = http.send(
HttpRequest.newBuilder(URI("${flink.restUrl}/taskmanagers")).GET().build(),
HttpResponse.BodyHandlers.ofString(),
)
body = resp.body()
if (resp.statusCode() == 200 && body.contains("\"id\"")) break
Thread.sleep(1000)
}
assertTrue(body.contains("\"id\""), "TaskManager never registered: $body")
} finally {
flink.stop()
}
}
}
Backend notes¶
A real Flink session cluster is two processes bound by a persistent bidirectional
RPC connection, not a one-shot request/response: the TaskManager dials the
JobManager's RPC port (6123) at boot and stays connected, carrying heartbeats and
slot offers/task deployments both ways for the cluster's whole lifetime.
withTaskManager() puts both containers on one internally-created network, aliases
the JobManager as jobmanager, and sets FLINK_PROPERTIES=jobmanager.rpc.address:
jobmanager on both containers — not just the TaskManager. Verified directly:
setting it on the TaskManager alone leaves the JobManager's own Pekko actor system
bound under its container hostname rather than the alias, so every registration
attempt from the TaskManager gets silently dropped as a non-local recipient. The
JobManager must be told its own address is the alias too.
withTaskManager() is Docker only — it throws UnsupportedByBackendException on
microsandbox, and the reason is more basic than a Pekko/tunnel incompatibility: the
official Flink image is missing a prerequisite the tunnel needs. On docker, this is
verified end-to-end: the TaskManager registers with the JobManager (Successful
registration at resource manager ... in its own log) and GET /taskmanagers on the
JobManager's REST port shows one slot-bearing TM within seconds of both containers
starting.
On microsandbox, withTaskManager() throws before ever booting anything. msb's
network-link emulation requires nc/busybox inside the consumer image to serve
the tunnel's in-guest listener, and the official flink:1.20.5 image is a bare JRE
+ Flink install with neither — the attempt fails immediately with
UnsupportedByBackendException: network links (no nc/busybox in consumer image
'flink:1.20.5'), thrown before a single byte of Pekko traffic could be exchanged.
Whether Pekko's persistent-connection registration would also work over the
tunnel's single-connection-at-a-time model was never reached or tested — the missing
nc/busybox prerequisite stops the attempt before that question is even in play.
A bare JobManager works fine on both backends. It needs no network-link
emulation at all — just the ordinary published-port HTTP path against /overview —
so this module supports msb for JobManager-only use; only withTaskManager() is
gated to docker.
Memory — JVM, the ladder applies to both roles. A JobManager settles around
~310 MiB RSS and a TaskManager around ~375 MiB RSS at rest on docker with no cap
(docker stats, real boot) — both comfortably over msb's ~450 MB default
individually, and this module runs the JobManager on msb too (see above), so
withMemoryLimit(1024) is this module's default for both roles, matching the
family's established single-JVM floor (Keycloak, Neo4j).