1. 程式人生 > >看完此文,媽媽還會擔心你docker入不了門?

看完此文,媽媽還會擔心你docker入不了門?

  上週物件突然心血來潮說想養個小寵物,我問想養啥她又說隨便,你看著辦!!!這我真的比較難辦啊!但是咱們程式設計師能有個物件就不錯了,還不趕緊寵著,我只能照辦咯!

  我去到了一家寵物店,半天也沒有找到合適的目標。正在我猶豫彷徨之時,看到了老闆門口魚缸裡面的金魚游來游去還挺順眼!於是我問老闆
  我:老闆,金魚多少錢?
  老闆:加魚缸一起100塊錢不講價!
  我:這...便宜一點咯!
  老闆:小夥子看你骨骼驚奇,定是個養魚的奇才,2塊錢賣給你吧!但是魚缸可不能給你!
  我:那,你幫我打包一條吧,幫我拿個袋子裝著就好了!

  於是我興高采烈的拎著小金魚就回家了,找了個大罐子養著!物件看到我買的小金魚後露出了幸福的笑容~

  第二天早上物件把我從睡夢中搖醒:“嚶嚶嚶,人家的小魚動不了了,你賠~~~ ”。於是我很憤怒的跑去寵物店找老闆索賠!
  我:你們家賣的魚有問題,回去就不行了!
  老闆:不可能,昨天在我們這都活蹦亂跳的!
  我:就是你們家的魚有問題!
  老闆:肯定是你自己買的魚缸有問題!
  我:手持兩把錕斤拷,口中疾呼燙燙燙。
  老闆:腳踏千朵屯屯屯,笑看萬物鍩鍩鍩?

  這一幕,似曾相識!像極了我們在開發中
  測試:xx,你的程式碼在生產環境上執行有問題。
  我:不可能,我本地都執行得好好的。
  測試:你自己上生產環境上看。
  我:我不看,我的程式碼在本地沒問題,肯定是運維的鍋,你去找運維!

  或許很多開發人員都有過上面的經歷,程式在本地執行都很正常,一上到生產環境就崩了。這是因為程式跟小金魚一樣也會“水土不服”!而導致程式水土不服的原因一般就是環境和配置的差異!加上現在網際網路高併發、大流量的訪問,一個應用往往需要部署到叢集的多臺機器上,並且叢集擴容縮容的需求也比較頻繁。如果按照傳統的方式部署,那每一臺伺服器上都需要裝各種軟體...然後進行各種配置...我彷彿看到了“工作996,生病ICU”在向運維工程師招手!

  那有沒有一種方案不僅能遮蔽環境的差異,並且還能快速部署呢?既然“水土不服”那我把程式及整個“水土”都打包遷移,就看你服不服。而docker就是這樣的一種讓你服技術!

  昂,上面的物件是我自己new的(* ̄︶ ̄)...

Hello Docker

  Docker是什麼呢?百度百科是這樣跟我說的:Docker 是一個開源的應用容器引擎,讓開發者可以打包他們的應用以及依賴包到一個可移植的映象中,然後釋出到任何流行的 Linux或Windows 機器上,也可以實現虛擬化。容器是完全使用沙箱機制,相互之間不會有任何介面。

  這段話已經很概括的描述了docker是什麼,docker能幹嘛,docker的基本特性!相信剛開始接觸docker的你跟我也一樣,看了幾遍都很懵逼!沒關係,看完全文再回頭看這段話,或許就有不一樣的體會了!我們先看看docker官方給出的“定妝照”

  如果非要我用一句話描述這張圖片,還在上幼兒園的我會說:“一條可愛的鯨魚揹著多個集裝箱,暢遊在大海里 ”!而現在我會說:“docker是一個執行在作業系統上的軟體,這個軟體上面可以執行多個相互隔離的容器”!不同的表述,同一個意思!這條可愛的鯨魚就是咱們的docker,而大海就是我們的作業系統,多個集裝箱就是在docker上執行的容器!什麼是容器咱們後面會說~

  假如你想漂洋過海來看我,你可以選擇自己造一條船,這樣你就得自己備足很多幹糧,還得準備很多一些其他的必需品才能出發!但是現在有一條鯨魚游過來對你說,我這裡有很多集裝箱,裡面有你所需要的一切,你選一個適合你的進來就可以了,我會帶你乘風破浪的!

  看到這裡,你是否對docker有個初步的印象了呢?至少知道了:1、docker是什麼?2、為什麼需要docker?

