2017/04/30

Linux 與 Mac 使用者如何透過終端機 ssh 登入 GCP

以下是 Linux 以及 Mac 使用者透過終端機 ssh 登入 GCP instance 的方法:

  1. 使用終端機建立 ssh keys,其中 [USERNAME] 輸入你登入 gcp instance 時要用的 linux username。gcp 機器沒這帳號時會他會自動建立。

    輸入指令,然後按兩次確定:
    ssh-keygen -t rsa -f ~/.ssh/my-ssh-key -C [USERNAME]
    
    範例:
    ssh-keygen -t rsa -f ~/.ssh/mayer-gcp-key -C mayer
  2. 改 private key 權限

    chmod 400 ~/.ssh/mayer-gcp-key
  3. 使用 cat 看 public key,然後把 cat 出來的內容完全複製:

    cat ~/.ssh/mayer-gcp-key.pub
    
    ssh-rsa xxxxxxxxxxxxxxxxxxxxAQABAAABAQDcO7K0z/etx95xbVG10ttgmuwnlyRuDu0LgoTU0X/RGsIhm3kaQxxxxxxxxxxxxxxxxxxxUWOC0L9RAO0+bQI36DY2XiubrbwzrfrkMk6o40u1lCVg5H/cFaTlkyyyyyyyyyyyyyyyyyyyyyyyyyORbQ8E8bBgSV/AlJqV/Qnm0z4Rp3ezAngI77I9GtbEgKxa7pHtRFf/YA99frvLYFVLh94sWDVijx6oKEp/emteL5ham6hZmPVe181Q9+hU8wfUNlAKDE8ZdJ+QC3hkSzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz7AL+Xp6u3epX1Hc+NgeEhtqnJ6ijmP mayer
  4. 到中繼資料(metadata)頁面,把複製出來的公鑰加到 SSH 金鑰上。如何進入中繼資料設定頁面請參考官網 - Setting instance metadata

  5. 接著就可以透過終端機使用 ssh 登入,ip 請到 VM 執行個體,看機器的 External IP。

    ssh -i ~/.ssh/mayer-gcp-key [USERNAME]@x.x.x.x
  6. 可以把上述指令加到 ssh config 內加快登入流程:

    vi ~/.ssh/config
    
    Host gcp1
      HostName x.x.x.x
      User mayer
      IdentityFile ~/.ssh/mayer-gcp-key
    
    設定完之後就能直接使用 hostname 登入
    ssh gcp1
  7. 登入 GCP 機器之後,使用者身份會是你的 [USERNAME]。要將使用者切換成 root,使用以下指令:

    sudo -s

    參考:

Connecting to Linux Instances

談談 docker network-alias

在使用 docker run 指令啟動 container 時,有一個與 network 有關的 flag,為 network-alias,可以為 container 在網路拓墣取一個別名,讓其他 container 透過這個別名找到該 container。一開始看到時,不知道能拿來做什麼,想說我透過 container name 就能直接找到我要的 container 了,為何還要在另外命名?但是最近在弄新的產品需求時,卻剛好有需要用到 network-alias 的應用場景出現,因此想做個記錄。

network-alias 效果

container 在自定義的 network 底下時,可以使用 container name 找到目標對象,若對象有定義 network-alias 時,則也可以使用 network-alias 找到目標。比如說以下範例,建立兩個 container,名為 foo1 與 foo2,將他們加到 foonet 這個自定義的 network 底下,然後 foo2 取 network-alias 為 bar2。使用 docer exec 指令進入到 foo1 的 bash 後,試著執行 ping foo2 與 ping bar2,都是能找到相同的 container。

docker network create --driver bridge foonet
docker run -d --name foo1 --network foonet tomcat:8.5.11-jre8
docker run -d --name foo2 --network foonet --network-alias bar2 tomcat:8.5.11-jre8
docker exec -it foo1 /bin/bash

root@3769d24e6700:/usr/local/tomcat# ping foo2 -c 3
PING foo2 (172.18.0.3): 56 data bytes
64 bytes from 172.18.0.3: icmp_seq=0 ttl=64 time=0.172 ms
64 bytes from 172.18.0.3: icmp_seq=1 ttl=64 time=0.106 ms
64 bytes from 172.18.0.3: icmp_seq=2 ttl=64 time=0.141 ms
--- foo2 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.106/0.140/0.172/0.027 ms

root@3769d24e6700:/usr/local/tomcat# ping bar2 -c 3
PING bar2 (172.18.0.3): 56 data bytes
64 bytes from 172.18.0.3: icmp_seq=0 ttl=64 time=0.093 ms
64 bytes from 172.18.0.3: icmp_seq=1 ttl=64 time=0.097 ms
64 bytes from 172.18.0.3: icmp_seq=2 ttl=64 time=0.099 ms
--- bar2 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.093/0.096/0.099/0.000 ms
root@3769d24e6700:/usr/local/tomcat#

兩個 container 擁有相同 network-alias

接下來是在官方文件看到的,可以多個 container 使用相同的 network-alias。對這真實的運作情況有點好奇,因此建立了模擬狀況來測試。

底下範例建立了三個 container,分別為 foobar1、foobar2 與 foobar3。其中 foobar1 要做為 ping 的發起端,而 foobar2 與 foobar3 為測試對象,他們使用相同的 network-alias,名為 app。

首先依序把三個 container 建立起來,然後進入 foobar1 的 bash,接著開始 ping network-alias 的測試。在測試的時候發現在 ping network-alias 時,不管 ping 多少次,只會找到 foobar2,也就是第一個使用該 network-alias 的 container,當第一個 foobar2 停掉時,才能 ping 到第二個使用該 network-alias 的 container,也就是 foobar3。

docker run -d --name foobar1 --network foonet tomcat:8.5.11-jre8
docker run -d --name foobar2 --network foonet --network-alias app tomcat:8.5.11-jre8
docker run -d --name foobar3 --network foonet --network-alias app tomcat:8.5.11-jre8
docker exec -it foobar1 /bin/bash
root@05c0ad2a04b2:/usr/local/tomcat# ping app
PING app (172.18.0.3): 56 data bytes
64 bytes from 172.18.0.3: icmp_seq=0 ttl=64 time=0.117 ms
64 bytes from 172.18.0.3: icmp_seq=1 ttl=64 time=0.126 ms
64 bytes from 172.18.0.3: icmp_seq=2 ttl=64 time=0.129 ms
root@05c0ad2a04b2:/usr/local/tomcat# exit
exit
mayertekiMacBook-Air:~ mayer$ docker stop foobar2
foobar2
mayertekiMacBook-Air:~ mayer$ docker exec -it foobar1 /bin/bash
root@05c0ad2a04b2:/usr/local/tomcat# ping app
PING app (172.18.0.4): 56 data bytes
64 bytes from 172.18.0.4: icmp_seq=0 ttl=64 time=0.126 ms
64 bytes from 172.18.0.4: icmp_seq=1 ttl=64 time=0.138 ms

