2025, Oct 06 03:33
pytest और GitLab CI में Docker के पार POSIX shared memory साझा करना
कंटेनरों के बीच POSIX shared memory साझा करने का भरोसेमंद तरीका: pytest और GitLab CI में /dev/shm को Docker volume के रूप में माउंट करें, एक ही DinD daemon उपयोग करें, ipc_mode से बचें.
कंटेनरों के बीच Python के multiprocessing.shared_memory को साझा करना डेवलपर की लैपटॉप पर सीधा-सा लगता है, लेकिन CI आते ही नियम बदल जाते हैं। जैसे ही pytest किसी कंटेनर के अंदर चलता है और Docker SDK के जरिए अतिरिक्त कंटेनरों का संयोजन करता है, /dev/shm को bind-mount करने जैसे परिचित तरीके कमजोर पड़ जाते हैं, और ipc_mode बदलना कोई फायदा नहीं देता। नीचे इस सेटअप में POSIX shared memory पर असल में क्या असर करता है और GitLab runner पर इसे भरोसेमंद तरीके से कैसे चलाया जाए, उसका व्यावहारिक मार्गदर्शन है।
समस्या विवरण
लक्ष्य यह है कि pytest एक POSIX shared memory ब्लॉक बनाए और उसमें लिखे, जिसे अन्य Docker कंटेनर पढ़ सकें। लोकल तौर पर, होस्ट के /dev/shm को टेस्ट कंटेनरों में bind-mount करना काम करता हुआ दिखता है। GitLab CI में, pytest खुद एक कंटेनर के अंदर चलता है और Docker SDK के जरिए Docker daemon से बात करता है, जिससे shared memory की दृश्यता और कंटेनरों के बीच पृथक्करण जटिल हो जाता है।
प्रारंभिक तरीके का कोड उदाहरण
लोकल तरीके में एक POSIX shared memory ब्लॉक बनाया जाता है, /dev/shm को bind-mount किया जाता है, और ऐसा कंटेनर शुरू किया जाता है जो नाम से उसी shared memory को खोल सके:
from multiprocessing.shared_memory import SharedMemory
import docker
api = docker.from_env()
mem_label = "example_shm"
segment = SharedMemory(create=True, name=mem_label, size=int(1e6))
bind_map = docker.types.Mount(source="/dev/shm", target="/dev/shm", type="bind")
api.containers.run(
    image="alpine",
    name="sample_worker",
    detach=True,
    remove=True,
    command="tail -f /dev/null",
    mounts=[bind_map],
    environment={
        "SHM_NAME": mem_label
    }
)
CI में, pytest कंटेनर ID का पता लगाकर ipc_mode पर शिफ्ट होने का मन करता है:
from multiprocessing.shared_memory import SharedMemory
import docker
api = docker.from_env()
mem_label = "example_shm"
container_id = fetch_self_id()  # वर्तमान कंटेनर ID खोजने का कुछ तर्क
segment = SharedMemory(create=True, name=mem_label, size=int(1e6))
if container_id is None:
    mount_list = [docker.types.Mount(source="/dev/shm", target="/dev/shm", type="bind")]
    ipc_cfg = None
else:
    mount_list = []
    ipc_cfg = f"container:{container_id}"