與傳統虛擬機器對比

  前面我們說過docker可以實現虛擬化,那docker與我們平時用的虛擬機器有什麼區別和聯絡呢?在那些年我們還買不起雲伺服器的時候,如果我們想學linux那就得先安裝一個建立虛擬機器的軟體,然後在軟體上面建立虛擬機器,然後分配記憶體、分配磁碟、安裝linux作業系統等等一系列的操作,然後等個分把鍾讓虛擬機器執行起來~
  為什麼傳統虛擬機器啟動會那麼慢呢?因為傳統虛擬機器技術是虛擬出一套硬體後,在其上面執行一個完整的作業系統,然後在該系統上面再執行所需要的應用程式,並且虛擬機器的資源需要提前分配,一旦分配這些資源將全部被佔用。但是docker容器內的應用程式是直接運行於宿主的核心,容器沒有自己的核心,更加不會對硬體進行虛擬。因此docker容器比傳統的虛擬機器更為輕便!但是docker容器技術也是參考虛擬機器一步一步的迭代優化過來的!我們來看看官方給出的docker容器和傳統虛擬機器的對比圖:

  圖中也能看出來,docker就是一個執行在作業系統上的軟體!以後如果想在windows上面學習Linux,只需要在本地安裝一個windows版本的docker,然後看完本文的剩下的部分,就能輕輕鬆鬆的玩轉linux啦!不過在windows上安裝docker也需要先安裝一個虛擬機器~

基本組成要素

  前面對docker的基本概念有了個大致印象,但是到目前為止,可能你對docker的認識還比較空泛,那下面部分我們就從docker的基本組成要素來更深入的走進docker!docker是一個client-server的結構!先看看官網給出的架構圖:

  這張圖裡面概括了docker的所有的元素!我們就逐一分析docker客戶端、docker服務、倉庫、映象、容器等概念!

docker客戶端

  最左邊是docker的客戶端,類似我們操作mysql的工具navcat,只不過我們這裡的是沒有圖形化介面的命令終端。docker客戶端是使用者與docker服務互動的視窗!我們能看到圖中就是各種操作的命令!

docker服務

  中間的是docker後臺執行的服務,一個稱為docker daemon的守護程序。可以理解為我們mysql的服務,我們的操作命令都是在這部分進行處理!docker deamon監聽著客戶端的請求,並且管理著docker的映象、容器、網路、磁碟(圖中只列出了映象與容器)等物件。同樣,docker的客戶端與服務可以執行在同一機器上,也可以用某臺機器上的客戶端遠端連線另一臺機器上的docker服務,這跟我們的mysql一樣的呢。

倉庫

  右邊部分是註冊倉庫,在遠古時代做開發的都知道,我們以前需要一個第三方包的時候需要去網上下載對應的jar包,很麻煩不說,還容易下的包是不穩定的版本。有了maven之後,我們只要在maven配置檔案中引入對應的依賴,就可以直接從遠端倉庫中下載對應版本的jar包了。docker中的倉庫與maven的倉庫是一個概念,可以遠端下載常用的映象,也可以push包到遠端倉庫(如圖中的redis、nginx等映象),同一個映象又可以有多個版本,在docker中稱為tag!

