容器生態圈之旅--第二章《容器》

第二章 容器

重點先知:html

1.  容器技術基礎原理前端

2.  docker的基本用法java

3.  docker鏡像管理基礎node

4.  容器網絡mysql

5.  docker存儲卷linux

6.  dockerfile詳解nginx

7.  docker倉庫git

8.  docker的系統資源限制及驗證github

原文分享:http://note.youdao.com/noteshare?id=cfaaab1ea5ecc8472e31313ee2bf3d9f&sub=7395451B766449B180466AECA5834D8Eweb

2.1 前言

依舊記得初次聽到容器這個詞是在2018年的8月份。

依舊記得那月發生了兩件改變我IT軌跡的事:1. 加入小馬哥的運維羣(駿馬金龍:www.junmajinlong.com) 2. 買了馬哥的docker和kubernetes視頻。

依舊記得那時候的本身完成了某孩的期末架構就自認爲本身已經足夠強了。

那時候我大二剛結束。

白駒過隙,又是一年,2019年的8月份到了,我大三剛結束。

2019年的7月29號,我開始動筆寫下我對於容器這塊的總結性筆記。

我有寫過docker的系統性博文的:https://blog.csdn.net/zisefeizhu/article/category/7960629  自認爲寫的蠻好的。

我對容器總結性的一句話歸納:容器等於鏡像加進程,鏡像是應用程序及其依賴環境的封裝。

2.3 容器技術基礎

統稱來講,容器是一種工具,指的是能夠裝下其它物品的工具,以方便人類概括放置物品、存儲和異地運輸,具體來講好比人類使用的衣櫃、行李箱、揹包等能夠成爲容器,但今天咱們所說的容器是一種IT技術。

容器技術是虛擬化、雲計算、大數據以後的一門新興的而且是煊赫一時的新技術,容器技術提升了硬件資源利用率、方便了企業的業務快速橫向擴容、實現了業務宕機自愈功能,所以將來數年會是一個容器愈發流行的時代,這是一個對於IT行業來講很是有影響和價值的技術,而對於IT行業的從業者來講,熟練掌握容器技術無疑是一個頗有前景的的行業工做機會。

知名的容器技術有:Docker(Docker的同名開源容器化引擎適用於大多數後續產品以及許多開源工具),CSDE(Docker公司擁有擴展Docker的全部權。CSDE支持在Windows服務器上運行docker實例),Rkt(rkt的發音爲「rocket」,它是由CoreOS開發的。rkt是Docker容器的主要競爭對手),Solaris Containers(Solaris容器架構比Docker更早出現。想必那些已經在Solaris上標準化的IT企業會繼續研究它),Microsoft容器(做爲Linux的競爭對手,Microsoft Containers能夠在很是特定的狀況下支持Windows容器)。

這裏我將講的容器技術是docker,畢竟別的容器技術我都沒接觸過,招聘簡歷上也沒見過。

Docker是一個在2013年開源的應用程序而且是一個基於go語言編寫是一個開源的pass服務(Platform as a Service,平臺即服務的縮寫),go語言是由google開發,docker公司最先叫dotCloud,後因爲Docker開源後大受歡迎就將公司更名爲 Docker Inc,總部位於美國加州的舊金山,Docker是基於linux 內核實現,Docker最先採用LXC技術(LinuX Container的簡寫,LXC是Linux 原生支持的容器技術,能夠提供輕量級的虛擬化,能夠說 docker 就是基於 LXC 發展起來的,提供 LXC 的高級封裝,發展標準的配置方法),源代碼託管在 Github 上,而虛擬化技術KVM(Kernel-based Virtual Machine) 基於模塊實現,Docker後改成本身研發並開源的runc技術運行容器。

Docker利用現有的Linux容器技術,以不用方式將其封裝及擴展(經過提供可移植的鏡像及友好的接口),來建立(負責建立與運行容器的docker引擎)及發佈方案(用來發布容器的雲服務docker hub)。

Docker的基本組成:docker client 客戶端、docker daemon 守護進程、docker image 鏡像、docker container 容器、docker registry 倉庫、docker 主機。

Docker 相比虛擬機的交付速度更快,資源消耗更低,Docker 採用客戶端/服務端架構,使用遠程API來管理和建立Docker容器,其能夠輕鬆的建立一個輕量級的、可移植的、自給自足的容器,docker 的三大理念是build(構建)、ship(運輸)、run(運行),Docker聽從aoache 2.0協議,並經過(namespace及cgroup等)來提供容器的資源隔離與安全保障等(安全和隔離能夠使你能夠同時在機器上運行多個容器),因此Docke容器在運行時不須要相似虛擬機(空運行的虛擬機佔用物理機6-8%性能)的額外資源開銷,所以能夠大幅提升資源利用率,總而言之Docker是一種用了新穎方式實現的輕量級虛擬機.相似於VM可是在原理和應用上和VM的差異仍是很大的,而且docker的專業叫法是應用容器(Application Container)。

IDC/IAAS/PAAS/SAAS 對比

2.3.1docker運行原理

構建 -- > 運輸 -->  運行

 

2.3.2 docker架構圖

總架構圖

主要模塊

DockerClient(與Daemon創建通訊,發起容器的管理請求)

DockerDaemon(接收Client請求,處理請求)

Docker Regisrty(鏡像管理)

Graph(存儲鏡像)

Drvier(鏡像管理驅動)

libcontainer(系統內核特性,提供完整、明確的接口給Daemon)

Docker Container

各模塊功能及實現

Docker Client

Docker架構中用戶與Docker Daemon創建通訊的客戶端。

用戶能夠使用可執行文件docker做爲Docker Client,發起Docker容器的管理請求。

三種方式創建通訊:

  tcp://host:port

  unix://path_to_socket

  fd://socketfd

Docker Client發送容器管理請求後,請求由Docker Daemon接收並處理,當Docker Client接收到返回的請求響應並作簡單處理後,Docker Client一次完整的生命週期就結束了。

Docker Daemon

常駐在後臺的系統進程。

主要做用:

  接收並處理Docker Client發送的請求

  管理全部的Docker容器

Docker Daemon運行時,會在後臺啓動一個Server,Server負責接收Docker Client發送的請求;接收請求後,Server經過路由與分發調度,找到相應的Handler來處理請求。

三部分組成:

A.Docker Server

專門服務於Docker Client,接收並調度分發Client請求。

Server經過包gorilla/mux建立mux。Router路由器,提供請求的路由功能,每個路由項由HTTP請求方法(PUT、POST、GET、DELETE)、URL和Handler組成。

每個Client請求,Server均會建立一個全新的goroutine來服務,在goroutine中,Server首先讀取請求內容,而後作請求解析工做,接着匹配相應的路由項,隨後調用相應的Handler來處理,最後Handler處理完請求後給Client回覆響應。

 

B.Engine

核心模塊,運行引擎。

存儲着大量容器信息,管理着Docker大部分Job的執行。

 

handlers對象:

存儲衆多特定Job各自的處理方法handler。

例如:

{"create":daemon.ContainerCreate,}

當執行名爲"create"的Job時,執行的是daemon.ContainerCreate這個handler。

 

C.Job

Engine內部最基本的執行單元,Daemon完成的每一項工做都體現爲一個Job。

Docker Registry

存儲容器鏡像(Docker Image)的倉庫。

Docker Image是容器建立時用來初始化容器rootfs的文件系統內容。

主要做用:

  搜索鏡像

  下載鏡像

  上傳鏡像

方式:

  公有Registry

  私有Registry

Graph

容器鏡像的保管者。

Driver

驅動模塊,經過Driver驅動,Docker實現對Docker容器運行環境的定製,定製的維度包括網絡、存儲、執行方式。

做用:

  將與Docker容器有關的管理從Daemon的全部邏輯中區分開。

實現:

A.graphdriver

用於完成容器鏡像管理。

初始化前的四種文件系統或類文件系統的驅動在Daemon中註冊:

aufs、btrfs、devmapper用於容器鏡像的管理

vfs用於容器volume的管理

 

B.networkdriver

完成Docker容器網絡環境的配置。

 

C.execdriver

執行驅動,負責建立容器運行時的命名空間,負責容器資源使用的統計與限制,負責容器內部進程的真正運行等。

Daemon啓動過程當中加載ExecDriverflag參數在配置文件中默認設爲native。

libcontainer

使用Go語言設計的庫,不依靠任何依賴,直接訪問內核中與容器相關的系統調用。

Docker Container

服務交付的最終體現。

用戶對Docker容器的配置:

  經過指定容器鏡像,使得Docker容器能夠自定義rootfs等文件系統;

  經過指定物理資源的配額,使得Docker容器使用受限的資源;

  經過配置容器網絡及其安全策略,使得Docker容器擁有獨立且安全的網絡環境;

  經過指定容器的運行命令,使得Docker容器執行指定的任務;

2.3.3 docker架構

Docker使用C/S架構,Client 經過接口與Server進程通訊實現容器的構建,運行和發佈。client和server能夠運行在同一臺集羣,也能夠經過跨主機實現遠程通訊。

Docker 客戶端會與Docker守護進程進行通訊。Docker 守護進程會處理複雜繁重的任務,例如創建、運行、發佈你的 Docker 容器。

Docker 客戶端和守護進程能夠運行在同一個系統上,固然也能夠使用Docker客戶端去鏈接一個遠程的 Docker 守護進程。

Docker 客戶端和守護進程之間經過socket或者RESTful API進行通訊。

 

2.3.4 docker的組成

Docker 客戶端(Client):Docker 客戶端,其實是 docker 的二進制程序,是主要的用戶與 Docker 交互方式。它接收用戶指令而且與背後的 Docker 守護進程通訊,如此來回往復。

Docker 服務端(Server):Docker守護進程,運行docker容器。Docker守護進程運行在一臺主機上。用戶並不直接和守護進程進行交互,而是經過 Docker 客戶端間接和其通訊。

Docker 鏡像(Images):Docker 鏡像是Docker容器運行時的只讀模板,每個鏡像由一系列的層 (layers) 組成。Docker 使用 UnionFS 來將這些層聯合到單獨的鏡像中。UnionFS 容許獨立文件系統中的文件和文件夾(稱之爲分支)被透明覆蓋,造成一個單獨連貫的文件系統。正由於有了這些層的存在,Docker 是如此的輕量。當你改變了一個 Docker 鏡像,好比升級到某個程序到新的版本,一個新的層會被建立。所以,不用替換整個原先的鏡像或者從新創建(在使用虛擬機的時候你可能會這麼作),只是一個新 的層被添加或升級了。如今你不用從新發布整個鏡像,只須要升級,層使得分發 Docker 鏡像變得簡單和快速。

Docker 容器(Container): 容器是從鏡像生成對外提供服務的一個或一組服務。Docker 容器和文件夾很相似,一個Docker容器包含了全部的某個應用運行所須要的環境。每個 Docker 容器都是從 Docker 鏡像建立的。Docker 容器能夠運行、開始、中止、移動和刪除。每個 Docker 容器都是獨立和安全的應用平臺,Docker 容器是 Docker 的運行部分。

Docker 倉庫(Registry): 保存鏡像的倉庫,相似於git或svn這樣的版本控制系統,官方倉庫: https://hub.docker.com/ 。一樣的,Docker 倉庫也有公有和私有的概念。公有的 Docker 倉庫名字是 Docker Hub。Docker Hub 提供了龐大的鏡像集合供使用。這些鏡像能夠是本身建立,或者在別人的鏡像基礎上建立。Docker 倉庫是 Docker 的分發部分。

Docker 主機(Host):一個物理機或虛擬機,用於運行Docker服務進程和容器。

2.3.5 docker對比虛擬機

資源利用率更高:一臺物理機能夠運行數百個容器,可是通常只能運行數十個虛擬機。

開銷更小:容器與主機的操做系統共享資源,提升了效率,性能損耗低

啓動速度更快:能夠在作到秒級完成啓動。

容器具備可移植性

容器是輕量的,可同時運行數十個容器,模擬分佈式系統

沒必要花時間在配置和安裝上,無需擔憂系統的改動,以及依賴關係是否知足

區別:

  A.容器只能運行與主機同樣的內核
  B.容器程序庫能夠共用
  C.容器中執行的進程與主機的進程等價(沒有虛擬機管理程序的損耗)
  D.隔離能力,虛擬機更高(將容器運行在虛擬機中)

  E.使用虛擬機是爲了更好的實現服務運行環境隔離,可是一個虛擬機只運行一個服務,很明顯資源利用率比較低

2.3.6 docker的優點與缺點

優點

  快速部署:短期內能夠部署成百上千個應用,更快速交付到線上。

  高效虛擬化:不須要額外的hypervisor支持,直接基於linux 實現應用虛擬化,相比虛擬機大幅提升性能和效率。

  節省開支:提升服務器利用率,下降IT 支出。

  簡化配置:將運行環境打包保存至容器,使用時直接啓動便可。

  快速遷移和擴展:可誇平臺運行在物理機、虛擬機、公有云等環境,良好的兼容性能夠方便將應用從A宿主機遷移到B宿主機,甚至是A平臺遷移到B平臺。

缺點:

  隔離性:各應用之間的隔離不如虛擬機。

2.3.7 docker容器的核心技術

容器規範:

除了docker以外的docker技術,還有coreOS的rkt,還有阿里的Pouch,爲了保證容器生態的標誌性和健康可持續發展,包括Google、Docker等公司共同成立了一個叫open container(OCI)的組織,其目的就是制定開放的標準的容器規範,目前OCI一共發佈了兩個規範,分別是runtime spec和image format spec,有了這兩個規範,不一樣的容器公司開發的容器只要兼容這兩個規範,就能夠保證容器的可移植性和相互可操做性。

容器runtime:

runtime是真正運行容器的地方,所以爲了運行不一樣的容器runtime須要和操做系統內核緊密合做相互在支持,以便爲容器提供相應的運行環境。

目前主流的三種runtime:

  Lxc:linux上早期的runtime,Docker早期就是採用lxc做爲runtime。

  runc:目前Docker默認的runtime,runc遵照OCI規範,所以能夠兼容lxc。

  rkt:是CoreOS開發的容器runtime,也符合OCI規範,因此使用rktruntime也能夠運行Docker容器。

容器管理工具:

管理工具鏈接runtime與用戶,對用戶提供圖形或命令方式操做,而後管理工具將用戶操做傳遞給runtime執行。

Lxd是lxc的管理工具。

Runc的管理工具是docker engine,docker engine包含後臺deamon和cli兩部分,你們常常提到的Docker就是指的docker engine。

Rkt的管理工具是rkt cli。 

容器定義工具:

容器定義工具容許用戶定義容器的屬性和內容,以方便容器可以被保存、共享和重建。

Docker image:是docker 容器的模板,runtime依據docker image建立容器。

Dockerfile:包含N個命令的文本文件,經過dockerfile建立出docker image。

ACI(App container image):與docker image相似,是CoreOS開發的rkt容器的鏡像格式。 

Registry:

統一保存共享鏡像的地方,叫作鏡像倉庫。

Image registry:docker 官方提供的私有倉庫部署工具。

Docker hub:docker官方的公共倉庫,已經保存了大量的經常使用鏡像,能夠方便你們直接使用。

Harbor:vmware 提供的自帶web的鏡像倉庫,目前有不少公司使用。

編排工具:

當多個容器在多個主機運行的時候,單獨管理每一個容器是至關負載並且很容易出錯,並且也沒法實現某一臺主機宕機後容器自動遷移到其餘主機從而實現高可用的目的,也沒法實現動態伸縮的功能,所以須要有一種工具能夠實現統一管理、動態伸縮、故障自愈、批量執行等功能,這就是容器編排引擎。

容器編排一般包括容器管理、調度、集羣定義和服務發現等功能。

Docker swarm:docker 開發的容器編排引擎。

Kubernetes:google領導開發的容器編排引擎,內部項目爲Borg,且其同時支持docker和CoreOS。

Mesos+Marathon:通用的集羣組員調度平臺,mesos與marathon一塊兒提供容器編排引擎功能。

2.3.8 docker容器的依賴技術

容器網絡:

docker自帶的網絡docker network僅支持管理單機上的容器網絡,當多主機運行的時候須要使用第三方開源網絡,例如calico、flannel等。 

服務發現:

容器的動態擴容特性決定了容器IP也會隨之變化,所以須要有一種機制開源自動識別並將用戶請求動態轉發到新建立的容器上,kubernetes自帶服務發現功能,須要結合kube-dns服務解析內部域名。 【如今是Core-dns】

容器監控:

能夠經過原生命令docker ps/top/stats 查看容器運行狀態,另外也能夠使heapster/ Prometheus等第三方監控工具監控容器的運行狀態。

數據管理:

容器的動態遷移會致使其在不一樣的Host之間遷移,所以如何保證與容器相關的數據也能隨之遷移或隨時訪問,能夠使用邏輯卷/存儲掛載等方式解決。 

日誌收集:

docker 原生的日誌查看工具docker logs,可是容器內部的日誌須要經過ELK等專門的日誌收集分析和展現工具進行處理。

2.3.9 docker命名空間【namespaces】

實現內核級虛擬化(容器)服務,讓同一個Namespace下的進程能夠感知彼此的變化,同時又能確保對外界的進程一無所知,以達到獨立和隔離的目的。

經過查看 /proc 目錄下以進程ID做爲名稱的子目錄中的信息,能瞭解該進程的一組Namespace ID

pid namespace

不一樣用戶的進程就是經過pid namespace隔離開的,且不一樣 namespace 中能夠有相同 PID。

具備如下特徵:

  每一個namespace中的pid是有本身的pid=1的進程(相似 /sbin/init 進程)

  每一個 namespace 中的進程只能影響本身的同一個 namespace 或子 namespace 中的進程

由於 /proc 包含正在運行的進程,所以在 container 中的 pseudo-filesystem 的 /proc 目錄只能看到本身namespace 中的進程

由於 namespace 容許嵌套,父 namespace 能夠影響子 namespace 的進程,因此子 namespace 的進程能夠在父namespace中看到,可是具備不一樣的 pid

mnt namespace

相似 chroot,將一個進程放到一個特定的目錄執行。mnt namespace 容許不一樣namespace的進程看到的文件結構不一樣,這樣每一個namespace 中的進程所看到的文件目錄就被隔離開了。同 chroot 不一樣,每一個 namespace 中的 container 在 /proc/mounts 的信息只包含所在namespace的mount point。

net namespace

網絡隔離是經過 net namespace 實現的, 每一個 net namespace 有獨立的 network devices, IP addresses, IP routing tables, /proc/net 目錄。這樣每一個 container 的網絡就能隔離開來。 docker 默認採用 veth 的方式將 container 中的虛擬網卡同 host 上的一個 docker bridge 鏈接在一塊兒。

uts namespace

UTS ("UNIX Time-sharing System") namespace 容許每一個 container 擁有獨立的 hostname 和 domain name, 使其在網絡上能夠被視做一個獨立的節點而非 Host 上的一個進程。

ipc namespace

container 中進程交互仍是採用 Linux 常見的進程間交互方法 (interprocess communication - IPC), 包括常見的信號量、消息隊列和共享內存。然而同 VM 不一樣,container 的進程間交互實際上仍是 host 上具備相同 pid namespace 中的進程間交互,所以須要在IPC資源申請時加入 namespace 信息 - 每一個 IPC 資源有一個惟一的 32bit ID。

user namespace

每一個 container 能夠有不一樣的 user 和 group id, 也就是說能夠以 container 內部的用戶在 container 內部執行程序而非 Host 上的用戶。

有了以上6種namespace從進程、網絡、IPC、文件系統、UTS 和用戶角度的隔離,一個 container 就能夠對外展示出一個獨立計算機的能力,而且不一樣container從OS層面實現了隔離。然而不一樣 namespace 之間資源仍是相互競爭的,仍然須要相似ulimit 來管理每一個container所能使用的資源。

2.3.10 docker資源配額【cgroups】

cgroups是Linux內核提供的一種能夠限制、記錄、隔離進程組所使用的物理資源(包括CPU、內存、磁盤I/O速度等)的機制,也是容器管理虛擬化系統資源的手段。

查看任意進程在/proc目錄下的內容,能夠看到一個名爲cgroup的文件,每一個掛載點都是一個CGroup子系統的根目錄

實現了對資源的配額和度量。cgroups的使用很是簡單,提供相似文件的接口,在/cgroup目錄下新建一個文件夾便可新建一個group,在此文件夾中新建 task 文件,並將 pid 寫入該文件,便可實現對該進程的資源控制。具體的資源配置選項能夠在該文件夾中新建子 subsystem ,{子系統前綴}.{資源項} 是典型的配置方法, 如 memory.usageinbytes 就定義了該 group 在 subsystem memory 中的一個內存限制選項。

另外,cgroups 中的 subsystem 能夠隨意組合,一個 subsystem 能夠在不一樣的 group 中,也能夠一個 group 包含多個 subsystem - 也就是說一個 subsystem。

在Linux 4.7.1內核中,已經支持了10類不一樣的子系統,分別以下所示:

hugetlb:  

限制進程對大頁內存(Hugepage)的使用

memory:  

限制進程對內存和Swap的使用,並生成每一個進程使用的內存資源報告

pids:  

限制每一個CGroup中可以建立的進程總數

cpuset:  

在多核系統中爲進程分配獨立CPU和內存

devices:  

容許或拒絕進程訪問特定設備

net_cls 和 net_prio: 

標記每一個網絡包,並控制網卡優先級

cpu 和 cpuacct:  

限制進程對CPU的用量,並生成每一個進程所使用的CPU報告

freezer:  

掛起或恢復特定的進程

blkio: 

爲進程對塊設備(如磁盤、USB等)限制輸入/輸出

perf_event: 

監測屬於特定的CGroup的全部線程以及運行在特定CPU上的線程

2.3.11 docker的工做原理

1)能夠創建一個容納應用程序的容器。

2)能夠從Docker鏡像建立Docker容器來運行應用程序。

3)能夠經過Docker Hub或者本身的Docker倉庫分享Docker鏡像。

docker鏡像是如何工做的

Docker鏡像是Docker容器運行時的只讀模板,每個鏡像由一系列的層(layers)組成;

Docker使用UnionFS(聯合文件系統)來將這些層聯合到一二鏡像中,UnionFS文件系統容許獨立文件系統中的文件和文件夾(稱之爲分支)被透明覆蓋,造成一個單獨連貫的文件系統。

正由於有了這些層(layers)的存在,Docker纔會如此的輕量。當你改變了一個Docker鏡像,好比升級到某個程序到新的版本,一個新的層會被建立。所以,不用替換整個原先的鏡像或者從新創建(在使用虛擬機的時候你可能會這麼作),只是一個新的層被添加或升級了。因此你不用從新發布整個鏡像,只須要升級。層使得分發Docker鏡像變得簡單和快速。

每一個鏡像都是從一個基礎的鏡像開始的,好比ubuntu,一個基礎的Ubuntu鏡像,或者是Centos,一個基礎的Centos鏡像。你能夠使用你本身的鏡像做爲新鏡像的基礎,例如你有一個基礎的安裝了Nginx的鏡像,你能夠使用該鏡像來創建你的Web應用程序鏡像。(Docker一般從Docker Hub獲取基礎鏡像)

Docker鏡像從這些基礎的鏡像建立,經過一種簡單、具備描述性的步驟,咱們稱之爲 指令(instructions)。

