1. 程式人生 > >使用docker搭建開發環境

使用docker搭建開發環境

我的主力機是windows,windows下面有太多提升效率的軟體.但是開發的時候不得不使用linux.就單單開發而言.我還是喜歡使用linux.所以就造成了我得在windows下面使用虛擬機器.這是最開始的辦法.後面得知有vagrant這個東西之後,用了一陣子感覺還不錯.但是我使用的時候動不動就會出現一些問題,所以一怒之下決定學學docker.然後使用docker來作為開發環境.

使用docker作為開發環境大概我有這幾點要求

  • 部署快,不要換臺機子裝了一天的環境

  • 穩定...

  • 輕輕輕!

  • container得可以訪問本機所在區域網

  • 可以實現檔案共享

在我接觸了一陣子docker之後,發現docker可以滿足我大部分意淫出來的美好開發環境.折騰一番之後終於搞定,於是祭出本文.希望可以幫助到需要的人.

學習本篇之前希望你對docker有一丟丟的瞭解,一丟丟就可以了.

安裝.

我一般不喜歡講如何安裝一個軟體,但是介於docker的一些問題.還是講講.

  • 如果是windows10之前的使用者,那麼安裝docker比較麻煩. 你可能需要一個Docker Toolbox的東西,具體安裝方式請自行google.因為我的機子是Windows10的.

  • 如果你是Windows10的使用者,恭喜你.你只要點這裡下載一個exe檔案,然後就可以無腦安裝了.但是要保證開啟Hyper-V

    功能.如何開啟看這裡.注意,這個開啟之後就不能使用virtualbox虛擬機器了.

安裝好之後,啟動docker在左下角就可以看到docker的logo了.之後我們的操作都是在PowerShell/CMD下面執行的了.執行docker info會看到下面的內容

PS C:\Windows\system32\WindowsPowerShell\v1.0> docker info
Containers: 1
 Running: 1
 Paused: 0
 Stopped: 0
Images: 4
........

由於docker主機在外國,安裝好之後我們需要更改下源,不然下載image的時候會很慢.這裡使用

daoCloud提供的映象,你需要註冊登入之後,獲取到每個人獨一無二的url.然後貼上要下面就可以了.記得重啟啊喂...

基本概念

在使用docker之前你要明白兩個概念,兩個學docker過程中一定會一直強調的概念

  • image

  • container (這種術語直接使用英文,不做翻譯)

這兩個是整個docker的基礎概念,這裡本著不負責任的僥倖心理大概的說一下這兩個的區別.

  • image是靜態的,類比為面向物件就是一個類

  • container是動態執行的,類比為面向物件就是一個例項化的物件.

一般,container是可執行的,我們啟動一個container之後,這個container裡面就是我們的linux環境.

懂得了上面的意思,你就明白了我們要做的事情很簡單:找一個合適的image,這個image裡面應該包含一切開發時候所需要的東西, 然後啟動它,我們就可以在這個container環境上工作了.當然這個時候container應該可以跟宿主共享檔案.並且可以在本區域網內可以被訪問到.

在繼續搭建我們的開發環境之前,我們還是要先學一點docker的命令和概念的.

id&&name

每個image都有一個唯一的id來標識,同樣container也有.這個唯一的id一般很長,比如:c59dc2dfad95,但是一般我們輸入的時候只要輸入若干位能標識當前系統內唯一標識某一個image就可以了.比如只要輸入c59d可能就可以標識這個image.除了id,還可以給一個image起名字,這樣子也可以通過name來操作一個image.

run

通過docker run image_name可以直接啟動本地的一個image.這個命令後面可以加很多子引數來開啟其他功能.如果本地不存在這個image,那麼docker會去官方的倉庫去下載,這個倉庫你可以理解為github一樣的網站,上面存放了許多別人push上去的image.

tag

