container ဆိုတာဘာလဲ (A Must Read for Seniors)
container တွေကဘာလဲဆိုတဲ့ မေးခွန်းကို မေးတဲ့သူရဲ့ background အပေါ်မူတည်ပြီး အဖြေအမျိုးမျိုး ဖြေပေးခဲ့ဖူးတယ်။ ကျွန်တော့်ရဲ့စာအုပ် The Journey to DevOps မှာဆိုရင်လည်း container ဆိုတာ OS filesystem ရယ်၊ application ရယ်၊ configuration file တွေရယ်ကို artifact တခုတည်းအဖြစ် package လုပ်ထားတဲ့ အဆင်သင့်ကောက် run ရုံ virtual machine template လို file တခုလို့ ဖြေခဲ့တယ်။ ဒါပေမယ့် တခေါက်မှာတော့ ဒီရိုးရှင်းလွန်းတဲ့ မေးခွန်းကိုပဲ UK ရဲ့ SRE အင်တာဗျူးတခုမှာ မေးလာခဲ့တယ်။ ကုမ္ပဏီက London Docklands မှာ data center (colo) တွေရှိတယ်။ သူတို့က အခုချိန်ထိ resource allocation (နဲ့ low level scheduling) အတွက် mesos ကိုသုံးနေတုန်း။ containerized workload တွေလည်းရှိပေမယ့် အစွန်းထွက်လောက်ပဲ။ ကုမ္ပဏီက ဒီ legacy system ကနေ migrate လုပ်ချင်တယ်။ ဒီတော့ သူတို့ရဲ့ အဓိက concern တွေထဲက တခုက candidate တယောက်က container တွေနဲ့ ဘယ်လောက်ရင်းနှီးလဲဆိုတာပဲ။
မေးခွန်းက သိပ်ရိုးရှင်းတယ်။ container တွေဆိုတာဘာလဲတဲ့။ ကျွန်တော် မဖြေခင် ပွင့်ပွင့်လင်းလင်း ပြန်မေးလိုက်ဖြစ်ပါတယ်။ ice breaker အဖြေမျိုး လိုချင်တာလား။ ဒါမှမဟုတ် ဘာကြောင့် container သုံးသင့်လဲဆိုတဲ့ advocate တယောက်ရဲ့ အဖြေမျိုး လိုချင်တာလား။ နှစ်ခုလုံး မဟုတ်ဘူးဆိုရင် container နဲ့ပတ်သက်လို့ ငါပြောပြချင်တဲ့ စိတ်ထဲရှိတာတွေကို အိတ်သွန်ဖာမှောက်ပြောချမှာမို့လို့ တောင်စဥ်ရေမရတော့ ဖြစ်နေနိုင်တယ်။ ခွင့်ပြုနော်လို့။
ပထမဆုံး ပြောဖြစ်တာက container ဆိုတဲ့ စကားလုံးကို အာရုံထဲက ခဏဖယ်လိုက်ဖို့ပဲ။ software တခု (C++, Go, PHP interpreter စသဖြင့် binary ဖြစ်ဖြစ်၊ generic script တွေပဲဖြစ်ဖြစ်) run ပြီဆိုရင် OS ကအဲဒီ executable ကို memory ထဲကူးထည့်ရတယ်။ ID တခုသတ်မှတ်တယ်။ execution အတွက်လိုအပ်တဲ့ register, stack pointer, heap နောက် environment variable တွေကို ပြင်ဆင်တယ်။ ready ဖြစ်ပြီဆိုရင် ဘယ် core ဘယ် thread စသဖြင့်မှာ task execution လုပ်ဖို့ kernel scheduler ကနေရာချပေးတယ်။ ဆိုလိုချင်တာက software တခု run တယ်ဆိုတာ (single-threaded, multi-threaded) process တခုအဖြစ် အလုပ်လုပ်တယ်ဆိုတာပေါ့။
ဒါပေမယ့် ပြဿနာ ၂ ခုရှိတယ်။ ပထမတခုက security ပြဿနာ။ security ဖက်ကပြောရင် စောနက process တွေအတွက် တယောက်ခြေမပေါ် တယောက်တက်နင်းမိဖို့ဆိုတာ ဖြစ်ခဲလှတဲ့ကိစ္စမျိုး မဟုတ်ဘူး။ process တခုထဲက bug တခုက တခြား process တွေအကုန်လုံးကို ဆွဲချပစ်သွားနိုင်သလို compromised process တခုကလည်း တခြား process တွေကို ရည်ရွယ်ချက်ရှိရှိ လှမ်းဒုက္ခပေးနိုင်တယ်။ ဒုတိယက operational burden ပြဿနာ။ ကိုယ့် process တွေက standardized dependency ဥပမာ system library တွေ၊ runtime တွေကို version အတူတူပဲ လိုအပ်တာမျိုးဆိုရင် ကိစ္စမရှိဘူး။ မတူဘူးဆိုရင် ဒီအတွက် workaround တွေနဲ့ အသေးစိတ် လိုက် configure လုပ်ဖို့လိုမယ်။ နောက်ပြီး process တွေကို machine:host:port အတွဲလိုက် bookkeeping လုပ်ဖို့လည်း လိုလိမ့်တယ်။ ဒီအလုပ်တွေကို machine ပေါင်းများစွာ၊ cluster ပေါင်းများစွာမှာ လုပ်ရမယ်ဆိုရင် ဒါနည်းနည်းနောနော manual labor မဟုတ်တော့ဘူး။ scalable လည်းမဖြစ်ဘူး။ အချိန်လည်းကုန်တယ်။ consistency လည်းမရှိဘူး။ အဆိုးဆုံးက error prone လည်းဖြစ်ဦးမယ်။ တကယ်လို့ static partitioning မလုပ်ပဲ Borg (ဒါမှမဟုတ် Omega), Mesos တို့လို schedular/orchestrator တွေသုံးထားမယ်ဆိုရင်တောင်မှ statically linked မဟုတ်တဲ့ binary တွေမှာ အခုလို dependency hell ပြဿနာက ကြုံရဦးမှာပဲ။
ဒီပြဿနာတွေကို hypervisor ခေတ်မှာ isolation နဲ့ immutability သုံးပြီးဖြေရှင်းခဲ့ကြတယ်။ process တခုကို VM တခုသတ်မှတ်ပေးထားမယ်။ Jenkins server တွေက Jenkins node တွေမှာပဲ run မယ်။ Jenkins အတွက် လိုအပ်တဲ့ JDK, JRE အတိအကျ ထည့်ထားပေးမယ်။ Apache Hadoop အတွက်ကိုလည်း သူလိုအပ်တဲ့ JRE version တင်ထားတဲ့ Hadoop node တွေမှာပဲ run စေမယ်။ ဒီလို isolate လုပ်လိုက်နိုင်တဲ့အတွက် operational burden အတွက်ကိုလည်း immutability သုံးပြီးဖြေရှင်းလို့ရသွားတယ်။ VM တခုမှာ ဘာ process ဘယ်လို dependency မျိုးတွေပါမလဲဆိုတာကို သိနှင့်နေပြီဆိုရင် OS, application နဲ့ configuration တွေအပါ disk image ကို snapshot ယူထားလိုက်နိုင်တယ်။ ဒီ snapshot ကနေ manual configuration တွေ patching တွေ လုပ်ဖို့မလိုတော့ပဲ Jenkins node တွေ၊ Apache Spark node တွေလိုသလောက် provision/scale လို့ရသွားတယ်။ machine affinity မရှိတော့ပဲ resource binpacking လုပ်လို့ရသွားတဲ့အတွက် static partitioning ထက်လည်း ပို efficient ဖြစ်သွားမယ်။
ဒါပေမယ့် ဒါကလည်း သူ့ challenge နဲ့သူပဲ။ VM တိုင်းက overhead ရှိတဲ့အပြင် portability မကောင်းလှဘူး။ overhead မြင့်တယ်ဆိုတာက VM (Guest OS) တခု run ဖို့သုံးရမယ့် resource တွေကို application တွေ run ဖို့အတွက် သုံးလိုက်လို့ရတယ်။ resource အလေအလွင့် ဖြစ်တယ်ပေါ့။ နောက်ပြီး VM တခုကို bootstrap လုပ်ရတာ၊ network ပေါ်ကနေ transport လုပ်ရတာတွေမှာ ကြာချိန်ရှိတယ်။ hypervisor compatibility ကြည့်ရမယ်။ failure (UPS power, top of rack network switch, cooling, disk စသဖြင့်တွေ) အပြင် machine maintenance တွေနဲ့ အမြဲတမ်း constant churning ဖြစ်နေတဲ့ data center တခုမှာ VM တိုင်း process တိုင်းက preempt, reschedule လုပ်ခံရနိုင်တဲ့ အခွင့်အလမ်းအများကြီးရှိတဲ့အတွက် downtime ထိခိုက်မယ်။
ဒီအချိန်မှာ technology နှစ်ခုပေါ်လာတယ်။ ပထမတခုက namespace လို့ခေါ်တယ်။ process နှစ်ခုက OS တခုတည်းပေါ်မှာ run နေလင့်ကစား မတူညီတဲ့ process tree, filesystem နဲ့ network စသဖြင့် သီးသန့် view တွေနဲ့ မြင်ရအောင် namespace တွေသုံးပြီး ခွဲထုတ်လို့ရလာတယ်။ နောက်တခုက cgroup တနည်း control group ပေါ့။ control group ကကျ resource isolation အတွက်မဟုတ်ဘူး။ ဘယ် process က CPU ဘယ်လောက်၊ memory ဘယ်လောက်၊ disk IOPS ဘယ်လောက်ပဲ သုံးခွင့်ရှိမယ်ဆိုတာမျိုး resource constraining/governing သတ်မှတ်တဲ့ဟာ။ ဒီ technology တွေကြောင့် process တွေကို machine တခုတည်းမှာ ဆံ့သလောက် colocate လုပ်ဖို့ ပြန်စဥ်းစားလာကြတယ်။ အပြောင်းအလဲတခုအနေနဲ့ ဒီတခေါက် revolution မှာတော့ ဘယ် executable ကိုမဆို statically linked binary အသွင်ရအောင် လိုအပ်တဲ့ tool နဲ့ dependency တွေအကုန်လုံးကို filesystem တခုတည်းအဖြစ် compressed package ထုတ်မယ်။ အဲဒီ package ကို runtime layer တခုကနေ unpack, decompress လုပ်ပြီး လိုအပ်တဲ့ process ကို run ပေးမယ်။
ဥပမာ Jenkins app ကို run ချင်ရင် Jenkins ကလိုအပ်တဲ့ JRE, glibc နဲ့နောက် filesystem (/dev/null, /tmp, /proc စသဖြင့်) တွေကို tarball တခုတည်းအဖြစ် package ထုတ်။ အဲဒီ tarball ကို destination node ဆီပို့။ runtime process ကသူ့အတွက် Host OS ပေါ်မှာ child process တခု clone ပြီး namespace ဆောက်။ အဲဒီ namespace ထဲကို ခုနက tarball ကိုဖြည်ပြီး filesystem ပြင်။ ဒီ filesystem ကို အခု bootstrapping process ရဲ့ root fs အသစ်အဖြစ်ချိန်း၊ root fs အဟောင်း (host ရဲ့ root fs) ကို unmount လုပ်။ application process ကိုသူသုံးခွင့်ရှိတဲ့ CPU, memory တွေအတွက် cgroup သတ်မှတ်၊ မလိုအပ်တဲ့ capability တွေ drop ပစ်၊ drop ထားတဲ့ကြားက အန္တရာယ်များတဲ့ syscall တွေလုပ်လာရင် catch နိုင်အောင် seccomp profile တွေဆင်၊ ပေးထားတဲ့ capability တွေကို အခွင့်ကောင်းယူပြီး kernel ကတဆင့် ထပ်ကျွံလာသေးရင် တားနိုင်ဖို့ SELinux သုံးပြီး ACL တွေသတ်မှတ်။ အကုန်စုံပြီဆို လိုအပ်တဲ့ application process ကို execute လုပ်ပြီး PID 1 ဖြစ်တဲ့ စောနက bootstrapping process နေရာမှာ အစားထိုးလိုက်ရင် သေသေသပ်သပ်နဲ့ security ရော၊ operational burden ပါမရှိတဲ့ model တခုဖြစ်သွားပြီ။
ဒါကိုမှ ထပ် optimize လို့ရတာက single artifact အဖြစ် package ထုတ်တဲ့နေရာမှာ union filesystem ကိုသုံးပြီး storage သက်သာအောင် လုပ်လို့ရတယ်။ union fs ဆိုတာ filesystem တွေကို view တခုတည်းအဖြစ် overlay လုပ်ပေးနိုင်တဲ့ technology တမျိုး။ overlay2 လို filesystem driver မျိုးတွေပေါ့ ဥပမာ။ ဒီ union filesystem ကို CoW လို့ခေါ်တဲ့ concept နဲ့ပေါင်းလိုက်ရင် optimization အကြမ်းကြီးတွေ လုပ်လို့ရလာစေတယ်။ ဥပမာ artifact တွေကို package ထုတ်တဲ့အခါ အပြောင်းအလဲ အနည်းဆုံးဖြစ်မယ့် read-only file တွေ ဥပမာ /lib, /usr/lib, /usr/bin စသဖြင့်ကို filesystem တခု (SHA256 content addressable layer တခုအဖြစ်) ထားပြီး application အလိုက် unique ဖြစ်တဲ့ executable တွေ ဥပမာ Jenkins, MapReduce စသဖြင့်နဲ့ သူတို့ရဲ့ configuration file တွေကို filesystem တခု (နောက် layer တခုအဖြစ်) ထားလိုက်ပြီး copy on write သုံးမယ်ဆိုရင် process တွေကြားမှာ layer တူတွေကို share လို့ရမယ်။ ဒီနည်းနဲ့ dependency တွေကို တတ်နိုင်သလောက် reuse လုပ်ပြီး process တွေအများကြီးကို host တခုတည်းပေါ်မှာ storage နည်းနည်းနဲ့ ဆံ့သလောက် သိပ်ထည့်ထားလို့ရသွားမယ်။ performance ဖက်ကကြည့်ရင် CoW သုံးထားတဲ့အတွက် read တွေမှာ file တခုကို အပေါ်ဆုံးအလွှာကနေ အောက်ဆုံး base layer အထိ traverse ဆင်းရှာရမယ်။ write တွေဆိုရင်တော့ ပထမဆုံးအကြိမ် write တဲ့အခါ file တခုလုံးကို အပေါ်ဆုံးအလွှာဆီ copy ခဲ့ရမှာမလို့ ပထမဆုံးအကြိမ်တွေမှာတော့ performance ထိလိမ့်မယ်။
ဒီ model မှာ process တွေက kernel တခုတည်းကို share သုံးထားသလို သူတို့ကို support ဖို့အတွက်လည်း runtime တခု၊ OS တခုပဲ လိုတယ်။ ဒါက container ဘာလဲဆိုတာရဲ့ အဖြေပါပဲ။ container တွေက linux process တွေသာဖြစ်တဲ့အတွက် VM တွေနဲ့ယှဥ်ရင် ချက်ချင်းနီးပါး spin up လုပ်လို့ရလောက်အောင် မြန်သလို namespace isolation နဲ့ control group တွေသုံးပြီး ထိန်းချုပ်ထားတဲ့အတွက် plain old process တွေထက်လည်း အများကြီး ပို secure ဖြစ်တယ်။ ဒါကြောင့် container process တခုစရာမှာ တကယ်တမ်း အချိန်ယူရမှာဆိုလို့ လိုအပ်တဲ့ image ကို target node တွေဆီ လှမ်း fetch တဲ့၊ bootstrap လုပ်ရတဲ့ ကြာချိန်ပဲရှိမယ်။ ဒီတော့ container တွေနဲ့ပတ်သက်ရင် မလုပ်သင့်တဲ့ anti-pattern တွေကိုလည်း သိထားဖို့လိုတယ်။ mesos လို schedular တွေကို သုံးနေသေးတဲ့ data center ဆိုရင်တောင် marathon လိုမျိုး framework တွေသုံးပြီး containerized workload တွေနဲ့ အစားထိုးသင့်ပါပြီ။
container တွေက ဘာလဲဆိုတဲ့ မေးခွန်းအတွက် container ဆိုတာ application အလုပ်လုပ်ဖို့ dependency တွေအကုန်ပါပြီး it works on my laptop ဆိုတဲ့ပြဿနာကို ဖြေရှင်းပေးနိုင်တဲ့ docker လို technology ပါဆိုတဲ့ အဖြေထက် ပိုလေးနက် အသေးစိတ်ကျတဲ့အဖြေကို ဆာလောင်နေတဲ့သူတွေအတွက် ဒီ article ကလွတ်နေတဲ့ gap တွေကို ဖြည့်ပေးလိုက်နိုင်မယ်လို့ မျှော်လင့်ပါတယ်။