每個指令會在鏡像中建立一個新的層,指令能夠包含這些動做:

  1)運行一個命令。

  2)增長文件或者文件夾。

  3)建立一個環境變量。

  4)當運行容器的時候哪些程序會運行。

這些指令存儲在Dockerfile文件中。當你須要創建鏡像的時候,Docker能夠從Dockerfile中讀取這些指令而且運行,而後返回一個最終的鏡像。

docker倉庫是如何工做的

Docker倉庫是Docker鏡像的存儲倉庫。能夠推送鏡像到Docker倉庫中,而後在Docker客戶端,能夠從Docker倉庫中搜索鏡像。

Docker容器是如何工做的

一個Docker容器包含了一個操做系統、用戶添加的文件和元數據(meta-data)。每一個容器都是從鏡像創建的,鏡像告訴Docker容器內包含了什麼,當容器啓動時運行什麼程序,還有許多配置數據。

Docker鏡像是隻讀的,當Docker運行一個從鏡像創建的容器,它會在鏡像頂部添加一個可讀寫的層,應用程序能夠在這裏運行。

當運行docker容器時發生了什麼

使用docker命令時,Docker客戶端都告訴Docker守護進程運行一個容器。

docker run -i -t ubuntu /bin/bash

能夠來分析這個命令,Docker客戶端使用docker命令來運行,run參數代表客戶端要運行一個新的容器。

Docker客戶端要運行一個容器須要告訴Docker守護進程的最小參數信息是:

  1)這個容器從哪一個鏡像建立,這裏是ubuntu,基礎的Ubuntu鏡像。

  2)在容器中要運行的命令,這裏是/bin/bash,在容器中運行Bash shell。

那麼運行這個命令以後在底層發生了什麼呢?

按照順序,Docker作了這些事情:

  1)拉取ubuntu鏡像: Docker檢查ubuntu鏡像是否存在,若是在本地沒有該鏡像,Docker會從Docker Hub下載。若是鏡像已經存在,Docker會使用它來建立新的容器。

  2)建立新的容器: 當Docker有了這個鏡像以後,Docker會用它來建立一個新的容器。

  3)分配文件系統而且掛載一個可讀寫的層: 容器會在這個文件系統中建立,而且一個可讀寫的層被添加到鏡像中。

  4)分配網絡/橋接接口: 建立一個容許容器與本地主機通訊的網絡接口。

  5)設置一個IP地址: 從池中尋找一個可用的IP地址而且服加到容器上。

  6)運行你指定的程序: 運行指定的程序。

  7)捕獲而且提供應用輸出: 鏈接而且記錄標準輸出、輸入和錯誤讓你能夠看到你的程序是如何運行的。

由此你就能夠擁有一個運行着的Docker容器了!從這裏開始你能夠管理你的容器,與應用交互,應用完成以後,能夠中止或者刪除你的容器。

2.3.12 docker與openstack的對比

2.3.13 docker用途

簡單配置、代碼流水線管理、開發效率、應用隔離、服務器整合、調試能力、多租戶、快速部署

2.4 dcker的基本用法

2.4.1 docker安裝及驗證

官方網址:

https://www.docker.com/

系統版本選擇:

 

Docker 目前已經支持多種操做系統的安裝運行,好比Ubuntu、CentOS、Redhat、Debian、Fedora,甚至是還支持了Mac和Windows,在linux系統上須要內核版本在3.10或以上,docker版本號以前一直是0.X版本或1.X版本,可是從2017年31號開始改成每一個季度發佈一次穩版,其版本號規則也統一變動爲YY.MM,例如18.09表示是20189月份發佈的,本次演示的操做系統使用Centos 7.6爲例,內核4.4。

[root@docker ~]# cat /etc/redhat-release CentOS Linux release 7.6.1810 (Core) [root@docker ~]# uname -r 4.4.186-1.el7.elrepo.x86_64

Docker版本選擇:

  Docker以前沒有區分版本,可是2017年推出(將docker改名爲)新的項目Moby,github地址:https://github.com/moby/moby,Moby項目屬於Docker項目的全新上游,Docker將是一個隸屬於的Moby的子產品,並且以後的版本以後開始區分爲CE版本(社區版本)和EE(企業收費版),CE社區版本和EE企業版本都是每一個季度發佈一個新版本,可是EE版本提供後期安全維護1年,而CE版本是4個月,本次演示的Docker版本爲19.03

下載rpm包安裝:

官方rpm包下載地址: https://download.docker.com/linux/centos/7/x86_64/stable/Packages/

阿里鏡像下載地址:https://mirrors.aliyun.com/docker-ce/linux/centos/7/x86_64/stable/Packages/

經過yum源安裝:【經常使用】

[root@docker ~]# wget -O /etc/yum.repos.d/docker-ce.repo  https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
[root@docker ~]# yum repolist ...... * extras: mirrors.aliyun.com      --- > extras倉庫裏有docker安裝包,版本能夠經過Centos鏡像直接查看 ...... docker-ce-stable/x86_64     Docker CE Stable - x86_64 #安裝 [root@docker ~]# yum install docker-ce ...... 正在安裝: docker-ce       x86_64             3:19.03.1-3.el7         docker-ce-stable       24 M          52

啓動並驗證docker服務:

#啓動docker服務 [root@docker ~]# systemctl enable docker ; systemctl start docker;systemctl status docker|grep Active Active: active (running) since 一 2019-07-29 16:37:37 CST; 23s ago #驗證docker信息 [root@docker ~]# docker info Server Version: 19.03.1 Storage Driver: overlay2 Backing Filesystem: xfs Supports d_type: true Native Overlay Diff: true Logging Driver: json-file Cgroup Driver: cgroupfs Plugins: Volume: local Network: bridge host ipvlan macvlan null overlay Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog Swarm: inactive Runtimes: runc Default Runtime: runc Init Binary: docker-init containerd version: 894b81a4b802e4eb2a91d1ce216b8817763c29fb runc version: 425e105d5a03fabd737a126ad93d62a9eeede87f init version: fec3683 Security Options: seccomp Profile: default Kernel Version: 4.4.186-1.el7.elrepo.x86_64 Operating System: CentOS Linux 7 (Core) OSType: linux Architecture: x86_64 CPUs: 4 Total Memory: 1.936GiB Name: docker ID: 6EXA:7EIF:JC2F:W5SG:RG2U:7FDZ:TSI7:TTUF:2OMS:BDX2:TMDF:CFN7 Docker Root Dir: /var/lib/docker Debug Mode: false Registry: https://index.docker.io/v1/
 Labels: Experimental: false Insecure Registries: 127.0.0.0/8 Live Restore Enabled: false #驗證docker0網卡

在docker安裝啓動以後,默認會生成一個名稱爲docker0的網卡而且默認IP地址爲172.17.0.1的網卡

[root@docker ~]# ifconfig docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500 inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255 ether 02:42:22:7f:4a:b1  txqueuelen 0 (Ethernet) RX packets 0  bytes 0 (0.0 B) RX errors 0  dropped 0  overruns 0  frame 0 TX packets 0  bytes 0 (0.0 B) TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0 eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500 inet 20.0.0.209  netmask 255.255.255.0  broadcast 20.0.0.255 ether 00:0c:29:da:d7:53  txqueuelen 1000 (Ethernet) RX packets 79925  bytes 109388735 (104.3 MiB) RX errors 0  dropped 0  overruns 0  frame 0 TX packets 14335  bytes 1002691 (979.1 KiB) TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0 #查看docker網絡 [root@docker ~]# docker network list NETWORK ID NAME DRIVER SCOPE 064e0098dd15 bridge bridge local 48cf42c3b371 host host local 4c1006be1ea1 none null                local

docker存儲引擎

目前docker的默認存儲引擎爲overlay2,須要磁盤分區支持d-type文件分層功能,所以須要系統磁盤的額外支持。

官方文檔關於存儲引擎的選擇文檔:https://docs.docker.com/storage/storagedriver/select-storage-driver/

Docker官方推薦首選存儲引擎爲overlay2其次爲devicemapper,可是devicemapper存在使用空間方面的一些限制,雖然能夠經過後期配置解決,可是官方依然推薦使用overlay2,如下是網上查到的部分資料:http://www.javashuo.com/article/p-mrfruger-bh.html

[root@docker ~]# xfs_info / meta-data=/dev/sda3              isize=512    agcount=4, agsize=1179584 blks =                       sectsz=512   attr=2, projid32bit=1
         =                       crc=1        finobt=0 spinodes=0 data =                       bsize=4096   blocks=4718336, imaxpct=25
         =                       sunit=0      swidth=0 blks naming =version 2              bsize=4096   ascii-ci=0 ftype=1 log =internal               bsize=4096   blocks=2560, version=2
         =                       sectsz=512   sunit=0 blks, lazy-count=1 realtime =none                   extsz=4096   blocks=0, rtextents=0

若是docker數據目錄是一塊單獨的磁盤分區並且是xfs格式的,那麼須要在格式化的時候加上參數-n ftype=1,不然後期在啓動容器的時候會報錯不支持d-type。

報錯界面:

2.4.2 鏡像加速

由於國情的緣由,國內下載 Docker HUB 官方的相關鏡像比較慢,能夠使用國內(docker.io)的一些鏡像加速器,鏡像保持和官方一致,關鍵是速度塊,推薦使用。

這裏須要明確一個問題,就是Mirror與Private Registry的區別。兩者有着本質的差異:

1)Private Registry(私有倉庫)是開發者或者企業自建的鏡像存儲庫,一般用來保存企業內部的 Docker 鏡像,用於內部開發流程和產品的發佈、版本控制。

2)Mirror是一種代理中轉服務,咱們(好比daocloud)提供的Mirror服務,直接對接Docker Hub的官方Registry。Docker Hub 上有數以十萬計的各種 Docker 鏡像。

3)在使用Private Registry時,須要在Docker Pull 或Dockerfile中直接鍵入Private Registry 的地址,一般這樣會致使與 Private Registry 的綁定,缺少靈活性。

4)使用 Mirror 服務,只須要在 Docker 守護進程(Daemon)的配置文件中加入 Mirror 參數,便可在全局範圍內透明的訪問官方的 Docker Hub,避免了對 Dockerfile 鏡像引用來源的修改。

5)簡單來講,Mirror相似CDN,本質是官方的cache;Private Registry相似私服,跟官方沒什麼關係。對用戶來講,因爲用戶是要拖docker hub上的image,對應的是Mirror。 yum/apt-get的Mirror又有點不同,它實際上是把官方的庫文件整個拖到本身的服務器上作鏡像(無論有沒有用),並定時與官方作同步;而Docker Mirror只會緩存曾經使用過的image。

目前國內訪問docker hub速度上有點尷尬,使用docker Mirror勢在必行。

現有國內提供docker鏡像加速服務的商家有很多,下面重點介紹幾家:

(1). ustc的鏡像

ustc是老牌的linux鏡像服務提供者了,還在遙遠的ubuntu 5.04版本的時候就在用。以前在blog裏有提到能夠用ustc的docker倉庫鏡像.

使用方法參考ustc docker鏡像使用幫助

ustc的docker鏡像加速器速度很不錯,一直用的挺happy。ustc docker mirror的優點之一就是不須要註冊,真正是公共服務啊。

 

----------------------------------這裏順便說下在新版Docker裏使用ustc的作法------------------------------------- 新版的Docker配置方法: [root@localhost ~]# vim /etc/docker/daemon.json   //若是沒有該文件的話,就手動建立。在該文件裏添加下面內容
{   "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"] } 而後就能夠直接docker pull下載鏡像了,速度槓槓滴!!! [root@localhost docker]# docker pull ubuntu Using default tag: latest Trying to pull repository docker.io/library/ubuntu ... latest: Pulling from docker.io/library/ubuntu d54efb8db41d: Pull complete f8b845f45a87: Pull complete e8db7bf7c39f: Pull complete 9654c40e9079: Pull complete 6d9ef359eaaa: Pull complete Digest: sha256:dd7808d8792c9841d0b460122f1acf0a2dd1f56404f8d1e56298048885e45535 Status: Downloaded newer image for docker.io/ubuntu:latest [root@localhost ~]# docker images REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE docker.io/ubuntu    latest              0ef2e08ed3fa        2 weeks ago         130 MB ----------------------------------------------------------------------------------------------------------------------

 

(2). daocloud鏡像

DaoCloud也提供了docker加速器,可是跟ustc不一樣,須要用戶註冊後才能使用,而且每個月限制流量10GB。linux上使用比較簡單,一條腳本命令搞定:

 

curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://{your_id}.m.daocloud.io
實際上面的腳本執行後,改的是/usr/lib/systemd/system/docker.service文件,加了個–registry-mirror參數。若是不執行上面的腳本命令,能夠以下直接修改這個文件也可: ExecStart=/usr/bin/docker-current daemon --registry-mirror=http://{your_id}.m.daocloud.io\
設置後,須要從新加載配置&重啓: # systemctl enable docker # systemctl daemon-reload # systemctl restart docker 可是最近使用DaoCloud的docker加速器體驗很是差,加速效果不是很明顯。

 

(3). alicloud

阿里雲也提供了docker加速器,不過比daocloud更麻煩:不光要註冊爲阿里雲的用戶,還得加入開發者平臺。

不過雖然麻煩,可是它的服務還真是不錯,pull速度很溜!配置方法跟daocloud相似,也是開通加速器之後給一個url。

 

能夠直接去改/usr/lib/systemd/system/docker.service: ExecStart=/usr/bin/docker-current daemon --registry-mirror=https://{your_id}.mirror.aliyuncs.com\
從新加載配置&重啓: # systemctl enable docker # systemctl daemon-reload # systemctl restart docker pull的時候仍是顯示docker.io,但速度一點都不docker.io。 # docker pull ubuntu Using default tag: latest Trying to pull repository docker.io/library/ubuntu ... latest: Pulling from docker.io/library/ubuntu cad964aed91d: Pull complete 3a80a22fea63: Pull complete 50de990d7957: Pull complete 61e032b8f2cb: Pull complete 9f03ce1741bf: Pull complete Digest: sha256:28d4c5234db8d5a634d5e621c363d900f8f241240ee0a6a978784c978fe9c737 Status: Downloaded newer image for docker.io/ubuntu:latest

 

(4). 網易鏡像

網易也提供了Docker鏡像服務:網易蜂巢

 echo "DOCKER_OPTS=\"\$DOCKER_OPTS --registry-mirror=http://hub-mirror.c.163.com\"" >> /etc/default/docker service docker restart

綜上,雖然aliyun docker mirror用以前的流程有點繁瑣,但服務講真是很不錯的。

我在本次學習中使用的是以下:

[root@docker ~]# sudo mkdir -p /etc/docker [root@docker ~]# sudo tee /etc/docker/daemon.json <<-'EOF'
> { >   "registry-mirrors": ["https://llpuz83z.mirror.aliyuncs.com"] > } > EOF { "registry-mirrors": ["https://llpuz83z.mirror.aliyuncs.com"] } [root@docker ~]# sudo systemctl daemon-reload [root@docker ~]# sudo systemctl restart docker [root@docker ~]# docker info Registry Mirrors: https://llpuz83z.mirror.aliyuncs.com/

2.4.3 docker鏡像基礎命令

[root@docker ~]# docker --help Usage: docker [OPTIONS] COMMAND Commands: attach Attach to a running container # 當前 shell 下 attach 鏈接指定運行鏡像 build Build an image from a Dockerfile # 經過 Dockerfile 定製鏡像 commit Create a new image from a container's changes # 提交當前容器爲新的鏡像
    cp        Copy files/folders from the containers filesystem to the host path # 從容器中拷貝指定文件或者目錄到宿主機中 create Create a new container # 建立一個新的容器,同 run,但不啓動容器 diff Inspect changes on a container's filesystem # 查看 docker 容器變化
    events    Get real time events from the server # 從 docker 服務獲取容器實時事件 exec Run a command in an existing container # 在已存在的容器上運行命令 export Stream the contents of a container as a tar archive # 導出容器的內容流做爲一個 tar 歸檔文件[對應 import ] history Show the history of an image # 展現一個鏡像造成歷史 images List images # 列出系統當前鏡像 import Create a new filesystem image from the contents of a tarball # 從tar包中的內容建立一個新的文件系統映像[對應 export] info Display system-wide information # 顯示系統相關信息 inspect Return low-level information on a container # 查看容器詳細信息 kill Kill a running container # kill 指定 docker 容器 load Load an image from a tar archive # 從一個 tar 包中加載一個鏡像[對應 save] login Register or Login to the docker registry server # 註冊或者登錄一個 docker 源服務器 logout Log out from a Docker registry server # 從當前 Docker registry 退出 logs Fetch the logs of a container # 輸出當前容器日誌信息 port Lookup the public-facing port which is NAT-ed to PRIVATE_PORT # 查看映射端口對應的容器內部源端口 pause Pause all processes within a container # 暫停容器 ps List containers # 列出容器列表 pull Pull an image or a repository from the docker registry server # 從docker鏡像源服務器拉取指定鏡像或者庫鏡像 push Push an image or a repository to the docker registry server # 推送指定鏡像或者庫鏡像至docker源服務器 restart Restart a running container # 重啓運行的容器 rm Remove one or more containers # 移除一個或者多個容器 rmi Remove one or more images # 移除一個或多個鏡像[無容器使用該鏡像纔可刪除,不然需刪除相關容器纔可繼續或 -f 強制刪除] run Run a command in a new container # 建立一個新的容器並運行一個命令 save Save an image to a tar archive # 保存一個鏡像爲一個 tar 包[對應 load] search Search for an image on the Docker Hub # 在 docker hub 中搜索鏡像 start Start a stopped containers # 啓動容器 stop Stop a running containers # 中止容器 tag Tag an image into a repository # 給源中鏡像打標籤 top Lookup the running processes of a container # 查看容器中運行的進程信息 unpause Unpause a paused container # 取消暫停容器 version Show the docker version information # 查看 docker 版本號 wait Block until a container stops, then print its exit code # 截取容器中止時的退出狀態值

大多數子命令下都用選項,須要用到時--help下。

docker 命令是最常使用的命令,其後面能夠加不一樣的參數以實現響應的功能,經常使用的命令以下:

#查看docker版本信息 [root@docker ~]# docker --version Docker version 19.03.1, build 74b1e89 [root@docker ~]# docker info #搜索鏡像 在官方的docker 倉庫中搜索指定名稱的docker鏡像,也會有不少三方鏡像。 [root@docker ~]# docker search nginx #不帶版本號默認latest [root@docker ~]# docker search nginx:1.17 #帶指定版本號 從站點查看搜索鏡像 https://hub.docker.com/ -->nginx -->nginx -->Tags

#下載鏡像 [root@docker ~]# docker pull busybox Using default tag: latest latest: Pulling from library/busybox ee153a04d683: Pull complete Digest: sha256:9f1003c480699be56815db0f8146ad2e22efea85129b5b5983d0e0fb52d9ab70 Status: Downloaded newer image for busybox:latest docker.io/library/busybox:latest BusyBox是一種特殊類型的程序,它將許多重要程序與標準UNIX命令(如Coreutils) 「打包」到一個可執行文件中(有時稱爲其打包方法)。BusyBox 可執行文件被設計爲Linux上最小的可執行文件,與安裝每一個命令的可執行文件相比,能夠大大減小磁盤使用量。所以,應用程序特定的Linux發行版和嵌入式系統都適合,「 嵌入式Linux的Jittoku刀也稱爲」。它是GPLv2中發佈的免費軟件。 從功能上講,它相似於crunchgen命令,它是1994年由馬里蘭大學帕克分校的James da Silva開發的FreeBSD程序。 #查看本地鏡像 下載完成的鏡像比下載的大,由於下載完成後會解壓 [root@docker ~]# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE busybox latest db8ee88ad75f 10 days ago         1.22MB REPOSITORY            #鏡像所屬的倉庫名稱 TAG                   #鏡像版本號(標識符),默認爲latest IMAGE ID              #鏡像惟一ID標示 CREATED               #鏡像建立時間 VIRTUAL SIZE          #鏡像的大小 #刪除鏡像 [root@docker ~]# docker image rm busybox:latest Untagged: busybox:latest Untagged: busybox@sha256:9f1003c480699be56815db0f8146ad2e22efea85129b5b5983d0e0fb52d9ab70 Deleted: sha256:db8ee88ad75f6bdc74663f4992a185e2722fa29573abcc1a19186cc5ec09dceb Deleted: sha256:0d315111b4847e8cd50514ca19657d1e8d827f4e128d172ce8b2f76a04f3faea #docker 鏡像導入導出(import export)和加載保存(load,save) save和load是一對,export和import是一對 export和import是對容器來說的,export時會對容器作快照保存下來,import時能夠從新命名鏡像,也能夠根據文件或url或目錄來建立鏡像(import命令比較複雜,推薦看文檔來嘗試) docker export container_id/container_name > latest.tar docker import latest.tar image:tag save和load對鏡像來說的,save時可能會保存全部層,以後能夠層回滾(我還沒試) docker save > latest.tar image[:tag] #將image(可指定tag,不指定默認全部)打包 docker load < latest.tar 區別 docker import能夠從新指定鏡像的名字,docker load不能夠 export導出的鏡像文件大小  小於 save保存的鏡像 咱們發現導出後的版本會比原來的版本稍微小一些。那是由於導出後,會丟失歷史和元數據。執行下面的命令就知道了:  顯示鏡像的全部層(layer)  docker images --tree  執行命令,顯示下面的內容。正如看到的,導出後再導入(exported-imported)的鏡像會丟失全部的歷史,而保存後再加載(saveed-loaded)的鏡像沒有丟失歷史和層(layer)。這意味着使用導出後再導入的方式,你將沒法回滾到以前的層(layer),同時,使用保存後再加載的方式持久化整個鏡像,就能夠作到層回滾(能夠執行docker tag 來回滾以前的層)。

2.4.4 docker容器操做基礎命令

docker最核心的命令:docker run

docker run  [選項]  [鏡像名]  [shell命令]  [參數]