也就是說可以使用 network-alias 建立一個至多個備援的 container,提供服務的 container 掛了後相同 network-alias 的 container 會繼續提供服務,不過實際應用上並不建議這樣使用,若是需要備援服務應該使用如 Docker Swarm 或是 kubernetes 之類的叢集管理工具比較恰當。

應用場景

知道了 network-alias 的效果之後,就會開始思考,這要應用在哪?這可能可以從一個 App Server 與 DB container 的互動來切入,首先我們建立一個 Web 應用程式,要將他放在 tomcat 上,這個 Web 應用程式需要與 DB 互動,因此我們需要一個 MySQL,此時用 Docker 來建立整個環境會如下圖所示:

在 Web 應用程式內,要連到 DB,通常會把他寫成屬性檔,用來描述 DB 的位址以及相關登入資訊,有可能如下:

miniweb.driverClassName = com.mysql.jdbc.Driver
miniweb.url = jdbc:mysql://db1:3306/miniweb?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=false
miniweb.username = foo
miniweb.password = bar
miniweb.initialSize = 1
miniweb.maxActive = 50
miniweb.maxIdle = 10
miniweb.maxWait = 180000
miniweb.validationQuery = SELECT CURRENT_TIMESTAMP

在開發與測試階段時,這樣是不會有問題的,但是在程式要上線時,這樣做就會有問題了。通常上線前會先把程式放到 staging 環境先經過測試後,才會上到 production 環境。此時依然可以使用 docker 來為我們建立這兩個環境所需要使用的 container,但是會有個問題,由於 container name 不能重複,因此應用程式內的屬性檔,必須要有兩份才能正常運作,比如說目前環境如下圖:

則你在 ap1 上應用程式的設定檔,DB 的 url 要如下:

miniweb.url = jdbc:mysql://db1:3306/miniweb?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=false

而在 ap2 上應用程式的 DB url 要如下:

miniweb.url = jdbc:mysql://db2:3306/miniweb?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=false

維護兩份設定檔文件會是一件很麻煩的事情,你可以在打包程式時,手動改這屬性檔,或是使用打包工具搭配一些指令來處理這問題,不管怎樣都需要額外花費一些功夫來處理這狀況。

此時就可以使用 docker network 搭配 network-alias 來處理這問題了,我們使用 docker network 建立兩個獨立的 network 環境,然後各跑各的 Web 應用環境,在 DB containers 上使用 network-alias 把他們都命名為 db,這樣一來,我們的應用程式只要透過 db 這個 network-alias,就能正確的找到相對應的 DB containers。整個環境的概念圖如下:

在這個環境下,屬性檔只要填上 network-alias 即可,如下:

miniweb.url = jdbc:mysql://db:3306/miniweb?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=false

2017/04/24

Actor Model

Actor Model 是一個 concurrent computing 的計算模型,Actor 是這個模型的基本計算單元,當 actor 透過一個自己的獨立 mailbox 接收到一個訊息,他能夠根據這個訊息,做出決策、進行運算、產生其他訊息、決定如何回應。

Actor model 是 1973 年由Carl Hewitt、Peter Bishop及Richard Steiger的論文: A Universal Modular Actor Formalism for Artificial Intelligence 提出。

在當時由於機器多屬於單核心且網路速度較慢,不同的 actor 甚至是遠端的 actor 之間發送訊息,存在著明顯的資料延遲時間。但由於 Moore's Law 的趨勢,目前的 CPU 已經是多核心的時代,網路速度也已經不可同日而語,以往的資料鎖定 (lock) 方法已經無法滿足於現今處理並行運算的硬體設備。Actor Model 簡化了 data lock 的實作的複雜性,去掉了 deadlock 的問題,在一個運算環境中以大量的actor 小單元完成並行運算的工作。


Everything's an Actor

相對於物件導向的 Everything is an Object,Actor Model 認為 Everything is an Actor。actors 是一個並行的運算系統,能夠處理下列的工作,這是 actor 的基本原則。

  1. 傳送有限數量的訊息給其他 actors
  2. 建立有限數量的 new actors
  3. 指定接受到下一個訊息時的行為

所有訊息的收送都是非同步的,透過 mail address 傳送資料。每一個 actor 都有一個 mailbox ,以 FIFO 方式暫存接收到的訊息。

Actor Model 的爸爸 Carl Hewitt 在 Lang.NEXT 2012 針對 acotr model 有一段討論會形式的說明 Hewitt, Meijer and Szyperski: The Actor Model (everything you wanted to know...),整個影片的過程就像是聊天一樣,3 個 actors 在互相溝通與討論 actor model。

實作 actor 有三個原則,必須要包含下面三種基本要素

  1. processing: 要能進行運算
  2. storage: 必須要能儲存資料
  3. communication: 要能跟其他 actors 溝通

actor 就像是一個獨立的個體,他能持續的接收訊息,進行工作,但同一時間內,只能處理一個訊息,所有訊息都是依照收到的順序依次進行處理。

當 actor 互相傳送訊息,可能透過某種方式發送訊息給自己,這有可能會造成 deadlock 嗎?

Carl Hewitt 給了使用 Futures and promises 的概念,因為 future 就像是在現在預約一個在未來會產生結果的訊息,因此 actor 可以產生一個 future,並發送給自己的 address,然後在收到自己發送的訊息時,進行下一步的運算,並將結果放到 future 裡面,這完全不可能會造成 deadlock。

address 跟 actor 本身並不是一對一的,而是多對多的,一個 actor 可以有多個 addresses,一個 address 也可能因為原本的 actor 失效,而指向到另一個重新產生的 actor,address 並不代表是 actor 的 identity。

actor 因為一次處理一個訊息的特性,因此不會造成資料同步上的問題,基本上資料只存在於這個 actor 裡面,一次又只能因為一個訊息的輸入及運算,而異動了這個資料,因此不會造成資料同步上的問題。

