2017/03/27

梅特卡夫定律 Metcalfe's law

梅特卡夫定律 Metcalfe's law 是一個關於通訊網絡的價值和網絡技術的發展的定律,一個網絡的價值等於該網絡內的節點數的平方,而且該網絡的價值與聯網的用戶數的平方成正比。

這個定律是由 George Gilder 在 1993 年提出,為表彰 3Com 創辦人 Robert Metcalfe 對於 Ethernet 的貢獻而以他的姓氏命名。這個定律基本上是用來估算一個網路公司的潛在價值的,如果使用人數呈現的趨勢增長速度很快,自然會影響到這個服務的影響力,等同於這個網路服務的總價值會比目前帳面上的數字來得高很多。

根據這個定律,一個網絡的用戶數目越多,那麼整個網絡和該網絡內的每台電腦的價值也就越大。如果一個網路中有 n 個人,因為節點之間連線的數量共有 n×(n-1) 條,這個網路對於每個人的價值與網路中其他人的數量成正比,也就是網路對於所有人的總價值與 n×(n-1) 成正比。

90 年代以來,由於 Internet 呈現了這種超乎尋常的指數增長趨勢,而且爆炸性地向經濟和社會各個領域進行廣泛的滲透和擴張。電腦網路的數目越多,對經濟和社會的影響就越大。梅特卡夫法則揭示了互聯網的價值隨著用戶數量的增長而呈指數級速度增長的規則。

新技術只有在有很多人使用它的時候才會變得更有價值。使用某個網路服務的人越多,產品就變得越有價值,因為身邊已經很多人使用了,因而越能吸引更多的人來使用,漸漸地以指數增長的方式,提高了整個網路的總價值。

一個通訊系統中,如果只有一隻電話沒有任何價值,幾部電話的價值也非常有限,成千上萬部電話組成的通訊網路,才會把通訊技術的價值極大化。一項技術的用戶規模,會影響到它的整體價值。一旦形成必要用戶規模,新技術開發者在理論上可以提高對用戶的價格,因為這項技術的應用價值比以前增加了。

不過實際的網路世界並沒有那麼單純,因為人數增長到某個程度,就會開始停滯觸頂,進入和緩期,還會有一些其他的因素會影響這個服務,例如因為人數太多,造成了訊息爆炸的反效果,還有網路服務的使用者,會根據使用的世代不同而有很大的差異,因為新世代接受的是酷炫、沒有爸媽監控的新服務,很難有一個服務可以老少通吃,跨足到多個世代之間。

References

梅特卡夫定律 wiki

Metcalfe’s Law wiki

梅特卡夫法則(Metcalfe’s Law)