每個image都有一個名稱.除了名稱之外還有一個叫做tag的東西,這個稱之為標籤的東西可以用來標識同一個image的不同版本.如果你沒有給一個image指定一個tag,那麼docker會預設為這個iamge新增一個名為:latest的tag.如果你使用docker run ubuntu,那麼就會預設執行ubuntu:latest.如果本地沒有這個image,那麼就會去從倉庫下載ubuntu:latest的iamge.很多時候你會看到ubuntu:14.04的image.這個14.04就是代表這個image的tag.只是很多時候image製作者把tag用來標記version了而已.

docker images

這個命令會列出本地所有的images.每個image都會有一個獨一無二的id.如下面 IMAGE ID欄位.

PS C:\Windows\system32\WindowsPowerShell\v1.0> docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu-ok           latest              5f93b91bc208        26 hours ago        423.7 MB
ubuntu              latest              a421b4d8494d        27 hours ago        423.1 MB
ubuntu              14.04               3f755ca42730        2 days ago          188 MB

docker ps

這個命令會列出所有在執行的container.當執行docker ps -a就會列出所有的container.包括已經退出的container.

docker commit

這個命令可以把一個container製作成一個image.

docker rm && docker rmi

docker rm container_id可以用來刪除一個container.docker rmi image_id/image_name可以用來刪除一個image.

AUFS

很多文章講docker都會把這個放到後面一點講.反正不會在類似"使用docker做開發環境"的文章裡面講. 但是我覺得這個東西是理解docker的關鍵.所以一定要講.

AUFS比不是docker獨有的,很多Linux的發行版中都用到了這個特性.說起AUFS,這個東西是UFS的升級版,前面的A就是代表advanced的意思.那AUFS/UFS到底是個什麼東西?

所謂AUFS,Advanced Union File System 就是把不同物理位置的目錄合併mount到同一個目錄中.這種技術有一點典型的應用:有些linux發行版只要插入一個光碟就可以直接執行.不用進行安裝.你對系統檔案進行的增刪改只是反映在電腦的硬碟上面,不會影響到光碟的內容.即對光碟只讀不寫.那麼docker是如何使把這個技術應用到docker上?

docker把一個映象分成了很多層layer.這些層合併在一起才成為了一個完整的image.這樣子有什麼好處?最直觀的一點就是,ubuntu15.04跟ubuntu16.04的image可能只有一點點差別.這點差別體現在第四層layer上.那麼ubuntu15.04跟16.04就可以共享前三層layer.這樣子如果你本地有了ubuntu15.04的image.那麼再pull ubuntu16.06的時候只要把第四層的pull下來就可以了.

而且,image的所有層都是只讀的,當你啟動一個image當做container執行的時候,docker會在image的只讀層上加一層薄薄的可寫層.你在container裡面做的所有操作都是反映在可寫層.當你退出container之後,下次啟動同一個image,之前操作的所有東西都會沒有掉.一個重新做人的image.

這個時候有一個問題就來了,我們pull一個image,啟動了container.好不容易把該安裝的軟體都安裝好了,然後退出了container.之前安裝的軟體就都沒有了!這個時候我們就要使用commit命令了.commit命令可以把當前的可寫層合併到image的只讀層裡面.這樣子這個image又多了一層.下次我們啟動這個image的時候安裝的軟體就都還在了.

一個image由好幾層layer構成.每個layer都是一個只讀層

當啟動一個container之後,就會在iamge的只讀層基礎上新增一個可寫層.所有對container執行的操作都反映在container上.(以上圖片都來自docker文件.)

這裡提一點,當使用docker images命令檢視iamge資訊的時候,後面的SIZE是表示當前iamge所佔用的大小,但是不意味著所有SIZE相加起來就是佔用磁碟空間的總大小.一定要注意,可能有image共享若干層layer.這些layer在相加的時候被計算了好幾遍.