AKKA NOTES - INTRODUCING ACTORS 這篇文章用了幾張圖片,說明 actor 之間如何互相傳送資料,Actor 就像是一群人,他們互相之間不會面對面地交流,而只是通過郵件的方式進行溝通。

傳遞消息是Actor模型的基礎,他以一名學生和一位教師舉例,描述了以下基礎流程:

  1. 一名學生給一位教師發送了一封郵件,郵件一旦發送之後,就不能夠修改了
  2. 教師會在她認為適合的時機去檢查她的郵箱,收到這封郵件
  3. 該教師稍後會寄一封回信給該學生,這封回信也是一旦發送就不可修改的
  4. 學生在一段時間後決定去檢查一下他的郵箱,收到了回信

信件是單向的,那麼它一旦送出之後並不會期待或等待任何響應,但 actor 都能夠在一個 request-response 週期內,為發送者發送回應消息。

Scratch 也使用了 Actor Model

Actor Model 中列出了現在支援了 Actor Model 的程式語言,其中有一個特別的是 Scratch,Scratch 是 MIT 為了小朋友開發的一套圖形介面的程式設計軟體,適合從來沒有學過程式設計的小朋友,通過拖曳預先設定好的積木式程式模件,堆疊出指令,設定或控制角色及背景的行動和變化,從而完成程式設計。

參考這個範例 Scratch 教學的第 1 堂課(1/5)適合親子共學的兒童程式設計入門,如果有用過 Scratch,也會知道在開發時,一開始就是要產生人物的主角,然後再程式區塊,產生數個 blocks,為每個 block 設定 trigger 該 block 的事件條件,當滿足該條件時,就持續下去進行下面程式區塊的運算,也可以在運算後,廣播一個訊息,驅動其他的程式區塊。

這跟 Actor Model 的概念是很接近的,執行時也可以發現,這些程式區塊是可以同時進行運算的,程式區塊之間,也可以透過訊息的發送傳遞消息,不過 scratch 還是支援了全域變數,這跟 actor model 本身就有些不同,但這畢竟是給小朋友使用的工具,因為變數的概念對學程式來說是一個門檻,如果變數沒有辦法共享,coding 就沒辦法那麼直覺地使用變數。

References

Akka in action: actor model

以Akka為示例,介紹Actor模型 I

2017/04/17

分散式系統 一致性演算法

分散式系統透過網路互相連接,但由於網路的時間延遲,再加上各節點可能的失效或異常,需要一個適當的演算法達到資料一致性,如果其中有些節點,可能會刻意造假以混淆資料的一致性結果,那麼需要更有效的演算法來解決這樣的問題。

一致性問題

資料一致性通常指關聯數據之間的邏輯關係是否正確和完整。而資料存儲的一致性模型則可以認為是存儲系統和資料使用者之間的一種約定。如果使用者遵循這種約定,則可以得到系統所承諾的訪問結果,常用的一致性模型有:

  • 嚴格一致性(linearizability, strict/atomic Consistency)

    讀出的數據始終為最近寫入的數據。這種一致性只有當全域時鐘存在時才有可能達成,在分散式網路環境不可能實現。

  • 順序一致性(sequential consistency)

    對同一資料的操作,所有使用者都看到同樣的操作順序,但是該順序不一定是即時的。

  • 因果一致性(causal consistency)

    只有存在因果關係的寫入操作才要求所有使用者看到相同的操作順序,對於無因果關係的寫入則並行進行,不保證操作順序。因果一致性可以看做對順序一致性性能的一種優化,但在實現時必須建立與維護因果依賴圖,這是相當困難的。

  • 管道一致性(PRAM/FIFO consistency)

    在因果一致性模型上的進一步弱化,要求由某一個使用者完成的寫入操作可以被其他所有的使用者按照順序的感知到,而從不同使用者中來的寫操作則無需保證順序,就像一個一個的管道一樣。相對來說比較容易實現。

  • 弱一致性(weak consistency)

    只要求對共享的資料結構的訪問保證順序一致性。對於同步變數的操作具有順序一致性,是全局可見的,且只有當沒有寫操作等待處理時才可進行,以保證對於臨界區域的訪問順序進行。在同一時間,所有使用者可以看到相同的資料。

  • 釋放一致性(release consistency)

    弱一致性無法區分使用者是要進入臨界區還是要出臨界區,釋放一致性使用兩個不同的操作語句進行了區分。需要寫入時,使用者先acquire該對象,寫完後release,acquire-release之間形成了一個臨界區,提供釋放一致性也就意味著當release操作發生後,所有使用者應該可以看到該操作。

  • 最終一致性(eventual consistency)

    在沒有新的更新的情況下,更新最終會通過網路傳播到所有副本點,所有副本點最終會一致,也就是說使用者在最終某個時間點前的中間過程中無法保證看到的是新寫入的數據。可以採用最終一致性模型有一個關鍵要求:可以接受讀取到舊的資料狀態。

  • delta consistency

    系統會在delta時間內達到一致。這段時間內會存在一個不一致的窗口,該窗口可能是因為log shipping的過程導致。資料庫完整性(Database Integrity)是指資料庫中數據的正確性和相容性。資料庫完整性由各種各樣的完整性約束來保證,因此可以說資料庫完整性設計就是資料庫完整性約束的設計。

共識演算法 consensus problem

要保障系統滿足不同程度的一致性,往往需要通過共識演算法來達成。共識算法解決的是對某個提案(Proposal),大家達成一致意見的過程。

提案的含義在分佈式系統中十分寬泛,例如多個事件發生的順序、某個鍵對應的值、誰是領導...,可以認為任何需要達成一致的信息都是一個提案。通過訪問足夠多個服務節點來驗證確保獲取共識後結果。

如果分佈式系統中各個節點都能保證以十分強大的性能(即時響應)無故障的運行,則實現共識過程並不複雜,簡單通過投票即可。但實際上每個資料 request 會有網路的傳輸時間延遲,節點可能會故障,甚至存在了惡意的節點要故意破壞系統。

  • 如果只有故障(不響應)的情況,沒有惡意節點,稱為「非拜占庭錯誤」,針對非拜占庭錯誤的情況,一般使用 Paxos、Raft 這一類的演算法。

  • 如果有惡意響應的情況,稱為「拜占庭錯誤」,對於要能容忍拜占庭錯誤的情況,一般使用 PBFT 系列、PoW 系列演算法。

