01 . Docker原理部署及常用操作命令

Docker的來源及構造:

容器是一種基礎工具:泛指任何用於容納其他物品的工具,可以部分或完全封閉,被用於容納,儲存,運輸物品: 物品可以被放置在容器中,而容器可以保護內容物:

人類使用容器的歷史有十萬年,甚至可能有數百萬年的歷史:

     #   容器類型:
     #     瓶,罐,箱,籃,桶,袋

但我們重點在LXC這裡;

虛擬化和容器的關係:

主機級虛擬化:虛擬化的是整個完整的物理硬件平台,比如VMware,KVM.裏面跑的是與宿主機不一樣的系統

兩種類型:
類型一: 直接在硬件上裝一個虛擬機管理器,不裝宿主機,在虛擬機管理器上跑需要的操作系統:
類型二: Vmware,kvm
完整意義的操作系統: 我們自己在上面裝一個內核,內核上面有一個用戶空間,用戶空間裏面跑進程。

  • 但是運行內核並不是我們主要目的,內核核心作用在於資源分配和管理。

  • 就是說真正在應用空間跑的應用程序才是能產生生產力的,就好比我們跑一個web服務,內核其實很少提供服務。

  • 而出於通用目的的而設計的資源管理平台,操作系統管理內核。我們並不太需要:

  • 但是內核也不得不有,原因在於要想在一組硬件平台跑軟件程序,而現在的軟件都是針對於內核調用和系統調用,庫調用研發的,而不安裝這些沒法運行程序,所以內核看上去沒多大用,但是也必不可少;

  • 更何況,我們一旦有了用戶空間後,裏面運行很多進程,這些進程為了協調也需要內核。

  • 但假設我們創建一個虛擬機,只為了運行一個nginx,tomcat,卻不得不去安裝內核,用戶空間,再去跑tomcat,代價就有點大了。

  • 而在這裡如果我們用二級虛擬化來分析的話:

  • 如果為了運行一個進程,就要實現兩級調度和資源分派。

  • 第一,自己虛擬機有一個內核,已經實現了一次內存虛擬化,cpu調度以及IO調度和io 管理。

  • 但是真正虛擬機也是被宿主機內核管理一次,這個中間浪費可以想一下。

  • 傳統的主機虛擬化技術,確實能讓我們一組硬件平台之上實現跨系統傳進的隔離和試驗,調試,資源高效利用。而帶來的資源開銷不容忽視的,而很多時候,我們創建虛擬機只是為了運行一個和幾個附有生產責任的進程而已,為此;

  • 減少中間層就是一個好的辦法,

  • 比如在二級虛擬化我們把虛擬機那一層給抽掉,只保留進程,但是我們加虛擬機就是為了環境隔離,比如說一台機器跑兩個或者五個nginx,我們一台機器沒有那麼多套接字和80端口。

  • 而我們虛擬機就是為了資源隔離,就算把裏面的nginx翻江倒海,壞的也是哪一個虛擬機。

  • 所以這裡的隔離才是我們的目標,我們雖然想抽掉這一層內核,但是不能讓他回到在一個鍋里吃飯那個環境,所以要讓每一個組進程互相不可干擾的,只不過共享一個底層資源而已。

  • 因此我們想要的環境就是:

  • 創建一個又一個隔離環境,我們要運行隔離的程序就讓他跑在隔離環境中,內核提供的是內核空間,進程試運行在用戶空間,而這裡就能實現將用戶空間隔離成多個用戶空間,互不干擾,這裡一般都有一個空間是有特權的,一般第一個。而這裡眾多用戶空間運行的都是同一個內核,被同一個內核管理的。但是運行時候能看到的邊界只能是當前用戶空間的邊界,雖然沒有主機級虛擬化隔離那麼徹底。

  • 更重要是,這個用戶空間放進程,給進程提供運行環境,並且保護內部進程不被其他進程所干擾.叫做容器.

  • 虛擬的隔離環境管理器

  • 一層硬件平台

  • 容器不是什麼新概念,最早出現在2000年,當年叫jail,監獄,監禁之意,裏面就像一個沙箱,就算裏面那個程序bug,有了故障,異常行為,也無法影響這個容器之外的程序運行,這也是jail初衷。

  • 容器並非起源於 Linux,但開源世界的最精彩之處就在於借鑒、修改和改進,容器也不例外.

  • 但這個概念非常有吸引力。

  • 2001 年,通過 Jacques Gélinas 的 [VServer 項目,]隔離環境的實施進入了 Linux 領域。正如 Gélinas 所說,這項工作的目的是「在高度獨立且安全的單一環境中運行多個通用 Linux 服務器 [sic]。」 在完成了這項針對 Linux 中多個受控制用戶空間的基礎性工作後,Linux 容器開始逐漸成形並最終發展成了現在的模樣。

  • 一般來講,就算一個程序被遠程劫持在裏面搞起破壞,他的邊界也僅僅是哪個容器的邊界,最初只是為了應用的安全運行。

  • 後來有人把這個技術山寨到Linux平台上,叫Vserver(chroot),在一個子目錄定義一個根,讓裏面的程序以為他就是根。

  • chroot他能實現的你看上去的那層空間,底層還是同一個內核,進程運行是特權模式還是普通模式,表面很簡單,真正實現起來涉及東西至少要有;

  • 在一個單獨用戶空間當中,它主要目的是隔離使用,要讓裏面進程以為他就運行在底層內核之上,有六點:

  1. 主機名和域名: UTS
  2. 根文件系統 : MOUNT
  3. 應該有自己的IPC,進程間的專用通道,如果能讓兩個用戶空間進程能互相通信,也沒有意義了,共享內存,拒絕跨用戶空間。 IPC
  4. 給每個用戶空間做一個假的init,只能有一個,pid,1,每個系統正常來說只有一個,為了隔離,給每個空間偽裝一個。
  5. 必須給每個用戶偽裝一個root,實現效果就是在真正系統只是普通用戶,在他所屬用戶空間裏面可以為所欲為,讓進程看起來是root.
  6. IP地址,互相通信,而在內核級別,tcp協議棧只有一個,
  • 為了容器機制的實現,Linux內核對着六種明成空間原生支持,namespaces。直接通過系統調用對外進行輸出,

  • 直到今天,所謂的容器就是靠六個namespaces和chroot來實現的。

  • 聽上去容易,這六種名成空間有一個是到內核3.8才實現的,所以要想完美使用容器,要內核版本3.8以上。centos6天然排除在外.

