摘要:當Docker還名不見經傳的時候,CoreOS創始人Alex就預見了這個項目的價值,并將其做為CoreOS支持的套應用程序隔離方案。本文將主要介紹在具體的場景下,如何在CoreOS中恰當地管理Docker容器。
在“漫步云端:CoreOS實踐指南”系列的前幾篇文章中,ThoughtWorks的軟件工程師林帆主要介紹了CoreOS及其相關組件和使用。說到CoreOS,不得不提Docker。當Docker還名不見經傳的時候,CoreOS創始人Alex就憑著敏銳直覺,預見了這個項目的價值,將Docker做為了這個系統支持的套應用程序隔離方案。本文將主要介紹在具體的場景下,如何在CoreOS中恰當的管理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版本就已經加入了的新特性,已經通過了較長時間的檢驗,被證實是可行并且可靠的。
當CoreOS 遇見 Docker
這篇文章里不會專門介紹Docker的使用,而是關注在具體的場景下,如何在CoreOS中恰當的管理Docker容器。了解過Docker在CoreOS生態系統中的角色后,下面通過在兩個容器中分別運行NodeJS和MongoDB的例子說明如何在CoreOS中通過Systemd管理服務,并在此基礎上快速瀏覽一些基本的Docker命令。
制作服務的Docker鏡像
服務鏡像有一些可以是現成的標準服務的鏡像,例如MongoDB服務。另一些則需要經過用戶定制,制作Docker鏡像文件一般可以通過Dockerfile或現有容器實例生成兩種方法。前者是比較推薦的做法,但需學習Dockerfile的寫法,已經超出了這個系列的范圍。后者相對簡單,但不利于后期的鏡像維護管理,這里僅僅作為演示目的,因此采用這種方法。
一、拉取基礎鏡像
每一個具體的容器實際上是運行在虛擬出來的獨立空間里面的,它被設計成只能夠訪問到存在于同一個虛擬空間下面的其他文件。因此為了使應用能夠使用基本的運行時依賴,還需要將一些Linux的命令和配置文件也打包放到虛擬空間里,這種打包好的依賴文件集合就是鏡像。
操作 docker 的方式與 systemctl、etcdctl 類似,需要由一個二級命令共同組成一個完整的命令。通過 docker pull 命令可以指定的網絡地址拉取鏡像到本地(如果指定的是名稱而不是網絡地址,則會在docker官方的鏡像倉庫里面搜索,比如下面的兩個例子)。
$ docker pull node:latest
...
Status: Downloaded newer image for node:latest
$ docker pull mongo:latest
...
Status: Downloaded newer image for mongo: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 --link mongo-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 clone https://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 to create 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
后,使用 docker push 命令將這個準備好的鏡像提交到Docker Hub倉庫中。
$ 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連接的兩個容器必須運行在同一個物理主機上,數據與應用不能在集群中分別獨立的部署或橫向擴展。
使用 Fleet 啟動服務容器
一、編寫 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 --name mongo-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 Management Service
After=docker.service
After=mongo.service
[Service]
TimeoutStartSec=0
ExecStart=/opt/bin/docker-run.sh -p 3000: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參數進入后臺運行之后,其中服務輸出的日志內容可以通過 docker logs 命令查看到。
$ docker logs mongo-ins
MongoDB starting : pid=1 port=27017 dbpath=/data/db 64-bit host=d9bba1bfc8be
...
二、容器實例列表
命令 docker ps 能夠列出所有當前正在運行的容器的基本信息。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d9bba1bfc8be mongo:2 "/entrypoint.sh" 4 minutes ago Up 4 minutes 27017/tcp mongo-ins
22de21d77174 node:0 "/bin/bash" 3 minutes ago Up 5 minutes node-app
...
三、容器實例詳情
使用 docker inspect 命令能夠查看到指定一個容器的詳細運行信息。
$ 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 文件做一個更深入的探索。
本文來自:CSDN,(作者/林帆)
本站文章版權歸原作者及原出處所有 。內容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構成任何投資及應用建議。本站是一個個人學習交流的平臺,網站上部分文章為轉載,并不用于任何商業目的,我們已經盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯系我們,我們將根據著作權人的要求,立即更正或者刪除有關內容。本站擁有對此聲明的最終解釋權。