Skip to content

Commit 2d4e7e2

Browse files
committed
KAFKA-19615: Enable to ducker-ak to isolate the ducker containers
1 parent 3c08439 commit 2d4e7e2

File tree

3 files changed

+184
-47
lines changed

3 files changed

+184
-47
lines changed

tests/README.md

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,38 @@ REBUILD="t" bash tests/docker/run_tests.sh
111111
```
112112
tests/docker/ducker-ak test tests/kafkatest/tests/core/security_test.py --debug -- --debug
113113
```
114+
* Run multiple ducker-ak clusters on one machine using prefixes:
115+
- Use the `--prefix` option to create isolated clusters that can run simultaneously:
116+
```
117+
bash tests/docker/ducker-ak up --prefix cluster1
118+
bash tests/docker/ducker-ak up --prefix cluster2
119+
```
120+
- Run tests on a specific prefixed cluster:
121+
```
122+
bash tests/docker/ducker-ak test tests/kafkatest/tests/client/pluggable_test.py::PluggableConsumerTest.test_start_stop --prefix cluster1
123+
```
124+
- Tear down a specific prefixed cluster:
125+
```
126+
bash tests/docker/ducker-ak down --prefix cluster1
127+
```
128+
- Alternatively, set the `DUCKER_PREFIX` environment variable:
129+
```
130+
DUCKER_PREFIX=cluster1 TC_PATHS="tests/kafkatest/tests/client/pluggable_test.py::PluggableConsumerTest.test_start_stop" bash tests/docker/run_tests.sh
131+
```
132+
* Configure debug port mapping:
133+
- By default, debug port 5678 is mapped to the same port on the host for unprefixed clusters
134+
- For prefixed clusters, debug ports are automatically assigned to random available host ports
135+
- Override debug port behavior with `--debug-port`:
136+
```
137+
bash tests/docker/ducker-ak up --prefix cluster1 --debug-port 55006
138+
```
139+
- The assigned debug port for prefixed clusters is saved to `build/debug-port.<prefix>.txt`
140+
- Use the `DUCKER_DEBUGPY_PORT` environment variable as an alternative to `--debug-port`
114141
115142
* Notes
116-
- The scripts to run tests creates and destroys docker network named *knw*.
117-
This network can't be used for any other purpose.
118-
- The docker containers are named knode01, knode02 etc.
143+
- The scripts to run tests create and destroy docker networks. For unprefixed clusters, the network is named `ducknet`. For prefixed clusters, the network is named `<prefix>-ducknet`.
144+
These networks can't be used for any other purpose.
145+
- The docker containers are named `ducker01`, `ducker02` etc. for unprefixed clusters, or `<prefix>-ducker01`, `<prefix>-ducker02` etc. for prefixed clusters.
119146
These nodes can't be used for any other purpose.
120147
121148
* Exposing ports using --expose-ports option of `ducker-ak up` command

tests/docker/ducker-ak

Lines changed: 133 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,36 @@ default_kafka_mode="jvm"
5858
# Port to listen on when debugging
5959
debugpy_port=5678
6060