拜占庭將軍問題是一個協議問題,拜占庭帝國軍隊的將軍們必須全體一致的決定是否攻擊某一支敵軍。但這些將軍在地理上是分隔開來的,並且將軍中存在叛徒。叛徒可以任意行動以達到以下目標:欺騙某些將軍採取進攻行動,促成一個不是所有將軍都同意的決定,如當將軍們不希望進攻時促成進攻行動;或者迷惑某些將軍,使他們無法做出決定。如果叛徒達到了這些目的之一,則任何攻擊行動的結果都是註定要失敗的,只有完全達成一致的努力才能獲得勝利。

由於硬體錯誤、網路擁塞或斷開以及遭到惡意攻擊,計算機和網路可能出現不可預料的行為。拜占庭容錯協議必須處理這些失效,並且這些協議還要滿足所要解決的問題要求的規範。

FLP 不可能性原理

在網絡可靠情況下,如果存在節點失效(即便只有一個)的最小化異步模型系統中,不存在一個可以解決一致性問題的確定性算法。

有個實例可以解釋這個原理,三個人在不同房間,進行投票(投票結果是 0 或者 1)。三個人彼此可以通過電話進行溝通,但經常會有人突然睡著。比如某個時候,A 投票 0,B 投票 1,C 收到了兩人的投票,然後 C 睡著了。A 和 B 永遠無法在有限時間內獲知最終的結果。逾時的時候,可以重新投票,如果類似情形每次在取得結果前發生,就永遠無法取得共識。

但加上一些條件限制,就可以達到共識。

CAP 原理

分佈式計算系統不可能同時達到一致性(Consistency)、可用性(Availablity)和分區容忍性(Partition),設計中往往需要弱化對某個特性的保證。

  • 一致性(Consistency)

    任何操作應該都是原子的(不能被中斷),發生在後面的事件能看到前面事件發生導致的結果,注意這裡指的是強一致性

  • 可用性(Availablity)

    在有限時間內,任何非失敗節點都能能夠回應請求的結果

  • 分區容忍性(Partition)

    網路可能發生分區,不保障節點之間的通信。

CAP 三個可以弱化某一個條件,用以設計系統。

  • 弱化一致性 AP

    對結果一致性不敏感的應用,可以允許在新版本的資料上線後過一段時間才更新成功,在更新過程中,不保證一致性。 例如網站靜態頁面內容、實時性較弱的查詢類數據庫等,CouchDB、Cassandra 等為此設計。

  • 弱化可用性 CP

    對結果一致性很敏感的應用,例如銀行取款機,當系統故障時候會拒絕服務。MongoDB、Redis 等為此設計。Paxos、Raft 等算法,主要處理這種情況。

  • 弱化分區容忍性 CA

    網絡分區出現概率減小,但較難避免。某些關聯式資料庫、ZooKeeper 即為此設計。網絡可透過雙路由等機制增強可靠性,達到高穩定的網路通信。

ACID 原則強調一致性 C,失去了可用性

資料庫管理系統(DBMS)在寫入/更新資料的過程中,為保證事務(transaction)是正確可靠的,所必須具備的四個特性:原子性(atomicity,或稱不可分割性)、一致性(consistency)、隔離性(isolation,又稱獨立性)、持久性(durability)。

  • 原子性:一個事務(transaction)中的所有操作,要麼全部完成,要麼全部失敗,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被 Rollback 到事務開始前的狀態,就像這個事務從來沒有執行過一樣。

  • 一致性:在事務開始之前和事務結束以後,資料庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設規則,這包含資料的精確度、串聯性以及後續資料庫可以自發性地完成預定的工作。

  • 隔離性:資料庫允許多個並發事務同時對齊數據進行讀寫和修改的能力,隔離性可以防止多個事務並發執行時由於交叉執行而導致數據的不一致。事務隔離分為不同級別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和串行化(Serializable)。

  • 持久性:transaction 處理結束後,對數據的修改就是永久的,即便系統故障也不會遺失資料。

BASE 原則強調可用性,但失去了一致性

Base: 一種 Acid 的替代方案

BASE(Basic Availiability,Soft state,Eventually Consistency)

  • BA Basic Availiability

    基本可用性

  • S Soft state

    server 在有限度的資源(一般是時間)內維持上下文 context,逾時則拋棄狀態回復到默認狀態。

  • E Eventually Consistency

    允許因延時等出現臨時的數據不一致現象,只要數據最終是一致的就可以。

Paxos 與 Raft

Paxos 是 1990 年由 Leslie Lamport 提出的演算法,針對分散式系統,其中存在著故障的節點,但不存在惡意節點的狀況下,如何達成共識的處理方法。

古希臘 Paxon 島上的多個法官在一個大廳內對一個議案進行表決,但要達成一致的結果。法官之間通過服務人員來傳遞紙條,但法官可能離開或進入大廳,服務人員可能偷懶去睡覺。

Paxos 是以 two-phase commit 的方式來解決問題。

演算法中將節點分為

  1. Proposer: 負責提案,等待大家同意並結案,通常是客戶端 client 擔任這個角色
  2. Acceptor: 對提案進行投票,通常是 server 擔任這個角色
  3. Learner: 被告知投票的結果,並與結果同步,但不參與投票的過程,client 或是 server 都有可能擔任這個角色

  • phase1(準備階段)

  1. Proposer向超過半數(n/2+1)Acceptor發起prepare消息(發送編號)
  2. 如果prepare符合協議規則,Acceptor回覆promise消息,否則拒絕

  • phase2(決議階段或投票階段)

  1. 如果超過半數Acceptor回覆promise,Proposer向Acceptor發送accept消息

  2. Acceptor檢查accept消息是否符合規則,消息符合則批准accept請求

以實際上三個 server 推舉 leader 的議案為例

細節可參閱 一致性算法Paxos詳解

Raft 是 Paxos 的簡化實現,是由Stanford提出的一種更易理解的一致性演算法,意在取代目前廣為使用的Paxos演算法。

包含了三種角色:leader、candiate 和 follower,其基本過程為

  1. Leader 選舉:每個 candidate 隨機經過一定時間都會提出選舉方案,最近階段中得票最多者被選為 leader;
  2. 同步 log:leader 會找到系統中 log 最新的記錄,並強制所有的 follower 來刷新到這個記錄 (log 是各種事件的發生記錄)