PS C:\Windows\system32\WindowsPowerShell\v1.0> docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
<none>              <none>              12e32b701daa        25 minutes ago      188 MB
ubuntu              14.04               3f755ca42730        3 days ago          188 MB
centos              6                   8315978ceaaa        6 weeks ago         194.6 MB

刪除

上面的命令提到刪除有rm跟rmi兩個,rm是用來刪除一個已經退出的container.rmi是用來刪除一個image的.有了上面AUFS的概念之後,要明白的是我們使用docker rm container_id的時候,其實只是刪除掉了一層可寫層的資料.因為只讀層是container跟image共享的.只要iamge沒有被刪掉,那麼只讀層的資料一定也不會被刪除掉.

同樣,當多個image共享若干層只讀層的時候,刪除掉一個image.只是刪除掉了這個image獨有的一層只讀層資料.其他共享的資料並沒有被刪除掉,只有當刪除掉所有的image之後,共享的layer層才會被刪掉.

執行刪除命令的時候會看到如下的資訊,這裡每一次deleted都是代表刪除掉了一層layer.

PS C:\Windows\system32\WindowsPowerShell\v1.0> docker rmi ubuntu-fin
Untagged: ubuntu-fin:latest
Deleted: sha256:9e0728e8edbaf72846c43c629590fba5f46b1d705111d3fb1d79b9cf03a6c50c
Deleted: sha256:d53e457ca7161cd6f2d1b6678ecaafd19043dcaeb1363471867e1047819268fa
Deleted: sha256:496ef4fa137e03d80cf821745f875860d3d3120447326b8609938aa70f2edbd9
Deleted: sha256:12e32b701daa90c435176a273b2b41b4bfb219523c1ae396dc2f7068bbb6c088
Deleted: sha256:e8f29656cf54ad60a17d4b38362d9207b52a846cce3cc13e245fc3b799ff53e9
Deleted: sha256:48f6b521c809e40468886b0a159040503d00a0abb1eabf310451edfea562b459
Deleted: sha256:e94abc94ab1aff00280016eaf0649a75270886a2b60c8fe862ca549a0601949f
Deleted: sha256:3f755ca4273009a8b9d08aa156fbb5e601ed69dc698860938f36b2109c19cc39
Deleted: sha256:565903b66233d5576592815ca4d499bd6fe09a9b4baf83f345aaf64544f1cd78
Deleted: sha256:b653e4373a4b35aa760ff67cfa3de2c9fe3c089823b63ec797eb04de256f86ba
Deleted: sha256:362e536c4e530b94ce4204461e4f8c998705bcb98c91be54dd40b22f50531f3a
Deleted: sha256:b69ad682d83af6a6962b4a60a0b5f033c8d39efcd20dbdf320b6dd8136e50aae
Deleted: sha256:bc224b1b676d12be2a49f99778dda08b90d22747244d0a0afcdf4cfeb7db5d89

我們再刪除iamge的時候有時候不能成功刪除.大概原因有一下幾點:

  • container正在執行,你刪除這個container會失敗.應該使用docker stop container_id退出當前container再嘗試刪除.

  • container退出了,刪除當前image也會失敗.因為container雖然退出,當前container儲存著執行環境等資料.container是在image的基礎上添加了一層可寫層.所以他們是共享只讀層的.

  • 刪除一個image會有Untagged: ubuntu:14.04.這個不是沒有刪除成功.這個是因為有其他image跟這個ubuntu:14.04共享layer層.所以刪除時候並沒有真正刪除掉layer層的資料.

ok,有了上面的預備知識,我們現在可以開始準備我們的環境了.剛剛說過,我們退出一個container之後在container所安裝的軟體,新增的檔案等等資料都會丟失掉,所以正確的辦法應該是:在一個container環境中配置好所有開發要用到的東西之後,使用docker commit命令來把當前這個container製作成一個image.然後下次我們啟動這個image的時候環境就是我們所需要的了.但是這樣子會存在三個問題:

  • 當別人給你一個image之後,你知道這個image裡面安裝了哪些檔案,修改了哪些資料麼?

  • 每次commit都會形成一個新的只讀層.commit次數多了會使得image變得越來越臃腫.

  • 再著,一個image動輒2,3G.帶著這麼大個檔案跑也不優雅.