61+
# Host port to bind for debugpy on ducker01
62+
debugpy_host_port="${DUCKER_DEBUGPY_PORT:-}"
63+
64+
# Support for running multiple ducker‑ak clusters on one machine.
65+
prefix="${DUCKER_PREFIX:-}"
66+
67+
# the docker network name for this cluster.
68+
net_name() {
69+
if [[ -n "${prefix}" ]]; then
70+
echo "${prefix}-ducknet"
71+
else
72+
echo "ducknet"
73+
fi
74+
}
75+
76+
# the container name prefix (e.g. ducker or <prefix>-ducker)
77+
container_prefix() {
78+
if [[ -n "${prefix}" ]]; then
79+
echo "${prefix}-ducker"
80+
else
81+
echo "ducker"
82+
fi
83+
}
84+
85+
# Given an integer index, produce the full container name.
86+
node_name() {
87+
local idx="${1}"
88+
printf "%s%02d" "$(container_prefix)" "${idx}"
89+
}
90+
6191
# Display a usage message on the terminal and exit.
6292
#
6393
# $1: The exit status to use
@@ -72,7 +102,7 @@ help|-h|--help
72102
Display this help message
73103
74104
up [-n|--num-nodes NUM_NODES] [-f|--force] [docker-image]
75-
[-C|--custom-ducktape DIR] [-e|--expose-ports ports] [-j|--jdk JDK_VERSION] [--ipv6]
105+
[-C|--custom-ducktape DIR] [-e|--expose-ports ports] [-j|--jdk JDK_VERSION] [--ipv6] [--prefix NAME] [--debug-port {PORT|auto}]
76106
Bring up a cluster with the specified amount of nodes (defaults to ${default_num_nodes}).
77107
The docker image name defaults to ${default_image_name}. If --force is specified, we will
78108
attempt to bring up an image even some parameters are not valid.
@@ -91,15 +121,17 @@ up [-n|--num-nodes NUM_NODES] [-f|--force] [docker-image]
91121
92122
If --ipv6 is specified, we will create a Docker network with IPv6 enabled.
93123
94-
Note that port 5678 will be automatically exposed for ducker01 node and will be mapped to 5678
95-
on your local machine to enable debugging in VS Code.
124+
Debug port mapping:
125+
- Without --prefix: ${debugpy_port} (container) -> ${debugpy_port} (host)
126+
- With --prefix: default 'auto' (host random -> ${debugpy_port}); override via --debug-port or DUCKER_DEBUGPY_PORT
127+
- The chosen host port is recorded at build/debug-port.<prefix>.txt
96128
97-
test [-d|--debug] [test-name(s)] [-- [ducktape args]]
129+
test [-d|--debug] [--prefix NAME] [test-name(s)] [-- [ducktape args]]
98130
Run a test or set of tests inside the currently active Ducker nodes.
99131
For example, to run the system test produce_bench_test, you would run:
100132
./tests/docker/ducker-ak test ./tests/kafkatest/tests/core/produce_bench_test.py
101133
102-
If --debug is passed, the tests will wait for remote VS Code debugger to connect on port 5678:
134+
If --debug is passed, the tests will wait for remote VS Code debugger to connect on host debug port:
103135
./tests/docker/ducker-ak test --debug ./tests/kafkatest/tests/core/produce_bench_test.py
104136
105137
To pass arguments to underlying ducktape invocation, pass them after `--`, e.g.:
@@ -113,14 +145,20 @@ ssh [node-name|user-name@node-name] [command]
113145
is specified, we will run that command. Otherwise, we will provide a login
114146
shell.
115147
116-
down [-q|--quiet] [-f|--force]
148+
down [-q|--quiet] [-f|--force] [--prefix NAME]
117149
Tear down all the currently active ducker-ak nodes. If --quiet is specified,
118150
only error messages are printed. If --force or -f is specified, "docker rm -f"
119151
will be used to remove the nodes, which kills currently running ducker-ak test.
152+
If --prefix is provided, only that cluster is removed.
120153
121154
purge [--f|--force]
122155
Purge Docker images created by ducker-ak. This will free disk space.
123156
If --force is set, we run 'docker rmi -f'.
157+
158+
Environment variables:
159+
- DUCKER_PREFIX : equivalent to --prefix
160+
- DUCKER_DEBUGPY_PORT : equivalent to --debug-port
161+
124162
EOF
125163
exit "${exit_status}"
126164
}
@@ -292,14 +330,18 @@ docker_run() {
292330
done
293331
fi
294332
if [[ -n ${port_mapping} ]]; then
295-
expose_ports="${expose_ports} -p ${port_mapping}:${port_mapping}"
333+
if [[ "${port_mapping}" == *:* ]]; then
334+
expose_ports="${expose_ports} -p ${port_mapping}"
335+
else
336+
expose_ports="${expose_ports} -p ${port_mapping}:${port_mapping}"
337+
fi
296338
fi
297339

298340
# Invoke docker-run. We need privileged mode to be able to run iptables
299341
# and mount FUSE filesystems inside the container. We also need it to
300342
# run iptables inside the container.
301343
must_do -v docker run --privileged \
302-
-d -t -h "${node}" --network ducknet "${expose_ports}" \
344+
-d -t -h "${node}" --network "$(net_name)" "${expose_ports}" \
303345
--memory=${docker_run_memory_limit} --memory-swappiness=1 \
304346
-v "${kafka_dir}:/opt/kafka-dev" --name "${node}" -- "${image_name}"
305347
}
@@ -310,14 +352,14 @@ setup_custom_ducktape() {
310352

311353
[[ -f "${custom_ducktape}/ducktape/__init__.py" ]] || \
312354
die "You must supply a valid ducktape directory to --custom-ducktape"
313-
docker_run ducker01 "${image_name}"
314-
local running_container="$(docker ps -f=network=ducknet -q)"
355+
docker_run "$(node_name 1)" "${image_name}"
356+
local running_container="$(docker ps -f=network=$(net_name) -q)"
315357
must_do -v -o docker cp "${custom_ducktape}" "${running_container}:/opt/ducktape"
316-
docker exec --user=root ducker01 bash -c 'set -x && cd /opt/kafka-dev/tests && sudo python3 ./setup.py develop install && cd /opt/ducktape && sudo python3 ./setup.py develop install'
358+
docker exec --user=root "$(node_name 1)" bash -c 'set -x && cd /opt/kafka-dev/tests && sudo python3 ./setup.py develop install && cd /opt/ducktape && sudo python3 ./setup.py develop install'
317359
[[ $? -ne 0 ]] && die "failed to install the new ducktape."
318-
must_do -v -o docker commit ducker01 "${image_name}"
360+
must_do -v -o docker commit "$(node_name 1)" "${image_name}"
319361
must_do -v docker kill "${running_container}"
320-
must_do -v docker rm ducker01
362+
must_do -v docker rm "$(node_name 1)"
321363
}
322364