PBFT

PBFT Practical Byzantine Fault Tolerance 實用拜占庭容錯算法,是 Miguel Castro 和 Barbara Liskov 在1999年提出來的,解決了原始拜占庭容錯算法效率不高的問題,將算法複雜度由指數級降低到多項式級。

PBFT的一致性協議如下圖所示,每一個客戶端的請求需要5個階段才能完成。PBFT通過採用兩次兩兩交互的方式在服務器達成一致之後再執行客戶端的請求,由於客戶端不能從服務器端獲得任何服務器運行狀態的信息,因此PBFT 協議中主節點是否發生錯誤只能由服務器監測。如果服務器在一段時間內都不能完成客戶端的請求,則會觸發視圖更換協議。

PBFT的熱門應用是在IBM主導的區塊鏈超級賬本項目中,除了PBFT之外,超級賬本項目還引入了基於PBFT的自用共識協議Sieve,它的目的是希望在PBFT基礎之上能夠對節點的輸出也做好共識,這是因為超級賬本項目的一個重要功能是提供區塊鏈之上的智能合約——就是在區塊鏈上執行的一段代碼,因此它會帶來區塊鏈賬本上最終狀態的不確定,為此Sieve會在PBFT實現的基礎之上引入代碼執行結果簽名進行驗證。

POW

Bitcoin 採用這種演算法。簡單理解就是一份證明,用來確認你做過一定量的工作,通過對工作的結果進行認證來證明完成了相應的工作量。

比特幣在Block的生成過程中使用了POW機制,一個符合要求的Block Hash由N個前導零構成,零的個數取決於網絡的難度值。要得到合理的Block Hash需要經過大量嘗試計算,計算時間取決於機器的哈希運算速度。

當某個節點提供出一個合理的Block Hash值,說明該節點確實經過了大量的嘗試計算,當然,並不能得出計算次數的絕對值,因為尋找合理hash是一個概率事件。當節點擁有佔全網n%的算力時,該節點即有n/100的概率找到Block Hash。

工作量證明就是假設如果一個人願意花 10 分鐘寫一封郵件,他就不會在意再多花一分鐘對其進行處理,以證明自己寫郵件付出的努力是真實的。

對比特幣而言,挖礦(Mining)也是使用隨機數進行工作量證明的過程。這種過程雖然從表面上來看沒有產生任何價值,但卻是解決互聯網中信任問題的有效辦法,是在不可靠的網路環境中一種較為可靠的信用證明。

References

一致性問題

基礎|想要成為大數據工程師?你需要掌握以下知識(下)

分佈式一致性算法 Paxos 介紹

Paxos算法

我所理解的Paxos

分佈式系統Paxos算法

拜占庭將軍問題

區塊鏈核心技術:拜占庭共識算法之PBFT

共識算法 區塊鏈實用手冊

從Paxos到拜占庭容錯,兼談區塊鏈的共識協議

區塊鏈的 consensus

比特幣和區塊鏈之:什麼是工作量證明?

比特幣基礎概念–工作量證明(Proof-of-Work)

2017/04/10

Hyperledger: blockchain 超級帳本

自 bitcoin 開始,blockchain被視為是下一代新的技術革新,因為本質為去中心化的一個資料庫,也就是用了 P2P 的技術,來解決原本集中式的網路架構,所造成的網路互信問題。

一般認為 blockchain 會是一項改變世界的創新技術,但也由於這樣分散式的架構,所有參與這個 blockchain 的網路節點,都會是一個參與驗證的獨立個體,每一個節點的運算速度跟網路頻寬,也間接影響了 blockchain 所能提供的每秒最大交易數量(tps transaction per second)的速度。

因此 blockchain 由 bitcoin 時代的 public blockchain,演進出現了 federated blockchain 以及 private blockchain,速度最快的 private blockchain 也是最貼近企業應用的一種 blockchain。

事實上也有人認為私有鏈並不是 blockchain,就像是傳統的資料庫一樣,只是換了一個 blockchain 的包裝,這就像是一種既有的分散式帳本技術而已,可以參閱這篇文章的討論:全面認識區塊鏈:公有鏈vs私有鏈,關於 blockchain 的企業應用目前並沒有很明確的答案。

Hyperledger

Hyperledger 是 2015 年 12 月由 Linux 基金會聯合了三十家公司,合作的一個 private blockchain 專案,它可說是繼 bitcoin,ethereum 之後,最受關注的一個 blockchain 專案。

在超級賬本聯盟成立之前,IBM公司就已經開源了一個 Open Blockchain,OBC 專案。在聯盟成立之後,IBM把OBC項目約 44000 行代碼貢獻給了Linux基金會,這部分代碼成為了Fabric的代碼的主要組成部分。在2016年3月的一次黑客松編程活動中,Blockstream和數字資產兩個成員公司把各自的區塊鏈功能代碼融合到OBC中,最終建立了Fabric的雛形,也就是Fabric項目進入孵化階段的基礎代碼。

目前有三個帳本平台項目

  • fabric:包括 fabric 和 fabric-api、fabric-sdk-node、fabric-sdk-py 等,目標是區塊鏈的基礎核心平台,支持 pbft 等新的 consensus 機制,支持權限管理,最早由 IBM 和 DAH 發起;
  • sawtooth Lake:包括 arcade、core、dev-tools、validator、mktplace 等。是 Intel 主要發起和貢獻的區塊鏈平台,支持全新的基於硬體晶片的共識機制 Proof of Elapsed Time(PoET)。
  • Iroha:賬本平台項目,主要由 Soramitsu 發起和貢獻。

測試 hyperledger smartcontract

ref: 從零開始,5分鐘創建並玩轉屬於自己的區塊鏈(圖文攻略)

IBM中國研究院開發的 SuperVessel 平臺提供了給區塊鏈愛好者、開發者的區塊鏈開發測試環境。通過該平臺,用戶能夠免費、超快速創建基於Hyperledger Fabric的多節點區塊鏈。

  1. SuperVessel 區塊鏈 申請帳號

  2. 點中間的 MY DASHBOARD

  3. 點擊 Chain -> Apply a new chain,產生一個 blockchain,我們選擇 pbft 演算法,4 個 nodes

  4. 主畫面是剛剛建立的 test pbft blockchain

  5. Smart Contract 裡面已經有兩個 sample: map 跟 chaincode_example02

  6. 回到 test pbft,點右下角的 deploy,選擇 chaincode_example02,任意一個 instance name,init a 與 b 分別有 100 及 200 元

  7. 點 invoke,function: transfer 就是轉帳,由 a 帳戶轉給 b 50 元

  8. 可以 Query a 或 b 帳戶目前的餘額

  9. 帳戶異動的動作都會產生一個新的 block

