چرا این موضوع مهم است؟
Container معمولاً kernel میزبان را share میکند و با namespaceها دید process، network، mount و user را جدا میکند؛ cgroup نیز مصرف منابع را محدود و اندازهگیری میکند. Image لایههای filesystem و metadata اجرای application را حمل میکند.
در یک سیستم واقعی، قبل از انتخاب ابزار باید مدل ذهنی درستی از لایهها داشته باشید. هر بار که یک مشکل را فقط با یک command یا تنظیم حفظی حل میکنیم، دانش به نسخه و محیط خاص گره میخورد. روش بهتر این است که ورودی، state، policy، مسیر داده و خروجی را مشخص کنیم. بعد ابزار را برای مشاهدهٔ یکی از این لایهها انتخاب کنیم. این رویکرد باعث میشود وقتی نسخه یا توزیع عوض شد، هنوز بدانیم دنبال چه evidenceای هستیم.
مدل معماری
در موضوع مدل اجرای container و تفاوت آن با virtualization، componentها را روی یک مسیر ساده بکشید و مرز مسئولیت هرکدام را مشخص کنید. state پایدار، state runtime، dependency بیرونی و نقطهٔ اعمال policy را جدا کنید. وقتی diagram ساده دارید، خطا از یک «سیستم بزرگ و مبهم» به چند مرحلهٔ قابل آزمون تبدیل میشود.

روش عملی کار
docker inspect، /proc، nsenter و cgroup filesystem را کنار CLI معمولی Docker ببینید. این مشاهده کمک میکند بفهمید container daemon جادو نمیکند؛ primitiveهای Linux را orchestrate میکند.
برای Production، تغییر باید قابل اندازهگیری و قابل برگشت باشد. baseline قبل از تغییر شامل نسخه، config مؤثر، log سالم، metric اصلی و یک تست end-to-end است. rollout را با canary شروع کنید؛ بعد از هر مرحله هم تست مثبت و هم تست منفی داشته باشید. اگر success criterion از قبل نوشته نشده باشد، بعد از اجرا بهسادگی هر نتیجهای را میتوان «موفق» تفسیر کرد. rollback هم باید تمرین شود، نه اینکه فقط یک دستور روی کاغذ باشد.
از دید امنیت
Container boundary را معادل VM boundary ندانید. kernel مشترک، privilege اضافی، mount حساس یا socket Docker میتواند isolation را بهشدت ضعیف کند.
از دید امنیت، کمکردن سطح دسترسی مهم است اما availability و قابلیت عیبیابی هم جزو امنیت عملیاتیاند. secretها، tokenها، capabilityها، network path و logهای حساس را جدا بررسی کنید. هر exception باید owner، دلیل و تاریخ بازبینی داشته باشد. کنترلهایی که هیچکس نمیتواند اثرشان را مشاهده یا علتشان را توضیح دهد، در زمان incident به مانع تبدیل میشوند.
آزمایشگاه پیشنهادی
یک container اجرا کنید، PID داخل و خارج را مقایسه کنید، network namespace را پیدا کنید، memory limit بگذارید و رفتار process هنگام عبور از limit را ثبت کنید.
برای هر مرحله timestamp و خروجی را ذخیره کنید. یک failure کنترلشده ایجاد کنید تا مطمئن شوید monitoring واقعاً failure را میبیند. سپس سیستم را به baseline برگردانید و زمان recovery را ثبت کنید.