323365
cleanup_native_dir() {
@@ -348,6 +390,7 @@ prepare_native_dir() {
348390

349391
ducker_up() {
350392
require_commands docker
393+
[[ -z "${prefix}" ]] && prefix="${DUCKER_PREFIX:-}"
351394
while [[ $# -ge 1 ]]; do
352395
case "${1}" in
353396
-C|--custom-ducktape) set_once custom_ducktape "${2}" "the custom ducktape directory"; shift 2;;
@@ -357,6 +400,8 @@ ducker_up() {
357400
-e|--expose-ports) set_once expose_ports "${2}" "the ports to expose"; shift 2;;
358401
-m|--kafka_mode) set_once kafka_mode "${2}" "the mode in which kafka will run"; shift 2;;
359402
--ipv6) set_once ipv6 "true" "enable IPv6"; shift;;
403+
--prefix) set_once prefix "${2}" "prefix"; shift 2;;
404+
--debug-port) set_once debugpy_host_port "${2}" "debug host port (number or 'auto')"; shift 2;;
360405
*) set_once image_name "${1}" "docker image name"; shift;;
361406
esac
362407
done
@@ -396,36 +441,66 @@ it up anyway."
396441
exit 1
397442
fi
398443
fi
399-
local running_containers="$(docker ps -f=network=ducknet -q)"
444+
local running_containers="$(docker ps -f=network=$(net_name) -q)"
400445
local num_running_containers=$(count ${running_containers})
401446
if [[ ${num_running_containers} -gt 0 ]]; then
402447
die "ducker_up: there are ${num_running_containers} ducker containers \
403-
running already. Use ducker down to bring down these containers before \
448+
running already. Use ducker down --prefix ${prefix:-} to bring down these containers before \
404449
attempting to start new ones."
405450
fi
406451