要解決上面的這些問題,就要使用Dockerfile了.所以我們開始之前還要做點功課.

Dockerfile

Dockerfile是用來描述如何構建一個image的,Dockerfile由一些指令構成,全部指令大概有20個左右,這裡不全部講解.只講一些我們下面會用到的.具體Dockerfile的全部用法參考Docker官方出的最佳實踐.

FROM

我們要製作的image必然是基於某個現有image的基礎,from命令就是用來指定使用哪個基礎iamge的.像很多ubuntu官方在Docker Hub上維護由官方的image.我們下面開發環境的搭建就是基於ubuntu:14.04的環境下完成的.

COPY && ADD

copy命令是把宿主機上的檔案拷貝到image中.add可以是copy的高階版.

  • copy要求拷貝的檔案在宿主機上存在

  • add可以指定一個url座位原始檔,docker會自動去下載這個url的檔案, 然後拷貝到image中.

我們待會兒就會用到add指令,因為我們需要使用163的ubuntu源來替換ubuntu原生的apt-get源.所以我們的Dockerfile會有類似的指令 : ADD http://mirrors.163.com/.help/sources.list.trusty /etc/apt/sources.list.

CMD

這個是指定啟動一個container之後,預設執行的命令.我們執行docker run ubuntu:14.04啟動一個container之後,預設就進入了bash介面.這就說明這個ubuntu:14.04的CMD就是bash.

這裡要澄清一個概念.使用docker run之後預設進入了bash會讓很多人以為啟動container跟啟動一個虛擬機器沒什麼區別.其實不是的.docker的container就是為了某個程序而存在的,這個程序就是CMD所指定的程式.比如:CMD /bin/bash就是啟動了bash.當我們退出了bash之後,整個container也就退出了.如果你的CMD寫成:CMD service nginx start.你會發現container執行之後就馬上結束了.這是因為整個container只是為了service nginx start這條命令而存在的,它不會管你這條命令啟動了什麼.預設啟動的bash正好是一直在前臺執行,只有你使用exit命令退出bash的時候才結束bash程序.這個時候container才結束.才會讓人有container跟虛擬機器差不多的錯覺.

上面的這個概念很重要,一定要理解透徹.如果沒有搞清楚這點.你會一直覺得docker跟虛擬機器沒有什麼區別.

RUN

這個命令指定了在構建image時候image中藥執行的命令.這麼說可能有點蹩腳.舉個例子,我們希望我們的映象構建好的時候就安裝好了git.那麼我們就可以在Dockerfile裡面寫RUN apt-get -y install git.這樣子在構建映象的時候就會去安裝git了.待會兒我們要安裝的軟體都是通過這個命令指定的.也是有了RUN指令,我們就可以知道一個image構建過程中做了一些什麼操作.

好了.Dockerfile我們目前只需要這些指令.下面我們就根據上面學到的東西來快速的搭建我們所需要的開發環境.

實戰--編寫Dockerfile

我知道,上面那樣子好像很隨意的講了一下Dockerfile,肯定也不會寫.所以,這裡我給出我構建image使用的Dockerfile作為參考.

FROM ubuntu:14.04
ADD http://mirrors.163.com/.help/sources.list.trusty /etc/apt/sources.list
COPY install.sh /usr/local/src/install.sh
COPY supervisord.conf /usr/local/src/supervisord.conf

