[{"data":1,"prerenderedAt":2296},["ShallowReactive",2],{"$Nehrp7Lchg":3},[4,959,1642],{"_path":5,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":9,"description":10,"layout":11,"series":12,"image":13,"keywords":18,"head":23,"body":37,"_type":953,"_id":954,"_source":955,"_file":956,"_stem":957,"_extension":958},"/blog/container-docker/00","container-docker",false,"","Container 基本概念介紹","近幾年，矽谷的各個科技大廠都快速的融入 Cloud Native 原生雲 的概念。就連新興 Startups 創業 公司的軟體開發也都是直接基於 Cloud Native 概念開始的。這代表著現代的工程師想要具有競爭力，無論工作是 Frontend、Backend、又或是 Operations，對 Cloud 雲端 相關內容有著基本的了解都會是大大的加分。而在 Cloud Native 的世界中，Container 容器 則扮演著至關重要的基本角色。","post","Container Technology",{"src":14,"alt":15,"width":16,"height":17},"hero.jpeg","hero",750,536,[19,20,21,22],"container","docker","tech","cloud",{"meta":24},[25,28,31,34],{"name":26,"content":27},"author","小貓貓工程師",{"name":29,"content":30},"read","7 min read",{"property":32,"content":33},"article:published_time","2020-12-13T00:00:00.000Z",{"property":35,"content":36},"article:modified_time","2023-03-20T00:00:00.000Z",{"type":38,"children":39,"toc":931},"root",[40,48,64,111,127,131,137,195,198,203,210,406,409,415,471,474,480,493,500,505,511,516,523,537,543,566,569,574,580,585,591,604,616,685,691,698,703,709,714,747,753,758,771,781,823,829,843,846,851,868,871,877,928],{"type":41,"tag":42,"props":43,"children":45},"element","h2",{"id":44},"前言",[46],{"type":47,"value":44},"text",{"type":41,"tag":49,"props":50,"children":51},"note-img",{},[52],{"type":41,"tag":53,"props":54,"children":55},"p",{},[56,62],{"type":41,"tag":57,"props":58,"children":61},"img",{"alt":59,"src":60},"雲端在軟體市場的占比在2020已經達到了30%以上，並且將來只會越來越高。","cloud.png",[],{"type":47,"value":63},"\n雲端在軟體市場的占比在2020已經達到了30%以上，並且將來只會越來越高。",{"type":41,"tag":53,"props":65,"children":66},{},[67,69,79,81,89,91,99,101,109],{"type":47,"value":68},"近幾年，矽谷的各個科技大廠都快速的融入 ",{"type":41,"tag":70,"props":71,"children":72},"em",{},[73],{"type":41,"tag":74,"props":75,"children":76},"strong",{},[77],{"type":47,"value":78},"Cloud Native 原生雲",{"type":47,"value":80}," 的概念。就連新興 ",{"type":41,"tag":74,"props":82,"children":83},{},[84],{"type":41,"tag":70,"props":85,"children":86},{},[87],{"type":47,"value":88},"Startups 創業",{"type":47,"value":90}," 公司的軟體開發也都是直接基於 Cloud Native 概念開始的。這代表著現代的工程師想要具有競爭力，無論工作是 Frontend、Backend、又或是 Operations，對 ",{"type":41,"tag":70,"props":92,"children":93},{},[94],{"type":41,"tag":74,"props":95,"children":96},{},[97],{"type":47,"value":98},"Cloud 雲端",{"type":47,"value":100}," 相關內容有著基本的了解都會是大大的加分。而在 Cloud Native 的世界中，",{"type":41,"tag":70,"props":102,"children":103},{},[104],{"type":41,"tag":74,"props":105,"children":106},{},[107],{"type":47,"value":108},"Container 容器",{"type":47,"value":110}," 則扮演著至關重要的基本角色。",{"type":41,"tag":53,"props":112,"children":113},{},[114,116,125],{"type":47,"value":115},"Cloud Native 的生態環境非常廣闊，不妨看看這個 ",{"type":41,"tag":117,"props":118,"children":122},"a",{"href":119,"rel":120},"https://landscape.cncf.io/?ref=blog.ewocker.com",[121],"nofollow",[123],{"type":47,"value":124},"CNCF 的生態圖",{"type":47,"value":126},"看看自己知道幾個又或是看過幾個。",{"type":41,"tag":128,"props":129,"children":130},"hr",{},[],{"type":41,"tag":42,"props":132,"children":134},{"id":133},"container-的優點",[135],{"type":47,"value":136},"Container 的優點",{"type":41,"tag":138,"props":139,"children":140},"ul",{},[141,156,169,182],{"type":41,"tag":142,"props":143,"children":144},"li",{},[145,150,154],{"type":41,"tag":74,"props":146,"children":147},{},[148],{"type":47,"value":149},"快速的交付和部署",{"type":41,"tag":151,"props":152,"children":153},"br",{},[],{"type":47,"value":155},"\nContainer 能夠一次建立或設定，可以在任意地方正常執行。",{"type":41,"tag":142,"props":157,"children":158},{},[159,164,167],{"type":41,"tag":74,"props":160,"children":161},{},[162],{"type":47,"value":163},"有效率的虛擬化",{"type":41,"tag":151,"props":165,"children":166},{},[],{"type":47,"value":168},"\nContainer 的執行是核心層級的虛擬化，比起 Virtual Machine 有更高的效能和效率。",{"type":41,"tag":142,"props":170,"children":171},{},[172,177,180],{"type":41,"tag":74,"props":173,"children":174},{},[175],{"type":47,"value":176},"輕鬆的遷移和擴展",{"type":41,"tag":151,"props":178,"children":179},{},[],{"type":47,"value":181},"\nContainer 幾乎可以在所有的平台上執行，從實體機器、虛擬機、雲端、到個人電腦等。 這種兼容性讓應用程式平台的遷移變的非常容易。並且因為容易運行，也使擴展變的相當輕鬆。",{"type":41,"tag":142,"props":183,"children":184},{},[185,190,193],{"type":41,"tag":74,"props":186,"children":187},{},[188],{"type":47,"value":189},"未來的趨勢",{"type":41,"tag":151,"props":191,"children":192},{},[],{"type":47,"value":194},"\nContainer 是未來的軟體的趨勢。以 Docker 為例，只需要簡單的修改就可以替代以往大量的更新工作。並且修改都是以增量的方式被分發和更新，使自動化並且有效率的管理得以實現。",{"type":41,"tag":128,"props":196,"children":197},{},[],{"type":41,"tag":42,"props":199,"children":201},{"id":200},"對比傳統以及虛擬機",[202],{"type":47,"value":200},{"type":41,"tag":53,"props":204,"children":205},{},[206],{"type":41,"tag":57,"props":207,"children":209},{"alt":8,"src":208},"vm.png",[],{"type":41,"tag":211,"props":212,"children":213},"table",{},[214,259],{"type":41,"tag":215,"props":216,"children":217},"thead",{},[218],{"type":41,"tag":219,"props":220,"children":221},"tr",{},[222,226,237,248],{"type":41,"tag":223,"props":224,"children":225},"th",{},[],{"type":41,"tag":223,"props":227,"children":228},{},[229],{"type":41,"tag":70,"props":230,"children":231},{},[232],{"type":41,"tag":74,"props":233,"children":234},{},[235],{"type":47,"value":236},"實體機",{"type":41,"tag":223,"props":238,"children":239},{},[240],{"type":41,"tag":70,"props":241,"children":242},{},[243],{"type":41,"tag":74,"props":244,"children":245},{},[246],{"type":47,"value":247},"虛擬機",{"type":41,"tag":223,"props":249,"children":250},{},[251],{"type":41,"tag":70,"props":252,"children":253},{},[254],{"type":41,"tag":74,"props":255,"children":256},{},[257],{"type":47,"value":258},"容器",{"type":41,"tag":260,"props":261,"children":262},"tbody",{},[263,293,321,349,378],{"type":41,"tag":219,"props":264,"children":265},{},[266,278,283,288],{"type":41,"tag":267,"props":268,"children":269},"td",{},[270],{"type":41,"tag":70,"props":271,"children":272},{},[273],{"type":41,"tag":74,"props":274,"children":275},{},[276],{"type":47,"value":277},"啟動速度",{"type":41,"tag":267,"props":279,"children":280},{},[281],{"type":47,"value":282},"慢",{"type":41,"tag":267,"props":284,"children":285},{},[286],{"type":47,"value":287},"數分鐘",{"type":41,"tag":267,"props":289,"children":290},{},[291],{"type":47,"value":292},"數秒",{"type":41,"tag":219,"props":294,"children":295},{},[296,307,312,316],{"type":41,"tag":267,"props":297,"children":298},{},[299],{"type":41,"tag":70,"props":300,"children":301},{},[302],{"type":41,"tag":74,"props":303,"children":304},{},[305],{"type":47,"value":306},"硬碟容量",{"type":41,"tag":267,"props":308,"children":309},{},[310],{"type":47,"value":311},"幾GB",{"type":41,"tag":267,"props":313,"children":314},{},[315],{"type":47,"value":311},{"type":41,"tag":267,"props":317,"children":318},{},[319],{"type":47,"value":320},"幾MB",{"type":41,"tag":219,"props":322,"children":323},{},[324,335,340,344],{"type":41,"tag":267,"props":325,"children":326},{},[327],{"type":41,"tag":70,"props":328,"children":329},{},[330],{"type":41,"tag":74,"props":331,"children":332},{},[333],{"type":47,"value":334},"效能",{"type":41,"tag":267,"props":336,"children":337},{},[338],{"type":47,"value":339},"快",{"type":41,"tag":267,"props":341,"children":342},{},[343],{"type":47,"value":282},{"type":41,"tag":267,"props":345,"children":346},{},[347],{"type":47,"value":348},"接近傳統",{"type":41,"tag":219,"props":350,"children":351},{},[352,363,368,373],{"type":41,"tag":267,"props":353,"children":354},{},[355],{"type":41,"tag":70,"props":356,"children":357},{},[358],{"type":41,"tag":74,"props":359,"children":360},{},[361],{"type":47,"value":362},"系統支援量",{"type":41,"tag":267,"props":364,"children":365},{},[366],{"type":47,"value":367},"極少量",{"type":41,"tag":267,"props":369,"children":370},{},[371],{"type":47,"value":372},"一般幾十個",{"type":41,"tag":267,"props":374,"children":375},{},[376],{"type":47,"value":377},"上千個容器",{"type":41,"tag":219,"props":379,"children":380},{},[381,388,394,400],{"type":41,"tag":267,"props":382,"children":383},{},[384],{"type":41,"tag":57,"props":385,"children":387},{"width":386},"200/",[],{"type":41,"tag":267,"props":389,"children":390},{},[391],{"type":41,"tag":57,"props":392,"children":393},{"width":386},[],{"type":41,"tag":267,"props":395,"children":396},{},[397],{"type":41,"tag":57,"props":398,"children":399},{"width":386},[],{"type":41,"tag":267,"props":401,"children":402},{},[403],{"type":41,"tag":57,"props":404,"children":405},{"width":386},[],{"type":41,"tag":128,"props":407,"children":408},{},[],{"type":41,"tag":42,"props":410,"children":412},{"id":411},"容器中的資源隔離-container-isolation",[413],{"type":47,"value":414},"容器中的資源隔離 Container Isolation",{"type":41,"tag":138,"props":416,"children":417},{},[418,438,451],{"type":41,"tag":142,"props":419,"children":420},{},[421,426,429,431,436],{"type":41,"tag":74,"props":422,"children":423},{},[424],{"type":47,"value":425},"Namespace 命名空間",{"type":41,"tag":151,"props":427,"children":428},{},[],{"type":47,"value":430},"\nNamespace 提供了一層隔離。Namespace 限制了 Container 所能看到的東西。當運行 Container 時，Docker 會為該 container 創建一組 namespace 如 ",{"type":41,"tag":74,"props":432,"children":433},{},[434],{"type":47,"value":435},"pid、net、mnt、uts、ipc",{"type":47,"value":437},"。",{"type":41,"tag":142,"props":439,"children":440},{},[441,446,449],{"type":41,"tag":74,"props":442,"children":443},{},[444],{"type":47,"value":445},"Cgroup 控制組",{"type":41,"tag":151,"props":447,"children":448},{},[],{"type":47,"value":450},"\nCgroup 將應用程序限制為一組特定的資源。它限制了應用程序可以使用多少資源。這使 Docker 引擎能夠將可用的硬件資源共享給容器，並有選擇地實施限制和約束。",{"type":41,"tag":142,"props":452,"children":453},{},[454,459,462,464,469],{"type":41,"tag":74,"props":455,"children":456},{},[457],{"type":47,"value":458},"Union Filesystem 聯合文件系統",{"type":41,"tag":151,"props":460,"children":461},{},[],{"type":47,"value":463},"\n通過創建層來運行的 ",{"type":41,"tag":74,"props":465,"children":466},{},[467],{"type":47,"value":468},"Union Filesystem",{"type":47,"value":470},"，Docker Image 由彼此分層的文件系統組成，使其非常輕便且快速。",{"type":41,"tag":128,"props":472,"children":473},{},[],{"type":41,"tag":42,"props":475,"children":477},{"id":476},"oci-cri-容器標準和運行時",[478],{"type":47,"value":479},"OCI & CRI 容器標準和運行時",{"type":41,"tag":49,"props":481,"children":482},{},[483],{"type":41,"tag":53,"props":484,"children":485},{},[486,491],{"type":41,"tag":57,"props":487,"children":490},{"alt":488,"src":489},"簡易的示意圖","ocr.png",[],{"type":47,"value":492},"\n簡易的示意圖",{"type":41,"tag":494,"props":495,"children":497},"h3",{"id":496},"open-container-initiative-oci",[498],{"type":47,"value":499},"Open Container Initiative (OCI)",{"type":41,"tag":53,"props":501,"children":502},{},[503],{"type":47,"value":504},"OCI 是容器開放標準。OCI 當前包含兩個規範：運行時規範（runtime-spec）和映像規範（image-spec）。Runtime-spec 規範定義瞭如何將 OCI Image 捆綁包作為 Container 運行。Image-spec 規範定義瞭如何創建 OCI Image，該 Image 包括 Image Manifest，文件系統（Layers）序列化和 Image Configuration。",{"type":41,"tag":494,"props":506,"children":508},{"id":507},"container-runtime-interface-cri",[509],{"type":47,"value":510},"Container Runtime Interface (CRI)",{"type":41,"tag":53,"props":512,"children":513},{},[514],{"type":47,"value":515},"CRI 容器運行時接口用於實現 OCI 的標準。一般被分為 High & Low Level 兩種 Runtime。",{"type":41,"tag":517,"props":518,"children":520},"h4",{"id":519},"low-level",[521],{"type":47,"value":522},"Low Level",{"type":41,"tag":53,"props":524,"children":525},{},[526,528,535],{"type":47,"value":527},"最常見的 low-level runtime 有 docker 的開源項目 ",{"type":41,"tag":117,"props":529,"children":532},{"href":530,"rel":531},"https://github.com/moby/moby?ref=blog.ewocker.com",[121],[533],{"type":47,"value":534},"moby",{"type":47,"value":536}," 所有的 runC。OCI runtime 是屬於 low-level 的 runtime，其功能主要用於創建和刪除 container。",{"type":41,"tag":517,"props":538,"children":540},{"id":539},"high-level",[541],{"type":47,"value":542},"High Level",{"type":41,"tag":53,"props":544,"children":545},{},[546,548,555,557,564],{"type":47,"value":547},"較為常見 CRI runtime的有由 docker 衍生除來的 ",{"type":41,"tag":117,"props":549,"children":552},{"href":550,"rel":551},"https://containerd.io/?ref=blog.ewocker.com",[121],[553],{"type":47,"value":554},"containerd",{"type":47,"value":556}," 和紅帽為了 Kubernetes 所開發的 ",{"type":41,"tag":117,"props":558,"children":561},{"href":559,"rel":560},"https://github.com/cri-o/cri-o?ref=blog.ewocker.com",[121],[562],{"type":47,"value":563},"CRI-O",{"type":47,"value":565},"。其功能包括 Container 生命週期管理、資源管理等等。",{"type":41,"tag":128,"props":567,"children":568},{},[],{"type":41,"tag":42,"props":570,"children":571},{"id":20},[572],{"type":47,"value":573},"Docker",{"type":41,"tag":494,"props":575,"children":577},{"id":576},"你為什麼需要認識-docker",[578],{"type":47,"value":579},"你為什麼需要認識 Docker ?",{"type":41,"tag":53,"props":581,"children":582},{},[583],{"type":47,"value":584},"說到 Container 最常被提及的就是 Docker 了。Docker 是 2013 年 DotCloud 公司的開源專案，公司在項目成功之後改名為 Docker Inc。簡而言之 Docker 已經算是 Container 的代名詞了，所以這篇文章的多數內容將會以 Docker 帶入 Container 的相關話題。",{"type":41,"tag":494,"props":586,"children":588},{"id":587},"架構和組件-docker-architecture-components",[589],{"type":47,"value":590},"架構和組件 Docker Architecture & Components",{"type":41,"tag":49,"props":592,"children":593},{},[594],{"type":41,"tag":53,"props":595,"children":596},{},[597,602],{"type":41,"tag":57,"props":598,"children":601},{"alt":599,"src":600},"Docker 架構簡圖","docker.png",[],{"type":47,"value":603},"\nDocker 架構簡圖",{"type":41,"tag":53,"props":605,"children":606},{},[607,609,614],{"type":47,"value":608},"Docker 體系結構包含 ",{"type":41,"tag":74,"props":610,"children":611},{},[612],{"type":47,"value":613},"五個",{"type":47,"value":615},"主要組件：",{"type":41,"tag":138,"props":617,"children":618},{},[619,632,645,658,671],{"type":41,"tag":142,"props":620,"children":621},{},[622,627,630],{"type":41,"tag":74,"props":623,"children":624},{},[625],{"type":47,"value":626},"Docker Daemon",{"type":41,"tag":151,"props":628,"children":629},{},[],{"type":47,"value":631},"\n監聽 Docker API 請求並管理 Docker 對象，例如 Image、Container、Network、Volume。",{"type":41,"tag":142,"props":633,"children":634},{},[635,640,643],{"type":41,"tag":74,"props":636,"children":637},{},[638],{"type":47,"value":639},"Docker Client",{"type":41,"tag":151,"props":641,"children":642},{},[],{"type":47,"value":644},"\n使用戶可以與 Docker 進行交互。Docker Client 提供了命令界面（CLI），允許用戶運行和停止對 Docker Daemon 的應用程序命令。",{"type":41,"tag":142,"props":646,"children":647},{},[648,653,656],{"type":41,"tag":74,"props":649,"children":650},{},[651],{"type":47,"value":652},"Docker Host",{"type":41,"tag":151,"props":654,"children":655},{},[],{"type":47,"value":657},"\n提供了執行和運行應用程序的完整環境。它由 Docker Daemon、 Image、Container、Network、Volume。",{"type":41,"tag":142,"props":659,"children":660},{},[661,666,669],{"type":41,"tag":74,"props":662,"children":663},{},[664],{"type":47,"value":665},"Docker Registry",{"type":41,"tag":151,"props":667,"children":668},{},[],{"type":47,"value":670},"\n用於存儲 Docker Image。Docker Hub 是任何人都可以使用的公共 Registry，並且 Docker 默認配置為在 Docker Hub 上使用 Image。可以在上面運行自己的 Registry。",{"type":41,"tag":142,"props":672,"children":673},{},[674,678,680,683],{"type":41,"tag":74,"props":675,"children":676},{},[677],{"type":47,"value":573},{"type":47,"value":679}," Image**",{"type":41,"tag":151,"props":681,"children":682},{},[],{"type":47,"value":684},"\nImage 是 read-only 模板，可通過以 Dockerfile 編寫的一組指令構建。Docker Image 定義了打包的應用程序及其 dependencies 啟動時所需運行的進程。",{"type":41,"tag":494,"props":686,"children":688},{"id":687},"dockerfile",[689],{"type":47,"value":690},"Dockerfile",{"type":41,"tag":53,"props":692,"children":693},{},[694],{"type":41,"tag":57,"props":695,"children":697},{"alt":8,"src":696},"dockerfile.png",[],{"type":41,"tag":53,"props":699,"children":700},{},[701],{"type":47,"value":702},"Dockerfile 是一個用來構建鏡像的文件，內容包含了一條條構建 Docker Image 所需的指令和說明。用於創建 Docker Image 所編寫的一組指令構建。",{"type":41,"tag":494,"props":704,"children":706},{"id":705},"docker-command-指令",[707],{"type":47,"value":708},"Docker Command 指令",{"type":41,"tag":53,"props":710,"children":711},{},[712],{"type":47,"value":713},"Docker 是一個能夠全套管理 Container 的工具，其中最常使用的功能包括：",{"type":41,"tag":138,"props":715,"children":716},{},[717,727,737],{"type":41,"tag":142,"props":718,"children":719},{},[720,722,725],{"type":47,"value":721},"docker build ",{"type":41,"tag":57,"props":723,"children":724},{},[],{"type":47,"value":726}," - 創建 Image",{"type":41,"tag":142,"props":728,"children":729},{},[730,732,735],{"type":47,"value":731},"docker pull/push ",{"type":41,"tag":57,"props":733,"children":734},{},[],{"type":47,"value":736}," - 對倉庫 Registry 推送和抓取 Image",{"type":41,"tag":142,"props":738,"children":739},{},[740,742,745],{"type":47,"value":741},"docker run ",{"type":41,"tag":57,"props":743,"children":744},{},[],{"type":47,"value":746}," - 根據 Image 運行 Container",{"type":41,"tag":494,"props":748,"children":750},{"id":749},"container-engine",[751],{"type":47,"value":752},"Container Engine",{"type":41,"tag":53,"props":754,"children":755},{},[756],{"type":47,"value":757},"Docker 並不屬於 OCI 或 CRI，但 Docker 的使用包括 Containerd (CRI) 和 runC (OCI)。像 Docker 這樣的全套 Container 管理軟體被稱作 Container Engine，而 Docker Inc 公司也將其商品稱為 Docker Engine。",{"type":41,"tag":49,"props":759,"children":760},{},[761],{"type":41,"tag":53,"props":762,"children":763},{},[764,769],{"type":41,"tag":57,"props":765,"children":768},{"alt":766,"src":767},"Docker Engine 簡單示意圖","engine.png",[],{"type":47,"value":770},"\nDocker Engine 簡單示意圖",{"type":41,"tag":53,"props":772,"children":773},{},[774,776,779],{"type":47,"value":775},"Docker 使用 Client-Server 架構。Docker Client 端與 Docker Daemon 進程進行溝通，Docker Daemon 用於構建、運行和分發 Docker Container。Docker Client 和 Docker Daemon 使用 REST API，通過 UNIX Socket 或網絡接口進行通信。",{"type":41,"tag":151,"props":777,"children":778},{},[],{"type":47,"value":780},"\nDocker Engine中包含三個組件 :",{"type":41,"tag":138,"props":782,"children":783},{},[784,797,810],{"type":41,"tag":142,"props":785,"children":786},{},[787,792,795],{"type":41,"tag":74,"props":788,"children":789},{},[790],{"type":47,"value":791},"Server",{"type":41,"tag":151,"props":793,"children":794},{},[],{"type":47,"value":796},"\n這是名為 dockerd 的 docker daemon。它可以創建和管理 docker 映像、容器、網絡等。",{"type":41,"tag":142,"props":798,"children":799},{},[800,805,808],{"type":41,"tag":74,"props":801,"children":802},{},[803],{"type":47,"value":804},"Rest API",{"type":41,"tag":151,"props":806,"children":807},{},[],{"type":47,"value":809},"\n用於指示 docker daemon 任務。",{"type":41,"tag":142,"props":811,"children":812},{},[813,818,821],{"type":41,"tag":74,"props":814,"children":815},{},[816],{"type":47,"value":817},"Command Line Interface CLI",{"type":41,"tag":151,"props":819,"children":820},{},[],{"type":47,"value":822},"\n用於輸入 docker 命令的客戶端。",{"type":41,"tag":494,"props":824,"children":826},{"id":825},"container-不是只有-docker",[827],{"type":47,"value":828},"Container 不是只有 Docker",{"type":41,"tag":53,"props":830,"children":831},{},[832,834,841],{"type":47,"value":833},"雖說 Docker 幾乎已經是 Container 的代名詞了，但除了 Docker 以外還有許多 Container 工具。可以參考 ",{"type":41,"tag":117,"props":835,"children":838},{"href":836,"rel":837},"https://github.com/containers?ref=blog.ewocker.com",[121],[839],{"type":47,"value":840},"Container Tool Project",{"type":47,"value":842}," 中像是 buildah + skopeo + podman 額外再加上 cri-o 就幾乎完全取代了 Docker 了所有功能了。",{"type":41,"tag":128,"props":844,"children":845},{},[],{"type":41,"tag":42,"props":847,"children":849},{"id":848},"總結",[850],{"type":47,"value":848},{"type":41,"tag":53,"props":852,"children":853},{},[854,856,866],{"type":47,"value":855},"Container 是現代軟體的趨勢，而在 Container 之上還有像是 Kubernetes、Docker-Swarm、Mesos 這些 Container Orchestration Tool 用於自動化管理 Container 的平台。目前發展最為迅速及成熟的無非就是 Kubernetes 了，可以參考我另一篇文章 ",{"type":41,"tag":117,"props":857,"children":860},{"href":858,"rel":859},"https://blog.ewocker.com/intro-to-k8s/",[121],[861],{"type":41,"tag":74,"props":862,"children":863},{},[864],{"type":47,"value":865},"Kubernetes 基本介紹",{"type":47,"value":867}," 來入門。",{"type":41,"tag":128,"props":869,"children":870},{},[],{"type":41,"tag":42,"props":872,"children":874},{"id":873},"reference",[875],{"type":47,"value":876},"Reference",{"type":41,"tag":138,"props":878,"children":879},{},[880,890,900,910,918],{"type":41,"tag":142,"props":881,"children":882},{},[883],{"type":41,"tag":117,"props":884,"children":887},{"href":885,"rel":886},"https://www.capitalone.com/tech/cloud/container-runtime/?ref=blog.ewocker.com",[121],[888],{"type":47,"value":889},"https://www.capitalone.com/tech/cloud/container-runtime/",{"type":41,"tag":142,"props":891,"children":892},{},[893],{"type":41,"tag":117,"props":894,"children":897},{"href":895,"rel":896},"https://philipzheng.gitbook.io/docker_practice/?ref=blog.ewocker.com",[121],[898],{"type":47,"value":899},"https://philipzheng.gitbook.io/docker_practice/",{"type":41,"tag":142,"props":901,"children":902},{},[903],{"type":41,"tag":117,"props":904,"children":907},{"href":905,"rel":906},"https://k21academy.com/docker-kubernetes/docker-architecture-docker-engine-components-container-lifecycle/?ref=blog.ewocker.com",[121],[908],{"type":47,"value":909},"https://k21academy.com/docker-kubernetes/docker-architecture-docker-engine-components-container-lifecycle/",{"type":41,"tag":142,"props":911,"children":912},{},[913],{"type":41,"tag":117,"props":914,"children":916},{"href":905,"rel":915},[121],[917],{"type":47,"value":909},{"type":41,"tag":142,"props":919,"children":920},{},[921],{"type":41,"tag":117,"props":922,"children":925},{"href":923,"rel":924},"https://medium.com/@avijitsarkar123/docker-and-oci-runtimes-a9c23a5646d6?ref=blog.ewocker.com",[121],[926],{"type":47,"value":927},"https://medium.com/@avijitsarkar123/docker-and-oci-runtimes-a9c23a5646d6",{"type":41,"tag":128,"props":929,"children":930},{},[],{"title":8,"searchDepth":932,"depth":932,"links":933},2,[934,935,936,937,938,943,951,952],{"id":44,"depth":932,"text":44},{"id":133,"depth":932,"text":136},{"id":200,"depth":932,"text":200},{"id":411,"depth":932,"text":414},{"id":476,"depth":932,"text":479,"children":939},[940,942],{"id":496,"depth":941,"text":499},3,{"id":507,"depth":941,"text":510},{"id":20,"depth":932,"text":573,"children":944},[945,946,947,948,949,950],{"id":576,"depth":941,"text":579},{"id":587,"depth":941,"text":590},{"id":687,"depth":941,"text":690},{"id":705,"depth":941,"text":708},{"id":749,"depth":941,"text":752},{"id":825,"depth":941,"text":828},{"id":848,"depth":932,"text":848},{"id":873,"depth":932,"text":876},"markdown","content:1.blog:014.container-docker:00.md","content","1.blog/014.container-docker/00.md","1.blog/014.container-docker/00","md",{"_path":960,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":961,"description":962,"layout":11,"series":12,"image":963,"keywords":965,"head":966,"body":974,"_type":953,"_id":1639,"_source":955,"_file":1640,"_stem":1641,"_extension":958},"/blog/container-docker/01","Docker 入門實作","開始之前如果對 Container 和 Docker 還不太了解，推薦先閱讀前面的文章。本篇將會簡單的講述 docker 常見的開發流程，主要是給對 docker 還不太了解的讀者參考。",{"src":964,"alt":15,"width":16,"height":17},"hero.png",[19,20,21,22],{"meta":967},[968,969,971,973],{"name":26,"content":27},{"name":29,"content":970},"5 min read",{"property":32,"content":972},"2021-01-15T00:00:00.000Z",{"property":35,"content":36},{"type":38,"children":975,"toc":1629},[976,993,999,1013,1016,1021,1028,1033,1069,1074,1082,1087,1092,1105,1117,1122,1128,1133,1143,1233,1237,1250,1349,1355,1360,1365,1477,1483,1488,1508,1613,1616,1621,1626],{"type":41,"tag":977,"props":978,"children":979},"note",{},[980],{"type":41,"tag":53,"props":981,"children":982},{},[983,985,991],{"type":47,"value":984},"開始之前如果對 Container 和 Docker 還不太了解，推薦先閱讀前面的文章 ",{"type":41,"tag":117,"props":986,"children":989},{"href":987,"rel":988},"https://blog.ewocker.com/blog/container-docker/00",[121],[990],{"type":47,"value":9},{"type":47,"value":992},"！",{"type":41,"tag":42,"props":994,"children":996},{"id":995},"安裝指南-installation-guide",[997],{"type":47,"value":998},"安裝指南 Installation Guide",{"type":41,"tag":53,"props":1000,"children":1001},{},[1002,1004,1011],{"type":47,"value":1003},"在開始之前首先需要安裝 Docker。只要按著文檔走，安裝 Docker 其實非常簡單。如果是 Linux OS 的話我個人推薦 ",{"type":41,"tag":117,"props":1005,"children":1008},{"href":1006,"rel":1007},"https://www.digitalocean.com/community/tutorials?q=install+docker&ref=blog.ewocker.com",[121],[1009],{"type":47,"value":1010},"Digital Ocean 的文檔",{"type":47,"value":1012},"，寫得很詳細而且各式版本都有。",{"type":41,"tag":128,"props":1014,"children":1015},{},[],{"type":41,"tag":42,"props":1017,"children":1019},{"id":1018},"開發流程",[1020],{"type":47,"value":1018},{"type":41,"tag":53,"props":1022,"children":1023},{},[1024],{"type":41,"tag":57,"props":1025,"children":1027},{"alt":8,"src":1026},"flow.png",[],{"type":41,"tag":53,"props":1029,"children":1030},{},[1031],{"type":47,"value":1032},"在使用 Docker 時，最常見的指令 Command 有以下幾種：",{"type":41,"tag":138,"props":1034,"children":1035},{},[1036,1046,1057],{"type":41,"tag":142,"props":1037,"children":1038},{},[1039],{"type":41,"tag":1040,"props":1041,"children":1043},"code",{"className":1042},[],[1044],{"type":47,"value":1045},"docker build",{"type":41,"tag":142,"props":1047,"children":1048},{},[1049,1051],{"type":47,"value":1050},"創建 Image- ",{"type":41,"tag":1040,"props":1052,"children":1054},{"className":1053},[],[1055],{"type":47,"value":1056},"docker pull/push \u003Cimage>",{"type":41,"tag":142,"props":1058,"children":1059},{},[1060,1062,1068],{"type":47,"value":1061},"對倉庫 Registry 推送和抓取 Image- ",{"type":41,"tag":1040,"props":1063,"children":1065},{"className":1064},[],[1066],{"type":47,"value":1067},"docker run \u003Cimage>",{"type":47,"value":746},{"type":41,"tag":53,"props":1070,"children":1071},{},[1072],{"type":47,"value":1073},"而創建 Image 則是需要撰寫一個 Dockerfile 文檔。寫 Dockerfile 有很多 Best Practices，比如所建出的 Image 大小、以及 Security 相關等等。這裡我們暫時先不考慮太多，以創建簡單的 Image 為重點。",{"type":41,"tag":977,"props":1075,"children":1076},{},[1077],{"type":41,"tag":53,"props":1078,"children":1079},{},[1080],{"type":47,"value":1081},"💡  下面 asciinema 影片裡的所有指令直接 highlight 用於複製貼上喔！",{"type":41,"tag":53,"props":1083,"children":1084},{},[1085],{"type":47,"value":1086},"下面我們會創建一個簡易的軟體並包裝成 Container，跑一些常用的除錯指令，然後上傳至 Registry 等等。",{"type":41,"tag":494,"props":1088,"children":1090},{"id":1089},"創建軟體",[1091],{"type":47,"value":1089},{"type":41,"tag":53,"props":1093,"children":1094},{},[1095,1097,1103],{"type":47,"value":1096},"首先我們來寫一個簡單的 ",{"type":41,"tag":1040,"props":1098,"children":1100},{"className":1099},[],[1101],{"type":47,"value":1102},"count.sh",{"type":47,"value":1104},"，它的功用是從一數到三。如果對 Shell 語法不太熟悉也不需要擔心，這只是演示用的而已。",{"type":41,"tag":1106,"props":1107,"children":1112},"pre",{"className":1108,"code":1110,"language":1111,"meta":8},[1109],"language-bash","#!/bin/sh\n\n# Purpose of this script is to count to 3\n\ni=\"1\"\n\nwhile [ $i -lt 4 ]\ndo\n  echo $i\n  i=$(($i + 1))\n  sleep 1\ndone\n","bash",[1113],{"type":41,"tag":1040,"props":1114,"children":1115},{"__ignoreMap":8},[1116],{"type":47,"value":1110},{"type":41,"tag":1118,"props":1119,"children":1121},"asciinema",{"id":1120},"381988",[],{"type":41,"tag":494,"props":1123,"children":1125},{"id":1124},"創建-image",[1126],{"type":47,"value":1127},"創建 Image",{"type":41,"tag":53,"props":1129,"children":1130},{},[1131],{"type":47,"value":1132},"接下來我們將寫好的 application 包裝成映像檔 Image。簡單解釋一下:",{"type":41,"tag":1106,"props":1134,"children":1138},{"className":1135,"code":1137,"language":687,"meta":8},[1136],"language-dockerfile","FROM alpine:3\n\nCOPY count.sh .\n\nCMD [\"./count.sh\"]\n",[1139],{"type":41,"tag":1040,"props":1140,"children":1141},{"__ignoreMap":8},[1142],{"type":47,"value":1137},{"type":41,"tag":138,"props":1144,"children":1145},{},[1146,1190,1211],{"type":41,"tag":142,"props":1147,"children":1148},{},[1149,1155,1158,1160,1166,1168,1174,1176,1179,1181,1189],{"type":41,"tag":1040,"props":1150,"children":1152},{"className":1151},[],[1153],{"type":47,"value":1154},"FROM alpine:3",{"type":41,"tag":151,"props":1156,"children":1157},{},[],{"type":47,"value":1159},"\n是指使用 Linux 發行版 alpine 的最新版本 3，這會從 Docker Hub 中抓取官方上傳好的 Linux alpine 發行版下來，相當於運行了 ",{"type":41,"tag":1040,"props":1161,"children":1163},{"className":1162},[],[1164],{"type":47,"value":1165},"docker pull alpine:3",{"type":47,"value":1167}," ，如果在本底已有 ",{"type":41,"tag":1040,"props":1169,"children":1171},{"className":1170},[],[1172],{"type":47,"value":1173},"alpine:3",{"type":47,"value":1175}," 的 Image 則會直接取用。",{"type":41,"tag":151,"props":1177,"children":1178},{},[],{"type":47,"value":1180},"\n通常我們會稱此為 ",{"type":41,"tag":74,"props":1182,"children":1183},{},[1184],{"type":41,"tag":70,"props":1185,"children":1186},{},[1187],{"type":47,"value":1188},"Base Image",{"type":47,"value":437},{"type":41,"tag":142,"props":1191,"children":1192},{},[1193,1199,1202,1204,1209],{"type":41,"tag":1040,"props":1194,"children":1196},{"className":1195},[],[1197],{"type":47,"value":1198},"COPY count.sh",{"type":41,"tag":151,"props":1200,"children":1201},{},[],{"type":47,"value":1203},"\n是指將上面寫好的 ",{"type":41,"tag":1040,"props":1205,"children":1207},{"className":1206},[],[1208],{"type":47,"value":1102},{"type":47,"value":1210}," 複製進入 Image 裡面。",{"type":41,"tag":142,"props":1212,"children":1213},{},[1214,1220,1223,1225,1231],{"type":41,"tag":1040,"props":1215,"children":1217},{"className":1216},[],[1218],{"type":47,"value":1219},"CMD [\"./count.sh\"]",{"type":41,"tag":151,"props":1221,"children":1222},{},[],{"type":47,"value":1224},"\n則是只默認運行 ",{"type":41,"tag":1040,"props":1226,"children":1228},{"className":1227},[],[1229],{"type":47,"value":1230},"./count.sh",{"type":47,"value":1232}," 。",{"type":41,"tag":1118,"props":1234,"children":1236},{"id":1235},"382209",[],{"type":41,"tag":53,"props":1238,"children":1239},{},[1240,1242,1248],{"type":47,"value":1241},"這裡使用了 ",{"type":41,"tag":1040,"props":1243,"children":1245},{"className":1244},[],[1246],{"type":47,"value":1247},"docker build -t ewocker/count .",{"type":47,"value":1249}," 來創建 Image，簡單解釋一下：",{"type":41,"tag":138,"props":1251,"children":1252},{},[1253,1310],{"type":41,"tag":142,"props":1254,"children":1255},{},[1256,1262,1268,1270,1276,1278,1284,1286,1292,1294,1300,1302,1308],{"type":41,"tag":1040,"props":1257,"children":1259},{"className":1258},[],[1260],{"type":47,"value":1261},"-t ewocker/count .",{"type":41,"tag":1040,"props":1263,"children":1265},{"className":1264},[],[1266],{"type":47,"value":1267},"-t => --tag",{"type":47,"value":1269}," 是指命名 Image 的全名為 ",{"type":41,"tag":1040,"props":1271,"children":1273},{"className":1272},[],[1274],{"type":47,"value":1275},"ewocker/count",{"type":47,"value":1277}," ， ",{"type":41,"tag":1040,"props":1279,"children":1281},{"className":1280},[],[1282],{"type":47,"value":1283},"ewocker",{"type":47,"value":1285}," 是我在 Docker Hub 上面的 Repository ID， ",{"type":41,"tag":1040,"props":1287,"children":1289},{"className":1288},[],[1290],{"type":47,"value":1291},"count",{"type":47,"value":1293}," 則是 Image 的名字。最後的 ",{"type":41,"tag":1040,"props":1295,"children":1297},{"className":1296},[],[1298],{"type":47,"value":1299},".",{"type":47,"value":1301}," 則是指在目前路徑 ",{"type":41,"tag":1040,"props":1303,"children":1305},{"className":1304},[],[1306],{"type":47,"value":1307},"~/Desktop/docker",{"type":47,"value":1309}," 運行指令。",{"type":41,"tag":142,"props":1311,"children":1312},{},[1313,1315,1321,1324,1326,1332,1334,1340,1342,1348],{"type":47,"value":1314},"最後創建出的 Image 寫到 ",{"type":41,"tag":1040,"props":1316,"children":1318},{"className":1317},[],[1319],{"type":47,"value":1320},"Successfully tagged ewocker/count:latest",{"type":41,"tag":151,"props":1322,"children":1323},{},[],{"type":47,"value":1325},"\n代表 Image 的全名 tag 為 ",{"type":41,"tag":1040,"props":1327,"children":1329},{"className":1328},[],[1330],{"type":47,"value":1331},"ewocker/count:latest",{"type":47,"value":1333}," ，因為在創建時沒有給予版本，所以默認會在後面加上 ",{"type":41,"tag":1040,"props":1335,"children":1337},{"className":1336},[],[1338],{"type":47,"value":1339},"latest",{"type":47,"value":1341},"。簡單來說 Image tag 的組成是 ",{"type":41,"tag":1040,"props":1343,"children":1345},{"className":1344},[],[1346],{"type":47,"value":1347},"\u003Cregistry path>/\u003Cname>:\u003Cversion>",{"type":47,"value":437},{"type":41,"tag":494,"props":1350,"children":1352},{"id":1351},"運行-container",[1353],{"type":47,"value":1354},"運行 Container",{"type":41,"tag":53,"props":1356,"children":1357},{},[1358],{"type":47,"value":1359},"在創建好 Image 後我們可以簡易的運行 Container。",{"type":41,"tag":53,"props":1361,"children":1362},{},[1363],{"type":47,"value":1364},"這裡使用了兩種方式來運行 Container，簡單解釋一下：",{"type":41,"tag":138,"props":1366,"children":1367},{},[1368,1392],{"type":41,"tag":142,"props":1369,"children":1370},{},[1371,1377,1383,1385,1390],{"type":41,"tag":1040,"props":1372,"children":1374},{"className":1373},[],[1375],{"type":47,"value":1376},"docker run --rm ewocker/count",{"type":41,"tag":1040,"props":1378,"children":1380},{"className":1379},[],[1381],{"type":47,"value":1382},"--rm",{"type":47,"value":1384}," 是指運行結束後將 container 移除。",{"type":41,"tag":1040,"props":1386,"children":1388},{"className":1387},[],[1389],{"type":47,"value":1275},{"type":47,"value":1391}," 則是指根據這個 Image 來運行 container。",{"type":41,"tag":142,"props":1393,"children":1394},{},[1395,1401,1404,1406,1412,1414,1420,1422,1428,1429,1432,1434,1440,1442,1447,1448,1451,1453,1459,1461,1467,1469,1475],{"type":41,"tag":1040,"props":1396,"children":1398},{"className":1397},[],[1399],{"type":47,"value":1400},"docker run --rm -it ewocker/count /bin/sh",{"type":41,"tag":151,"props":1402,"children":1403},{},[],{"type":47,"value":1405},"\n這是比較常見的 debug 用指令，",{"type":41,"tag":1040,"props":1407,"children":1409},{"className":1408},[],[1410],{"type":47,"value":1411},"-it",{"type":47,"value":1413}," 分別是 ",{"type":41,"tag":1040,"props":1415,"children":1417},{"className":1416},[],[1418],{"type":47,"value":1419},"-i => --interactive",{"type":47,"value":1421}," 和 ",{"type":41,"tag":1040,"props":1423,"children":1425},{"className":1424},[],[1426],{"type":47,"value":1427},"-t => --tty",{"type":47,"value":1232},{"type":41,"tag":151,"props":1430,"children":1431},{},[],{"type":47,"value":1433},"\n簡單的說就是在運行 container 後連接一個 STDIN Shell，並用 ",{"type":41,"tag":1040,"props":1435,"children":1437},{"className":1436},[],[1438],{"type":47,"value":1439},"/bin/sh",{"type":47,"value":1441}," 覆寫默認的 ",{"type":41,"tag":1040,"props":1443,"children":1445},{"className":1444},[],[1446],{"type":47,"value":1230},{"type":47,"value":1232},{"type":41,"tag":151,"props":1449,"children":1450},{},[],{"type":47,"value":1452},"\n在運行後本來應該數完 ",{"type":41,"tag":1040,"props":1454,"children":1456},{"className":1455},[],[1457],{"type":47,"value":1458},"1->3",{"type":47,"value":1460}," 就結束的 container 因為並沒有運行默認的 shellscript 而是運行了一個 ",{"type":41,"tag":1040,"props":1462,"children":1464},{"className":1463},[],[1465],{"type":47,"value":1466},"shell",{"type":47,"value":1468}," ，所以可以在 Container 裡面運行任何指令用於 debug。簡單來說就是進入到一個 Container 裡面，在上面影片裡我們在進入到 container 裡面後運行 ",{"type":41,"tag":1040,"props":1470,"children":1472},{"className":1471},[],[1473],{"type":47,"value":1474},"ls",{"type":47,"value":1476}," 發現 output 和在本地運行時完全不同，因為我們是在一個運行中的 linux alpine container 中運行的指令。",{"type":41,"tag":494,"props":1478,"children":1480},{"id":1479},"使用-registry",[1481],{"type":47,"value":1482},"使用 Registry",{"type":41,"tag":53,"props":1484,"children":1485},{},[1486],{"type":47,"value":1487},"最後我們演示如何使用 registry。",{"type":41,"tag":53,"props":1489,"children":1490},{},[1491,1493,1499,1500,1506],{"type":47,"value":1492},"這裡主要使用 ",{"type":41,"tag":1040,"props":1494,"children":1496},{"className":1495},[],[1497],{"type":47,"value":1498},"docker push ewocker/count",{"type":47,"value":1421},{"type":41,"tag":1040,"props":1501,"children":1503},{"className":1502},[],[1504],{"type":47,"value":1505},"docker pull ewocker/count",{"type":47,"value":1507}," ：",{"type":41,"tag":138,"props":1509,"children":1510},{},[1511,1584],{"type":41,"tag":142,"props":1512,"children":1513},{},[1514,1519,1522,1524,1529,1531,1538,1540,1546,1548,1551,1553,1559,1561,1567,1569,1575,1577,1583],{"type":41,"tag":1040,"props":1515,"children":1517},{"className":1516},[],[1518],{"type":47,"value":1498},{"type":41,"tag":151,"props":1520,"children":1521},{},[],{"type":47,"value":1523},"\n首先我們將 Image ",{"type":41,"tag":1040,"props":1525,"children":1527},{"className":1526},[],[1528],{"type":47,"value":1331},{"type":47,"value":1530}," 上傳至 registry，在那之前我們必須要有一個 Docker Hub 的帳號，如果還沒有創辦可以在這個 ",{"type":41,"tag":117,"props":1532,"children":1535},{"href":1533,"rel":1534},"https://hub.docker.com/?ref=blog.ewocker.com",[121],[1536],{"type":47,"value":1537},"link",{"type":47,"value":1539}," 上先創辦一個。有了帳號後要先運行 ",{"type":41,"tag":1040,"props":1541,"children":1543},{"className":1542},[],[1544],{"type":47,"value":1545},"docker login",{"type":47,"value":1547}," 來登入帳號。",{"type":41,"tag":151,"props":1549,"children":1550},{},[],{"type":47,"value":1552},"\n在影片中我們看到 ",{"type":41,"tag":1040,"props":1554,"children":1556},{"className":1555},[],[1557],{"type":47,"value":1558},"The push refers to repository [docker.io/ewocker/count]",{"type":47,"value":1560}," 。在使用 ",{"type":41,"tag":1040,"props":1562,"children":1564},{"className":1563},[],[1565],{"type":47,"value":1566},"docker push",{"type":47,"value":1568}," 時默認 registry 會使用 Docker Hub ",{"type":41,"tag":1040,"props":1570,"children":1572},{"className":1571},[],[1573],{"type":47,"value":1574},"docker.io",{"type":47,"value":1576}," ，而全 repository path 則變成 ",{"type":41,"tag":1040,"props":1578,"children":1580},{"className":1579},[],[1581],{"type":47,"value":1582},"docker.io/ewocker/count",{"type":47,"value":437},{"type":41,"tag":142,"props":1585,"children":1586},{},[1587,1592,1595,1597,1603,1605,1611],{"type":41,"tag":1040,"props":1588,"children":1590},{"className":1589},[],[1591],{"type":47,"value":1505},{"type":41,"tag":151,"props":1593,"children":1594},{},[],{"type":47,"value":1596},"\n在上傳之後，我們先用 ",{"type":41,"tag":1040,"props":1598,"children":1600},{"className":1599},[],[1601],{"type":47,"value":1602},"docker image rm ewocker/count",{"type":47,"value":1604}," 將本地的 Image 移除來模擬在另一台電腦上原先並沒有 Image 的情況。接著運行 pull 來將 Image 抓取下來，而在那之後運行 ",{"type":41,"tag":1040,"props":1606,"children":1608},{"className":1607},[],[1609],{"type":47,"value":1610},"docker image ls | grep ewocker/count",{"type":47,"value":1612}," 來表示 Image 已經被抓取到本地了。",{"type":41,"tag":128,"props":1614,"children":1615},{},[],{"type":41,"tag":42,"props":1617,"children":1619},{"id":1618},"結語",[1620],{"type":47,"value":1618},{"type":41,"tag":53,"props":1622,"children":1623},{},[1624],{"type":47,"value":1625},"本篇非常簡單的講述了 docker 常見的開發流程，說到 container 雖然不有 docker，但最常見和必須知道的目前還是非 docker 莫屬。",{"type":41,"tag":128,"props":1627,"children":1628},{},[],{"title":8,"searchDepth":932,"depth":932,"links":1630},[1631,1632,1638],{"id":995,"depth":932,"text":998},{"id":1018,"depth":932,"text":1018,"children":1633},[1634,1635,1636,1637],{"id":1089,"depth":941,"text":1089},{"id":1124,"depth":941,"text":1127},{"id":1351,"depth":941,"text":1354},{"id":1479,"depth":941,"text":1482},{"id":1618,"depth":932,"text":1618},"content:1.blog:014.container-docker:01.md","1.blog/014.container-docker/01.md","1.blog/014.container-docker/01",{"_path":1643,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":961,"description":1644,"layout":11,"series":12,"image":1645,"keywords":1646,"head":1647,"body":1655,"_type":953,"_id":2293,"_source":955,"_file":2294,"_stem":2295,"_extension":958},"/blog/container-docker/02","Docker 的 Slogan 就是快速的 build ship run。而其中 building 和 shipping 大概是最花時間的步驟了！為什麼明明一個簡單的 Image 卻有 500MB 的大小呢呢？今天就來看看創建一個優質 Docker Image 的各種方法。",{"src":964,"alt":15,"width":16,"height":17},[19,20,21,22],{"meta":1648},[1649,1650,1652,1654],{"name":26,"content":27},{"name":29,"content":1651},"9 min read",{"property":32,"content":1653},"2021-01-27T00:00:00.000Z",{"property":35,"content":36},{"type":38,"children":1656,"toc":2278},[1657,1665,1671,1705,1712,1724,1738,1743,1748,1759,1764,1767,1773,1778,1798,1812,1819,1824,1830,1838,1846,1890,1910,1916,1925,1933,1975,1981,1990,2002,2010,2016,2029,2042,2048,2061,2074,2142,2150,2156,2167,2196,2202,2211,2256,2261,2275],{"type":41,"tag":977,"props":1658,"children":1659},{},[1660],{"type":41,"tag":53,"props":1661,"children":1662},{},[1663],{"type":47,"value":1664},"以下所有影片都是 asciinema，所以任何指令和 output 都可以直接複製下來貼到你自己的終端機 Terminal 上。",{"type":41,"tag":42,"props":1666,"children":1668},{"id":1667},"docker-image-layers-分層式結構-unionfs",[1669],{"type":47,"value":1670},"Docker Image Layers 分層式結構 - UnionFS",{"type":41,"tag":53,"props":1672,"children":1673},{},[1674,1676,1683,1689,1691,1703],{"type":47,"value":1675},"首先必須先了解 Docker 和 ",{"type":41,"tag":117,"props":1677,"children":1680},{"href":1678,"rel":1679},"https://en.wikipedia.org/wiki/UnionFS?ref=blog.ewocker.com",[121],[1681],{"type":47,"value":1682},"UnionFS",{"type":41,"tag":1684,"props":1685,"children":1686},"sup",{},[1687],{"type":47,"value":1688},"[1]",{"type":47,"value":1690}," 的關係。簡而言之",{"type":41,"tag":74,"props":1692,"children":1693},{},[1694,1696,1701],{"type":47,"value":1695},"每一層 Layer",{"type":41,"tag":1684,"props":1697,"children":1698},{},[1699],{"type":47,"value":1700},"[2]",{"type":47,"value":1702}," 包含許多檔案，而 Docker Image 就是一層疊一層的集合體",{"type":47,"value":1704},"。看看以下的示意圖：",{"type":41,"tag":53,"props":1706,"children":1707},{},[1708],{"type":41,"tag":57,"props":1709,"children":1711},{"alt":8,"src":1710},"unionfs.png",[],{"type":41,"tag":53,"props":1713,"children":1714},{},[1715,1717,1722],{"type":47,"value":1716},"以上圖為例，Image 本身有三層，並且每層都有各自的檔案。",{"type":41,"tag":74,"props":1718,"children":1719},{},[1720],{"type":47,"value":1721},"一個完整的 Image 本身所有的階層都是 read-only (只讀) 的，但 Docker 會在上面加上一個 read-write (讀寫皆可)  的第四層",{"type":47,"value":1723},"。幾乎 Dockerfile 的每一行都會建立起一層。",{"type":41,"tag":1725,"props":1726,"children":1727},"footnote-block",{},[1728],{"type":41,"tag":53,"props":1729,"children":1730},{},[1731,1733,1736],{"type":47,"value":1732},"[1] UnionFS - Union File System",{"type":41,"tag":151,"props":1734,"children":1735},{},[],{"type":47,"value":1737},"\n[2] Layer 階層",{"type":41,"tag":494,"props":1739,"children":1741},{"id":1740},"首先來證明創建出的大小是和上面敘述的一樣",[1742],{"type":47,"value":1740},{"type":41,"tag":53,"props":1744,"children":1745},{},[1746],{"type":47,"value":1747},"而了解分層式結構為什麼那麼重要呢？假如在示意圖中的 Layer 3 將 Layer 1 的 file1 刪除，那最後 Image 的大小會是多少呢？（答案是不變）",{"type":41,"tag":53,"props":1749,"children":1750},{},[1751,1753,1758],{"type":47,"value":1752},"總結就是即便我們在後面的階層中刪除了一些檔案，那些檔案依然會留在我們的原先的階層裡面。因為 Image 是階層的總體，所以",{"type":41,"tag":74,"props":1754,"children":1755},{},[1756],{"type":47,"value":1757},"即便後面刪除了也只是多了一個新的階層而已，Image 的大小並不會減少",{"type":47,"value":437},{"type":41,"tag":53,"props":1760,"children":1761},{},[1762],{"type":47,"value":1763},"以此為起點，接下來來看看有哪些方法可以減少 Image 的大小。",{"type":41,"tag":128,"props":1765,"children":1766},{},[],{"type":41,"tag":42,"props":1768,"children":1770},{"id":1769},"normalize-image-layers-標準化映像階層",[1771],{"type":47,"value":1772},"Normalize Image Layers 標準化映像階層",{"type":41,"tag":53,"props":1774,"children":1775},{},[1776],{"type":47,"value":1777},"一個 Image 最多可以有 127 個階層，根據不同的 Storage Drive 可以有更多的階層數量，但若是有超過這個數量可能會限制一個 Image 可以被創建的地方 (因為不是所有的 Storage Drive 都可以)。",{"type":41,"tag":53,"props":1779,"children":1780},{},[1781,1783,1788,1790,1796],{"type":47,"value":1782},"上面說過在 UnionFS 裡，",{"type":41,"tag":74,"props":1784,"children":1785},{},[1786],{"type":47,"value":1787},"任何加到階層裡的檔案都會一直存在於那個階層",{"type":47,"value":1789},"，即便在後的階層裡將其刪除 ",{"type":41,"tag":1040,"props":1791,"children":1793},{"className":1792},[],[1794],{"type":47,"value":1795},"rm",{"type":47,"value":1797}," 也一樣。下面來證明一下：",{"type":41,"tag":53,"props":1799,"children":1800},{},[1801,1803,1810],{"type":47,"value":1802},"這理介紹一個實用的小工具 ",{"type":41,"tag":117,"props":1804,"children":1807},{"href":1805,"rel":1806},"https://github.com/wagoodman/dive?ref=blog.ewocker.com",[121],[1808],{"type":47,"value":1809},"dive",{"type":47,"value":1811},"，用來檢視 Image 的各種細項。這裡我們用來看看 Image 的效率 efficiency。",{"type":41,"tag":53,"props":1813,"children":1814},{},[1815],{"type":41,"tag":57,"props":1816,"children":1818},{"alt":8,"src":1817},"image.png",[],{"type":41,"tag":53,"props":1820,"children":1821},{},[1822],{"type":47,"value":1823},"圖中看到刪除後的效率從 100% 變成 43% 而已，並且相較原先 Image 浪費了 7.3 MB 的空間。那有哪些方法能更快速的創建出高效的 Image 呢？",{"type":41,"tag":494,"props":1825,"children":1827},{"id":1826},"build-context",[1828],{"type":47,"value":1829},"Build Context",{"type":41,"tag":1106,"props":1831,"children":1833},{"code":1832},"# Git\n.git\n.gitignore\n.gitattributes\n.github\n\n# Docker\nDockerfile\ndocker-compose.yml\n.dockerignore\n\n# Documentation\nREADME.md\nCHANGELOG.md\nCONTRIBUTING.md\nCODE_OF_CONDUCT.md\nSECURITY.md\n\n# macOS (optional)\n.DS_Store\n\n# Visual Studio Code (optional)\n.vscode\n",[1834],{"type":41,"tag":1040,"props":1835,"children":1836},{"__ignoreMap":8},[1837],{"type":47,"value":1832},{"type":41,"tag":49,"props":1839,"children":1840},{},[1841],{"type":41,"tag":53,"props":1842,"children":1843},{},[1844],{"type":47,"value":1845},"常見的 .dockerignore 檔案",{"type":41,"tag":53,"props":1847,"children":1848},{},[1849,1851,1857,1859,1864,1866,1872,1874,1880,1882,1888],{"type":47,"value":1850},"在創建 Image 時，最常見的就是跑 ",{"type":41,"tag":1040,"props":1852,"children":1854},{"className":1853},[],[1855],{"type":47,"value":1856},"docker build .",{"type":47,"value":1858}," ，這時前面的 ",{"type":41,"tag":1040,"props":1860,"children":1862},{"className":1861},[],[1863],{"type":47,"value":1299},{"type":47,"value":1865}," 是在告訴 docker 將當前路徑用作 root filesystem。這代表在每一次創建 Image 時 docker 都必須先將當前路徑裡的所有資料和檔案載入 build context，考慮到多數人習慣將 Dockerfile 放在專案的最上層，這可能是幾百 MB 甚至幾 GB 的大小。舉例來說一個 nodejs 的專案也許只有 2MB 的 code 但 ",{"type":41,"tag":1040,"props":1867,"children":1869},{"className":1868},[],[1870],{"type":47,"value":1871},"node_modules",{"type":47,"value":1873}," 卻是好幾 GB 的大小，但這些在創建 Image 時正常並不會被 ",{"type":41,"tag":1040,"props":1875,"children":1877},{"className":1876},[],[1878],{"type":47,"value":1879},"COPY",{"type":47,"value":1881},"，而是會在 Image 裡面額外跑 ",{"type":41,"tag":1040,"props":1883,"children":1885},{"className":1884},[],[1886],{"type":47,"value":1887},"npm install",{"type":47,"value":1889}," ，這導致 docker 必須要載入多餘幾 GB 的資料。",{"type":41,"tag":53,"props":1891,"children":1892},{},[1893,1895,1901,1903,1908],{"type":47,"value":1894},"改善這個的方法有許多種，其一是將 Dockerfile 放在適合的路徑，讓創建 Image 時不會載入多餘的資料。再來是 ",{"type":41,"tag":1040,"props":1896,"children":1898},{"className":1897},[],[1899],{"type":47,"value":1900},".dockerignore",{"type":47,"value":1902}," 檔案，將不許要被載入的檔案和資料夾列入 ",{"type":41,"tag":1040,"props":1904,"children":1906},{"className":1905},[],[1907],{"type":47,"value":1900},{"type":47,"value":1909}," 來指示 docker 在創建 Image 時忽略這些檔案。",{"type":41,"tag":494,"props":1911,"children":1913},{"id":1912},"commands-merge-合併指令",[1914],{"type":47,"value":1915},"Commands merge 合併指令",{"type":41,"tag":1106,"props":1917,"children":1920},{"code":1918,"language":687,"meta":8,"className":1919},"RUN apt-get update \\\n  && apt-get install -y git \\\n  && git clone \u003Conline-project> \\\n  && rm -rf /var/lib/apt/lists/* \\\n  && apt-get remove git -y\n",[1136],[1921],{"type":41,"tag":1040,"props":1922,"children":1923},{"__ignoreMap":8},[1924],{"type":47,"value":1918},{"type":41,"tag":49,"props":1926,"children":1927},{},[1928],{"type":41,"tag":53,"props":1929,"children":1930},{},[1931],{"type":47,"value":1932},"只需要用 git 拉一個之後需要用的 Code，那可以在後面將 git 移除",{"type":41,"tag":53,"props":1934,"children":1935},{},[1936,1938,1944,1946,1951,1952,1958,1960,1965,1967,1973],{"type":47,"value":1937},"上面說道在 Dockerfile 中每一行 ",{"type":41,"tag":1040,"props":1939,"children":1941},{"className":1940},[],[1942],{"type":47,"value":1943},"RUN",{"type":47,"value":1945},"  ",{"type":41,"tag":1040,"props":1947,"children":1949},{"className":1948},[],[1950],{"type":47,"value":1879},{"type":47,"value":1945},{"type":41,"tag":1040,"props":1953,"children":1955},{"className":1954},[],[1956],{"type":47,"value":1957},"ADD",{"type":47,"value":1959}," 都會生成一個階層，而在 docker 創建 Image 時會常常包括許多從安裝各種 package 的步驟和編譯代碼等等，這些都是非常花時間的步驟。如果能將這類型的指令在同一個 ",{"type":41,"tag":1040,"props":1961,"children":1963},{"className":1962},[],[1964],{"type":47,"value":1943},{"type":47,"value":1966}," 裡面合併起來的話，那生成的階層就只會有一個了。舉例來說像上面一樣用 ",{"type":41,"tag":1040,"props":1968,"children":1970},{"className":1969},[],[1971],{"type":47,"value":1972},"&&",{"type":47,"value":1974}," 把指令串起來，不但可以減少階層的數量，還能預防刪除的檔案被留在階層內。",{"type":41,"tag":494,"props":1976,"children":1978},{"id":1977},"delete-caches-刪除快取",[1979],{"type":47,"value":1980},"Delete Caches 刪除快取",{"type":41,"tag":1106,"props":1982,"children":1985},{"code":1983,"language":1111,"meta":8,"className":1984},"APK: ... && rm -rf /etc/apk/cache\nYUM: ... && rm -rf /var/cache/yum\nAPT: ... && rm -rf /var/cache/apt\n",[1109],[1986],{"type":41,"tag":1040,"props":1987,"children":1988},{"__ignoreMap":8},[1989],{"type":47,"value":1983},{"type":41,"tag":53,"props":1991,"children":1992},{},[1993,1995,2000],{"type":47,"value":1994},"在大多數 Dockerfile 中，安裝各種 package 都是免不了的步驟，在安裝後如果能在同一個 ",{"type":41,"tag":1040,"props":1996,"children":1998},{"className":1997},[],[1999],{"type":47,"value":1943},{"type":47,"value":2001}," 指示中刪除各種 caches，就可以避免這寫多餘的資料了。上面是各種常見的 package manager 存放快取的地方。",{"type":41,"tag":977,"props":2003,"children":2004},{},[2005],{"type":41,"tag":53,"props":2006,"children":2007},{},[2008],{"type":47,"value":2009},"💡 Dive 工具的 efficiency 只會找出各種 ghost files（在之後階層被刪除的檔案），像這種被遺忘的檔案是不會被找出來的。",{"type":41,"tag":494,"props":2011,"children":2013},{"id":2012},"squashing-the-image",[2014],{"type":47,"value":2015},"Squashing the image",{"type":41,"tag":49,"props":2017,"children":2018},{},[2019],{"type":41,"tag":53,"props":2020,"children":2021},{},[2022,2027],{"type":41,"tag":57,"props":2023,"children":2026},{"alt":2024,"src":2025},"squash 後的 Image 只有Base Image 的 5.61MB，所以把浪費的空間都移除了","squash.png",[],{"type":47,"value":2028},"\nsquash 後的 Image 只有 Base Image 的 5.61MB，所以把浪費的空間都移除了",{"type":41,"tag":53,"props":2030,"children":2031},{},[2032,2034,2040],{"type":47,"value":2033},"使用 ",{"type":41,"tag":1040,"props":2035,"children":2037},{"className":2036},[],[2038],{"type":47,"value":2039},"--squash",{"type":47,"value":2041}," 是 docker v1.13 以上的一個測試功能 Experimental Feature。Docker 會在最後將所有的階層合為一個階層 (圖中的 merge sh)。因為只有一個階層，這確保後期刪除的檔案確實不存在於 Image 中。",{"type":41,"tag":494,"props":2043,"children":2045},{"id":2044},"choose-a-base-image",[2046],{"type":47,"value":2047},"Choose a Base Image",{"type":41,"tag":49,"props":2049,"children":2050},{},[2051],{"type":41,"tag":53,"props":2052,"children":2053},{},[2054,2059],{"type":41,"tag":57,"props":2055,"children":2058},{"alt":2056,"src":2057},"Common Linux distro and node distro example","distroless.png",[],{"type":47,"value":2060},"\nCommon Linux distro and node distro example",{"type":41,"tag":53,"props":2062,"children":2063},{},[2064,2066,2072],{"type":47,"value":2065},"Base Image 就是 Dockerfile 中 ",{"type":41,"tag":1040,"props":2067,"children":2069},{"className":2068},[],[2070],{"type":47,"value":2071},"FROM",{"type":47,"value":2073}," 所指的 Image，是創建一個新 Image 的基底。上圖中會發現各種 Linux 發行版的 Image 大小差距很大，特別是 alpine 只需要 5.61MB 的大小，和其他 Base Image 相比真的非常小。一般的 application 如果沒有特定需求要使用各種 Dependency，其實使用 alpine 是較佳的選擇。",{"type":41,"tag":53,"props":2075,"children":2076},{},[2077,2079,2085,2087,2092,2094,2100,2102,2108,2110,2116,2118,2125,2127,2133,2135,2140],{"type":47,"value":2078},"圖中下面兩行是包含 ",{"type":41,"tag":1040,"props":2080,"children":2082},{"className":2081},[],[2083],{"type":47,"value":2084},"node.js",{"type":47,"value":2086}," 的 Image。許多 Developer 在挑選 Image 時會直接到 Dockerhub 上直接隨便找個有 ",{"type":41,"tag":1040,"props":2088,"children":2090},{"className":2089},[],[2091],{"type":47,"value":2084},{"type":47,"value":2093}," 或其他編程語言的 Image 使用，殊不知其中的差別非常巨大。像是圖中的 ",{"type":41,"tag":1040,"props":2095,"children":2097},{"className":2096},[],[2098],{"type":47,"value":2099},"node:10.16.3-buster",{"type":47,"value":2101}," 就是一個在 ",{"type":41,"tag":1040,"props":2103,"children":2105},{"className":2104},[],[2106],{"type":47,"value":2107},"debian@buster",{"type":47,"value":2109}," 發行版上安裝了 ",{"type":41,"tag":1040,"props":2111,"children":2113},{"className":2112},[],[2114],{"type":47,"value":2115},"nodejs@10.16.3",{"type":47,"value":2117}," 的 Image，其大小將進到了 1GB（想像每一次要部署新東西時首先要下載 1GB 大小的 Image）。而最下面的 ",{"type":41,"tag":117,"props":2119,"children":2122},{"href":2120,"rel":2121},"https://github.com/GoogleContainerTools/distroless?ref=blog.ewocker.com",[121],[2123],{"type":47,"value":2124},"distroless",{"type":47,"value":2126}," Image 雖然也是基於 ",{"type":41,"tag":1040,"props":2128,"children":2130},{"className":2129},[],[2131],{"type":47,"value":2132},"debian",{"type":47,"value":2134}," 發行版，卻把不必要的 Packages 全部移除了，只留下 ",{"type":41,"tag":1040,"props":2136,"children":2138},{"className":2137},[],[2139],{"type":47,"value":2084},{"type":47,"value":2141}," 運行必要的相關東西。",{"type":41,"tag":977,"props":2143,"children":2144},{},[2145],{"type":41,"tag":53,"props":2146,"children":2147},{},[2148],{"type":47,"value":2149},"💡 distroless 是由 Google 維持的一個 Project，所以他所使用的 Image Registry 並不是 dockerhub 而是 GCR。",{"type":41,"tag":494,"props":2151,"children":2153},{"id":2152},"scratch",[2154],{"type":47,"value":2155},"Scratch",{"type":41,"tag":53,"props":2157,"children":2158},{},[2159,2165],{"type":41,"tag":1040,"props":2160,"children":2162},{"className":2161},[],[2163],{"type":47,"value":2164},"FROM scratch",{"type":47,"value":2166}," 在 Dockerfile 中代表完從零開始創建這個 Image。這種方法創建起來的 Image 大小無疑是最小且最優化的。但這個方法也稍微比較有難度，最常見的就是 Image 內只需要一個簡單的 binary。",{"type":41,"tag":53,"props":2168,"children":2169},{},[2170,2172,2178,2180,2186,2188,2194],{"type":47,"value":2171},"上面的 Asciinema 影片用於演示一段 ",{"type":41,"tag":1040,"props":2173,"children":2175},{"className":2174},[],[2176],{"type":47,"value":2177},"Golang",{"type":47,"value":2179}," 編譯出的 binary ",{"type":41,"tag":1040,"props":2181,"children":2183},{"className":2182},[],[2184],{"type":47,"value":2185},"app",{"type":47,"value":2187}," ，創建時用 ",{"type":41,"tag":1040,"props":2189,"children":2191},{"className":2190},[],[2192],{"type":47,"value":2193},"From scratch",{"type":47,"value":2195}," 也可以運行，而且 Image 的大小和 binary 大小一模一樣。",{"type":41,"tag":494,"props":2197,"children":2199},{"id":2198},"multi-stage-builds",[2200],{"type":47,"value":2201},"Multi-Stage Builds",{"type":41,"tag":1106,"props":2203,"children":2206},{"code":2204,"language":687,"meta":8,"className":2205},"FROM golang:1.15.7-buster as builder\nWORKDIR /go/src/github.com/ewocker/simple-website/\nRUN go get -d -v golang.org/x/net/html  \nCOPY app.go .\nRUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .\n\nFROM alpine:latest  \nRUN apk --no-cache add ca-certificates\nWORKDIR /root/\nCOPY --from=builder /go/src/github.com/ewocker/simple-website/app .\nCMD [\"./app\"]\n",[1136],[2207],{"type":41,"tag":1040,"props":2208,"children":2209},{"__ignoreMap":8},[2210],{"type":47,"value":2204},{"type":41,"tag":53,"props":2212,"children":2213},{},[2214,2216,2222,2224,2230,2232,2238,2240,2246,2248,2254],{"type":47,"value":2215},"我個人認為 Multi-Stage Build 是創建 Image 的最優辦法，這種方式不但簡單而且也非常容易。當然並不是所有 Image 都適合這種方法，但大多數時候都能使用 Multi-Stage Build 來操作。用上面的 Dockerfile 舉個例子，上面分別有兩個 ",{"type":41,"tag":1040,"props":2217,"children":2219},{"className":2218},[],[2220],{"type":47,"value":2221},"From",{"type":47,"value":2223}," 指示，第一個我稱其為 ",{"type":41,"tag":1040,"props":2225,"children":2227},{"className":2226},[],[2228],{"type":47,"value":2229},"builder",{"type":47,"value":2231}," Image 並將其用於編譯軟體，第二個則是我實際上創建出的 Image。這樣我在本機上無需安裝 Golang 就可以編譯出 Golang 的 Binary 了，而且雖然編譯時雖然是使用 ",{"type":41,"tag":1040,"props":2233,"children":2235},{"className":2234},[],[2236],{"type":47,"value":2237},"golang:1.15.7-buster",{"type":47,"value":2239}," 但實際上最後創建出的 Image 卻是使用 ",{"type":41,"tag":1040,"props":2241,"children":2243},{"className":2242},[],[2244],{"type":47,"value":2245},"alpine",{"type":47,"value":2247}," 的。簡而言之 Multi-Stage Build 的好處就是可以無視編譯時環境所需的 Image 大小，甚至可以在其中跑 Test 和做 Code Coverage，最後只要將編譯好的產物用 ",{"type":41,"tag":1040,"props":2249,"children":2251},{"className":2250},[],[2252],{"type":47,"value":2253},"COPY --from=",{"type":47,"value":2255}," 複製進最後的 Image 即可。",{"type":41,"tag":494,"props":2257,"children":2259},{"id":2258},"其他方法",[2260],{"type":47,"value":2258},{"type":41,"tag":53,"props":2262,"children":2263},{},[2264,2266,2273],{"type":47,"value":2265},"優化 Docker Image 的方式有很多種，除了上述的幾種靠自己知識的方法外，也有許多開源的 Project 專門於優化 Image 的，像是 ",{"type":41,"tag":117,"props":2267,"children":2270},{"href":2268,"rel":2269},"https://github.com/docker-slim/docker-slim?ref=blog.ewocker.com",[121],[2271],{"type":47,"value":2272},"Docker-slim",{"type":47,"value":2274}," 等之類的。這裡因為涉及到更多複雜的概念像是 Seccomp 和 AppArmor 等等的自動生成，所以暫時不對 docker-slim 多做介紹。",{"type":41,"tag":128,"props":2276,"children":2277},{},[],{"title":8,"searchDepth":932,"depth":932,"links":2279},[2280,2283],{"id":1667,"depth":932,"text":1670,"children":2281},[2282],{"id":1740,"depth":941,"text":1740},{"id":1769,"depth":932,"text":1772,"children":2284},[2285,2286,2287,2288,2289,2290,2291,2292],{"id":1826,"depth":941,"text":1829},{"id":1912,"depth":941,"text":1915},{"id":1977,"depth":941,"text":1980},{"id":2012,"depth":941,"text":2015},{"id":2044,"depth":941,"text":2047},{"id":2152,"depth":941,"text":2155},{"id":2198,"depth":941,"text":2201},{"id":2258,"depth":941,"text":2258},"content:1.blog:014.container-docker:02.md","1.blog/014.container-docker/02.md","1.blog/014.container-docker/02",1775371293057]