glibc বনাম musl: Linux binary কেন চলে না
`GLIBC_2.34 not found` মানে build host binary-র ন্যূনতম glibc version ঠিক করেছে। binary কী চায় বুঝে portable shipping-এর 4টি উপায় জানুন।
পুরোনো সার্ভারে Linux binary কেন চলবে না
একটি portable Linux binary তৈরি করা কঠিন, কারণ glibc, GNU C library, কেবল এক দিকের compatibility নিশ্চিত করে। পুরোনো binary নতুন glibc-তে চলতে থাকে। নতুন binary পুরোনো glibc-তে চলে না। যে মেশিনে compile করেন, সেটিই আপনার deployment-এর প্রতিটি মেশিনের জন্য ন্যূনতম compatibility স্তর নির্ধারণ করে।
Error message-এ প্রয়োজনীয় নির্দিষ্ট version উল্লেখ থাকে:
./mytool: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38' not found (required by ./mytool)কোনো কিছু corrupt হয়নি এবং সাধারণত configuration-এও কোনো সমস্যা থাকে না। একমাত্র ব্যতিক্রম হলো কোনো LD_LIBRARY_PATH setting loader-কে পুরোনো libc ব্যবহার করতে নির্দেশ দিলে। Message-এ থাকা path দেখায় loader আসলে কোন libc পড়েছে। Dynamic loader binary-র ভেতরে সংরক্ষিত version tag পড়ে, সেই libc-তে tag-টি খোঁজে, না পেয়ে program চালু করতে অস্বীকার করে। File আবার download করে chmod +x চালালেও কোনো পরিবর্তন হবে না, কারণ প্রয়োজনীয়তাটি file-টির মধ্যেই লেখা থাকে। আপনাকে হয় binary কীভাবে build করা হয় তা পরিবর্তন করতে হবে, নয়তো আপনি কী ship করেন তা পরিবর্তন করতে হবে।
glibc প্রতীক সংস্করণিং আসলে যা করে
glibc যে প্রতিটি function export করে, তার সঙ্গে একটি version tag থাকে। printf-এর ভেতরের libc.so.6 আসলে printf@@GLIBC_2.2.5। glibc কোনো function-এর আচরণ বা ABI (application binary interface) পরিবর্তন করলে পুরোনোটিকে প্রতিস্থাপন করে না। এটি পুরোনো code-কে পুরোনো tag-এর অধীনে রেখে নতুন code-কে নতুন tag-এর অধীনে যোগ করে। ফলে একটি libc.so.6-তে একই symbol-এর একাধিক version একসঙ্গে থাকতে পারে। 2009 সালের একটি binary তখনও 2009 সালের tag খুঁজে পায়। এ কারণেই forward compatibility এত ভালোভাবে কাজ করে।
Link ধাপে কোন version ব্যবহার করা হয়েছে, তা নথিভুক্ত হয়। আপনার binary-তে একটি .gnu.version_r section থাকে, যেখানে কার্যত লেখা থাকে, "আমার libc.so.6 থেকে GLIBC_2.38 প্রয়োজন।" পুরোনো libc-তে সেই tag কখনো ছিল না। তাই main চালানোর আগেই loader থেমে যায়। কোনো fallback নেই। কারণ fallback থাকলে program-কে যে function-এর বিরুদ্ধে compile করা হয়েছিল, তার বদলে নীরবে অন্য একটি function দেওয়া হতো।
2021 সালের পর সবচেয়ে সাধারণ trigger হলো glibc 2.34। এই release-এ libpthread এবং libdl-কে libc-এর সঙ্গে একীভূত করা হয় এবং __libc_start_main-কে, অর্থাৎ প্রতিটি C program চালু করা function-কে, GLIBC_2.34 tag-এ সরিয়ে নেওয়া হয়। তাই glibc 2.34 বা তার পরের version থাকা যেকোনো distribution-এ compile করা একটি hello-world program GLIBC_2.34 দাবি করে, যদিও source code-এ কোনো আধুনিক function call নেই। এ কারণেই বহু বছর নিজের code পরিবর্তন না করলেও মানুষের কাছে সমস্যাটি হঠাৎ দেখা দেয়।
আমার distro কোন glibc version release করে?
এই চার্টের পেছনের ডেটা
[
{
"distro": "CentOS 7",
"glibc": 2.17
},
{
"distro": "RHEL 8",
"glibc": 2.28
},
{
"distro": "Ubuntu 20.04",
"glibc": 2.31
},
{
"distro": "Debian 11",
"glibc": 2.31
},
{
"distro": "RHEL 9",
"glibc": 2.34
},
{
"distro": "Ubuntu 22.04",
"glibc": 2.35
},
{
"distro": "Debian 12",
"glibc": 2.36
},
{
"distro": "Ubuntu 24.04",
"glibc": 2.39
},
{
"distro": "Debian 13",
"glibc": 2.41
}
]এগুলো হলো প্রতিটি distribution-এর নিজস্ব repository-তে থাকা version, যা distribution-গুলো প্রকাশ করেছে এবং August 2026-এ বর্তমান ছিল। CentOS 7-এর end of life অনেক আগেই হয়ে গেছে। তবু উত্তরাধিকারসূত্রে পাওয়া server-গুলো এখনও এটি চালায় বলে এটি তালিকায় রাখা হয়েছে। আপনার কাছে থাকা যেকোনো box-এ ldd --version চালান। এটি প্রথম লাইনে glibc release দেখায়। আপনি getconf GNU_LIBC_VERSION-ও চালাতে পারেন।
এই 9টি row-কে একটি ধাপভিত্তিক তালিকা হিসেবে দেখুন। glibc 2.41 সহ Debian 13-এ build করলে ফলাফল Debian 13-এর চেয়ে পুরোনো কোনো system-এ চলবে না। glibc 2.31 সহ Ubuntu 20.04-এ build করলে একই source ওই line-এর উপরের প্রতিটি row-তে চলবে, Debian 13-এর box-সহ। আপনাকে যে সবচেয়ে পুরোনো server support করতে হবে, সেটিই একমাত্র গুরুত্বপূর্ণ build host। তাই আপনি যে distribution standardise করেন, সেটিই তার জন্য compile করা সবকিছুর ABI floor নির্ধারণ করে। fleet তৈরি হওয়ার আগেই এই সিদ্ধান্ত নেওয়া উচিত। VPS-এ কোন OS চালাবেন তা বেছে নেওয়ার মতো অন্যান্য trade-off-এর পাশাপাশি এটি বিবেচনা করুন: আপনার VPS-এ কোন OS চালাবেন তা বেছে নেওয়া।
একটি binary-এর জন্য প্রয়োজনীয় সর্বনিম্ন glibc সংস্করণ কীভাবে খুঁজে বের করব?
ফাইল থেকেই তথ্যটি পড়ুন। এটি যেকোনো কিছুর ক্ষেত্রে কাজ করে, এমনকি build note ছাড়া কোনো vendor-এর দেওয়া binary-এর ক্ষেত্রেও।
objdump -T ./mytool | grep -o 'GLIBC_[0-9.]*' | sort -Vu | tail -5শেষ লাইনটিই সর্বনিম্ন প্রয়োজনীয় সংস্করণ। objdump না থাকলে binutils package install করুন। ওই সিস্টেমে কিছু install করতে না পারলে strings -a ./mytool | grep -o 'GLIBC_[0-9.]*' | sort -Vu | tail -5 ব্যবহার করলেও কাছাকাছি ফল পাবেন, কারণ tag-গুলো ফাইলের ভেতরে plain string হিসেবে সংরক্ষিত থাকে।
readelf -V ./mytool একই requirement কাঠামোবদ্ধভাবে দেখায়। Version needs section '.gnu.version_r' শিরোনামযুক্ত block খুঁজুন। সেখানে প্রতিটি tag-এর জন্য একটি করে Name: GLIBC_x.y line থাকে। এগুলো সেই library-এর অধীনে group করা থাকে, যেটি tag-গুলো সরবরাহ করবে।
কোন function set সর্বনিম্ন সংস্করণ নির্ধারণ করেছে তা জানতে tag-টি grep করুন: objdump -T ./mytool | grep GLIBC_2.38। এটি প্রায়ই একটি মাত্র symbol হয়, এবং কখনও এমন symbol-ও হতে পারে যা এড়িয়ে চলা সম্ভব। objdump -T একেবারেই কিছু print না করলে binary-টি statically linked। তাই এতে dynamic symbol table নেই এবং print করার মতো কোনো version requirement-ও নেই।
কেউ আপনাকে দেওয়া কোনো binary সম্পর্কে file এবং readelf -d কী জানায়
file এক লাইনে architecture এবং link type জানায়।
file ./mytoolএকটি সাধারণ glibc build-এর output এমন হয়:
ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=..., for GNU/Linux 3.2.0, not strippedStatic build-এ statically linked দেখা যায় এবং কোনো interpreter-এর নাম থাকে না। musl build-এ অন্য একটি interpreter-এর নাম থাকে:
ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-x86_64.so.1, ...Interpreter-ই গুরুত্বপূর্ণ field। এটি সেই dynamic loader, যেটি আপনার program চালানোর আগে kernel চালায়। এই exact path-টি target machine-এ অবশ্যই থাকতে হবে। কেউ সেখানে musl install না করলে Debian বা Ubuntu server-এ /lib/ld-musl-x86_64.so.1 থাকে না।
readelf -d ./mytool binary-টির প্রয়োজনীয় shared library-গুলো নাম অনুযায়ী তালিকাভুক্ত করে:
Dynamic section at offset 0x2d58 contains 27 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libssl.so.3]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000001d (RUNPATH) Library runpath: [$ORIGIN/../lib]প্রতিটি NEEDED entry একটি hard requirement এবং soname দিয়ে মিলানো হয়। libssl.so.3 হলো OpenSSL 3। তাই যে server-এ শুধু libssl.so.1.1 আছে, সেখানে binary-টি load হবে না। এর কারণ soname: ভিন্ন soname ইচ্ছাকৃতভাবে incompatible ABI নির্দেশ করে। RUNPATH এমন directory যোগ করে, যেখানে loader binary-টির সরাসরি dependency-গুলো খোঁজে। এটি যেকোনো LD_LIBRARY_PATH setting-এর পরে, কিন্তু system library cache এবং default path-এর আগে প্রয়োগ হয়। $ORIGIN binary-টি থাকা directory-তে expand হয়। এভাবেই একটি self-contained bundle নিজের library খুঁজে পায়।
যে binary-কে আপনি বিশ্বাস করেন না, সেটিতে ldd চালাবেন না। glibc-তে ldd loader-এর অধীনে program চালিয়ে dependency resolve করতে পারে। তাই hostile file code execute করার সুযোগ পায়। readelf এবং objdump শুধু byte পড়ে। এর আগে নিশ্চিত করুন যে download-টি project প্রকৃতপক্ষে প্রকাশ করা file-ই। কারণ প্রকাশিত checksum-এর সঙ্গে download মিলিয়ে দেখা-ই একমাত্র ধাপ, যা বলে দেয় আপনি কার byte হাতে রেখেছেন।
আপনি বাস্তবে যে error দেখতে পাবেন এবং প্রতিটির অর্থ
loader এমন একটি GLIBC version উল্লেখ করছে যা খুঁজে পাওয়া যাচ্ছে না। binary-টি target system-এ থাকা glibc-এর চেয়ে নতুন glibc-এর বিরুদ্ধে compile করা হয়েছে। target system-এ কিছু install করলেই এটি নিরাপদে ঠিক হবে না। নিচের চারটি option-এর একটি বেছে নিন।
আপনি স্পষ্টভাবে দেখতে পাচ্ছেন এমন একটি file-এর জন্য shell No such file or directory দেখাচ্ছে। অনুপস্থিত জিনিসটি binary নয়, interpreter। ELF header-এ loader path না থাকলে execve, ENOENT ফেরত দেয় এবং shell তার কাছে থাকা একমাত্র message দেখায়। file চালিয়ে interpreter path দেখুন। glibc-নির্ভর server-এ musl-linked binary চালালে ঠিক এই লক্ষণ দেখা যায়।
cannot execute binary file: Exec format error সাধারণত architecture ভুল বোঝায়: arm64 server-এ x86-64 binary, অথবা এর বিপরীত। file-টি আদৌ valid executable না হলেও এমন হতে পারে, যেমন download অসম্পূর্ণ বা damaged হলে। file output-এর প্রথম line-এ কোন architecture-এর file আপনার কাছে আছে, অথবা file-টি ব্যবহারযোগ্য ELF executable নয়—তা দেখা যাবে।
error while loading shared libraries: libssl.so.3: cannot open shared object file বোঝায় একটি NEEDED library অনুপস্থিত, অথবা ভিন্ন soname-এ উপস্থিত। matching distribution package install করুন। distribution family ভেদে package name আলাদা হয়। তাই README থেকে কোনো apt line copy করার আগে তা রূপান্তর করুন। dnf ও apt command-এর সমতুল্য রূপ সেই mapping দেখায়।
glibc-এর অধীনে ঠিকমতো চলা musl build থেকে পাওয়া শুধু Segmentation fault। সাধারণত কারণটি thread stack size, যা option 2-এ ব্যাখ্যা করা হয়েছে।
একটি বহনযোগ্য Linux binary বিতরণের চারটি উপায়
প্রতিটি পদ্ধতির জন্য আপনাকে বাস্তব কোনো মূল্য দিতে হবে। আপনার program run time-এ কী করে, তার ভিত্তিতে পদ্ধতি বেছে নিন; কোনটি সবচেয়ে পরিষ্কার শোনায়, তার ভিত্তিতে নয়।
Option 1: আপনি যে পুরোনোতম distro সমর্থন করেন, সেটির ওপর build করুন
সবচেয়ে নিরস উত্তর, এবং সাধারণত সঠিকটিও এটিই। আপনি যে পুরোনোতম distribution সমর্থনের প্রতিশ্রুতি দেন, সেই distribution-এর container image-এর ভেতরে compile করুন। Linker কেবল সেই tag-গুলোই record করতে পারে, যেগুলো পুরোনো glibc-তে বাস্তবে আছে। তাই binary-তে ওই release-ই সর্বনিম্ন compatibility floor হিসেবে থাকে, অথচ binary স্বাভাবিক dynamic binary হিসেবেই থাকে এবং glibc-এর সম্পূর্ণ আচরণ অক্ষুণ্ণ থাকে।
docker run --rm -v "$PWD:/src" -w /src ubuntu:20.04 sh -c 'apt-get update && apt-get install -y build-essential && make'Output-টি glibc 2.31 এবং তার পরের সব version-এ চলে। অনুমান না করে যাচাই করুন: আগে দেওয়া objdump -T one-liner-টি result-এর বিরুদ্ধে চালান এবং দেখুন সর্বোচ্চ tag-টি আপনার প্রত্যাশিত tag কি না।
এর বিনিময়ে toolchain পুরোনো হয়ে যায়। পুরোনো base image-এর সঙ্গে পুরোনো compiler-ও থাকে। আপনার code-এর সাম্প্রতিক C++ standard প্রয়োজন হলে এটি সমস্যা তৈরি করে। Go এবং Rust সাধারণত এই সমস্যা এড়ায়, কারণ তাদের toolchain distribution package-এর বাইরে আলাদাভাবে container-এ install হয়। C এবং C++-এর জন্য distribution-এর নিজস্ব toolchain channel থেকে নতুন compiler যোগ করতে পারেন। অথবা manylinux image ব্যবহার করতে পারেন। এগুলো এমনভাবে তৈরি যে পুরোনো glibc-এর সঙ্গে বর্তমান GCC একত্রে ব্যবহার করা যায়। Zig-এর bundled C compiler-ও সরাসরি নির্দিষ্ট glibc target করতে পারে, যেমন zig cc -target x86_64-linux-gnu.2.28-এ দেখানো হয়েছে। এতে পুরোনো image সংরক্ষণ না করেও একই compatibility floor পাওয়া যায়।
Option 2: musl-এর বিরুদ্ধে statically link করুন
musl হলো static linking মাথায় রেখে লেখা একটি ছোট C library। একটি static musl binary-তে নিজস্ব libc থাকে, কোনো interpreter-এর নাম থাকে না, এবং সঠিক architecture-এর যেকোনো Linux kernel-এ চলে। অধিকাংশ single-file tool download এভাবেই তৈরি করা হয়।
sudo apt install -y musl-tools
musl-gcc -static -O2 hello.c -o hello
file ./hellofile-এ এখন statically linked দেখানোর কথা; সেখানে কোনো interpreter field থাকবে না। Rust-এর জন্য target যোগ করে সেটির বিরুদ্ধে build করুন:
rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-muslGo-এর ক্ষেত্রে এর কোনোটিই প্রয়োজন নেই। CGO_ENABLED=0 go build ব্যবহার করলে ফলাফল ইতিমধ্যে static থাকে এবং কোনো libc-ই link করে না।
NSS lookup-এর আচরণ বদলে যায়। glibc NSS (name service switch)-এর মাধ্যমে user এবং host name resolve করে। প্রোগ্রাম চলার সময় এটি libnss_*-এর module dlopen দিয়ে load করে। Staticভাবে link করা glibc এটি করতে পারে না। Linker নিম্নলিখিত বার্তা দিয়ে সতর্ক করে:
warning: Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linkingmusl NSS বাস্তবায়ন না করায় এই সতর্কতা এড়িয়ে যায়। এর নিজস্ব resolver আছে এবং এটি সরাসরি /etc/resolv.conf ও /etc/hosts পড়ে। সাধারণ VPS-এ এটি যথেষ্ট এবং আরও সরল। যে host-এ account বা name LDAP অথবা SSSD থেকে আসে, সেখানে আপনার binary সেগুলো দেখতে পাবে না, যদিও ওই host-এর অন্য সব program দেখতে পাবে। musl-এর resolver glibc-এর resolver-এর তুলনায়ও অপেক্ষাকৃত নতুন। UDP response-এ ধারণ করার জন্য DNS reply অতিরিক্ত বড় হলে TCP fallback musl 1.2.4-এ 2023 সালে যোগ হয়েছে। পুরোনো musl-এর বিরুদ্ধে তৈরি build বড় DNS record query করতে পারে না। Truncated UDP reply-তে কোনো আংশিক ফলাফল না দেওয়া recursive nameserver-এর বিরুদ্ধে তাদের lookup ব্যর্থ হতে পারে।
dlopen কাজ করে না। Static musl binary-তে dlopen এমন একটি stub, যা সবসময় ব্যর্থ হয়। Runtime-এ code load করে এমন সবকিছুই বাদ পড়ে: plugin system, PAM module, GPU driver এবং glibc-এর নিজস্ব iconv character-set module। আপনার program-এর dlopen প্রয়োজন হলে static linking ব্যবহার করা যাবে না। তখন অন্য তিনটি বিকল্পের একটি নিতে হবে।
Security update-এর দায়িত্ব আপনার। Dynamically linked binary server-এর package upgrade চলার সঙ্গে সঙ্গে libc-এর fix পেয়ে যায়। Static binary কখনো তা পায় না। আপনার libc-এর বিরুদ্ধে, অথবা bundle করা static OpenSSL-এর বিরুদ্ধে কোনো CVE (common vulnerabilities and exposures) entry প্রকাশিত হলে আপনাকে rebuild করে আবার distribute করতে হবে। ইতিমধ্যে deploy করা প্রতিটি copy কেউ file প্রতিস্থাপন না করা পর্যন্ত vulnerable থাকবে। কোন কোন library link করেছেন তার record রাখুন। কারণ target system-এর কোনো কিছু administrator-কে জানাতে পারবে না যে আপনার single-file binary-তে দুই বছর আগের library রয়েছে।
Licence-ও বদলে যায়। glibc হলো LGPL। Static linking করলে LGPL-এর relinking obligation প্রযোজ্য হয়। অর্থাৎ recipient-কে এমন উপকরণ দিতে হবে, যাতে তারা আপনার program-কে ভিন্ন glibc-এর বিরুদ্ধে আবার link করতে পারে। musl হলো MIT এবং এতে এমন কোনো শর্ত নেই। Static glibc-এর বদলে single-file binary প্রকাশকারী project-গুলো musl বেছে নেওয়ার প্রধান কারণ এটিই।
দুটি runtime difference বিভ্রান্তিকর crash ঘটাতে পারে। musl-এর default thread stack হলো 128 KiB। glibc program শুরু হওয়ার সময়কার stack limit থেকে default নির্ধারণ করে। সাধারণত এটি 2 থেকে 10 MB-এর মধ্যে থাকে। সেই limit unlimited হলে অধিকাংশ architecture-এ glibc 2 MB ব্যবহার করে। ফলে thread stack-এ বড় buffer রাখে এমন code musl-এর অধীনে কোনো message বা log line ছাড়াই segfault করতে পারে। pthread_attr_setstacksize দিয়ে size নির্দিষ্ট করুন, অথবা buffer-টি heap-এ সরান। musl-এর allocator একসঙ্গে অনেক thread-এর allocation-এর বদলে ছোট size এবং পূর্বানুমেয় আচরণের জন্য তৈরি। তাই allocation-heavy threaded program উল্লেখযোগ্যভাবে ধীর চলতে পারে। কোনো library-এর সুনাম দেখে নির্ভর না করে নিজের workload benchmark করুন।
বিকল্প 3: loader এবং library একসঙ্গে bundle করা
matching loader-সহ binary-এর পাশে library-গুলো রাখুন। এরপর সেই loader-এর মাধ্যমে program চালু করুন।
./ld-linux-x86-64.so.2 --library-path ./lib ./mytoolএটি স্থায়ী করতে patchelf ব্যবহার করে file-এ path-গুলো লিখুন:
patchelf --set-interpreter "$PWD/lib/ld-linux-x86-64.so.2" --set-rpath '$ORIGIN/lib' ./mytoolএটি কাজ করবে কি না, তা একটি নিয়ম নির্ধারণ করে: loader এবং libc.so.6 একই glibc build থেকে আসতে হবে। এগুলো একটি matching pair। host-এর loader-এর সঙ্গে bundle করা libc মিশিয়ে ব্যবহার করলে পড়ার মতো error না দেখিয়ে start-up-এর সময় crash হয়। দুটিই bundle করুন, অথবা কোনোটিই bundle করবেন না।
AppImage হলো এই pattern-এর packaged রূপ। এতে payload একটি squashfs image-এ থাকে এবং একটি ছোট runtime সেটি mount করে। এখানে একটি বিষয় প্রায়ই বাদ পড়ে: অধিকাংশ AppImage glibc bundle না করে host-এর glibc ব্যবহার করে। তাই বর্তমান distribution-এ এভাবে build করা AppImage পুরোনো server-এ একই version error-এ ব্যর্থ হয়। AppImage-এর best-practice নির্দেশনা হলো, আপনি যে system-গুলো support করবেন তার মধ্যে সবচেয়ে পুরোনোটির চেয়ে নতুন base-এ build না করা। ফলে এমন AppImage option 1-এর ওপর তৈরি একটি delivery format, এর বিকল্প নয়। glibc bundle করাও সম্ভব। appimage-builder এবং go-appimage উভয়ই AppImage-এর ভেতরে glibc ship করতে পারে। এভাবে build করা AppImage build system-এর চেয়ে পুরোনো এবং নতুন—উভয় ধরনের distribution-এ চলতে পারে। এর বিনিময়ে file বড় হয় এবং libssl-এর মতো library bundle-এর ভেতরে নির্দিষ্ট version-এ স্থির থাকে।
একটি headless server-এ AppImage payload mount করার জন্য FUSE (filesystem in userspace) প্রয়োজন। minimal VPS image-এ এটি প্রায়ই থাকে না। পুরোনো runtime দিয়ে build করা AppImage-এর জন্য host-এ FUSE 2 library প্রয়োজন। Debian এবং Ubuntu-তে এটি libfuse2 package হিসেবে থাকে, আর Ubuntu 24.04-এ এর নাম libfuse2t64। এটি না থাকলে AppImage AppImages require FUSE to run. দিয়ে বন্ধ হয়ে যায়। নতুন AppImage-এ FUSE 3 support built in থাকে। তাই host-এ আর libfuse install করার প্রয়োজন হয় না। ./App.AppImage --appimage-extract-and-run ব্যবহার করে mount সম্পূর্ণ এড়িয়ে যেতে পারেন। এটি একটি temporary directory-তে unpack করে এবং সেখান থেকেই execute করে।
বিকল্প 4: একটি container image ব্যবহার করুন
প্রোগ্রামের সঙ্গে সম্পূর্ণ userland স্থানান্তর করুন। image-এ নিজস্ব libc থাকে, তাই host-এর glibc আর গুরুত্বপূর্ণ থাকে না; শুধু kernel এবং architecture-এর সামঞ্জস্য থাকতে হয়। এটি সবচেয়ে সরল বিকল্প এবং সমস্যার এই পুরো শ্রেণিটিই দূর করে। তাই অনেক server software এইভাবে বিতরণ করা হয়। এটি ব্যবহার করার প্রচলিত উপায় হলো VPS-এ Docker চালানো।
Host kernel এখনও একটি সীমা নির্ধারণ করে। নতুন glibc নতুন system call ব্যবহার করে, আর পুরোনো container host সেগুলো আটকে দিতে পারে: glibc 2.34 এবং পরবর্তী সংস্করণ clone3 ব্যবহার করে, যা পুরোনো default seccomp profile প্রত্যাখ্যান করে। এর লক্ষণ হলো Operation not permitted-সহ তাৎক্ষণিক ব্যর্থতা, কিন্তু কোথাও glibc-এর উল্লেখ থাকে না। Host-এ container runtime upgrade করলে সমস্যা সমাধান হয়, কারণ বাধাটি kernel-এ নয়, runtime-এর syscall filter-এ থাকে।
খরচগুলো সাধারণ। Target system-এ একটি container runtime এবং সেটি ব্যবহারের permission দরকার। Download-এর আকার একটি file থেকে বেড়ে দশ বা শত শত megabyte হতে পারে। এখন একটি base image এবং তার patch schedule-এর দায়িত্বও আপনার। ফলে option 2-এ যে security কাজ এড়ানো হয়েছিল, তা image rebuild হিসেবে আবার ফিরে আসে। দীর্ঘ সময় চলা service-এর ক্ষেত্রে এটি যুক্তিসঙ্গত বিনিময়। কেউ একবার চালাবে এমন command-line tool-এর ক্ষেত্রে নয়।
কোন বিকল্পটি বেছে নেওয়া উচিত?
- আপনার নিয়ন্ত্রণাধীন সার্ভারগুলোর জন্য একটি অভ্যন্তরীণ tool, যেগুলো একই distribution চালায়: সেই distribution-এ dynamically build করুন এবং সেখানেই থামুন।
- অপরিচিত ব্যবহারকারীরা download করে চালাবে এমন একটি single file: static musl ব্যবহার করুন, যদি programটির
dlopenপ্রয়োজন না থাকে এবং NSS-backed lookup না করে। - plugins বা GPU access-সহ একটি program: dynamic অবস্থায় থাকুন, পুরোনো base-এ build করুন এবং এটি সরবরাহ করতে option 3 ব্যবহার করুন।
- ইতিমধ্যে runtime থাকা একটি machine-এ দীর্ঘ সময় চলা service: image ship করুন।
আপনি যে বিকল্পই বেছে নিন, তা লিখে রাখুন এবং যাচাই করুন। Build host-এর glibc এখন আপনার release process-এর অংশ। একটি build machine-কে এক LTS release থেকে পরবর্তীটিতে upgrade করলে ন্যূনতম সামঞ্জস্যের স্তর নীরবে বেড়ে যায়। এতে গত মাসে ঠিকমতো চলা ব্যবহারকারীদের জন্য সমস্যা তৈরি হতে পারে। Build image-কে tag অনুযায়ী pin করুন এবং output-এ সর্বোচ্চ GLIBC_ tag আছে কি না build step হিসেবে assert করুন। তাহলে সমস্যাটি অন্য কারও terminal-এ দেখা দেওয়ার পরিবর্তে আপনার pipeline-এই check ব্যর্থ হবে।
FAQ
আমার সার্ভারে "version GLIBC_2.38 not found" দেখায় কেন?
আপনার সার্ভারে থাকা glibc-এর চেয়ে নতুন glibc সংস্করণযুক্ত একটি মেশিনে binary-টি compile করা হয়েছিল। glibc-এর symbol versioning শুধু forward compatibility দেয়: নতুন glibc-তে পুরোনো binary চলে, কিন্তু পুরোনো glibc-তে নতুন binary চলে না। কারণ loader-এর file-এ সংরক্ষিত সঠিক version tag প্রয়োজন, আর পুরোনো libc-তে সেই tag কখনো ছিল না। সার্ভারে কিছু install করে এটি নিরাপদভাবে ঠিক করা যায় না। পুরোনো base ব্যবহার করে rebuild করুন, static musl build সরবরাহ করুন, matching loader-সহ library bundle করুন, অথবা container image সরবরাহ করুন।
একটি binary-এর কোন glibc version প্রয়োজন, তা কীভাবে জানব?
objdump -T ./mytool | grep -o 'GLIBC_[0-9.]*' | sort -Vu | tail -5 ব্যবহার করে file থেকে version tag পড়ুন। শেষ line-এ binary-টি load করতে সক্ষম সর্বনিম্ন glibc version থাকে। readelf -V ./mytool একই requirement .gnu.version_r section-এ দেখায় এবং কোন library সেগুলো সরবরাহ করবে অনুযায়ী group করে। objdump -T কোনো output না দিলে binary-টি static এবং এর glibc requirement নেই। ফলাফলটি ldd --version থেকে পাওয়া সার্ভারের নিজস্ব version-এর সঙ্গে তুলনা করুন।
একটি musl binary কি glibc binary-এর চেয়ে ধীর?
এটি workload-এর ওপর নির্ভর করে। নির্ভরযোগ্য উত্তর পেতে measurement করতে হবে। Static musl binary দ্রুত start হয়, কারণ চালানোর মতো loader থাকে না এবং exec time-এ relocation-এর কাজও হয় না। তবে musl-এর allocator একই সময়ে অনেক thread allocation করার বদলে ছোট size এবং predictable behaviour-এর জন্য তৈরি। glibc-এর তুলনায় musl-এ hand-optimised string ও memory routine-ও কম থাকে। তাই allocation-heavy বা string-heavy threaded program লক্ষণীয়ভাবে ধীর হতে পারে। কোনো দাবি গ্রহণ করার আগে নিজের সার্ভারে নিজের program benchmark করুন।
কোনো file উপস্থিত থাকা সত্ত্বেও bash কেন "No such file or directory" বলে?
অনুপস্থিত file-টি আপনার binary নয়, dynamic loader। Kernel ELF header থেকে interpreter path পড়ে। সেই path অনুপস্থিত হলে kernel ENOENT ফেরত দেয়, আর shell-এর কাছে থাকা একমাত্র message দিয়ে তা দেখায়। file ./mytool চালিয়ে interpreter field পড়ুন। Debian বা Ubuntu সার্ভারে সেখানে /lib/ld-musl-x86_64.so.1 লেখা থাকলে, আপনার binary-টি glibc system-এ musl দিয়ে linked। Distribution-এর musl runtime install করুন; Debian ও Ubuntu-তে এটি musl package। অথবা programটির glibc build কিংবা static build সংগ্রহ করুন।
এটি ঠিক করতে কি নতুন সার্ভার থেকে libc.so.6 copy করতে পারি?
না। libc.so.6 এবং ld-linux-x86-64.so.2 একই glibc build-এর matched pair। মেশিনের প্রতিটি dynamically linked glibc program এগুলো ব্যবহার করে। তাই system copy overwrite করলে সার্ভার প্রায় কোনো program-ই চালাতে নাও পারে। যেসব tool দিয়ে পরিবর্তনটি ফিরিয়ে আনবেন, সেগুলোও চলবে না। পুরোনো host-এ একটি নতুন binary চালাতেই হলে নতুন glibc-টি private directory-তে unpack করুন। এরপর --library-path ব্যবহার করে সেই program-কে তার নিজস্ব loader-এর মাধ্যমে start করুন। এতে শুধু ওই process প্রভাবিত হবে। পুরোনো glibc-এর বিরুদ্ধে rebuild করাই এখনও এমন সমাধান, যা 6 মাস পর অপ্রত্যাশিত সমস্যায় ফেলবে না।