註:本文首發於CSDN,轉載請標明出處。
【編者按】在「漫步雲端:CoreOS實踐指南」系列的前幾篇文章中,ThoughtWorks的軟體工程師林帆主要介紹了CoreOS及其相關組件和使用。說到CoreOS,不得不提Docker。當Docker還名不見經傳的時候,CoreOS創始人Alex就憑著敏銳直覺,預見了這個項目的價值,將Docker做為了這個系統支持的第一套應用程式隔離方案。本文將主要介紹在具體的場景下,如何在CoreOS中恰當的管理Docker容器。
作者簡介:
林帆,生在80後尾巴的IT攻城獅,ThoughtWorks成都辦公室CloudOps小組成員,平時喜歡在業餘時間研究DevOps相關的應用,目前在備考AWS認證和推廣Docker相關技術。
這次的主角終於輪到了大鯨魚Docker。不曉得有多少人是因為Docker認識了CoreOS的,至少它在社區的知名度事實上高於CoreOS項目本身。這篇文章裡不會對Docker做很深入的講解,而重點放在開始使用Docker所需的基本知識以及在CoreOS中使用Docker託管服務的推薦實踐方法。
雷教主說,「站在風口上,豬也能飛起來」。Docker正是借著雲計算的風飛上了天。伴隨著Docker和應用容器的興起,拉動了一批PaaS產品的發展,而CoreOS也借了這股勁兒賺足了人氣,進行得風生水起。同時CoreOS的成熟也在回饋Docker社區,為社區帶來了例如Etcd、Deis(私有PaaS雲平臺,目前是基於CoreOS構建的)等許多新的活力。
說起CoreOS與Docker的淵源,確有一段歷史了。故事大致是這樣開始的,2013年2月,美國的dotCloud公司發布了一款新型的Linux容器軟體Docker,並建立了一個網站發布它的首個演示版本( 見Docker第一篇官方博客)。而幾乎同時,2013年3月,美國加州,年輕的帥小夥Alex Polvi正在自己的車庫開始他的 第二次創業。此前,他的首個創業公司Cloudkick賣給了雲計算巨頭Rackspcace(就是OpenStack的東家)。
有了第一桶金的Alex這次準備幹一票大的,他計劃開發一個足以顛覆傳統的伺服器系統的Linux發行版。為了提供能夠從任意作業系統版本穩定無縫地升級到最新版系統的能力,Alex急需解決應用程式與作業系統之間的耦合問題。因此,當時還名不見經傳的Docker容器引起了他的注意,憑著敏銳直覺,Alex預見了這個項目的價值,當仁不讓地將Docker做為了這個系統支持的第一套應用程式隔離方案。不久以後,他們成立了以自己的系統發行版命名的組織:CoreOS。事實證明,採用Docker這個決定,後來很大程度上成就了CoreOS的生態系統。
現在看來,CoreOS已經不是唯一預裝了Docker的作業系統了,但它是第一個,也是目前做得最成功的一個。RedHat和Canonical(Ubuntu的母公司)隨其後也分別推出了自己的預裝Docker的系統發行版,但知悉者寥寥,並沒有做成氣候。其項目發起時間見下圖(出自成都ThoughtWorks技術雷達分享活動),Atomic和Ubuntu Core Snappy分別是RedHat和Canonical公司推出的預裝Docker的作業系統,目標也都是直指伺服器集群和容器化部署。
「應用容器」現在對許多人已經並不陌生了。但它在伺服器的系統上還不是那麼普及,至少與你手上的智慧型手機系統相比。至今在伺服器系統上流行的安裝軟體方式依然是編譯原始碼、手工的安裝包或各種包管理工具,雖然包管理工具的出現解決了應用軟體安裝、卸載以及自身依賴等諸多問題,卻無法很好的解決軟體之間的依賴衝突。而早在Docker誕生以前,「沙盒」的概念已經被普遍使用在Android、iOS等主流的手機系統中了。通過沙盒的隔離,應用軟體將自己所有的依賴與應用本身打包在一起,並通過SDK API提供的可控的方式訪問作業系統,軟體與系統的耦合度大大降低。這樣帶來的直接好處是,軟體之間的依賴衝突得到了很好的解決,移除一個應用軟體一般只需要很短的幾秒鐘並且徹底無痕,軟體訪問系統的安全性也更加可控。
事實上,Android實現沙盒同樣的基於Linux內核的cgroup和namespace機制用於限制和隔離資源的使用,所使用的技術與Docker如出一轍。這些早在Linux 2.6.x版本就已經加入了的新特性,已經通過了較長時間的檢驗,被證實是可行並且可靠的。
這篇文章裡不會專門介紹Docker的使用,而是關注在具體的場景下,如何在CoreOS中恰當的管理Docker容器。了解過Docker在CoreOS生態系統中的角色後,下面通過在兩個容器中分別運行NodeJS和MongoDB的例子說明如何在CoreOS中通過Systemd管理服務,並在此基礎上快速瀏覽一些基本的Docker命令。
服務鏡像有一些可以是現成的標準服務的鏡像,例如MongoDB服務。另一些則需要經過用戶定製,製作Docker鏡像文件一般可以通過Dockerfile或現有容器實例生成兩種方法。前者是比較推薦的做法,但需學習Dockerfile的寫法,已經超出了這個系列的範圍。後者相對簡單,但不利於後期的鏡像維護管理,這裡僅僅作為演示目的,因此採用這種方法。
一、拉取基礎鏡像
每一個具體的容器實際上是運行在虛擬出來的獨立空間裡面的,它被設計成只能夠訪問到存在於同一個虛擬空間下面的其他文件。因此為了使應用能夠使用基本的運行時依賴,還需要將一些Linux的命令和配置文件也打包放到虛擬空間裡,這種打包好的依賴文件集合就是鏡像。
操作 docker 的方式與 systemctl、etcdctl 類似,需要由一個二級命令共同組成一個完整的命令。通過
docker pull命令可以指定的網絡地址拉取鏡像到本地(如果指定的是名稱而不是網絡地址,則會在docker官方的鏡像倉庫裡面搜索,比如下面的兩個例子)。
$ docker pull node:latest
...
Status: Downloaded newer image fornode:latest
$ docker pull mongo:latest
...
Status: Downloaded newer image formongo:latest
鏡像是按照「地址/鏡像名:版本標籤」格式命名的,其中鏡像名是必須的,如果地址部分為空則默認為官方倉庫地址。如果版本標籤部分為空,對於較新的Docker版本(大約1.3.x以後),會僅僅下載標籤為latest的版本,而較早版本的Docker則會下載指定鏡像的所有版本,常常會因此意外下載許多不需要的鏡像版本。
在一大段輸出以後,若一切順利(事實是,在國內可能不會太順利),本地的Docker已經可以直接使用這兩個預裝了NodeJS和MongoDB的鏡像了。可以通過 docker images 命令驗證。
$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
node latest 61afc26cd88e 3 days ago 696.2 MB
mongo latest 59b3d123f9b8 6 days ago 392.4 MB
...
在國內的一些地區,拉取官方鏡像倉庫的鏡像可能會失敗(或許是大名鼎鼎的某防火牆的功勞)。此時可以採用國內的第三方開源鏡像倉庫,比如DockerPool或Docker.cn提供的鏡像文件。前者需要配置本地的SSL證書,否則會遇到「Error:Invalid registry endpoint」錯誤,略微麻煩。後者可以直接使用:
docker pull docker.cn/docker/node:latest
docker pull docker.cn/docker/mongo:latest
二、製作定製鏡像
MongoDB可以直接使用官方的Docker鏡像。而NodeJs的容器還需要些許定製,將應由部署到容器中然後生成新的鏡像。再次說明,製作鏡像的最佳途徑是寫一個Dockerfile,實現基礎設施可視化。以下通過修改現有鏡像的方法一般只用於演示目的。
接下來我們要分別啟動MongoDB和NodeJs的容器實例,並將MongoDB的埠暴露到NodeJs的容器中。
首先啟動一個MongoDB容器實例,命名為mongo-ins。啟動容器的命令是 docker run,除了運行配置參數如 --name、--port 等,這個命令的最後兩個參數分別是實例使用的鏡像名字,和實例本身需要運行的命令。有的容器已經配置好了默認的運行程序,此時後面的一個參數可以省略,比如下面的例子。
參數 -d 表示運行後直接進入後臺,屏幕上回顯的一串輸出是新啟動容器實例的ID。
然後啟動一個NodeJs容器實例,使用官方的node鏡像作為基礎鏡像,並將它與 mongo-ins 實例建立「連接」。這個容器實例命名為node-app。
$ docker run --name node-app -p 3000 --linkmongo-ins:mongo -it node /bin/bash
root@e73e7d7836a6:/# <—已經進入容器中的Bash>
-it 實際上是 -i -t 的簡便寫法,表示啟用交互式模式和啟用顯示終端,這樣我們可以進入容器中做一些手工操作。而參數 --link 用來將兩個容器進行關聯,關於Docker Link的用法可以參考Docker的相關文檔。簡單來說,Link的參數 mongo-ins:mongo表示將容器mongo-ins 引入到正在建立的容器鏡像中,並將其稱為 mongo。這樣做的結果是,在新建的node-app 容器實例中,能夠訪問到兩個全局環境變量: $MONGO_PORT_27017_TCP_ADDR 和 $MONGO_PORT_27017_TCP_PORT,分別是用來訪問 MongoDB 的 IP 地址和埠。
作為演示,我們將在容器中部署一個從Github獲取的簡單示例。
$ git clonehttps://github.com/ijason/NodeJS-Sample-App.git
$ cd /NodeJS-Sample-App/EmployeeDB
$ sed -i -e"s/27017/process.env.MONGO_PORT_27017_TCP_PORT/" -e"s/'localhost'/process.env.MONGO_PORT_27017_TCP_ADDR/" app.js
$ exit
上面的第三條命令將原本容器中指定的 MongoDB 位置改成了從另一個容器中暴露的IP位址和埠。至此這個node-app容器已經部署好了一個名為 Employees 的示例應用,接下來將它生成鏡像並放到集群的每個節點上。
三、生成並提交鏡像
為了在集群裡對容器中的服務提供橫向擴展能力,需要將定製好的容器在集群的所有節點共享。
首先需要一個存放共享鏡像的地方,在企業環境可以使用私有的鏡像倉庫,但為了演示簡便起見,我們直接使用Docker的公共倉庫。首先需要在Docker Hub註冊一個用戶,然後使用 docker login 命令登陸到倉庫伺服器。
$ docker login
Username: linfan
Password:
Email: linfan@******.com
Login Succeeded
然後我們需要將本地修改過的容器使用 docker commit 命令生成一個本地的鏡像。注意,由於之後需要將鏡像提交至Docker Hub,這裡鏡像的名字必須以自己的Docker Hub用戶名作為前綴,否則在後面的 push 時候會遇到 403 「Access Denied: Not allowed tocreate Repo at given location」 錯誤。例如名為 linfan/employees。
$ docker commit node-app linfan/employees
a4281aa8baf9aee1173509b30b26b17fd1bb2de62d4d90fa31b86779dd15109b
$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
linfan/employees latest a4281aa8baf9 14 seconds ago 696.2 MB
最後,使用 dockerpush 命令將這個準備好的鏡像提交到DockerHub倉庫中。
$ docker push linfan/employees
The push refers to a repository[linfan/employees] (len: 1)
Sending image list
...
Pushing tag for rev [5577d6743652] on{https://cdn-registry-1.docker.io/v1/repositories/linfan/employees/tags/latest}
提交完成後,在其他節點就可以使用 docker pull 命令獲取到這個鏡像了。
注意:嚴格來說,將資料庫服務容器通過Docker Link暴露給應用服務容器的方法並不符合分布式應用的12條準則,因為通過Docker Link連接的兩個容器必須運行在同一個物理主機上,數據與應用不能在集群中分別獨立的部署或橫向擴展。
一、編寫 Unit 文件
有了相應的服務容器後,在CoreOS中正確啟動服務的方法應該是通過Fleet來管理。通過合理使用 Unit 的 X-Fleet 配置,能夠很好的解決容器直接相互依賴的問題。
用 vagrant ssh 進入一個 CoreOS 的 Shell 中,創建以下兩個服務 Unit 文件。
首先是mongo.service
[Unit]
Description=General MongoDB Service
After=docker.service
[Service]
TimeoutStartSec=0
ExecStart=/opt/bin/docker-run.sh --namemongo-ins -d mongo
ExecStop=/usr/bin/docker stop mongo-ins
然後是employees.service,請注意它的 Unit 和 X-Fleet 段的內容。在Unit段指定了這個服務啟動前必須首先啟動 mongo.service 服務,而在 X-Fleet 段指定了自己需要運行在與 mongo.service 相同的服務節點上。
[Unit]
Description=Employee Information ManagementService
After=docker.service
After=mongo.service
[Service]
TimeoutStartSec=0
ExecStart=/opt/bin/docker-run.sh -p3000:3000 --link mongo-ins:mongo -d --name node-app node-app node/NodeJS-Sample-App/EmployeeDB/app.js
ExecStop=/usr/bin/docker stop mongo-ins
[X-Fleet]
X-ConditionMachineOf=mongo.service
上面的兩個 Unit 文件都使用到了一個 /opt/bin/docker-run.sh 腳本,用於替代 docker run 命令。這個腳本需要額外創建並放置到 /opt/bin 目錄下面,其作用是檢測是否已經有一個同名的容器在運行了,如果沒有則執行相應的 docker run 命令,否則直接使用 docker start 命令啟動已經存在的容器。其內容如下:
#!/bin/bash
PARA="${*}"
NAME=$(echo "${PARA}" | grep'\-\-name' | sed 's/.*--name \([^ ]*\).*/\1/g')
if [ "${NAME}" == "" ];then
echo "[ERROR] Must specify a name to the container!";
exit -1;
fi
EXIST=$(sudo docker ps -a | grep"${NAME}[ ]*$")
if [ "${EXIST}" == ""]; then
sudo docker run ${PARA}
else
sudo docker start ${NAME}
fi
二、啟動服務
通過 fleetctl 命令啟動服務,具體的用法在系列前面的內容裡面已經介紹過了。
fleetctl start ./mongo.service
fleetctl start ./employees.service
這裡為了簡便直接用了fleetctl start 命令,更推薦的啟動服務方法請參考系列中關於Fleet的一篇。
到這一步,這個部署在容器中的服務已經可以使用了。從外部訪問伺服器的 3000 埠即可打開下面這個頁面,並向MongoDB服務中的資料庫中添加員工信息了。
最後,再來看一些用於檢測容器運行狀態和日常管理的Docker命令。
一、查看運行日誌
容器通過-d參數進入後臺運行之後,其中服務輸出的日誌內容可以通過 dockerlogs 命令查看到。
$ docker logs mongo-ins
MongoDB starting : pid=1 port=27017dbpath=/data/db 64-bit host=d9bba1bfc8be
...
二、容器實例列表
命令 docker ps 能夠列出所有當前正在運行的容器的基本信息。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d9bba1bfc8be mongo:2 "/entrypoint.sh" 4minutes ago Up 4 minutes 27017/tcp mongo-ins
22de21d77174 node:0 "/bin/bash" 3minutes ago Up 5 minutes node-app
...
三、容器實例詳情
使用 dockerinspect 命令能夠查看到指定一個容器的詳細運行信息。
$ docker inspect mongo-ins
{ ... }
四、備份和還原容器
簡單的提一下,用來將現有的本地鏡像打包備份和還原的命令是 docker save 和 docker load。也可以直接將容器實例打包,相關命令是 docker export 和 docker import,注意 import 之後會將備份的數據恢復成一個新的本地鏡像,而不是容器實例。
這兩個命令的使用可以參考文檔。只額外說明一個問題,既然兩種還原都會將備份的內容還原為容器,為什麼需要兩種還原命令呢?原因在於使用 save 和 export 生成的打包效果是不太一樣的,簡單說就是 export 生成的備份會丟棄所有的鏡像分層結構,而 save 生成的備份不會。鏡像分層結構有利於減少相似鏡像本地存儲所需的空間,細節可參考這篇文章。
以上介紹的這些命令僅僅是Docker強大功能的冰山一角,網絡上已經有許多十分優秀的Docker使用教程,作為學習Docker和應用容器都是極好的途徑。這裡推薦一個Dockerone翻譯的Docker系列文章。
事實上,隨著CoreOS的獨立容器項目 Rocket 的發起,Docker 在未來將不再是 CoreOS 和其他Linux作業系統設計容器方案的唯一選擇。但作為 CoreOS 乃至整個 Linux 生態圈的應用容器服務佼佼者,Docker的王者地位還會持續很長的時間,而CoreOS始終會保持對Docker容器的一流支持(見CoreOS關於Rocket博客中的F&Q)。
正值提筆寫這篇文章的那天,Bing的首頁內容是泰國的曼谷港,這幅畫面與Docker的Logo頗有幾分神似。如此的巧合,使人不由的聯想,這艘萬噸貨輪底下是否也正藏著一隻蓄勢待發的藍鯨呢。
在這一篇內容中,將重點放在了使用Docker容器管理服務的介紹,正如文章中已經指出的,例子中的有些實踐(使用docker commit創建鏡像,以及fleet start直接啟動服務等)並不適合在實際的項目中使用。從下下篇的文章起,我們將講解幾個完整的,符合產品應用的例子。在進入正式的綜合實例前,在下一篇中,會對 Systemd 和 Fleet 使用的 Unit 文件做一個更深入的探索。(作者/林帆 責編/周小璐)
系列連結: 漫步雲端:CoreOS實踐指南(一) CoreOS實踐指南(二):架設CoreOS集群 CoreOS實踐指南(三):系統服務管家Systemd CoreOS實踐指南(四):集群的指揮所Fleet CoreOS實踐指南(五):分布式數據存儲Etcd(上) CoreOS實踐指南(六):分布式數據存儲Etcd(下) 如需要了解更多Docker相關的資訊或是技術文檔可訪問Docker技術社區;如有更多的疑問請在Dcoker技術論壇提出,我們會邀請專家回答。CSDN Docker技術交流QQ群:303806405。