RUN apt-get  update && \
    apt-get -y install build-essential && \
    apt-get -y install supervisor && \
    cp /usr/local/src/supervisord.conf /etc/supervisor/supervisord.conf && \
    apt-get -y install openssh-server && \
    apt-get -y install git && \
    apt-get -y install vim && \
    apt-get -y install lrzsz && \
    apt-get -y install libxml2-dev && \
    apt-get -y install  pkg-config libssl-dev libsslcommon2-dev && \
    apt-get -y install libbz2-dev && \
    apt-get -y install libcurl4-gnutls-dev && \
    apt-get -y install libjpeg8-dev && \
    apt-get -y install libpng-dev && \
    apt-get -y install libfreetype6-dev && \
    apt-get -y install libmcrypt-dev && \
    apt-get -y install libxslt-dev && \
    apt-get -y install libgmp-dev && \
    apt-get -y install libreadline-dev && \
    ln -s /usr/include/x86_64-linux-gnu/gmp.h /usr/include/gmp.h && \
    bash /usr/local/src/install.sh && \
    adduser --gecos '' --disabled-password chenjiayao && \ 
    echo -e '1111\n1111' | passwd chenjiayao && \
    echo -e '11\n11' | passwd root

CMD supervisord -n

上面的Dockerfile其實相當的簡單,指令都是我們上面用到的,這裡再解釋一下每一行的作用.

  • 第一行FROM ubuntu:14.04指明瞭使用ubuntu官方維護的14.04的image作為基礎image來構建自己的image.執行這條指令之後,如果你的本地沒有ubuntu:14.04這個image的話, 那麼就會去hub docker下載

  • 第二行ADD指令上面提到了,這裡就是使用163的源代替ubuntu內建的源,這樣子下載軟體的速度就會比較快.

  • 接著是兩個copy指令.這裡從宿主機拷貝了兩個檔案到映象中.其中install.sh是我自己寫的編譯安裝php+apache的指令碼檔案,這裡根據自己需要來決定.後面的supervisord是linux下面用來管理程序的軟體.你會發現CMD啟動的就是supervisord.後面-n引數說明是以前臺的方式啟動.而不是後臺啟動.這樣子就避免了container執行一下就退出了.

  • RUN 裡面都是在安裝軟體.執行一些必要的操作.你會發現我把所有的軟體安裝都寫成了一個RUN指令.你可能會有疑問為什麼不使用很多個RUN來編寫.為什麼要再一個RUN裡面安裝全部軟體.這裡就要說明一點 : 每執行一個Dockerfile的指令都會讓我們的image增加一層只讀層.所以,寫很多指令的話,我們的image就會有太多的layer.所以儘量要剋制命令的個數.

  • CMD命令.這裡我沒有使用預設的bash作為啟動命令是因為:如果使用bash作為預設的啟動程序之後,當前container就只會有一個程序bash.那麼其他的apache.ssh等服務都不會自動啟動.*每次執行container都得手動啟動這些服務很麻煩.所以這裡使用supervisor來管理.配置好supervisor之後,只要啟動了supervisor,supervisor就會自動幫我們啟動其他程序.比如apache.ssh等等.這樣就比較方便.所以如果還不知道supervisor的童鞋,趕緊學起來,而且相當的簡單.如果就是不學的同學,也不要急,後面我會給出我的Dockerfile和其他配置檔案.可以直接clone我的.

好了,Dockerfile我們已經準備好了,下面使用docker build -t ubuntu-php .來構建自己的image了.但是在開始之前要強調一下build的命令.

build命令 接著 -t ubuntu-php表示構建好的image的名稱.注意後面的.,這引數表示的是當前目錄.很多時候我們在一個目錄下建立了Dockerfile,編寫好之後.使用powershell進入這個目錄. 然後執行docker build -t image_name .就開始編譯.很容易就以為最後一個引數是指定Dockerfile所在的目錄.其實不是這樣子的.這個目錄指定的是當前docker編譯這個image的工作目錄.