namespace 系統調用參數 隔離內容 內核版本
UTS clone_newuts 主機名和域名 2.6.19
IPC clone_newipc 信號量、消息隊列和共享內存 2.6.19
PID clone_newpid 進程編號 2.6.24
Network clon_newnet 網絡設備、網絡棧、端口等 2..29
Mount clone_newns 掛載點(文件系統) 2.4.19
User clone_newuser 用戶和用戶組 3.8

容器級虛擬化:

  • 資源限制: 我們可以在整體資源做比例性分配,也可以在單一用戶空間做核心綁定,你只能使用幾個核;不然的話一個進程內存泄露整個系統都蹦了;或者一個進程佔用了絕大部分CPU.

  • 內存是壓縮不了的,多一點都不能,一給就收不回來了,因為內存是壓縮不了的;

  • 實現這個Control Cgroups

    blkio:  		# 塊設備IO
    cpu: 			# CPU
    cpuacct:  		# CPU資源使用報告
    cpuset:   		# 多處理器平台上的cpu集合
    devices:  		# 設備訪問
    freezer:   		# 掛起或恢復任務
    memory:  		# 內存用量及報告
    perf_event: 		# 對cgroup中的任務進行統一性能測試
    net_cls:  		# cgroup中的任務創建的數據報文的類別;        
    
  • 劃分成組後,在不同組內進行資源分配,組還可以再分。

  • 即使有了chroot,nameespaces, control cgroups,容器的隔離能力比起主機級別虛擬化依然差很多,因為容器畢竟屬於同一個內核,而主機級虛擬化那麼好,因為內核本來就是天然的隔離,因此為了加強安全性,防止裏面的進程通過漏洞繞過邊界伸到別的用戶空間,通過selinux各種安全機制加強容器的邊界,為了支撐容器做的更加完善。

  • 容器實現就是靠chroot,nameespaces,cgroups核心技術實現,而這些在內核已經實現了。

  • 容器本來是靠jail啟發有了vserver,後來為了讓這種容器更加易用,

  • 因為創建容器要自己寫代碼,系統調用,克隆等來實現的,但麻煩程度很大。

  • 所以我們最好把實現容器的這種功能做成一種工具,極大的簡化容器的使用,於是就有了LXC的解決方案,

  • LXCx container,最早一批真正把完整的容器技術用一組簡易使用的工具和摸板來極大的簡化了容器的使用方案。

  • lxc-create : 創建一個容器,template,摸板一組腳本,

  • 創建一個明成空間後,這腳本自動執行後,自動實現安裝過程,指向了你所打算創建哪一類明成空間的系統發行版所屬的倉庫,從倉庫中鏡像下載過來,安裝,生成這個新的名成空間,就可以像虛擬機一樣使用。

  • 所有的明成空間都這樣實現,而lxc就靠這樣這一組工具包快速實現創建空間,利用模板完成內部所需要各種文件的安裝,同時還有些工具可以通過chroot切換來切換去,於是我們就可以愉快的使用了,跟虛擬機沒多大區別;

  • lxc在容器技術推廣絕對功不可沒,但依然有很高門檻:

  1. 學好lxc命令
  2. 必要時候需要定製模板,
  3. 如果在裏面運行Mysql,如果服務在裏面出現故障,如何遷移數據;
  4. 批量創建也不容易
  • 雖然極大的簡化了容器使用,但是在複雜程度沒有多大降低的,更何況他的隔離性沒有虛擬機那麼好,

  • 好處是性能資源方面的節約,讓每一個進程直接使用宿主機的性能。

  • 於是後來出現了docker:

  • lxc增強版,也不是容器,容器的一個易用前端工具。

  • lxc批量創建挺難,docker就在這個上面找突破點,docker早期核心就是lxc,他是lxc的二次封裝發行版。

  • 功能上利用lxc做容器管理引擎,創建容器時不再使用模板安裝,而是使用一種鏡像技術,

  • 我們嘗試着把一個操作系統用戶空間所需要用到的所需要所有組件事先準備編排好,編排好後整體打包成一個文件,叫做鏡像文件,這個鏡像文件是放在一個集中統一的倉庫中的,我把大家都會用到的最小化centos,ubuntu分別放在倉庫內;

  • 而在這裡我們在這個最小化系統裏面先裝好源碼nginx,再打包成一個鏡像,也放入這個倉庫中,當啟動創建容器時候,需用鏡像,把鏡像拖到本地,基於鏡像啟動容器。所以docker極大的簡化了容器使用程度;

  • 比如說你想跑一個tomcat,nginx直接docker run就完成了

  • 為了易於管理,docker還採用另外一種方式,在一個用戶空間當中,我們嘗試只運行一組進程或一個進程,我們目的就是為了運行一個有生產力的程序,比如我們在主機上要跑tomcat,nginx,nginx運行在nginx容器中,tomcat運行在tomcat容器中,二者用容器間通信邏輯進行通信,所以以後一個容器只運行一個進程,這也是docker的目的;

  • lxc就是當虛擬機使用,到時候管理極為不便;

  • 而docker這裡實現功能:

  • 不用容器,一個內核之上運行各種進程 ,大家屬於同一個用戶空間,共享同一組主機名,用戶,ipc,pid,在同一個可視範圍內,如果一個黑客劫持一個進程以他做跳板可以威脅到其他進程

  • 而docker,把他們給圈起來了,彼此間不可見,而且這個容器只為了這一個進程,最小化定義的;
    壞處,佔用更多空間,如果服務壞了,調試工具只針對一個容器有效,而如果加上調試工具違反了唯一進程目的,

  • 所以帶來問題:本來調試一個進程極為簡單,可能沒有工具:

  • 而要想調試得突破他的邊界:

  • 好處;刪除了不影響別人:

  • 給運維帶來極大不便利,給開發帶來巨大好處,分發容易了,一次編寫到處運行。現在環境都是異構的,