映象&容器

  前面我們有多次提到映象和容器,這是docker裡面很核心的兩個概念。那映象和容器分別是什麼呢?映象和容器的關係是什麼呢?

  映象
  官方給出的定義是:docker映象是一個只讀模板,可以用來建立docker容器。映象是一種輕量級的、可執行的獨立軟體包,用來打包軟體執行環境和基於執行環境開發的軟體。它包含執行某個軟體所需要的所有的內容,包括程式碼、執行時、庫、環境變數、配置檔案等。我們開發的web應用需要jdk環境、需要tomcat容器、需要linux作業系統,那我們可以把我們所需要的一切都進行打包成一個整體(包括自己開發的web應用+jdk+tomcat+centos/ubuntu+各種配置檔案)。打包後的映象在某臺機器上能執行,那它就能夠在任何裝有docker的機器上執行。

  任何映象的建立會基於其他的父映象,也就是說映象是一層套一層,比如一個tomcat映象,需要執行在centos/ubuntu上,那我們的tomcat映象就會基於centos/ubuntu映象建立(在後面的操作部分我們可以通過命令檢視),這樣的結構就類似於我們吃的洋蔥,如果你願意一層一層一層地剝開我的心~

  容器
  官方給出的定義是:docker的容器是用映象建立的執行例項,docker可以利用容器獨立執行一個或一組應用。我們可以使用客戶端或者API控制容器的啟動、開始、停止、刪除。每個容器之間是相互隔離的。上一步我們構建的映象只是一個靜態的檔案,這個檔案需要執行就需要變為容器,我們可以把容器看做是一個簡易版的linux系統和執行在其中的應用程式!就是前面看到的鯨魚背上的一個一個的集裝箱,每個集裝箱都是獨立的!

  映象與容器關係
  上面的概念很抽象,可以理解為容器就是映象的一個例項,相信大家都寫過類似下面的程式碼:

public void Dog extends Animal{  
    ......
}  
......
Dog dog = new Dog()

  我們在程式碼中定義了一個Dog類,這個類就相當於一個映象,可以根據這個類new出很多的例項,new出來的例項就相當於一個個的容器。映象是靜態的檔案,而容器就是有生命的個體!Dog類可以繼承父類Animal,如果不顯式的指定繼承關係,Dog類就預設繼承Object類。同樣上面也說到過docker中的映象也有繼承關係,一個映象可以繼承其他的映象建立,新增新的功能!

  看到這裡的你是不是對docker有了更多的瞭解了呢?我們再回頭看看百度百科對docker的描述,可能你又會有更深的印象:
  Docker 是一個開源的應用容器引擎,讓開發者可以打包他們的應用以及依賴包到一個可移植的映象中,然後釋出到任何流行的 Linux或Windows 機器上,也可以實現虛擬化。容器是完全使用沙箱機制,相互之間不會有任何介面。

容器資料卷

  上面說到容器是一個簡易版的linux系統和執行在其中的應用程式,那我們的應用程式產生的資料(比如操作日誌、異常日誌、資料)也是在容器內的系統中存放的,預設不會做持久化,我們可以進入到容器中檢視。但是萬一有一天,docker這條鯨魚不滿人類的壓迫,反抗了...老子打爛你的集裝箱!

  隨著容器的關閉,容器內的資料也會丟失,重新開啟的容器不會載入原來的資料(簡單說就是容器重新啟動又是另外一個例項了)。那對容器內的資料需要持久化到宿主機上就很有必要了,這就需要了解我們的容器資料卷~

  容器資料卷的設計目的就是做資料的持久化和容器間的資料共享,資料卷完全獨立於容器的生命週期,也就是說就算容器關閉或者刪除,資料也不會丟失。簡單點說就將宿主機的目錄掛在到容器,應用在容器內的資料可以同步到宿主機磁碟上,這樣容器內產生的資料就可以持久化了。關於容器卷的命令我們後面會有操作例項!

命令操作

  上面說了那麼多,下面就到了咱們的實操環節啦!這一節的內容會通過一些常用的命令讓大家更進一步的瞭解docker,注意!!這裡只是一些常用的命令來加深理解,而不是命令大全!如果沒有安裝docker的小夥伴可以自己按照官網的文件進行安裝,本文不會講到這部分的內容!所以我假設你在自己的伺服器上已經裝好了docker!

幫助命令

  1、docker version 檢視docker客戶端和服務的版本。

  2、docker info 檢視docker的基本資訊,如有多少容器、多少映象、docker根目錄等等。

  3、docker --help 檢視docker的幫助資訊,這個命令可以檢視所有docker支援的命令~

  這幾個命令非常簡單,有過一點linux基礎的小夥伴應該很容易理解!

