《Docker:從入門到實踐》學習筆記(二):資料卷、網路、安全

前言

本文複習 Docker 相關概念與指令。

資料卷

資料卷是一種可供一或多個容器使用的特殊目錄,它繞過 UFS(通用快閃記憶體儲存),提供很多有用的特性:

  • 資料卷可以在容器之間共享和重用。
  • 對資料卷的修改會立即生效。
  • 對資料卷的更新,不會影響映像檔。
  • 卷會一直存在,直到沒有容器使用。

建立資料卷

使用 -v 參數建立一個資料卷並掛載到容器裡。

1
docker run -ti --name test -v /dbdata ubuntu bash

也可以指定掛載一個本地主機的目錄到容器裡。

1
docker run -ti --name test -v ~/Workspace/docker:/dbdata ubuntu bash

注意,本地目錄的路徑必須是絕對路徑,如果目錄不存在,Docker 會自動建立。

Docker 掛載資料卷的預設權限是讀寫,使用者也可以透過 :ro 參數指定為唯讀。

1
docker run -ti --name test -v ~/Workspace/docker:/dbdata:ro ubuntu bash

建立資料卷容器

如果有一些持續更新的資料需要在容器之間共享,最好建立資料卷容器。資料卷容器其實就是一個一般的容器,專門用來提供資料卷供其它容器掛載。

首先,建立一個名為 dbdata 的資料卷容器。

1
docker run -d --name dbdata -v /dbdata ubuntu

然後,在其他容器中使用 --volumes-from 參數來掛載 dbdata 容器中的資料卷。

1
2
docker run -d --volumes-from dbdata --name test1 ubuntu
docker run -d --volumes-from dbdata --name test2 ubuntu

使用 docker rm -v 命令來刪除關聯的容器。

1
docker rm -v dbdata test1 test2

備份

首先,建立一個名為 dbdata 的資料卷容器。

1
docker run -t -d --name dbdata -v /dbdata ubuntu

使用 --volumes-from 參數來建立一個載入 dbdata 容器卷的容器,並從本地主機掛載當前目錄到容器的 /backup 目錄,再使用 tar 指令進行壓縮。

1
docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvfP /backup/backup.tar /dbdata

復原

首先,建立一個名為 dbdata2 的資料卷容器。

1
docker run -t -d --name dbdata2 -v /dbdata ubuntu

使用 --volumes-from 參數來建立一個載入 dbdata2 容器卷的容器,並從本地主機掛載當前目錄到容器的 /backup 目錄,再使用 tar 指令進行解壓縮。

1
docker run -t -d --volumes-from dbdata2 -v $(pwd):/backup ubuntu tar xvfP /backup/backup.tar

網路

網路架構

當 Docker 啟動時,會自動在主機上建立一個 docker0 虛擬橋接器,實際上是 Linux 的一個 bridge,可以理解為一個軟體交換機。它會在掛載到它的網卡之間進行轉發。

同時,Docker 隨機分配一個本地未占用的私有網段(在 RFC1918 中定義)中的一個位址給 docker0 界面。比如典型的 172.17.42.1,網路遮罩為 255.255.0.0。此後啟動的容器內的網卡也會自動分配一個同一網段(172.17.0.0/16)的網址。

當建立一個 Docker 容器的時候,同時會建立了一對 veth pair 界面(當資料包發送到一個界面時,另外一個界面也可以收到相同的資料包)。這對界面一端在容器內,即 eth0;另一端在本地並被掛載到 docker0 橋接器,名稱以 veth 開頭(例如 vethAQI2QT)。透過這種方式,主機可以跟容器通訊,容器之間也可以相互通訊。Docker 就建立了在主機和所有容器之間一個虛擬共享網路。

外部存取

容器中可以執行一些網路應用,要讓外部也可以存取這些應用,可以透過 -P-p 參數來指定連接埠映射。

當使用 -P 參數時,Docker 會暴露所有容器開放的網路連接埠。

1
2
3
4
docker run -d -P --name nginx nginx
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
733f68958e83 nginx "/docker-entrypoint.…" 3 seconds ago Up 2 seconds 0.0.0.0:55001->80/tcp nginx

當使用 -p 參數時,可以將本地的 80 連接埠映射到容器的 80 連接埠。

1
2
3
4
docker run -d -p 80:80 --name nginx nginx
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d65ab2cd7336 nginx "/docker-entrypoint.…" 2 seconds ago Up 1 second 0.0.0.0:80->80/tcp nginx

或者,可以指定一個特定位址,比如 127.0.0.180 連接埠。

1
2
3
4
docker run -d -p 127.0.0.1:80:80 --name nginx nginx
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
117d76809326 nginx "/docker-entrypoint.…" 3 seconds ago Up 2 seconds 127.0.0.1:80->80/tcp nginx