centos5,6,7/Ubuntu/deepin/suse/kali/redhat/AIX

  • 要開發一個軟件,要開發每一種平台的軟件,各自組織一個團隊開發,只需要打包一個鏡像,不管你是windows,linux,unix跟內核沒關係,跟操作系統沒關係,他有自己的明成空間
  • 跟java類似這種效果,但是java有很多版本,6,7,8
  • 以前部署需要發佈,變更,故障處理
  • 有了鏡像,直接one,就行了,但是還需要接路由器和調度器,如果有一個容器編排工具,之間run結束,甚至連run都不需要你手動執行;
  • 像以前的java容器只能支持java一種語言;
  • 而docker不會管你是什麼語言;
  • 隨之帶來運維的問題,發佈操作用編排工具來實現,docker必須要使用編排工具來管理,不用的話手動管理比我們直接管理應用程序更麻煩,增加我們運維環境複雜度;但確實是降低開發壓力;
  • 運維工作就是維穩,以往調試很容易,而容器可能沒有調試工具,這麼一來就意味着,我以後做鏡像需要為每一個鏡像自帶調試工具,這麼一來意味着做鏡像需要自帶一些工具,以前能共享的,現在不能,但他們卻是隔離的;
  • docker還有一個好處: 批量創建,他創建容器採用
  • 分層構建,聯合掛載;使得我們以後鏡像分發沒有那麼龐大,比如說我在一個系統上運行三個容器,都是基於底層centos構建,在這裡只用一個centos基礎鏡像,三個上層nginx,tomcat,mariadb,底層鏡像是共享的,底層鏡像是只讀的,要想改,在每一層聯合掛載鏡像棧最頂層額外附加一個新層,這個層才是能讀能寫的;
  • 遷移很困難,如果要把容器遷移到其他地方,但這裡是有狀態的,真正在使用容器時,不會在本地存儲有效數據,他會在文件系統上共享存儲,而用戶存儲數據,這個服務不小心宕掉了,再找一個主機重新數據加載就行了,再訪問數據還在,所以數據脫離宿主機,脫離容器而持久,容器可以丟到任何主機上;
  • 存儲需要一個外置持久性存儲,程序是由指令+數據組成,
  • 把容器當進程使用,啟動一個容器,就是為了運行一個進程,進程一終止,把容器刪了,不要了,下次重新創建,重新掛載
  • 容器不需要持久,容器和進程一樣有生命周期,從創建而開始,從停止到結束,跟我們主機都沒多大關係,可以運行在任何主機上;
  • 在docker之上建設一個層次,看那個主機更閑,來個調度法則,如果需要持久數據,給個web存儲空間,掛載上去存儲數據,一旦任務結束直接容器一刪,結束,這個組件能幫我們把要啟動容器調度於整個底層集群環境中某一個docker主機之上,當要構建lnmp,誰先啟動,docker就解決不來這種功能,我們需要一個docker基礎之上,能夠把這種應用程序依賴關係,從屬關係,隸屬關係反映在啟動關閉時的次序和管理邏輯中,這種功能叫容器編排工具;
# 在docker之後出現了各種編排工具,
machine+swarm+compose:    # compose只能單機編排,
mesos:    		  # 統一資源調度和分配的     + 實現編排需要加上  marathon
kubernetes  -- >  k8s
  • google使用容器有十幾年歷史了,據說每一周銷毀和新建容器多達幾十億。
  • docker因緣巧合摸到了這個門道,並且做成開源軟件,谷歌就坐不住了,自己本來做為獨門武器,那小子居然找到一種辦法還公開所有人使用,本來我最有話語權,秘而不宣藏起來,這樣才能稱為獨門武器,必要時候必殺技;
  • 但是docker已然獨霸話語權了,好在docker也不是鐵板一塊,後來出來出來另一個公司,谷歌就在後面大力扶植反對派,後來發現難以跟docker抗衡,上不來檯面;
  • 容器編排工具在谷歌已經跑了十幾年了,該踩的坑都踩的差不多了;
  • docker三兩年做不到;
  • 於是kubemetes橫空出世,佔據了百分八十的市場,成為市場的標準,還成立了CNCF 容器標準組織,docker有一個問題,docker在編排上沒有經驗,義無建樹,技術沒走好,沒有吸引更多的土豪進來投資,上市做獨角獸,雖然互聯網上火的不要不要的,但是無法變現,決定,把開源版拆分為企業版和社區版,將社區改名,把docker流量引入企業版,後來改名叫moby,把所有docker引入企業版,
  • 之所以這樣 docker是因為一家商業公司,谷歌在做k8s時候向大家表明我是沒有任何意圖的,於是把k8s源代碼捐給了cncf,cncf是由很多組織聯合成立的,主導權不再屬於谷歌,屬於IMB,微軟等等,不會被人說想把k8s私有化,一年四個版本發佈,go研發的
  • 後來docker研發了一個容器引擎,libcontainer,替換了lxc,docker已經被cncf挾持了,cncf自己玩,把docker排除在外,如果以後容器要走下去肯定要開源,標準化,誰來負責標準化,cncf就可以做,定義標準,但這樣太欺負docker了,所以給docker個機會,你來定標準化,同時做一款開源軟件。
  • 現在新的docker引擎已經是runC了,
  • 雖然k8s支持很多種容器,但常見的還是docker+k8s;

鏡像管理基礎

docker架構形式:只考慮單機之上,整體架構是這樣的:

整體是一個dockerhosts: docker server端