عیبیابی بر پایهٔ Evidence
Troubleshooting را با تغییر config شروع نکنید. اول محل دقیق failure را پیدا کنید: آیا ورودی وارد سیستم میشود؟ آیا state مورد انتظار ساخته شده؟ آیا policy اجازه میدهد؟ آیا خروجی از component خارج میشود؟ log، packet capture، metric و state API باید به یک timeline مشترک وصل شوند. نبودن event نیز evidence است؛ اگر request قبل از یک component دیده میشود و بعد از آن نه، دامنهٔ مسئله کوچک شده است.
Performance و ظرفیت
عملکرد را فقط با average نسنجید. p95/p99 latency، saturation، queue depth، retry و resource pressure تصویر بهتری میدهند. تغییری که در lab کوچک سریع است ممکن است زیر concurrency یا dataset واقعی bottleneck متفاوتی نشان دهد. capacity limitها را قبل از rollout بزرگ شناسایی کنید و alert را به thresholdهای قابل اقدام وصل کنید.
Production readiness
قبل از rollout نهایی، owner، change window، blast radius، success metric و rollback trigger باید مشخص باشند. اگر dependency خارجی دارید، رفتار هنگام timeout یا unavailable شدن آن را تست کنید. اگر config به چند host میرود، rollout مرحلهای و inventory دقیق لازم است. یک تغییر خوب باید observable، explainable و reversible باشد.
Runbook و انتقال دانش
در مستندسازی، فقط «چه دستورهایی اجرا شد» را ننویسید. سؤال، فرضیه، evidence مورد انتظار، نتیجه و دلیل تصمیم را ثبت کنید. اگر همکار دیگری بتواند با همان ورودی آزمایش را تکرار کند و به نتیجهٔ مشابه برسد، runbook ارزش عملیاتی دارد. اگر فقط نویسنده میداند چرا مرحلهای لازم است، دانش هنوز منتقل نشده است.
قبل از Production چه چیزی را اعتبارسنجی کنیم؟
نتیجهٔ Lab را مستقیم به Production تعمیم ندهید. یک change plan کوتاه بسازید که در آن دامنهٔ تغییر، owner، زمان اجرا، dependencyها، success metric و rollback trigger مشخص باشد. ابتدا روی canary یا درصد کوچکی از workload اجرا کنید و حداقل یک چرخهٔ واقعی restart، credential rotation یا deploy دوم را ببینید؛ بسیاری از خطاها در اولین اجرای سالم ظاهر نمیشوند. رفتار هنگام unavailable شدن dependency خارجی، پر شدن disk، timeout شبکه و ورودی نامعتبر را نیز بررسی کنید. اگر feature امنیتی است، test منفی باید نشان دهد مسیر غیرمجاز واقعاً بسته است؛ اگر feature performance است، benchmark باید با load و dataset نزدیک به واقعیت اجرا شود. در پایان، تصمیم rollout را با evidence ثبت کنید، نه با جملهٔ مبهم «بهنظر خوب است».
نگهداری بلندمدت و بازبینی دورهای
معماری بعد از deploy ثابت نمیماند. نسخهٔ سیستمعامل، dependency، policy سازمان، certificate، endpoint و حجم workload تغییر میکنند. برای همین باید بازبینی دورهای تعریف شود: نسخههای پشتیبانینشده را پیدا کنید، exceptionهای قدیمی را ببندید، alertهای بیارزش را حذف کنید و recovery را دوباره تمرین کنید. اگر configuration در Git نگهداری میشود، drift میان state واقعی و repository را اندازه بگیرید. اگر ابزار SaaS یا سرویس خارجی است، تغییر API، pricing، limit و مدل permission را دنبال کنید. مستندات باید تاریخ review داشته باشند تا تیم بداند کدام فرضها هنوز معتبرند. نگهداری خوب یعنی سیستم بعد از شش ماه هم قابل توضیح و قابل بازیابی باشد، نه اینکه فقط روز اول deploy موفق بوده باشد.
جمعبندی
هدف این مقاله دربارهٔ مدل اجرای container و تفاوت آن با virtualization ارائهٔ یک دستور جادویی نیست؛ هدف ساختن مدل ذهنی است. وقتی معماری را بفهمید، baseline داشته باشید و تغییر را با evidence کوچک کنید، ابزارها قابل تعویض میشوند اما روش حل مسئله باقی میماند. منبع اصلی این مقاله از Docker انتخاب شده تا جزئیات فنی قابل پیگیری باشند.
چکلیست سریع
- مسئله را در یک جمله تعریف کن.
- component و مرز اعتماد را مشخص کن.
- baseline را قبل از تغییر ذخیره کن.
- یک test مثبت و یک test منفی بساز.
- تغییر را با canary شروع کن.
- evidence را timestampدار نگه دار.
- rollback را واقعاً تمرین کن.
- نتیجه و exceptionها را در runbook ثبت کن.
آزمایش دقیق مرز فرایند و تصویر
برای دیدن تفاوت تصویر و ظرف اجرایی، یک برنامه ساده انتخاب کنید که هر چند ثانیه زمان جاری را در خروجی استاندارد بنویسد. تصویر، بسته آماده اجرای آن برنامه است؛ ظرف، نمونه فعال یا متوقف شده آن تصویر همراه تنظیمات اجرا و لایه قابل نوشتن است. از یک تصویر دو نمونه بسازید و در مسیر موقت هرکدام فایلی متفاوت قرار دهید. وجود فایل در نمونه نخست نباید باعث ظاهر شدن آن در نمونه دوم شود. این مشاهده مفهوم لایه نوشتنی مستقل را روشن میکند. سپس نمونه نخست را حذف و دوباره ایجاد کنید. اگر فایل فقط در لایه موقت بوده، انتظار حفظ آن اشتباه است؛ برای داده ماندگار باید فضای ذخیره مستقل طراحی شود.
شناسه فرایند نیز آزمایش خوبی است. برنامه ممکن است درون محیط جداشده شناسه یک داشته باشد، ولی میزبان همان برنامه را با شناسه دیگری بشناسد. این دو گزارش متناقض نیستند؛ هر مشاهده در فضای نام متفاوت انجام شده است. فضای نام، دید فرایند را تغییر میدهد و به تنهایی سقف مصرف حافظه نمیگذارد. محدودیت مصرف کار گروههای کنترلی است. در نتیجه دیدن فهرست کوتاه فرایندها اثبات نمیکند که برنامه نمیتواند حافظه میزبان را تحت فشار قرار دهد. برای بررسی این موضوع باید تنظیم واقعی حافظه و رخدادهای مربوط به محدودیت را نیز بخوانید.
حافظه، پردازنده و خاتمه درست برنامه
فرض کنید یک خدمت وب درخواستهای کوچک را پاسخ میدهد اما هنگام تولید گزارش بزرگ متوقف میشود. ابتدا خروج غیرعادی برنامه را از توقف ناشی از محدودیت حافظه جدا کنید. گزارش خود برنامه، وضعیت خاتمه ظرف و رخدادهای گروه کنترلی سه منبع مکمل هستند. افزایش بیحساب سقف حافظه ممکن است علامت را پنهان کند، ولی اگر تعداد درخواستهای همزمان نامحدود باشد مشکل به میزبان منتقل میشود. اندازه صف، تعداد کارگرها و مصرف هر درخواست باید همراه سقف حافظه بررسی شوند. در آزمایش از داده ساختگی استفاده کنید تا تولید گزارش سنگین روی اطلاعات واقعی اثر نگذارد.
برای پردازنده نیز سهم زمانی با تعداد هسته قابل مشاهده یکسان نیست. برنامهای که تعداد زیادی کارگر ایجاد میکند ممکن است به علت سهم محدود پردازنده، زمان بیشتری در انتظار بماند. بنابراین کندی را فقط به شبکه یا زبان برنامه نسبت ندهید. زمان اجرای درخواست، تعداد کارگر و نشانههای محدود شدن مصرف را کنار هم مقایسه کنید. یک تغییر کوچک در تعداد کارگرها میتواند از رقابت بیهوده جلوگیری کند، اما نتیجه به نوع بار وابسته است و باید اندازهگیری شود.
فرایندی که نقش اصلی ظرف را دارد باید پیام خاتمه را دریافت و مدیریت کند. اگر یک پوسته واسط پیام را به برنامه نرساند، توقف عادی ممکن است تا پایان مهلت منتظر بماند و سپس اجباری شود. برای بررسی، درخواست کوتاهی را حین توقف کنترلشده اجرا کنید و ببینید آیا خدمت درخواستهای تازه را متوقف و درخواستهای جاری را کامل میکند. نیاز برنامه به جمعآوری فرایندهای فرزند نیز باید در طراحی راهانداز در نظر گرفته شود.
انتخاب میان ظرف و ماشین مجازی
ماشین مجازی هسته مهمان خود را دارد؛ ظرف لینوکسی معمولاً هسته محیط میزبان لینوکسی را به اشتراک میگذارد. این تفاوت هنگام اجرای برنامه نیازمند قابلیت خاص هسته، یا جداسازی مستأجرهای نامطمئن مهم میشود. روی رایانه غیرلینوکسی ممکن است ابزار توسعه یک ماشین مجازی لینوکسی در پشت صحنه اجرا کند؛ پس عملکرد دیسک و شبکه آن محیط را نباید بیواسطه به سرور لینوکسی تعمیم داد.
برای یک خدمت داخلی، اجرای کاربر غیرریشه، حذف قابلیتهای اضافی، فایلسیستم عمدتاً فقطخواندنی و محدودیت منابع نقطه شروع مناسبی هستند. هرکدام مسئله مشخصی حل میکند و جای دیگری را نمیگیرد. اتصال سوکت مدیریت Docker به برنامه، اختیار بسیار بزرگی ایجاد میکند؛ آن را مشابه یک فایل معمولی تنظیمات نبینید. اگر برنامه فقط نیاز به دریافت رویداد دارد، ابتدا بررسی کنید آیا واسطه محدودتری میتواند همان داده را ارائه کند. انتخاب معماری باید از نیاز واقعی برنامه و مرز اعتماد شروع شود، سپس هزینه عملیاتی و سرعت استقرار سنجیده شود.