如果在同一個 blockchain 部署兩個 smart contact,blocks 上的區塊是會累積起來的。

以 docker 測試 hyperledger

ref: 區塊鏈技術指南

用 dokcer 下載 hyperledger images

$ docker pull hyperledger/fabric-peer:x86_64-0.6.1-preview \
  && docker pull hyperledger/fabric-membersrvc:x86_64-0.6.1-preview \
  && docker pull yeasy/blockchain-explorer:latest \
  && docker tag hyperledger/fabric-peer:x86_64-0.6.1-preview hyperledger/fabric-peer \
  && docker tag hyperledger/fabric-peer:x86_64-0.6.1-preview hyperledger/fabric-baseimage \
  && docker tag hyperledger/fabric-membersrvc:x86_64-0.6.1-preview hyperledger/fabric-membersrvc

可使用 noops 或是 PBFT 兩種不同的一致性演算法

如果使用 noops 可以只開一個節點

$ docker run --name=vp0 \
    --restart=unless-stopped \
    -it \
    -p 7050:7050 \
    -p 7051:7051 \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -e CORE_PEER_ID=vp0 \
    -e CORE_PEER_ADDRESSAUTODETECT=true \
    -e CORE_NOOPS_BLOCK_WAIT=10 \
    hyperledger/fabric-peer:latest peer node start

PBFT 是比較常用的一致性演算法,只少需要四個節點

在 yeasy/docker-compose-files 有幾個 template,可以用 git 下載

git clone https://github.com/yeasy/docker-compose-files

在 docker-compose-files/hyperledger/0.6/pbft 目錄中有以下這些 templates

  1. 4-peers.yml: 啟動四個 PBFT peer 節點
  2. 4-peers-with-membersrvc.yml: 啟動 4 個 PBFT peer 節點 + 1 個 CA 節點,並啟用 CA 功能。
  3. 4-peers-with-explorer.yml: 啟動 4 個 PBFT peer 節點 + 1 個 Blockchain-explorer,可以通過 Web 界面監控集群狀態。
  4. 4-peers-with-membersrvc-explorer.yml: 啟動 4 個 PBFT peer 節點 + 1 個 CA 節點 + 1 個 Blockchain-explorer,並啟用 CA 功能。

可用 docker-compose 快速啟動一個 4 個 PBFT 節點的集群

$ docker-compose -f 4-peers.yml up

如果要自己一個一個慢慢建立節點

# vp0 初始的探測節點 (10.0.0.1)
$docker run --name=vp0 \
    --net="host" \
    --restart=unless-stopped \
    -it --rm \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -e CORE_PEER_ID=vp0 \
    -e CORE_PBFT_GENERAL_N=4 \
    -e CORE_LOGGING_LEVEL=debug \
    -e CORE_PEER_ADDRESSAUTODETECT=true \
    -e CORE_PEER_NETWORKID=dev \
    -e CORE_PEER_VALIDATOR_CONSENSUS_PLUGIN=pbft \
    -e CORE_PBFT_GENERAL_MODE=batch \
    -e CORE_PBFT_GENERAL_TIMEOUT_REQUEST=10s \
    hyperledger/fabric-peer:latest peer node start

# vp1 ~ vp3
$ NAME=vp1
$ ROOT_NODE=10.0.0.1
$ docker run --name=${NAME} \
    --net="host" \
    --restart=unless-stopped \
    -it --rm \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -e CORE_PEER_ID=${NAME} \
    -e CORE_PBFT_GENERAL_N=4 \
    -e CORE_LOGGING_LEVEL=debug \
    -e CORE_PEER_ADDRESSAUTODETECT=true \
    -e CORE_PEER_NETWORKID=dev \
    -e CORE_PEER_VALIDATOR_CONSENSUS_PLUGIN=pbft \
    -e CORE_PBFT_GENERAL_MODE=batch \
    -e CORE_PBFT_GENERAL_TIMEOUT_REQUEST=10s \
    -e CORE_PEER_DISCOVERY_ROOTNODE=${ROOT_NODE}:7051 \
    hyperledger/fabric-peer:latest peer node start
測試 example02 smart contract

使用 docker-compose 快速啟動一個 4 個 PBFT 節點的集群,如果剛剛有啟動過,就先把舊的 containers 刪除

$ docker-compose -f 4-peers.yml up

進入 pbftvp01

$ docker exec -it pbft_vp0_1 bash

部署 example02 chain code,就是 init a 100 元 b 200 元兩個帳戶

# peer chaincode deploy -p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02 -c '{"Function":"init", "Args": ["a","100", "b", "200"]}'

03:42:46.851 [chaincodeCmd] chaincodeDeploy -> INFO 001 Deploy result: type:GOLANG chaincodeID:<path:"github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02" name:"ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539" > ctorMsg:<args:"init" args:"a" args:"100" args:"b" args:"200" >
Deploy chaincode: ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539
03:42:46.852 [main] main -> INFO 002 Exiting.....

得到的 codecode id,放在環境變數中

$ CC_ID=ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539

查詢 a 帳戶的餘額

$ peer chaincode query -n ${CC_ID} -c '{"Function": "query", "Args": ["a"]}'