要先明白,docker是一個C/S的軟體,我們使用powerShell輸入命令 .之後命令是被髮送到服務端執行,然後返回結果的.這跟MySQL一樣.只是我們把客戶端和服務端安裝在一臺主機上.

當我們構建image的時候,執行類似COPY指令,那麼把檔案拷貝到image中,但是構建檔案是在服務端完成的,如何讓docker服務端得到拷貝的檔案?這裡我們就要指定一個docker構建的工作目錄了.當構建開始的時候,docker會把工作目錄下的所有檔案都發送到服務端.然後開始構建.這樣子他就可以得到我們要copy到image的檔案了.

所以我們構建的時候指定.是想把當前目錄下的檔案等傳送到docker服務端進行構建.只是在上面,我們的Dockerfile正好是放在了docker構建image的工作目錄中了.

那麼,既然上面的引數不是指定Dockerfile所在的目錄.那如果我的機子上有多個Dockerfile的話,那麼docker會使用哪個?我編寫這個Dockerfile的目的就是希望使用這個Dockerfile.這個不用擔心. 如果你在build的時候沒有指定使用哪個Dockerfile.預設會使用構建iamge的工作目錄下名字為Dockerfile的那個Dockerfile....聽著有點暈...如果不想理清楚這些問題.每次構建的時候使用powerShell進入Dockerfile所在的目錄下,然後執行docker build image_name .就可以了.

在構建過程中會輸出類似下面的內容

PS D:\code\docker\ubuntu> docker build -t ubuntu-fin .
Sending build context to Docker daemon 8.192 kB
Step 1 : FROM ubuntu:14.04
 ---> 3f755ca42730
Step 2 : ADD http://mirrors.163.com/.help/sources.list.trusty /etc/apt/sources.list
Downloading [==================================================>]    872 B/872 B
 ---> 386d7ab302b9
Removing intermediate container f183c42cf864
Step 3 : COPY supervisord.conf /usr/local/src/supervisord.conf
 ---> 8ce5250f8498
Removing intermediate container 2c6d89b3be22
Step 4 : COPY install.sh /usr/local/src
 ---> efa055e7d1b3
Removing intermediate container e0c7dacd9136
Step 5 : RUN apt-get  update &&     apt-get -y install build-essential &&     apt-get -y install supervisor &&  cp /usr/local/src/supervisord.conf /etc/supervisor/supervisord.conf &&     apt-get -y install openssh-server &&     apt-get -y install git &&     apt-get -y install vim &&     apt-get -y install lszrz &&     apt-get -y install libxml2-dev &&     apt-get -y install  pkg-config libssl-dev libsslcommon2-dev &&     apt-get -y install libbz2-dev &&     apt-get -y install libcurl4-gnutls-dev &&     apt-get -y install libjpeg8-dev &&     apt-get -y install libpng-dev &&     apt-get -y install libfreetype6-dev &&     apt-get -y install libmcrypt-dev &&     apt-get -y install libxslt-dev &&     apt-get -y install libgmp-dev &&     apt-get -y install libreadline-dev &&     ln -s /usr/include/x86_64-linux-gnu/gmp.h /usr/include/gmp.h &&     bash /usr/local/src/install.sh &&     adduser --gecos '' --disabled-password chenjiayao &&     echo -e '1111\n1111' | passwd chenjiayao &&     echo -e '11\n11' | passwd root
 ---> Running in 1dd5ade41249

發現,每一個Step其實就是執行Dockerfile中的每一個指令.好了,構建已經開始,等待構建結束之後,我們的環境也就搭建好了,建議把Dockerfile等構建必須的檔案放到github上面,以後換一個環境.只要下載檔案.然後就可以構建了.

這裡我放出我構建環境時寫的Dockerfile,有需要自取.傳送門.

最後我們還有三個問題需要解決:

  • 檔案共享

  • 埠對映

  • commit製作映象

這些問題,考慮到文章篇幅應該夠多,所以將再開一篇文章簡介.