407452
echo "ducker_up: Bringing up ${image_name} with ${num_nodes} nodes..."
408-
if docker network inspect ducknet &>/dev/null; then
409-
must_do -v docker network rm ducknet
453+
if docker network inspect "$(net_name)" &>/dev/null; then
454+
must_do -v docker network rm "$(net_name)"
410455
fi
411456
network_create_args=""
412457
if [[ "${ipv6}" == "true" ]]; then
413458
subnet_cidr_prefix="${DUCKER_SUBNET_CIDR:-"fc00:cf17"}"
414459
network_create_args="--ipv6 --subnet ${subnet_cidr_prefix}::/64"
415460
fi
416-
must_do -v docker network create ${network_create_args} ducknet
461+
must_do -v docker network create ${network_create_args} "$(net_name)"
462+
417463
if [[ -n "${custom_ducktape}" ]]; then
418464
setup_custom_ducktape "${custom_ducktape}" "${image_name}"
419465
fi
420-
docker_run ducker01 "${image_name}" "${expose_ports}" "${debugpy_port}"
466+
# Determine debug host port mapping for the first node:
467+
# - If no prefix and not overridden -> fixed ${debugpy_port}
468+
# - If prefix present and not overridden -> auto (random free host port)
469+
if [[ -z "${debugpy_host_port}" ]]; then
470+
if [[ -n "${prefix}" ]]; then
471+
debugpy_host_port="auto"
472+
else
473+
debugpy_host_port="${debugpy_port}"
474+
fi
475+
fi
476+
if [[ "${debugpy_host_port}" == "auto" ]]; then
477+
debug_map="0:${debugpy_port}"
478+
else
479+
debug_map="${debugpy_host_port}:${debugpy_port}"
480+
fi
481+
docker_run "$(node_name 1)" "${image_name}" "${expose_ports}" "${debug_map}"
482+
483+
# Inform the user which host port is actually used for debug on node1.
484+
if [[ "${debugpy_host_port}" == "auto" ]]; then
485+
assigned="$(docker port "$(node_name 1)" ${debugpy_port}/tcp | awk -F: 'END{print $NF}')"
486+
echo "ducker_up: assigned debug host port ${assigned} for $(node_name 1) (container ${debugpy_port}/tcp)"
487+
debugpy_host_port="${assigned}"
488+
else
489+
echo "ducker_up: debug host port ${debugpy_host_port} mapped to container ${debugpy_port} on $(node_name 1)"
490+
fi
491+
if [[ -n "${prefix}" ]]; then
492+
mkdir -p "${ducker_dir}/build"
493+
echo "${debugpy_host_port}" > "${ducker_dir}/build/debug-port${prefix:+.${prefix}}.txt"
494+
fi
495+
421496
for n in $(seq -f %02g 2 ${num_nodes}); do
422-
local node="ducker${n}"
497+
local node="$(container_prefix)${n}"
423498
docker_run "${node}" "${image_name}" "${expose_ports}"
424499
done
425500
mkdir -p "${ducker_dir}/build"
426-
exec 3<> "${ducker_dir}/build/node_hosts"
501+
exec 3<> "${ducker_dir}/build/node_hosts${prefix:+.${prefix}}"
427502
for n in $(seq -f %02g 1 ${num_nodes}); do
428-
local node="ducker${n}"
503+
local node="$(container_prefix)${n}"
429504
if [[ "${ipv6}" == "true" ]]; then
430505
docker exec --user=root "${node}" grep "${node}" /etc/hosts | grep "${subnet_cidr_prefix}" >&3
431506
else
@@ -435,25 +510,25 @@ attempting to start new ones."
435510
done
436511
exec 3>&-
437512
for n in $(seq -f %02g 1 ${num_nodes}); do
438-
local node="ducker${n}"
513+
local node="$(container_prefix)${n}"
439514
docker exec --user=root "${node}" \
440-
bash -c "grep -v ${node} /opt/kafka-dev/tests/docker/build/node_hosts >> /etc/hosts"
515+
bash -c "grep -v ${node} /opt/kafka-dev/tests/docker/build/node_hosts${prefix:+.${prefix}} >> /etc/hosts"
441516
[[ $? -ne 0 ]] && die "failed to append to the /etc/hosts file on ${node}"
442517
# Filter out ipv4 addresses if ipv6
443518
if [[ "${ipv6}" == "true" ]]; then
444519
docker exec --user=root "${node}" \
445-
bash -c "grep -v -E '([0-9]{1,3}\.){3}[0-9]{1,3}' /opt/kafka-dev/tests/docker/build/node_hosts >> /etc/hosts"
520+
bash -c "grep -v -E '([0-9]{1,3}\.){3}[0-9]{1,3}' /opt/kafka-dev/tests/docker/build/node_hosts${prefix:+.${prefix}} >> /etc/hosts"
446521
[[ $? -ne 0 ]] && die "failed to append to the /etc/hosts file on ${node}"
447522
fi
448523
done
449524