使用 docker port 指令查看連接埠。

1
2
docker port nginx
80/tcp -> 0.0.0.0:80

使用 docker inspect 查看容器的網路設定。

1
docker inspect nginx

容器互連

建立一個資料庫容器。

1
docker run -d --name db mysql/mysql-server

使用 --link 參數,將它連接到資料庫容器。格式為 name:alias,其中 name 是要連接的容器名稱,alias 是這個連接的別名。

1
docker run -d -p 80:80 --name ubuntu --link db:db ubuntu

Docker 在兩個互聯的容器之間創建了一個安全隧道,而且不用映射它們的連接埠到宿主主機上。在啟動資料庫容器的時候並沒有使用 -p-P 參數,從而避免了暴露資料庫連接埠到外部網路上。

使用 env 指令來查看 ubuntu 容器的環境變數。

1
2
3
DB_PORT_33060_TCP_ADDR=172.17.0.4
DB_PORT_3306_TCP_PORT=3306
...

其中 DB_ 開頭的環境變數是供 ubuntu 容器連接 db 容器使用,前綴採用大寫的連接別名。

除了環境變數,Docker 還新增 host 訊息到父容器的 /etc/hosts 的檔案。

1
2
3
4
5
6
7
8
9
cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.4 db 843b83837114
172.17.0.6 ee6e5abc9f65

另外,可以在 ubuntu 容器中安裝 ping 指令來測試跟 db 容器的連通。

1
apt-get update && apt-get install -yqq inetutils-ping

使用 ping 指令測試連通。

1
2
3
4
ping db
PING db (172.17.0.4): 56 data bytes
64 bytes from 172.17.0.4: icmp_seq=0 ttl=64 time=0.127 ms
64 bytes from 172.17.0.4: icmp_seq=1 ttl=64 time=0.297 ms

最後,使用者可以連接多個子容器到父容器,比如可以連接多個應用程式容器到 db 容器上。

安全

評估 Docker 的安全性時,主要考慮三個方面:

  • 由核心的命名空間和控制組機制提供的容器內在安全。
  • Docker 程式(特別是服務端)本身的抗攻擊性。
  • 核心安全性的加強機制對容器安全性的影響。

核心命名空間

Docker 容器和 LXC 容器很相似,所提供的安全特性也差不多。當用 docker run 命令啟動一個容器時,在後台 Docker 為容器建立了一個獨立的命名空間和控制組集合。

命名空間提供了最基礎也是最直接的隔離,在容器中執行的程式不會被執行在主機上的程式和其它容器發現和作用。

每個容器都有自己獨有的網路堆疊,意味著它們不能存取其他容器的 sockets 或界面。不過,如果主機系統上做了相應的設定,容器可以像跟主機互動一樣地和其他容器互動。當指定公共連接埠或使用 links 來連接 2 個容器時,容器就可以相互通訊了(可以根據設定來限制通訊的策略)。

從網路架構的角度來看,所有的容器透過本地主機的橋接器界面相互通訊,就像物理機器透過物理交換機通訊一樣。

核心命名空間從 2.6.15 版本(2008 年 7 月發布)之後被引入,數年間,這些機制的可靠性在諸多大型生產系統中被實踐驗證。

實際上,命名空間的想法和設計提出的時間要更早,最初是為了在核心中引入一種機制來實作 OpenVZ 的特性。 而 OpenVZ 專案早在 2005 年就發布了,其設計和實作都已經十分成熟。

控制組

控制組是 Linux 容器機制的另外一個關鍵元件,負責實作資源的統計和限制。

它提供了很多有用的特性,以及確保各個容器可以公平地分享主機的記憶體、CPU、磁碟 IO 等資源;當然,更重要的是,控制組確保了當容器內的資源使用產生負載時不會連累主機系統。

儘管控制組不負責隔離容器之間相互存取、處理資料和程式,它在防止分散式阻斷服務(DDOS)攻擊方面是必不可少的。尤其是在多使用者的平台(比如公有或私有的 PaaS)上,控制組十分重要。例如,當某些應用程式表現異常的時候,可以保證一致地正常執行和效能。

控制組機制始於 2006 年,核心從 2.6.24 版本開始被引入。

伺服端防護

執行一個容器或應用程式的核心是透過 Docker 服務端。Docker 服務的執行目前需要 root 權限,因此其安全性十分關鍵。

首先,確保只有可信的使用者才可以存取 Docker 服務。Docker 允許使用者在主機和容器間共享檔案夾,同時不需要限制容器的存取權限,這就容易讓容器突破資源限制。例如,惡意使用者啟動容器的時候將主機的根目錄 / 映射到容器的 /host 目錄中,那麽容器理論上就可以對主機的檔案系統進行任意修改了。這聽起來很瘋狂,但是事實上幾乎所有虛擬化系統都允許類似的資源共享,而沒法禁止使用者共享主機根檔案系統到虛擬機系統。