梅特卡夫定律 (Metcalfe's Law) 以iphone為例

2017/03/20

WebJars

在實作網頁服務時,常常會使用很多網頁前端的 framework,例如 jQuery, Bootstrap,以往在使用這些 framework 就只能下載這些 framework,然後自己放到開發的 app server 裡面,但這種手工管理的方式,對於套件的版本管理會造成很大的困擾,往往會搞不清楚哪些檔案是那些套件在使用的。

WebJars將這些常用的網頁前端 framework/library 都轉換成 Jar,然後借助 Maven 工具,讓我們可以用簡單的幾行 Maven 設定,就將整個 library include 到專案當中,也不會困擾要怎麼升級套件。只要是 JVM-based 的 web application 都能夠使用 Maven/Gradle/sbt/Ivy 等等 build tool 關聯到某個套件。

目前 WebJars 提供三種套件封裝的方式

  • NPM WebJars

    1. 任何人都可以發布資源
    2. 根據 NPM 鏡像資源即時建立並部署資源
    3. NPM 是 javascript 資源包的一種格式
  • Bower WebJars

    1. 任何人都可以發布資源
    2. 根據 Bower 鏡像資源即時建立並部署資源
    3. Bower 是 javascript css 資源包的一種格式
  • Classic WebJars

    1. 只能由 WebJars 官方團隊發布資源
    2. 人工封裝及部署資源
    3. 手動建立 RequireJs 設定

以 Scala Play 2 為例,我們使用 Classic WebJars 的 SBT/Play2 方式,在 build.sbt 中增加 bootstrap webjar 設定

libraryDependencies ++= Seq(
  "org.webjars" % "bootstrap" % "3.3.7-1"
)

可以在 IDE 中看到增加的 bootstrap 及 jquery 的 jar files。

另外要注意 routes 設定裡面要保留這一行

# Map static resources from the /public folder to the /assets URL path
GET     /assets/*file              controllers.Assets.at(path="/public", file)

接下來就可以在網頁中使用 jquery 跟 bootstrap

http://localhost:9000/assets/lib/jquery/jquery.js

http://localhost:9000/assets/lib/bootstrap/css/bootstrap.css

從檔案管理員中可以看到整個 library 的資料很完整,bootstrap 也包含了 js extensions。

以往的 WebJars 網址會看到 library 的版本資訊,但現在這樣看起來是沒有版本資訊,這對於 library 更新的工作來說,會更節省一些時間。

References

WebJars介紹

WebJars servlet 3 使用示例

使用Maven匯入Webjars的bootstrap

Spring boot中使用WebJars

JavaWeb開發分享:WebJars

使用WebJar管理css、JavaScript文件

webjars 官方文件

2017/03/19

Docker Volume 初步閱讀與學習紀錄

Docker Volume 是個讓 Docker Container 保存與共用資料的機制。原本 Docker Container 的資料只會存在該 container 內,並不會與外部或是其他 container 共享,如果在 container 運作時新增了一些資料,而在 container 移除時又沒有將該 container commit 成 image,則當 container 被刪除時,資料就會遺失。可以參考官方文件 Understand images, containers, and storage drivers 了解 image 與 container 的運作方式。

要避免資料遺失,就可以使用 Docker Volume。你可以建立一個 volume,將它指定到 container 內的某個目錄,這樣該目錄的資料就可以永續保存著,不會因為 container 移除而被移除。你也可以為 volume 命名,這樣其他 container 就能透過這個名稱來使用相同的 volume,另外 volume 也可以與本機的目錄上連結,這樣你就可以讓你本機上的目錄和 container 上的目錄共享資料。

建立 volume

你可以使用許多方式建立 volume,比如說在建立 container 時使用 -v flag 一併建立:

docker run -d -v /var/lib/mysql mysql:5.7.17

你也可以在使用 -v 時一併指定 volume 名稱,讓資料保存下來,當下次要用別的 container 時,指定相同 volume 名稱就能使用相同資料:

docker run -d -v db_data:/var/lib/mysql mysql:5.7.17

或是先使用 docker volume 指令建立好 volume,在 run 時指定你建立的 volume 名稱即可使用:

docker volume create db_data2
docker run -d -v db_data2:/var/lib/mysql mysql:5.7.17

你也可以使用 volume 來實現本機目錄與 container 目錄資料共享:

docker run -d -v /Users/mayer/Documents/dockerspace/mysqldata:/var/lib/mysql mysql:5.7.17

volume 掛載測試

官方有說明,如果你是建立一個 volume 然後掛載到 container 的目錄,則原本 container 的目錄內所有的資料會複製到 volume 內。但是看文件沒有說明如果掛載的目錄有 a 檔案,而 volume 內也一樣有 a 檔案,會有什麼狀況發生,因此就自己想了個例子測試。

首先建立一個全新的 tomcat container,命名為 foo1,然後使用 -v 指令掛載一個名為 foovolume 的 VOLUME 到 tomcat container 的 /usr/local/tomcat 目錄,接著進入到該 tomcat 的 /bin/bash:

$ docker run -d --name foo1 -v foovolume:/usr/local/tomcat/conf tomcat:8.5.11-jre8
$ docker exec -it foo1 /bin/bash

使用 ls 觀察 server.xml 檔案修改時間,接著在使用 echo 指令在 server.xml 底下加上一些註解,用 tail 指令觀察該檔案是否已經加入資料。然後退出 bash 並停止 foo1 container,讓他不影響接下來的測試。

root@a6e3c1a143f5:/usr/local/tomcat# ls -al conf/server.xml
-rw-------  1 root root 7511 Jan 10 21:05 server.xml
root@a6e3c1a143f5:/usr/local/tomcat# echo '<!-- foobar -->' >> conf/server.xml
root@a6e3c1a143f5:/usr/local/tomcat# tail -n 3 conf/server.xml
  </Service>
</Server>
<!-- foobar -->
root@a6e3c1a143f5:/usr/local/tomcat# ls -al conf/server.xml
-rw------- 1 root root 7527 Mar 16 09:10 conf/server.xml
root@a6e3c1a143f5:/usr/local/tomcat# exit

$ docker stop foo1

接著建立另一個全新的 tomcat container,命名為 foo2,一樣使用 -v 指令掛載剛剛建立好的 foovolume VOLUME 到 /usr/local/tomcat 目錄,接著進入到該 tomcat 的 /bin/bash:

$ docker run -d --name foo2 -v foovolume:/usr/local/tomcat/conf tomcat:8.5.11-jre8
$ docker exec -it foo2 /bin/bash

然後使用 ls 與 tail 指令觀察 server.xml 檔案修改日期與檔案的內容,可以發現到與剛剛 foo1 修改後的檔案日期與內容是一致的。

root@a564777a0059:/usr/local/tomcat# ls -al conf/server.xml
-rw------- 1 root root 7527 Mar 16 09:10 conf/server.xml
root@a564777a0059:/usr/local/tomcat# tail -n 3 conf/server.xml
  </Service>
</Server>
<!-- foobar -->
root@a564777a0059:/usr/local/tomcat# exit

$ docker stop foo2

從這個測試可以觀察出,如果你將一個剛建立的 volume 掛載到某個 container 的目錄,若該目錄裡面已經有資料了,則 Docker 會將資料複製到這個剛建立的 volume,然後把這個 volume 覆蓋在你指定的目錄上,原本目錄的東西將會變成不可視。若你掛載的是一個經過初始化的 volume,則他會直接把 volume 內的資料覆蓋在你指定的目錄。

可以用另一個簡單的測試來驗證上面的說法是否正確。新建立一個 volume,掛載到 tomcat 的 /usr/local/tomcat/conf,然後在將這個 volume 掛載到另一個 tomcat 的 /usr/local/tomcat/webapps,就可以看到,原本 webapps 底下的 ROOT 等等的 Web App 都變成了設定檔了,也就是說,初次建立 volume 時,Docker 把 設定檔複製到 volume 上,volume 再次被掛載時,Docker 把 volume 蓋到 webapps 目錄之上。

$ docker run -d --name foo3 -v foovolume2:/usr/local/tomcat/conf tomcat:8.5.11-jre8
$ docker stop foo3
$ docker run -d --name foo4 -v foovolume2:/usr/local/tomcat/webapps tomcat:8.5.11-jre8
$ docker exec -it foo4 /bin/bash
root@028b5095b04a:/usr/local/tomcat# ls -al webapps/
total 236
drwxr-x---  3 root root    4096 Mar 16 09:50 .
drwxr-sr-x 14 root staff   4096 Mar 16 09:51 ..
drwxr-x---  3 root root    4096 Mar 16 09:50 Catalina
-rw-------  1 root root   12895 Jan 10 21:05 catalina.policy
-rw-------  1 root root    7202 Jan 10 21:05 catalina.properties
-rw-------  1 root root    1338 Jan 10 21:05 context.xml
-rw-------  1 root root    1149 Jan 10 21:05 jaspic-providers.xml
-rw-------  1 root root    2358 Jan 10 21:05 jaspic-providers.xsd
-rw-------  1 root root    3622 Jan 10 21:05 logging.properties
-rw-------  1 root root    7511 Jan 10 21:05 server.xml
-rw-------  1 root root    2164 Jan 10 21:05 tomcat-users.xml
-rw-------  1 root root    2633 Jan 10 21:05 tomcat-users.xsd
-rw-------  1 root root  168133 Jan 10 21:05 web.xml

$ docker stop foo4

volume 與本機目錄掛載測試:

接下來是一些 volume 與本機目錄的掛載測試,主要是要看確認資料是否真的共享,與掛載本機目錄之後的 container 其目錄變化,測試將會使用到 tomcat container,還會用到自己打包的 war 檔。

首先在測試前先準備好 war 檔,放在本機目錄,

$ ls /Users/mayer/Documents/dockerspace/volumetest/
miniweb.war

接著啟動一個沒有掛載 volume 的 tomcat container,然後觀察其 webapps 目錄,可以看到 webapps 底下會有一些預設安裝 tomcat 時的應用程式:

$ docker run -d  --name foo tomcat:8.5.11-jre8
$ docker exec -it foo /bin/bash

$ root@635985e5b693:/usr/local/tomcat# ls webapps
ROOT  docs  examples  host-manager  manager

接著使用 -v flag 啟動一個有掛載的 container,將本機目錄掛載到 webapps 目錄:

$ docker run -d -v
/Users/mayer/Documents/dockerspace/volumetest:/usr/local/tomcat/webapps --name bar tomcat:8.5.11-jre8

然後觀察 tomcat webapps 目錄,可以跟上面沒掛載的對照看,發現原本的 ROOT 等等目錄都不見了,只有你本機目錄上的 war 檔與 tomcat 自動幫你解開的目錄。也就是說原本 webapps 的目錄被隱藏了,現在只會顯示 volume 掛載目錄的內容。

$ docker exec -it bar /bin/bash
root@3eca0381a364:/usr/local/tomcat# ls webapps
miniweb  miniweb.war

然後接著測試是否真的目錄共享,先在 container 內新增一個 bar.txt,然後退出 container bash,觀察本機目錄,可以看到本機目錄上也新增了一個 bar.txt 檔案:

root@3eca0381a364:/usr/local/tomcat# touch webapps/bar.txt
root@3eca0381a364:/usr/local/tomcat# ls webapps
bar.txt  miniweb  miniweb.war
root@3eca0381a364:/usr/local/tomcat# exit
$ ls /Users/mayer/Documents/dockerspace/volumetest
bar.txt     miniweb     miniweb.war

接著反向測試,看看在本機目錄新增 foo.txt,再回到 container 內觀察 webapps 目錄是否也會新增一個 foo.txt。從結果可以看出,container 內也新增 foo.txt 檔案了:

$ touch /Users/mayer/Documents/dockerspace/volumetest/foo.txt
$ docker exec -it bar /bin/bash
root@3eca0381a364:/usr/local/tomcat# ls webapps
bar.txt  foo.txt  miniweb  miniweb.war

透過上面的幾項簡單測試,可以看出當 volume 掛載本機目錄時,container 的目錄與本機的目錄資料會完全共用。

需要注意這個測試和上一個測試的不同之處,若是透過 volume 把本機目錄掛載到 container 的目錄,則 container 目錄裡面原本的內容會被完全隱藏,只是出現你本機目錄的檔案;如果是單純的建立 volume 然後掛載到 container 的目錄,則對於初次建立的 volume,Docker 會將 container 目錄裡面原本的內容全部都複製到 volume 上。

使用其他 container 當成 data volume

官方有提到另一個用法,就是在啟動 container 時使用 volumes-from 指令,來分享 container 之間的資料,對於此用法我個人感受不深,覺得很不直覺,因此這邊只會紀錄一下官方的範例。

首先先建立一個有名稱的 container。該 container 不執行任何的應用,他只重新利用 training/postgres image,使所有容器都使用共同的層,節省磁盤空間。

$ docker create -v /dbdata --name dbstore training/postgres /bin/true

接著你就可以在其他 container 上使用 --volumes-form 來掛載 /dbdata volumes。

$ docker run -d --volumes-from dbstore --name db1 training/postgres
$ docker run -d --volumes-from dbstore --name db2 training/postgres

這範例,如果 training/postgres images 原本 /dbdata 目錄內有資料,則會被隱藏,只有從 dbstore container 掛載過來的資料可以被看見。

另外也可以用在備份上,可以透過以下方式進行備份:

$ docker run --rm --volumes-from dbstore -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata

然後將備份還原到其他 container:

$ docker run -v /dbdata --name dbstore2 ubuntu /bin/bash
$ docker run --rm --volumes-from dbstore2 -v $(pwd):/backup ubuntu bash -c "cd /dbdata && tar xvf /backup/backup.tar --strip 1"

移除 volumes

使用 volumes 時要注意到,docker 並不會自動幫你移除你沒用到的 volumes。例如你建立 foo container 並掛載一個 foovolume,當你的 foo container 移除時,foovolume 會繼續存在。

在 docker run 時,可以加上 --rm 指令,讓 container 移除時一併把匿名 volume 移除,比如說以下指令:

$ docker run --rm -v /foo -v awesome:/bar busybox top

此命令會建立一個匿名的 /foo volume。當 container 被移除時,Docker Engine 會移除 /foo volume,而不會移除 awesome volume。

你可以用以下指令查看看哪些 volumes 目前沒被任何 container 關聯,然後在下指令移除他們:

docker volume ls -qf dangling=true
docker volume rm $(docker volume ls -qf dangling=true)

若你也像我一樣,目前正在測試階段,則可以直接使用以下指令,清除沒被用到的 volumes:

docker volume prune

參考

Manage data in containers

Docker volume 簡單用法

深入理解Docker Volume(一)

2017/03/13

False Sharing 偽共享

在 multicore 的 CPU 電腦中,為了在多核心的狀況下,讓程式跑得更快,會把資料放在 cache 中,在 cache system 裡面儲存的記憶體的基本單位稱為 cache lines,通常 cache line 都是 2 的指數次方,也就是 32 ~ 256 bytes,最常見的 cache line size 為 64 bytes。

false sharing 發生在多核心執行不同的 threads 時,不同的thread修改到存在同一個 cache line 裡面不同的變數,因為頻繁地發生 cache invalidate,因此造成在多核心的狀況下,反而削弱了程式的效能。

為了避免這個問題,解決方式就是不要讓兩個以上的 threads 寫入同一個變數,或是 cache line。

這個圖片說明了 false sharing 發生的狀況,core 1 想要更新 X,core 2 想要更新 Y,但 X 與 Y 存放在同一個 cache line 中,每一個 thread 都需要競爭 cache line 的使用權,去更新變數的內容,當 core 1 取得 cache line 使用權,就會造成 core 2 的 cache invalidated,多次來來回回之後,影響了效能。

False Sharing

False Sharing-上面那篇的翻譯

提供了一個 java 的範例,文章中刻意製造了多個 threads,並實際計算在 multicore 的狀況下,耗費的時間,使用了連續七個沒有使用到的 long 變數,利用這個方法來避免讓多個 VolatileLong 放在同一個 cache line 裡面。

What is @Contended and False Sharing ?

The end for false sharing in Java?

Java 8 根據上面的方式,提供了一個新的 annotation @Contended,他可以用在兩個連續的變數之間,經過 compiler 自動加上 padding variables,用以避免這兩個變數被配置在同一個 cache line。

References

今天就以False-sharing就做為一天的結束吧

https://en.wikipedia.org/wiki/False_sharing

Avoiding and Identifying False Sharing Among Threads

2017/03/06

Digital Twin 數字化雙生

對Gartner發佈2017年十大技術趨勢的分析 文章中,討論一些新的技術趨勢,我們注意到其中兩個項目,(1) blockchain 區塊鏈 (2) Digital Twins 數字化雙生。

Blockchain:Internet問世以來最具破壞力的發明: 在啟動交易的一方,先建立一個資訊區塊(block),然後這個區塊會由網路上的幾千部、甚至幾百萬部電腦進行驗證。每一筆交易資訊都和它本身的變動歷史連結,並透過幾百萬部電腦來驗證,所以可以解決現有交易模式的信用問題。但目前在 blockchain 還在研究的過程,應該還需要一些時間才會在真實的環境上應用。

Digital Twin 數字化雙生是沒有聽過的名詞,數字化雙生(Digital Twin)是事物或系統的多元軟件模型,它依賴於傳感器數據來理解其處境,響應變化,改進操作和增加價值。

公司將使用數字化雙生主動修復和規劃設備服務、規劃製造流程、操作工廠、預測設備故障或提高運營效率,以及執行增強的產品開發。因此,數字化雙生最終將成為技術人員和傳統監測設備和控制(例如,壓力計,壓力閥)的組合的代理。

Digital Twin 可說是 IOT 未來,IOT 的重點強調收集資料,收集很多週邊環境的資料,收集很多週邊設備的資料,收集資料後可以進行分析與統計。

而 Digital Twin 在數位世界建造了一個真實世界的化身,這個化身可能會用到許多真實環境的資料,讓這個數位化身更貼近真實世界的原形,也可能用 3D 的方式,讓使用者可以使用這個虛擬化身,就像是在使用真實世紀的設備一樣。

Digital Twin 就是一種虛實融合的技術,建造一個分身的用途,無非是為了節省成本,不管是生產、測試、研發等等產品開發過程中,所耗費的成本,正因為真實世界的裝置或設備太過於昂貴,我們才需要一個虛擬的分身,可以不斷地進行分析、測試,並能隨時在損壞後就馬上復原回來。

都談Digital Twin,大咖各有解讀 提出了兩家不同公司,對 Ditigial Twin 有不同的理解。達索通過打造3D體驗平台實現Digital Twin的建立與交互,從而為具有極端複雜系統的客戶提供支持。PTC則致力於在虛擬世界與現實世界之間建立一個實時的連接,使PLM系統形成閉環,從而為客戶提供更具有競爭性和變革性的業務解決方案。

工業4.0術語:Digital Twin 數字孿生 提供了一個美國國防部的實例:到了2035年,當航空公司接收一架飛機的時候,將同時還驗收另外一套數字模型。每個飛機尾號,都伴隨著一套高度詳細的數字模型。」

每一台飛機都會伴隨著一個虛擬空間的飛機分身,透過傳感器實現與飛機真實狀態完全同步,每次飛行後,根據結構現有情況和過往載荷,即時分析評估是否需要維修,能否承受下次的任務載重等。

網宇實體系統(Cyber-Physical System, CPS) 本質就是 big data 運算,在真實實物跟數位分身之間的橋樑,CPS系統的本質就是人、機、物的融合計算,使用者、機器分身跟實物之間,三個角色的互動關聯。CPS 有著網際網路的、基於個性化服務的、基於數據決策的、基於高效節能的四大特質。

CPS 的實例1: MIT 的 Distributed Robot Garden,這個花園裡有一群的機器人負責照顧番茄。這個系統結合了感測網路(每一株植物上都有感測器會去監控植物狀態)、導航、機器人控制和無線網路。

CPS 的實例2: CarTel 計畫,這個計畫裡,有一隊的計程車會蒐集波士頓的即時交通資訊,路徑規劃就可以使用即時的交通資訊和歷史資訊規劃出最快的交通路徑。

2017/03/04

Docker Compose 初步閱讀與學習記錄

上次在看 Dockerfile 時,有注意到一個工具,Docker Compose。透過這個工具,可以先寫一份檔案,預先定義好多個 service,然後透過單一命令來啟動多個 container 執行你定義的 service,讓他們組成一個你想要的應用服務。直覺這是個 docker run 命令的進階工具,值得看看,因此就花點時間閱讀了官方文件,以下是一些簡單的學習記錄。

什麼是 Docker Compose

Docker Compose 官方說明文件 Overview of Docker Compose 已經有些簡介,以下為官方文件的部分翻譯:

Compose 是一個工具,用來定義與執行多個 container 組成的 Docker Applications。你可以使用 Compose 檔案來組態設定你的應用服務。然後使用單一命令,透過你的組態設定來建立與啟動你的服務。

Compose 適合用來開發、測試、與建立 staging 環境,如同 CI workflows。

使用 Compose 有基本的三個處理步驟:

  1. 使用 Dockerfile 定義你的 app 環境,讓它可以在任何地方都能複製(reproduced)。
  2. 使用 docker-compose.yml 定義你的服務,讓他們可以在獨立環境內一起執行。
  3. 最後,執行 docker-compose up,Compose 將會開始與執行你所有的 app。

docker-compose.yml 檔案看起來像這樣

  version: '2'
  services:
    web:
      build: .
      ports:
      - "5000:5000"
      volumes:
      - .:/code
      - logvolume01:/var/log
      links:
      - redis
    redis:
      image: redis
  volumes:
    logvolume01: {}

docker-compose.yml 也就是組態設定文件,是一種 yaml 格式撰寫的文件,可以上維基看一下 YAML 格式說明,比較需要注意的是他的縮排規定要空白鍵,而不是 tab。

安裝

如果你和我一樣是 Mac 的使用者,在安裝 Docker for Mac 時,就會安裝 docker-compose 工具。

若是其他作業系統使用者,可以到 github - docker/compose 查看最新版本的 docker-compose 與如何下載安裝。

Compose file version 3

目前最新的 Compose file 為版本 3,此文件會分成三大組態設定,分別為 services、networks 以及 volumes:

  • services (top-level)

    services 主要讓你定義你應用服務啟動時,用來執行的 container 相關資訊,比如說 image 是用哪個、是不是要透過 Dockerfile 先進行編譯、要不要覆寫預設 command 或是 entrypoint、環境變數為何、port 導出與對應等等的,這些參數多半在 docker run 指令有相對應的參數。可以參照著看。

    service 雖然提供許多組態設定,但是如果你已經知道使用的 image 或是你自己寫的 Dockerfile 原本有寫了,就不用在這邊在寫一次,比如說像等等範例會用到的 wordpress image,他預設就有定義 entrypoint,這時你就不用在這邊重新定義一次。

    以下簡介一些 services 底下的組態設定:

    • build

      如果你的 service 要使用 Dockerfile 來建立,你可以透過此設定,指定你 Dockerfile build context 在哪,如果你 build 和 image 兩個設定一起使用,則透過 Dockerfile 建立起來的 image 會被命名成你在 image 設定寫的名稱。需要注意的是,要透過 docker stack 指令部署到 swarm 上時,這設定會失效,因為 docker stack 只接受預先建好的 image。

    • deploy

      只有在 Compose file 版本 3 才能使用。此設定只有在使用 docker stack deploy 指令將應用服務部署到 swarm 上才會有作用,在 docker-compose up、docker-compose down 會被忽略。

    • depends_on

      可以定義 service 之間的相依性,比如說官方範例:

      version: '2'
      services:
        web:
          build: .
          depends_on:
            - db
            - redis
        redis:
          image: redis
        db:
          image: postgres

      當下 docker-compose up 指令時,db service 和 redis service 會先啟動,然後在啟動 web service。

    • environment

      定義環境變數,注意,如果值是布林,要用單引號包起來,如 'true'、'false'。否則 YML 解析器會把它變成 True 或是 False。以下為官方範例:

      environment:
        RACK_ENV: development
        SHOW: 'true'
        SESSION_SECRET:
      
      environment:
        - RACK_ENV=development
        - SHOW=true
        - SESSION_SECRET
    • image

      指定 container 要從哪個 image 啟動。以下為官方範例:

      image: redis
      image: ubuntu:14.04
      image: tutum/influxdb
      image: example-registry.com:4000/postgresql
      image: a4bc65fd
    • networks

      container 要加入哪個網路,這邊的項目會參考到最外層 networks 的設定。

    • ports

      讓你指定要導出的 port,可以是 HOST:CONTAINER,或是只指定 CONTAINER,這時會隨機挑一個 HOST Port 來用。注意,port 指定最好都用字串,因為 YAML 解析器在你挑的 port 小於 60 時會出問題。以下為官方範例:

      ports:
       - "3000"
       - "3000-3005"
       - "8000:8000"
       - "9090-9091:8080-8081"
       - "49100:22"
       - "127.0.0.1:8001:8001"
       - "127.0.0.1:5000-5010:5000-5010"
       - "6060:6060/udp"
    • volumes, volume_driver

      設定 container 要使用的 volume,可以是一個路徑或是參考到最外層 volume 的設定,格式為 HOST:CONTAINER,你也可以只寫 CONTAINER,讓 Docker 自動幫你建立一個。參考官方範例:

      volumes:
        # Just specify a path and let the Engine create a volume
        - /var/lib/mysql
      
        # Specify an absolute path mapping
        - /opt/data:/var/lib/mysql
      
        # Path on the host, relative to the Compose file
        - ./cache:/tmp/cache
      
        # User-relative path
        - ~/configs:/etc/configs/:ro
      
        # Named volume
        - datavolume:/var/lib/mysql
  • networks (top-level)

    networks 讓你設定網路。類似 docker network 指令。可以和 servcies 區塊內的 network 搭配使用,例如:

    version: '3'
    
    services:
      t1:
        image: tomcat:8.5.11-jre8
        networks:
          - test-net
      t2:
        image: tomcat:8.5.11-jre8
        networks:
          - test-net
    networks:
      test-net:
  • volumes (top-level)

    volumes 讓你處理資料共享與資料持久(persist)。類似 docker volume 指令。可以和 servcies 區塊內的 volume 搭配使用,比如說像官方範例:

    version: "3"
    
    services:
      db:
        image: db
        volumes:
          - data-volume:/var/lib/db
      backup:
        image: backup-service
        volumes:
          - data-volume:/var/lib/backup/data
    
    volumes:
      data-volume:

特別要注意的地方是,雖然官方說 Compose file 分成三大組態設定,但是其實最上面還有一個 version 設定,這是一定要加的,不然在執行 docker-compose up 時會一直出現無法理解的錯誤。例如:

version: "3"
services:
  foobar:
    image: tomcat:8.5.11-jre8
    networks:
      - foobar-nets
volumes:
  foobar-volume:
networks:
  foobar-nets:

更詳細的 Compose file 組態設定介紹建議可以參考官方網站 Compose file version 3 reference

Sample

推薦官方 WordPress 範例 Quickstart: Compose and WordPress 。雖然他是版本 2 的文件,但是裡面其實已經包含了 docker-compose 的精神,使用單一文件定義服務,之後透過一個指令啟動服務。

首先開啟終端機,在自己本機上建立一個目錄,用來放置 docker-compose.yml 檔案 例如:

# cd /tmp
# mkdir -p docker/compose_wordpress
# cd docker/compose_wordpress
# pwd
/tmp/docker/compose_wordpress 

接著建立 docker-compose.yml

# vi docker-compose.yml

以下為官方網頁上的 docker-compose.yml 檔案內容:

version: '2'

services:
   db:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: wordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_PASSWORD: wordpress
volumes:
    db_data:

此範例定義從 services 區塊來看,定義了兩個服務,分別為 db 與 wordpress。

db service 指定 image 為 mysql:5.7,然後使用 volume db_data 對應到 container 的目錄 /var/lib/mysql,設定 volume 是因為當 docker-compose down 指令執行時,會將 container 停止並移除,所以資料是會完全消失的,必須把 MySQL 的儲存資料透過 volume 永久保存。然後他設定 restart 為 always。接著他設定 MySQL 預設環境變數,這部分可以參考 Docker Store - MySQL,這邊可以找到 MySQL 官方 image 環境變數的說明。

接著是 wordpress service,他先寫了 depends_on db 這組態設定,意思是 wordpress service 和 db service 有相依性的關係,因此在下 docker-compose up 指令時,會先啟動 db service 的 container,接著才是 wordpress service 的 container。container 使用的 image 為 wordpress:latest image,然後將 port 導到本機的 8000 port,restart 策略為 always,最後在設定環境變數。

docker-compose.yml 檔案準備好之後,在終端機輸入:

# docker-compose up -d
Starting composewordpress_db_1
Starting composewordpress_wordpress_1
Creating network "composewordpress_default" with the default driver
Creating volume "composewordpress_db_data" with default driver
Pulling db (mysql:5.7)...
5.7: Pulling from library/mysql
....
Pulling wordpress (wordpress:latest)...
latest: Pulling from library/wordpress
....
Creating composewordpress_db_1
Creating composewordpress_wordpress_1

會看到首先 docker 會先建立所需的 volume 與一個全新的 network 讓你的應用服務使用,接著會幫你先確認你定義的 service 所需的 image 是否有抓下來了,最後會建立 container 來執行你定義的 service。接著你就可以在你的瀏覽器上輸入網址,連到 WordPress 的管理頁面了:

http://your-docker-host-ip:8000

當你下 docker-compose down 時,你會看到兩個 container 被停止,然後被刪除,建立的 network 也會被刪除。可以用 docker ps -a 驗證一下。

# docker-compose down
Stopping composewordpress_wordpress_1 ... done
Stopping composewordpress_db_1 ... done
Removing composewordpress_wordpress_1 ... done
Removing composewordpress_db_1 ... done
Removing network composewordpress_default

如果你再一次使用 docker-compose up 命令,Docker 又會把所需的 network 以及兩個 container 建立起來並執行,由於 MySQL 有使用 volume 的關係,因此你可以打開 browser 確認一下,資料都還在,不需在重新設定。

# docker-compose up -d
Creating network "composewordpress_default" with the default driver
Creating composewordpress_db_1
Creating composewordpress_wordpress_1

這兩個範例可以改成手動用 docker run 執行,docker-compose up -d 會變成:

# docker network create wordpress
# docker run -d -v db_data:/var/lib/mysql --name sample_mysql --network wordpress --restart always -e MYSQL_ROOT_PASSWORD=wordpress -e MYSQL_DATABASE=wordpress -e MYSQL_USER=wordpress -e MYSQL_PASSWORD=wordpress mysql:5.7
# docker run -d -p 8000:80 --name sample_wordpress --network wordpress --restart always -e WORDPRESS_DB_HOST=sample_mysql:3306 -e WORDPRESS_DB_PASSWORD=wordpress wordpress:latest

要注意要先建立一個 network,讓兩個 container 在同一個 network 下執行,wordpress 才能使用 mysql 的 container name 找到 DB。

而 docker-compose down 會變成:

# docker stop sample_wordpress
# docker stop sample_mysql
# docker rm sample_wordpress
# docker rm sample_mysql
# docker network rm wordpress

小結

從上述自己手動啟動與停止應用服務的多行命令就可以看出 Docker Compose 的強大與方便了。使用 Docker Compose 可以透過組態設定幫你處理好自己下 docker run 時要下的多個參數,而且能將多個 container 組織成一個應用服務,並管理 container 的相依性;另外透過閱讀 docker-compose.yml 檔案,也可以很容易的了解到整個應用服務的組成與架構。是個軟體開發與測試時能好好運用的工具。

ref

Install Docker Compose

Get started with Docker for Mac

github - docker/compose

Docker Compose

Overview of Docker Compose

wikipedia - YAML

YAML 语言教程

Compose file version 3 reference

Quickstart: Compose and WordPress