Docker-এ ডেটাবেস চালানো কি নিরাপদ? জেনে নিন বিস্তারিত
Docker-এ PostgreSQL, MySQL বা MongoDB চালানো প্রোডাকশনে সম্পূর্ণ নিরাপদ। তবে ভলিউম ম্যানেজমেন্ট, ডেটা ব্যাকআপ, ভার্সন আপগ্রেড এবং মেমোরি লিমিট সঠিকভাবে কনফিগার করা জরুরি।
ডেটাবেস কি Docker-এ চলবে নাকি সরাসরি host-এ?
ডেটাবেস Docker-এ চালান। একটি VPS-এ একটি অ্যাপ্লিকেশন স্ট্যাকের জন্য কন্টেইনারাইজড PostgreSQL, MySQL, MongoDB বা Redis ব্যবহার করা একটি সাধারণ প্রোডাকশন সিদ্ধান্ত। এটি নিয়ে সাধারণত যে বিতর্ক হয়, তা ভুল। কন্টেইনার হলো Linux-এর একটি প্রসেস, যার চারপাশে namespaces এবং cgroups থাকে; এটি কোনো virtual machine নয়। তাই ডেটাবেস এবং ডিস্কের মাঝে কোনো hypervisor থাকে না। bind mount বা local named volume ব্যবহার করলে read এবং write সরাসরি host filesystem-এই হয়, যা প্যাকেজ ইন্সটলেশনের ক্ষেত্রেও একই।
এর আসল খরচ হলো অপারেশনাল। এই সেটআপটি সঠিক হবে নাকি বিপর্যয় ডেকে আনবে, তা চারটি বিষয়ের ওপর নির্ভর করে: ডেটা কোথায় থাকে, সেই ডিরেক্টরির মালিক কে, major version আপগ্রেড প্রক্রিয়া কেমন, এবং আপনি কখনো ব্যাকআপ রিস্টোর করেছেন কি না। এই বিষয়গুলো ঠিক থাকলে কন্টেইনার কেবল একটি প্রযুক্তিগত খুঁটিনাটি মাত্র। আর এগুলো ভুল হলে কন্টেইনারই হবে আপনার সব সমস্যার কারণ।
যেকোনো সার্ভার ডেটাবেসের ক্ষেত্রে সিদ্ধান্তটি একই। নিচের উদাহরণগুলোতে PostgreSQL, MySQL, MongoDB এবং Redis ব্যবহার করা হয়েছে এবং যেখানে প্রয়োজন সেখানে প্রোডাক্ট-নির্দিষ্ট পার্থক্যগুলো উল্লেখ করা হয়েছে।
একটি কন্টেইনার আসলে যা পরিবর্তন করে
স্টোরেজ পাথ পরিবর্তন হয় না, যদি আপনি একটি মাউন্ট পয়েন্ট ব্যবহার করেন। একই কার্নেল, একই পেজ ক্যাশ এবং একই ফাইলসিস্টেম ব্যবহৃত হয়।
পারফরম্যান্সের ক্ষেত্রে একটি বড় সমস্যা হলো যখন আপনি কিছুই মাউন্ট করেন না। ভলিউম ছাড়া, ডেটা ডিরেক্টরি কন্টেইনারের রাইটেবল লেয়ারে চলে যায়, যা ইমেজের ওপর একটি ওভারলে ফাইলসিস্টেম হিসেবে থাকে। সেখানে রাইট অপারেশন ধীরগতির হয় এবং কন্টেইনার মুছে ফেললে পুরো লেয়ারটিও মুছে যায়। "আজ সকালে আমার ডেটাবেস খালি ছিল" এই সমস্যার মূল কারণ এটিই।
যা প্রকৃতপক্ষে পরিবর্তিত হয়:
- লাইফসাইকেল।
docker compose downকন্টেইনারটিকে ধ্বংস করে দেয়। ভলিউমে নেই এমন যেকোনো কিছু এর সাথেই মুছে যায়। - ভার্সন। ইমেজ ট্যাগই হলো ভার্সন। ডেটাবেস কন্টেইনারের ভেতরে এমন কোনো
apt upgradeনেই যা পরবর্তীdocker compose pull-এর পরেও টিকে থাকে। - মেমোরি অ্যাকাউন্টিং। cgroup লিমিট হলো কার্নেল দ্বারা কার্যকর করা একটি কঠোর সীমা, এবং ডেটাবেস জানে না যে এটি সেখানে বিদ্যমান।
- ইউজার। প্রসেসটি কন্টেইনারের ভেতরে একটি নিউমেরিক ইউজার আইডি হিসেবে চলে, যার আপনার হোস্ট মেশিনে কোনো কিছুর ওপর মালিকানা নাও থাকতে পারে।
ডেটা কোথায় থাকবে তা সবকিছু নির্ধারণ করে
এখানে দুটি ভালো বিকল্প এবং একটি সাধারণ ভুল রয়েছে।
- একটি named volume:
pgdata:/var/lib/postgresql/data। Docker/var/lib/docker/volumes/<project>_pgdata/_data-এ ডিরেক্টরি তৈরি করে এবং প্রথমবার চালানোর সময় image entrypoint এর মালিকানা নির্ধারণ করে। এটিই ডিফল্ট সমাধান। - একটি bind mount:
/srv/appname/pg:/var/lib/postgresql/data। আপনি পাথ নির্বাচন করেন, তাই অনুমতির (permissions) সমস্যাটিও আপনাকেই সমাধান করতে হবে। - কোনো mount না রাখা। উপরের অংশটি দেখুন। ডেটা কন্টেইনারের ভেতরেই থাকে।
এর সুবিধা-অসুবিধাগুলো একটি বড় বিষয়, এবং bind mounts বনাম named volumes-এ তা বিস্তারিত আলোচনা করা হয়েছে। ডেটাবেসের ক্ষেত্রে সংক্ষেপে বলতে গেলে: যদি host path জানার কোনো সুনির্দিষ্ট কারণ না থাকে, তবে named volume ব্যবহার করুন। আর যদি bind mount ব্যবহার করতেই হয়, তবে তা /srv/appname/pg-এর মতো কোনো স্থিতিশীল জায়গায় রাখুন, প্রজেক্ট ডিরেক্টরির ভেতরে নয় যেখানে git clean-এর অ্যাক্সেস থাকতে পারে।
একটি কঠোর সীমাবদ্ধতা: ডেটাবেসের ডেটা ডিরেক্টরি কখনোই NFS (network file system) বা এমন কোনো নেটওয়ার্ক মাউন্টে রাখবেন না যার লকিং এবং fsync আচরণ আপনি পরীক্ষা করেননি। ডেটাবেস ধরে নেয় যে একটি সফল fsync মানেই হলো বাইটগুলো স্থিতিশীল স্টোরেজে সংরক্ষিত হয়েছে। যখন এই ধারণাটি ভুল প্রমাণিত হয়, তখন কয়েক সপ্তাহ পর ডেটা করাপশন দেখা দিতে পারে।
ভলিউম হারিয়ে যাওয়ার আগেই এর নাম নির্ধারণ করুন
Compose ভলিউমের নাম দেয় <project>_<volume> হিসেবে, এবং প্রজেক্টের নাম ডিফল্টভাবে ডিরেক্টরির নাম থেকে নেওয়া হয়। তাই ভলিউমের পরিচয় ডিরেক্টরির নামের ওপর নির্ভর করে, যা মানুষ প্রায়ই না ভেবেই পরিবর্তন করে ফেলে।
আপনি যদি /srv/app-কে /srv/app-old-এ সরিয়ে নেন, অথবা compose ফাইলে pgdata কি (key) রিনেম করেন, তবে পরবর্তী docker compose up -d কমান্ডটি একটি নতুন খালি ভলিউম তৈরি করবে। Postgres এতে একটি নতুন ক্লাস্টার ইনিশিয়ালাইজ করবে। কন্টেইনারটি হেলদি দেখাবে, অ্যাপ্লিকেশন চালু হবে, কিন্তু সব টেবিল মুছে যাবে। ভালো খবর হলো, পুরনো ভলিউমটি আগের নামেই ডিস্কে রয়ে গেছে।
docker volume ls
docker volume inspect app_pgdataএই সমস্যা এড়াতে ভলিউমের নাম নির্দিষ্ট করে দিন। প্রজেক্টের নাম এবং ভলিউমের নাম স্পষ্টভাবে সেট করুন:
name: myapp
services:
db:
image: postgres:17
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
name: myapp_pgdataযদি কোনো অনাথ (stray) ভলিউমে আপনার ডেটা থেকে থাকে, তবে ডেটাবেস বন্ধ করে সেটি কপি করে নিন:
docker compose stop db
docker run --rm -v app_pgdata:/from -v myapp_pgdata:/to alpine sh -c 'cp -a /from/. /to/'
docker compose start dbডেটাবেস চালু থাকা অবস্থায় কপি করলে ফাইলগুলো অসম্পূর্ণ (torn) অবস্থায় কপি হতে পারে, কারণ তখন সেগুলোতে রাইট অপারেশন চলছিল। তাই আগে ডেটাবেস বন্ধ করুন।
ডেটা ডিরেক্টরির মালিক কে
অফিসিয়াল Postgres, MySQL এবং MongoDB ইমেজগুলো তাদের সার্ভারকে একটি unprivileged user id-তে চালায়, যা সাধারণত 999। কন্টেইনারটি যখন root হিসেবে শুরু হয়, তখন entrypoint ডেটা ডিরেক্টরির মালিকানা সেই ইউজারের কাছে হস্তান্তর করে এবং তারপর প্রিভিলেজ ত্যাগ করে। এই কারণেই একটি খালি bind mount সাধারণত প্রথমবারই কাজ করে।
আপনি যখন compose ফাইলে user: সেট করেন, তখনই এটি কাজ করা বন্ধ করে দেয়, কারণ তখন entrypoint-এর কাছে কোনো কিছু ঠিক করার মতো আর কোনো প্রিভিলেজ থাকে না। Postgres সরাসরি এটিই বলে:
initdb: error: could not change permissions of directory "/var/lib/postgresql/data": Operation not permittedভুল মোডসহ একটি ডেটা ডিরেক্টরি থাকলে ভিন্ন একটি বার্তা দেখা যায়, এবং এটি চিনে রাখা জরুরি কারণ এর সমাধান হলো chmod, chown নয়:
FATAL: data directory "/var/lib/postgresql/data" has invalid permissions
DETAIL: Permissions should be u=rwx (0700) or u=rwx,g=rx (0750).root-এর মালিকানাধীন bind mount-এ MongoDB লক ফাইলের কারণে ব্যর্থ হয়:
Unable to create/open the lock file: /data/db/mongod.lock (Permission denied). Ensure the user executing mongod is the owner of the lock file and has the appropriate permissions.এর সমাধান হলো হোস্ট ডিরেক্টরিটিকে কোনো নামের পরিবর্তে সংখ্যাসূচক id-তে chown করা:
sudo chown -R 999:999 /srv/appname/pg
sudo chmod 700 /srv/appname/pg
ls -ldn /srv/appname/pgls -ldn নামের পরিবর্তে নম্বর প্রদর্শন করে এবং এটি 999 999 দেখানো উচিত। আপনার হোস্টে postgres নামের অ্যাকাউন্ট এবং ইমেজের ভেতরে postgres নামের অ্যাকাউন্টের মধ্যে কোনো সম্পর্ক নেই: কার্নেল নম্বরগুলো তুলনা করে, এবং নামগুলো উভয় পাশে আলাদাভাবে দেখা হয়। কিভাবে PUID এবং PGID হোস্ট ইউজারদের কন্টেইনারে ম্যাপ করে বিষয়টি সঠিকভাবে ব্যাখ্যা করে। rootless Docker বা user namespace remapping-এর ক্ষেত্রে নম্বরগুলো আবার পরিবর্তিত হয়, তাই 999 ধরে না নিয়ে চলমান কন্টেইনার থেকে idগুলো দেখে নিন।
Named volumes ব্যবহার করলে প্রথমবার চালানোর সময় এই পুরো বিষয়টি আর থাকে না, কারণ Docker একটি খালি ডিরেক্টরি তৈরি করে এবং entrypoint সেটির মালিকানা গ্রহণ করে।
আপগ্রেড: ইমেজ ট্যাগ পরিবর্তনের মাধ্যমে প্যাকেজ আপগ্রেড
হোস্টে, apt upgrade আপনাকে একটি মাইনর ভার্সন থেকে অন্যটিতে নিয়ে যায়। আপনার ডিস্ট্রিবিউশন নিজে থেকে ডাটাবেসের মেজর ভার্সন পরিবর্তন করবে না, এবং যখন আপনি নিজে এটি পরিবর্তন করতে চাইবেন, তখন উভয় সেট বাইনারি একসাথে ইনস্টল করা সম্ভব, যা pg_upgrade-এর জন্য প্রয়োজন।
কন্টেইনারের ক্ষেত্রে ট্যাগই হলো ভার্সন, তাই আপগ্রেড করার অর্থ হলো মাত্র একটি লাইন এডিট করা। এটি মাইনর আপগ্রেডকে সহজ করে এবং মেজর আপগ্রেডকে একটি সুনির্দিষ্ট প্রক্রিয়ায় পরিণত করে।
postgres:16-কে postgres:17-এ পরিবর্তন করুন, docker compose up -d চালান, এবং কন্টেইনারটি সাথে সাথে বন্ধ হয়ে যাবে:
PostgreSQL Database directory appears to contain a database; Skipping initialization
FATAL: database files are incompatible with server
DETAIL: The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17.এতে কোনো ক্ষতি হয় না। নতুন বাইনারিগুলো পুরনো অন-ডিস্ক ক্যাটালগ লেআউট পড়তে অস্বীকার করে, কারণ মেজর ভার্সনগুলোর মধ্যে এটি পরিবর্তিত হয়। ট্যাগটি আবার postgres:16-এ ফিরিয়ে আনলে এটি পুনরায় চালু হবে। এই রোলব্যাক সুবিধাটিই কন্টেইনার ব্যবহারের ক্ষেত্রে আপগ্রেডের প্রকৃত সুবিধা।
সমর্থিত পদ্ধতি হলো ডাম্প এবং রিস্টোর। PostgreSQL নতুন ক্লায়েন্ট দ্বারা ডাম্প নেওয়া পছন্দ করে, তাই কম্পোজ নেটওয়ার্কে চলমান পুরনো সার্ভারের বিপরীতে নতুন ইমেজ থেকে এটি চালান:
docker run --rm --network myapp_default -e PGPASSWORD="$POSTGRES_PASSWORD" \
postgres:17 pg_dumpall -h db -U postgres > /srv/backups/all.sql
ls -lh /srv/backups/all.sql
tail -n 2 /srv/backups/all.sqlফাইলটি অন্তত কয়েক দশ কিলোবাইট হওয়া উচিত এবং এর শেষ লাইনে PostgreSQL database cluster dump complete লেখা থাকতে হবে। কয়েকশ বাইটের ফাইল মানে হলো ডাম্প ব্যর্থ হয়েছে এবং আপনি অকারণে ভলিউম ডিলিট করতে যাচ্ছেন। এই বিষয়টি নিশ্চিত হওয়ার পরেই কেবল:
docker compose down
docker volume rm myapp_pgdata
# edit the compose file: image: postgres:17
docker compose up -d db
docker compose exec -T db psql -U postgres -f /dev/stdin < /srv/backups/all.sqlঅন্যান্য ইঞ্জিনের ক্ষেত্রে নিয়ম ভিন্ন:
- MySQL 8 স্টার্টআপের সময় নিজের ডাটা ডিকশনারি আপগ্রেড করে, তাই মাইনর ট্যাগ পরিবর্তনের জন্য সাধারণত শুধু রিস্টার্টই যথেষ্ট। রিলিজ সিরিজের মধ্যে বড় কোনো পরিবর্তনের আগে রিলিজ নোট পড়ুন এবং যেকোনো ক্ষেত্রেই আগে একটি ডাম্প নিয়ে রাখুন।
- MariaDB প্রত্যাশা করে যে নতুন ভার্সনে সার্ভার চালু হওয়ার পর
mariadb-upgradeচালানো হবে। - MongoDB-কে অবশ্যই একবারে একটি মেজর ভার্সন করে আপগ্রেড করতে হবে এবং প্রতিটি ধাপের পর পরবর্তী ধাপে যাওয়ার আগে ফিচার কম্প্যাটিবিলিটি ভার্সন সেট করতে হবে। ভার্সন বাদ দিয়ে আপগ্রেড করলে
mongodচালু হতে অস্বীকার করবে এবং লগেfeatureCompatibilityVersionউল্লেখ করে একটিUPGRADE PROBLEMলাইন দেখাবে। MongoDB 7.0 থেকে এই কমান্ডের জন্য একটি স্পষ্ট কনফার্মেশন ফ্ল্যাগ প্রয়োজন:db.adminCommand({ setFeatureCompatibilityVersion: "8.0", confirm: true })। - Redis পুরনো স্ন্যাপশট ফাইল অনায়াসেই লোড করতে পারে কিন্তু নতুন ফাইল পারে না, তাই আপগ্রেড মানে হলো রিস্টার্ট এবং ডাউনগ্রেড করার সময় ডাটা লোড হতে ব্যর্থ হতে পারে।
সাধারণ নিয়ম: কন্টেইনার ডাউনগ্রেডকে সহজ করে কিন্তু আপগ্রেডকে খুব একটা সহজ করে না।
আমার ডাটাবেস কন্টেইনার কেন কোড 137 নিয়ে বন্ধ হয়ে যায়?
এর কারণ হলো কার্নেলের out of memory (OOM) কিলার এটিকে বন্ধ করে দিয়েছে। 137 হলো 128 যোগ সিগন্যাল 9।
docker compose ps
docker inspect myapp-db-1 | grep -i oomkilled
journalctl -k | tail -n 20docker compose ps কমান্ডটি Exited (137) দেখায়, inspect লাইনে "OOMKilled": true লেখা থাকে এবং কার্নেল লগে একটি মিল থাকা এন্ট্রি পাওয়া যায়:
Memory cgroup out of memory: Killed process 4711 (postgres) total-vm:2170416kBএর পেছনের মেকানিজমটি এমন, যা অনেককে অবাক করে। PostgreSQL এবং MySQL তাদের বাফারের আকার নির্ধারণ করে হোস্টের মোট মেমোরি অনুযায়ী। cgroup লিমিট তাদের জন্য এই সংখ্যাটি পরিবর্তন করে না। 16 GB হোস্টের ওপর 2 GB লিমিট থাকলে ডাটাবেসটি এমনভাবে পরিকল্পনা করে যেন তার কাছে 16 GB মেমোরি আছে। ফলে হোস্টের ওপর কোনো চাপ পড়ার আগেই cgroup এটিকে বন্ধ করে দেয়। তাই শুধুমাত্র মেমোরি লিমিট যথেষ্ট নয়। আপনাকে ডাটাবেসকেও জানাতে হবে তার কাছে কতটুকু মেমোরি আছে:
- PostgreSQL:
shared_buffersসেট করুন এবংwork_mem-এর দিকে নজর রাখুন।work_memপ্রতিটি কানেকশনের প্রতিটি সর্ট অপারেশনের জন্য বরাদ্দ করা হয়, তাই 50টি কানেকশনের জন্য একটি বড় মান সেট করা থাকলে কন্টেইনারটি সাধারণত লোডের সময় বন্ধ হয়ে যায়, শুরুতে নয়। - MySQL এবং MariaDB:
innodb_buffer_pool_sizeসেট করুন, যার ডিফল্ট মান 128M। কন্টেইনারের ভেতরেinnodb_dedicated_serverবন্ধ রাখুন, কারণ এর মূল কাজই হলো মেশিনের মেমোরি শনাক্ত করে নিজেকে সেই অনুযায়ী সাজানো। - MongoDB: হোস্ট মেমোরি থেকে অনুমান করতে না দিয়ে WiredTiger ক্যাশের আকার স্পষ্টভাবে সেট করুন।
- Redis:
maxmemoryডিফল্টভাবে আনলিমিটেড থাকে, তাই cgroup এটিকে থামিয়ে না দেওয়া পর্যন্ত Redis বাড়তে থাকে।maxmemoryকন্টেইনার লিমিটের চেয়ে কিছুটা কম সেট করুন এবং একটিmaxmemory-policyবেছে নিন।
Postgres নিজেও এই ঘটনাটি রিপোর্ট করে, এবং লগে আপনি এই দুটি লাইন দেখতে পাবেন:
LOG: server process (PID 123) was terminated by signal 9: Killed
LOG: terminating any other active sessions due to crash of another server processএকটি ব্যাকএন্ড বন্ধ হয়ে গেলে অন্য সব ব্যাকএন্ডকেও রিস্টার্ট করতে হয়, কারণ শেয়ারড মেমোরি এখন অসামঞ্জস্যপূর্ণ হতে পারে। এটি আপনার অ্যাপ্লিকেশনের জন্য একটি কানেকশন স্টর্ম তৈরি করে, এটি কোনো সাধারণ ঘটনা নয়। Docker Compose-এ মেমোরি লিমিট সেট করা অংশে এর সিনট্যাক্স এবং mem_limit ও deploy.resources ফরম্যাটের পার্থক্য আলোচনা করা হয়েছে।
এতে হোস্টের ওপর চাপ কমে না, শুধু স্থানান্তরিত হয়। cgroup ছাড়া ডাটাবেসটি হোস্টের অন্য সবকিছুর সাথে মেমোরির জন্য প্রতিযোগিতা করে। তখন হোস্টের OOM কিলার স্কোর অনুযায়ী একটি ভিকটিম বেছে নেয়, যা sshd-ও হতে পারে। হোস্টের OOM কিলার আপনাকে সিস্টেম থেকে লক আউট করে দেওয়ার চেয়ে, একটি লিমিট সেট করে ডাটাবেসকে নিয়ন্ত্রিতভাবে বন্ধ হতে দেওয়া অনেক বেশি সুবিধাজনক।
ব্যাকআপ: ভেতরে ডাম্প করুন, বাইরে ব্যাকআপ রাখুন
চলমান কোনো ডাটাবেসের ডাটা ডিরেক্টরি সরাসরি কপি করে ব্যাকআপ নেবেন না। সার্ভার যখন ডাটা লিখছে, সেই সময়ে ফাইল-লেভেল কপি নিলে তা অসম্পূর্ণ (torn copy) হয়ে যায়, যা আপনি কেবল রিস্টোর করার সময় বুঝতে পারবেন।
দুটি সঠিক পদ্ধতি আছে: ডাটাবেস চলাকালীন তার নিজস্ব টুল দিয়ে ডাম্প তৈরি করে সেই ডাম্পের ব্যাকআপ নিন, অথবা কন্টেইনার বন্ধ করে ভলিউমটি কোল্ড কপি করুন।
docker compose exec -T db pg_dump -U postgres -Fc appdb > /srv/backups/appdb.dump
docker compose exec -T db mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" --single-transaction --all-databases > /srv/backups/mysql.sql
docker compose exec -T db mongodump --archive --gzip --db appdb > /srv/backups/appdb.archive.gz
docker compose exec -T redis redis-cli BGSAVE-T গুরুত্বপূর্ণ। এটি ছাড়া, docker compose exec কমান্ডের সাথে টার্মিনাল যুক্ত করতে পারে এবং টার্মিনাল লেয়ার আউটপুট স্ট্রিমে ক্যারেজ রিটার্ন যোগ করে দেয়। ফলে টেক্সট ডাম্প রিস্টোর করার সময় অদ্ভুত ত্রুটি দেখা দেয় এবং বাইনারি ডাম্প পুরোপুরি নষ্ট হয়ে যায়। এটি ব্যাকআপের সময় নীরবে ব্যর্থ হয় এবং এক মাস পর বড় ধরনের সমস্যা তৈরি করে।
--single-transaction পুরো সার্ভার লক না করেই mysqldump-কে InnoDB টেবিলের একটি সামঞ্জস্যপূর্ণ স্ন্যাপশট দেয়।
এই কমান্ডগুলো প্রতিটি একটি করে ফাইল লেখে। এগুলো কোনো ব্যাকআপ সিস্টেম নয়: এতে কোনো রিটেনশন, অফ-বক্স কপি বা ভেরিফিকেশন নেই। ডাম্প ডিরেক্টরিটিকে এমন একটি টুলের কাছে হস্তান্তর করুন যা এই তিনটি কাজই করে, যেমনটি restic backups from a VPS-এর ক্ষেত্রে করা হয়। /var/lib/docker/volumes-এর ব্যাকআপ না নিয়ে /srv/backups-এর ব্যাকআপ নিন।
এরপর রিস্টোর প্রক্রিয়াটি চালিয়ে দেখুন, কারণ যে ব্যাকআপ আপনি কখনো রিস্টোর করেননি, তা আসলে কোনো ব্যাকআপই নয়:
docker compose exec -T db createdb -U postgres restore_test
docker compose exec -T db pg_restore -U postgres -d restore_test < /srv/backups/appdb.dump
docker compose exec -T db psql -U postgres -d restore_test -c '\dt'\dt কমান্ডটি আপনার অ্যাপ্লিকেশনের টেবিলগুলোর তালিকা দেখাবে। ফলাফল খালি থাকলে বা Did not find any relations. দেখালে বুঝবেন ডাম্পটি আপনার প্রত্যাশা অনুযায়ী হয়নি। কাজ শেষ হলে restore_test ড্রপ করে দিন।
সবকিছু মুছে ফেলার কমান্ড
docker compose down -v।
সাধারণ down কমান্ডটি কন্টেইনার এবং নেটওয়ার্ক মুছে ফেলে। -v কমান্ডটি সেই compose ফাইলে ঘোষিত প্রতিটি named volume এবং সেই কন্টেইনারগুলোর সাথে যুক্ত প্রতিটি anonymous volume মুছে ফেলে। এটি কোনো নিশ্চিতকরণ বার্তা (prompt) দেখায় না এবং এটি পূর্বাবস্থায় ফেরানোর কোনো উপায় নেই। এটি self-hosted ডাটাবেস মুছে ফেলার সবচেয়ে সাধারণ উপায়, এবং সাধারণত কোনো অপ্রাসঙ্গিক সমস্যার সমাধানের সময় এটি ঘটে, কারণ কোনো ফোরামের উত্তরে এটি চালানোর পরামর্শ দেওয়া হয়েছিল।
চারটি বিষয় ক্ষতির সম্ভাবনা কমায়:
- ডাটাবেস ভলিউমকে
external: trueহিসেবে ঘোষণা করুন। Compose এমন কোনো ভলিউম মুছে ফেলে না যার মালিকানা তার নেই, তাই-vসেটিকে স্পর্শ করতে পারে না। আপনিdocker volume create myapp_pgdataব্যবহার করে এটি একবার তৈরি করে নিন। - নিয়মিত রিস্টার্টের জন্য
docker compose stopএবংdocker compose startব্যবহার করুন। Compose-এ down বনাম stop প্রতিটি কমান্ড কী কী মুছে ফেলে তা বিস্তারিত ব্যাখ্যা করে। - Compose দ্বারা পরিচালিত ভলিউমের বাইরে হোস্টের কোনো পাথে ডাটাবেসের ডাম্প (dump) রাখুন।
- আপনার গুরুত্বপূর্ণ ডাটা আছে এমন কোনো স্ট্যাকে ট্রাবলশুটিং উত্তরের
-vকমান্ডটি কখনোই কপি-পেস্ট করবেন না।
ডেটাবেস পোর্ট পাবলিক করবেন না
এই লাইনটি আপনার ডেটাবেসকে পাবলিক ইন্টারনেটে উন্মুক্ত করে দেয়:
ports:
- "5432:5432"এটি প্রতিটি ইন্টারফেসের সাথে বাইন্ড (bind) হয়। Docker আপনার ফায়ারওয়ালের ইনপুট রুলস কার্যকর হওয়ার আগেই প্যাকেটের গন্তব্য পরিবর্তন করে পোর্ট পাবলিশ করে। যেহেতু ufw-এর রুলস ইনপুট চেইনে থাকে, তাই ufw deny 5432 কোনো কাজই করে না। কেন Docker পাবলিশ করা পোর্ট ufw-কে বাইপাস করে লিঙ্কে চেইন ট্রাভার্সাল দেখানো হয়েছে।
একই compose প্রজেক্টের একটি অ্যাপ্লিকেশন compose নেটওয়ার্কে সার্ভিস নেম ব্যবহার করে ডেটাবেসে পৌঁছাতে পারে, তাই এর জন্য কোনো পাবলিশ করা পোর্টের প্রয়োজন নেই। এই ব্লকটি মুছে ফেলুন। যদি হোস্ট থেকে কোনো ক্লায়েন্ট ব্যবহার করতে চান, তবে শুধুমাত্র লুপব্যাক (loopback) ইন্টারফেসে বাইন্ড করুন:
ports:
- "127.0.0.1:5432:5432"বর্তমানে কী লিসেন (listen) করছে তা যাচাই করুন:
sudo ss -ltnp | grep 5432127.0.0.1:5432 হলো আপনার কাঙ্ক্ষিত অবস্থা। 0.0.0.0:5432 মানে হলো যে কেউ আপনার পাসওয়ার্ড দিয়ে প্রবেশের চেষ্টা করতে পারবে।
কোথায় কী চালাবেন
একটি VPS-এ একটি অ্যাপ্লিকেশন। কন্টেইনার ব্যবহার করুন। একটি নির্দিষ্ট নামের ভলিউম ব্যবহার করুন, কোনো পোর্ট সরাসরি উন্মুক্ত করবেন না, ডেটাবেসের সেটিংসের সাথে সামঞ্জস্য রেখে মেমোরি লিমিট সেট করুন এবং প্রতি রাতে একটি ডাম্প ফাইল হোস্টের পাথে রাখুন যা restic সংগ্রহ করবে। একটি VPS-এ ক্লিন Docker ইনস্টলেশন থেকে শুরু করুন এবং পুরো স্ট্যাকটি একটি compose ফাইলে রাখুন যা আপনি গিট-এ কমিট করবেন। এর সুবিধা স্পষ্ট: ডেটাবেস ভার্সনটি গিট-এ একটি রিভিউযোগ্য লাইন হিসেবে থাকে।
একটি হোস্টে একাধিক সার্ভিস চালানো। কন্টেইনার ব্যবহার করুন এবং প্রতিটি অ্যাপ্লিকেশনের জন্য আলাদা ডেটাবেস রাখুন, সবার জন্য একটি শেয়ারড সার্ভার ব্যবহার করবেন না। একটি শেয়ারড সার্ভার প্রতিটি অ্যাপ্লিকেশনকে একটি নির্দিষ্ট আপগ্রেড শিডিউলের সাথে আটকে ফেলে এবং একটি ত্রুটিপূর্ণ কুয়েরি সবার জন্য বিভ্রাট তৈরি করতে পারে। প্রতিটি কন্টেইনারকে নিজস্ব মেমোরি লিমিট দিন যাতে একটি খারাপ কুয়েরি শুধুমাত্র সেই অ্যাপের মধ্যেই সীমাবদ্ধ থাকে। একাধিক ছোট Postgres ইনস্ট্যান্সের ক্ষেত্রে ডিস্কের জায়গা সামান্য বেশি লাগলেও ব্যবস্থাপনার জটিলতা অনেক কমে যায়।
ডেটাবেসই যখন মূল পণ্য। এটি ভেন্ডরের প্যাকেজ রিপোজিটরি থেকে সরাসরি হোস্টে চালান অথবা ম্যানেজড সার্ভিস ব্যবহার করুন। pg_upgrade-এর জন্য একই সময়ে বাইনারিগুলোর দুটি মেজর ভার্সন ইনস্টল থাকা প্রয়োজন, যা প্যাকেজ ম্যানেজার দিয়ে সম্ভব কিন্তু সিঙ্গেল-ভার্সন ইমেজে সম্ভব নয়। যখন ডেটাবেসটি মেশিন এবং এর ডিস্কের পূর্ণ নিয়ন্ত্রণ পায়, তখন রেপ্লিকেশন এবং WAL (write ahead log) আর্কাভিংয়ের মাধ্যমে পয়েন্ট-ইন-টাইম রিকভারি করা সহজ হয়। যে সিস্টেমটি আপনাকে রাত 3টায় অ্যালার্ট দেবে, সেটির জন্য সবসময় সহজ এবং পরীক্ষিত পথটি বেছে নিন।
অ্যাপ্লিকেশনটি ছোট হলে। কোনো সার্ভার-ভিত্তিক ডেটাবেস ব্যবহার না করার কথা বিবেচনা করুন। একটি VPS-এ সিঙ্গেল-রাইটার ওয়েব অ্যাপ্লিকেশনের জন্য প্রোডাকশনে SQLite ব্যবহার করা অনেক ক্ষেত্রে ভালো, যেখানে ব্যাকআপ নেওয়া মানে একটি মাত্র ফাইল কপি করা এবং আপগ্রেড করা মানে শুধুমাত্র একটি লাইব্রেরি ভার্সন আপডেট করা।
FAQ
Docker-এ প্রোডাকশন ডাটাবেস চালানো কি নিরাপদ?
হ্যাঁ, সিঙ্গেল-সার্ভার অ্যাপ্লিকেশন স্ট্যাকের জন্য এটি নিরাপদ। একটি কন্টেইনার হলো Linux প্রসেস, যার চারপাশে namespaces এবং cgroups থাকে। তাই ভলিউম মাউন্ট করা থাকলে ডাটাবেসটি হোস্টের সেই ফাইলসিস্টেমেই লেখে, যা আপনি প্যাকেজ থেকে ইন্সটল করলে ব্যবহার করত। ঝুঁকিগুলো গতির চেয়ে বরং অপারেশনাল: নামহীন ভলিউম, ভুল ইউজার আইডি-র মালিকানাধীন বাইন্ড মাউন্ট, অথবা এমন ব্যাকআপ যা আপনি কখনো পরীক্ষা করেননি, এবং docker compose down -v। এই চারটি বিষয় ঠিক থাকলে কন্টেইনারটি সঠিকভাবে কাজ করবে। যখন ডাটাবেসটি আপনার প্রধান কাজের অংশ হয়ে দাঁড়াবে এবং আপনার pg_upgrade, রেপ্লিকেশন বা পয়েন্ট-ইন-টাইম রিকভারি প্রয়োজন হবে, তখন হোস্ট ইন্সটলেশনে চলে যান।
ডাটাবেস ডাটার জন্য আমি কি বাইন্ড মাউন্ট ব্যবহার করব নাকি নেমড ভলিউম?
যদি হোস্টের পাথ জানার বিশেষ কোনো কারণ না থাকে, তবে নেমড ভলিউম ব্যবহার করুন। Docker ডিরেক্টরি তৈরি করে এবং প্রথমবার স্টার্ট করার সময় ইমেজ এন্ট্রি পয়েন্ট মালিকানা নির্ধারণ করে দেয়, তাই পারমিশন সংক্রান্ত সমস্যা হয় না। একটি নির্দিষ্ট name: দিয়ে ভলিউমটি পিন করুন অথবা এটিকে external: true হিসেবে চিহ্নিত করুন। অন্যথায়, প্রজেক্ট ডিরেক্টরির নাম পরিবর্তন করলে নীরবে একটি নতুন খালি ভলিউম এবং খালি ডাটাবেস তৈরি হয়ে যাবে। বাইন্ড মাউন্ট ব্যবহার করা যেতে পারে যদি আপনি হোস্ট ডিরেক্টরিটিকে সেই নিউমেরিক ইউজার আইডিতে chown করেন, যেটিতে ইমেজটি রান করে (অফিসিয়াল Postgres, MySQL এবং MongoDB ইমেজের জন্য এটি 999)। ls -ldn দিয়ে এটি যাচাই করুন, কারণ ls -l আপনার হোস্টের জন্য সেই নাম দেখাবে যা কন্টেইনারের ভেতরে অর্থহীন।
docker compose down -v কী কী মুছে ফেলে?
এটি সাধারণ down-এর মতোই কন্টেইনার এবং নেটওয়ার্ক মুছে ফেলে, তবে -v অতিরিক্ত হিসেবে compose ফাইলে ঘোষিত প্রতিটি নেমড ভলিউম এবং কন্টেইনারের সাথে যুক্ত প্রতিটি অ্যানোনিমাস ভলিউম মুছে ফেলে। এর মধ্যে ডাটাবেসও অন্তর্ভুক্ত। এখানে কোনো নিশ্চিতকরণ বার্তা (confirmation prompt) আসে না এবং কোনো রিকভারির সুযোগ নেই। external: true হিসেবে চিহ্নিত ভলিউমগুলো মুছে ফেলা হয় না, আর এই কারণেই ডাটাবেস ভলিউমকে এক্সটার্নাল হিসেবে চিহ্নিত করা ভালো। নিয়মিত রিস্টার্টের জন্য docker compose stop এবং docker compose start ব্যবহার করুন।
Docker-এ PostgreSQL-এর নতুন মেজর ভার্সনে কীভাবে আপগ্রেড করব?
ডাম্প এবং রিস্টোর করুন। postgres:16 পরিবর্তন করে postgres:17 দিয়ে রিস্টার্ট করলে FATAL: database files are incompatible with server ত্রুটি দেখা দেবে এবং DETAIL লাইনে উভয় ভার্সনের নাম উল্লেখ থাকবে, কারণ নতুন বাইনারিগুলো পুরনো ক্যাটালগ লেআউট পড়তে পারে না। এতে কোনো ক্ষতি হয় না: পুরনো ট্যাগটি আবার সেট করলে এটি চালু হয়ে যাবে। চলমান পুরনো কন্টেইনারের বিপরীতে নতুন ভার্সনের ক্লায়েন্ট ব্যবহার করে একটি pg_dumpall নিন, ফাইলটি PostgreSQL database cluster dump complete দিয়ে শেষ হয়েছে কি না নিশ্চিত করুন, তারপর নতুন ট্যাগটি খালি ভলিউমে চালু করে ডাম্পটি লোড করুন। একটি মেজর ভার্সনের ভেতরে মাইনর আপগ্রেডের জন্য শুধু একটি পুল এবং রিস্টার্টই যথেষ্ট।
আমার ডাটাবেস কন্টেইনার কেন 137 কোড নিয়ে বন্ধ হয়ে যায়?
137 হলো 128 যোগ সিগন্যাল 9, যার অর্থ কোনো কিছু প্রসেসটিকে সরাসরি বন্ধ করে দিয়েছে। docker inspect <container> | grep -i oomkilled চালান; true মান আসার অর্থ হলো কন্টেইনারটি তার cgroup মেমোরি লিমিটে পৌঁছে গেছে। সাধারণত PostgreSQL এবং MySQL হোস্টের মোট মেমোরি পড়ে এবং কন্টেইনারের লিমিট দেখতে পায় না, তাই তারা 2 GB মেমোরির ভেতরে থেকেও 16 GB-এর পরিকল্পনা করে। কন্টেইনারকে দেওয়া লিমিটের সাথে সামঞ্জস্য রাখতে shared_buffers এবং work_mem, অথবা innodb_buffer_pool_size সেট করুন। কার্নেল কোন প্রসেসটিকে বেছে নিয়েছে তা নিশ্চিত করতে journalctl -k-এ সংশ্লিষ্ট Memory cgroup out of memory লাইনটি পরীক্ষা করুন।