dockerhost 就是運行有docker進程(守護進程)的主機,被稱為docerhost,dockerserver;

docker接收到創建和啟動容器命令後,將在本地創建容器,一個dockerhost上可以啟動多個容器,此處我們可能運行的分別不屬於各種不同程序的容器,而容器取決於鏡像來實現,如果本地沒有,dockerdaemon自動鏈接到registries上,而後從中獲得鏡像後,先存儲到本地一個能夠專門存儲所謂鏡像存儲空間中,要求得是特殊並且特殊的文件系統,overlay2,

1

這裏面可能有多個鏡像文件,鏡像本身是只讀的,而且鏡像在registries放的時候倉庫名就是應用程序名,而後倉庫內可以放多個鏡像,而且這些鏡像通常屬於同一個應用程序不同版本,我們用標籤來識別;

docker鏡像:

含有啟動容器所需要的文件系統及其內容,因此,其用於創建並啟動docker容器:

採用分層構建機制,大體分為兩層,最底層為bootfs,其之為rootfs

真正讓用戶拿來去構建用戶空間並運行進程容器的是rootfs;

bootfs: 用於系統引導的文件系統,包括bootloader和kernel ,容器啟動完成後會被卸載以節約內存資源

這裡的kernel僅僅用於引導並啟動一個用戶空間,啟動完之後就沒有了以節約內存資源,畢竟很有可能我們用戶空間跟底層內核還是有一點不同之處的,向上就是rootfs了;

2

# rootfs: 位於bootfs之上,表現為docker容器的根文件系統;
# rootfs:  位於bootfs之上,表現為docker容器的根文件系統

傳統模式中,系統啟動時,內核掛載bootfs時會首先將其掛載為只讀模式,完整性自檢完成後將其重新掛載為讀寫模式;

docker中,rootfs由內核掛載為「只讀」模式,而後通過「聯合掛載技術」額外掛載一個「可寫層」;

這裡的分層構建

我們做一個apache鏡像,運行httpd鏡像,我們很有可能在一個底層的非常基礎系統鏡像之上一個純凈最小化centos版本,在他之上添加一個編輯器,相當於vim,除此之外添加一個httpd,每添加一個軟件都是一個獨立的層次,這裡是三層,底下bootfs那一層在容器啟動時,一旦被引導了完了rootfs時候再卸載並移除,不是刪除文件,而是從內存中移除;

而這時候底層只有三層,base image 用來構建一個系統基本組成,/bin;如果要用到額外一個工具,需要額外安裝;

但是對於我們鏡像來講,底層鏡像是不會動的,額外安裝一個vim 他會在裏面額外生成一個vim 層,再裝個nginx,生成個nginx層;疊加在一起掛載的,這三層都是只讀的,因此,對於一個容器來講,僅能在writoble上能寫,而且如果刪除容器,writable也會被刪除;

含有啟動容器所需要的文件系統及其內容,因此,其用於創建並啟動docker容器:

採用分層構建機制,大體分為兩層,最底層為bootfs,其之為rootfs

真正讓用戶拿來去構建用戶空間並運行進程容器的是rootfs;

bootfs: 用於系統引導的文件系統,包括bootloader和kernel ,容器啟動完成後會被卸載以節約內存資源

這裡的kernel僅僅用於引導並啟動一個用戶空間,啟動完之後就沒有了以節約內存資源,畢竟很有可能我們用戶空間跟底層內核還是有一點不同之處的,向上就是rootfs了;

鏡像分層構建和聯合掛載依賴於文件系統的支撐

早起用到的是Aufs,高級多層統一文件系統:

最早被docker拿來用於實現聯合掛載的Linux文件系統,

aufs是之前的unionfs重新實現,重寫後依然很爛,三萬行代碼,一個ext4才四五千代碼,這是要被整合進內核的,因此被申請時,次次被拒絕,一直到不申請,aufs一直都不是內核中自有的文件系統,想用需要向內核打補丁,centos不會幹這種事情,因為他們以保守穩定為初衷,ubuntu是很早一批把aufs打包進內核,早些時候想要使用docker需使用ubuntu.

aufs的競爭產品是overlayfs(),後者自從3.18版本才開始被合併到linux內核;

docker的分層鏡像,除了aufs,docker還支持btrfs,devicemapper和vsf等;

docker默認是aufs; centos7用的是devicemapper;在試用聯合掛載很差,不穩定,因為它使用target driver;

比較成熟的支持的文件系統必須要能夠是docker info當中的overlay2,xfs,overlay2是一種抽象的二級文件系統,他需要建立在本地文件系統之上;

構建鏡像時,鏡像做好之後,應該有一個統一存儲的位置,叫做doceker registry

啟動容器時,docker daemon會試圖從本地獲取相關的鏡像: 本地鏡像不存在時,將其從registry中下載該鏡像並保存到本地;

如果我們沒有特別指定,那麼他就是registry,如果要指向別的registry我們必須在鏡像的訪問路徑當中給明服務器地址;否則訪問默認的registry,除非我們修改默認;
3

Docker Registry分類
# Registry用於保存docker鏡像,包括鏡像的層次結構和元數據
# 用戶可自建Registry,也可以使用官方的docker  hub
分類:
# Sponsor Registry   第三方的registry,供客戶和docker社區使用           
# Mirror Registry        第三方的registry,只讓客戶使用            
# Vendor  Registry      由發佈docker 鏡像的供應商提供的registry提供給買了紅帽系統使用
# Private Registry      #  通過設有防火牆和額外的安全層的私有實體提供的registry 不消耗互聯網帶寬,尤其是本地大規模容器自建本地registry;

OCI

由linux基金會於2015年6月創立
旨在圍繞容器格式和運行時制定一個開放的工業化標準
RunC: 無論是客戶端還是服務端,都由docker一個程序提供,他有很多子程序,他可以監聽在一個套件字之上;
docker有三種類型套接字,
docker啟動容器就是基於鏡像啟動,在鏡像基礎之上,為一個容器創建一個專用可寫層;