映象命令

  1、docker images 檢視本地主機上所有的映象。注意是本地主機的!這裡能看到映象的名稱、版本、id、大小等基本資訊,注意這裡的image ID是映象的唯一標識!還可以通過docker images tomcat指定某個具體的映象檢視對應資訊。這裡還要注意的是centos的映象才200MB的大小,比我們物理機器上裝的centos要小得多的多,這是因為centos的映象只保留了linux核心部分,這也是為什麼docker虛擬化技術比虛擬機器執行效率更高的原因!那為什麼tomcat的映象這麼大呢?那是因為我們之前說過我們的映象就像一個洋蔥一樣,是一層套一層的!tomcat的執行需要基於centos、jdk等等映象,tomcat在上層所以體積比較大啦!
  

  2、docker rmi 刪除本地的映象,如下圖所示,可以加上-f引數進行強制刪除。這裡的rmi命令跟linux中的刪除命令就很像啦,只是這裡加了一個i代表image!
  

  3、docker search 根據映象名稱搜尋遠端倉庫中的映象!
  

  4、docker pull 搜尋到某個映象之後就可以從遠端拉取映象啦,有點類似咱們git中的pull命令,當然對應的還有個docker push的命令。如圖,如果我們沒有指定tag,預設就會拉取latest版本,也可以通過docker pull tomcat:1.7的方式拉取指定版本!注意這裡在拉取映象的時候打印出來的資訊有很多,這也是前面說到的映象是一層套一層,拉取一個映象也是一層一層的拉取!
  

容器命令

  通過映象命令我們就能獲取映象、刪除映象等操作啦!映象有了下面自然就需要通過映象建立對應的例項啦,也就是我們的容器。下面我們以tomcat為例:

  1、docker run [OPTIONS] IMAGE [COMMAND] [ARG...] 可以基於某個映象執行一個容器,如果本地有指定的映象則使用本地映象,如果沒有則從遠端拉取對應的映象然後啟動!由於這個命令非常重要,所以下面列出幾個比較重要的引數:

-d:啟動容器,並且後臺執行(docker容器後臺執行,就必須要有一個前臺程序,容器執行的命令如果不是一直掛起的命令,容器啟動後就會自動退出);
-i:以互動模式執行容器,通常與-t同時使用;
-t:為容器重新分配一個偽輸入終端,通常與-i同時使用(容器啟動後進入到容器內部的命令視窗);
-P:隨機埠對映,容器內部埠隨機對映到主機的高階口;
-p:指定埠對映,格式為:主機(宿主)埠:容器埠;
-v:建立宿主機與容器目錄的同步;
--name="myTomcat": 為容器指定一個名稱(如果不指定,則有個隨機的名字);

  

  上面我通過命令啟動了一個tomcat的容器,由於使用了 -t 的引數,所以容器啟動後就進入到了容器的內部的命令視窗,列印了很多tomcat啟動的日誌。並且使用 -p 引數指定了埠對映,也就是容器內tomcat執行的埠是8080,並且對映到了宿主機上的8888埠,這樣我們在外部就可以同過伺服器的ip+8888埠 訪問到我們容器內部tomcat部署的服務了。

  前面我們提到過容器內的資料會隨著容器的關閉而丟失。那我們就需要有容器資料卷的技術能將容器內的資料持久化到宿主機。這裡需要用到 -v 引數!我們看下面的截圖
  

  這裡第一個要注意的是我們用的 -d 引數,啟動後沒有進入到容器內部,還是在宿主機。(可以對比一下與上面 -it 引數的區別)。第二個要注意的是 -v /宿主機:/容器內目錄 實現了宿主機與容器內指定目錄的資料同步!容器啟動後就可以使用 linux 的 ll 命令檢視宿主機上已經同步到了容器內的檔案。第三個要注意的是這裡的同步是雙向的,也就是說在宿主機上對檔案的修改也會同步到容器內部!多個不同的容器對映到宿主機的同一個目錄,就可以實現不同容器間的資料共享啦!
  

  2、進入到容器後可以通過exit命令退出容器,也可以通過ctrl+P+Q快捷鍵退出容器,這兩種方式的不同之處是exit會退出並且關閉容器,而ctrl+P+Q快捷鍵只是單純的退出,容器還在執行,並且還能再次進入!

  3、docker ps我們可以通過該命令檢視正在執行的容器的資訊,這裡能看到容器的唯一id,啟動時間等等...這裡跟linux的ps命令類似,所以也可以把容器理解為一個執行在docker上的程序!docker ps -a可以檢視執行中與停止的所有容器。
  

  4、docker attach [OPTIONS] CONTAINER上面說過通過ctrl+P+Q快捷鍵退出容器後容器還在後臺執行,那如果想再次進入容器怎麼辦呢?我們就可以通過attach命令+容器的id再次進入容器!

  5、docker exec [OPTIONS] CONTAINER這個命令與attach一樣都可以再次進入後臺執行的容器,但是該命令可以不進入容器而在執行的容器中執行命令!比attach更加強大!

  6、docker stop docker kill docker restart這三個命令分別用來停止容器、強制停止容器和重啟容器,就跟我們在linux上停止、強制停止和重啟某個程序一樣的啦,這裡就不做演示了!

  7、docker rm 使用這個命令就可以刪除某個容器,這裡跟刪除映象的區別是這裡少了一個 i 啦!需要注意的是通過stop和kill停止的容器還存在於docker中,而使用 rm 命令操作後的容器將不再存在!
  

  8、docker inspect 檢視容器的詳情(也能檢視映象詳情)。

