使用Docker构建服务(6)
1. 構建第一個應用
要構建的第一個應用是使用Jekyll框架的自定義網站。我們會構建以下兩個鏡像。
** 一個鏡像安裝了Jekyll及其他用于構建Jekyll網站的必要的軟件包。 ** 一個鏡像通過Apache來讓Jekyll網站工作起來。我們打算在啟動容器時,通過創建一個新的Jekyll網站來實現自服務。工作流程如下:
** 創建Jekyll基礎鏡像和Apache鏡像(只需要構建一次)。 ** 從Jekyll鏡像創建一個容器,這個容器存放通過卷掛載的網站源代碼。 ** 從Apache鏡像創建一個容器,這個容器利用包含編譯后的網站的卷,并為其服務。 ** 在網站需要更新時,清理并重復上面的步驟。可以把這個例子看作是創建一個多主機站點最簡單的方法。
1.1 Jekyll基礎鏡像
讓我們開始為第一個鏡像(Jekyll基礎鏡像)創建Dockerfile。我們先創建一個新目錄和一個空的Dockerfile,代碼清單如下:
** 創建Jekyll Dockerfile $ mkdir jekyll $ cd jekyll $ vi Dockerfile讓我們來看看Dockerfile文件的內容,代碼清單:
** Jekyll Dockerfile FROM ubuntu:14.04 MAINTAINER James Turnbull <james@example.com> ENV REFRESHED_AT 2014-06-01RUN apt-get -yqq update RUN apt-get -yqq install ruby ruby-dev make node.js RUN gem install --no-rdoc --no-ri jekyll -v 2.5.3VOLUME /data VOLUME /var/www/html WORKDIR /dataENTRYPOINT [ "jekyll", "build", "--destination=/var/www/html" ]鏡像基于Ubuntu 14.04,并且安裝了Ruby和用于支持Jekyll包。然后我們使用了VOLUME指令創建了以下兩個卷。
** /data/, 用來存放網站的源代碼。 ** /var/www/html/,用來存放編譯后的Jekyll網站代碼。然后我們需要將工作目錄設置到/data/,并通過ENTRYPOINT指令指定自動構建的命令,這個命令會將工作目錄/data/中的所有的Jekyll網站代碼構建到/var/www/html目錄中。
1.2 構建Jekyll基礎鏡像
通過這個Dockerfile,可以使用docker build命令構建出可以啟動容器的鏡像,代碼清單:
** 構建Jekyll鏡像 $ sudo docker build -t jamtur01/jekyll .這樣就構建成了名為jamtur01/jekyll,ID為****的新鏡像。這就是將要使用的新的Jekyll鏡像??梢允褂胐ocker images命令來查看這個新鏡像,代碼清單:
** 查看新的Jekyll基礎鏡像 $ sudo docker images1.3 Apache 鏡像
接下來,我們來構建第二個鏡像,一個用來架構新網站的Apache服務器。我們先創建一個新目錄和一個空的Dockerfile,代碼清單:
** 創建 Apache Dockerfile $ mkdir apache $ cd apache $ vi Dockerfile現在讓我們來看看這個Dockerfile的內容,代碼清單:
** Jekyll Apache 的Dockerfile FROM ubuntu:14.04 MAINTAINER James Turnbull <james@example.com> ENV REFRESHED_AT 2014-06-01RUN apt-get -yqq update RUN apt-get -yqq install apache2VOLUME [ "/var/www/html" ] WORKDIR /var/www/htmlENV APACHE_RUN_USER www-data ENV APACHE_RUN_GROUP www-data ENV APACHE_LOG_DIR /var/log/apache2 ENV APACHE_PID_FILE /var/run/apache2.pid ENV APACHE_RUN_DIR /var/run/apache2 ENV APACHE_LOCK_DIR /var/lock/apache2RUN mkdir -p $APACHE_RUN_DIR $APACHE_LOCK_DIR $APACHE_LOG_DIREXPOSE 80ENTRYPOINT [ "/usr/sbin/apache2" ] CMD ["-D", "FOREGROUND"]這個鏡像也是基于Ubuntu 14.04的,并安裝了Apache。然后我們使用VOLUME指令創建了一個卷,即/var/www/html/,用來存放編譯后的Jekyll網站。然后將/var/www/html設為工作目錄。
然后我們使用ENV指令設置了一些必要的環境變量,創建了必要的目錄,并且使用EXPOSE公開了80端口。最后指定了ENTRYPOINT和CMD指令組合來在容器啟動時默認運行Apache。
1.4 構建Jekyll Apache鏡像
有了這個Dockerfile, 可以使用docker build命令來構建可以啟動容器的鏡像,代碼清單:
** 構建Jekyll Apache鏡像 $ sudo docker build -t jamtur01/apache . ....這樣就構建了名為jamtur01/apache,ID為****的新鏡像。這就是要使用的Apache鏡像。可以使用docker images命令來查看這個新鏡像,代碼清單:
** 查看新的Jekyll Apache鏡像 $ sudo docker images ...1.5 啟動Jekyll網站
現在有了以下兩個鏡像。
** Jekyll:安裝了Ruby及其它必備軟件包的Jekyll鏡像。 ** Apache:通過Apache Web服務器來讓Jeryll網站工作起來的鏡像。我們從使用docker run命令來創建一個新的Jekyll容器開始我們的網站。我們將啟動容器,并構建我們的網站。
然后我們需要一些我的博客的源代碼。先把示例Jekyll博客復制到$HOME目錄(在這個例子里/home/james)中,代碼清單:
** 創建示例Jekyll博客 $ cd $HOME $ git clone https://github.com/jamtur01/james_blog.git在這個目錄下可以看到一個啟用了Twitter Bootstrap(http://getbootstrap.com)的最基礎的Jekyll博客。如果你也想使用這個博客,可以修改_config.yml文件和主題,以符合你的要求。
現在在Jekyll容器里使用這個示例數據,代碼清單:
** 創建Jekyll容器 $ sudo docker run -v /home/james/james_blog:/data/ --name james_blog jamtur01/jekyll Configuration file:/data/_config.ymlSource:/dataDestination:/var/www/htmlGenerating...done.我們啟動了一個叫作james_blog的新容器,把本地的james_blog目錄作為/data/卷掛載到容器里。容器已經拿到網站的源代碼,并將其構建到已編譯的網站,存放到/var/www/html/目錄。
卷是在一個或多個容器中特殊指定的目錄,卷會繞過聯合文件系統,為持久化數據和共享數據提供幾個有用的特性。
** 卷可以在容器間共享和重用。 ** 共享卷時不一定要運行相應的容器。 ** 對卷的修改會直接在卷上反映出來。 ** 更新鏡像時不會包含對卷的修改。 ** 卷會一直存在,直到沒有容器使用它們。利用卷,可以在不用提交鏡像修改的情況下,向鏡像里加入數據(如源代碼,數據或者其他內容),并且可以在容器間共享這些數據。
卷在Docker宿主機的/var/lib/docker/volumes目錄中。可以通過docker inspect命令來查看某個卷的具體位置,如docker inspect -f "{{ range.Mounts }} {{.}} {{end}}"。
提示:在Docker 1.9中,卷功能已經得到擴展,能通過插件的方式支持第三方存儲系統,如Ceph,Flocker和EMC等。可以在卷插件文檔(http://docs.docker.com/engine/extend/plugins_volume/)和docker volume create命令文檔(https://docs.docker.com/engine/reference/commandline/volume_create/)中獲取更詳細的解釋。
?所以,如果想在另一個容器里使用/var/www/html/卷里編譯好的網站,可以創建一個新的鏈接到這個卷的容器,代碼清單:
** 創建Apache容器 $ sudo docker run -d -P --volumes-from james_blog jamtur01/apache ...這看上去和典型的docker run很像,只是使用了一個新標志--volume-from。標志--volumes-from把指定容器里的所有卷都加入新創建的容器里。這意味著,Apache容器可以訪問之前創建的james_blog容器里/var/www/html卷中存放的編譯后的Jekyll網站。即便james_blog容器沒有運行,Apache容器也可以訪問這個卷。想想,這只是卷的特性之一。不過,容器本身必須存在。
注意:即使刪除了使用了卷的最后一個容器,卷中的數據也會持久保存。
構建Jekyll網站的最后一步是什么?來查看一下容器把已公開的80端口映射到了哪個端口,代碼清單:
** 解析Apache容器的端口 $ sudo docker port ****容器ID 80 0.0.0.0:49160現在在Docker宿主機上瀏覽該網站。
1.6 更新Jekyll網站
如果要更新網站的數據,就更有意思了。假設要修改Jekyll網站。我們將通過編輯james_blog/_config.yml文件來修改博客的名字,代碼清單:
** 編輯Jekyll博客 $ vi james_blog/_config.yml并將title域改為James's Dynamic Docker-Driven Blog。那么如何才能更新博客網站呢?只需要再次使用docker start命令啟動Docker容器即可,代碼清單:
** 再次啟動james_blog容器 $ sudo docker start james_blog ...** 查看james_blog容器的日志 $ sudo docker logs james_blog ...可以看到,Jekyll編譯過程第二次被運行,并且網站已經被更新。這次更新已經寫入了對應的卷。現在瀏覽Jekyll網站,就能看到變化了。
由于共享的卷會自動更新,這一切都不需要更新或者重啟Apache容器。這個流程非常簡單,可以將其擴展到更復雜的部署環境。
1.7 備份Jekyll卷
你可能會擔心一不小心刪除卷(盡管能使用已有的步驟輕松重建這個卷)。由于卷的優點之一就是可以掛載到任意容器,因此可以輕松備份它們。現在創建一個新容器,用來備份/var/www/html卷,代碼清單:
** 備份/var/www/html卷 $ sudo docker run --rm --volumes-from james_blog \ -v $(pwd):/backup ubuntu \ tar cvf /backup/james_blog_backup.tar /var/www/html ...$ ls james_ blog_backup.tar ....提示:我們還指定了--rm標志,這個標志對于只用一次的容器,或者用完即扔的容器,很有用。這個標志會在容器的進程運行完畢后,自動刪除容器。對于只用一次的容器來說,這是一種很方便的清理方法。
這里我們運行了一個已有的Ubuntu容器,并把james_blog的卷掛載到該容器里。這會在該容器里創建/var/www/html目錄。然后我們使用-v標志把當前目錄(通過$(pwd)命令獲得)掛載到容器的/backup目錄。最后我們的容器運行這一備份命令,代碼清單:
** 備份命令 tar cvf /backup/james_blog_backup.tar /var/www/html這個命令會創建一個名為james_blog_backup.tar的tar文件(該文件包括了/var/www/html目錄里所有內容),然后退出。這個過程創建了卷的備份。這顯然只是一個最簡單的備份過程。用戶可以擴展這個命令,備份到本地存儲或者云端(如Amazon S3(http://aws.amazon.com/s3/)或者更傳統的類似Amanda(http://www.amanda.org的備份軟件))
提示:這個例子對卷中存儲的數據庫或者其他類似的數據也適用。只要簡單地把卷掛載到新容器,完成備份,然后廢棄這個用于備份的容器就可以了。
1.8 擴展Jekyll示例網站
下面是幾種擴展Jekyll網站的方法。
** 運行多個Apache容器,這些容器都使用來自james_blog容器的卷。在這些Apache容器前面加一個負載均衡器,我們就擁有了一個Web集群。
** 進一步構建一個容器,這個鏡像把用戶提供的源數據復制(如通過git clone)到卷里。再把這個卷掛載到從jamtur01/jekyll鏡像創建的容器。這就是一個可遷移的通用方案,而且不需要宿主機本地包含任何源代碼。
** 在上一個擴展基礎上可以很容易為我們的服務構建一個Web前端,這個服務用于從指定的源自動構建和部署網站。這樣用戶就有一個完全屬于自己的GitHub Pages了。
2. 使用Docker構建一個Java應用服務
現在我們來試一些稍微不同的方法,考慮把Docker作為應用服務器和編輯管道。這次做一個更加"企業化"且用于傳統工作負載的服務:獲取Tomcat服務器上的WAR文件,并運行一個Java應用程序。為了做到這一點,構建一個有兩個步驟的Docker管道。
** 一個鏡像從URL拉取指定的WAR文件并將其保存到卷里。 ** 一個含有Tomcat服務器的鏡像運行這些下載的WAR文件。2.1 WAR文件的獲取程序
我們從構建一個鏡像開始,這個鏡像會下載WAR文件并將其掛載在卷里,代碼清單:
** 創建獲取程序(fetcher)的Dockerfile $ mkdir fetcher $ cd fetcher $ touch Dockerfile現在我們來看看這個Dockerfile的內容,代碼清單:
** WAR文件的獲取程序 FROM ubuntu:14.04 MAINTAINER James Turnbull <james@example.com> ENV REFRESHED_AT 2014-06-01RUN apt-get -yqq update RUN apt-get -yqq install wgetVOLUME [ "/var/lib/tomcat7/webapps/" ] WORKDIR /var/lib/tomcat7/webapps/ENTRYPOINT [ "wget" ] CMD [ "-?" ]這個非常簡單的鏡像只做了一件事:容器執行時,使用wget從指定的URL獲取文件并把保存文件保存在/var/lib/tomcat7/webapps/目錄。這個目錄也是一個卷,并且是所有容器的工作目錄。然后我們會把這個卷共享給Tomcat服務器并且運行里面的內容。
最后,如果沒有指定URL,ENTRYPOINT和CMD指令會讓容器運行,在容器不帶URL運行的時候,這兩條指令通過返回wget幫助來做這一點。
現在我們來構建這個鏡像,代碼清單:
** 構建獲取程序的鏡像 $ sudo docker build -t jamtur01/fetcher .2.2 獲取WAR文件
現在讓我們獲取一個示例文件來啟動新鏡像。從https://tomcat.apache.org/tomcat-7.0-doc/appdev/sample/下載Apache Tocat示例應用,代碼清單:
** 獲取WAR文件 $ sudo docker run -t -i --name sample jamtur01/fetcher \ https://tomcat.apache.org/toncat-7.0-doc/appdev/sample/sample.war ...可以看到,容器通過提供的URL下載了sample.war文件。從輸出結果看不出最終的保存路徑,但是因為設置了容器的工作目錄,sample.war文件最終會保存到/var/lib/tomcat7/webapps/目錄中。
可以在/var/lib/docker目錄找到這個WAR文件。我們先用docker inspect命令查找卷的存儲位置,代碼清單:
** 查看示例里的卷 $ sudo docker inspect -f "{{ range .Mounts }} {{.}} {{ end }}" sample {...}然后我們可以查看這個目錄,代碼清單:
** 查看卷所在的目錄 $ ls -l /var/lib/docker/volumes/ ...2.3 Tomcat7 應用服務器
現在我們已經有了一個可以獲取WAR文件的鏡像,并已經將示例WAR文件下載到了容器中。接下來我們構建Tomcat應用服務器的鏡像來運行這個WAR文件,代碼清單:
** 創建Tomcat 7 Dockerfile $ mkdir tomcat7 $ cd tomcat7 $ touch Dockerfile現在我們來看看這個Dockerfile,代碼清單:
FROM ubuntu:14.04 MAINTAINER James Turnbull <james@example.com> ENV REFRESHED_AT 2014-06-01RUN apt-get -yqq update RUN apt-get -yqq install tomcat7 default-jdkENV CATALINA_HOME /usr/share/tomcat7 ENV CATALINA_BASE /var/lib/tomcat7 ENV CATALINA_PID /var/run/tomcat7.pid ENV CATALINA_SH /usr/share/tomcat7/bin/catalina.sh ENV CATALINA_TMPDIR /tmp/tomcat7-tomcat7-tmpRUN mkdir -p $CATALINA_TMPDIRVOLUME [ "/var/lib/tomcat7/webapps" ]EXPOSE 8080ENTRYPOINT [ "/usr/share/tomcat7/bin/catalina.sh", "run" ]這個鏡像很簡單。我們需要安裝Java JDK和Tomcat服務器。我們首先指定一些啟動Tomcat需要的環境變量,然后我們創建一個臨時目錄,還創建了/var/lib/tomcat7/webapps/卷,公開了Tomcat默認的8080端口,最后使用ENTRYPOINT指令來啟動Tomcat。
現在我們來構建Tomcat 7鏡像,代碼清單:
** 構建Tomcat7鏡像 $ sudo docker build -t jamtur01/tomcat7 .2.4 運行WAR文件
現在,讓我們創建一個新的Tomcat實例,運行示例應用,代碼清單:
** 創建第一個Tomcat實例 $ sudo docker run --name sample_app --volumes-from sample \-d -P jamtur01/tomcat7這會創建一個名為sample_app的容器,這個容器會復用sample容器里的卷。這意味著存儲在/var/lib/tomcat7/webapps/卷里的WAR文件會從sample容器掛載到sample_app容器,最終被Tomcat加載并執行。
讓我們在Web瀏覽器里看看這個示例程序。首先,我們必須使用docker port命令找出被公開的端口,代碼清單:
** 查找Tomcat應用的端口 sudo docker port sample_app 8080 0.0.0.0:49154現在我們來瀏覽這個應用(使用URL和端口,并在最后加上/sample)看看都有什么。應該能看到正在運行的Tomcat應用。
2.5 基于Tomcat應用服務器的構建服務
現在有了自服務Web服務的基礎模塊,讓我們來看看怎么基于這些基礎模塊做擴展。為了做到這一點,我們已經構建好了一個簡單的基于Sinatra的Web應用,這個應用可以通過網頁自動展示Tomcat應用。這個應用叫TProv??梢栽诒緯倬W(http://dockerbook.com/code/6/tomcat/tprov/)或者GitHub(https://github.com/jamtur01/dockerbook-code/tree/master/code/6/tomcat/tprov)找到其源碼。
然后我們使用這個程序來演示如何擴展之前的示例。首先,要保證已經安裝了Ruby,代碼清單如下:TProv應用會直接安裝在Docker宿主機上,因為這個應用會直接和Docker守護進程交互。這也是要裝Ruby的地方。
注意:也可以把TProv應用安裝在Docker容器里。
** 安裝Ruby $ sudo apt-get -qqy install ruby make ruby-dev然后可以通過Ruby gem安裝這個應用,代碼清單:
** 安裝TProv應用 $ sudo gem install --no-rdoc --no-ri tprov ...這個命令會安裝TProv應用及相關的支撐gem。
然后可以使用tprov命令來啟動應用,代碼清單:
** 啟動TProv應用 $ sudo tprov ...這個命令會啟動應用。現在我們可以在Docker宿主機上通過端口4567瀏覽TProv網站。
如我們所見,我們可以指定Tomcat應用的名字和指向Tocat WAR文件的URL。從https://gwt-examples.googlecode.com/files/Calendar.war下載示例日歷應用程序,并將其稱為Calendar。
單擊Submit按鈕下載WAR文件,將其放入卷里,運行Tomcat服務器,加載卷里的WAR文件。可以點擊List instances(展示實例)鏈接來查看實例的運行狀態。
這展示了:
** 容器的ID。
** 容器的內部IP地址。
** 服務映射到的接口和端口。
利用這些信息我們可以通過瀏覽映射的端口來查看應用的運行狀態,還可以使用Delete?(是否刪除)復選框來刪除正在運行的實例。
可以查看TProv應用的源代碼(https://github.com/jamtur01/dockerbook-code/blob/master/code/6/tomcat/tprov/lib/tprov/app.rb),看看程序是如何實現這些功能的。這個應用很簡單,只是通過shell執行docker程序,再捕獲輸出,來運行或者刪除容器。
可以隨意使用TProv代碼,在之上做擴展,或者干脆重新寫一份自己的代碼。本文應用主要用于展示,使用Docker構建一個應用程序部署管道是很容易的事。
警告:TProv應用確實太簡單了,缺少某些錯誤處理和測試。這個應用的開發過程很快:只寫了一個小時,用于展示再構建應用和服務時Docker是一個多么強大的工具。如果你在這個應用里找到了bug(或者想把它寫得更好),可以通過再https://github.com/jamtur01/dockerbook-code提交issue或者PR來告訴我。
3. 多容器的應用棧
在最后一個服務應用得實例中我們把一個使用Express框架的,帶有Redis后端的Node.js應用完全Docker化了。這里要繼續演示如何把之前兩章學到的Docker特性結合起來使用,包括鏈接和卷。
在這個例子中,我們會構建一系列的鏡像來支持部署多容器的應用。
** 一個Node容器,用來服務于Node應用,這個容器會鏈接到。
** 一個Redis主容器,用于保存和集群化應用狀態,這個容器會鏈接到。
** 兩個Redis副本容器,用于集群化應用狀態。
** 一個日志容器,用于捕獲應用日志。
我們的Node應用程序會運行在一個容器中,它后面會有一個配置"主-副本"模式運行在多個容器中的Redis集群。
3.1 Node.js鏡像
先從構建一個安裝了Node.js的鏡像開始,這個鏡像有Express應用和相應的必要的軟件包,代碼如下:
** 創建Node.js Dockerfile $ mkdir nodejs $ cd nodejs $ mkdir -p nodeapp $ cd nodeapp $ wget https://raw.githubusercontent.com/jamtur01/dockerbook-code/master/code/6/node/nodejs/nodeapp/package.json $ wget https://raw.githubusercontent.com/jamtur01/dockerbook-code/master/code/6/node/nodejs/nodeapp/server.js $ cd .. $ vi Dockerfile我們已經創建了一個叫nodejs的新目錄,然后創建了子目錄nodeapp來保存應用代碼。然后我們進入這個目錄,并下載了Node.js應用的源代碼。
注意:可以從本書官網(http://docker.com/code/6/node/)或者GitHub倉庫(https://github.com/jamtur01/dockerbook-code/tree/master/code/6/node/)下載Node應用的源代碼。
最后我們回到了nodejs目錄。現在我們來看看這個Dockerfile的內容,代碼清單:
** Node.js鏡像 FROM ubuntu:14.04 MAINTAINER James Turnbull <james@example.com> ENV REFRESHED_AT 2014-06-01RUN apt-get -yqq update RUN apt-get -yqq install nodejs npm RUN ln -s /usr/bin/nodejs /usr/bin/node RUN mkdir -p /var/log/nodeappADD nodeapp /opt/nodeapp/WORKDIR /opt/nodeapp RUN npm installVOLUME [ "/var/log/nodeapp" ]EXPOSE 3000ENTRYPOINT [ "nodejs", "server.js" ]Node.js鏡像安裝了Node,然后我們用了一個簡單的技巧把二進制文件nodejs鏈接到node,解決了Ubuntu上原有的一些無法向后兼容的問題。
然后我們將nodeapp的源代碼通過ADD指令添加到/opt/nodeapp目錄。這個Node.js應用是一個簡單的Express服務器,包括一個存放應用依賴信息的package.json文件和包含實際應用代碼的server.js文件。
接著我們將工作目錄設置為/opt/nodeapp,并且安裝了Node應用的必要軟件包,還創建了用于存放Node應用日志的卷/var/log/nodeapp。
最后我們公開了3000端口,并使用了ENTRYPOINT指定了運行Node應用的命令nodejs server.js。
現在我們來構建鏡像,代碼清單:
** 構建Node.js鏡像 $ sudo docker build -t jamtur01/nodejs .3.2 Redis基礎鏡像
現在我們繼續構建第一個Redis鏡像:安裝Redis的基礎鏡像(代碼清單如下:)。然后我們會使用這個鏡像構建Redis主鏡像和副本鏡像。
** 創建Redis基礎鏡像的Dockerfile $ mkdir redis_base $ cd redis_base $ vi Dockerfile讓我們來看看這個Dockerfile的內容,代碼清單:
** 基礎Redis鏡像 FROM ubuntu:14.04 MAINTAINER James Turnbull <james@example.com> ENV REFRESHED_AT 2014-06-01RUN apt-get -yqq update RUN apt-get install -yqq software-properties-common python-software-properties RUN add-apt-repository ppa:chris-lea/redis-server RUN apt-get -yqq update RUN apt-get -yqq install redis-server redis-toolsVOLUME [ "/var/lib/redis", "/var/log/redis" ]EXPOSE 6379 CMD []這個Redis基礎鏡像安裝了最新版本的Redis(從PPA庫安裝,而不是使用Ubuntu自帶的較老的Redis包),指定了兩個VOLUME(/var/lib/redis和/var/log/redis),公開了Redis的默認端口6379。因為不會執行這個鏡像,所以沒有包含ENTRYPOINT或者CMD指令。然后我們將只是基于這個鏡像構建別的鏡像。
現在我們來構建Redis基礎鏡像,代碼清單:
** 構建Redis鏡像 $ sudo docker build -t jamtur01/redis .3.3 Redis主鏡像
我們繼續第一個Redis鏡像,即Redis主服務器,代碼清單:
** 創建Redis主服務器的Dockerfile $ mkdir redis_primary $ cd redis_primary $ vi Dockerfile我們來看看這個Dockerfile的內容,代碼清單:
** Redis主鏡像 FROM jamtur01/redis MAINTAINER James Turnbull <james@example.com> ENV REFRESHED_AT 2014-06-01ENTRYPOINT [ "redis-server", "--logfile /var/log/redis/redis-server.log" ]Redis主鏡像基于之前的jamtur01/redis鏡像,并通過ENTRYPOINT指令指定了Redis服務啟動命令,Redis服務的日志文件保存到/var/log/redis/redis-server.log。
現在我們來看看構建Redis主鏡像,代碼清單如下:
** 構建Redis主鏡像 $ sudo docker build -t jamtur01/redis_primary .3.4 Redis副本鏡像
為了配合Redis主鏡像,我們會創建Redis副本鏡像,保證為Node.js應用提供Redis服務的冗余度,代碼清單:
** 創建Redis副本鏡像的Dockerfile $ mkdir redis_replica $ cd redis_replica $ touch Dockerfile現在我們來看看對應的Dockerfile,代碼清單:
** Redis副本鏡像 FROM jamtur01/redis MAINTAINER James Turnbull <james@example.com> ENV REFRESHED_AT 2014-06-01ENTRYPOINT [ "redis-server", "--logfile /var/log/redis/redis-replica.log", "--slaveof redis_primary 6379" ]Redis副本鏡像也是基于jamtur01/redis構建的,并且通過ENTRYPOINT指令指定了運行Redis服務器的命令,設置了日志文件和slaveof選項。這就把Redis配置為主-副本模式,從這個鏡像構建的任何容器都會將redis_primary主機的Redis作為主服務,鏈接其6379端口,成為其對應的副本服務器。
現在我們來構建Redis副本鏡像,代碼清單:
** 構建Redis副本鏡像 $ sudo docker build -t jamtur01/redis_replica .3.5 創建Redis后端集群
現在我們已經有了Redis主鏡像和副本鏡像,已經可以構建我們自己的Redis復制環境了。首先我們創建了一個用來運行我們的Express應用程序的網絡,我們稱其為express,代碼清單:
** 創建express網絡 $ sudo docker network create express ...現在讓我們在這個網絡中運行Redis主容器,代碼清單:
** 運行Redis主容器 $ sudo docker run -d -h redis-primary \ --net express --name redis_primary jamtur01/redis_primary ...這里使用docker run命令從jamtur01/redis_primary鏡像創建了一個容器。這里使用了一個以前沒有見過的新標志-h,這個標志用來設置容器的主機名。這會覆蓋默認的行為(默認將容器的主機名設置為容器ID)并允許我們指定自己的主機名。使用這個標志可以確保容器使用redis_primary作為主機名,并被本地的DNS服務正確解析。
我們已經指定了--name標志,確保容器的名字是redis_primary,我們還指定了--net標志,確保該容器在express網絡中運行。稍后我們會看到,我們將使用這個網絡來確保容器的連通性。
讓我們使用docker logs命令來查看Redis主容器的運行狀況,代碼清單:
** Redis主容器的日志 $ sudo docker logs redis_primary什么日志都沒有?這是怎么回事?原來Redis服務會將日志記錄到一個文件而不是記錄標準輸出,所以使用Docker查看不到任何日志。那怎么知道Redis服務器的運行情況呢?為了做到這一點,可以使用之前創建的/var/log/redis卷?,F在我們來看看這個卷,讀取一些日志文件的內容,代碼清單:
** 讀取Redis主日志 $ sudo docker run -ti --rm --volumes-from redis_primary \ubuntu cat /var/log/redis/redis-server.log ...這里以交互方式運行了另一個容器。這個命令指定了--rm標志,它會在進程運行完后自動刪除容器。我們還指定了--volumes-from標志,告訴它執行cat /var/log/redis/redis-server.log來展示日志文件。這種方法利用率卷的優點,可以直接從redis_primary容器掛載/var/log/redis目錄并讀取里面的日志文件。一會兒我們將會看到更多使用這個命令的情況。
查看Redis日志,可以看到一些常規警告,不過一切看上去都沒什么問題。Redis服務器已經準備好從6379端口接收數據了。
那么下一步,我們創建一個Redis副本容器,代碼清單:
** 運行第一個Redis副本容器 $ sudo docker run -d -h redis_replical \--name redis_replical \--net express \jamtur01/redis_replica ...這里我們運行了另一個容器:這個容器來自jamtur01/redis_replica鏡像。和之前一樣,命令里指定了主機名(通過-h標志)和容器名(通過--name標志)都是redis_replical。我們還使用了--net標志在express網絡中運行Redis副本容器。
提示:在Docker 1.9之前的版本中,不能使用Docker Networking,只能使用Docker鏈接來連接Redis主容器和副本容器。
現在我們來檢查一下這個新容器的日志,代碼清單:
** 讀取Redis副本容器的日志 $ sudo docker run -ti --rm --volumes-from redis_replical \ubuntu cat /var/log/redis/redis-replica.log ....這里通過交互的方式運行了一個新容器來查詢日志。和之前一樣,我們又使用了--rm標志,它在命令執行完畢后自動刪除容器。我們還指定了--volumes-from標志,掛載了redis_replical容器的所有卷。然后我們指定了ubuntu基礎鏡像,并讓它cat日志文件/var/log/ redis/redis-replica.log。
到這里我們已經成功啟動了redis_primary和redis_replical容器,并讓這兩個容器進行主從復制。
現在我們來加入另一個副本容器redis_replica2,確保萬無一失,代碼清單:
** 運行第二個Redis副本容器 $ sudo docker run -d -h redis_replica2 \--name redis_replica2 \--net express \jamtur01/redis_replica ...我們來看看新容器的日志,代碼清單:
** 第二個Redis副本容器的日志 $ sudo docker run -ti --rm --volumes-from redis_replica2 ubuntu \cat /var/log/redis/redis-replica.log ...現在可以確保Redis服務萬無一失了!
3.6 創建Node容器
現在我們已經讓Redis集群運行了,我們可以為啟動Node.js應用啟動一個容器,代碼清單如下:
** 運行Node.js容器 $ sudo docker run -d \--name nodeapp -p 3000:3000 \--net express \jamtur01/nodejs ....我們來看看新容器的日志,代碼清單:
** nodeapp容器的控制日志 $ sudo docker logs nodeapp Listening on port 3000從這個日志可以看到Node應用程序監聽了3000端口。 ** Node應用的輸出 {"status":"ok" }這個輸出表明應用正在工作。瀏覽器的會話狀態會先被記錄到Redis主容器redis_primary,然后復制到兩個Redis副本容器redis_replical和redis_replica2。
3.7 捕獲應用日志
現在應用已經可以運行了,需要把這個應用放到生產環境中。在生產環境里需要確??梢圆东@日志并將日志保存到日志服務器。我們將使用Logstash(http://logstash.net/)來完成這件事。我們先來創建一個Logstash鏡像,代碼清單:
現在我們來看看這個Dockerfile的內容,代碼清單:
** Logstash鏡像 FROM ubuntu:14.04 MAINTAINER James Turnbull <james@example.com> ENV REFRESHED_AT 2014-06-01RUN apt-get -yqq update RUN apt-get -yqq install wget RUN wget -O - http://packages.elasticsearch.org/GPG-KEY-elasticsearch | apt-key and - RUN echo 'deb http://packages.elasticsearch.org/logstash/1.4/debian stable main' > /etc/apt/sources.list.d/logstash.list RUN apt-get -yqq update RUN apt-get -yqq install logstashADD logstash.conf /etc/WORKDIR /opt/logstashENTRYPOINT [ "/bin/logstash" ] CMD [ "--config=/etc/logstash.conf" ]我們已經創建了鏡像并安裝了Logstash,然后將logstash.conf文件使用ADD指令添加到/etc/目錄?,F在我們來看看Logstash.conf文件的內容,代碼清單:
** Logstash配置文件 input {file {type => "syslog"path => ["/var/log/nodeapp/nodeapp.log", "/var/log/redis/redis-server.log"]} }output {stdout {codec => rubydebug} }這個Logstash配置很簡單,它監控兩個文件,即/var/log/nodeapp/nodeapp.log和/var/log/redis/redis-server.log。Logstash會一直監視這兩個文件,將其中斷的內容發送給Logstash。配置文件的第二部分是output部分,接受所有Logstash輸入的內容并將其輸出到標準輸出上。現實中,一般會將Logstash配置為輸出到Elasticsearch集群或者其他的目的地,不過這里只使用標準輸出做演示,所以忽略了現實的細節。
注意:如果不太了解Logstash,想要深入學習可以參考作者的書(http://www.logstashbook.com)或者Logstash文檔(http://logstash.net)。
我們指定了工作目錄為/opt/logstash。最后,我們指定了ENTRYPOINT為/bin/logstash,并且指定了CMD為--config=/etc/logstash.conf。這樣容器啟動時會啟動Logstash并加載/etc/logstash.conf配置文件。
現在我們來構建Logstash鏡像,代碼清單:
** 構建Logstash鏡像 $ sudo docker build -t jamtur01/logstash .構建好鏡像后,可以從這個鏡像啟動一個容器,代碼清單:
** 啟動Logstash容器 $ sudo docker run -d --name logstash \--volumes-from redis_primary \--volumes-from nodeapp \jamtur01/logstash我們成功地啟動了一個名為logstash的新容器,并指定了兩次--volumes-from標志,分別掛載了redis_primary和nodeapp容器的卷,這樣就可以訪問Redis和Node的日志文件了。任何加到這些日志文件里的內容都會反映在logstash容器的卷里,并傳給Logstash做后續處理。
現在我們使用-f標志來查看logstash容器的日志,代碼清單:
** logstash容器的日志 $ sudo docker logs -f logstash ...現在再在瀏覽器里刷新Web應用,產生一個新的日志。這樣應該能在logstash容器的輸出中看到這個事件,代碼清單:
** Logstash中的Node事件 {。。。 }現在Node和Redis容器都將日志輸出到了Logstash。在生產環境中,這些事件會發生到Logstash服務器并存儲到Elasticsearch里。如果要加入新的Redis副本容器或者其他組件,也可以容易地將其日志輸出到日志容器里。
注意:如果需要,也可以通過卷對Redis做備份。
3.8 Node程序棧的小結
現在我們已經演示過了如何使用多個容器組成的應用程序棧,演示了如何使用Docker鏈接來將應用容器連在一起,還演示了如何使用Docker卷來管理應用中的各種數據。這些技術可以很容易地用來構建更加復雜地應用程序和架構。
4. 不使用SSH管理Docker容器
最后,在結束關于使用Docker運行服務的話題之前,了解一些管理Docker鏈接來將應用容器連在一起,還演示了如何使用Docker卷來管理應用中的各種數據。這些技術可以很容易地用來構建更加復雜地應用程序和架構。
傳統上講,通過SSH登入運行環境或者虛擬機里來管理服務。在Docker的世界里,大部分容器都只運行一個進程,所以不能使用這種訪問方法。不過就像之前多次看到的,其實不需要這種訪問:可以使用卷或者鏈接完成大部分同樣的管理操作。比如說,如果服務通過某個網絡接口做管理,就可以在啟動容器時公開這個接口;如果服務通過Unix套接字(socket)來管理,就可以通過卷公開這個套接字。如果需要給容器發送信號,就可以像代碼清單那樣使用docker kill命令發送信號。
** 使用docker kill發送信號 $ sudo docker kill -s <signal> <container>這個操作會發送指定的信號(如HUP信號)給容器,而不是殺掉容器。
然而,有時候確實需要登入容器。即便如此,也不需要在容器里執行SSH服務或者打開任何不必要的訪問。需要登入容器時,可以使用一個叫nsenter的小工具。
注意:nsenter一般適用于Docker 1.2或者更早的版本呢。docker exec命令是在Docker 1.3中引入的,替換了它的大部分功能。
工具nsenter讓我們可以進入Docker用來構成容器的內核命名空間。從技術上說,這個工具可以進入一個已經存在的命名空間,或者在新的一組命名空間里執行一個進程。簡單來說,使用nsenter可以進入一個已經存在的容器的shell,即便這個容器沒有運行SSH或者任何類似目的的守護進程??梢酝ㄟ^Docker容器安裝nsenter,代碼清單:
** 安裝nsenter $ sudo docker run -v /usr/local/bin:/target jpetazzo/nsenter這會把nsenter安裝到/usr/loca/bin目錄下,然后立刻就可以使用這個命令。
提示:工具nsenter也可能由所使用的Linux發行版(在util-linux包里)提供。
為了使用nsenter,首先要拿到要進入的容器的進程ID(PID)??梢允褂胐ocker inspect命令獲取PID,代碼清單如下:
** 獲取容器的進程ID PID=$(sudo docker inspect --format '{{.State.Pid}}' <container>)然后就可以進入容器,代碼清單如下:
** 使用nsenter進入容器 $ sudo nsenter --target $PID --mount --uts --ipc --net --pid這會在容器里啟動一個shell,而不需要SSH或者其他類似的守護進程或者進程。我們還可以將想在容器內執行的命令添加在nsenter命令行的后面,代碼清單:
** 使用nsenter在容器內執行命令 $ sudo nsenter --target $PID --mount --uts --ipc --net --pid ls ...這會在目標容器內執行ls命令。
總結
以上是生活随笔為你收集整理的使用Docker构建服务(6)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Illumina输出文件详解
- 下一篇: 加拿大曼尼托巴大学计算机专业几年,202