api.containers.run(
    image="alpine",
    name="sample_worker",
    detach=True,
    remove=True,
    command="tail -f /dev/null",
    mounts=mount_list,
    ipc_mode=ipc_cfg,
    environment={
        "SHM_NAME": mem_label
    }
)
और एक वैरिएंट जो shareable के रूप में चिह्नित एक अतिरिक्त “anchor” कंटेनर स्पॉन करता है:
import docker
api = docker.from_env()
anchor_ref = api.containers.run(
    image="alpine",
    name="mem_anchor",
    detach=True,
    remove=True,
    command="tail -f /dev/null",
    ipc_mode="shareable",
)
ipc_cfg = f"container:{anchor_ref.id}"
ये विचार GitLab runner में परेशानी में पड़ते हैं, क्योंकि उपलब्ध पर्यावरण में मौजूदा कंटेनर ID की खोज अविश्वसनीय हो सकती है और, सबसे बढ़कर, POSIX shared memory के लिए ipc_mode वह नियंत्रण-प्लेन नहीं है जिसकी आपको जरूरत है।
असल में हो क्या रहा है
IPC namespaces System V IPC को नियंत्रित करते हैं, POSIX shared memory को नहीं। ipc_namespaces(7) में SysV ऑब्जेक्ट्स पर namespace के प्रभाव बताए गए हैं, और sysvipc(7) System V IPC की syscalls सूचीबद्ध करता है। Python का multiprocessing.shared_memory, SysV के बजाय shm_open के जरिए POSIX शैली की shared memory लागू करता है। यानी, ipc_mode टॉगल करने से POSIX segments कंटेनरों के पार दिखाई नहीं देंगे।
क्या दो प्रक्रियाएँ एक ही POSIX shared memory ब्लॉक से जुड़ सकती हैं, यह इस पर निर्भर करता है कि दोनों को /dev/shm के नीचे वही filesystem-backing दिखे ताकि shm_open कॉल एक ही ऑब्जेक्ट तक पहुँचे। इसे कंटेनरों के बीच व्यावहारिक रूप से हासिल करने का तरीका है कि हर सहभागी में /dev/shm पर एक Docker volume माउंट कर वही डायरेक्टरी साझा की जाए—IPC namespaces पर निर्भर रहने या होस्ट के /dev/shm को bind-mount करने के बजाय।
न्यूनतम, पुनरुत्पादित किया जा सकने वाला परीक्षण
निम्न सेटअप बिना --ipc के किसी उपयोग के दो कंटेनरों द्वारा एक POSIX shared memory क्षेत्र साझा करना दिखाता है। एक नामित Docker volume दोनों कंटेनरों में /dev/shm पर माउंट है, और SharedMemory का नाम दोनों ओर समान है।
Runner स्क्रिप्ट:
#!/usr/bin/env bash
set -euo pipefail
docker build . -t memx-test
docker run -i -e PYTHONUNBUFFERED=1 -v shared_mem:/dev/shm memx-test python3 /code/server.py &
docker run -i -e PYTHONUNBUFFERED=1 -v shared_mem:/dev/shm memx-test python3 /code/client.py &
wait
Dockerfile:
FROM python:3.13
RUN mkdir /code
COPY server.py /code
COPY client.py /code
सर्वर प्रक्रिया:
from multiprocessing.shared_memory import SharedMemory
import time
region = SharedMemory(create=True, name='foo2', size=int(1e6))
print("Created SHM")
time.sleep(1)
region.buf[0] = 10
print("wrote value")
time.sleep(1)
region.unlink()
print("Removed shm")
क्लाइंट प्रक्रिया:
from multiprocessing.shared_memory import SharedMemory
import time
time.sleep(0.5)
view = SharedMemory(create=False, name='foo2', size=int(1e6), track=False)
print("buf0", view.buf[0])
time.sleep(1)
print("buf0", view.buf[0])
इसे चलाने पर दिखाई देता है कि क्लाइंट buf[0] को 0 से 10 में अपडेट होते हुए देखता है, जिससे साबित होता है कि मेमोरी सच में साझा है। यह --ipc फ़्लैग के बिना भी काम करता है, और --ipc=private के साथ भी, जो इस परिदृश्य में POSIX shared memory के लिए ipc_mode की अप्रासंगिकता की पुष्टि करता है।
व्यावहारिक समाधान
होस्ट के /dev/shm को bind-mount करने की जगह, एक Docker volume बनाइए और उसे /dev/shm के रूप में pytest कंटेनर तथा परीक्षण के दौरान शुरू किए जाने वाले हर कंटेनर में माउंट कीजिए। जब तक दोनों पक्ष SharedMemory का वही नाम इस्तेमाल करते हैं और साझा वॉल्यूम के जरिए वही /dev/shm देखते हैं, वे उसी region से जुड़ेंगे।
यहाँ CI-संदर्भ का एक खास ध्यान देने योग्य बिंदु है। अगर pytest एक अलग Docker‑in‑Docker (DinD) daemon से बात करता है जबकि pytest खुद उसी DinD वातावरण में नहीं चल रहा, तो DinD daemon होस्ट Docker daemon के स्वामित्व वाले वॉल्यूम नहीं देख सकता। GitLab का दस्तावेज़ यह बँटवारा स्पष्ट करता है। सब कुछ एक ही Docker संदर्भ में रखने के लिए, pytest को भी Docker‑in‑Docker वातावरण के भीतर चलाएँ, और उस DinD daemon का docker.sock pytest कंटेनर में माउंट करें ताकि pytest उसी daemon में कंटेनरों को प्रबंधित कर सके जिसमें वॉल्यूम मौजूद है।
यह क्यों महत्वपूर्ण है
होस्ट के /dev/shm को bind करना नाज़ुक है। इससे पिछले रन का बचा हुआ state और समांतर जॉब्स के बीच क्रॉस‑टॉक का जोखिम रहता है। एक नामित Docker volume साफ‑सुथरा, पुनरुत्पादक रास्ता देता है, जिसे आप ठीक उन्हीं कंटेनरों में माउंट कर सकते हैं जिन्हें भाग लेना है, और यह उसी तरह मेल खाता है जैसे POSIX shared memory shm_open के जरिए आधारभूत ऑब्जेक्ट चुनती है। यह POSIX और System V IPC मॉडलों को मिलाने से भी बचाता है, जो namespaces के तहत अलग ढंग से व्यवहार करते हैं।
निष्कर्ष
कंटेनरों के पार shared memory की जरूरत वाली pytest‑आधारित इंटीग्रेशन टेस्टिंग के लिए, POSIX shared memory को फ़ाइल‑बैक्ड संसाधन की तरह मानें और Docker volume से सभी प्रतिभागियों में /dev/shm को एक ही डायरेक्टरी बनाइए। ipc_mode पर भरोसा न करें, क्योंकि वह System V IPC को लक्ष्य करता है और shm_open पर असर नहीं डालेगा। GitLab CI में सुनिश्चित करें कि pytest उसी Docker daemon के विरुद्ध चले जो टेस्ट कंटेनर चलाता है। जब ये शर्तें पूरी हों, तो Python का SharedMemory नाम और साझा /dev/shm भरोसेमंद cross‑container sharing के लिए पर्याप्त हैं।
यह लेख StackOverflow के एक प्रश्न (लेखक: user2416984) और Nick ODell के उत्तर पर आधारित है।