[root@docker ~]# docker run --help -d, --detach=false 指定容器運行於前臺仍是後臺,默認爲false -i, --interactive=false 打開STDIN,用於控制檯交互 -t, --tty=false 分配tty設備,該能夠支持終端登陸,默認爲false -u, --user="" 指定容器的用戶 -a, --attach=[] 登陸容器(必須是以docker run -d啓動的容器) -w, --workdir="" 指定容器的工做目錄 -c, --cpu-shares=0 設置容器CPU權重,在CPU共享場景使用 -e, --env=[] 指定環境變量,容器中能夠使用該環境變量 -m, --memory="" 指定容器的內存上限 -P, --publish-all=false 指定容器暴露的端口 -p, --publish=[] 指定容器暴露的端口 -h, --hostname="" 指定容器的主機名 -v, --volume=[] 給容器掛載存儲卷,掛載到容器的某個目錄 --volumes-from=[] 給容器掛載其餘容器上的卷,掛載到容器的某個目錄 --cap-add=[] 添加權限,權限清單詳見:http://linux.die.net/man/7/capabilities 
--cap-drop=[] 刪除權限,權限清單詳見:http://linux.die.net/man/7/capabilities 
--cidfile="" 運行容器後,在指定文件中寫入容器PID值,一種典型的監控系統用法 --cpuset="" 設置容器能夠使用哪些CPU,此參數能夠用來容器獨佔CPU --device=[] 添加主機設備給容器,至關於設備直通 --dns=[] 指定容器的dns服務器 --dns-search=[] 指定容器的dns搜索域名,寫入到容器的/etc/resolv.conf文件 --entrypoint="" 覆蓋image的入口點 --env-file=[] 指定環境變量文件,文件格式爲每行一個環境變量 --expose=[] 指定容器暴露的端口,即修改鏡像的暴露端口 --link=[] 指定容器間的關聯,使用其餘容器的IP、env等信息 --lxc-conf=[] 指定容器的配置文件,只有在指定--exec-driver=lxc時使用 --name="" 指定容器名字,後續能夠經過名字進行容器管理,links特性須要使用名字 --net="bridge" 容器網絡設置: bridge 使用docker daemon指定的網橋 host //容器使用主機的網絡 
container:NAME_or_ID >//使用其餘容器的網路,共享IP和PORT等網絡資源 
none 容器使用本身的網絡(相似--net=bridge),可是不進行配置 --privileged=false 指定容器是否爲特權容器,特權容器擁有全部的capabilities --restart="no" 指定容器中止後的重啓策略: no:容器退出時不重啓 on-failure:容器故障退出(返回值非零)時重啓 always:容器退出時老是重啓 --rm=false 指定容器中止後自動刪除容器(不支持以docker run -d啓動的容器) --sig-proxy=true 設置由代理接受並處理信號,可是SIGCHLD、SIGSTOP和SIGKILL不能被代理 #從鏡像啓動一個容器 會直接進入到容器,並隨機生成容器ID和名稱 [root@docker ~]# docker run --name b1 -it busybox:latest  ---> --name 自定義容器名稱 Unable to find image 'busybox:latest' locally   ---> 本地沒有 就從默認的公有倉庫中拉取 latest: Pulling from library/busybox ee153a04d683: Pull complete Digest: sha256:9f1003c480699be56815db0f8146ad2e22efea85129b5b5983d0e0fb52d9ab70 Status: Downloaded newer image for busybox:latest / #                            ---> 直接進入到容器 / # ps    ---> 默認狀況下進入的是sh PID USER TIME COMMAND 1 root      0:00 sh 8 root      0:00 ps / # ls / bin dev etc home proc root sys tmp usr var    ---> 都是busybox的別名 / # hostname     ---> 隨機生成的容器ID 02379c036216 #退出容器不註銷 / # exit     ---> exit或者ctrl + d 退出容器 容器是退出的 [root@docker ~]# docker ps   ---> 顯示正在運行的容器 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES [root@docker ~]# docker container ls  --->列出正在運行的容器 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES [root@docker ~]# docker ps -a    ---> 顯示全部容器,包括當前正在運行以及已經關閉的全部容器 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 02379c036216 busybox:latest "sh"                4 minutes ago       Exited (0) 14 seconds ago b1 [root@docker ~]# docker container ls -a   ---> 顯示全部容器,包括當前正在運行以及已經關閉的全部容器 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 02379c036216 busybox:latest "sh"                38 minutes ago      Exited (1) About a minute ago b1 [root@docker ~]# docker exec -it b1 sh   ---> 進入容器 Error response from daemon: Container 02379c036216092f07c46c58361c2da6b5df7d15a733b3c39cfc984cd2dcf314 is not running [root@docker ~]# docker start b1    ---> 啓動暫定的容器 b1 [root@docker ~]# docker exec -it b1 sh / # hostname 02379c036216 #容器暫停不刪除是對容器原有資源信息無影響的 ctrl +p +q 操做,退出容器,容器依然運行 [root@docker ~]# docker exec -it b1 sh / # read escape sequence [root@docker ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 02379c036216 busybox:latest "sh"                17 minutes ago      Up 4 minutes                            b1

 中止容器的另外一種方法:

進入容器的方法對比

方法一: docker attach container_NAME/container_ID

注:當多個窗口使用該命令進入該容器時,全部窗口都會顯示同步。若是一個窗口阻塞了,其餘窗口沒法再進行操做;。所以docker attach命令不太適合於生產環境。且該命令有點古老,不太建議使用

方法二:使用ssh 進入docker容器

docker應用容器是一個Linux虛擬主機,那麼就能夠在該主機上面安裝一個ssh server 就能夠經過ssh協議來連接該容器

注:這種出力不討好的方法,瞭解一下就能夠了。

方法三:使用nsenter進入docker容器

對於nsenter網上有比較多且詳細的介紹,這裏我大體寫一下操做過程

nsenter命令須要經過PID進入到容器內部,不過能夠使用docker inspect獲取到容器的PID

# 安裝nsenter wget https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz 
tar -xzvf util-linux-2.24.tar.gz cd util-linux-2.24/ ./configure --without-ncurses make nsenter cp nsenter /usr/local/bin nsenter --help # nsenter能夠訪問另外一個進程名稱空間。所以咱們須要獲取容器的PID docker inspect -f {{.State.Pid}} container_NAME/container_ID // 假設進程號爲 4426
nsenter --target 目標PID --mount --uts --ipc --net --pid 簡寫: nsenter -t 目標PID -m -u -i -n -p #--target 4426 目標pid 腳本方式: 將nsenter命令寫入到腳本進行調用 vim /service/scripts/docker-in.sh #!/bin/bash docker_in(){   NAME_ID=$1   PID=$(docker inspect -f "{{.State.Pid}}" ${NAME_ID})   nsenter -t ${PID} -m -u -i -n -p } docker_in $1 chmod a+x /service/scripts/docker-in.sh #測試腳本是否能夠正常進入到容器且退出後仍正常運行 /service/scripts/docker-in.sh container_NAME/container_ID exit /service/scripts/docker-in.sh container_NAME/container_ID exit

注:我的理解nsenter:經過容器在宿主機中的pid進行通信

所以:nsenter須要在宿主機安裝而非容器或者鏡像

方法四:docker exec 命令

[root@docker ~]# docker exec --help Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...] Run a command in a running container Options: -d, --detach               Detached mode: run command in the background --detach-keys string   Override the key sequence for detaching a container -e, --env list Set environment variables -i, --interactive          Keep STDIN open even if not attached --privileged Give extended privileges to the command -t, --tty                  Allocate a pseudo-TTY -u, --user string          Username or UID (format: <name|uid>[:<group|gid>]) -w, --workdir string Working directory inside the container [root@docker ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 02379c036216 busybox:latest "sh"                33 minutes ago      Up 12 minutes b1 [root@docker ~]# docker exec -it b1 sh

注:最經常使用的方法!!!

#刪除運行中的容器 即便容器正在運行中,也會被強制刪除 [root@docker ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 02379c036216 busybox:latest "sh"                39 minutes ago      Up 2 seconds b1 [root@docker ~]# docker container rm -f b1 b1 [root@docker ~]# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

端口映射