03:45:54.865 [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Successfully queried transaction: chaincodeSpec:<type:GOLANG chaincodeID:<name:"ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539" > ctorMsg:<args:"query" args:"a" > >
Query Result: 100
03:45:54.865 [main] main -> INFO 002 Exiting.....

a 轉帳 50 元給 b

$ peer chaincode invoke -n ${CC_ID} -c '{"Function": "invoke", "Args": ["a", "b", "50"]}'

03:46:58.806 [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Successfully invoked transaction: chaincodeSpec:<type:GOLANG chaincodeID:<name:"ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539" > ctorMsg:<args:"invoke" args:"a" args:"b" args:"50" > > (b63aead7-154c-42bb-8ae5-3a880f305f9e)
03:46:58.806 [main] main -> INFO 002 Exiting.....

查詢 a 帳戶的餘額

$ peer chaincode query -n ${CC_ID} -c '{"Function": "query", "Args": ["a"]}'

03:47:25.763 [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Successfully queried transaction: chaincodeSpec:<type:GOLANG chaincodeID:<name:"ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539" > ctorMsg:<args:"query" args:"a" > >
Query Result: 50
03:47:25.763 [main] main -> INFO 002 Exiting.....

測試 權限管理

啟動帶成員管理的 PBFT 集群,包含 4 個 PBFT peer 節點 + 1 個 CA 節點 + 1 個 Blockchain-explorer,並啟用 CA 功能。

docker-compose -f 4-peers-with-membersrvc.yml up

連線到 pbftvp01

docker exec -it pbft_vp0_1 bash

以內建帳號 jim (密碼為: 6avZQLwcUe9b)登錄到系統

# peer network login jim
08:09:30.235 [networkCmd] networkLogin -> INFO 001 CLI client login...
08:09:30.236 [networkCmd] networkLogin -> INFO 002 Local data store for client loginToken: /var/hyperledger/production/client/
Enter password for user 'jim': 6avZQLwcUe9b
08:11:18.446 [networkCmd] networkLogin -> INFO 003 Logging in user 'jim' on CLI interface...
08:11:18.881 [networkCmd] networkLogin -> INFO 004 Storing login token for user 'jim'.
08:11:18.882 [networkCmd] networkLogin -> INFO 005 Login successful for user 'jim'.
08:11:18.882 [main] main -> INFO 006 Exiting.....

先安裝 curl

apt-get update
apt-get install curl

用 POST 的方式呼叫 register

$ curl -X POST -H 'Content-Type: application/json' -d '{"enrollId": "jim","enrollSecret": "6avZQLwcUe9b"}' http://localhost:7050/registrar

{"OK":"User jim is already logged in."}

接下來 chaincode 的部屬跟呼叫都需要再加上 -u 指定帳號。

# peer chaincode deploy -u jim -p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02 -c '{"Function":"init", "Args": ["a","100", "b", "200"]}'

08:20:35.263 [chaincodeCmd] getChaincodeSpecification -> INFO 001 Local user 'jim' is already logged in. Retrieving login token.
08:20:38.338 [chaincodeCmd] chaincodeDeploy -> INFO 002 Deploy result: type:GOLANG chaincodeID:<path:"github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02" name:"ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539" > ctorMsg:<args:"init" args:"a" args:"100" args:"b" args:"200" >
Deploy chaincode: ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539
08:20:38.338 [main] main -> INFO 003 Exiting.....

紀錄 chaincode ID

$ CC_ID=ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539

查詢帳戶 a 的餘額為 100

$ peer chaincode query -u jim -n ${CC_ID} -c '{"Function": "query", "Args": ["a"]}'

08:22:25.600 [chaincodeCmd] getChaincodeSpecification -> INFO 001 Local user 'jim' is already logged in. Retrieving login token.
08:22:25.876 [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 002 Successfully queried transaction: chaincodeSpec:<type:GOLANG chaincodeID:<name:"ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539" > ctorMsg:<args:"query" args:"a" > secureContext:"jim" >
Query Result: 100
08:22:25.876 [main] main -> INFO 003 Exiting.....

用 REST 方式進行 query

$ curl -X POST -H 'Content-Type: application/json' -d '
{
  "jsonrpc": "2.0",
  "method": "invoke",
  "params": {
      "type": 1,
      "chaincodeID":{
          "name":"ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539"
      },
      "ctorMsg": {
         "function":"query",
         "args":["a"]
      },
    "secureContext": "jim"
  },
  "id": 3
}
' http://localhost:7050/chaincode

{"jsonrpc":"2.0","result":{"status":"OK","message":"c62697b9-5ade-41ff-99ef-0733cf99c05d"},"id":3}

查詢 block 資訊

$ curl -X GET http://localhost:7050/chain/blocks/2

{"stateHash":"QkIbllrDhpZ1+ZGCTwbu83CEnR9oA8/fECHDCvYNz6wjdpxvCS/aTsG24NbDAhMtHQmhq12yhoCYmSLgGvLm+A==","previousBlockHash":"DnwB438SiFir+5AoeUPLup8l1qEZN1ddPdgOPr2BnbBQT4W0ENOPym1o4c0IkAHC+xuQiw68cXkcSjcrZi1CJg==","consensusMetadata":"CAI=","nonHashData":{"localLedgerCommitTimestamp":{"seconds":1482913592,"nanos":719911090},"chaincodeEvents":[{}]}}

References

區塊鏈科技趨勢與應用

專訪R3聯盟高層:分散式分類帳和區塊鏈有何不同?

中国区块链技术和应用发展白皮书 2016

五個問答讓你秒懂區塊鏈原理及應用

最具商用價值的開源區塊鏈項目:超級賬本

hyperledger 官方網站

hyperledger fabric doc

區塊鏈組織-超級賬本(Hyperledger)的簡介

區塊鏈在中國:IBM HyperLedger

Hyperledger智能合约Hello World示例程序

學習鏈碼

在开发环境下编写,运行,测试chaincode

hyperledger fabric本地開發環境mac部署

Hyperledger fabric 開發環境搭建

2017/04/08

Dcoker network - bridge

本篇是閱讀 docker network 官方相關文件後一些自己覺得重要的內容而做的筆記,主要是以 bridge 為主,其他詳細的內容可以參考 Docker container networking

network 簡介

network 為 docker 的一項功能,讓你可以建立虛擬網路,將 container 加到網路內,建立起屬於你自己應用程式的網路拓墣。另外 network 也可以橫跨多台主機,讓你實現分散式應用服務。

安裝 Docker 後,預設會有三個 network,分別為 bridge、none 以及 host, 你可以用以下指令查看相關資訊:

# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
1623cc36aeae        bridge              bridge              local
7039bb756843        host                host                local
564a81a95b49        none                null                local

bridge 是預設的 network,在你使用 docker run 指令啟動 container 時,如果你沒有下 --network 參數指定,則預設都會是 bridge。 你可以透過以下指令來觀察 bridge 的詳細參數:

[root@lzstg ~]# docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "1623cc36aeae8538c05780f86517dc07d6dc613c4b9d13ad6f949e33fced8f1e",
        "Created": "2017-03-06T11:35:35.006599348+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Containers": {
            "5b7be3dc833e3b2a46d9f6510af70e507f0cb068653fca024da7df8d5c86276b": {
                "Name": "miniweb_lzstg",
                "EndpointID": "bd107fcd1fbe5fe269665797b4fbd6728bfd8df06afdf5871ca10d8d426d4888",
                "MacAddress": "02:42:ac:11:00:03",
                "IPv4Address": "172.17.0.3/16",
                "IPv6Address": ""
            },
            "f3aabba6c0a2bc1fac6a262854b0fb1980e5c3329552f63d1fbc61c1eb45ab7d": {
                "Name": "miniweb",
                "EndpointID": "7c8cd504316cd351784a6afee89a4eea48f079e522883322d3f7a253207acf55",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

在 IPAM -> Config 底下可以看見,Subnet 設定為 172.17.0.0/16,而 Gateway 為 172.17.0.1。

在 Containers 設定內,如果你已經有啟動 container 且它的 network 為 bridge,這邊就會有資料,否則會是 {};另外也可以看到這邊啟動了兩個 container,IP 分別為 172.17.0.3 與 172.17.0.2。

其他兩個預設建立的 network 分別為 host 與 none,設為 host 則 container 的網路會與你 docker host 主機的網路設定相同。舉例來說,如果你啟動一個 tomcat container,並且把它的 network 設為 host,當 container 啟動時,你就可以直接在瀏覽器上輸入你的機器 ip,port 為 8080,就能開啟 tomcat 管理頁面了。設定為 none 顧名思義就是 container 不使用任何網路介面卡。

預設的 bridge

上面有提到,在啟動 container 時如果沒有加上 --network 指令來指定網路設定,則都會使用預設的 bridge。在預設的 bridge 底下的 container,是可以透過 ip 互相 ping 對方的。

比如說我先啟動兩個 container,命名為 foo1 與 foo2,然後在使用 docker network inspect 指令觀察他們配到的 ip,如下:

$ docker run --name foo1 -d tomcat:8.5.11-jre8
$ docker run --name foo2 -d tomcat:8.5.11-jre8
$ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "f2a2d7dd6e4ab4edd774ef0d80f8b91f5b80a8f08b0f5162a35c02abf41ef4e1",
        "Created": "2017-03-31T11:30:40.628367278Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Containers": {
            "70cdec387fc68128b9968689b9bf883150b180ff37134756a97fec2c881b62a4": {
                "Name": "foo2",
                "EndpointID": "3ed1dcee3d7805fd61cf5436b53d18563314c9abdb617f9d0665b19ff986fa6a",
                "MacAddress": "02:42:ac:11:00:03",
                "IPv4Address": "172.17.0.3/16",
                "IPv6Address": ""
            },
            "c52be8382496c30d735620a601167c1de527c47cbead841e747be2b3bec6843d": {
                "Name": "foo1",
                "EndpointID": "3601fcf1ea3b94d1150925e10238795eab2d7165394024006542516619a105fb",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

可以在 Containers 的設定內看到,foo1 配到的 ip 為 172.17.0.2,而 foo2 配到的 ip 為 172.17.0.3,接著我們使用 docker exec 指令進入到 foo1 的 bash,然後看看能不能 ping 到 foo2(172.17.0.3)

$ docker exec -it foo1 /bin/bash
root@c52be8382496:/usr/local/tomcat# ping 172.17.0.3 -c 4
PING 172.17.0.3 (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: icmp_seq=0 ttl=64 time=0.171 ms
64 bytes from 172.17.0.3: icmp_seq=1 ttl=64 time=0.094 ms
64 bytes from 172.17.0.3: icmp_seq=2 ttl=64 time=0.094 ms
64 bytes from 172.17.0.3: icmp_seq=3 ttl=64 time=0.115 ms
--- 172.17.0.3 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.094/0.118/0.171/0.032 ms

透過 ip ping 的結果是能找到對方的,但是若是透過 container name 則無法找到對方,如下:

root@c52be8382496:/usr/local/tomcat# ping foo2
ping: unknown host

要能透過 container name 直接找到其他 container,有兩種方式,一種是 docker 比較早期的方法,在 docker run 時加上 --link,讓 container 連接在一起,此方法可以參考官網的這篇文件 Legacy container links,官方有說明 --link 最終可能會被棄用,有興趣的可以閱讀此篇文章,這邊不多做說明。另一種方式就是使用自己定義的 network 來達成。

自定義的 bridge network

我們可以使用 docker network create 指令來建立一個新的網路拓墣。

docker network create foonet

接著在使用 docker run 啟動 container 時,使用 --network flag 將 container 指定到此網路拓墣:

docker run -d --network foonet --name foobar1 tomcat:8.5.11-jre8
docker run -d --network foonet --name foobar2 tomcat:8.5.11-jre8

我們可以透過 docker network inspect 指令觀察目前網路狀態,可以在印出來的 Containers 設定內找到兩個加入到此網路拓墣的 container 網路資訊。

docker network inspect foonet
[
    {
        "Name": "foonet",
        "Id": "c15a5f50792fe854f7722d266ca4cc7e57ccf9ac3dfe479dd711959de55deb0d",
        "Created": "2017-03-25T02:37:24.164070946Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Containers": {
            "8c5956967f3e9c0701a84aa868f08053a8f03d691c509c37c462f045dedaa827": {
                "Name": "foobar2",
                "EndpointID": "576f9ee3f923af8838d4ce126de16b0c576dcb2aa814f642a482b1b54dd6f01d",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            },
            "b219692bba64311507c23bf4ee69ff33f415ebfcffe3d412b28fa7a44b942356": {
                "Name": "foobar1",
                "EndpointID": "37138005c073250f90f94cad523fbab656155a68f9a238f7a529d116a1bf0833",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

最後我們進入到其中一個 container 內,然後使用 container name 來 ping 對方,看看找不找的到對方。可以從結果看出,若是在自己定義的網路拓墣內,是可以直接用 container name 來找到對方機器的。

docker exec -it foobar1 /bin/bash
root@b219692bba64:/usr/local/tomcat# ping foobar2
PING foobar2 (172.18.0.3): 56 data bytes
64 bytes from 172.18.0.3: icmp_seq=0 ttl=64 time=0.151 ms
64 bytes from 172.18.0.3: icmp_seq=1 ttl=64 time=0.141 ms
64 bytes from 172.18.0.3: icmp_seq=2 ttl=64 time=0.285 ms

參考

Docker container networking

Work with network commands