這將會造成很嚴重的安全後果。因此,當提供容器建立服務時(例如透過一個網頁伺服器),要更加注意進行參數的安全檢查,防止惡意的使用者用特定參數來建立一些破壞性的容器。

為了加強對服務端的保護,Docker 的 REST API 在 0.5.2 版本之後使用本地的 Unix socket 機制替代了原先綁定在 127.0.0.1 上的 TCP socket,因為後者容易遭受跨站腳本攻擊。現在使用者使用 Unix 權限檢查來加強 socket 的存取安全。

使用者仍可以利用 HTTP 提供 REST API 存取。建議使用安全機制,確保只有可信的網路或 VPN,或憑證保護機制(例如受保護的 stunnel 和 SSL 認證)下的存取可以進行。此外,還可以使用 HTTPS 和憑證來加強保護。

最近改進的 Linux 命名空間機制將可以實作使用非 root 使用者來執行全功能的容器。這將從根本上解決了容器和主機之間共享檔案系統而引起的安全問題。

終極目標是改進 2 個重要的安全特性:

  • 將容器的 root 使用者映射到本地主機上的非 root 使用者,減輕容器和主機之間因權限提升而引起的安全問題;
  • 允許 Docker 服務端在非 root 權限下執行,利用安全可靠的子行程來代理執行需要特權權限的操作。這些子行程將只允許在限定範圍內進行操作,例如僅僅負責虛擬網路設定或檔案系統管理、設定操作等。

最後,建議採用專用的伺服器來執行 Docker 還有相關的管理服務(例如 ssh 監控、程式監控、管理工具 nrpe、collectd 等),而其它的業務服務都放到容器中去執行。

核心能力機制

能力機制(Capability)是 Linux 核心一個強大的特性,可以提供細緻的權限存取控制。Linux 核心自 2.2 版本起就支援能力機制,它將權限劃分為更加細緻的操作能力,既可以作用在程式上,也可以作用在檔案上。

例如,一個 Web 服務程式只需要綁定一個低於 1024 的連接埠的權限,並不需要 root 權限。那麽它只需要被授權 net_bind_service 能力即可。此外,還有很多其他的類似能力來避免程式取得 root 權限。

預設情況下,Docker 啟動的容器被嚴格限制只允許使用核心的一部分能力。

使用能力機制對加強 Docker 容器的安全有很多好處。通常,在伺服器上會執行一堆需要特權權限的程式,包括有 ssh、cron、syslogd、硬體管理工具模組(例如負載模組)、網路設定工具等等。容器跟這些程式是不同的,因為幾乎所有的特權程式都由容器以外的支援系統來進行管理:

  • ssh 存取被主機上 ssh 服務來管理;
  • cron 通常應該作為使用者程式執行,權限交給使用它服務的應用來處理;
  • 日誌系統可由 Docker 或第三方服務管理;
  • 硬體管理無關緊要,容器中也就無需執行 udevd 以及類似服務;
  • 網路管理也都在主機上設定,除非特殊需求,容器不需要對網路進行設定。

從上面的例子可以看出,大部分情況下,容器並不需要「真正」的 root 權限,容器只需要少數的能力即可。為了加強安全,容器可以禁用一些沒必要的權限:

  • 完全禁止任何 mount 操作;
  • 禁止直接存取本地主機的 socket;
  • 禁止存取一些檔案系統的操作,比如建立新的設備、修改檔案屬性等;
  • 禁止模組載入。

這樣,就算攻擊者在容器中取得了 root 權限,也不能獲得本地主機的較高權限,能進行的破壞也有限。

預設情況下,Docker 採用白名單機制,禁用必需功能之外的其它權限。當然,使用者也可以根據自身需求來為 Docker 容器啟用額外的權限。

其他安全特性

除了能力機制之外,還可以利用一些現有的安全機制來增強使用 Docker 的安全性,例如 TOMOYO、AppArmor、SELinux、 GRSEC 等。

Docker 當前預設只啟用了能力機制。使用者可以採用多種方案來加強 Docker 主機的安全,例如:
在核心中啟用 GRSEC 和 PAX,這將增加很多編譯和執行時的安全檢查;透過位址隨機化避免惡意探測等,並且啟用該特性不需要 Docker 進行任何設定。

使用一些有增強安全特性的容器模板,比如帶 AppArmor 的模板,和 Redhat 帶 SELinux 策略的模板。這些模板提供了額外的安全特性。

使用者可以自定義存取控制機制來定制安全策略。

跟其它新增到 Docker 容器的第三方工具一樣(比如網路拓撲和檔案系統共享),有很多類似的機制,在不改變 Docker 核心情況下就可以強化現有的容器。

參考資料