Docker和Vagrant經常被認為是兩種相互替代的工具,其實它們可以結合使用,構建隔離的、可重復的開發環境。我們將證明該環境可以構建一個Docker容器以便開發Java應用程序,并充分利用Vagrant的強大功能,以解決一些現實當中的實際問題。
2:用Docker和Vagrant構建簡潔高效開發環境">
這篇博客的第一部分探討了開發環境的常見缺陷、簡單Docker環境的構建以及Vagrant+Docker配置具有的優點。但是如果你想就開始使用Docker和Vagrant,不妨直接跳到本文的這個章節:使用Vagrant,讓Docker容器易于移植。
開發環境有什么問題?
要花很長的時間來構建
新的開發人員要花多長時間才能構建好當前項目的開發環境?答案取決于諸多因素(項目時間、從事該項目的開發人員數量等),但至少需要半天時間并不罕見。
嘿!其實應該比這快得多:查看腳本,執行腳本。就是這樣。這兩個步驟應該足以構建你的環境,并準備隨時開發。
它可能與測試環境和生產環境大不一樣
你有沒有因構建的環境在機器上未通過而跳過自動化測試?或者更糟糕的是,即使更改的內容在機器上順利編譯,但是在持續整合(CI)服務器上老是失敗,你有沒有查過問題的根源出在哪里?
任何稍有不同,就會導致意料不到的行為。有的方法可能很簡單,比如試一試框架的上一個版本,或者改用不同的項目。
查明什么導致你的系統出現不同行為是每個開發人員都應該避免的煩人任務。
虛擬機和Docker
因而,開發環境應該具有兩個特點:
隔離:你不希望在測試某個新工具或不同項目時弄得一團糟。
可重復:同一個環境應該在每個團隊成員的機器、持續整合服務器和生產服務器上都一再可復制。
虛擬機環境確保了這些特性,可是典型的虛擬機很耗費資源。開發人員需要每隔幾分鐘編碼/構建/測試,不會接受虛擬化帶來的開銷。
這時候,Docker顯得大有幫助。相比典型的虛擬機,其輕型容器極其快速,而且在開發人員當中極受歡迎。下面是來自Docker博客的一段摘要,解釋了這種成功的原因:
在問世后的頭12個月內,Docker在初創企業和早期采用者當中迅速流行起來,他們重視該平臺的這一功能,即可以將應用程序開發管理的問題與基礎設施提 供、配置和運營的問題分離開來。Docker為這些早期用戶提供了一種新穎的、更迅速的方法,可以構建分布式應用程序,另外提供了一種“編寫一次到處運 行”的部署選擇,部署對象從筆記本電腦、裸機、虛擬機到私有云/公有云,不一而足。
使用Docker來配置的、可重復的開發環境
為了舉例說明,我們將構建一個構建并測試Vert.x HTTP服務器的Docker容器。
Vert.x是一種輕型應用程序框架,鼓勵小型、獨立微服務的架構。微服務“就是一種小巧的獨立式可執行程序,可與其他的獨立式可執行程序進行溝通”(Uncle Bob,http://blog.cleancoder.com/uncle-bob/2014/09/19/MicroServicesAndJars.html)。我們認為,它在Docker容器中再合適不過了,這就是為什么我們在此選擇它作為例子。
要是你之前還沒有安裝過Docker,先安裝它。你可以參閱官方文檔(https://docs.docker.com/installation/),或者使用get docker script(https://get.docker.io)來安裝它。我們假設在本章節中,我們在Linux上運行。即使Docker也可以安裝到Windows和Mac上(借助boot2docker),我們會在下一章中看到如何使用Vagrant來安裝、為什么Vagrant是一種更好的選擇。
[page]Docker文件(Dockerfile)
為了描述容器,我們需要一個Docker文件:
FROM ubuntu:14.04
# 安裝開發工具:jdk和git等
RUN apt-get update
RUN apt-get install -y openjdk-7-jdk git wget
# jdk7是默認JDK
RUN ln -fs /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java /etc/alternatives/java
# 安裝vertx
RUN
mkdir -p /usr/local/vertx && cd /usr/local/vertx &&
wget http://dl.bintray.com/vertx/downloads/vert.x-2.1.2.tar.gz -qO - | tar -xz
# 將vertx添加到路徑
ENV PATH /usr/local/vertx/vert.x-2.1.2/bin:$PATH
RUN mkdir -p /usr/local/src
WORKDIR /usr/local/src
CMD ["bash"]
Docker文件確實簡單直觀,當你需要更深入地挖掘時,有一份出色的在線參考手冊(https://docs.docker.com/reference/builder/)。
FROM ubuntu:14.04定義了我們依賴的基本映像。你可以在docker中心(registry.hub.docker.co)找到Docker基本映像的完整列表。就這個例子而言,我們使用了docker團隊構建Docker所用的基本映像。
后面幾行描述了將應用到基本映像上的改動:
使用apt-get安裝開發工具:openjdk、git、wget
下載和安裝vertx
將vertx二進制文件夾添加到路徑
創建文件夾/usr/local/src,并讓它成為默認的工作目錄
一旦我們拷貝了Docker文件,就可以構建Docker映像:
$ sudo docker build -t=vertxdev .
獲得源代碼
我們剛構建的映像已安裝了git。我們可以用它從Github獲取源代碼:
$ sudo docker run -t --rm -v /src/vertx/:/usr/local/src vertxdev git clone https://github.com/vert-x/vertx-examples.git
請注意:git在容器里面運行,源代碼因而在容器里面傳送(確切的位置是在文件夾/usr/local/src)。為了讓代碼具有持續性,即使在容器停止和刪除之后,我們也可以使用標志-v /src/vertx/:/usr/local/src,將容器的文件夾/usr/local/src綁定掛載到主機文件夾/src/vertx。一旦git clone命令完成了執行,--rm”就銷毀容器。
構建并運行應用程序
由于源代碼已獲取,我們將啟動新的容器,這個容器負責構建并運行vertx示例:HelloWorldServer。要注意:vertx run同時負責vertx應用程序的構建和執行。
$ sudo docker run -d -v /src/vertx/:/usr/local/src -p 8080:8080 vertxdev vertx run vertx-examples/src/raw/java/httphelloworld/HelloWorldServer.java
與前一個容器相反,這個容器在停止后不會被銷毀,暴露端口8080(-p 8080:8080),在后臺運行(-d)。若想看一看vertx run的輸出結果:
$ sudo docker logs
Succeeded in deploying verticle
不妨使用curl,從主機端測試應用程序:
$ curl localhost:8080
Hello World
這個簡單例子應該足以體現出運行Docker容器有多快速。在Docker容器里面運行git clone和vertx run的開銷可忽略不計。
但構建的這個環境很基本。在現實環境中,純Docker配置存在一些不足,而Vagrant就有助于克服這些不足。
[page]Docker + Vagrant
Docker(其實是作為Docker模塊的libcontainer)仍需要Linux內核3.8或更高版本和x86_64架構。這在大大限定了Docker能夠原生運行在其中的環境。
Vagrant是一種開源軟件,它為跨眾多操作系統構建可重復的開發環境提供了一種方法。Vagrant使用提供者(provider)來啟動隔離的虛擬環境。默認的提供者是Virtualbox;自v1.6以來,基于docker的開發環境也得到支持。相比幫助Docker在非Linux平臺上運行的其他工具(比如boot2docker),Vagrant具有一些重大優點:
配置一次,即可到處運行:Vagrant就是原生支持Docker的系統上的Docker封裝器,同時它可啟動“主機虛擬機”,以便在不支持它的系統上運行容器。用戶沒必要操心Docker是不是得到原生支持:同一個配置可適用于每一個操作系統。
Docker主機并不局限于boot2docker(Tiny Core Linux的Virtualbox映像),但Debian、Ubuntu、CoreOS及其他Linux發行版也得到支持。而且可以在比Virtualbox更穩定的虛擬機管理器(比如VMware)上運行。
Vagrant可以編排Docker容器:同時運行多個容器,并且把它們聯系起來。
2:用Docker和Vagrant構建簡潔高效開發環境">
在Linux及其他操作系統上使用Vagrant運行Docker
在下面三個章節中,我們將探討這每一個要點。
使用vagrant,讓Docker容器易于移植
Vagrant可支持Docker,既作為提供者,又作為配置者。但是為了讓它可以在Windows和Mac上自動創建Docker主機虛擬機,應該將它用作提供者。
我們將重復使用我們在上面見到的同一個Docker文件。與上面一樣,我們將運行兩個Docker容器,以便執行git clone和vertx run。不過將改而使用Vagrant命令,而不是Docker命令。
安裝Vagrant(https://www.vagrantup.com/downloads)和Virtualbox(https://www.virtualbox.org/wiki/Downloads),以便能夠運行示例。
Vagrantfile
Vagrantfile描述了Vagrant設備。我們將使用下列內容:
ENV['VAGRANT_DEFAULT_PROVIDER'] = 'docker'
Vagrant.configure("2") do |config|
config.vm.define "vertxdev" do |a|
a.vm.provider "docker" do |d|
d.build_dir = "."
d.build_args = ["-t=vertxdev"]
d.ports = ["8080:8080"]
d.name = "vertxdev"
d.remains_running = true
d.cmd = ["vertx", "run", "vertx-examples/src/raw/java/httphelloworld/HelloWorldServer.java"]
d.volumes = ["/src/vertx/:/usr/local/src"]
end
end
end
ENV'VAGRANT_DEFAULT_PROVIDER' = 'docker' 讓我們不必在每個Vagrant命令中指定提供者是Docker(默認提供者是Virtualbox)。該文件的其余部分擁有Vagrant構建Docker映像和運行容器所用的選項。想了解更多信息,請參閱Vagrantfilehttps://docs.vagrantup.com/v2/vagra...(https://docs.vagrantup.com/v2/vagrantfile/index.html)和Docker提供者(https://docs.vagrantup.com/v2/docker/configuration.html)說明文檔。
獲得源代碼
一旦我們將Vagrantfile拷貝到Dockerfile文件夾中,我們可以運行git clone,以獲取源代碼:
$ vagrant docker-run vertxdev -- git clone https://github.com/vert-x/vertx-examples.git
與之前一樣,git clone完成執行后,容器將被銷毀。請注意:我們還沒有構建映像,Vagrant自動構建了映像。減少了手動步驟。
構建并運行應用程序
我們能夠構建并運行HTTP Hello World服務器:
$ vagrant up
在底層,Vagrant將執行docker run,啟動容器的命令由d.cmd選項指定。
想獲得vertx run命令的輸出結果:
$ vagrant docker-logs
==> vertxdev: Succeeded in deploying verticle
測試
在Linux平臺上,只要運行:
$ curl localhost:8080
Hello World
在Windows和Mac上,端口8080并不從Docker主機虛擬機被轉發到主vagrant主機(不過Docker容器端口被轉發到Docker主機)。因而,我們需要通過ssh進入到Docker主機虛擬機,以便連接至HTTP服務器。不妨檢索Vagrant默認Docker主機的ID:
$ vagrant global-status
id name provider state directory
-------------------------------------------------------------------------------------------------------
c62a174 default virtualbox running /Users/mariolet/.vagrant.d/data/docker-host
一旦檢索到該設備,我們可以測試HTTP服務器了:
[page]你有說過一模一樣嗎?如何定制Docker主機?
在不支持容器的平臺上,默認情況下,Vagrant啟動一個Tiny Core Linux(boot2docker)Docker主機。如果我們的持續整合環境、試運行環境或生產環境不運行boot2docker,我們會在這些環境的配置之間有一個缺口。這實際上是生產環境缺陷的根源,不可能在開發環境中發現。不妨試著解決這個問題。
2:用Docker和Vagrant構建簡潔高效開發環境">
不同環境上的不同Docker主機:實際上違反安全
如上所見,Vagrant的主要便利之一是,它讓我們可以指定自定義的Docker主機。換句話說,我們并不被boot2docker和Tiny Core Linux所束縛。
Docker主機虛擬機Vagrantfile
我們將使用一個新的Vagrantfile來定義Docker主機虛擬機。下面這個文件基于Ubuntu Server 14.04 LTS:
Vagrant.configure("2") do |config|
config.vm.provision "docker"
# 下面這一行終結所有ssh連接,因此
# Vagrant將被迫重新連接。
# 那是在PATH中擁有docker命令的替代辦法
config.vm.provision "shell", inline:
"ps aux | grep 'sshd:' | awk '{print $2}' | xargs kill"
config.vm.define "dockerhost"
config.vm.box = "ubuntu/trusty64"
config.vm.network "forwarded_port",
guest: 8080, host: 8080
config.vm.provider :virtualbox do |vb|
vb.name = "dockerhost"
end
end
將它保存到原始的Vagrantfile文件夾,名稱為DockerHostVagrantfile。
在自定義的Docker主機中運行Docker容器
下一步指定使用這個新的虛擬機作為Docker主機,而不是默認主機,因而將下面新的兩行添加到a.vm.provider代碼段:
config.vm.define "vertxdev" do |a|
a.vm.provider "docker" do |d|
[...]
d.vagrant_machine = "dockerhost"
d.vagrant_vagrantfile = "./DockerHostVagrantfile"
end
end
請注意:配置自定義的Docker主機有另一個好處:我們現在可以指定自定義的轉發端口:
config.vm.network "forwarded_port",
guest: 8080, host: 8080
因而,我們能夠從主主機操作系統里面訪問vertx HTTP服務器:
不同環境上同樣的Docker主機和端口轉發
當然,主機虛擬機并不局限于Ubuntu。可以在Vagrant云(https://vagrantcloud.com)上發現更多的vagrant設備。值得關注的支持Docker的設備有boot2docker(原始版和改良版)和CoresOS。
[page]使用Vagrant編排Docker容器
就在不久前,我們還每次只能運行一個Docker容器。然而在現實生活中,我們卻常常需要同時運行多個容器:數據庫、http、Web容器等都在單獨的容器中運行。
我們在本章節將探討使用Vagrant“多機器”環境(https://docs.vagrantup.com/v2/multi-machine/),同時執行多個docker容器。然而,我們不會考慮Docker容器分布在不同的Docker主機這一場景:所有容器都在同一個主機里面運行。
運行多個容器
2:用Docker和Vagrant構建簡潔高效開發環境">
多個容器
作為第一個例子,我們將使用Vert.x Event Bus Point to Point這個例子。我們利用了在文章開頭定義的同一個Docker文件,并且在新的Vagrantfile文件里面配置了兩個Docker容器:“vertxreceiver”和“vertxsender”:
ENV['VAGRANT_DEFAULT_PROVIDER'] = 'docker'
DOCKER_HOST_NAME = "dockerhost"
DOCKER_HOST_VAGRANTFILE = "./DockerHostVagrantfile"
Vagrant.configure("2") do |config|
config.vm.define "vertxreceiver" do |a|
a.vm.provider "docker" do |d|
d.build_dir = "."
d.build_args = ["-t=vertxreceiver"]
d.name = "vertxreceiver"
d.remains_running = true
d.cmd = ["vertx", "run", "vertx-examples/src/raw/java/eventbus_pointtopoint/Receiver.java","-cluster"]
d.volumes = ["/src/vertx/:/usr/local/src"]
d.vagrant_machine = "#{DOCKER_HOST_NAME}"
d.vagrant_vagrantfile = "#{DOCKER_HOST_VAGRANTFILE}"
end
end
config.vm.define "vertxsender" do |a|
a.vm.provider "docker" do |d|
d.build_dir = "."
d.build_args = ["-t=vertxsender"]
d.name = "vertxsender"
d.remains_running = true
d.cmd = ["vertx", "run", "vertx-examples/src/raw/java/eventbus_pointtopoint/Sender.java","-cluster"]
d.volumes = ["/src/vertx/:/usr/local/src"]
d.vagrant_machine = "#{DOCKER_HOST_NAME}"
d.vagrant_vagrantfile = "#{DOCKER_HOST_VAGRANTFILE}"
end
end
end
對這兩個docker容器而言,vagrant_mahchine即Docker主機虛擬機的ID是dockerhost。Vagrant要足夠智能化,才能重復使用dockerhost的同一個實例來運行兩個容器。
想啟動vertxsender和vertxreceiver,把Vagrantfile換成這一個文件,并運行vagrant up:
$ vagrant up
...
$ vagrant docker-logs
==> vertxsender: Starting clustering...
==> vertxsender: No cluster-host specified so using address 172.17.0.18
==> vertxsender: Succeeded in deploying verticle
==> vertxreceiver: Starting clustering...
==> vertxreceiver: No cluster-host specified so using address 172.17.0.19
==> vertxreceiver: Succeeded in deploying verticle
==> vertxreceiver: Received message: ping!
==> vertxsender: Received reply: pong
==> vertxreceiver: Received message: ping!
==> vertxreceiver: Received message: ping!
==> vertxsender: Received reply: pong
==> vertxsender: Received reply: pong
...
即使vertxsender和vertxreceiver根本不知道彼此的主機名和IP地址,vertx eventbus協議卻有一項發現功能,以便連接發送方和接收方。對于沒有類似功能的應用程序而言,Docker提供了容器連接選項。
[page]連接容器
在這個例子中,我們先運行Docker容器(vertxdev),它啟動我們之前看到的那個HelloWorld Web服務器。然后,第二個容器(vertxdev-client)將使用wget執行HTTP請求:
ENV['VAGRANT_DEFAULT_PROVIDER'] = 'docker'
Vagrant.configure("2") do |config|
config.vm.define "vertxdev" do |a|
a.vm.provider "docker" do |d|
d.image = "vertxdev:latest"
d.ports = ["8080:8080"]
d.name = "vertxdev"
d.remains_running = true
d.cmd = ["vertx", "run", "vertx-examples/src/raw/java/httphelloworld/HelloWorldServer.java"]
d.volumes = ["/src/vertx/:/usr/local/src"]
d.vagrant_machine = "dockerhost"
d.vagrant_vagrantfile = "./DockerHostVagrantfile"
end
end
config.vm.define "vertxdev-client" do |a|
a.vm.provider "docker" do |d|
d.image = "vertxdev:latest"
d.name = "vertxdev-client"
d.link("vertxdev:vertxdev")
d.remains_running = false
d.cmd = ["wget","-qO", "-","--save-headers","http://vertxdev:8080"]
d.vagrant_machine = "dockerhost"
d.vagrant_vagrantfile = "./DockerHostVagrantfile"
end
end
end
這個新Vagrantfile文件的重要部分是這一行d.link("vertxdev:vertxdev")。由于這一行,vertxdev-client就能夠解析主機名vertxdev,因而使用命令wget -qO - --save-headers http://vertxdev:8080,處理HTTP請求。
想運行容器,把Vagrantfile換成這個新文件,并運行vagrant up.。--no-parallel選項確保vertxdev容器在vertxdev-client之前啟動。
$ vagrant up --no-parallel
不妨看一下日志,以證實發生的情況:
[page]伙計,我的IDE在哪里?
雖然集成開發環境(IDE)是開發環境的一個重要部分,但我們還沒有討論它。那是由于圖形化應用程序并不通常在Docker容器里面運行。Eclipse或IntelliJ等IDE在主主機中通常很適合,源代碼在主機和容器之間使用Docker卷來共享。這就是本章節所介紹的內容。
Vagrant隨帶synced_folder選項,以便在docker容器和主主機之間共享文件夾:
ENV['VAGRANT_DEFAULT_PROVIDER'] = 'docker'
Vagrant.configure("2") do |config|
config.vm.synced_folder ".", "/usr/local/src"
config.vm.define "vertxdev-src" do |a|
a.vm.provider "docker" do |d|
d.build_dir = "."
d.build_args = ["-t=vertxdev"]
d.ports = ["8080:8080"]
d.name = "vertxdev-src"
d.remains_running = true
d.cmd = ["vertx", "run", "vertx-examples/src/raw/java/httphelloworld/HelloWorldServer.java"]
d.vagrant_machine = "dockerhost"
d.vagrant_vagrantfile = "./DockerHostVagrantfile"
end
end
end
在這個例子中,vertxdev-src文件夾/usr/local/src將與主主機Vagrantfile文件夾(.)同步。請注意:Vagrant負責為我們構建Docker卷。
一旦我們把Vagrantfile換成這一個文件,就可以運行git clone,再次使用vertxdev-src容器:
$ vagrant docker-run vertxdev-src -- git clone https://github.com/vert-x/vertx-examples.git
一旦克隆完畢,源代碼將同時出現在容器和主主機。因而,我們可以直接訪問,還可以編輯文件:
$ cd vertx-examples/src/raw/java/httphelloworld/
$ sed -i '' 's/Hello World/I m in a docker container and I feel good/' HelloWorldServer.java
想測試該應用程序,運行vagrant up:
$ cd -
$ vagrant up
$ curl localhost:8080
I m in a docker container and I feel good
結束語
如果你在處理一系列不同的平臺:一些平臺支持Docker,另一些不支持,那么使用Vagrant來控制Docker容器很有用。在這種場景下,使用Vagrant可以讓構建環境的過程在不同平臺上具有一致性。
作為Vagrant的替代方案,Fig(http://www.fig.sh)無疑值得關注。Docker雇用Fig的主要開發人員,大力支持它,將其視作一種構建基于Docker的開發環境的出色工具。
原文標題:Setting up a development environment using Docker and Vagrant