450525
if [ "$kafka_mode" == "native" ]; then
451-
docker exec --user=root ducker01 bash -c 'cp /opt/kafka-binary/kafka.Kafka /opt/kafka-dev/kafka.Kafka'
526+
docker exec --user=root "$(node_name 1)" bash -c 'cp /opt/kafka-binary/kafka.Kafka /opt/kafka-dev/kafka.Kafka'
452527
fi
453528

454529
echo "ducker_up: added the latest entries to /etc/hosts on each node."
455-
generate_cluster_json_file "${num_nodes}" "${ducker_dir}/build/cluster.json"
456-
echo "ducker_up: successfully wrote ${ducker_dir}/build/cluster.json"
530+
generate_cluster_json_file "${num_nodes}" "${ducker_dir}/build/cluster${prefix:+.${prefix}}.json"
531+
echo "ducker_up: successfully wrote ${ducker_dir}/build/cluster${prefix:+.${prefix}}.json"
457532
echo "** ducker_up: successfully brought up ${num_nodes} nodes."
458533
}
459534

@@ -491,7 +566,8 @@ EOF
491566
else
492567
suffix=","
493568
fi
494-
local node=$(printf ducker%02d ${n})
569+
local node_prefix="${prefix:+${prefix}-}ducker"
570+
local node=$(printf "%s%02d" "${node_prefix}" ${n})
495571
cat<<EOF >&3
496572
{
497573
"externally_routable_ip": "${node}",
@@ -527,13 +603,14 @@ correct_latest_link() {
527603

528604
ducker_test() {
529605
require_commands docker
530-
docker inspect ducker01 &>/dev/null || \
531-
die "ducker_test: the ducker01 instance appears to be down. Did you run 'ducker up'?"
606+
[[ -z "${prefix}" ]] && prefix="${DUCKER_PREFIX:-}"
607+
532608
declare -a test_name_args=()
533609
local debug=0
534610
while [[ $# -ge 1 ]]; do
535611
case "${1}" in
536612
-d|--debug) debug=1; shift;;
613+
--prefix) set_once prefix "${2}" "prefix"; shift 2;;
537614
--) shift; break;;
538615
*) test_name_args+=("${1}"); shift;;
539616
esac
@@ -563,9 +640,10 @@ ducker_test() {
563640
local ducktape_cmd="ducktape"
564641
fi
565642

566-
cmd="cd /opt/kafka-dev && ${ducktape_cmd} --cluster-file /opt/kafka-dev/tests/docker/build/cluster.json $test_names $ducktape_args"
567-
echo "docker exec ducker01 bash -c \"${cmd}\""
568-
docker exec --user=ducker ducker01 bash -c "${cmd}"
643+
local cluster_json_in_container="/opt/kafka-dev/tests/docker/build/cluster${prefix:+.${prefix}}.json"
644+
cmd="cd /opt/kafka-dev && ${ducktape_cmd} --cluster-file ${cluster_json_in_container} $test_names $ducktape_args"
645+
echo "docker exec $(node_name 1) bash -c \"${cmd}\""
646+
docker exec --user=ducker "$(node_name 1)" bash -c "${cmd}"
569647
docker_status=$?
570648
correct_latest_link
571649
exit "${docker_status}"
@@ -611,7 +689,7 @@ $(echo_running_container_names)"
611689

612690
# Echo all the running Ducker container names, or (none) if there are no running Ducker containers.
613691
echo_running_container_names() {
614-
node_names="$(docker ps -f=network=ducknet -q --format '{{.Names}}' | sort)"
692+
node_names="$(docker ps -f=network=$(net_name) -q --format '{{.Names}}' | sort)"
615693
if [[ -z "${node_names}" ]]; then
616694
echo "(none)"
617695
else
@@ -621,20 +699,22 @@ echo_running_container_names() {
621699

622700
ducker_down() {
623701
require_commands docker
702+
[[ -z "${prefix}" ]] && prefix="${DUCKER_PREFIX:-}"
624703
local verbose=1
625704
local force_str=""
626705
while [[ $# -ge 1 ]]; do
627706
case "${1}" in
628707
-q|--quiet) verbose=0; shift;;
629708
-f|--force) force_str="-f"; shift;;
709+
--prefix) set_once prefix "${2}" "prefix"; shift 2;;
630710
*) die "ducker_down: unexpected command-line argument ${1}";;
631711
esac
632712
done
633713
local running_containers
634-
running_containers="$(docker ps -f=network=ducknet -q)"
635-
[[ $? -eq 0 ]] || die "ducker_down: docker command failed. Is the docker daemon running?"
714+
running_containers="$(docker ps -f=network=$(net_name) -q)"
715+
[[ $? -eq 0 ]] || die "ducker_down: docker command failed. Is the docker daemon running?"
636716
running_containers=${running_containers//$'\n'/ }
637-
local all_containers="$(docker ps -a -f=network=ducknet -q)"
717+
local all_containers="$(docker ps -a -f=network=$(net_name) -q)"
638718
all_containers=${all_containers//$'\n'/ }
639719
if [[ -z "${all_containers}" ]]; then
640720
maybe_echo "${verbose}" "No ducker containers found."
@@ -648,9 +728,21 @@ ducker_down() {
648728
must_do ${verbose_flag} docker kill "${running_containers}"
649729
fi
650730
must_do ${verbose_flag} docker rm ${force_str} "${all_containers}"
651-
must_do ${verbose_flag} -o rm -f -- "${ducker_dir}/build/node_hosts" "${ducker_dir}/build/cluster.json"
652-
if docker network inspect ducknet &>/dev/null; then
653-
must_do -v docker network rm ducknet
731+
if [[ -n "${prefix}" ]]; then
732+
# Prefixed cluster: remove only prefixed files
733+
must_do ${verbose_flag} -o rm -f -- \
734+
"${ducker_dir}/build/node_hosts.${prefix}" \
735+
"${ducker_dir}/build/debug-port.${prefix}.txt" \
736+
"${ducker_dir}/build/cluster.${prefix}.json"
737+
else
738+
# Unprefixed cluster: remove only unprefixed files
739+
must_do ${verbose_flag} -o rm -f -- \
740+
"${ducker_dir}/build/node_hosts" \
741+
"${ducker_dir}/build/cluster.json"
742+
fi
743+
744+
if docker network inspect "$(net_name)" &>/dev/null; then
745+
must_do -v docker network rm "$(net_name)"
654746
fi
655747
maybe_echo "${verbose}" "ducker_down: removed $(count ${all_containers}) containers."
656748
}

0 commit comments

Comments
 (0)