containers:容器,

lmages: 鏡像  鏡像來自於Registry,註冊表,可以稱為docker的鏡像倉庫,默認就是docker hub,默認本地是沒有的,鏡像是分層構建的,所以下載到本地後,可以共享多個上層鏡像使用,因為鏡像是只讀的,所以啟動容器就是基於鏡像來啟動,在鏡像基礎上為一個容器創建一個專用的可寫層,從而來啟動這個容器。

所以這裡鏡像也需要在docker本地存儲,因此這有專門的倉庫來放鏡像,而鏡像擁有幾十萬之多,所以放到一個公共的倉庫,需要時候拉取過來加載到本地,這裡的協議是http/https,默認是加密的,需要明確定義成不安全才可以使用;

docker的運行過程中尤其是創建容器時可能有一點慢,原因是他要下載一次鏡像,取決於他的寬帶;

因為服務器在國外, 為了能使加速訪問,docker在大陸這邊做了一個docker鏡像服務器,docker.cn,加速不太好,可以使用阿里,科大,所以要想使用docker,必須要能接入到互聯網。

docker鏡像是分層構建的

倉庫:  一個docker擁有兩重功能,第一,他提供鏡像提供的倉庫,第二,他還提供用戶來獲取鏡像時的認證等功能,還提供了當前服務器所有可用鏡像的索引;

所以鏡像也會有應用到不同程序版本的鏡像,為了讓鏡像跟應用程序版本有一定的關聯,給鏡像外面加了一個標籤,倉庫名+標籤才能唯一標識一個鏡像。如果只給了倉庫名,那就是默認最新版;一個鏡像可以有多個標籤,在倉庫名+標籤外面加上 stable最新版,穩定版什麼的;

鏡像是靜態的;

#       容器: 動態,生命周期,類似於程序;
#任何images,networks,volumes,plugins可以支持增刪改查的,因為他們都是對象;
#       依賴的基礎環境:
#            64 bits CPU
#            Linux KERNEL  3.10+  CentOS6也支持docker,2.6.32,打了補丁;

如果我們要是 就使用docker,如果我們要使用在這的倉庫就下載docker,區別EE和CE;

Docker 從 1.13 版本之後採用時間線的方式作為版本號,分為社區版 CE 和企業版 EE,社區版是免費提供給個人開發者和小型團體使用的,企業版會提供額外的收費服務,比如經過官方測試認證過的基礎設施、容器、插件等。
社區版按照 stable 和 edge 兩種方式發佈,每個季度更新 stable 版本,如 17.06,17.09;每個月份更新 edge 版本,如17.09,17.10。

Docker的部署

初始化環境
init_security() {
systemctl stop firewalld
systemctl disable firewalld &>/dev/null
setenforce 0
sed -i '/^SELINUX=/ s/enforcing/disabled/'  /etc/selinux/config
sed -i '/^GSSAPIAu/ s/yes/no/' /etc/ssh/sshd_config
sed -i '/^#UseDNS/ {s/^#//;s/yes/no/}' /etc/ssh/sshd_config
systemctl enable sshd crond &> /dev/null
rpm -e postfix --nodeps
echo -e "\033[32m [安全配置] ==> OK \033[0m"
}
init_security

