Docker 核心技術與實現原理

      網友投稿 820 2025-04-03

      提到虛擬化技術,我們首先想到的一定是 Docker,經過四年的快速發展 Docker 已經成為了很多公司的標配,也不再是一個只能在開發階段使用的玩具了。作為在生產環境中廣泛應用的產品,Docker 有著非常成熟的社區以及大量的使用者,代碼庫中的內容也變得非常龐大。


      同樣,由于項目的發展、功能的拆分以及各種奇怪的改名 PR,讓我們再次理解 Docker 的的整體架構變得更加困難。

      雖然 Docker 目前的組件較多,并且實現也非常復雜,但是本文不想過多的介紹 Docker 具體的實現細節,我們更想談一談 Docker 這種虛擬化技術的出現有哪些核心技術的支撐。

      首先,Docker 的出現一定是因為目前的后端在開發和運維階段確實需要一種虛擬化技術解決開發環境和生產環境環境一致的問題,通過 Docker 我們可以將程序運行的環境也納入到版本控制中,排除因為環境造成不同運行結果的可能。但是上述需求雖然推動了虛擬化技術的產生,但是如果沒有合適的底層技術支撐,那么我們仍然得不到一個完美的產品。本文剩下的內容會介紹幾種 Docker 使用的核心技術,如果我們了解它們的使用方法和原理,就能清楚 Docker 的實現原理。

      Namespaces

      命名空間 (namespaces) 是 Linux 為我們提供的用于分離進程樹、網絡接口、掛載點以及進程間通信等資源的方法。在日常使用 Linux 或者 macOS 時,我們并沒有運行多個完全分離的服務器的需要,但是如果我們在服務器上啟動了多個服務,這些服務其實會相互影響的,每一個服務都能看到其他服務的進程,也可以訪問宿主機器上的任意文件,這是很多時候我們都不愿意看到的,我們更希望運行在同一臺機器上的不同服務能做到完全隔離,就像運行在多臺不同的機器上一樣。

      在這種情況下,一旦服務器上的某一個服務被入侵,那么入侵者就能夠訪問當前機器上的所有服務和文件,這也是我們不想看到的,而 Docker 其實就通過 Linux 的 Namespaces 對不同的容器實現了隔離。

      Linux 的命名空間機制提供了以下七種不同的命名空間,包括 CLONENEWCGROUP、CLONENEWIPC、CLONENEWNET、CLONENEWNS、CLONENEWPID、CLONENEWUSER 和 CLONE_NEWUTS,通過這七個選項我們能在創建新的進程時設置新進程應該在哪些資源上與宿主機器進行隔離。

      進程

      進程是 Linux 以及現在操作系統中非常重要的概念,它表示一個正在執行的程序,也是在現代分時系統中的一個任務單元。在每一個 *nix 的操作系統上,我們都能夠通過 ps 命令打印出當前操作系統中正在執行的進程,比如在 Ubuntu 上,使用該命令就能得到以下的結果:

      $?ps?-efUID????????PID??PPID??C?STIME?TTY??????????TIME?CMDroot?????????1?????0??0?Apr08??????????00:00:09?/sbin/initroot?????????2?????0??0?Apr08??????????00:00:00?[kthreadd]root?????????3?????2??0?Apr08??????????00:00:05?[ksoftirqd/0]root?????????5?????2??0?Apr08??????????00:00:00?[kworker/0:0H]root?????????7?????2??0?Apr08??????????00:07:10?[rcu_sched]root????????39?????2??0?Apr08??????????00:00:00?[migration/0]root????????40?????2??0?Apr08??????????00:01:54?[watchdog/0]...

      當前機器上有很多的進程正在執行,在上述進程中有兩個非常特殊,一個是 pid 為 1 的 /sbin/init 進程,另一個是 pid 為 2 的 kthreadd 進程,這兩個進程都是被 Linux 中的上帝進程 idle 創建出來的,其中前者負責執行內核的一部分初始化工作和系統配置,也會創建一些類似 getty 的注冊進程,而后者負責管理和調度其他的內核進程。

      如果我們在當前的 Linux 操作系統下運行一個新的 Docker 容器,并通過 exec 進入其內部的 bash 并打印其中的全部進程,我們會得到以下的結果:

      root@iZ255w13cy6Z:~#?docker?run?-it?-d?ubuntub809a2eb3630e64c581561b08ac46154878ff1c61c6519848b4a29d412215e79root@iZ255w13cy6Z:~#?docker?exec?-it?b809a2eb3630?/bin/bashroot@b809a2eb3630:/#?ps?-efUID????????PID??PPID??C?STIME?TTY??????????TIME?CMDroot?????????1?????0??0?15:42?pts/0????00:00:00?/bin/bashroot?????????9?????0??0?15:42?pts/1????00:00:00?/bin/bashroot????????17?????9??0?15:43?pts/1????00:00:00?ps?-ef

      在新的容器內部執行 ps 命令打印出了非常干凈的進程列表,只有包含當前 ps -ef 在內的三個進程,在宿主機器上的幾十個進程都已經消失不見了。

      當前的 Docker 容器成功將容器內的進程與宿主機器中的進程隔離,如果我們在宿主機器上打印當前的全部進程時,會得到下面三條與 Docker 相關的結果:

      UID????????PID??PPID??C?STIME?TTY??????????TIME?CMDroot?????29407?????1??0?Nov16??????????00:08:38?/usr/bin/dockerd?--raw-logsroot??????1554?29407??0?Nov19??????????00:03:28?docker-containerd?-l?unix:///var/run/docker/libcontainerd/docker-containerd.sock?--metrics-interval=0?--start-timeout?2m?--state-dir?/var/run/docker/libcontainerd/containerd?--shim?docker-containerd-shim?--runtime?docker-runcroot??????5006??1554??0?08:38??????????00:00:00?docker-containerd-shim?b809a2eb3630e64c581561b08ac46154878ff1c61c6519848b4a29d412215e79?/var/run/docker/libcontainerd/b809a2eb3630e64c581561b08ac46154878ff1c61c6519848b4a29d412215e79?docker-runc

      Docker 核心技術與實現原理

      在當前的宿主機器上,可能就存在由上述的不同進程構成的進程樹:

      這就是在使用 clone(2) 創建新進程時傳入 CLONE_NEWPID 實現的,也就是使用 Linux 的命名空間實現進程的隔離,Docker 容器內部的任意進程都對宿主機器的進程一無所知。

      containerRouter.postContainersStart└──?daemon.ContainerStart????└──?daemon.createSpec????????└──?setNamespaces????????????└──?setNamespace

      Docker 的容器就是使用上述技術實現與宿主機器的進程隔離,當我們每次運行 docker run 或者 docker start 時,都會在下面的方法中創建一個用于設置進程間隔離的 Spec:

      func?(daemon?*Daemon)?createSpec(c?*container.Container)?(*specs.Spec,?error)?{????s?:=?oci.DefaultSpec()????//?...????if?err?:=?setNamespaces(daemon,?&s,?c);?err?!=?nil?{????????return?nil,?fmt.Errorf("linux?spec?namespaces:?%v",?err)????}????return?&s,?nil}在?setNamespac

      在 setNamespaces 方法中不僅會設置進程相關的命名空間,還會設置與用戶、網絡、IPC 以及 UTS 相關的命名空間:

      func?setNamespaces(daemon?*Daemon,?s?*specs.Spec,?c?*container.Container)?error?{????//?user????//?network????//?ipc????//?uts????//?pid????if?c.HostConfig.PidMode.IsContainer()?{????????ns?:=?specs.LinuxNamespace{Type:?"pid"}????????pc,?err?:=?daemon.getPidContainer(c)????????if?err?!=?nil?{????????????return?err????????}????????ns.Path?=?fmt.Sprintf("/proc/%d/ns/pid",?pc.State.GetPID())????????setNamespace(s,?ns)????}?else?if?c.HostConfig.PidMode.IsHost()?{????????oci.RemoveNamespace(s,?specs.LinuxNamespaceType("pid"))????}?else?{????????ns?:=?specs.LinuxNamespace{Type:?"pid"}????????setNamespace(s,?ns)????}????return?nil}

      所有命名空間相關的設置 Spec 最后都會作為 Create 函數的入參在創建新的容器時進行設置:

      daemon.containerd.Create(context.Background(),?container.ID,?spec,?createOptions)

      所有與命名空間的相關的設置都是在上述的兩個函數中完成的,Docker 通過命名空間成功完成了與宿主機進程和網絡的隔離。

      網絡

      如果 Docker 的容器通過 Linux 的命名空間完成了與宿主機進程的網絡隔離,但是卻有沒有辦法通過宿主機的網絡與整個互聯網相連,就會產生很多限制,所以 Docker 雖然可以通過命名空間創建一個隔離的網絡環境,但是 Docker 中的服務仍然需要與外界相連才能發揮作用。

      每一個使用 docker run 啟動的容器其實都具有單獨的網絡命名空間,Docker 為我們提供了四種不同的網絡模式,Host、Container、None 和 Bridge 模式。

      在這一部分,我們將介紹 Docker 默認的網絡設置模式:網橋模式。在這種模式下,除了分配隔離的網絡命名空間之外,Docker 還會為所有的容器設置 IP 地址。當 Docker 服務器在主機上啟動之后會創建新的虛擬網橋 docker0,隨后在該主機上啟動的全部服務在默認情況下都與該網橋相連。

      在默認情況下,每一個容器在創建時都會創建一對虛擬網卡,兩個虛擬網卡組成了數據的通道,其中一個會放在創建的容器中,會加入到名為 docker0 網橋中。我們可以使用如下的命令來查看當前網橋的接口:

      $?brctl?showbridge?name????bridge?id???????STP?enabled?interfacesdocker0????????8000.0242a6654980???no??????veth3e84d4f????????????????????????????????????????veth9953b75

      docker0 會為每一個容器分配一個新的 IP 地址并將 docker0 的 IP 地址設置為默認的網關。網橋 docker0 通過 iptables 中的配置與宿主機器上的網卡相連,所有符合條件的請求都會通過 iptables 轉發到 docker0 并由網橋分發給對應的機器。

      $?iptables?-t?nat?-LChain?PREROUTING?(policy?ACCEPT)target?????prot?opt?source???????????????destinationDOCKER?????all??--??anywhere?????????????anywhere?????????????ADDRTYPE?match?dst-type?LOCALChain?DOCKER?(2?references)target?????prot?opt?source???????????????destinationRETURN?????all??--??anywhere?????????????anywhere

      我們在當前的機器上使用 docker run -d -p 6379:6379 redis 命令啟動了一個新的 Redis 容器,在這之后我們再查看當前 iptables 的 NAT 配置就會看到在 DOCKER 的鏈中出現了一條新的規則:

      DNAT???????tcp??--??anywhere?????????????anywhere?????????????tcp?dpt:6379?to:192.168.0.4:6379

      上述規則會將從任意源發送到當前機器 6379 端口的 TCP 包轉發到 192.168.0.4:6379 所在的地址上。

      這個地址其實也是 Docker 為 Redis 服務分配的 IP 地址,如果我們在當前機器上直接 ping 這個 IP 地址就會發現它是可以訪問到的:

      $?ping?192.168.0.4PING?192.168.0.4?(192.168.0.4)?56(84)?bytes?of?data.64?bytes?from?192.168.0.4:?icmp_seq=1?ttl=64?time=0.069?ms64?bytes?from?192.168.0.4:?icmp_seq=2?ttl=64?time=0.043?ms^C---?192.168.0.4?ping?statistics?---2?packets?transmitted,?2?received,?0%?packet?loss,?time?999msrtt?min/avg/max/mdev?=?0.043/0.056/0.069/0.013?ms

      從上述的一系列現象,我們就可以推測出 Docker 是如何將容器的內部的端***露出來并對數據包進行轉發的了;當有 Docker 的容器需要將服務暴露給宿主機器,就會為容器分配一個 IP 地址,同時向 iptables 中追加一條新的規則。

      當我們使用 redis-cli 在宿主機器的命令行中訪問 127.0.0.1:6379 的地址時,經過 iptables 的 NAT PREROUTING 將 ip 地址定向到了 192.168.0.4,重定向過的數據包就可以通過 iptables 中的 FILTER 配置,最終在 NAT POSTROUTING 階段將 ip 地址偽裝成 127.0.0.1,到這里雖然從外面看起來我們請求的是 127.0.0.1:6379,但是實際上請求的已經是 Docker 容器暴露出的端口了。

      $?redis-cli?-h?127.0.0.1?-p?6379?pingPONG

      Docker 通過 Linux 的命名空間實現了網絡的隔離,又通過 iptables 進行數據包轉發,讓 Docker 容器能夠優雅地為宿主機器或者其他容器提供服務。

      libnetwork

      整個網絡部分的功能都是通過 Docker 拆分出來的 libnetwork 實現的,它提供了一個連接不同容器的實現,同時也能夠為應用給出一個能夠提供一致的編程接口和網絡層抽象的容器網絡模型。

      The goal of libnetwork is to deliver a robust Container Network Model that provides a consistent programming interface and the required network abstractions for applications.

      libnetwork 中最重要的概念,容器網絡模型由以下的幾個主要組件組成,分別是 Sandbox、Endpoint 和 Network:

      在容器網絡模型中,每一個容器內部都包含一個 Sandbox,其中存儲著當前容器的網絡棧配置,包括容器的接口、路由表和 DNS 設置,Linux 使用網絡命名空間實現這個 Sandbox,每一個 Sandbox 中都可能會有一個或多個 Endpoint,在 Linux 上就是一個虛擬的網卡 veth,Sandbox 通過 Endpoint 加入到對應的網絡中,這里的網絡可能就是我們在上面提到的 Linux 網橋或者 VLAN。

      要獲得更多與 libnetwork 或者容器網絡模型相關的信息,可以閱讀 Design · libnetwork 了解更多信息,當然也可以閱讀源代碼了解不同 OS 對容器網絡模型的不同實現。

      掛載點

      雖然我們已經通過 Linux 的命名空間解決了進程和網絡隔離的問題,在 Docker 進程中我們已經沒有辦法訪問宿主機器上的其他進程并且限制了網絡的訪問,但是 Docker 容器中的進程仍然能夠訪問或者修改宿主機器上的其他目錄,這是我們不希望看到的。

      在新的進程中創建隔離的掛載點命名空間需要在 clone 函數中傳入 CLONE_NEWNS,這樣子進程就能得到父進程掛載點的拷貝,如果不傳入這個參數子進程對文件系統的讀寫都會同步回父進程以及整個主機的文件系統。

      如果一個容器需要啟動,那么它一定需要提供一個根文件系統(rootfs),容器需要使用這個文件系統來創建一個新的進程,所有二進制的執行都必須在這個根文件系統中。

      想要正常啟動一個容器就需要在 rootfs 中掛載以上的幾個特定的目錄,除了上述的幾個目錄需要掛載之外我們還需要建立一些符號鏈接保證系統 IO 不會出現問題。

      為了保證當前的容器進程沒有辦法訪問宿主機器上其他目錄,我們在這里還需要通過 libcontainer 提供的 pivot_root 或者 chroot 函數改變進程能夠訪問個文件目錄的根節點。

      //?pivor_rootput_old?=?mkdir(...);pivot_root(rootfs,?put_old);chdir("/");unmount(put_old,?MS_DETACH);rmdir(put_old);//?chrootmount(rootfs,?"/",?NULL,?MS_MOVE,?NULL);chroot(".");chdir("/");

      到這里我們就將容器需要的目錄掛載到了容器中,同時也禁止當前的容器進程訪問宿主機器上的其他目錄,保證了不同文件系統的隔離。

      chroot

      在這里不得不簡單介紹一下 chroot(change root),在 Linux 系統中,系統默認的目錄就都是以 / 也就是根目錄開頭的,chroot 的使用能夠改變當前的系統根目錄結構,通過改變當前系統的根目錄,我們能夠限制用戶的權利,在新的根目錄下并不能夠訪問舊系統根目錄的結構個文件,也就建立了一個與原系統完全隔離的目錄結構。

      與 chroot 的相關內容部分來自 理解 chroot 一文,各位讀者可以閱讀這篇文章獲得更詳細的信息。

      CGroups

      我們通過 Linux 的命名空間為新創建的進程隔離了文件系統、網絡并與宿主機器之間的進程相互隔離,但是命名空間并不能夠為我們提供物理資源上的隔離,比如 CPU 或者內存,如果在同一臺機器上運行了多個對彼此以及宿主機器一無所知的『容器』,這些容器卻共同占用了宿主機器的物理資源

      如果其中的某一個容器正在執行 CPU 密集型的任務,那么就會影響其他容器中任務的性能與執行效率,導致多個容器相互影響并且搶占資源。如何對多個容器的資源使用進行限制就成了解決進程虛擬資源隔離之后的主要問題,而 Control Groups(簡稱 CGroups)就是能夠隔離宿主機器上的物理資源,例如 CPU、內存、磁盤 I/O 和網絡帶寬。

      每一個 CGroup 都是一組被相同的標準和參數限制的進程,不同的 CGroup 之間是有層級關系的,也就是說它們之間可以從父類繼承一些用于限制資源使用的標準和參數。

      inux 的 CGroup 能夠為一組進程分配資源,也就是我們在上面提到的 CPU、內存、網絡帶寬等資源,通過對資源的分配,CGroup 能夠提供以下的幾種功能:

      在 CGroup 中,所有的任務就是一個系統的一個進程,而 CGroup 就是一組按照某種標準劃分的進程,在 CGroup 這種機制中,所有的資源控制都是以 CGroup 作為單位實現的,每一個進程都可以隨時加入一個 CGroup 也可以隨時退出一個 CGroup。

      – CGroup 介紹、應用實例及原理描述

      Linux 使用文件系統來實現 CGroup,我們可以直接使用下面的命令查看當前的 CGroup 中有哪些子系統:

      $?lssubsys?-mcpuset?/sys/fs/cgroup/cpusetcpu?/sys/fs/cgroup/cpucpuacct?/sys/fs/cgroup/cpuacctmemory?/sys/fs/cgroup/memorydevices?/sys/fs/cgroup/devicesfreezer?/sys/fs/cgroup/freezerblkio?/sys/fs/cgroup/blkioperf_event?/sys/fs/cgroup/perf_eventhugetlb?/sys/fs/cgroup/hugetlb

      大多數 Linux 的發行版都有著非常相似的子系統,而之所以將上面的 cpuset、cpu 等東西稱作子系統,是因為它們能夠為對應的控制組分配資源并限制資源的使用。

      如果我們想要創建一個新的 cgroup 只需要在想要分配或者限制資源的子系統下面創建一個新的文件夾,然后這個文件夾下就會自動出現很多的內容,如果你在 Linux 上安裝了 Docker,你就會發現所有子系統的目錄下都有一個名為 docker 的文件夾:

      $?ls?cpucgroup.clone_children??...cpu.stat??docker??notify_on_release?release_agent?tasks$?ls?cpu/docker/9c3057f1291b53fd54a3d12023d2644efe6a7db6ddf330436ae73ac92d401cf1?cgroup.clone_children??...cpu.stat??notify_on_release?release_agent?tasks

      9c3057xxx 其實就是我們運行的一個 Docker 容器,啟動這個容器時,Docker 會為這個容器創建一個與容器標識符相同的 CGroup,在當前的主機上 CGroup 就會有以下的層級關系:

      每一個 CGroup 下面都有一個 tasks 文件,其中存儲著屬于當前控制組的所有進程的 pid,作為負責 cpu 的子系統,cpu.cfsquotaus 文件中的內容能夠對 CPU 的使用作出限制,如果當前文件的內容為 50000,那么當前控制組中的全部進程的 CPU 占用率不能超過 50%。

      如果系統管理員想要控制 Docker 某個容器的資源使用率就可以在 docker 這個父控制組下面找到對應的子控制組并且改變它們對應文件的內容,當然我們也可以直接在程序運行時就使用參數,讓 Docker 進程去改變相應文件中的內容。

      $?docker?run?-it?-d?--cpu-quota=50000?busybox53861305258ecdd7f5d2a3240af694aec9adb91cd4c7e210b757f71153cdd274$?cd?53861305258ecdd7f5d2a3240af694aec9adb91cd4c7e210b757f71153cdd274/$?lscgroup.clone_children??cgroup.event_control??cgroup.procs??cpu.cfs_period_us??cpu.cfs_quota_us??cpu.shares??cpu.stat??notify_on_release??tasks$?cat?cpu.cfs_quota_us50000

      當我們使用 Docker 關閉掉正在運行的容器時,Docker 的子控制組對應的文件夾也會被 Docker 進程移除,Docker 在使用 CGroup 時其實也只是做了一些創建文件夾改變文件內容的文件操作,不過 CGroup 的使用也確實解決了我們限制子容器資源占用的問題,系統管理員能夠為多個容器合理的分配資源并且不會出現多個容器互相搶占資源的問題。

      UnionFS

      Linux 的命名空間和控制組分別解決了不同資源隔離的問題,前者解決了進程、網絡以及文件系統的隔離,后者實現了 CPU、內存等資源的隔離,但是在 Docker 中還有另一個非常重要的問題需要解決 - 也就是鏡像。

      Docker 鏡像其實本質就是一個壓縮包,我們可以使用下面的命令將一個 Docker 鏡像中的文件導出:

      $?docker?export?$(docker?create?busybox)?|?tar?-C?rootfs?-xvf?-$?lsbin??dev??etc??home?proc?root?sys??tmp??usr??var

      你可以看到這個 busybox 鏡像中的目錄結構與 Linux 操作系統的根目錄中的內容并沒有太多的區別,可以說 Docker 鏡像就是一個文件。

      存儲驅動

      Docker 使用了一系列不同的存儲驅動管理鏡像內的文件系統并運行容器,這些存儲驅動與 Docker 卷(volume)有些不同,存儲引擎管理著能夠在多個容器之間共享的存儲。

      想要理解 Docker 使用的存儲驅動,我們首先需要理解 Docker 是如何構建并且存儲鏡像的,也需要明白 Docker 的鏡像是如何被每一個容器所使用的;Docker 中的每一個鏡像都是由一系列只讀的層組成的,Dockerfile 中的每一個命令都會在已有的只讀層上創建一個新的層:

      FROM?ubuntu:15.04COPY?.?/appRUN?make?/appCMD?python?/app/app.py

      容器中的每一層都只對當前容器進行了非常小的修改,上述的 Dockerfile 文件會構建一個擁有四層 layer 的鏡像:

      當鏡像被 docker run 命令創建時就會在鏡像的最上層添加一個可寫的層,也就是容器層,所有對于運行時容器的修改其實都是對這個容器讀寫層的修改。

      容器和鏡像的區別就在于,所有的鏡像都是只讀的,而每一個容器其實等于鏡像加上一個可讀寫的層,也就是同一個鏡像可以對應多個容器。

      AUFS

      UnionFS 其實是一種為 Linux 操作系統設計的用于把多個文件系統『聯合』到同一個掛載點的文件系統服務。而 AUFS 即 Advanced UnionFS 其實就是 UnionFS 的升級版,它能夠提供更優秀的性能和效率。

      AUFS 作為聯合文件系統,它能夠將不同文件夾中的層聯合(Union)到了同一個文件夾中,這些文件夾在 AUFS 中稱作分支,整個『聯合』的過程被稱為聯合掛載(Union Mount):

      每一個鏡像層或者容器層都是 /var/lib/docker/ 目錄下的一個子文件夾;在 Docker 中,所有鏡像層和容器層的內容都存儲在 /var/lib/docker/aufs/diff/ 目錄中:

      $?ls?/var/lib/docker/aufs/diff/00adcccc1a55a36a610a6ebb3e07cc35577f2f5a3b671be3dbc0e74db9ca691c???????93604f232a831b22aeb372d5b11af8c8779feb96590a6dc36a80140e38e764d800adcccc1a55a36a610a6ebb3e07cc35577f2f5a3b671be3dbc0e74db9ca691c-init??93604f232a831b22aeb372d5b11af8c8779feb96590a6dc36a80140e38e764d8-init019a8283e2ff6fca8d0a07884c78b41662979f848190f0658813bb6a9a464a90???????93b06191602b7934fafc984fbacae02911b579769d0debd89cf2a032e7f35cfa...

      而 /var/lib/docker/aufs/layers/ 中存儲著鏡像層的元數據,每一個文件都保存著鏡像層的元數據,最后的 /var/lib/docker/aufs/mnt/ 包含鏡像或者容器層的掛載點,最終會被 Docker 通過聯合的方式進行組裝。

      上面的這張圖片非常好的展示了組裝的過程,每一個鏡像層都是建立在另一個鏡像層之上的,同時所有的鏡像層都是只讀的,只有每個容器最頂層的容器層才可以被用戶直接讀寫,所有的容器都建立在一些底層服務(Kernel)上,包括命名空間、控制組、rootfs 等等,這種容器的組裝方式提供了非常大的靈活性,只讀的鏡像層通過共享也能夠減少磁盤的占用。

      其他存儲驅動

      AUFS 只是 Docker 使用的存儲驅動的一種,除了 AUFS 之外,Docker 還支持了不同的存儲驅動,包括 aufs、devicemapper、overlay2、zfs 和 vfs 等等,在最新的 Docker 中,overlay2 取代了 aufs 成為了推薦的存儲驅動,但是在沒有 overlay2 驅動的機器上仍然會使用 aufs 作為 Docker 的默認驅動。

      不同的存儲驅動在存儲鏡像和容器文件時也有著完全不同的實現,有興趣的讀者可以在 Docker 的官方文檔 Select a storage driver 中找到相應的內容。

      想要查看當前系統的 Docker 上使用了哪種存儲驅動只需要使用以下的命令就能得到相對應的信息:

      $?docker?info?|?grep?StorageStorage?Driver:?aufs

      總結

      Docker 目前已經成為了非常主流的技術,已經在很多成熟公司的生產環境中使用,但是 Docker 的核心技術其實已經有很多年的歷史了,Linux 命名空間、控制組和 UnionFS 三大技術支撐了目前 Docker 的實現,也是 Docker 能夠出現的最重要原因。

      https://mp.weixin.qq.com/s?__biz=MzAxNjk4ODE4OQ==&mid=2247484658&idx=1&sn=a28a06a347234f4990840f5f9ace3142&chksm=9bed2580ac9aac961f795f072fd03c68f66bdca3ddca9f66c58796842e0aded65b9b567641c2&scene=21#wechat_redirect

      任務調度 容器 Docker

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:word2007中表格的樣式應用設置教程(word文檔表格怎么設置表格樣式)
      下一篇:wps字數
      相關文章
      亚洲AV成人精品日韩一区| 亚洲精品无码久久久久久| 亚洲成A人片在线观看中文| 亚洲粉嫩美白在线| 亚洲午夜成激人情在线影院| 亚洲黄色一级毛片| 久久亚洲日韩看片无码| 亚洲视频在线免费播放| 亚洲欧洲国产综合| 国产精品亚洲综合五月天| 国产成人精品日本亚洲11| 亚洲一级片在线观看| 亚洲一区二区三区在线网站| 亚洲娇小性xxxx| 亚洲熟女综合一区二区三区| 亚洲AV性色在线观看| xvideos亚洲永久网址| 亚洲乱码中文字幕综合234| 亚洲一区二区三区无码影院| 久久亚洲AV永久无码精品| 亚洲人成精品久久久久| 亚洲成色在线综合网站| 亚洲视频精品在线| 亚洲电影免费观看| 亚洲人成人77777在线播放| 日本亚洲免费无线码| 亚洲AV无码成人精品区狼人影院| 成人精品国产亚洲欧洲| 亚洲精品无码成人片在线观看 | 91亚洲精品麻豆| 亚洲无mate20pro麻豆| 亚洲精品第一国产综合亚AV| 国产亚洲精品2021自在线| 国产亚洲av片在线观看18女人| 亚洲国产精品特色大片观看完整版| 亚洲avav天堂av在线不卡| 亚洲国产综合精品| 亚洲精品国产suv一区88| 久久精品国产精品亚洲艾草网美妙| 亚洲欧洲精品无码AV| 麻豆亚洲AV永久无码精品久久|