#隨機映射端口
[root@docker ~]# docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest db8ee88ad75f 11 days ago 1.22MB
[root@docker ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[root@docker ~]# docker pull nginx
docker.io/library/nginx:latest

 

注:隨機端口映射,其實默認是從1024開始

瀏覽器訪問

#指定端口映射 方式1:本地端口81映射到容器80端口: # docker run -d -p 81:80 --name nginx-test-port1 nginx    --> -d 後臺啓動容器 方式2:本地IP:本地端口:容器端口 # docker run -d -p 20.0.0.209:82:80 --name nginx-test-port2 docker.io/nginx 方式3:本地IP:本地隨機端口:容器端口 # docker run  -d -p 20.0.0.209::80 --name nginx-test-port3 docker.io/nginx 方式4:本機ip:本地端口:容器端口/協議,默認爲tcp協議 #  docker run -d -p 20.0.0.209:83:80/udp  --name nginx-test-port4 docker.io/nginx 方式5:一次性映射多個端口+協議: # docker run  -d -p 86:80/tcp  -p 443:443/tcp -p 53:53/udp --name  nginx-test-port5 docker.io/nginx

查看容器的日誌

#一次查看 [root@docker ~]# docker logs nginx-test-port2 20.0.0.1 - - [30/Jul/2019:03:42:17 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
20.0.0.1 - - [30/Jul/2019:03:42:17 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://20.0.0.209:82/" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
2019/07/30 03:42:17 [error] 6#6: *2 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 20.0.0.1, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "20.0.0.209:82", referrer: "http://20.0.0.209:82/" #持續查看 [root@docker ~]# docker logs -f nginx-test-port2 20.0.0.1 - - [30/Jul/2019:03:42:17 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
20.0.0.1 - - [30/Jul/2019:03:42:17 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://20.0.0.209:82/" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
2019/07/30 03:42:17 [error] 6#6: *2 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 20.0.0.1, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "20.0.0.209:82", referrer: "http://20.0.0.209:82/"
20.0.0.1 - - [30/Jul/2019:03:43:07 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
20.0.0.1 - - [30/Jul/2019:03:43:08 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
20.0.0.1 - - [30/Jul/2019:03:43:08 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
20.0.0.1 - - [30/Jul/2019:03:43:08 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
20.0.0.1 - - [30/Jul/2019:03:43:08 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"

查看容器已經映射的端口

[root@docker ~]# docker port nginx-test-port3 80/tcp -> 20.0.0.209:1024

容器退出後自動刪除

[root@docker ~]# docker run -it  --rm --name nginx nginx bash

傳遞運行命令

容器須要有一個前臺運行的進程才能保持容器的運行,經過傳遞運行參數是一種方式,另外也能夠在構建鏡像的時候指定容器啓動時運行的前臺命令。 [root@docker ~]# docker run -d centos /usr/bin/tail -f '/etc/hosts' [root@docker ~]# docker ps -l CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c8a7605ede61 centos "/usr/bin/tail -f /e…"   32 seconds ago      Up 31 seconds                           strange_curie

容器的啓動和關閉

docker stop/start  container_NAME/container_ID

批量操做

#批量關閉正在運行的容器 [root@docker ~]# docker stop  $(docker ps -a -q)    --->正常關閉全部運行中的容器 #批量強制關閉正在運行的容器 [root@docker ~]# docker kill  $(docker ps -a -q)   --->強制關閉全部運行中的容器 #批量刪除已退出的容器 [root@docker ~]# docker rm -f  `docker ps -aq -f status=exited` #批量刪除全部容器 [root@docker ~]# docker rm -f  `docker ps -a -q`

一個例子

[root@docker ~]# docker run --name text -it --network bridge -h zisefeizhu.com --dns 223.6.6.6 --rm busybox:latest / # hostname zisefeizhu.com / # cat /etc/resolv.conf nameserver 223.6.6.6
/ # exit [root@docker ~]# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

2.5 docker鏡像技術

一個鏡像是一個惰性的,不可變的文件,它本質上是一個容器的快照。它只是一個模板,其中包含有關建立Docker容器的說明。

鏡像存儲在Docker倉庫中,例如registry.hub.docker.com。由於它們可能變得很是大,因此鏡像被設計爲由其餘鏡像的層組成,容許在經過網絡傳輸圖像時發送最少許的數據。

docker 鏡像是一個只讀的 docker 容器模板,含有啓動 docker 容器所需的文件系統結構及其內容,所以是啓動一個 docker 容器的基礎。docker 鏡像的文件內容以及一些運行 docker 容器的配置文件組成了 docker 容器的靜態文件系統運行環境:rootfs。能夠這麼理解,docker 鏡像是 docker 容器的靜態視角,docker 容器是 docker 鏡像的運行狀態。咱們能夠經過下圖來理解 docker daemon、docker 鏡像以及 docker 容器三者的關係(此圖來自互聯網):

從上圖中咱們能夠看到,當由 ubuntu:14.04 鏡像啓動容器時,ubuntu:14.04 鏡像的鏡像層內容將做爲容器的 rootfs;而 ubuntu:14.04 鏡像的 json 文件,會由 docker daemon 解析,並提取出其中的容器執行入口 CMD 信息,以及容器進程的環境變量 ENV 信息,最終初始化容器進程。固然,容器進程的執行入口來源於鏡像提供的 rootfs。

rootfs

rootfs 是 docker 容器在啓動時內部進程可見的文件系統,即 docker 容器的根目錄。rootfs 一般包含一個操做系統運行所需的文件系統,例如可能包含典型的類 Unix 操做系統中的目錄系統,如 /dev、/proc、/bin、/etc、/lib、/usr、/tmp 及運行 docker 容器所需的配置文件、工具等。

在傳統的 Linux 操做系統內核啓動時,首先掛載一個只讀的 rootfs,當系統檢測其完整性以後,再將其切換爲讀寫模式。而在 docker 架構中,當 docker daemon 爲 docker 容器掛載 rootfs 時,沿用了 Linux 內核啓動時的作法,即將 rootfs 設爲只讀模式。在掛載完畢以後,利用聯合掛載(union mount)技術在已有的只讀 rootfs 上再掛載一個讀寫層。這樣,可讀寫的層處於 docker 容器文件系統的最頂層,其下可能聯合掛載了多個只讀的層,只有在 docker 容器運行過程當中文件系統發生變化時,纔會把變化的文件內容寫到可讀寫層,並隱藏只讀層中的舊版本文件。

2.5.1 Docker 鏡像的主要特色

爲了更好的理解 docker 鏡像的結構,下面介紹一下 docker 鏡像設計上的關鍵技術。

分層

docker 鏡像是採用分層的方式構建的,每一個鏡像都由一系列的 "鏡像層" 組成。分層結構是 docker 鏡像如此輕量的重要緣由。當須要修改容器鏡像內的某個文件時,只對處於最上方的讀寫層進行變更,不覆寫下層已有文件系統的內容,已有文件在只讀層中的原始版本仍然存在,但會被讀寫層中的新版本所隱藏。當使用 docker commit 提交這個修改過的容器文件系統爲一個新的鏡像時,保存的內容僅爲最上層讀寫文件系統中被更新過的文件。分層達到了在不的容器同鏡像之間共享鏡像層的效果。

分層技術——aufs

 

  Aufs是Another Union File System的縮寫,支持將多個目錄掛載到同一個虛擬目錄下。

  已構建的鏡像會設置成只讀模式,read-write寫操做是在read-only上的一種增量操做,固不影響read-only層。

  這個研究有一個好處,好比咱們如今能夠看到手機裏面的APP,在命令裏面都會用APP字段下回來,在下回來以前它就是一個靜態的,咱們沒有往裏面寫東西,可是咱們啓動起來之後,咱們就能夠往裏面寫東西,進行各類各樣的操做。可是若是咱們把它關掉了之後,或者刪除了之後,它的這個鏡像是存在遠端的,因此在這個鏡像裏面是不會去修改的。而且這樣也會有一個很是好的地方,這個場景很是適合咱們去實現測試環境,由於咱們的測試環境常常會有一個操做就是灌數據,咱們能夠提早把這個鏡像數據打包到測試裏面,那麼這個鏡像軟件裏面包含了,最上面是nginx,好比它裏面會有一些數據,咱們能夠在往上面打一層數據,打完以後把它起成一個容器就能夠去測試,測試完以後這個容器裏面會生成各類各樣的數據,也就是髒數據,這樣的話,咱們就能夠把這個容器刪掉,刪掉之後咱們鏡像裏面的容器是不會受影響的。若是說它想再建立一套,咱們能夠把這個鏡像再啓一個容器,就能夠是一個如出一轍的,而且是一個乾淨的環境。

  咱們先來看一個Ubuntu系統的鏡像

 

咱們看見鏡像能夠分層不少個layer,而且他們都有大小和ID,咱們能夠看到這裏有4個layer ID號,最終這個鏡像是由他們layer組合而成,而且這個鏡像它是隻讀的,它不能往裏面寫數據,若是想寫數據怎麼辦呢?咱們會在鏡像上啓一層contain layer,其實就是至關於把鏡像啓動成一個容器,那麼在容器這一層,咱們是可寫的。

 

好比咱們想在Ubuntu這個系統上加一層,只能在上面繼續疊加,這些工做其實都是由cow,寫字庫下的機制來實現的。

寫時複製

docker 鏡像使用了寫時複製(copy-on-write)的策略,在多個容器之間共享鏡像,每一個容器在啓動的時候並不須要單獨複製一份鏡像文件,而是將全部鏡像層以只讀的方式掛載到一個掛載點,再在上面覆蓋一個可讀寫的容器層。在未更改文件內容時,全部容器共享同一份數據,只有在 docker 容器運行過程當中文件系統發生變化時,纔會把變化的文件內容寫到可讀寫層,並隱藏只讀層中的老版本文件。寫時複製配合分層機制減小了鏡像對磁盤空間的佔用和容器啓動時間。

內容尋址

在 docker 1.10 版本後,docker 鏡像改動較大,其中最重要的特性即是引入了內容尋址存儲(content-addressable storage) 的機制,根據文件的內容來索引鏡像和鏡像層。與以前版本對每一個鏡像層隨機生成一個 UUID 不一樣,新模型對鏡像層的內容計算校驗和,生成一個內容哈希值,並以此哈希值代替以前的 UUID 做爲鏡像層的惟一標識。該機制主要提升了鏡像的安全性,並在 pull、push、load 和 save 操做後檢測數據的完整性。另外,基於內容哈希來索引鏡像層,在必定程度上減小了 ID 的衝突而且加強了鏡像層的共享。對於來自不一樣構建的鏡像層,主要擁有相同的內容哈希,也能被不一樣的鏡像共享。

聯合掛載

通俗地講,聯合掛載技術能夠在一個掛載點同時掛載多個文件系統,將掛載點的原目錄與被掛載內容進行整合,使得最終可見的文件系統將會包含整合以後的各層的文件和目錄。實現這種聯合掛載技術的文件系統一般被稱爲聯合文件系統(union filesystem)。如下圖所示的運行 Ubuntu:14.04 鏡像後的容器中的 aufs 文件系統爲例:

因爲初始掛載時讀寫層爲空,因此從用戶的角度看,該容器的文件系統與底層的 rootfs 沒有差異;然而從內核的角度看,則是顯式區分開來的兩個層次。當須要修改鏡像內的某個文件時,只對處於最上方的讀寫層進行了變更,不復寫下層已有文件系統的內容,已有文件在只讀層中的原始版本仍然存在,但會被讀寫層中的新版本文件所隱藏,當 docker commit 這個修改過的容器文件系統爲一個新的鏡像時,保存的內容僅爲最上層讀寫文件系統中被更新過的文件。

聯合掛載是用於將多個鏡像層的文件系統掛載到一個掛載點來實現一個統一文件系統視圖的途徑,是下層存儲驅動(aufs、overlay等) 實現分層合併的方式。因此嚴格來講,聯合掛載並非 docker 鏡像的必需技術,好比在使用 device mapper 存儲驅動時,實際上是使用了快照技術來達到分層的效果。

2.5.2 docker鏡像的存儲組織方式

綜合考慮鏡像的層級結構,以及 volume、init-layer、可讀寫層這些概念,一個完整的、在運行的容器的全部文件系統結構能夠用下圖來描述:

從圖中咱們不難看到,除了 echo hello 進程所在的 cgroups 和 namespace 環境以外,容器文件系統實際上是一個相對獨立的組織。可讀寫部分(read-write layer 以及 volumes)、init-layer、只讀層(read-only layer) 這 3 部分結構共同組成了一個容器所需的下層文件系統,它們經過聯合掛載的方式巧妙地表現爲一層,使得容器進程對這些層的存在一無所知。

2.5.3 docker鏡像中的關鍵概念

registry

咱們知道,每一個 docker 容器都要依賴 docker 鏡像。那麼當咱們第一次使用 docker run 命令啓動一個容器時,是從哪裏獲取所需的鏡像呢?

答案是,若是是第一次基於某個鏡像啓動容器,且宿主機上並不存在所需的鏡像,那麼 docker 將從 registry 中下載該鏡像並保存到宿主機。若是宿主機上存在該鏡像,則直接使用宿主機上的鏡像完成容器的啓動。

那麼 registry 是什麼呢?

registry 用以保存 docker 鏡像,其中還包括鏡像層次結構和關於鏡像的元數據。能夠將 registry 簡單的想象成相似於 Git 倉庫之類的實體。

用戶能夠在本身的數據中心搭建私有的 registry,也能夠使用 docker 官方的公用 registry 服務,即 Docker Hub。它是由 Docker 公司維護的一個公共鏡像庫。Docker Hub 中有兩種類型的倉庫,即用戶倉庫(user repository) 與頂層倉庫(top-level repository)。用戶倉庫由普通的 Docker Hub 用戶建立,頂層倉庫則由 Docker 公司負責維護,提供官方版本鏡像。理論上,頂層倉庫中的鏡像通過 Docker 公司驗證,被認爲是架構良好且安全的。

分類:

  ​Sponsor Registry: 第三方的registry,供客戶和Docker社區使用

   Mirror  Registry: 第三方的registry,只讓客戶使用

   Vendor  Registry: 由發佈Docker鏡像的供應商提供的registry

   Private Registry: 經過設有防火牆和額外的安全層的私有實體提供的registry

repository

repository 由具備某個功能的 docker 鏡像的全部迭代版本構成的鏡像組。Registry 由一系列通過命名的 repository 組成,repository 經過命名規範對用戶倉庫和頂層倉庫進行組織。所謂的頂層倉庫,其其名稱只包含倉庫名,如:

 

而用戶倉庫的表示相似下面:

能夠看出,用戶倉庫的名稱多了 "用戶名/" 部分。

比較容易讓人困惑的地方在於,咱們常常把 mysql 視爲鏡像的名稱,其實 mysql 是 repository 的名稱。repository 是一個鏡像的集合,其中包含了多個不一樣版本的鏡像,這些鏡像之間使用標籤進行版本區分,如 mysql:5.六、mysql:5.7 等,它們均屬於 mysql 這個 repository。

簡單來講,registry 是 repository 的集合,repository 是鏡像的集合

 manifest

manifest(描述文件)主要存在於 registry 中做爲 docker 鏡像的元數據文件,在 pull、push、save 和 load 過程當中做爲鏡像結構和基礎信息的描述文件。在鏡像被 pull 或者 load 到 docker 宿主機時,manifest 被轉化爲本地的鏡像配置文件 config。在咱們拉取鏡像時顯示的摘要(Digest):就是對鏡像的 manifest 內容計算 sha256sum 獲得的。

image 和 layer

docker 內部的 image 概念是用來存儲一組鏡像相關的元數據信息,主要包括鏡像的架構(如 amd64)、鏡像默認配置信息、構建鏡像的容器配置信息、包含全部鏡像層信息的 rootfs。docker 利用 rootfs 中的 diff_id 計算出內容尋址的索引(chainID) 來獲取 layer 相關信息,進而獲取每個鏡像層的文件內容。

layer(鏡像層) 是 docker 用來管理鏡像層的一箇中間概念。咱們前面提到,鏡像是由鏡像層組成的,而單個鏡像層可能被多個鏡像共享,因此 docker 將 layer 與 image 的概念分離。docker 鏡像管理中的 layer 主要存放了鏡像層的 diff_id、size、cache-id 和 parent 等內容,實際的文件內容則是由存儲驅動來管理,並能夠經過 cache-id 在本地索引到。

2.5.4 獲取docker鏡像的方法 

docker.hub

如今docker官方公有倉庫裏面有大量的鏡像,因此最基礎的鏡像,咱們能夠在公有倉庫直接拉取,由於這些鏡像都是原廠維護,能夠獲得及時的更新和修護。

Dockerfile:

咱們若是想去定製這些鏡像,咱們能夠去編寫Dockerfile,而後從新bulid,最後把它打包成一個鏡像,這種方式是最爲推薦的方式包括咱們之後去企業當中去實踐應用的時候也是推薦這種方式。

 

Commit :

固然還有另一種方式,就是經過鏡像啓動一個容器,而後進行操做,最終經過commit這個命令commit一個鏡像,可是不推薦這種方式,雖說經過commit這個命令像是操做虛擬機的模式,可是容器畢竟是容器,它不是虛擬機,因此你們仍是要去適應用Dockerfile去定製這些鏡像這種習慣。

[root@docker ~]# docker commit --help Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]] Create a new image from a container's changes
Options: -a, --author string    Author (e.g., "John Hannibal Smith <hannibal@a-team.com>") -c, --change list Apply Dockerfile instruction to the created image -m, --message string Commit message -p, --pause            Pause container during commit (default true)

 

2.5.5 鏡像的一個例子詳解

#從默認倉庫拉取buxybox鏡像,默認爲docker.hub [root@docker ~]# docker pull buxybox #查看主機現有鏡像 [root@docker ~]# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE busybox latest db8ee88ad75f 13 days ago         1.22MB #用buxybox鏡像啓動一個名稱爲b1的容器 [root@docker ~]# docker run --name b1 -it busybox / # ls / bin dev etc home proc root sys tmp usr var #在b1容器中創先一個index.html / # mkdir -p /data/html / # vi /data/html/index.html / # cat /data/html/index.html #ctrl+p+q退出容器不關閉 <h1> Busybox httpd server. </h1>
/ # [root@docker ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7b030688d7c6 busybox "sh" About a minute ago Up About a minute b1 #利用b1容器建立新鏡像 [root@docker ~]# docker commit -p b1 sha256:470b5b0a5f15cf5270634f4ae53c227592114e45eeba925468f662f710b26a12 #由於在用commit建立鏡像時未指明鏡像名稱,因此爲none [root@docker ~]# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE <none>              <none>              470b5b0a5f15        14 seconds ago      1.22MB busybox latest db8ee88ad75f 13 days ago         1.22MB #給鏡像打標籤,相似與硬鏈接。 [root@docker ~]# docker tag 470b5b0a5f15 zisefeizhu/httpd:v0.1-1 [root@docker ~]# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE zisefeizhu/httpd    v0.1-1              470b5b0a5f15        About a minute ago   1.22MB busybox latest db8ee88ad75f 13 days ago          1.22MB [root@docker ~]# docker tag zisefeizhu/httpd:v0.1-1 zhujingxing/httpd:latest [root@docker ~]# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE zhujingxing/httpd   latest              470b5b0a5f15        2 minutes ago       1.22MB zisefeizhu/httpd    v0.1-1              470b5b0a5f15        2 minutes ago       1.22MB busybox latest db8ee88ad75f 13 days ago         1.22MB #刪除鏡像相似於刪除硬連接。關於文件的刪除原理在此不聲明,必需要懂! [root@docker ~]# docker image rm zisefeizhu/httpd:v0.1-1 Untagged: zisefeizhu/httpd:v0.1-1 #獲取鏡像源/容器數據,json 格式 [root@docker ~]# docker inspect  -f '{{.ContainerConfig.Cmd}}' busybox [/bin/sh -c #(nop)  CMD ["sh"]] [root@docker ~]# docker inspect -f '{{.ContainerConfig.Cmd}}'  zhujingxing/httpd:latest [sh] [root@docker ~]# docker run -it --name t1 zhujingxing/httpd:latest / # ls / bin data dev etc home proc root sys tmp usr var
/ # cat /data/html/index.html <h1> Busybox httpd server. </h1>
/ # [root@docker ~]# [root@docker ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7cf93b2d56be zhujingxing/httpd:latest   "sh"                6 minutes ago       Up 6 minutes t1 7b030688d7c6 busybox "sh"                22 minutes ago      Up 22 minutes b1 [root@docker ~]#  docker commit -a "zisefeizhu <zisefeizhu@zhujingxing.com>" -c 'CMD ["/bin/httpd","-f","-h","/data/html"]' -p b1 zisefeizhu/httpd:v0.2 sha256:9f0e2f6192bf72cb2230e9654d92985bbbd77a800f749a58fa72e496dd28f452 [root@docker ~]# docker run --name t2 zisefeizhu/httpd:v0.2 前臺運行 卡住 另開一個窗口 [root@docker ~]# docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a39c73488430 zisefeizhu/httpd:v0.2      "/bin/httpd -f -h /d…"   12 seconds ago      Up 11 seconds t2 7cf93b2d56be zhujingxing/httpd:latest   "sh"                     9 minutes ago       Up 9 minutes t1 7b030688d7c6 busybox "sh"                     25 minutes ago      Up 25 minutes b1 [root@docker ~]# docker inspect -f '{{.Config.Cmd}}' t2 [/bin/httpd -f -h /data/html] [root@docker ~]# docker inspect -f '{{.NetworkSettings.IPAddress}}' t2 172.17.0.4 [root@docker ~]# curl 172.17.0.4
<h1> Busybox httpd server. </h1>

 

2.5.6 共享存儲 

注:我建議仍是自建倉庫harbor,因此這部分,在此不提,後面講「docker 倉庫」會重點介紹harbor。有興趣的同道能夠看此篇博文:http://www.javashuo.com/article/p-zjunyxlw-gb.html

2.6 docker容器網絡 

在開始的時候就有提過,如今的linux內核已經支持六種名稱空間:

  UTS主機名和域名

  USER用戶

  Mount掛載文件系統

  IPC進程間通訊

  Pid進程id

  Net網絡

網絡做爲docker容器化實現的6個名稱空間的其中之一,是必不可少的。其在Linux內核2.6時已經被加載進內核支持了。

網絡名稱空間主要用於實現網絡設備和協議棧的隔離。

2.6.1 理解網絡虛擬化

網絡虛擬化相對計算、存儲虛擬化來講是比較抽象的,以咱們在學校書本上學的那點網絡知識來理解網絡虛擬化多是不夠的。

在咱們的印象中,網絡就是由各類網絡設備(如交換機、路由器)相連組成的一個網狀結構,世界上的任何兩我的均可以經過網絡創建起鏈接。

帶着這樣一種思路去理解網絡虛擬化可能會感受雲裏霧裏——這樣一個龐大的網絡如何實現虛擬化?

其實,網絡虛擬化更多關注的是數據中心網絡、主機網絡這樣比較「細粒度」的網絡,所謂細粒度,是相對來講的,是深刻到某一臺物理主機之上的網絡結構來談的。

若是把傳統的網絡看做「宏觀網絡」的話,那網絡虛擬化關注的就是「微觀網絡」。網絡虛擬化的目的,是要節省物理主機的網卡設備資源。從資源這個角度去理解,可能會比較好理解一點。

2.6.2 傳統網絡架構

在傳統網絡環境中,一臺物理主機包含一個或多個網卡(NIC),要實現與其餘物理主機之間的通訊,須要經過自身的 NIC 鏈接到外部的網絡設施,如交換機上,以下圖所示。

這種架構下,爲了對應用進行隔離,每每是將一個應用部署在一臺物理設備上,這樣會存在兩個問題

1)是某些應用大部分狀況可能處於空閒狀態

2)是當應用增多的時候,只能經過增長物理設備來解決擴展性問題。無論怎麼樣,這種架構都會對物理資源形成極大的浪費。

2.6.3 虛擬化網絡架構 

爲了解決這個問題,能夠藉助虛擬化技術對一臺物理資源進行抽象,將一張物理網卡虛擬成多張虛擬網卡(vNIC),經過虛擬機來隔離不一樣的應用。

這樣對於上面的問題

針對問題 1),能夠利用虛擬化層 Hypervisor (系統管理程序)的調度技術,將資源從空閒的應用上調度到繁忙的應用上,達到資源的合理利用;

針對問題 2),能夠根據物理設備的資源使用狀況進行橫向擴容,除非設備資源已經用盡,不然沒有必要新增設備。

這種架構以下所示:

其中虛擬機與虛擬機之間的通訊,由虛擬交換機完成,虛擬網卡和虛擬交換機之間的鏈路也是虛擬的鏈路,整個主機內部構成了一個虛擬的網絡,若是虛擬機之間涉及到三層的網絡包轉發,則又由另一個角色——虛擬路由器來完成。

通常,這一整套虛擬網絡的模塊均可以獨立出去,由第三方來完成,如其中比較出名的一個解決方案就是 Open vSwitch(OVS)。

OVS 的優點在於它基於 SDN 的設計原則,方便虛擬機集羣的控制與管理,另外就是它分佈式的特性,能夠「透明」地實現跨主機之間的虛擬機通訊。

以下是跨主機啓用 OVS 通訊的:

總結下來,網絡虛擬化主要解決的是虛擬機構成的網絡通訊問題,完成的是各類網絡設備的虛擬化,如網卡、交換設備、路由設備等。

2.6.4 linux下網絡設備虛擬化的幾種形式

爲了完成虛擬機在同主機和跨主機之間的通訊,須要藉助某種「橋樑」來完成用戶態到內核態(Guest 到 Host)的數據傳輸,這種橋樑的角色就是由虛擬的網絡設備來完成,上面介紹了一個第三方的開源方案——OVS,它實際上是一個融合了各類虛擬網絡設備的集大成者,是一個產品級的解決方案。

但 Linux 自己因爲虛擬化技術的演進,也集成了一些虛擬網絡設備的解決方案,主要有如下幾種:

TAP/TUN/VETH

TAP/TUN 是 Linux 內核實現的一對虛擬網絡設備,TAP 工做在二層,TUN 工做在三層。Linux 內核經過 TAP/TUN 設備向綁定該設備的用戶空間程序發送數據,反之,用戶空間程序也能夠像操做物理網絡設備那樣,向 TAP/TUN 設備發送數據。

基於 TAP 驅動,便可實現虛擬機 vNIC 的功能,虛擬機的每一個 vNIC 都與一個 TAP 設備相連,vNIC 之於 TAP 就如同 NIC 之於 eth。

當一個 TAP 設備被建立時,在 Linux 設備文件目錄下會生成一個對應的字符設備文件,用戶程序能夠像打開一個普通文件同樣對這個文件進行讀寫。

好比,當對這個 TAP 文件執行 write 操做時,至關於 TAP 設備收到了數據,並請求內核接受它,內核收到數據後將根據網絡配置進行後續處理,處理過程相似於普通物理網卡從外界收到數據。當用戶程序執行 read 請求時,至關於向內核查詢 TAP 設備是否有數據要發送,有的話則發送,從而完成 TAP 設備的數據發送。

TUN 則屬於網絡中三層的概念,數據收發過程和 TAP 是相似的,只不過它要指定一段 IPv4 地址或 IPv6 地址,並描述其相關的配置信息,其數據處理過程也是相似於普通物理網卡收到三層 IP 報文數據。

VETH 設備老是成對出現,一端連着內核協議棧,另外一端連着另外一個設備,一個設備收到內核發送的數據後,會發送到另外一個設備上去,這種設備一般用於容器中兩個 namespace 之間的通訊。

Bridge

Bridge 也是 Linux 內核實現的一個工做在二層的虛擬網絡設備,但不一樣於 TAP/TUN 這種單端口的設備,Bridge 實現爲多端口,本質上是一個虛擬交換機,具有和物理交換機相似的功能。 

Bridge 能夠綁定其餘 Linux 網絡設備做爲從設備,並將這些從設備虛擬化爲端口,當一個從設備被綁定到 Bridge 上時,就至關於真實網絡中的交換機端口上插入了一根連有終端的網線。

以下圖所示,Bridge 設備 br0 綁定了實際設備 eth0 和 虛擬設備 tap0/tap1,當這些從設備接收到數據時,會發送給 br0 ,br0 會根據 MAC 地址與端口的映射關係進行轉發。

 

由於 Bridge 工做在二層,因此綁定到它上面的從設備 eth0、tap0、tap1 均不須要設 IP,可是須要爲 br0 設置 IP,由於對於上層路由器來講,這些設備位於同一個子網,須要一個統一的 IP 將其加入路由表中。

這裏有人可能會有疑問,Bridge 不是工做在二層嗎,爲何會有 IP 的說法?其實 Bridge 雖然工做在二層,但它只是 Linux 網絡設備抽象的一種,能設 IP 也不足爲奇。

對於實際設備 eth0 來講,原本它是有本身的 IP 的,可是綁定到 br0 以後,其 IP 就生效了,就和 br0 共享一個 IP 網段了,在設路由表的時候,就須要將 br0 設爲目標網段的地址。

2.6.5 跨主機docker容器通訊方案介紹 

NET:網絡名稱空間
描述:主要是網絡設備、協議棧等實現,假設物理機上有四塊網卡,須要建立兩個名稱空間,這些設備能夠單獨關聯給某個空間所使用的,如第一個網卡分配給第一個名稱空間使用,其餘就看不見這個設備了,一個設備通常只能授予一個空間,一樣有四個網卡就能夠使用四個名稱空間,使得每一個名稱空間均可以配置IP地址與外界進行通訊。
  若是名稱空間的數量超過物理網卡數量,每一個名稱空間內部的進程也是須要經過網絡進行通訊,應該如何上報,能夠使用模擬技術,linux設備支持兩種內核級的模擬,是二層設備和三層設備,網卡就是一個二層設備,工做在鏈路層,可以封裝報文實現各設備之間報文轉發的實現,這功能是徹底能夠在Linux之上利用內核中對二層虛擬設備的支持,建立虛擬網卡接口,並且這種虛擬網卡接口很獨特,每一個網絡接口設備是成對出現的,能夠模擬爲一根網線的兩頭,其中一頭能夠插在主機之上,另外一頭插在交換機之上進行模擬,至關於一個主機鏈接到交換機上去了,而linux內核源生就支持模擬二層網絡設備,使用軟件來構建一個交換機。
  若是有兩個名稱空間,那麼兩臺主機就像鏈接到同一個交換機上進行通訊,若是配置的網絡地址在同一個網段就能夠直接進行通信了。這就是虛擬化的網絡。
OVS: OpenVSwitch 能夠模擬高級的網絡技術,二層交換,甚至三層網絡設備,vlan,,不屬於Linux內核組件,要額外安裝,由cisco衆多公司所構建的,有云計算的浪潮下,構建網絡是比較複雜的,而後纔是網絡之上所承載的主機,才能通信,這個網絡虛擬化所實現的功能,須要軟件硬件結合起來實現,並且把傳統意義上的網絡平面,控制平面,傳輸平面等,隔離開來,集中到一個設備之上實現全局的調度,實現SDN,軟件定義網絡。
單節點上容器通信:同一個物理機上的兩個容器,或者兩個名稱空間要通信,就是在主機上創建一個虛擬的交換機,讓兩個容器各自使用純軟件的方式,建一對虛擬網卡,一半在交換機上,一半在容器上,從而實現單節點上容器進行通信,可是也有比較複雜的狀況,有可能會出現有兩個軟交換機的狀況,鏈接不一樣的容器,這時兩個軟交換機要鏈接,須要再作一塊網卡,一頭在交換機1上,另外一頭在交換機2之上,若是不一樣交換機之間要實現路由轉發,就須要在兩能交換機上加一臺路由器,linux內核自身能夠看成路由器來使用,打開轉發或者使用iptables規則,可是路由器是一個三層的設備,在linux內核直接使用一個單獨的名稱空間就能夠實現,就是再作一個容器看成路由器來使用,可是要模擬出網卡來讓它們創建關聯關係。

 

多節點:另外一臺主機上的一個容器,與1號主機上的容器進行通訊,vmware實現不一樣主機上的虛擬機之間的通信能夠使用橋接的方式,就是把物理網卡看成交換機來使用,全部一臺主機上的容器都到一個物理網卡來,經過MAC地址來肯定交給那個容器,若是是到物理機的,就給物理機,也就是虛擬機裏也有自身的獨特的MAC地址,因此數據包來時能夠區別各個設備,把物理網卡看成交換機來使用,把報文轉發給各容器,若是報文目標是物理網卡時,須要虛擬出一個軟網卡做爲物理網卡的使用,這樣就沒有虛擬交換機概念,因此兩臺主機上的虛擬機要使用橋接通信時,都是鏈接到各自主機上的物理網卡的的。可是這種通信方式要實現有很大的代價,由於全部容器的橋接都在同一個平面中,很容易產生風暴,因此在大規模的虛擬機或容器的使用場景中使用橋接不太好,除非能隔離得很好(橋接)。

Nat技術:如圖中C3與C6通信,C3是虛擬網卡,C3網卡與物理網卡物理地址不在同一個網段中,C3把網關指向S2,把S3看成宿主機的一個網卡來使用,IP地址與C3在同一個網段,把C3的網關指向S2,而後在物理機上打開核心轉發功能,因此當C3與C6通信時,先轉給s2,再到達內核,內核斷定查路由列不是本身要到另外一個主機上的C6,這時報文回不來,由於C3和C4是一個私有地址,若是要報文可以回來,最後到報文送走物理機以前,要把源IP地址修改爲物理網卡的IP地址,這樣C5或者C6回覆物理主機的IP就能夠了,經過NAT表的查詢是C3的訪問,就把報文送給C3,這就使用NAT實現跨主機之間的通信,可是這裏有一個很大的問題,C6也多是NAT的模式下工做,也就是說它也是使用私有地址的,若是C6要被訪問只能把它暴露出去,在物理機的能外網卡上明確說明某個端口是提供服務的,若是要C4可以訪問C6,就要先訪問C6所在的宿主機的物理地址,再使用H2作dnat發給C6,可是C4發送報文時是經過SNAT出來的,C4也是隱藏在NAT背後的,發出去的報文要其餘的主機能夠響應就應該改寫源地址。因此在跨服務主機實現兩個虛擬機之間的通信要實現兩級的NAT操做,從C4到C6,首先C4出去就SNAT,到到C6要使用到DNAT,這樣的效率不會高,可是網絡比較容易管理。
Overlay Network: 疊加網絡,是NAT和橋接的一個解決方案,有多個物理主機,在虛擬機上作一個虛擬的橋,讓各虛擬機鏈接到虛擬橋上,通訊時借用物理網絡來完成報文的隧道轉發,從而實現C1能夠直接看見C5或C6,物理主機原本就是使用物理網絡鏈接在一塊兒的,C1與物理網絡不在同一個地址段內,可是C1與C5是在同一地址段內的,C1發送報文時,先發送給虛擬機,假設它是知道C5是不要本地的物理主機上的,以是報文要從物理網卡發送出去,可是要作隧道轉發,也就是C1的報文源IP地址是C1,目標地址是C5,而後再封裝一個IP包頭的首部源地址是C1所在物理主機的IP地址,目標地址是C5所在物理主機的IP地址,當報文送到C5所在的物理機,把報文拆完第一層後,第二層的目標地址就是C5的,就直接交給本地的軟交換機,再交給C5,C1與C5之間的通信直接源地址和目標地址就是各自雙方,可是它寄於別的網絡,本地自身就是一個三層的網絡,應該封裝二層,可是沒有封裝,又封裝三層四層報文,就是一個TCP或者UDP的首部,再封裝一個首部實現一個兩級的三層封裝,從而完成報文的轉發

2.6.5.1 其餘方案

基於實現方式的分類

  隧道方案(Overlay Networking):

    Weave:UDP廣播,本機創建新的BR,經過PCAP互通。

    Open vSwitch(OVS):基於VxLAN和GRE協議,可是性能方面損失比較嚴重。

    Flannel:UDP廣播,VxLan。

  路由方案:

    Calico:基於BGP協議的路由方案,支持很細緻的ACL控制,對混合雲親和度比較高。

    Macvlan:從邏輯和Kernel層來看隔離性和性能最優的方案,基於二層隔離,因此須要二層路由器支持,大多數雲服務商不支持,因此混合雲上比較難以實現。

基於網絡模型分類

  Docker Libnetwork Container Network Model(CNM):

    Docker Swarm overlay

    Macvlan & IP network drivers

    Calico

    Contiv(from Cisco)

    #Docker Libnetwork的優點就是原生,並且和Docker容器生命週期結合緊密;缺點也能夠理解爲是原生,被Docker「綁架」。

  Container Network Interface(CNI):

    Kubernetes

    Weave

    Macvlan

    Flannel

    Calico

    Contiv

    Mesos CNI

    #CNI的優點是兼容其餘容器技術(e.g. rkt)及上層編排系統(Kuberneres & Mesos),並且社區活躍勢頭迅猛,Kubernetes加上CoreOS主推;缺點是非Docker原生。

詳解

以Flannel方案爲例

Flannel以前的名字是Rudder,它是由CoreOS團隊針對Kubernetes設計的一個重載網絡工具,它的主要思路是:預先留出一個網段,每一個主機使用其中一部分,而後每一個容器被分配不一樣的ip;讓全部的容器認爲你們在同一個直連的網絡,底層經過UDP/VxLAN等進行報文的封裝和轉發

下面這張是Flannel網絡的經典架構圖:

  1. 容器直接使用目標容器的ip訪問,默認經過容器內部的eth0發送出去。
  2. 報文經過veth pair被髮送到vethXXX。
  3. vethXXX是直接鏈接到虛擬交換機docker0的,報文經過虛擬bridge docker0發送出去。
  4. 查找路由表,外部容器ip的報文都會轉發到flannel0虛擬網卡,這是一個P2P的虛擬網卡,而後報文就被轉發到監聽在另外一端的flanneld。
  5. flanneld經過etcd維護了各個節點之間的路由表,把原來的報文UDP封裝一層,經過配置的iface發送出去。
  6. 報文經過主機之間的網絡找到目標主機。
  7. 報文繼續往上,到傳輸層,交給監聽在8285端口的flanneld程序處理。
  8. 數據被解包,而後發送給flannel0虛擬網卡。
  9. 查找路由表,發現對應容器的報文要交給docker0。
  10. docker0找到連到本身的容器,把報文發送過去

關於docker網絡解決方案,強烈建議參考「散盡浮華」前輩的博文:https://www.cnblogs.com/kevingrace/category/839227.html

注:關於「散盡浮華」前輩,我有必要說一下:此乃我IT路上的良師和精神支柱!

關於docker網絡解決方案,我本人更推薦使用calico方案。關於calico 網絡插件會在kubernetes章節重點闡述

docker 網絡是深刻學習docker的重點、難點,強烈建議:必定要耐着性子深刻學習這部分。

2.6.6 單機網絡

[root@docker ~]# docker network ls NETWORK ID NAME DRIVER SCOPE 1fbc952b8c69 bridge bridge local 48cf42c3b371 host host local 4c1006be1ea1 none null local
bridge:默認網絡驅動程序。當你的應用程序在須要通訊的獨立容器中運行時,一般會使用橋接網絡。 host:對於獨立容器,刪除容器和Docker主機之間的網絡隔離,並直接使用主機的網絡。 none:對於此容器,禁用全部網絡。 container: Container 網絡模式是 Docker 中一種較爲特別的網絡的模式。處於這個模式下的 Docker 容器會共享其餘容器的網絡環境,所以,至少這兩個容器之間不存在網絡隔離,而這兩個容器又與宿主機以及除此以外其餘的容器存在網絡隔離。

bridge 模式【默認網絡模式】

橋接時網絡,並非物理橋,本機上建立一個純粹的軟交換機docker0,也能夠看成網卡來使用,每啓動一個容器就能夠給容器分配一段網卡的地址,一半在容器上,一半在docker0橋上,veth176661b這種在機器能夠看到的不管容器仍是KVM時,每次建立網卡時,都是建立一對的,一半放在虛擬機上,一半放在軟交換機上,至關於一根網線鏈接着兩個設備同樣。

bridge網絡的特色

  使用一個 linux bridge,默認爲 docker0

  使用veth 對,一頭在容器的網絡 namespace中,一頭在docker0上

  該模式下Docker Container不具備一個公有IP,由於宿主機的IP地址與veth pair的IP地址不在同一個網段內

  Docker採用NAT方式,將容器內部的服務監聽的端口與宿主機的某一個端口進行「綁定」,使得宿主機之外的世界能夠主動將網絡報文發送至容器內部

  外界訪問容器內的服務時,須要訪問宿主機的 IP 以及宿主機的端口 port

  NAT 模式因爲是在三層網絡上的實現手段,故確定會影響網絡的傳輸效率。

  容器擁有獨立、隔離的網絡棧;讓容器和宿主機之外的世界經過NAT創建通訊

#當前運行有三個容器 [root@docker ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a39c73488430 zisefeizhu/httpd:v0.2      "/bin/httpd -f -h /d…"   27 hours ago        Up 27 hours t2 7cf93b2d56be zhujingxing/httpd:latest   "sh"                     27 hours ago        Up 27 hours t1 7b030688d7c6 busybox "sh"                     27 hours ago        Up 27 hours b1 #由於啓動了三個容器,因此生成了三個虛擬IP,同時這三個虛擬IP都是插在docker0橋上的 [root@docker ~]# ifconfig docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500 inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255 ...... veth0a020f8: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500 ...... veth74be495: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500 ...... veth99cb539: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500 [root@docker ~]# brctl show bridge name bridge id STP enabled interfaces docker0 8000.0242dcbf397a no veth0a020f8 veth74be495 veth99cb539 #查看網卡之間的鏈接關係、紅色爲容器內部網卡 [root@docker ~]# ip link show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000 link/ether 00:0c:29:da:d7:53 brd ff:ff:ff:ff:ff:ff 3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default link/ether 02:42:dc:bf:39:7a brd ff:ff:ff:ff:ff:ff 5: veth99cb539@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default link/ether 42:07:17:86:75:3b brd ff:ff:ff:ff:ff:ff link-netnsid 0
7: veth0a020f8@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default link/ether 3a:de:33:6e:cc:ff brd ff:ff:ff:ff:ff:ff link-netnsid 1
9: veth74be495@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default link/ether 3e:02:4d:e7:34:e5 brd ff:ff:ff:ff:ff:ff link-netnsid 2 #進入容器t2 [root@docker ~]# docker exec -it t2 sh / # ifconfig eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:04 inet addr:172.17.0.4  Bcast:172.17.255.255  Mask:255.255.0.0 #Nat橋:docker建立時默認就是nat橋,是使用Iptables來實現的 [root@docker ~]# iptables -t nat -vnL Chain PREROUTING (policy ACCEPT 231 packets, 28675 bytes) pkts bytes target prot opt in     out source destination 5   260 DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL Chain INPUT (policy ACCEPT 231 packets, 28675 bytes) pkts bytes target prot opt in     out source destination Chain OUTPUT (policy ACCEPT 611 packets, 45623 bytes) pkts bytes target prot opt in     out source destination 0     0 DOCKER     all  --  *      *       0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL Chain POSTROUTING (policy ACCEPT 611 packets, 45623 bytes) pkts bytes target prot opt in     out source destination 0     0 MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0  
in: 從使用接口進來,只要不出docker0出去,源地址來自於172.17.0.0/16的,不管到達任何主機0.0.0.0/0,都要作地址假裝MASQUERADE,至關於SNAT,並且是自動實現SNAT,也就是自動選擇一個最合適物理地址看成源地址,因此docker0橋默認就是nat橋 Chain DOCKER (2 references) pkts bytes target prot opt in     out source destination 0     0 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0 #建立bridge容器 [root@docker ~]# docker run --name t3 -it --rm busybox:latest / # ifconfig eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:05 inet addr:172.17.0.5  Bcast:172.17.255.255  Mask:255.255.0.0 ...... / # exit [root@docker ~]# docker run --name t3 -it --network bridge  --rm busybox:latest / # ifconfig eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:05 inet addr:172.17.0.5  Bcast:172.17.255.255  Mask:255.255.0.0 ......

 容器中網絡通信狀況

  同一個宿主機中,使用同一個docker0中的軟交換機進行通信

/ # netstat -lnt Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0      0 :::80                   :::* LISTEN #容器內訪問 / # wget -O - -q http://172.17.0.4
<h1> Busybox httpd server. </h1> #宿主機訪問 [root@docker ~]# curl http://172.17.0.4
<h1> Busybox httpd server. </h1>

 跨主機通信

在同一個宿方機之間的容器通信能夠實現,可是跨主機就會產生問題,由於docker自己就是一個nat bridge,對外來講是不可見的,要實現不一樣主機之間的容器的實現通信,就要作dnat,把接口中發佈出來的,假設物理主機上有一個物理網卡,開通一個端口而後提供對外服務,外部主機訪問容器中的服務時,使用dnat的方式轉到容器中的虛擬網卡中,提供服務。

可是存在一個問,若是在同一臺宿主機上,起了兩個容器分別是兩個nginx的web服務,可是對外的IP只有一個,只能使用端口來區分,假設nginx1使用80,另外一個nginx2就只能使用非80的端口,這時client訪問的出現問題,由於默認訪問就要給80,若是是非80端口就請求不到。

如查使用ovetlay network疊加網絡方式就能夠直接使用隧道來承載,直接訪問就能夠了,能夠不用對地址進行映射。通常的跨主機之間的虛擬機訪問方式橋接、nat的。

容器特殊功能,在容器內部有6個隔離的名稱空間,user,mount,pid,uts,net,ipc,每一個容器都有自身獨立的資源,假設讓每一個容器都有隔離而獨立的user,mount,pid,而uts,net,ipc這三個資源是共享使用的,擁有同一個網卡,同一組網絡協議棧,有同一個主機名和域名,對外使用同一個IP地址,優勢是如第一個容器使用的tomcat服務,第二個容器的是redis服務時,若是tomcat要訪問redis中的數據時,是同一個協議棧,以前若是是隔離的,經過127來訪問是不能夠的,實現有本身獨立隔離的名稱空間,卻又共享一部分名稱空

host 模式

Host模式並無爲容器建立一個隔離的網絡環境。該模式下的Docker容器會和host宿主機共享同一個網絡namespace,因此容器能夠和宿 主機同樣,使用宿主機的eth0,實現和外界的通訊。

host 網絡特色

  這種模式下的容器沒有隔離的network namespace

  容器的IP地址同 Docker主機的IP地址

  須要注意容器中服務的端口號不能與Docker主機上已經使用的端口號相沖突

  host模式可以和其它模式共存

[root@docker ~]# docker run --name b2 --network host -it --rm busybox / # ifconfig docker0 Link encap:Ethernet HWaddr 02:42:DC:BF:39:7A inet addr:172.17.0.1  Bcast:172.17.255.255  Mask:255.255.0.0 ...... / # echo "live zhujingxing" > /tmp/index.html / # httpd -h /tmp/
/ # netstat -nat | grep 80 tcp 0      0 :::80                   :::* LISTEN [root@docker ~]# netstat -ant | grep 80 tcp6 0      0 :::80                   :::*                    LISTEN     

none 模式

網絡模式爲 none,即不爲Docker容器構造任何網絡環境,不會爲容器建立網絡接口,一旦Docker容器採用了none網絡模式,那麼容器內部就只能使用loop back網絡設備,不會再有其餘的網絡資源。

[root@docker ~]# docker run -it --name host --network none --rm busybox / # ifconfig lo Link encap:Local Loopback inet addr:127.0.0.1  Mask:255.0.0.0 ......

container模式

joined 容器是另外一種實現容器間通訊的方式。它能夠使兩個或多個容器共享一個網絡棧,共享網卡和配置信息,joined 容器之間能夠經過 127.0.0.1 直接通訊。

[root@docker ~]# docker run --name busy01 -it --rm busybox / # ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 8: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue link/ether 02:42:0a:00:00:03 brd ff:ff:ff:ff:ff:ff inet 172.17.0.3/16 brd 10.0.255.255 scope global eth0 valid_lft forever preferred_lft forever / # hostname 4a5449c67f3a # 此時另開一窗口,在啓動另一個容器,能夠看到ip和主機名啥的都是同樣的 [root@docker ~]# docker run --name busy02 -it --network container:busy01 --rm busybox / # ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 8: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue link/ether 02:42:0a:00:00:03 brd ff:ff:ff:ff:ff:ff inet 172.17.0.3/16 brd 10.0.255.255 scope global eth0 valid_lft forever preferred_lft forever / # hostname 4a5449c67f3a # 作個測試,證實兩個容器時共用lo接口的,在busy01上面啓動一個httpd / # echo 'I live zhujingxing' >/tmp/index.html / # httpd -f -h /tmp/ # 在busy02上訪問本地接口lo,能夠看到是成功的 / # wget -O - -q 127.0.0.1 I live zhujingxing # 可是文件系統仍是隔離的,在busy01容器中建立一個目錄 / # mkdir /tmp/test # 在busy02中查看,是沒有的 / # ls /tmp/ 

2.6.7 自定義docker的網絡屬性

#修改docker0橋的地址,添加bip設置 [root@docker ~]# docker ps [root@docker ~]# ip a | grep docker0 3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0 [root@docker ~]# systemctl stop docker [root@docker ~]# cp /etc/docker/daemon.json{,.bak} [root@docker ~]# vim /etc/docker/daemon.json [root@docker ~]# systemctl start docker [root@docker ~]# diff /etc/docker/daemon.json{,.bak} 2,3c2 <   "registry-mirrors": ["https://llpuz83z.mirror.aliyuncs.com"], <     "bip": "10.0.0.1/16"
---
>   "registry-mirrors": ["https://llpuz83z.mirror.aliyuncs.com"] [root@docker ~]# ip a | grep docker0 3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default inet 10.0.0.1/16 brd 10.0.255.255 scope global docker0 #還能夠修改其餘選項 [root@node2 ~]# cat /etc/docker/daemon.json { "registry-mirrors": ["https://registry.docker-cn.com"], "bip": "10.0.0.1/16", #核心選項爲big,即bridge ip,用於指定docker0橋自身的IP地址,其餘選項能夠經過此計算出來,除了DNS "fixed-cidr": "10.20.0.0/16", "fixed-cidr-v6": "2001:db8::/64", "default-gateway": "10.20.1.1", "default-gateway-v6": "2001:db8:abcd::89", "dns": ["10.20.1.2","10.20.1.3"] } #關於daemon.json 請看1.5.2.2章節 #設定外部主機鏈接docker dockerd守護進程的C/S,其默認僅監聽unix socket格式地址,/var/run/docker.sock;若是使用tcp套接字 # 如今個人docker01和docker02主機都是有安裝docker的,我如今修改配置,使docker01可以控制docker02的容器 [root@docker2 ~]# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2157df82161b hamerle/httpd:v1    "/bin/httpd -f -h /d…"  2 days ago          Exited (135) 1 hours ago web01 [root@docker02 ~]# systemctl edit docker.service [Service] ExecStart= ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2375
[root@docker02 ~]# systemctl daemon-reload [root@docker02 ~]# systemctl restart docker.service [root@docker02 ~]# netstat -lntp Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name tcp 0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      765/sshd tcp 0      0 127.0.0.1:2375          0.0.0.0:*               LISTEN      5180/dockerd tcp6 0      0 :::22                   :::*                    LISTEN      765/sshd # docke daemon已經監聽到2375的端口,咱們如今能夠經過docker01遠程啓動docker02上的web007容器 [root@docker01 ~]# docker -H 20.0.0.210:2375 ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2157df82161b hamerle/httpd:v1    "/bin/httpd -f -h /d…"  2 days ago          Exited (135) 1 hours ago web01 [root@docker01 ~]# docker -H 20.0.0.210:2375 start web01 web01 [root@docker2 ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2157df82161b hamerle/httpd:v1    "/bin/httpd -f -h /d…"  2 days ago          Exited (135) 1 hours ago web01 #手動建立一個網絡類型,並指定對應網橋設備的名稱爲docker,最終實現基於兩個不一樣網絡啓動的容器間互相通訊。 [root@docker ~]# docker network ls NETWORK ID NAME DRIVER SCOPE bddc0093fe11 bridge bridge local 48cf42c3b371 host host local 4c1006be1ea1 none null local [root@docker ~]# docker network create --help [root@docker ~]# docker network create -o com.docker.network.bridge.name=docker -d bridge --subnet '172.18.0.0/16' bridge-test 38ce06f558bab2dd57959448953dc214411c3d6147185c2d78fd90b37ae34c62 # -o:在使用bridge的driver類型時,能夠使用-o的附加參數。上面實例中的參數意思是指定建立bridge類型網絡時對應虛擬網橋設備的名字。(就是ip a命令看到的名字) # -d:指定driver,默認類型就是bridge。 # --subnet:指定新建的docker網絡的網段 # 最後的bridg-test是即將要將建立出的網絡的名字. [root@docker ~]# docker network ls NETWORK ID NAME DRIVER SCOPE bddc0093fe11 bridge bridge local 38ce06f558ba bridge-test bridge local 48cf42c3b371 host host local 4c1006be1ea1 none null local [root@docker ~]# ip a | grep docker 3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default inet 10.0.0.1/16 brd 10.0.255.255 scope global docker0 19: docker: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default inet 172.18.0.1/16 brd 172.18.255.255 scope global docker # 咱們以bridge-test網絡啓動一個容器 [root@docker ~]# docker run --name busy01 -it --network bridge-test --rm busybox:latest / # ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 20: eth0@if21: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff inet 172.18.0.2/16 brd 172.18.255.255 scope global eth0 valid_lft forever preferred_lft forever / # # 另開一個窗口,使用bridge網絡再起一個容器 [root@docker ~]# docker run --name busy02 -it --network bridge --rm busybox:latest / # ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 22: eth0@if23: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue link/ether 02:42:0a:00:00:02 brd ff:ff:ff:ff:ff:ff inet 10.0.0.2/16 brd 10.0.255.255 scope global eth0 valid_lft forever preferred_lft forever # 能夠看到兩個容器,一個是172.18網段,一個是10.0網段,此時作連通性測試。 / # ping 172.18.0.2 # 不通,此時肯定宿主機的ip_forward是否開啓,若是開啓還不通,則須要另開一個窗口排查防火牆規則。 [root@docker ~]# cat /proc/sys/net/ipv4/ip_forward 1 [root@docker ~]# iptables -nvL # 排查防火牆規則,其實很簡單,把target類型爲DROP的刪掉就行了。我這裏只列出有DROP的鏈,並刪除 [root@docker ~]#  iptables -nvL DOCKER-ISOLATION-STAGE-2 --line-number Chain DOCKER-ISOLATION-STAGE-2 (2 references) num pkts bytes target prot opt in     out source destination 1        0     0 DROP       all  --  *      docker  0.0.0.0/0            0.0.0.0/0           
2        0     0 DROP       all  --  *      docker0  0.0.0.0/0            0.0.0.0/0           
3        7   588 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0 [root@docker ~]# iptables -D DOCKER-ISOLATION-STAGE-2 2 [root@docker ~]#  iptables -D DOCKER-ISOLATION-STAGE-2 1 # 刪除完後再ping / # ping 172.18.0.2 PING 172.18.0.2 (172.18.0.2): 56 data bytes 64 bytes from 172.18.0.2: seq=0 ttl=63 time=0.115 ms 64 bytes from 172.18.0.2: seq=1 ttl=63 time=0.081 ms 64 bytes from 172.18.0.2: seq=2 ttl=63 time=0.169 ms

2.7 docker存儲卷

2.7.1 爲何須要數據卷

這得從 docker 容器的文件系統提及。出於效率等一系列緣由,docker 容器的文件系統在宿主機上存在的方式很複雜,這會帶來下面幾個問題:

  不能在宿主機上很方便地訪問容器中的文件。

  沒法在多個容器之間共享數據。

  當容器刪除時,容器中產生的數據將丟失。

爲了解決這些問題,docker 引入了數據卷(volume) 機制。數據卷是存在於一個或多個容器中的特定文件或文件夾,這個文件或文件夾以獨立於 docker 文件系統的形式存在於宿主機中。數據卷的最大特定是:其生存週期獨立於容器的生存週期

2.7.2 使用數據卷的最佳場景

在多個容器之間共享數據,多個容器能夠同時以只讀或者讀寫的方式掛載同一個數據卷,從而共享數據卷中的數據。

當宿主機不能保證必定存在某個目錄或一些固定路徑的文件時,使用數據卷能夠規避這種限制帶來的問題。

當你想把容器中的數據存儲在宿主機以外的地方時,好比遠程主機上或雲存儲上。

當你須要把容器數據在不一樣的宿主機之間備份、恢復或遷移時,數據卷是很好的選擇。

2.7.3 細述存儲卷

背景:一個程序,對於容器來講,啓動時依賴於可能不止一層的鏡像,聯合掛載啓動而成,使用overlay2文件系統,引導最上層的可寫層,對於讀寫層來講,全部在容器中可執行的操做,包括對數據和內容的修改,都是保存在最上層之上的,對於下層內容的操做,假設要刪除一個文件,須要使用寫時複製。

docker鏡像由多個只讀層疊加面成,啓動容器時,docker會加載只讀鏡像層並在鏡像棧頂部加一個讀寫層

若是運行中的容器修改了現有的一個已經存在的文件,那該文件將會從讀寫層下面的只讀層複製到讀寫層,該文件版本仍然存在,只是已經被讀寫層中該文件的副本所隱藏,此即「寫時複製(COW)」機制

描述:若是一個文件在最底層是可見的,若是在layer1上標記爲刪除,最高的層是用戶看到的Layer2的層,在layer0上的文件,在layer2上能夠刪除,可是隻是標記刪除,用戶是不可見的,總之在到達最頂層以前,把它標記來刪除,對於最上層的用戶是不可見的,當標記一刪除,只有用戶在最上層建一個同名同樣的文件,纔是可見的。

 

對於這類的操做,修改刪除等,通常效率很是低,若是對一於I/O要求比較高的應用,如redis在實現持久化存儲時,是在底層存儲時的性能要求比較高。

假設底層運行一個存儲庫mysql,mysql原本對於I/O的要求就比較高,若是mysql又是運行在容器中本身的文件系統之上時,也就是容器在中止時,就意味着刪除,其實現數據存取時效率比較低,要避免這個限制要使用存儲捲來實現。

存儲卷:能夠想象來在各全局的名稱空間中,也就是理解爲在宿主機中找一個本地的文件系統,可能存在某一個目錄中,直接與容器上的文件系統中的某一目錄創建綁定關係。

相似於掛載同樣,宿主機的/data/web目錄與容器中的/container/data/web目錄綁定關係,而後容器中的進程向這個目錄中寫數據時,是直接寫在宿主機的目錄上的,繞過容器文件系統與宿主機的文件系統創建關聯關係,使得能夠在宿主機和容器內共享數據庫內容,讓容器直接訪問宿主機中的內容,也能夠宿主機向容器供集內容,二者是同步的。

mount名稱空間原本是隔離的,可讓兩個原本是隔離的文件系統,在某個子路徑上創建必定程度的綁定關係,從而使得在兩個容器之間的文件系統的某個子路徑上再也不是隔離的,實現必定程度上共享的效果。

在宿主機上可以被共享的目錄(能夠是文件)就被稱爲volume。

優勢容器中進程所生成的數據,都保存在存儲捲上,從而脫離容器文件系統自身後,當容器被關閉甚至被刪除時,都不用擔憂數據被丟失,實現數據能夠脫離容器生命週期而持久,當再次重建容器時,若是可讓它使用到或者關聯到同一個存儲捲上時,再建立容器,雖然不是以前的容器,可是數據仍是那個數據,特別相似於進程的運行邏輯,進程自己不保存任何的數據,數據都在進程以外的文件系統上,或者是專業的存儲服務之上,因此進程每次中止,只是保存程序文件,對於容器也是同樣,

容器就是一個有生命週期的動態對象來使用,容器關閉就是容器刪除的時候,可是它底層的鏡像文件仍是存在的,能夠基於鏡像再從新啓動容器。

可是容器有一個問題,通常與進程的啓動不太同樣,就是容器啓動時選項比較多,若是下次再啓動時,不少時候會忘記它啓動時的選項,因此最好有一個文件來保存容器的啓動,這就是容器編排工具的做用。通常狀況下,是使用命令來啓動操做docker,可是能夠經過文件來讀,也就讀文件來啓動,讀所須要的存儲卷等,可是它也只是操做一個容器,這也是須要專業的容器編排工具的緣由。

另外一個優點就是容器就能夠不置於啓動在那臺主機之上了,如幾臺主機後面掛載一個NFS,在各自主機上建立容器,而容器上經過關聯到宿主機的某個目錄上,而這個目錄也是NFS所掛載的目錄中,這樣容器若是中止或者是刪除均可以不限制於只能在原先的宿主機上啓動才能夠,能夠實現全集羣範圍內調試容器的使用,當再分配存儲、計算資源時,就不會再侷限於單機之上,能夠在集羣範圍內創建起來,基本各類docker的編排工具都能實現此功能,可是後面嚴重依賴於共享存儲的使用。

考慮到容器應用是須要持久存儲數據的,多是有狀態的,若是考慮使用NFS作反向代理是不必存儲數據的,應用能夠分爲有狀態和無狀態,有狀態是當前此次鏈接請求處理必定此前的處理是有關聯的,無狀態是先後處理是沒有關聯關係的,大多數有狀態應用都是數據持久存儲的,如mysql,redis有狀態應用,在持久存儲,如nginx做爲反向代理是無狀態應用,tomcat能夠是有狀態的,可是它有可能不須要持久存儲數據,由於它的session都是保存在內存中就能夠的,會致使節點宕機而丟失session,若是有必要應該讓它持久,這也算是有狀態的。

 

應用狀態:是否有狀態或無狀態,是否須要持久存儲,能夠定立一個正軸座標系,第一象限中是那些有狀態須要存儲的,像mysql,redis等服務,有些有有狀態可是無需進行存儲的,像tomcat把會話保存在內存中時,無狀態也無須要存儲的數據,如各類反向代理服務器nginx,lvs請求鏈接都是看成一個獨立的鏈接來調度,本地也不須要保存數據,第四象限是無狀態,可是須要存儲數據是比較少見。

運維起來比較難的是有狀態且須要持久的,須要大量的運維經驗和大量的操做步驟才能操做起來的,如作一個Mysql主從須要運維知識、經驗整合進去才能實現所謂的部署,擴展或縮容,出現問題後修復,必需要了解集羣的規模有多大,有多少個主節點,有多少個從節點,主節點上有多少個庫,這些都要一清二楚,才能修復故障,這些就強依賴於運維經驗,無狀態的如nginx一安裝就能夠了,並不複雜,對於無狀態的應用能夠迅速的實現複製,在運維上實現自動化是很容易的,對於有狀態的現狀比較難脫離運維人員來管理,即便是k8s在使用上也暫時沒有成熟的工具來實現。

總之:對於有狀態的應用的數據,不使用存儲卷,只能放在容器本地,效率比較低,而致使一個很嚴重問題就是沒法遷移使用,並且隨着容器生命週期的中止,還不能把它刪除,只能等待下次再啓動狀態才能夠,若是刪除了數據就可能沒了,由於它的可寫層是隨着容器的生命週期而存在的,因此只要持久存儲數據,存儲卷就是必需的。

docker存儲卷難度:對於docker存儲卷運行起來並不太麻煩,若是不本身藉助額外的體系來維護,它自己並無這麼強大,由於docker存儲卷是使用其所在的宿主機上的本地文件系統目錄,也就是宿主機有一塊磁盤,這塊磁盤並無共享給其餘的docker主要,而後容器所使用的目錄,只是關聯到宿主機磁盤上的某個目錄而已,也就是容器在這宿主機上中止或刪除,是能夠從新再建立的,可是不能調度到其餘的主機上,這也是docker自己沒有解決的問題,因此docker存儲卷默認就是docker所在主機的本地,可是本身搭建一個共享的NFS來存儲docker存儲的數據,也能夠實現,可是這個過程強依賴於運維人員的能力。

2.7.3.1 使用存儲卷的緣由

關閉並重啓容器,其數據不受影響,可是刪除docker容器,則其更改將會所有丟失

存在的問題

  存儲於聯合文件系統中,不易於宿主機訪問

  容器間數據共享不便

  刪除容器其數據會丟失

解決方案:卷

卷是容器上一個或多個"目錄「,此類目錄可繞過聯合文件系統,與宿主機上的某目錄綁定(關聯)

2.7.3.2 存儲卷原理

volume於容器初始化之時會建立,由base image提供的卷中的數據會於此期間完成複製

volume的初意是獨立於容器的生命週期實現數據持久化,所以刪除容器之時既不會刪除卷,也不會對哪怕未被引用的卷作垃圾回收操做

卷爲docker提供了獨立於容器的數據管理機制

能夠把「鏡像」想像成靜態文件,例如「程序」,把卷類比爲動態內容,例如「數據」,因而,鏡像能夠重用,而卷能夠共享

卷實現了「程序(鏡像)"和」數據(卷)「分離,以及」程序(鏡像)「和"製做鏡像的主機」分離,用記製做鏡像時無須考慮鏡像運行在容器所在的主機的環境 

描述:有了存儲卷,若是寫在/上,仍是存在聯合掛載文件系統中,若是要寫到捲上,就會寫到宿主機關聯的目錄上,程序運行過程生成的臨時數據會寫到tmp目錄中,也就會在容器的可寫層中存儲,隨着容器被刪除而刪除,並沒太大的影響,只有關鍵型的數據纔會保存在存儲捲上。

Volume types

Docker有兩種類型的卷,每種類型都在容器中存在一個掛載點,但其在宿主機上位置有所不一樣:

  綁定掛載卷:在宿主機上的路徑要人工的指定一個特定的路徑,在容器中也須要指定一個特定的路徑,兩個已知的路徑創建關聯關係

  docker管理卷: 只須要在容器內指定容器的掛載點是什麼,而被綁定宿主機下的那個目錄,是由容器引擎daemon自行建立一個空的目錄,或者使用一個已經存在的目錄,與存儲卷創建存儲關係,這種方式極大解脫用戶在使用卷時的耦合關係,缺陷是用戶沒法指定那些使用目錄,臨時存儲比較適合

2.7.3.3 docker volume 子命令

docker 專門提供了 volume 子命令來操做數據卷:

create        建立數據卷 inspect      顯示數據卷的詳細信息  ls               列出全部的數據卷 prune        刪除全部未使用的 volumes,而且有 -f 選項 rm             刪除一個或多個未使用的 volumes,而且有 -f 選項

2.7.3.4 使用mount 語法掛載數據卷

使用 --volume(-v) 選項來掛載數據卷,如今 docker 提供了更強大的 --mount 選項來管理數據卷。mount 選項能夠經過逗號分隔的多個鍵值對一次提供多個配置項,所以 mount 選項能夠提供比 volume 選項更詳細的配置。使用 mount 選項的經常使用配置以下:

type 指定掛載方式,咱們這裏用到的是 volume,其實還能夠有 bind 和 tmpfs。 volume-driver 指定掛載數據卷的驅動程序,默認值是 local。 source 指定掛載的源,對於一個命名的數據卷,這裏應該指定這個數據卷的名稱。在使用時能夠寫 source,也能夠簡寫爲 src。 destination 指定掛載的數據在容器中的路徑。在使用時能夠寫 destination,也能夠簡寫爲 dst 或 target。 readonly 指定掛載的數據爲只讀。 volume-opt 能夠指定屢次,用來提升更多的 mount 相關的配置。

 2.7.3.5 數據的覆蓋問題

若是掛載一個空的數據捲到容器中的一個非空目錄中,那麼這個目錄下的文件會被複制到數據卷中。

若是掛載一個非空的數據捲到容器中的一個目錄中,那麼容器中的目錄中會顯示數據卷中的數據。若是原來容器中的目錄中有數據,那麼這些原始數據會被隱藏掉。

這兩個規則都很是重要:

  靈活利用第一個規則能夠幫助咱們初始化數據卷中的內容。

  掌握第二個規則能夠保證掛載數據卷後的數據老是你指望的結果 

2.7.3.5 在容器中使用volumes

爲docker run 命令使用-v 選項可以使用volume

docker-managed volume docker run -it -name bbox1 -v /data busybox   #/data指定docker的目錄 docker inspect -f {{.Mounts}} bbox1 #查看rbox1容器的卷,卷標識符及掛載的主機目錄 Bind-mount volume docker run -it -v HOSTDIR:VOLUMEDIR --name bbox2 busybox #宿主機目錄:容器目錄 docker inspect -f {{.Mounts}} bbox2

2.7.4 實操:docker管理卷 

[root@docker ~]# docker run --help -v, --volume list Bind mount a volume --volume-driver string           Optional volume driver for the container --volumes-from list              Mount volumes from the specified container(s) [root@docker ~]# docker run --name volume01 -it -v /data busybox / # ls / bin data dev etc home proc root sys tmp usr var   ---> data目錄默認是不存在的 [root@docker ~]# docker inspect volume01 "Mounts": [ { "Type": "volume", "Name": "632514d35d152b677553d166601fe44091720ac9788dc30e2912cb7c63ba76b4", "Source": "/var/lib/docker/volumes/632514d35d152b677553d166601fe44091720ac9788dc30e2912cb7c63ba76b4/_data", "Destination": "/data",  ---> 容器中的data目錄掛載在宿主機上的Source所指目錄 "Driver": "local", "Mode": "", "RW": true, "Propagation": "" } ], #宿主機: 能夠很方便實如今宿主機和容器之間共享目錄 [root@docker ~]# cd /var/lib/docker/volumes/632514d35d152b677553d166601fe44091720ac9788dc30e2912cb7c63ba76b4/_data [root@docker _data]# ls [root@docker _data]# echo "hello zhujingxing" > test.html #在容器中查看 / # ls /data/ test.html / # cat /data/test.html hello zhujingxing / # echo "hello zisefeizhu" >> /data/test.html [root@docker _data]# cat test.html hello zhujingxing hello zisefeizhu #docker綁定卷 [root@docker ~]# docker run --name volume02 -it --rm -v /data/volumes/volume02:/data busybox / # [root@docker _data]# docker inspect volume02 "Mounts": [ { "Type": "bind", "Source": "/data/volumes/volume02", "Destination": "/data", "Mode": "", "RW": true, "Propagation": "rprivate" } ], [root@docker _data]# cd /data/volumes/volume02/   ---> 目錄會自動建立 [root@docker volume02]# echo "<h1> hello zhujingxing </h1>" > index.html / # cat /data/index.html <h1> hello zhujingxing </h1> #持久化的實現 / # exit [root@docker ~]# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES [root@docker ~]# docker run --name volume02 -it  --rm -v  /data/volumes/volume02:/data/web/htm busybox / # cat /data/web/htm/index.html <h1> hello zhujingxing </h1> #使用golong模板查看 [root@docker volume02]# docker inspect -f {{.Mounts}} volume02 [{bind /data/volumes/volume02 /data/web/htm   true rprivate}] [root@docker volume02]# docker inspect -f {{.NetworkSettings}} volume02 {{ 03562d1741cd19808e9a904d991a6c0f1db1ba9d5098e20779edbd4529798d0d false  0 map[] /var/run/docker/netns/03562d1741cd [] []} {fd45e4499b9bbebbfe6d383593a20ccaadbcac2d130bb097037ed19444abb009 10.0.0.1  0 10.0.0.3 16  02:42:0a:00:00:03} map[bridge:0xc0005a8000]} ##引入某個鍵(鍵中鍵) [root@docker volume02]# docker inspect -f {{.NetworkSettings.IPAddress}} volume02 10.0.0.3

場景:一個docker容器能夠關聯到宿主機的目錄中,也可讓兩個docker容器同時關聯到同一個宿主機的目錄中,實現共享使用同一個存儲卷,容器之間的數據共享

[root@docker ~]# docker run --name volume02 -it  --rm -v  /data/volumes/volume02:/data/web/htm busybox / # cat /data/web/htm/index.html <h1> hello zhujingxing </h1> [root@docker volume02]# docker run --name volume03 -it --rm -v /data/volumes/volume02:/data busybox / # cat /data/index.html <h1> hello zhujingxing </h1> [root@docker ~]# cat /data/volumes/volume02/index.html <h1> hello zhujingxing </h1>

場景:須要多個容器同進使用多個卷,卷在那裏寫每次初始化時都要使用-v來指定,若是不想記錄這個路徑,docker還支持複製其餘的存儲卷路徑

實現:制定一個容器,不執行任何任務,建立時,只要指定它的存儲路徑,做爲其餘相關聯容器的基礎架構容器,其餘的容器啓動時去複製它的存儲卷設置,可是這樣的點浪費,不過使用joined container的基礎的話,幾個容器原本就有密切的關係,如nginx+tomcat,nginx的容器和tomcat容器共享一個底層的網絡,有一個對外的接口,有一個loop接口,這樣80給nginx,在內loop給tomcat,請求進來,nginx做爲反射代理轉給tomcat就能夠了,再加一個mysql,也是使用loop接口來通信。

讓它們共享網絡名稱空間中的uts,net,ipc,還能夠共享存儲卷,ngInx處理靜態,tomcat處理動態的,在同一個目錄下,使用存儲捲來解決這個問題,這種組織方式使用構建應用。

 

#多個容器的卷使用同一個主機目錄,如 docker run -it --name c1 -v /docker/volumes/v1:/data/ busybox #複製使用其餘容器的卷,爲docker run 命令使用--volumes-from選項 docker run -it --name bbox1 -v /docker/volumes/v1:/data busybox docker run -it --name bbox2 --volumes-from bbox1 busybox #制定基礎鏡像(網上有專門製做基礎架構容器的,不用啓動,只要建立就能夠了) [root@docker ~]# docker run --name basiccon -it -v /data/basic/volume/:/data/web/html busybox / # ls /data/ web [root@docker ~]# docker run --name nginx --network container:basiccon --volumes-from basiccon -it busybox   #加入網絡,同時複製卷 / # ls /data/ web

2.8 dockerfile詳解

各位想必應該記得,此前若是安裝一個nginx的話,安裝完之後,一般不會運行在默認配置下,那所以,咱們一般須要去改一改它的配置文件或者定義模塊化配置文件,而後啓動服務。那爲何,nginx的默認配置不符合咱們的須要呢?很顯然,不一樣的生產場景所須要用到的配置參數各不相同,所以,對方只能用一個默認的,認爲適用於大多數廣泛場景情形的或者適用於較小主機資源狀況下的那麼一個設定來啓動服務,一樣的邏輯,各位試想一下,若是咱們從docker hub下拖下來一個nginx的鏡像,去啓動nginx容器的時候,請問這個鏡像內的nginx容器內的配置文件必定就會符合咱們的須要嗎?應該說叫必定不會,基本上幾乎不能徹底適合咱們的須要,此時咱們就必須去要修改配置,那怎麼改呢?

以前作法:啓動容器docker exec連進容器,在內部執行vi,再reload重啓

另一種方式:假設咱們把它對應的那個配置文件的路徑作存儲卷,從咱們宿主機上加載文件,在宿主機上進行編輯,也能讓它當即生效,啓動容器以前先把它編輯好(容器啓動以前,咱們事先找一目錄把配置文件準備好,而後啓動容器時,把容器內的應用程序默認加載配置文件的路徑與宿主機上的目錄進行創建關聯關係,而後去啓動容器,也能加載到在宿主機上定製的配置文件)

缺點:咱們在宿主上作的編輯,能不能讓它當即生效呢?好比咱們啓動之後發現,有些參數仍是須要改,改完之後依然須要重載才能生效。

還有一種方式:自制鏡像

基於容器:先啓動起來,交互式連入進來,作修改,改完之後,改的結果必定時保存在最上層的可寫層的。這個時候咱們把可寫層保存在一個新鏡像中,然後,咱們再去建立容器時,根據咱們本身所建立的鏡像來使用。

缺點:作的鏡像也是直接把文件直接備進鏡像中的,直接寫死在鏡像中的就是,若是咱們想在改,仍是改不了,運行過程中去修改配置的需求可能對於作運維來說,變動不就是平常操做嗎?不少時候,也有可能須要隨時進行修改。那依然解決不了問題。並且這種備進鏡像的設計方式,最悲慘的地方在於:一次更新,維護複雜 。環境簡單能夠使用。

 

基於dockerfiledockerfile,至關因而一個文檔,客戶能夠基於dockerfile生成新的容器。dockerfile僅僅是用來製做鏡像的源碼文件,是構建容器過程當中的指令,docker可以讀取dockerfile的指定進行自動構建容器,基於dockerfile製做鏡像,每個指令都會建立一個鏡像層,即鏡像都是多層疊加而成,所以,層越多,效率越低,建立鏡像,層越少越好。所以能在一個指令完成的動做盡可能經過一個指令定義。

2.8.1 docker鏡像製做的工做邏輯

首先須要有一個製做鏡像的目錄,該目錄下有個文件,名稱必須爲Dockerfile,Dockerfile有指定的格式,#號開頭爲註釋,,指定默認用大寫字母來表示,以區分指令和參數,docker build讀取Dockerfile是按順序依次Dockerfile裏的配置,且第一條非註釋指令必須是FROM 開頭,表示基於哪一個基礎鏡像來構建新鏡像。能夠根據已存在的任意鏡像來製做新鏡像。

Dockerfile能夠使用環境變量,用ENV來定義環境變量,變量名支持bash的變量替換,如${variable:-word},表示若是變量值存在,就使用原來的變量,變量爲空時,就使用word的值做爲變量的值,通常使用這個表示法。${variable:+word},表示若是變量存在了,不是空值,那麼變量將會被賦予爲word對應的值,若是變量爲空,那麼依舊是空值。

[root@docker ~]# echo ${NAME:-zhujingxing} zhujingxing [root@docker ~]# NAME=zisefeizhu [root@docker ~]# echo ${NAME:-zhujingxing} zisefeizhu [root@docker ~]# echo ${NAME:+zhujingxing} zhujingxing [root@docker ~]# unset NAME [root@docker ~]# echo ${NAME:+zhujingxing} [root@docker ~]# 

 

dockerignore file:在docker發送上下文給 docker daemon 以前,會尋找.dockerignore file文件,去排除一些不須要的文件,或者很大的文件,不把這些文件發送給docker daemon,提高效率。而若是以後須要用到,則能夠使用 ADD 或者 COPY 把他們放進image中。

2.8.2 dockerfile 格式

Dockerfile總體就兩類語句組成:

    # Comment 註釋信息

    Instruction arguments 指令 參數,一行一個指令。

Dockerfile文件名首字母必須大寫。

Dockerfile指令不區分大小寫,可是爲方便和參數作區分,一般指令使用大寫字母。

Dockerfile中指令按順序從上至下依次執行。

Dockerfile中第一個非註釋行必須是FROM指令,用來指定製做當前鏡像依據的是哪一個基礎鏡像。

Dockerfile中須要調用的文件必須跟Dockerfile文件在同一目錄下,或者在其子目錄下,父目錄或者其它路徑無效

2.8.3 dockerfile --> image --> registry

2.8.4 dockerfile 指令語法

1.FROM 介紹 FROM指令是最重要的一個且必須爲 Dockerfile文件開篇的第一個非註釋行,用於爲映像文件構建過程指定基準鏡像,後續的指令運行於此基準鏡像所提供的運行環境 . 實踐中,基準鏡像能夠是任何可用鏡像文件,默認狀況下, docker build會在 docker主機上查找指定的鏡像文件,在其不存在時,則會從 Docker Hub Registry上拉取所需的鏡像文件 .若是找不到指定的鏡像文件, docker build會返回一個錯誤信息 語法 FROM <repository>[:<tag>] 或者 FROM <repository>@<digest>

<repository>:指定做爲base image的名稱 <tag>:base image的標籤,爲可選項,省略時默認爲 latest; <digest>爲校驗碼 例子 FROM busybox:latest
2.LABEL 介紹 LABEL用於爲鏡像添加元數據,元數以鍵值對的形式指定,使用LABEL指定元數據時,一條LABEL指定能夠指定一條或多條元數據,指定多條元數據時不一樣元數據之間經過空格分隔。推薦將全部的元數據經過一條LABEL指令指定,以避免生成過多的中間鏡像。 語法 LABEL <key>=<value> <key>=<value> <key>=<value> ... 例子 LABEL version="1.0" description="這是一個Web服務器" by="IT筆錄" 指定後能夠經過docker inspect查看: docker inspect itbilu/test "Labels": { "version": "1.0", "description": "這是一個Web服務器", "by": "IT筆錄" },
3.COPY 介紹 用於從 Docker宿主機複製文件至建立的鏡像文件。 語法 COPY <src>... <dest> 或者 COPY ["<src>",... "<dest>"] <src>:要複製的源文件或者目錄,支持通配符 <dest>:目標路徑,即正建立的鏡像的文件系統路徑,建議使用絕對路徑,絕對路徑爲鏡像中的路徑,而不是宿主機的路徑。不然,COPY指令會以WORKDIR爲其起始路徑。 若是路徑中若是包含空白字符,建議使用第二種格式用引號引發來,不然會被當成兩個文件。 規則 <src>必須是build上下文中的目錄,即只能放在workshop這個工做目錄下,不能是其父目錄中的文件。 若是<src>是目錄,則其內部的文件或則子目錄會被遞歸複製,但<src>目錄自己不會被複制。 若是指定了多個<src>,或者<src>中使用通配符,則<dest>必須是一個目錄,且必須以 / 結尾。 若是<dest>事先不存在,它將會被自動建立,包括其父目錄路徑。 例子 copy文件 COPY index.html /data/web/html/ copy目錄 COPY yum.repos.d /etc/yum.repos.d/

4.ADD 介紹 ADD指令跟COPY相似,不過它還支持使用tar文件和URL路徑。主機能夠聯網的狀況下,docker build能夠將網絡上的某文件引用下載並打包到新的鏡像中。 語法 ADD <src> ... <dest>或 ADD ["<src>",... "<dest>"] 規則 同COPY指令的4點準則  若是<src>爲URL且<dest>不以/結尾,則<src>指定的文件將被下載並直接被建立爲<dest>;若是<dest>以/結尾,則文件名URL指定的文件將被直接下載,並保存爲<dest>/<filename>,注意,URL不能是ftp格式的url。 若是<src>是一個本地系統上的壓縮格式的tar文件,它將被展開爲一個目錄,其行爲相似於「tar -x」命令,而後,經過URL獲取到的tar文件將不會自動展開。 若是<src>有多個,或其間接或直接使用了通配符,則<dest>必須是一個以/結尾的目錄路徑;若是<dest>不以/結尾,則其被視做一個普通文件,<src>的內容將被直接寫入到<dest>; 例子 ADD  http://nginx.org/download/nginx-1.15.5.tar.gz  /usr/local/src/

5.WORKDIR 介紹 workdir爲工做目錄,指當前容器環境的工做目錄,用於爲Dockerfile中全部的RUN、CMD、ENTRYPOINT、COPY和ADD指定設定工做目錄。在Dockerfile文件中,WORKDIR指令可出現屢次,其路徑也能夠爲相對路徑,不過,其是相對此前一個WORKDIR指令指定的路徑。另外,WORKDIR也可調用由ENV指定定義的變量。 語法 WORKDIR  <dirpath> 例子 WORKDIR /var/log WORKDIR  $STATEPATH
6.VOLUME 介紹 定義卷,只能是docker管理的卷VOLUME爲容器上的目錄(也就是說只能指定容器內的路徑,不能指定宿主機的路徑),用於在image中建立一個掛載點目錄,以掛載Docker host上的卷或其它容器上的卷。若是掛載點目錄路徑下此前在文件存在,docker run命令會在卷掛載完成後將此前的全部文件複製到新掛載的卷中。 語法 VOLUME <mountpoint> 或者 VOLUME ["<mountpoint>"] 例子 VOLUME /data/mysql/

7.EXPOSE 介紹 暴露指定端口,用於爲容器打開指定要監聽的端口以實現與外部通訊。EXPOSE指令可一次指定多個端口,可是不能指定暴露爲宿主機的指定端口,由於指定的宿主機端口可能已經被佔用,所以這裏使用隨機端口。好比容器提供的是一個https服務且須要對外提供訪問,那就須要指定待暴露443端口,而後在使用此鏡像啓動容器時搭配 -P 的參數才能將待暴露的狀態轉換爲真正暴露的狀態,轉換的同時443也會轉換成一個隨機端口,跟 -p :443一個意思 語法 EXPOSE <port>[/<protocol>] [<port>[/<protocol>] ...] 例子 EXPOSE 11211/udp 11211/tcp
8.ENV 介紹 ENV用於爲鏡像定義所需的環境變量,並可被Dockerfile文件中位於其後的其它指令(如ENV、ADD、COPY等)所調用,即先定義後調用,調用格式爲$variable_name或${variable_name} 使用docker run啓動容器的時候加上 -e 的參數爲variable_name賦值,能夠覆蓋Dockerfile中ENV指令指定的此variable_name的值。可是不會影響到dockerfile中已經引用過此變量的文件名。 語法 ENV <key> <value> ENV <key>=<value> ... 第一種格式一次只能定義一個變量,<key>以後全部內容都會被視爲<value>的組成部分 第二種格式一次能夠定義多個變量,每一個變量爲一個"="的鍵值對,若是<value>中包含空格,能夠用反斜線  進行轉義,也能夠爲<value>加引號,另外參數過長時可用反斜線作續行。 定義多個變量時,建議使用第二種方式,由於Dockerfile中每一行都是一個鏡像層,構建起來比較吃資源。 例子 # 基於busybox啓動一個鏡像,將test文件拷貝至容器內的/usr/local/zisefeizhu/目錄下。 [root@docker dockerfile]# pwd /root/dockerfile [root@docker dockerfile]# echo zhujingxing  > test [root@docker dockerfile]# vim Dockerfile  # Description: test image FROM busybox ENV file=zisefeizhu ADD ./test /usr/local/$file/ [root@docker dockerfile]# docker build -t busy:v1 ./ # 根據此鏡像啓動容器並查看文件是否拷貝成功,而且查看file變量的值 [root@docker dockerfile]# docker run --name busy02 --rm busy:v1 ls /usr/local/zisefeizhu zhujingxing [root@docker dockerfile]# docker run --name busy02 --rm busy:v1 printenv PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin HOSTNAME=57111b7b234c file=zisefeizhu HOME=/root # 接下來咱們在啓動容器的時候加上-e參數爲file變量指定一個新值,而且查看file變量的值 [root@docker dockerfile]# docker run --name busy02 -e file=zhujingxing --rm busy:v1 printenv PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin HOSTNAME=787ad8582fs0 file=zhujingxing HOME=/root # 此時再看test的文件,依然是在zisefeizhu的目錄下的 [root@docker1 docker]# docker run --name busy02 -e file=bbb --rm busy:v1 ls /usr/local/zisefeizhu zhujingxing # 這是由於docker build屬於第一階段,而docker run屬於第二階段。第一階段定義file變量的值zisefeizhu已經被引用了,生米已經煮成熟飯了,後續階段再改file變量的值也影響不了zisefeizhu。

9.RUN 介紹 RUN用於指定 docker build過程當中運行的程序,其能夠是任何命令,可是這裏有個限定,通常爲基礎鏡像能夠運行的命令,如基礎鏡像爲centos,安裝軟件命令爲yum而不是ubuntu裏的apt-get命令。 RUN和CMD均可以改變容器運行的命令程序,可是運行的時間節點有區別,RUN表示在docker build運行的命令,而CMD是將鏡像啓動爲容器運行的命令。由於一個容器正常只用來運行一個程序,所以CMD通常只有一條命令,若是CMD配置多個,則只有最後一條命令生效。而RUN能夠有多個。 語法 RUN <command> 或者 RUN ["<executable>", "<param1>", "<param2>"] 第一種格式中,<command>一般是一個shell命令,系統默認會把後面的命令做爲shell的子進程來運行,以「/bin/sh -c」做爲父進程來運行它,這意味着此進程在容器中的PID不爲1(若是是PID爲1完事就解釋了。用腿想),不能接收Unix信號,所以,當使用 docker stop <container>命令中止容器時,此進程接收不到SIGTERM信號; 第二種語法格式中的參數是一個JSON格式的數組,其中<executable>爲要運行的命令,後面的<paramN>爲傳遞給命令的選項或參數;然而,此種格式指定的命令不會以「/bin/sh -c」來發起(也就是直接由內核建立),表示這種命令在容器中直接運行,不會做爲shell的子進程,所以常見的shell操做如變量替換以及通配符(?,*等)替換將不會進行。過,若是要運行的沒能力依賴此shell特性的話,能夠將其替換爲相似下面的格式 RUN ["/bin/bash","-C","<executable>","<paraml>"] 若是RUN的命令不少,就用&&符號鏈接多個命令,少構建鏡像層,提升容器的效率 例子 基礎鏡像爲centos,RUN多個命令 因爲安裝是到互聯網上的倉庫進行安裝,因此,建議把centos的yum源配置爲本地,即建立鏡像時,把yum的配置有本地倉庫源配置在CentOS-Base.repo文件放在imp1下面,配置文件配置ADD拷貝一份到新建鏡像的/etc/yum.repos.d目錄下,由於常常默認會優先加載CentOS-Base.repo下的包,可是不建議使用這個方法,除非本地倉庫有足夠的包解決依賴關係,不然建議僅使用默認的便可 編輯dockerfile [root@docker img1]# vim Dockerfile # Description: nginx image FROM centos:7.3.1611 MAINTAINER "zisefeizhu <zisefeizhu@zhujingxing.com>" ENV nginx_ver=1.14.0 ENV nginx_url=http://nginx.org/download/nginx-${nginx_ver}.tar.gz WORKDIR "/usr/local/src" ADD CentOS-Base.repo  /etc/yum.repos.d/ ADD ${nginx_url} /usr/local/src/ RUN tar xf nginx-${nginx_ver}.tar.gz && \         yum -y install gcc pcre-devel openssl-devel make &&  \         cd nginx-${nginx_ver} && \         ./configure && make && make install 建立鏡像 [root@docker img1]# docker build -t nginx:v1 ./ 運行容器,啓動nginx進程 [root@docker img1]# docker run -it --rm --name nginxv1 nginx:v1 [root@ccedfdf5e63f src]# /usr/local/nginx/sbin/nginx 此時,nginx進程運行於後臺,不建議這麼作,由於容器的進程要運行於前臺模式,不然容器會終止,nginx運行於前臺,須要在nginx的配置文件nginx.conf裏添加配置項 vi /usr/local/nginx/conf/nginx.conf daemon off; 這樣使得nginx運行於前臺 再次運行nginx,則運行於前臺 或者經過-g選項,在運行nginx的全局配置模式以後再運行某些參數,注意off後面的冒號 [root@ccedfdf5e63f local]# /usr/local/nginx/sbin/nginx -g "daemon off;"


10.CMD 介紹 指定啓動容器的默認要運行的程序,也就是PID爲1的進程命令,且其運行結束後容器也會終止。若是不指定,默認是bash。CMD指令指定的默認程序會被docker run命令行指定的參數所覆蓋。Dockerfile中能夠存在多個CMD指令,但僅最後一個生效。由於一個docker容器只能運行一個PID爲1的進程。相似於RUN指令,也能夠運行任意命令或程序,可是二者的運行時間點不一樣:RUN指令運行在docker build的過程當中,而CMD指令運行在基於新鏡像啓動容器(docker run)時。 語法 CMD <command> 或者 CMD ["<executable>","<param1>","<param2>"] 或者 CMD["<param1>","<param2>"] 前兩種語法格式的意義同 RUN 第三種則用於爲 ENTRYPOINT指令提供默認參數 例子 CMD /bin/httpd -f -h ${WEB_DOC_ROOT} CMD [ "/bin/httpd","-f","-h ${WEB_DOC_ROOT}"] CMD [ "/bin/sh","-c","/bin/httpd","-f","-h ${WEB_DOC_ROOT}"] CMD [ "/bin/sh","-c","/bin/httpd","-f","-h /data/web/html"] CMD ["/usr/local/nginx/sbin/nginx","-g","daemon off;"] [root@docker dockerfile]# cat Dockerfile #Description: test image FROM busybox LABEL maintainer="zisefeizhu <zisefeizhu@qq.com>" app="httpd" ENV WEBDIR="/data/web/html" RUN mkdir -p ${WEBDIR} && \ echo 'this is a test web' > ${WEBDIR}/index.html CMD [ "sh","-c","/bin/httpd","-f","-h ${WEBDIR}" ] [root@docker dockerfile]# docker build -t httpd:v1 ./ [root@docker dockerfile]# docker run --name web01 -it --rm httpd:v1 ls /data/web/html index.html # 能夠看出命令行的參數已經替代了本來的CMD指令指定的程序

11.ENTRYPOINT 介紹 相似CMD指令的功能,用於爲容器指定默認運行程序。Dockerfile中能夠存在多個ENTRYPOINT指令,但僅最後一個生效。與CMD區別在於,由ENTRYPOINT啓動的程序不會被docker run命令行指定的參數所覆蓋,並且這些命令行參數會被當作參數傳遞給ENTRYPOINT指令指定的程序。不過,docker run的--entrypoint選項的參數可覆蓋ENTRYPOINT指定的默認程序 語法 ENTRYPOINT command param1 param2 ENTRYPOINT ["executable", "param1", "param2"] 例子 [root@docker dockerfile]# cat Dockerfile #Description: test image FROM busybox LABEL maintainer="zisefeizhu <zisefeizhu@qq.com>" app="httpd" ENV WEBDIR="/data/web/html" RUN mkdir -p ${WEBDIR} && \ echo 'this is a test web' > ${WEBDIR}/index.html ENTRYPOINT [ "sh","-c","/bin/httpd -f -h ${WEBDIR}" ] [root@docker dockerfile]# docker build -t httpd:v2 ./ [root@docker dockerfile]# docker run --name web01 -it --rm httpd:v2 # 也是前臺啓動,複製一個窗口,kill掉容器,而後開始docker run結尾傳入新的指令 [root@docker dockerfile]# docker run --name web01 -it --rm httpd:v2 ls /data/web/html # 能夠看到沒有反應,這種狀況實際上是把ls /data/web/html當作參數傳給了/bin/httpd -f -h ${WEBDIR}程序。只是httpd不識別罷了。咱們kill掉容器。加上--entrypoint參數再試一下 [root@docker dockerfile]# docker run --name web01 -it --rm --entrypoint="" httpd:v2 ls /data/web/html index.html # 使用--entrypoint參數替換命令成功。 # 再測試下CMD的第三種語法,CMD指令的後面的命令做爲參數傳給ENTRYPOINT指令後的命令 [root@docker dockerfile]# vim Dockerfile # Description: test image FROM busybox LABEL maintainer="zisefeizhu <zisefeizhu@qq.com>" app="httpd" ENV WEBDIR="/data/web/html" RUN mkdir -p ${WEBDIR} && \ echo 'this is a test web' > ${WEBDIR}/index.html CMD [ "/bin/httpd -f -h ${WEBDIR}" ] ENTRYPOINT [ "sh","-c" ] [root@docker dockerfile]# docker build -t httpd:v3 ./ [root@docker dockerfile]# docker run --name web01 -it --rm httpd:v3 # OK的,前面有說過:指定ENTRYPOINT的狀況下,若是docker run命令行結尾有參數指定,那CMD後面的參數不生效,下面咱再試試,還用v3的鏡像。 [root@docker dockerfile]# docker run --name web01 -it --rm httpd:v3 "ls /data/web/html" index.html [root@docker dockerfile]# vim Dockerfile  FROM nginx:1.14-alpine LABEL maintainer="zhujingxing  <zisefeizhu@zhujingxing>" ENV NGX_DOC_ROOT='/data/web/html/' ADD index.html ${NGX_DOC_ROOT} ADD entrypoint.sh /bin/ CMD ["/usr/sbin/nginx","-g","daemon off;"]     //注:雙引號 ENTRYPOINT ["/bin/entrypoint.sh"] [root@docker dockerfile]# vim entrypoint.sh  #!/bin/sh cat > /etc/nginx/conf.d/www.conf <<EOF server {         server_name $HOSTNAME;         listen ${IP:-0.0.0.0}:${PORT:-80};         root ${NGX_DOC_ROOT:-/usr/share/nginx/html}; } EOF exec "$@" [root@docker dockerfile]# docker build -t myweb:v0.3-6 ./ [root@docker dockerfile]# docker run --name myweb1 --rm -P -e "PORT=8080" myweb:v0.3-6 [root@docker dockerfile]# docker exec -it myweb1 /bin/sh / # netstat -lnt Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address           Foreign Address         State        tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN       tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN       / # ps PID   USER     TIME   COMMAND     1 root       0:00 nginx: master process /usr/sbin/nginx -g daemon off;     8 nginx      0:00 nginx: worker process     9 root       0:00 /bin/sh    15 root       0:00 ps / # wget -O - -q adf1e144ec50 <h1>zhujingxing</h1> / # exit

12.HEALTHCHECK 介紹 健康檢查。此指令的就是告訴docker若是檢查容器是否正常工做。拿apache舉例,即使進程運行,服務也不必定正常,由於萬一root指錯了呢? 經過HEALTHCHECK,咱們能夠知道如何測試一個容器查檢一個它是否在工做,好比檢測一個web 服務是否陷入死循環,不能處理新的鏈接、即便服務器進程仍在運行。 當一個窗口指定了健康檢查時、除了正常狀態以外、還會有一個健康狀態做爲初始、若是檢查經過、則會變成健康狀態、若是通過了必定次數的連續故障、則會變成非健康狀態。 語法 HEALTHCHECK [OPTIONS] CMD command (經過在容器中運行一個命令執行健康檢查) HEALTHCHECK NONE (禁用從基本鏡像繼承的任何健康檢查) HEALTHCHECK指令讓咱們去定義一個CMD,在CMD後面編寫一條命令去判斷咱們的服務運行是否正常。檢查確定不是一次性的,因此OPTIONS就是指定檢查的頻率等等。 --interval=DURATION(默認值:30s):每隔多久檢查一次,默認30s --timeout=DURATION(默認值:30s):超時時長,默認30s --start-period=DURATION(默認值:0s):啓動健康檢查的等待時間。由於容器啓動成功時,進程不必定立馬就啓動成功,那過早開始檢查就會返回不健康。 --retries=N(默認值:3):若是檢查一次失敗就返回不健康未免太武斷,因此默認三次機會。 CMD健康檢測命令發出時,返回值有三種狀況 0:成功 1:不健康 2:保留,無實際意義。 HEALTHCHECK NONE就是不作健康檢查 規則 啓動週期爲須要時間啓動的容器提供初始化時間。 在此期間的探測失敗不會計入最大重試次數。 可是,若是在啓動期間運行情況檢查成功,則認爲容器已啓動,而且全部連續的故障都將計入最大重試次數。 單次運行檢查花費時間超過timeout指定時間、斷定失敗。 每一個Dockerfile中只能存在一個HEALTHCHECK指令,若是有多個則最後一個起做用。 HEALTHCHECK CMD後面的命令既能夠是一個shell命令、也能夠是一個exec 的數組。 例子 實例:每隔五分鐘檢查一次網絡服務器是否可以在三秒鐘內爲該網站的主頁面提供服務 HEALTHCHECK --interval=5m --timeout=3s CMD curl -f http://localhost/ || exit 1 爲方便故障探測調試、檢測命令經過stdout或者stderr輸出的文本都會被緩存在健康狀態中(緩存大小爲4096字節)、並能夠經過docker inspect查詢 當容器的運行情況發生變化時,新的狀態會生成一個health_status事件 成功的例子: [root@docker dockerfile]# cat Dockerfile  FROM nginx:1.14-alpine LABEL maintainer="zhujingxing  <zisefeizhu@zhujingxing>" ENV NGX_DOC_ROOT=」/data/web/html/」 ADD index.html ${NGX_DOC_ROOT} ADD entrypoint.sh /bin/ EXPOSE 80/TCP HEALTHCHECK --start-period=3s CMD wget -O - -q http://${IP:-0.0.0.0}:${PORT:80}/ CMD ["/usr/sbin/nginx","-g","daemon off;"] ENTRYPOINT ["/bin/entrypoint.sh"] [root@docker dockerfile]# docker build -t myweb:v0.1 ./ [root@docker dockerfile]# docker run --name myweb1 --rm -P -e "PORT=8080" myweb:v0.1 [root@docker dockerfile]# docker exec -it myweb1 /bin/sh / # netstat -tnl Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address           Foreign Address         State        tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN       tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN       / # wget -O [root@docker dockerfile]# docker run --name myweb1 --rm -P -e "PORT=8080" myweb:v0.1 127.0.0.1 - - [03/Aug/2019:13:15:35 +0000] "GET / HTTP/1.1" 200 612 "-" "Wget" "-" 127.0.0.1 - - [03/Aug/2019:13:15:43 +0000] "GET / HTTP/1.1" 200 612 "-" "Wget" "-" 失敗的例子 [root@docker dockerfile]# vim Dockerfile  FROM nginx:1.14-alpine LABEL maintainer="zhujingxing  <zisefeizhu@zhujingxing>" ENV NGX_DOC_ROOT='/data/web/html/' ADD index.html ${NGX_DOC_ROOT} ADD entrypoint.sh /bin/ EXPOSE 80/TCP HEALTHCHECK --start-period=3s CMD wget -O - -q http://${IP:-0.0.0.0}:10080/ CMD ["/usr/sbin/nginx","-g","daemon off;"] ENTRYPOINT ["/bin/entrypoint.sh"] [root@docker dockerfile]# docker build -t myweb:v0.2 ./ [root@docker dockerfile]# docker run --name myweb1 --rm -P -e "PORT=8080" myweb:v0.2 //三個週期默認1.5分鐘後報錯 [root@docker dockerfile]# docker exec -it myweb1 /bin/sh / # netstat -tnl Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address           Foreign Address         State        tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN       tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN     

13.ARG 介紹 ARG命令同EVN相似,也是指定一個變量,但不一樣的是,ENV指令配合-e參數能夠在docker run過程當中傳參,而使用ARG指令配合--build-arg參數能夠在docker build過程當中傳參,這方便了咱們爲不一樣場景構建不一樣鏡像。 語法 ARG <name>[=<default value>] 例子 [root@docker dockerfile]# vim Dockerfile FROM nginx:1.14-alpine ARG AUTHOR=";zisefeizhu <zisefeizhu@qq.com>" # 指定默認值 LABEL maintainer=$AUTHOR ENV NGXDIR='/data/web/html/' ADD index.html $NGXDIR ADD entrypoint.sh /bin/ CMD ["nginx", "-g", "daemon off;"] ENTRYPOINT ["/bin/entrypoint.sh"] [root@docker dockerfile]# docker build --build-arg AUTHOR="zhujingxing <zhujingxing@qq.com>" -t nginx:v4 ./ [root@docker dockerfile]# docker image inspect nginx:v4 | grep maintainer "maintainer": "zhujingxing <zhujingxing@qq.com>" # 上面只是以maintainer舉例,實踐環境中能夠修改成不一樣jar包的名字構建不一樣java程序鏡像。

14.STOPSIJNAL 介紹 指定發送使容器退出的系統調用信號。docker stop之因此能中止容器,就是發送了15的信號給容器內PID爲1的進程。此指令通常不會使用。 語法 STOPSIGNAL signal

15.SHELL 介紹 用來指定運行程序默認要使用的shell類型,由於windows環境默認是powershell。此指令通常不會使用。 語法 SHELL ["executable", "parameters"] 例子 # Executed as powershell -command Write-Host hello SHELL ["powershell", "-command"] RUN Write-Host hello # Executed as cmd /S /C echo hello SHELL ["cmd", "/S"", "/C"] RUN echo hello

16.USER 介紹 USER用於指定運行image時的或運行Dockerfile中任何RUN、CMD或ENTRYPOINT指令指定的程序時的用戶名或UID,即改變容器中運行程序的身份。默認狀況下,container的運行身份爲root用戶。 語法 USER <user>[:<group>] USER <UID>[:<GID>] 實踐中UID須要是/etc/passwd中某用戶的有效UID,不然docker run命令將運行失敗 規則 使用USER指定用戶後,Dockerfile中其後的命令 RUN、CMD、ENTRYPOINT 都將使用該用戶。鏡像構建完成後,經過 docker run運行容器時,能夠經過-u 參數來覆蓋所指定的用戶。

17.ONBUILD 介紹 ONBUILD用於在Dockerfile中定義一個觸發器,用來指定運行docker指令。Dockerfile用於build鏡像文件,此鏡像文件亦可做爲base image被另外一個Dockerfile用做FROM指令的參數,並以之構建新的鏡像文件。在後面的這個Dockerfile中的FROM指令在build過程當中被執行時,將會「觸發」建立其base image的Dockerfile文件中的ONBUILD指令定義的觸發器。 語法 ONBUILD <INSTRUCTION> 規則 儘管任何指令均可註冊成爲觸發器指令,可是ONBUILD不能自我嵌套,且不會觸發FROM和MAINTAINER(LABEL)指令。 使用包含ONBUILD指令的Dockerfile構建的鏡像應該使用特殊的標籤,例如ruby:2.0-onbuild。 在ONBUILD指令中使用ADD或COPY指令應該格外當心,由於新構建過程的上下文在缺乏指定的源文件時會失敗。 ONBUILD 在構建鏡像時不會運行,是別人基於這個鏡像做爲基礎鏡像構建時,纔會運行。 例子 增長一個ONBUILD命令,執行RUN FROM centos:7.3.1611 ENV nginx_ver=1.14.0 ENV nginx_url=http://nginx.org/download/nginx-${nginx_ver}.tar.gz WORKDIR "/usr/local/src" EXPOSE 80/tcp ADD ${nginx_url} /usr/local/src/ RUN tar xf nginx-${nginx_ver}.tar.gz && yum -y install gcc pcre-devel openssl-devel make \ && cd nginx-${nginx_ver} && ./configure && make && make install CMD ["/usr/local/nginx/sbin/nginx","-g","daemon off;"] ONBUILD RUN echo -e "\nSunny do an onbuild~\n" >> /etc/issue 構建鏡像 [root@docker dockerfile]# docker build -t nginx:v6 ./ 基於nginx:v6啓動容器,此時/etc/issue還沒寫入echo要插入的信息 [root@docker dockerfile]# docker run -it --rm -P --name nginxv3 nginx:v6 /bin/bash [root@docker dockerfile]# cat /etc/issue \S Kernel \r on an \m 而後基於這個nginx:v6鏡像,再次製做一個新鏡像,編輯一個新的Dockerfile [root@docker dockerfile]# mkdir nginxv7 [root@docker dockerfile]# cd nginxv7/ [root@docker dockerfile]# vim Dockerfile FROM nginx:v6 CMD "/bin/bash" 構建鏡像,注意,會提示執行一個build trigger,以下Executing 1 build trigger [root@docker dockerfile]# docker build -t nginx:v7 ./  基於新鏡像nginx:v7啓動新容器nginxv7 [root@docker dockerfile]# docker run -it --rm --name nginxv7 nginx:v7 [root@becc66948713 src]# cat /etc/issue \S Kernel \r on an \m Sunny do an onbuild [root@becc66948713 src]# 此時,在舊的鏡像中的dockerfile裏的ONBUILD已經觸發,把信息寫入到/etc/issue裏

2.8.5 dockerfile 優化

最不容易發生變化的文件的拷貝操做放在較低的鏡像層中

器輕量化。從鏡像中產生的容器應該儘可能輕量化,能在足夠短的時間內中止、銷燬、從新生成並替換原來的容器。

爲了減小鏡像的大小,減小依賴,僅安裝須要的軟件包。

一個容器只作一件事。解耦複雜的應用,分紅多個容器,而不是全部東西都放在一個容器內運行。如一個 Python Web 應用,可能須要 Server、DB、Cache、MQ、Log 等幾個容器。一個更加極端的說法:One process per container。

減小鏡像的圖層。不要多個 Label、ENV 等標籤。

對續行的參數按照字母表排序,特別是使用apt-get install -y安裝包的時候。

使用構建緩存。若是不想使用緩存,能夠在構建的時候使用參數--no-cache=true來強制從新生成中間鏡像。

2.9 docker倉庫

細心的道友必定發現,我在第一章的集羣搭建中並無部署倉庫集羣。緣由是:我我的想把鏡像倉庫作成kubernetes集羣的pod,這樣的話必然牽扯到後端存儲。而存儲是我要單獨用一章來說的。爲了docker章節的完整性,不得不闡述鏡像倉庫,但這並不表明我集羣中的倉庫部署。僅僅是爲了學習,爲了章節完整性。

前面的章節有講過公有倉庫的使用,如 Docker.Hub 和阿里雲鏡像倉庫。這種方式有明顯的缺陷:push 和 pull 的速度很慢,倘若實際環境有上百臺機器,那須要多大帶寬才能 hold 住。因此多數時候仍是須要建立本身的私有倉庫。工做中的生產環境主機選擇基本有三種:自建機房、IDC機房託管和阿里公有云,前兩種狀況最好是將 docker 私有倉庫創建在局域網內,而第三種使用阿里雲鏡像倉庫無非是最恰當的選擇。

搭建私有倉庫有兩種種方式:

  使用 Docker 官方提供的 docker-distribution。能夠經過 docker container 或者 yum 的方式安裝。docker container 的方式須要把鏡像存儲目錄掛載到宿主機的某目錄下,防止容器意外停止或者刪除致使倉庫不可用。此種 registry 功能比較單一。

  使用 harbor,這是 VMware 基於 docker-distribution 二次開發的軟件,如今已經加入了 CNCF。功能強大,界面美觀。值得一提的是harbor支持中文,是否是很 happy,道友們。由於二次開發此軟件的主力是 VMware 中國區團隊。另外,本來的 harbor 部署是很是困難的,所以 harbor 官網直接把 harbor 作成了能夠在容器中運行的應用,且 harbor 容器啓動時要依賴於其它一些容器協同工做,因此它在部署和使用時須要用到 docker 的單機編排工具 docker compose。

在生產實際中,現階段的鏡像倉庫仍是harbor佔據明顯優點,so,這節,我將直接闡述harbor倉庫。

我曾寫過一篇《docker私有registry和harbor的使用》。我認爲已經闡述的足夠詳盡,有興趣的道友能夠了解:http://www.javashuo.com/article/p-nsuggzlw-n.html

2.9.1 harbor簡介

Harbor 是Vmware公司開源的企業級Docker Registry管理項目,開源項目地址:https://github.com/vmware/harbor

Harbor的全部組件都在Docker中部署,因此Harbor可以使用Docker Compose快速部署。(因爲Harbor是基於Docker Registry V2版本,因此docker版本至少1.10.0、docker-compose版本至少1.6.0) 

組件

(1)proxy:nginx前端代理,分發前端頁面ui訪問和鏡像上傳和下載流量;

(2)ui:提供前端頁面和後端API,底層使用mysql數據庫;

(3)registry:鏡像倉庫,負責存儲鏡像文件,當鏡像上傳完畢後經過hook通知ui建立repository,registry的token認證也是經過ui組件完成;

(4)adminserver是系統的配置管理中心附帶檢查存儲用量,ui和jobserver啓動時候回須要加載adminserver的配置

(5)jobsevice:負責鏡像複製工做,和registry通訊,從一個registry pull鏡像而後push到另外一個registry,並記錄job_log;

(6)log:日誌彙總組件,經過docker的log-driver把日誌彙總到一塊兒。

Harbor介紹

Harbor是一個用於存儲和分發Docker鏡像的企業級Registry服務器,經過添加一些企業必需的功能特性,例如安全、標識和管理等,擴展了開源Docker Distribution。做爲一個企業級私有Registry服務器,Harbor提供了更好的性能和安全。提高用戶使用Registry構建和運行環境傳輸鏡像的效率。Harbor支持安裝在多個Registry節點的鏡像資源複製,鏡像所有保存在私有Registry中, 確保數據和知識產權在公司內部網絡中管控。另外,Harbor也提供了高級的安全特性,諸如用戶管理,訪問控制和活動審計等。

Harbor特性

基於角色的訪問控制 :用戶與Docker鏡像倉庫經過「項目」進行組織管理,一個用戶能夠對多個鏡像倉庫在同一命名空間(project)裏有不一樣的權限。

鏡像複製 : 鏡像能夠在多個Registry實例中複製(同步)。尤爲適合於負載均衡,高可用,混合雲和多雲的場景。

圖形化用戶界面 : 用戶能夠經過瀏覽器來瀏覽,檢索當前Docker鏡像倉庫,管理項目和命名空間。

AD/LDAP 支持 : Harbor能夠集成企業內部已有的AD/LDAP,用於鑑權認證管理。

審計管理 : 全部針對鏡像倉庫的操做均可以被記錄追溯,用於審計管理。

國際化 : 已擁有英文、中文、德文、日文和俄文的本地化版本。更多的語言將會添加進來。

RESTful API : RESTful API 提供給管理員對於Harbor更多的操控, 使得與其它管理軟件集成變得更容易。

部署簡單 : 提供在線和離線兩種安裝工具, 也能夠安裝到vSphere平臺(OVA方式)虛擬設備。

2.9.2 harbor部署

[root@docker02 ~]# hostname -I 20.0.0.200 # harbor 託管在GitHub上,頁面搜索" Installation & Configuration Guide "能夠查看安裝步驟。下載 harbor壓縮包,並解壓。 [root@docker02 ~]# mkdir work [root@docker02 ~]# cd work [root@docker02 work]# wget https://storage.googleapis.com/harbor-releases/release-1.8.0/harbor-offline-installer-v1.8.1.tgz
[root@docker02 work]# ls harbor-offline-installer-v1.8.1.tgz [root@docker02 work]# tar xf harbor-offline-installer-v1.8.1.tgz [root@docker02 work]# tar xf harbor-offline-installer-v1.8.1.tgz -C /usr/local/ [root@docker02 work]# cd /usr/local/harbor/ [root@docker02 harbor]# ll 總用量 551208
-rw-r--r-- 1 root root 564403568 6月  17 11:30 harbor.v1.8.1.tar.gz -rw-r--r-- 1 root root      4519 6月  17 11:29 harbor.yml -rwxr-xr-x 1 root root      5088 6月  17 11:29 install.sh -rw-r--r-- 1 root root     11347 6月  17 11:29 LICENSE -rwxr-xr-x 1 root root      1654 6月  17 11:29 prepare #修改hardor的配置文件 hostname: 20.0.0.200 # 填寫局域網或者互聯網能夠訪問得地址,有域名能夠寫域名 harbor_admin_password: 123456 # 管理員的初始密碼,默認用戶名爲admin database: password: root123 # 數據庫密碼。默認是root123 data_volume: /data # 存儲harbor數據的位置 jobservice: max_job_workers: 10 # 啓動幾個併發進程來處理用戶的上傳下載請求。通常略小於CPU核心數。 # 通常會修改的參數也就上面幾項,另外http和https根據本身實際狀況配置進行,這裏就使用默認的http。 [root@docker02 harbor]# ./install.sh [Step 0]: checking installation environment ... Note: docker version: 19.03.1 ✖ Need to install docker-compose(1.18.0+) by yourself first and run this script again. [root@docker02 harbor]# yum info docker-compose | egrep -i 'repo|version'
 * elrepo: hkg.mirror.rackspace.com [root@docker02 harbor]# yum -y install docker-compose # 開始安裝harbor,由於須要解壓使用harbor.v1.8.1.tar.gz中打包好的鏡像,因此須要稍微等一下。 [root@docker02 harbor]# ./install.sh ✔ ----Harbor has been installed and started successfully.---- Now you should be able to visit the admin portal at http://20.0.0.200. 
For more details, please visit https://github.com/goharbor/harbor .
[root@docker02 harbor]# netstat -lntup Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name tcp 0      0 127.0.0.1:1514          0.0.0.0:*               LISTEN      2973/docker-proxy tcp 0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      955/sshd tcp6 0      0 :::80                   :::*                    LISTEN      3724/docker-proxy tcp6 0      0 :::22                   :::*                    LISTEN      955/sshd udp 0      0 127.0.0.1:323           0.0.0.0:*                           709/chronyd udp6 0      0 ::1:323                 :::*                                709/chronyd     

問harbor的web界面,上面執行 ./install.sh 的結尾有提示web登入的方式。用戶名和密碼:admin/123456

 

接下來咱們開始建立私有倉庫。

先建立一個普通的帳戶

切換上面的普通帳戶,新創建一個私有項目

 

推送鏡像到baseimages項目中

[root@docker ~]# cp /etc/docker/daemon.json{,.bak} cp:是否覆蓋"/etc/docker/daemon.json.bak"? y [root@docker ~]# vim /etc/docker/daemon.json [root@docker ~]# diff /etc/docker/daemon.json{,.bak} 3,4c3 <     "bip": "10.0.0.1/16", <     "insecure-registries": ["20.0.0.200"] ---
>     "bip": "10.0.0.1/16" [root@docker ~]# systemctl restart docker Warning: docker.service changed on disk. Run 'systemctl daemon-reload' to reload units. [root@docker ~]# systemctl daemon-reload [root@docker ~]# systemctl restart docker [root@docker ~]# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE busy v1 e0078e53d05d 6 hours ago         1.22MB [root@docker ~]# docker tag busy:v1 20.0.0.200/baseimages/busy:v1 [root@docker ~]# docker login -u zhujingxing 20.0.0.200 Password: WARNING! Your password will be stored unencrypted in /root/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store
 Login Succeeded [root@docker ~]# docker push  20.0.0.200/baseimages/busy:v1 The push refers to repository [20.0.0.200/baseimages/busy] f6d434d1b26b: Pushed 0d315111b484: Pushed v1: digest: sha256:2c7184696987b361dbf2c30cc4410858d1245a31921cec80bf6ca6b73857470b size: 734

刷新harbor頁面

到這私有倉庫也就搭建完成了,也能夠在 /data 目錄下查看數據

[root@docker02 harbor]# ll /data/registry/docker/registry/v2/repositories/baseimages/ 總用量 0 drwxr-xr-x 5 10000 10000 55 8月   3 21:35 busy

若是要對harbor服務作一些操做,須要使用docker-compose命令

# 其實前面的./install.sh也是使用的 docker-compose create 和 docker-compose start 命令啓動的 harbor。注意,命令執行須要再harbor的目錄下,不然會報錯找不到配置文件。 [root@docker02 harbor]# docker-compose --help [root@docker02 ~]# docker-compose pause ERROR: Can't find a suitable configuration file in this directory or any
        parent. Are you in the right directory? Supported filenames: docker-compose.yml, docker-compose.yaml [root@docker02 ~]# cd -
/usr/local/harbor [root@docker2 harbor]# docker-compose pause Pausing harbor-log ... done Pausing redis ... done Pausing harbor-db ... done Pausing registry ... done Pausing registryctl ... done Pausing harbor-core ... done Pausing harbor-portal ... done Pausing harbor-jobservice ... done Pausing nginx ... done #開機自啓動harbor [root@docker02 harbor]# cp /etc/rc.d/rc.local{,.bak} [root@docker02 harbor]# vim /etc/rc.d/rc.local [root@docker02 harbor]# diff /etc/rc.d/rc.local{,.bak} 14,15d13 < #harbor < cd /usr/local/harbor && docker-compose start 

注:

在此章節,我只是圍繞harbor倉庫進行闡述,詳細瞭解請看我提供的連接。

在此章節,我並無闡述harbor倉庫的高可用,安全等問題,這算幾處坑吧,在kubernetes編排工具章,我會進行填補。

2.10 docker資源限制

默認狀況下,容器沒有資源限制,能夠使用主機內核調度程序容許的儘量多的給定資源。

Docker同LXC同樣,其對資源的隔離和管控是以Linux內核的namespaces和cgroup爲基礎。Docker的資源隔離使用了Linux內核 Kernel中的Namespaces功能來實現,隔離的對象包括:主機名與域名、進程編號、網絡設備、文件系統的掛載點等,namespace中的IPC隔離docker並未使用,docker中使用TCP替代IPC。

在使用Namespaces隔離資源的同時,Docker使用了Linux內核Kernel提供的cgroup來對Container使用的CPU、內存、磁盤IO資源進行配額管控。換句話說,在docker的容器啓動參數中,像--cpu*、--memory*和--blkio*的設置,實際上就是設置cgroup的相對應cpu子系統、內存子系統、磁盤IO子系統的配額控制文件,只不過這個修改配額控制文件的過程是docker實例代替咱們作掉罷了。所以,咱們徹底能夠直接修改docker容器所對應的cgroup子系統中的配額控制文件來達到控制docker容器資源配額的一樣目的。

2.10.1 memory

內存風險

不容許容器消耗宿主機太多的內存是很是重要的。在Linux主機上,若是內核檢測到沒有足夠的內存來執行重要的系統功能,它會拋出OOME或Out of Memory異常,並開始終止進程以釋放內存。任何進程都會被殺死,包括 Docker和其餘重要的應用程序。若是殺錯進程,可能致使整個系統癱瘓。

Docker 經過調整 Docker daemon 上的 OOM 優先級來下降這些風險,以便它比系統上的其餘進程更不可能被殺死。容器上的 OOM 優先級未調整,這使得單個容器被殺死的可能性比 Docker daemon 或其餘系統進程被殺死的可能性更大。你不該試圖經過在 daemon 或容器上手動設置--oom-score-adj到極端負數,或經過在容器上設置--oom-kill-disable來繞過這些安全措施。

有關Linux內核的OOM管理的更多信息,查看 Out of Memory Management:https://www.kernel.org/doc/gorman/html/understand/understand016.html

能夠經過如下方式下降 OOME 致使系統不穩定的風險:

在應用程序發佈到生產以前,執行相關測試以便了解應用程序的內存要求;

確保應用程序僅在具備足夠資源的主機上運行;

限制容器能夠使用的內存,以下所述;

在 Docker 主機上配置 Swap 時要當心,Swap 比內存更慢且性能更低,但能夠提供緩衝以防止系統內存耗盡;

考慮將 Container 轉換部署爲 Service,並使用服務級別約束和節點標籤來確保應用程序僅在具備足夠內存的主機上運行。

限制容器內存

下述選項中的大多數採用正整數,後跟b/k/m/g的後綴,表明單位:字節/千字節/兆字節/千兆字節。

 

 有關 cgroup 和內存的更多信息,查看Memory Resource Controllerhttps://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt

關於 --memory-swap

--memory-swap是一個修飾符標誌,只有在設置了--memory時纔有意義。使用swap容許容器在容器耗盡全部可用的RAM時,將多餘的內存需求寫入磁盤。對於常常將內存交換到磁盤的應用程序,性能會受到影響。

它的設置會產生複雜的影響:

  若是--memory-swap設置爲正整數,則必須設置--memory和--memory-swap。--memory-swap表示能夠使用的memory和swap總量, --memory控制no-swap的用量。因此,若是設置--memory="300m"和--memory-swap="1g", 容器能夠使用300m memory和700m (1g - 300m)swap。

  若是--memory-swap設置爲0,該設置被忽略,該值被視爲未設置。

  若是--memory-swap的值等於--memory的值,而且--memory設置爲正整數,則容器無權訪問swap。這是由於--memory-swap是能夠使用組合的 Memory和 Swap,而--memory只是能夠使用的 Memory。

  若是--memory-swap不設置,而且--memory設置了值, 容器能夠使用--memory兩倍的Swap(若是主機容器配置了Swap)。

    示例:設置--memory="300m"而且不設置--memory-swap,容器能夠使用300m memory和600m swap。

  若是--memory-swap設置爲-1,容許容器無限制使用Swap。

在容器內部,像free等工具報告的是主機的可用Swap,而不是容器內可用的。不要依賴於free或相似工具的輸出來肯定是否存在 Swap。

 

關於 --memory-swappiness

  值爲0時,關閉匿名頁交換。

  值爲100時,將全部匿名頁設置爲可交換。

  默認狀況下,若是不設置--memory-swappiness,該值從主機繼承。

關於 --kernel-memory

  內核內存限制是就分配給容器的總內存而言的,考慮一下方案:

    無限內存,無限內核內存:這是默認行爲。

    無限內存,有限內核內存:當全部 cgroup 所需的內存量大於主機上實際存在的內存量時,它是合適的。能夠將內核內存配置爲永遠不會超過主機上可用的內存,而需求更多內存的容器須要等待它。

    有限內存,無限內核內存:總體內存有限,但內核內存不是。

    有限內存,有限內核內存:限制用戶和內核內存對於調試與內存相關的問題很是有用,若是容器使用意外數量的任意類型的內存,則內存不足不會影響其餘容器或主機。在此設置中,若是內核內存限制低於用戶內存限制,則內核內存不足會致使容器遇到 OOM 錯誤。若是內核內存限制高於用戶內存限制,則內核限制不會致使容器遇到OOM。

當你打開任何內核內存限制時,主機會根據每一個進程跟蹤「高水位線」統計信息,所以你能夠跟蹤哪些進程正在使用多餘的內存。經過查看主機上的/proc/<PID>/status,能夠在每一個進程中看到這一點。

2.10.2 cpu

默認狀況下,每一個容器對主機 CPU 週期的訪問權限是不受限制的,你能夠設置各類約束來限制給定容器訪問主機的CPU週期。大多數用戶使用和配置默認 CFS調度程序。在Docker 1.13 及更高版本中,還能夠配置實時調度程序。

配置默認 CFS 調度程序

CFS 是用於普通Linux進程的Linux內核CPU調度程序。經過如下設置,能夠控制容器的CPU資源訪問量,使用這些設置時,Docker會修改主機上容器的 cgroup的設置。

 

示例:若是你有 1 個 CPU,則如下每一個命令都會保證容器每秒最多佔 CPU 的 50%。

Docker 1.13 或更高版本:

[root@docker ~]# docker run -it --cpus=".5" busybox 

配置實時調度程序

在 Docker 1.13 或更高版本,你能夠配置容器使用實時調度程序。在配置 Docker daemon 或配置容器以前,須要確保正確配置主機的內核。

警告:CPU 調度和優先級是高級內核級功能,大多數用戶不須要從默認值更改這些值,錯誤地設置這些值可能會致使主機系統變得不穩定或沒法使用。

配置主機機器的內核

經過運行zcat /proc/config.gz|grep CONFIG_RT_GROUP_SCHED驗證是否在Linux內核中啓用了CONFIG_RT_GROUP_SCHED,或者檢查是否存在文件/sys/fs/cgroup/cpu.rt_runtime_us。有關配置內核實時調度程序的教程,請參閱操做系統的文檔。

配置DOCKER DAEMON

要使用實時調度程序運行容器,請運行Docker daemon,並將--cpu-rt-runtime設置爲每一個運行時間段爲實時任務保留的最大微秒數。例如,默認週期爲 1000000 微秒(1秒),設置--cpu-rt-runtime=950000可確保使用實時調度程序的容器每1000000微秒可運行950000微秒,並保留至少50000微秒用於非實時任務。要在使用systemd的系統上永久保留此配置,請參閱Control and configure Docker with systemd:https://docs.docker.com/config/daemon/systemd/

配置個別容器

使用docker run啓動容器時,能夠傳遞多個參數來控制容器的CPU優先級。有關適當值的信息,請參閱操做系統的文檔或ulimit命令。

示例:

[root@docker ~]# docker run -it --cpu-rt-runtime=95000 --ulimit rtprio=99 --cap-add=sys_nice debian:jessie

注:若是未正確配置內核或 Docker Daemon,則會發生錯誤。

2.10.3 案例演示

[root@docker ~]# lscpu 
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                4
#拖壓測工具
[root@docker ~]# docker pull lorel/docker-stress-ng
#設置選項
給docker傳選項,該進程最多佔用256M內存    --vm 2 則須要512M,內存溢出
[root@docker ~]# docker run --name stress -it --rm  -m 256m lorel/docker-stress-ng stress --vm 2
stress-ng: info: [1] defaulting to a 86400 second run per stressor
stress-ng: info: [1] dispatching hogs: 2 vm
另開一個窗口,查看進程
[root@docker ~]# docker top stress
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                62445               62428               0                   11:25               pts/0               00:00:00            /usr/bin/stress-ng stress --vm 2
root                62479               62445               0                   11:25               pts/0               00:00:00            /usr/bin/stress-ng stress --vm 2
root                62480               62445               0                   11:25               pts/0               00:00:00            /usr/bin/stress-ng stress --vm 2
root                62666               62480               0                   11:25               pts/0               00:00:00            /usr/bin/stress-ng stress --vm 2
root                62676               62479               0                   11:25               pts/0               00:00:00            /usr/bin/stress-ng stress --vm 2
#顯示內存變量信息
[root@docker ~]# docker stats
CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT   MEM %               NET I/O             BLOCK I/O           PIDS
76c2d2f81ee2        stress              218.24%             215MiB / 256MiB     83.97%              0B / 0B             0B / 0B  5

#演示限制CPU核心數  
也能夠不加,默認會把CPU核數全吞下去            
[root@docker ~]# docker run --name stress -it --rm  --cpus 1 lorel/docker-stress-ng:latest stress  --cpu 8 
stress-ng: info: [1] defaulting to a 86400 second run per stressor
stress-ng: info: [1] dispatching hogs: 8 cpu
[root@docker ~]# docker top stress
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                64853               64836               0                   11:29               pts/0               00:00:00            /usr/bin/stress-ng stress --cpu 8
root                64887               64853               13                  11:29               pts/0               00:00:08            /usr/bin/stress-ng stress --cpu 8
root                64888               64853               12                  11:29               pts/0               00:00:07            /usr/bin/stress-ng stress --cpu 8
root                64889               64853               13                  11:29               pts/0               00:00:08            /usr/bin/stress-ng stress --cpu 8
root                64890               64853               11                  11:29               pts/0               00:00:07            /usr/bin/stress-ng stress --cpu 8
root                64891               64853               13                  11:29               pts/0               00:00:08            /usr/bin/stress-ng stress --cpu 8
root                64892               64853               13                  11:29               pts/0               00:00:08            /usr/bin/stress-ng stress --cpu 8
root                64893               64853               11                  11:29               pts/0               00:00:07            /usr/bin/stress-ng stress --cpu 8
root                64894               64853               11                  11:29               pts/0               00:00:07            /usr/bin/stress-ng stress --cpu 8
[root@docker ~]# docker stats

CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT   MEM %               NET I/O             BLOCK I/O           PIDS
390fae70a245        stress              0.10%               28MiB / 1.936GiB    1.41%               0B / 0B             0B / 0B             9

CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT   MEM %               NET I/O             BLOCK I/O           PIDS
390fae70a245        stress              0.10%               28MiB / 1.936GiB    1.41%               0B / 0B             0B / 0B             9
[root@docker ~]# docker stats
CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT   MEM %               NET I/O             BLOCK I/O           PIDS
390fae70a245        stress              108.93%             28MiB / 1.936GiB    1.41%               0B / 0B 

2.11 感言

單單容器是基本不會出如今生產場景的,必然要藉助於編排工具,而編排工具能提供諸如:網絡、資源限制、存儲等功能,也就是說單單對於docker章的學習是集中於docker的基本用法、docker的鏡像技術、dockerfile的書寫。

對於docker倉庫,我並無詳細闡述,甚至是一筆帶過的,緣由我以前已經有講,我期待的是將重點、難點、複雜點都放在Kubernetes章節。

又是一星期,如今是2019年8月4號12點14分。到此爲止docker章初版算是基本完成。這個星期我熬了兩天晚上的夜,倒不是由於寫docker,主要是go語言有點難搞哦。7月份工做穩住,8月份該衝刺一波了。將來可期,應約而至,不忘初心,放得本心。

 

年輕人,一個星期熬兩三次夜,也沒得啥事嘛!

相關文章
相關標籤/搜索