Dockerfile

  前面我們對docker以及相關概念、常用命令有了基本的瞭解,我們也知道了可以從遠端pull一個映象,那遠端的映象是怎麼來的呢?如果我們想自己建立一個映象又該怎麼做呢?
對,Dockerfile!Dockerfile是一個包含使用者能夠構建映象的所有命令的文字文件,它有自己的語法以及命令,docker能夠從dockerfile中讀取指令自動的構建映象!
  我們要想編寫自己的Dockerfiler並構建映象,那對Dockerfile的語法和命令的瞭解就是必須的,瞭解規則才好辦事嘛!

相關指令

FROM
  FROM <image> [AS <name>]
  FROM <image>[:<tag>] [AS <name>]
  FROM <image>[@<digest>] [AS <name>]
  指定基礎映象,當前映象是基於哪個映象建立的,有點類似java中的類繼承。FROM指令必是Dockerfile檔案中的首條命令。

MAINTAINER
  MAINTAINER <name>
  映象維護者的資訊,該命令已經被標記為不推薦使用了。

LABEL
  LABEL <key>=<value> <key>=<value> <key>=<value> ...
  給映象新增元資料,可以用LABEL命令替換MAINTAINER命令。指定一些作者、郵箱等資訊。

ENV
  ENV <key> <value>
  ENV <key>=<value> ...
  設定環境變數,設定的變數可供後面指令使用。跟java中定義變數差不多的意思!

WORKDIR
  WORKDIR /path/to/workdir
  設定工作目錄,在該指令後的RUN、CMD、ENTRYPOINT, COPY、ADD指令都會在該目錄執行。如果該目錄不存在,則會建立!

RUN
  RUN <command>
  RUN ["executable", "param1", "param2"]
  RUN會在當前映象的最上面建立一個新層,並且能執行任何的命令,然後對執行的結果進行提交。提交後的結果映象在dockerfile的後續步驟中可以使用。

ADD
  ADD [--chown=<user>:<group>] <src>... <dest>
  ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
  從宿主機拷貝檔案或者資料夾到映象,也可以複製一個網路檔案!如果拷貝的檔案是一個壓縮包,會自動解壓縮!

COPY
  COPY [--chown=<user>:<group>] <src>... <dest>
  COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
  從宿主機拷貝檔案或者資料夾到映象,不能複製網路檔案也不會自動解壓縮!

VOLUME
  VOLUME ["/data"]
  VOLUME用於建立掛載點,一般配合run命令的-v引數使用。

EXPOSE
  EXPOSE <port> [<port>/<protocol>...]
  指定容器執行時對外暴露的埠,但是該指定實際上不會發布該埠,它的功能是映象構建者和容器執行者之間的記錄檔案。
  回到容器命令中的run命令部分,run命令有-p和-P兩個引數,如果是-P就是隨機埠對映,容器內會隨機對映到EXPOSE指定的埠,如果是-p就是指定埠對映,告訴運維人員容器內需要對映的埠號。