init_yumsource() {
if [ ! -d /etc/yum.repos.d/backup ];then
    mkdir /etc/yum.repos.d/backup
fi
mv /etc/yum.repos.d/* /etc/yum.repos.d/backup 2>/dev/null
if ! ping -c2 www.baidu.com &>/dev/null    
then
    echo "您無法上外網,不能配置yum源"
    exit    
fi
    curl -o /etc/yum.repos.d/163.repo //mirrors.163.com/.help/CentOS7-Base-163.repo &>/dev/null
    curl -o /etc/yum.repos.d/epel.repo //mirrors.aliyun.com/repo/epel-7.repo &>/dev/null
    yum clean all
    timedatectl set-timezone Asia/Shanghai
    echo "nameserver 114.114.114.114" > /etc/resolv.conf
    echo "nameserver 8.8.8.8" >> /etc/resolv.conf
    chattr +i /etc/resolv.conf
    echo -e "\033[32m [YUM Source] ==> OK \033[0m"
}
init_yumsource
安裝必要系統工具和軟件源
# 安裝一些必要的系統工具
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
# 添加軟件源信息
# docker 官方源
sudo yum-config-manager --add-repo //download.docker.com/linux/centos/docker-ce.repo

# 阿里雲源
sudo yum-config-manager --add-repo //mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
安裝並啟動Docker-ce
# 安裝前可以先更新 yum 緩存:
sudo yum makecache fast

# CentOS7安裝 Docker-ce
yum -y install docker-ce        # CentOS 中安裝
apt-get install docker-ce       # Ubuntu 中安裝
pacman -S docker                # Arch 中安裝
emerge --ask docker             # Gentoo 中安裝

# 如果想安裝特定版本的Docker-ce版本,先列出repo中可用版本,然後選擇安裝
yum list docker-ce --showduplicates |sort -r
Loading mirror speeds from cached hostfile
Loaded plugins: fastestmirror
Installed Packages
docker-ce.x86_64            3:19.03.4-3.el7                    docker-ce-stable
docker-ce.x86_64            3:19.03.4-3.el7                    @docker-ce-stable
docker-ce.x86_64            3:19.03.3-3.el7                    docker-ce-stable
docker-ce.x86_64            3:19.03.2-3.el7                    docker-ce-stable
docker-ce.x86_64            3:19.03.1-3.el7                    docker-ce-stable

yum install docker-ce-<VERSION STRING>
# 選擇安裝 docker-ce-18.06.1.ce
yum install docker-ce-18.06.1.ce

# Docker鏡像加速
# 沒有啟動/etc/docker目錄不存在,需要自己創建,docker啟動也會自己創建
# 為了期望我們的鏡像下載快一點,應該定義一個鏡像加速器,加速器在國內
mkdir /etc/docker
vim /etc/docker/daemon.json
{
"registry-mirrors": ["//registry.docker-cn.com"]
}

# 啟動Docker後台服務
systemctl start docker && systemctl enable docker
systemctl daemon-reload                 # 守護進程重啟

# 通過運行hello-world鏡像,驗證是否正確安裝了docker,或者通過查看版本
docker run hello-world
docker version
Client: Docker Engine - Community
Version:           19.03.4
API version:       1.40
Go version:        go1.12.10
Git commit:        9013bf583a
Built:            Fri Oct 18 15:52:22 2019
OS/Arch:           linux/amd64
Experimental:      false

CentOS/RHEL的用戶需要注意的事項

在 Ubuntu/Debian 上有 UnionFS 可以使用,如 aufs 或者 overlay2 ,而 CentOS 和 RHEL 的內核中沒有相關驅動。因此對於這類系統,一般使用 devicemapper 驅動利用 LVM 的一些 機制來模擬分層存儲。這樣的做法除了性能比較差外,穩定性一般也不好,而且配置相對復 雜。Docker 安裝在 CentOS/RHEL 上後,會默認選擇 devicemapper ,但是為了簡化配置, 其 devicemapper 是跑在一個稀疏文件模擬的塊設備上,也被稱為 loop-lvm 。這樣的選擇是 因為不需要額外配置就可以運行 Docker,這是自動配置唯一能做到的事情。但是 loop-lvm 的做法非常不好,其穩定性、性能更差,無論是日誌還是 docker info 中都會看到警告信 息。官方文檔有明確的文章講解了如何配置塊設備給 devicemapper 驅動做存儲層的做法,這 類做法也被稱為配置 direct-lvm 。

除了前面說到的問題外, devicemapper + loop-lvm 還有一個缺陷,因為它是稀疏文件,所 以它會不斷增長。用戶在使用過程中會注意到 /var/lib/docker/devicemapper/devicemapper/data 不斷增長,而且無法控制。很多人會希望刪 除鏡像或者可以解決這個問題,結果發現效果並不明顯。原因就是這個稀疏文件的空間釋放 後基本不進行垃圾回收的問題。因此往往會出現即使刪除了文件內容,空間卻無法回收,隨 着使用這個稀疏文件一直在不斷增長。 所以對於 CentOS/RHEL 的用戶來說,在沒有辦法使用 UnionFS 的情況下,一定要配置 direct-lvm 給 devicemapper ,無論是為了性能、穩定性還是空間利用率。 或許有人注意到了 CentOS 7 中存在被 backports 回來的 overlay 驅動,不過 CentOS 里的 這個驅動達不到生產環境使用的穩定程度,所以不推薦使用。

標準化開發測試和生產環境

對於大部分企業來說,搭建PaaS既沒有那個精力,也沒那個必要,用Docker做個人的sandbox用處又小了點。可以用Docker來標準化開發、測試、生產環境。

Docker佔用資源小,在一台E5128G內存的服務器上部署100個容器都綽綽有餘,可以單獨抽一個容器或者直接在宿主物理主機上部署samba,利用samba的home分享方案將每個用戶的home目錄映射到開發中心和測試部門的Windows機器上。

針對某個項目組,由架構師搭建好一個標準的容器環境供項目組和測試部門使用,每個開發工程師可以擁有自己單獨的容器,通過 docker run -v 將用戶的home 目錄映射到容器中。需要提交測試時,只需要將代碼移交給測試部門,然後分配一個容器使用 -v加載測試部門的 home目錄啟動即可。這樣,在公司內部的開發、測試基本就統一了,不會出現開發部門提交的代碼,測試部門部署不了的問題。

測試部門發佈測試通過的報告後,架構師再次檢測容器環境,就可以直接交由部署工程師將代碼和容器分別部署到生產環境中了。這種方式的部署橫向性能的擴展性也極好。

Docker鏡像使用

管理命令

Docker  --help
  container   管理容器
  image       管理鏡像
  network     管理網絡
命令:
  attach      介入到一個正在運行的容器
  build       根據 Dockerfile 構建一個鏡像
  commit      根據容器的更改創建一個新的鏡像
  cp          在本地文件系統與容器中複製 文件/文件夾
  create      創建一個新容器
  exec        在容器中執行一條命令
  images      列出鏡像
  kill        殺死一個或多個正在運行的容器    
  logs        取得容器的日誌
  pause       暫停一個或多個容器的所有進程
  ps          列出所有容器
  pull        拉取一個鏡像或倉庫到 registry
  push        推送一個鏡像或倉庫到 registry
  rename      重命名一個容器
  restart     重新啟動一個或多個容器
  rm          刪除一個或多個容器
  rmi         刪除一個或多個鏡像
  run         在一個新的容器中執行一條命令
  search      在 Docker Hub 中搜索鏡像
  start       啟動一個或多個已經停止運行的容器
  stats       顯示一個容器的實時資源佔用
  stop        停止一個或多個正在運行的容器
  tag         為鏡像創建一個新的標籤
  top         顯示一個容器內的所有進程
  unpause     恢復一個或多個容器內所有被暫停的進程

Docker服務管理

service docker start       # 啟動 docker 服務,守護進程
service docker stop        # 停止 docker 服務
service docker status      # 查看 docker 服務狀態

chkconfig docker on        # 設置為開機啟動
systemctl stop docker
systemctl start docker 
systemctl enable docker
systemctl daemon-reload    # 守護進程重啟

鏡像管理

獲取鏡像
# 鏡像可以看做我們平時裝系統的鏡像,裏面就是一個運行環境
# Docker Hub上有大量的高質量鏡像可以用,這裡就說一下怎麼獲取這些鏡像,
# 從鏡像倉庫獲取鏡像的命令是docker pull,其命令格式為:
docker pull [選項]  [Docker Registry 地址[:端口號]/]倉庫名[:標籤]

docker search centos                            # 搜索docker官方提供的centos鏡像
docker search centos --filter=stars=100         # 查找stars數至少為100的鏡像
docker pull centos                              # 默認從官網拉取
docker pull centos:7.7.1908                     # 默認拉取centos8,需要指定版本才能下載7.
docker pull daocloud.io/library/centos          # 從daocloud拉取
docker dao pull centos                          # 從daocloud拉取,國內倉庫

注意

我們使用docker image ls時候會發現,鏡像體積的所佔用空間在Docker Hub上看到的鏡像大小不同,比如nginx鏡像在docker hub官網上是50多兆,而把他pull下來就變成一百多兆了,這是因為docker hub所顯示大小是網絡傳輸中更關心的流量大小,而docker image ls顯示的是鏡像下載到本地展開後的各層所佔空間的綜合,因為鏡像下載到本地後,更關心的是磁盤空間佔用的大小.

另一個需要注意問題是,docker image ls列表中的鏡像體積綜合併非是所有鏡像實際硬盤消耗,由於Docker鏡像是多層存儲結構,並且可以繼承、復用,因此不同鏡像可能因為使用相同的基礎鏡像,從而擁有共同的層,由於Docker使用UnionFS,相同的層只需要保存一份即可,因此實際佔用硬盤空間很可能比這個列表鏡像大小的總和小的多.

查看鏡像
docker system df				# 查看鏡像、容器、數據卷所佔用的空間.
docker images                                   # 查看已下載的鏡像
docker rm image_id                              # 刪除鏡像,指定鏡像id
docker images                                   # 查看已下載的鏡像
docker images -q                                # 只查看所有鏡像的id
docker inspect imageID                          # 查看鏡像詳情
刪除鏡像
docker rm image_id                              # 刪除鏡像,指定鏡像id
docker rmi RepositoryName --force               
# 刪除鏡像,指定鏡像名,<倉庫名>:<標籤> --force鏡像在使用中強制刪除
# 如果鏡像正在被未運行的容器使用,則需要強制刪除,但是如果正在被運行的容器使用,則強制刪除也無法刪除

docker image ls -a			
# 這樣會看到很多無標籤的鏡像,這些無標籤鏡像很多都是中間層鏡像,
# 是其他鏡像所需要依賴的鏡像,這些無標籤鏡像不應該刪除,否則會導致上層鏡像因為缺失依賴而出錯,
# 實際上也沒必要刪除,因為相同的層只會存一遍,而這些鏡像是別的鏡像的依賴,
# 因此並不會因為他們被列出來而多存了一份,無論如何你也會需要他們,
# 刪除那些依賴他們的鏡像,這些中間層鏡像也會被連帶刪除.

# 刪除所有倉庫名為redis的鏡像:
docker image rm $(docker image ls -q redis)

# 刪除所有鏡像
# none 默認為 docker.io
docker rmi $(docker images | grep none | awk '{print $3}' | sort -r)
docker rmi $(docker images -q)

容器管理

啟動容器有兩種方式,一種基於鏡像新建一個容器並啟動,另外一個是將在終止狀態(stopped)的容器重新啟動.

因為docker的容器太輕量級了,很多時候用戶都是隨時刪除和重建.

創建容器
# 容器就像是一個類的實例(比如一個基於CentOS7鏡像啟動的虛擬機)
# 連接進行進入命令行模式,exit命令退出。
docker run -t -i nginx:latest /bin/bash
-i                      # 交互式操作,讓容器的標準輸入保持打開.
-t                      # 讓docker分配一個偽終端(pseudo-tty)並綁定到容器的標準輸入上
nginx:latest            # 基於centos構建的nginx鏡像
/bin/bash               # 放在鏡像後的命令,這裡我們希望有個交互式shell,因此用的是/bin/bash
# 當我們基於鏡像啟動一個實例的時候,此時他就是容器了.
# 就好比CentOS7.iso鏡像和已經運行了的CentOS7虛擬機一樣.

# 同一個鏡像可以啟動多個容器  
# 創建運行容器且連接到容器  
docker run -it --rm -d --cidfile="id.txt" centos /bin/bash  
-i                            # 捕獲標準輸入輸出,保持交互式的意思  
-t                            # 分配一個終端或控制台,每一個控制台都要伴隨一個shell  
--rm  # 退出時就刪除該容器,默認情況下,每個容器在退出時,他的文件系統會保存下來,這樣一方面有利於調試,
# 因為可以通過查看日誌等方式來確定最終狀態;另一方面,也可以報錯容器所產生的數據,  
# 如果僅僅需要短暫的運行一個容器,且不需要保存容器中的數據,
# 就可以在exit容器時自動清理掉容器及產生的數據,但此選項不能與-d共用  
/bin/bash                     # 容器運行起來之後運行的程序,也可以是任何的命令,/bin/echo  hello  
--cidfile                     # 指定容器運行之後container長id的存放文件位置
-d			      # 如果不使用-d參數運行容器,容器會把輸出的結果(STDOUT)打印到宿主機上面,
# 如果使用了-d參數會返回一個id,也可以通過docker ps 查看容器信息.
# 要獲取容器的輸出信息,可以通過docker container logs命令查看
# 容器能否長久運行是和docker run指定的命令有關,和-d參數無關.

當利用docker run來創建容器時,Docker在後台運行的標準操作包括:

# 1. 檢查本地是否有指定的鏡像,不存在就從公有倉庫下載
# 2. 利用鏡像創建並啟動一個容器
# 3. 分配一個文件系統,並在只讀的鏡像層外面掛載一層可讀寫層.
# 4. 從宿主主機配置的網橋接口中橋接一個虛擬接口道容器中去.
# 5. 從地址池中配置一個ip地址給容器.
# 6. 執行用戶指定的應用程序.
# 7. 執行完畢後容器被終止.
Docker容器服務管理
docker start my-nginx                       # 啟動一個已經存在的容器  
docker restart my-nginx                     # 重啟容器  
docker stop my-nginx                        # 停止運行一個容器  
docker kill my-nginx                        # 殺死一個運行中的容器  
docker rename my-nginx new-nginx            # 重命名容器  
docker rm new-nginx                         # 刪除容器
docker stop $(docker ps -q) & docker rm $(docker ps -aq) # 停掉所有容器並刪除
docker container prune			    # 刪除所有處於終止狀態的容器.
docker logs [containerID/Names]             # 查看日誌
docker logs my-nginx                        # 查看 my-nginx 容器日誌

# 使用docker exec命令進入一個已經在運行的容器
docker exec -it [containerID/Names] /bin/bash    # 進入容器
docker attach 7968b44369    # 會附加該容器的標準輸出到當前命令行   
    
# 啟動狀態的容器,執行任務   
# 通過exec命令可以創建兩種任務:後台型任務和交互型任務   
# 後台型任務:docker exec -it test /bin/bash   
# 交互型任務:docker attach 7968

docker run centos echo "hello world"             # 在docker容器中運行hello world!
docker run centos yum install -y wget            # 在docker容器中,安裝wget軟件
docker ps                                        # 列出包括未運行的容器
docker ps -a                                     # 查看所有容器(包括正在運行和已停止的)
docker ps -a -q                                  # 查看所有容器的ID
docker ps -s                                     # 查看容器使用了多少內存
docker ps -qf status=running                     # 查看某種狀態的容器ID
docker ps -l                                     # 列出最近一次啟動的容器
docker inspect  7657b3785bcf      
# 查看容器詳細配置信息,包含容器名,環境變量,運行命令,主機配置,網絡配置,數據卷配置等,json格式;
docker inspect -f {{.State.Pid}} 44fc0f0582d9    # 獲取id為 44fc0f0582d9 的PID進程編號
docker inspect --format  '{{.Config.Image}}' 7657b3485    # 獲取當前運行鏡像版本
docker inspect --format='{{.NetworkSettings.IPAddress}}' 7657b3485    # 獲取當前運行鏡像的IP地址
# 打印該容器輸出
docker run -it -d --name test centos /bin/bash -c "while true; do echo hello world;sleep 2;done"
docker logs test
# 監控容器運行
docker logs container_id/container_name
--tail:            # 選項可以指定查看最後幾條日誌
-t:                # 選項則可以對日誌條目附加時間戳
-f:                # 選項可以跟蹤日誌的輸出,直到手動停止

# 運行遠程機器上的容器
docker run -it -d -h 39.108.140.0 daocloud.io/centos:7

# 斷開容器
# 斷開與容器的連接,並且關閉容器
[root@7968b4436989 /]# exit
[root@7968b4436989 /]# docker stop 7968b443

# 只斷開和容器的連接而不關閉容器
# 快捷鍵: ctrl+p+q

# 關閉運行中的容器
# 如果此時有其他終端正在對他進行交互會自動中斷
# docker stop contrainer_id/name        //發送SIGTERM信號,可忽略,15信號
# docker kill contrainer_id/name        //發送SIGKILL信號,9信號
容器的導入導出
# 導出容器
# 鏡像打包
# 方案一: export
#    利用export把正在運行的容器直接導出為tar包的鏡像文件,可以用-o或>
docker run --name my-nginx -d -p 8080:80 some-centent-nginx:1.2
docker export my-nginx > youmen_nginx.tar  &&  docker export -o youmen_nginx.tar my-nginx
scp youmen_nginx.tar 120.77.248.31:
docker import youmen_nginx.tar
docker tag 121d8 mynginx:1                            # 設置鏡像名字
docker import youmen_nginx.tar mynginx:1.1            # 導入時即設置鏡像名字

方案二: 利用save直接把鏡像打包出來
docker save -o suibian.tar library/centos:latest
scp suibian.tar 192.168.135.161:
docker load < suibian.tar                # 導入之後使用原名

# 導入也可以通過指定URL或者某個目錄來導入
docker import //example.com/exampleimage.tgz example/imagerepo

------------------------------------區別介紹-------------------------------------
# docker save:將一個鏡像導出為文件,保存的是該鏡像的所有歷史記錄;
# docker export:將一個容器導出為文件,保存的是容器當時的狀態,即容器快照;
# docker load:將鏡像存儲文件導入到本地鏡像庫;
# docker import:導入一個容器快照到本地鏡像庫;

docker save和docker export之間的區別:
    1> docker save是將鏡像保存為tar包,且會保存該鏡像的父層、標籤、所有歷史等信息;
       docker export是將容器文件系統保存為tar包,僅僅保存的是容器當時的狀態(快照);
    2> docker save可以同時指定多個鏡像名稱;docker export只能指定一個容器名稱;
    3> docker save保存的鏡像文件tar包使用docker load命令加載還原;
       docker export保存的容器快照tar包使用docker import命令導入還原;
    4> docker save保存的tar包文件通常比docker export導出的文件要大;

docker load和docker import之間的區別:
    1)docker load將鏡像存儲文件導入到本地鏡像庫;docker import將容器快照文件導入到本地鏡像庫;
    2)docker load不能指定url;而docker import可以指定url來進行導入;

容器服務管理及開機啟動設置

# Docker容器開機啟動設置
sudo docker run --restart=always -it centos /bin/bash
--restart=always # 默認情況下docker重啟之後所有容器會被關閉,這個選項的意思是容器隨docker engine自啟動
# 如果創建時候未指定--restart=always,可通過docker  update命令設置:
docker update --restart=always  7b5f30fe77c0    

# 注意Docker服務開啟啟動
# restart參數介紹
# no:容器退出時候,不重啟容器
# on-failure: 只有在非0狀態退出時才重新啟動容器
# always:無論退出狀態是如何,都重啟容器
# unless-stopped: 在容器退出時總是重啟容器,但是不考慮在Docker守護進程啟動時就已經停止了的容器
# 在使用on-failure策略時,指定Docker將嘗試重新啟動容器的最大次數;
# 默認情況下,Docker將嘗試永遠重新啟動容器
# sudo docker run --restart=on-failure:5  <image>