最近接觸PAAS相關的知識,在研發過程當中開始使用Docker搭建了本身完整的開發環境,感受生活在PAAS時代的程序員真是幸福,本文會簡要介紹下Docker是什麼,如何利用Docker來搭建本身的開發環境(本文主要是面向Mac OS X),以及期間所遇到的一些坑和解決方案。(本文會要求你對PAAS、LXC、CGroup、AUFS有必定的瞭解基礎,請自行Google )mysql
大背景–虛擬化技術歷史
計算機虛擬化技術由來已久,從硬件仿真到全虛擬化,再到準虛擬化和操做系統虛擬化,各類技術粉墨登場,種類繁多,說實在的有點眼花繚亂和複雜;但用戶的核心訴求一直是比較簡單的,下降信息技術(IT)的運營成本,提升資源利用率,提升安全性和可靠性等等;雖然說用戶的核心訴求比較簡單,但每一個時代的需求場景倒是不一樣的。在大型機時代,虛擬化技術被用來支持多個用戶可以同時使用大型機,在x86架構時代,隨着企業服務的大規模部署,虛擬化技術主要是用來提升企業資源的利用率,而現現在,隨着雲計算時代的到來,人們對應用的安全性、隔離性愈來愈高,對於部署的標準化以及虛擬機的性能要求愈來愈高。現現在,一種叫Linux容器的虛擬化技術逐漸獲得普遍的應用,它的優勢有許多,本文不一一贅述,有太多的文章能夠參考。linux
什麼是Docker?
docker的英文本意是碼頭工人,也就是搬運工,這種搬運工搬運的是集裝箱(Container),集裝箱裏面裝的可不是商品貨物,而是任意類型的App,Docker把App(叫Payload)裝在Container內,經過Linux Container技術的包裝將App變成一種標準化的、可移植的、自管理的組件,這種組件能夠在你的latop上開發、調試、運行,最終很是方便和一致地運行在production環境下。nginx
Docker的核心底層技術是LXC(Linux Container),Docker在其上面加了薄薄的一層,添加了許多有用的功能。這篇stackoverflow上的問題和答案很好地詮釋了Docker和LXC的區別,可以讓你更好的瞭解什麼是Docker, 簡單翻譯下就是如下幾點:git
- Docker提供了一種可移植的配置標準化機制,容許你一致性地在不一樣的機器上運行同一個Container;而LXC自己可能由於不一樣機器的不一樣配置而沒法方便地移植運行;
- Docker以App爲中心,爲應用的部署作了不少優化,而LXC的幫助腳本主要是聚焦於如何機器啓動地更快和耗更少的內存;
- Docker爲App提供了一種自動化構建機制(Dockerfile),包括打包,基礎設施依賴管理和安裝等等;
- Docker提供了一種相似git的Container版本化的機制,容許你對你建立過的容器進行版本管理,依靠這種機制,你還能夠下載別人建立的Container,甚至像git那樣進行合併;
- Docker Container是可重用的,依賴於版本化機制,你很容易重用別人的Container(叫Image),做爲基礎版本進行擴展;
- Docker Container是可共享的,有點相似github同樣,Docker有本身的INDEX,你能夠建立本身的Docker用戶並上傳和下載Docker Image;
- Docker提供了不少的工具鏈,造成了一個生態系統;這些工具的目標是自動化、個性化和集成化,包括對PAAS平臺的支持等;
那麼Docker有什麼用呢?對於運維來講,Docker提供了一種可移植的標準化部署過程,使得規模化、自動化、異構化的部署成爲可能甚至是輕鬆簡單的事情;而對於開發者來講,Docker提供了一種開發環境的管理方法,包括映像、構建、共享等功能,然後者是本文的主題。程序員
Docker的安裝和構成
Docker官方自己提供了很是具體的安裝教程,這裏不說具體的安裝過程,請參考Docker安裝(Mac系統),重要的是描述下原理和安裝完成後的結構,好對Docker更好的瞭解。 因爲LXC自己不支持Mac內核,所以須要跑一個VirtualBox虛擬機(TinyCoreLinux)來安裝,幸虧Docker社區提供了一個很是方便的工具boot2docker(其實就是一個VBoxManage的包裝shell腳本),用於安裝Mac下的整個Docker環境。具體的結構以下:github
如圖所示,安裝完成後,具體狀況以下:sql
- 在Mac的home目錄~/.boot2docker下建立了虛擬機所須要的文件,其中boot2docker.iso是虛擬機映像,這是一個由CD-ROM引導的TinyCoreLinux系統;而boot2docker-vm.vmdk文件則是你的虛擬機磁盤,你全部的持久化數據都存放在這裏,包括docker建立的lxc容器等文件。
-
在Mac下,docker被分爲客戶端docker-client和服務端docker-daemon兩部分,若是是在linux(好比ubuntu),實際上則是同一個可執行文件同時充當客戶端和服務端。docker-daemon能夠監聽unix scoket,也能夠在tcp socket(默認端口爲4234),docker-client會經過一個叫DOCKER_HOST的環境變量讀取服務地址和端口,所以你應該在你的bash_profile文件裏面添加這麼一行:docker
12export DOCKER_HOST=tcp://127.0.0.1:4243
docker-daemon跑在虛擬機上,這個程序實際上就是接收docker-client發送過來的消息命令,建立、啓動和銷燬lxc容器,以及docker自己的版本管理、映像存儲等等 運行你的第一個docker容器 安裝完成後,就差很少能夠開始建立和運行docker容器了,在這以前,你首先得下載一個Image,什麼是Image?咱們先來了解docker的2個基礎概念:Image和Container。shell
Container和Image 在Docker的世界裏,Image是指一個只讀的層(Layer),這裏的層是AUFS裏的概念,最直觀的方式就是看一下docker官方給出的圖:ubuntu
Docker使用了一種叫AUFS的文件系統,這種文件系統可讓你一層一層地疊加修改你的文件,最底下的文件系統是隻讀的,若是須要修改文件,AUFS會增長一個可寫的層(Layer),這樣有不少好處,例如不一樣的Container能夠共享底層的只讀文件系統(同一個Kernel),使得你能夠跑N多個Container而不至於你的硬盤被擠爆了!這個只讀的層就是Image!而如你所看到的,一個可寫的層就是Container。
那Image和Container的區別是什麼?很簡單,他們的區別僅僅是一個是隻讀的層,一個是可寫的層,你可使用docker commit 命令,將你的Container變成一個Image,也就是提交你所運行的Container的修改內容,變成一個新的只讀的Image,這很是相似於git commit命令,感受真棒!
實際上這就是Docker對Container映像的版本管理基石,AUFS文件系統實在是太美妙了,更多細節能夠參考DotCloud的這篇文章。
運行和退出
在瞭解了Image和Container的概念後,咱們能夠開始下載一個Image,Docker的好處就是提供了一個相似github的Image倉庫管理,你能夠很是方便pull別人的Image下來運行,例如,咱們能夠下載一個ubuntu Image:
1
2
|
docker pull ubuntu:13.10
|
這裏的13.10是一個Tag,相似於git的tag,這裏的tag能夠爲你制定一個ubuntu的版本。下載完成後,執行docker images命令能夠列出你已經下載或者本身構建的image:(請容許我使用可愛的馬賽克 :) )
你能夠看到ubuntu:13.10的大小爲178MB,以及它的IMAGE ID。 如今咱們開始運行一個Container,命令很簡單,例如咱們想運行一個執行Shell終端的Container:
如你看到的,你已經進入到一個Shell裏面,能夠執行你想執行的任何命令,就和在ubuntu裏面同樣,進去後默認是在根目錄/下,能夠看到經典的unix/linux目錄結構,以及你所運行的bash版本等信息。你能夠給你的Container定一個名字,經過–name選項,例如這裏命名了shell,往後你就能夠直接用這個名字引用Contanier。
退出一個Container也很簡單,你直接exit就行了。 其餘更多的命令這裏不作贅述,由於官方的文檔已經很是全面,這裏只是給一個直觀的初步印象。下面進入主題。
利用Docker搭建開發環境
咱們先看看程序員在搭建開發環境時遇到的一些問題:
- 軟件安裝麻煩,好比不少公司都使用redhat,通常開發人員又不給root,安裝一個nginx或者是mysql都得本身下載編譯安裝 權限問題,沒有root,一些軟件沒法運行,例如dnsmasq;
- 沒有root,沒法修改hosts,沒法netstat -nptl,沒法tcpdump,沒法iptable
- 隔離性差,例如不一樣的開發人員若是在同一臺主機環境下共享開發,雖然是用戶隔離,但端口若是不規範可能會衝突;同一個Mysql若是權限管理很差頗有可能誤刪別人的數據
- 可移植性差,例如和生產環境不一致,開發人員之間也沒法共享;更嚴重的狀況是當有新人入職時,一般須要又折騰一遍開發環境,沒法快速搭建
這些問題能夠經過在本地搭建虛擬機來解決,但虛擬機是一個很笨重的解決方案,Docker是一個很是輕量級的方案,並且還擁有虛擬機沒有的一些功能,例如標準化Image,Image共享等,更重要的是,利用Docker,你能夠運行很是多的容器,在你的Mac下搭建一個分佈式的開發環境根本不是什麼大的問題,並且對內存、磁盤和cpu的消耗相比傳統的虛擬機要低許多,這些都要歸功於AUFS和LXC這兩大神奇的技術。
構建基礎Image
想要搭建一個節省磁盤空間和擴展性良好的開發環境,最重要的第一步就是構建一個基礎性的Image,好比你的主要開發語言是Ruby,那麼你確定須要一個已經安裝好如下工具的基礎Image:
- ruby
- bundler
- gem
而後在此基礎上,你能夠擴展這個基礎的Image(下面叫base)爲不一樣的開發環境,例如rails,或者是nats。固然,你的這個base也能夠從別人的Image擴展而來,還記得咱們剛剛pull下來的ubuntu:13.10這個Image嗎?你能夠從這個Image擴展開始構建你的base,如何作呢?Docker提供了一種標準化的DSL方式,你只須要編寫一個Dockerfile,運行docker build指令,就能夠構建你本身的Image,這有點像Makefile和make命令同樣,只是你們要構建的內容和構建語言不一樣。
Dockerfile的語法請參考Dockerfile Reference,這裏給出上面提到的Ruby開發的base Dockerfile示例:
1
2
3
4
5
|
FROM ubuntu:13.10
RUN apt-get update
RUN apt-get install -y ruby ruby-dev gem
RUN gem install bundler
|
這裏只用到了很簡單的2個指令:FROM和RUN,FROM指定了咱們要擴展的Image,RUN指定咱們要運行的命令,這裏是安裝ruby,gem、bundler等軟件。寫好Dockerfile後,運行如下指令就能夠建立你的base image了:
1
2
|
docker build --rm -t dev:base .
|
-t 選項是你要構建的base image的tag,就比如ubuntu:13.10同樣 –rm 選項是告訴Docker在構建完成後刪除臨時的Container,Dockerfile的每一行指令都會建立一個臨時的Container,通常你是不須要這些臨時生成的Container的 如你所想,咱們能夠像運行ubuntu:13.10那樣運行咱們的base了:
1
2
|
docker run -i -t --name ruby dev:base irb
|
這裏咱們使用dev:base這個Image運行了一個irb解釋器(Ruby的交互式解釋器)。 在構建完base以後,你能夠依樣畫葫蘆構建你的rails環境,很簡單,只須要FROM dev:base,而後RUN安裝你的rails組件就能夠了,再也不贅述。最終你可能構建的開發環境是這樣的:
如上圖所示,base和service都是從ubutnu:13.10繼承而來,他們做爲不一樣的基礎開發環境,base是ruby開發環境(也許命名爲dev:ruby更爲合適?),而service是一些基礎數據服務,例如mysql,memcache,我建議將這些第三方組件集中在一個Container中,由於他們的環境不常常修改,能夠做爲一種底層服務Container運行,除非你須要構建分佈式的服務,例如memcache集羣,那能夠繼續拆分。
指定Image入口
當你構建完你的base Image和其餘應用的Image以後,你就能夠啓動這些Image了,還記得前面咱們給出的運行命令嗎?
1
2
|
docker run -i -t --name shell dev:base /bin/bash
|
這裏咱們運行了一個bash,這樣你就能夠在shell裏面執行你所想要執行的任何命令了,可是咱們有時候並不想每次都啓動一個shell,接着再在shell裏面啓動咱們的程序,好比一個mysql,而是想一啓動一個容器,mysql服務就自動運行了,這很簡單,Dockerfile提供了CMD和ENTRYPOINT這2個指令,容許你指定一個Image啓動時的默認命令。CMD和ENTRYPOINT的區別是CMD的參數能夠由docker run指令指定的參數覆蓋,而ENTRYPOINT則不能夠。例如咱們想運行一個memcached服務,能夠這麼寫Dockerfile:
1
2
3
|
FROM ubuntu:13.10
RUN apt-get install -y memcached CMD memcached -u root -p 40000
|
或者能夠這麼寫:
1
2
3
|
FROM ubuntu:13.10
RUN apt-get install -y memcached ENTRYPOINT ["memcached", "-u", "root", "-p", "40000"]
|
注意不要把memcached啓動爲後臺進程,即加上-d選項,不然docker啓動的container會立刻stop掉,這點我也以爲比較意外。 接着咱們build這個Image:
1
2
|
docker build -t dev:memcache .
|
這樣,當你build完你的Image後,你能夠直接將該Image運行爲一個容器,它會自動啓動mysql服務:
1
2
|
docker run --name memcache_service -d dev:memcache
|
注意使用-d (detach) 選項,這樣這個container就會做爲後臺進程運行了,接着你可使用docker ps命令查看是否有在運行。
磁盤映射
大部分時候你會須要把你host主機(宿主)上的目錄映射到Container裏面,這樣你就很是方便地在host主機上編輯代碼,而後直接就能夠在Container裏面運行它們,而不用手動copy到Container裏面再重啓Container。按理將host的目錄映射到guest(指Container)上應該是一件很容易的事情,就好像VMWare那樣,但惋惜的是,因爲Mac上的Docker多了一層虛擬機,所以多了一層周折,你必須先VM上的目錄經過sshfs mount到host(指Mac)上,而後再將你的目錄或文件copy到這個mount的目錄,再將VM上的這個目錄映射到Container裏,聽起來比較拗口,畫個圖會清晰不少。
如上圖所示,VM裏面的/mnt/sda1/dev/目錄(你須要本身建立)經過sshfs命令mount到了host主機(Mac)的~/workspace/dev/目錄 ,而VM裏的/mnt/sda1/dev/目錄又被映射到了Container的/src/目錄下,這樣你就能夠在Container裏面的/src/目錄下訪問你的host文件了。具體如何作呢?首先你須要安裝sshfs命令,而後將VM的password寫到一個文件中,例如~/.boot2docker/b2d-passwd,在用sshfs命令mount起VM的/mnt/sda1/dev目錄:
1
2
3
4
|
brew install sshfs
cat tcuser > ~/.boot2docker/b2d-passwd
sshfs docker@localhost:/mnt/sda1/dev ~/workspace/dev -p 2022 -o reconnect -o password_stdin < ~/.boot2docker/b2d-passwd
|
接着你在run一個Container的時候須要經過-v選項來將/mnt/sda1/dev/映射到/src目錄:
1
2
|
docker run -i -t dev:base -v /mnt/sda1/dev:/src /bin/bash
|
這樣你就能夠在你的Container的/src目錄下看到你host裏的文件了。 磁盤映射還有2個地方須要注意:
- 你的文件其實是存儲在VM裏面的,也就是說你須要將你的目錄或者文件copy到VM裏面,你sshfs以後,就是copy到~/workspace/dev目錄下
- 千萬不要sshfs mount非/mnt/sda1下的目錄,由於VM裏面跑的是TinyCoreLinux,這個OS的rootfs是臨時性的(放在內存的,實際上就是boot2docker.iso文件裏面的一個rootfs),所以其根目錄/下的東西(包括/home)根本不會持久化,只有/mnt/sda1這個目錄下的才能持久化。若是你放在/home目錄下,只要VM一重啓,就會丟失的,/mnt/sda1則不會,實際上就是那個~/.boot2docker-vm.vmdk文件掛載到了/mnt/sda1目錄下
端口映射
和磁盤映射同樣,你有時候會須要將Container的端口映射到host主機上,一樣蛋疼的是,因爲多了一層VM,端口映射也顯得比較麻煩。首先你須要設置VirtualBox的端口映射,而後再將Container的端口映射到你的VM裏面:
具體是這麼作的,經過2條命令:
1
2
3
|
boot2docker ssh -L 8000:localhost:8000
docker run -i -t -p 8000:8000
|
也就是說在docker run的時候經過-p選項指定要映射的端口到VM,而boot2docker ssh命令則是將VM的8000端口映射到了host(Mac)的8000端口,這樣你就能夠經過Mac的localhost:8000訪問Container的8000端口了。 其實,有另外一種解決方案就是你不用映射到host(Mac),而是直接登陸到VM裏面進行訪問就行了,boot2docker ssh就能夠登陸到VM,這樣就相似於你的host是ubuntu,但這種解決方案的問題是這個ubuntu太弱了(TinyCoreLinux),若是你在這個ubuntu裏面開發代碼,或者是運行瀏覽器,是很是蛋疼的事情,關鍵仍是這個ubuntu是每次重啓都會復原的!因此我建議仍是作多一層映射好了。 最後,實際上在VM裏面,你是能夠直接訪問全部的Container的端口的,由於VM到Container的網絡都是橋接的。
其餘的一些坑
在使用的過程當中,還遇到一些很多的坑:
- /etc/hosts文件沒法修改,這樣你就不能本身作域名解析
- VM的系統時間是UTC +0000的,並且貌似沒法修改
- Container的IP沒法指定爲靜態IP,所以每次重啓Container時,IP可能會變化
第1個問題的解決方案是經過安裝dnsmasq軟件來作域名解析:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
# 首先,在你的Container裏面安裝dnsmasq軟件:
apt-get install dnsmasq
# 將如下文本添加到 /etc/dnsmasq.conf文件的最後:
listen-address=127.0.0.1 resolv-file=/etc/resolv.dnsmasq.conf conf-dir=/etc/dnsmasq.d user=root
# 接着在/etc/dnsmasq.d/目錄下新建一個文件,隨意起個名字
vi /etc/dnsmqsq.d/dns.conf
# 指定你要映射的域名,例如google.com,則將下面貼進dns.conf文件
address="/google.com/172.17.0.4"
# 最後退出容器,重啓啓動容器時,經過-dns選項指定域名服務器
docker run -i -t -dns 127.0.0.1 -dns 8.8.8.8 dev:base /bin/bash
# 必定要注意上面添加google的域名服務器8.8.8.8,不然你訪問不了外網
# 進去Container後,啓動dnsmasq,這樣你就可以ping google.com了
/etc/init.d/dnsmasq start
|
第2個問題的解決方案就稍微麻煩些,起碼我沒有找到更好的解決方案,我是將boot2docker.iso文件從新制做一次來解決這個問題的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
# 首先你須要將boot2docker.iso文件mount到一個目錄下
hdiutil mount ~/.boot2docker/boot2docker.iso
# 系統會mount到/Volumes/boot2docker目錄下,而後你最好將這下面的東西copy出來到一個另外的目錄,這樣咱們好製做一張新的ISO
cp -r /Volumes/boot2docker/* ~/tmp/
# 接着咱們修改如下文件
vi ~/tmp/boot/isolinux/isolinux.cfg
# 將其中的如下這行修改:
append loglevel=3 user=docker console=ttyS0 console=tty0 nomodeset norestore base
# 修改成:(其實就是加了tz的啓動參數),而後保存
append tz=CST-8 loglevel=3 user=docker console=ttyS0 console=tty0 nomodeset norestore base
# 接着你必須在ubuntu環境下從新制做ISO文件,你能夠利用docker跑一個ubuntu,哈哈,假設你將boot2docker目錄copy到了ubuntu的/src/目錄下,那麼接着這麼作
# 安裝xorriso命令
apt-get install xorriso
# 構建ISO映射
xorriso -as mkisofs -J -R -V boot2docker -no-emul-boot -boot-load-size 4 -boot-info-table -b boot/isolinux/isolinux.bin -c boot/isolinux/boot.cat -o /boot2docker.iso /src/
# 這樣就生成了/boot2docker.iso文件,最後你就能夠替換到VM的啓動ISO文件了,而後重啓VM了
boot2docker restart
# 最後你必須設置你的VM爲正確的時間,使用date -s 命令,最後用date命令查看,你就能看到CST時區的正確時間了
Sun Mar 30 00:27:13 CST 2014
# 對於你啓動的container,你都必須從新設置TZ環境變量,不然即便VM是CST-8,你的container仍是UCT +00:00的時間
export TZ='CST-8'
|
第三個問題暫時沒法解決(可能須要編輯底層的LXC配置文件)。
docker的限制以及後續的一些想法
docker其實仍是有一些限制的:
- 要求你的環境是Linux的,並且內核必須很新(>= 2.6.27 (29)),這實際上是LXC自己的限制,和docker無關
- docker的Container目前host是不能修改的,固然有解決方案(dnsmasq)
- docker的Container也暫時沒法指定靜態IP
用docker做爲開發環境甚至是生產環境其實還有不少地方值得嘗試:
- 在團隊內部構建本地的倉庫,標準化全部的開發環境,使得團隊的新人能夠快速上手
- 在生產環境部署docker,這實際上是PAAS的虛擬化和自動化的一種方式,利用LXC和Docker可以更便捷地實施PAAS
- 嘗試用docker作分佈式集羣模擬和測試,成本會更加低廉,更加容器維護
參考文章
- Linux虛擬化技術
- 利用Linux容器實現可移植的應用部署
- 如何修改host
- Building a Development With Docker
- boot2docker的build
- PAAS Under the Hood