CMD
  CMD ["executable","param1","param2"]
  CMD ["param1","param2"]
  CMD command param1 param2
  指定容器啟動時預設執行的命令,在一個Dockerfile檔案中,如果有多個CMD命令,只有一個最後一個會生效!同樣是可以執行命令,可能你會覺得跟上面的RUN指令很相似,RUN指令是在構建映象時候執行的,而CMD指令是在每次容器執行的時候執行的!docker run命令會覆蓋CMD的命令!

ENTRYPOINT
  ENTRYPOINT ["executable", "param1", "param2"]
  ENTRYPOINT command param1 param2
  這個指令與CMD指令類似,都是指定啟動容器時要執行的命令,如果指定了ENTRYPOINT,則CMD指定的命令不會執行!在一個Dockerfile檔案中,如果有多個ENTRYPOINT命令,也只有一個最後一個會生效!不同的是通過docker run command命令會覆蓋CMD的命令!執行的命令不會覆蓋ENTRYPOINT,docker run命令中指定的任何引數都會被當做引數傳遞給ENTRYPOINT!

RUN、CMD、ENTRYPOINT區別
1、RUN指令是在映象構建時執行,而後兩個是在容器啟動時執行!
2、CMD指令設定的命令是容器啟動時預設執行的命令,如果docker run沒有指定任何的命令,並且Dockerfile中沒有指定ENTRYPOINT,那容器啟動的時候就會執行CMD指定的命令!有點類似程式碼中的預設引數!
3、如果設定了ENTRYPOINT指令,則優先使用!並且可以通過docker run給該指令設定的命令傳參!
4、CMD有點類似程式碼中的預設引數

USER
  USER <user>[:<group>]
  USER <UID>[:<GID>]
  用於指定執行映象所使用的使用者。

ARG
  ARG <name>[=<default value>]
  指定在映象構建時可傳遞的變數,定義的變數可以通過docker build --build-arg =的方式在構建時設定。

ONBUILD
  ONBUILD [INSTRUCTION]
  當所構建的映象被當做其他映象的基礎映象時,ONBUILD指定的命令會被觸發!

STOPSIGNAL
  STOPSIGNAL signal
  設定當容器停止時所要傳送的系統呼叫訊號!

HEALTHCHECK
  HEALTHCHECK [OPTIONS] CMD command (在容器內執行執行命令檢測容器的執行情況)
  HEALTHCHECK NONE (禁止從父映象繼承檢查)
  該指令可以告訴Docker怎麼去檢測一個容器的執行狀況!

SHELL
  SHELL ["executable", "parameters"]
  用於設定執行命令所使用的預設的shell型別!該指令在windows作業系統下比較有用,因為windows下通常會有cmd和powershell兩種shell,甚至還有sh。

構建

  Dockerfile執行順序是從上到下,順序執行!每條指令都會建立一個新的映象層,並對映象進行提交。編寫好Dockerfile檔案後,就需要使用docker build命令對映象進行構建了。
  docker build的格式:docker build [OPTIONS] PATH | URL | -

-f :指定要使用的Dockerfile路徑,如果不指定,則在當前工作目錄尋找Dockerfile檔案!
-t: 映象的名字及標籤,通常 name:tag 或者 name 格式;可以在一次構建中為一個映象設定多個標籤。

  例如我們可以docker build -t myApp:1.0.1 .這樣來構建自己的映象,注意後面的 . ,用於指定映象構建過程中的上下文環境的目錄。如果大家想了解那些官方映象的Dockerfile檔案都是怎麼樣寫的,可以上https://hub.docker.com/ 進行搜尋,以tomcat映象為例

  能看到tomcat映象的父映象是openjdk映象,我們再搜尋openjdk的Dockerfile檔案

  openjdk映象的父映象又是oraclelinux映象,我們再搜尋oraclelinux的Dockerfile檔案


  openjdk映象的父映象是scratch,這是根映象,所有的映象都會依賴該映象,就像我們程式碼中所有的物件的父類都是Object!所以能看到tomcat映象就是這樣一層一層的構建出來的,這也是為什麼前面通過docker images檢視到的tomcat映象為什麼會有四百多兆的原因啦!

  看到這裡的你,是否對docker是什麼?為什麼需要docker?docker映象、docker容器的概念是什麼?docker中常用的命令有哪些?Dockerfile有哪些指令?怎麼去構建自己的映象?這些問題都能明白了呢