Docker CheatSheet | Docker 配置與實踐清單

王下邀月熊發表於2018-09-18

? 節選自 Awesome CheatSheet/Docker CheatSheet,對來自官方文件Docker Links 中連結內容的歸檔整理,包含了日常工作中常用的 Docker 概念與命令,如果對於 Linux 常用操作尚不熟悉的可以參考 Linux Commands CheatSheet

Docker CheatSheet | Docker 配置與實踐清單

Docker 是一個開源的應用容器引擎,基於 Go 語言 並遵從 Apache2.0 協議開源。Docker 可以讓開發者打包他們的應用以及依賴包到一個輕量級、可移植的容器中,然後釋出到任何流行的 Linux 機器上。

image

虛擬機器最大的瓶頸在於其需要特殊硬體虛擬化技術支援,並且攜帶完整的作業系統;而 Docker 沒有硬體虛擬化,可以執行在物理機、虛擬機器, 甚至巢狀執行在 Docker 容器內,並且其不攜帶作業系統的,會輕巧很多。在呼叫宿主機的記憶體、CPU、磁碟等等資源時,虛擬機器是利用 Hypervisor 去虛擬化記憶體,整個呼叫過程是虛擬記憶體->虛擬實體記憶體->真正實體記憶體,但是 Docker 是利用 Docker Engine 去呼叫宿主的的資源,這時候過程是虛擬記憶體->真正實體記憶體。

Docker CheatSheet | Docker 配置與實踐清單

Docker 綜合運用了 Cgroup, Linux Namespace,Secomp capability, Selinux 等機制,在 Docker Internals CheatSheet 中我們會有詳細的討論,或者前往 Backend Boilerplate/docker 瀏覽常見服務/應用的 Docker 配置案例。

image

安裝與配置

Docker CE

這裡我們使用科大的 Docker CE 源進行安裝:

# 更改 Ubuntu 預設源地址
$ sudo sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list

# 安裝必備的系統命令
$ sudo apt-get install -y python-software-properties

$ curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/gpg | sudo apt-key add -

$ sudo add-apt-repository "deb [arch=amd64] https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu $(lsb_release -cs) stable"

$ sudo apt-get update

$ apt-cache policy docker-ce # 列舉 docker-ce 版本

$ apt-get install docker-ce=17.03.2-ce....
複製程式碼

Daemon Configuration

# 配置開機自啟動
$ sudo systemctl enable docker

# 取消開機自啟動
$ sudo systemctl disable docker
複製程式碼

我們還需要修改儲存路徑,指定映象儲存地址,允許遠端訪問;此時我們可以修改 systemd 中的配置檔案,也可以修改 /etc/docker/daemon.json,此處以修改服務為例:

# 使用 systemctl 命令列修改
$ sudo systemctl edit docker.service

# 或者查詢配置地址並使用 Vim 修改
$ systemctl status docker

# 修改檔案內容
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock --insecure-registry 10.196.108.176:5000 --dns 114.114.114.114 --dns 8.8.8.8 --dns 8.8.4.4 -g /mnt
複製程式碼

然後重啟服務:

# 重新載入服務配置
$ sudo systemctl daemon-reload

# 重啟 Docker
$ sudo systemctl restart docker.service

# 判斷是否配置成功
$ sudo netstat -lntp | grep dockerd
複製程式碼

Docker Swarm

# 在主節點啟動 Swarm
$ docker swarm init

# 檢視 Swarm 金鑰
$ docker swarm join-token -q worker

# 在主節點啟動 Procontainer
$ docker run -it -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer

# 在主節點啟動 Registry
$ docker run -d -p 5000:5000 --restart=always --name registry registry:2

# 將子節點加入到 Swarm
$ docker swarm join \
--token ${TOKEN} \
10.196.108.176:2377
複製程式碼

代理設定

鑑於 gcr 域名的不可用,我們需要利用 ss-privoxy 等工具搭建 Docker 源代理,也可以參考這裡手動配置客戶端:

$ docker run -i -t -e SERVER_ADDR=ss.server.ip -e SERVER_PORT=port -e PASSWORD=123456 bluebu/shadowsocks-privoxy
複製程式碼

如果需要手動安裝,需要先安裝 sslocal 命令:

$ apt install python3-pip
$ pip3 install https://github.com/shadowsocks/shadowsocks/archive/master.zip -U
複製程式碼

寫入你的配置檔案到例如 config.json

{
    "server": "...",
    "server_port": ...,
    "local_port": 1080,
    "password": "..."
    "method": "chacha20-ietf-poly1305",
    "timeout": 600
}
複製程式碼

啟動:

$ sslocal -c config.json
複製程式碼

這時一個 socks5 代理在你本機就啟動了。下面安裝配置 privoxy 把他轉成 http/https 代理。安裝略。修改/新增兩個 privoxy 的配置(對於 ubuntu, 在 /etc/privoxy/config):

listen-address 0.0.0.0:8118        # 所有 interface 上監聽流量
forward-socks5 / 127.0.0.1:1080 .  # 流量導向本機上的 ss 代理
複製程式碼

這時可以訪問一下不存在的網站測試一下:

HTTP_PROXY=127.0.0.1:8118 HTTPS_PROXY=127.0.0.1:8118 curl https://www.google.com
複製程式碼

下面修改各臺機器的 docker 配置(假定我們的 master 內網地址 1.1.1.2, 其他兩臺機器地址為 1.1.1.31.1.1.4):

[Environment]
Environment="HTTP_PROXY=127.0.0.1:8118" "HTTPS_PROXY=127.0.0.1:8118" "NO_PROXY=localhost,127.0.0.1,1.1.1.2,1.1.1.3,1.1.1.4"

...
複製程式碼

環境變數 NO_PROXY 顧名思義,它不支援 CIDR 應該,所以需要你列舉一下叢集主機地址。

映象

映象描述了 Docker 容器執行的初始檔案系統, 包含執行應用所需的所有依賴。即可以是一個完整的作業系統, 也可以僅包含應用所需的最小 bin/lib 檔案集合。Docker 映象和容器採用分層檔案系統結構, 每個容器包含一層薄薄的可寫層, 只讀部分是共享的,這種機制保證了資源的可複用性,減少了映象與容器的空間佔用。Docker 映象儲存引擎有 aufs, devicemapper, overlay 等多種實現。

構建與拉取

編寫完成 Dockerfile 之後,可以通過 docker build 命令來建立映象;關於 Dockfile 的具體語法,可以檢視下文。Dockfile 基本的格式為 docker build [ 選項 ] 路徑,該命令將讀取指定路徑下(包括子目錄)的 Dockerfile,並將該路徑下所有內容傳送給 Docker 服務端,由服務端來建立映象。因此一般建議放置 Dockerfile 的目錄為空目錄。也可以通過 .dockerignore 檔案(每一行新增一條匹配模式)來讓 Docker 忽略路徑下的目錄和檔案。

映象的完整 tag 不僅包含映象名字, 還指明瞭映象從哪裡來, 要到哪裡去, 就像一個 URL。可以通過 -t 選項指定映象的標籤資訊,譬如:

$ sudo docker build -t myrepo/myapp /tmp/test1/

$ docker build -t username/image_name:tag_name .

$ docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
複製程式碼

Docker 支援從 Registry 拉取映象,或者將某個容器儲存為映象:

# 拉取映象
$ docker pull image_name

# 將某個容器儲存為映象
$ docker commit -m “commit message” -a “author”  container_name username/image_name:tag
複製程式碼

Docker 支援將映象儲存為檔案,以方便映象的匯出與載入:

# 儲存映象
$ docker save --output saved-image.tar my-image:1.0.0
$ docker save my-image:1.0.0 > saved-image.tar
$ docker save my_image:my_tag | gzip > my_image.tar.gz

# 匯入映象
$ docker load --input saved-image.tar
$ docker load < saved-image.tar
複製程式碼

映象管理

docker images 命令會列舉出全部的映象:

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
mynewimage          latest              4d2eab1c0b9a        5 minutes ago       278.1 MB
ubuntu              14.04               ad892dd21d60        11 days ago         275.5 MB
<none>              <none>              6b0a59aa7c48        11 days ago         169.4 MB
<none>              <none>              6cfa4d1f33fb        7 weeks ago         0 B
複製程式碼

Docker 中映象主要分為三種狀態:

  • 已使用映象(used image): 指所有已被容器(包括已停止的)關聯的映象。即 docker ps -a 看到的所有容器使用的映象。
  • 未引用映象(unreferenced image):沒有被分配或使用在容器中的映象,但它有 Tag 資訊。
  • 懸空映象(dangling image):未配置任何 Tag (也就無法被引用)的映象,所以懸空。這通常是由於映象 build 的時候沒有指定 -t 引數配置 Tag 導致的。
# 列舉未使用的
$ docker images --filter "dangling=true"

# 刪除所有無用的映象
$ docker rmi $(docker images -q -f dangling=true)
複製程式碼

Dockfile

Dockerfile 由一行行命令語句組成,並且支援以 # 開頭的註釋行。一般的,Dockerfile 分為四部分:基礎映象資訊、維護者資訊、映象操作指令和容器啟動時執行指令;指令的一般格式為 INSTRUCTION arguments,指令包括 FROMMAINTAINERRUN 等。例如:

#
# MongoDB Dockerfile
#
# https://github.com/dockerfile/mongodb
#

# Pull base image.
FROM dockerfile/ubuntu

ENV SOURCE http://downloads-distro.mongodb.org/repo/ubuntu-upstart

# Install MongoDB.
RUN \
  apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 && \
  echo 'deb $SOURCE dist 10gen' > /etc/apt/sources.list.d/mongodb.list && \
  apt-get update && \
  apt-get install -y mongodb-org && \
  rm -rf /var/lib/apt/lists/*

ENV PATH /usr/local/mongo/bin:$PATH

# Define mountable directories.
VOLUME ["/data/db"]

# Define working directory.
WORKDIR /data

# Define default command.
CMD ["mongod"]

# Expose ports.
#   - 27017: process
#   - 28017: http
EXPOSE 27017
EXPOSE 28017
複製程式碼

其中,一開始必須指明所基於的映象名稱,接下來推薦說明維護者資訊。後面則是映象操作指令,例如 RUN 指令,RUN 指令將對映象執行跟隨的命令。每執行一條 RUN 指令,映象新增新的一層,並提交。最後是 CMD 指令,來指定執行容器時的操作命令。

指令名 格式 描述 備註
FROM 格式為 FROM <image>FROM <image>:<tag> 第一條指令必須為 FROM 指令。 如果在同一個 Dockerfile 中建立多個映象時,可以使用多個 FROM 指令(每個映象一次)
MAINTAINER 格式為 MAINTAINER <name> 指定維護者資訊。
RUN RUN <command>RUN ["executable", "param1", "param2"] 前者將在 shell 終端中執行命令,即 /bin/sh -c;後者則使用 exec 執行。指定使用其它終端可以通過第二種方式實現,例如 RUN ["/bin/bash", "-c", "echo hello"] 每條 RUN 指令將在當前映象基礎上執行指定命令,並提交為新的映象。當命令較長時可以使用 \ 來換行。
CMD 支援三種格式,CMD ["executable","param1","param2"] 使用 exec 執行,推薦方式;CMD command param1 param2/bin/sh 中執行,提供給需要互動的應用;CMD ["param1","param2"] 提供給 ENTRYPOINT 的預設引數; 指定啟動容器時執行的命令,每個 Dockerfile 只能有一條 CMD 命令。如果指定了多條命令,只有最後一條會被執行。如果使用者啟動容器時候指定了執行的命令,則會覆蓋掉 CMD 指定的命令。
EXPOSE EXPOSE <port> [<port>...] 告訴 Docker 服務端容器暴露的埠號,供互聯絡統使用 在啟動容器時需要通過 -p 來指定埠對映,Docker 主機會自動分配一個埠轉發到指定的埠
ENV ENV<key><value>。指定一個環境變數,會被後續 RUN 指令使用,並在容器執行時保持
ADD ADD<src><dest> 該命令將複製指定的 <src> 到容器中的 <dest> <src> 可以是 Dockerfile 所在目錄的一個相對路徑;也可以是一個 URL;還可以是一個 tar 檔案(自動解壓為目錄)
COPY COPY <src><dest> 複製本地主機的 <src>(為 Dockerfile 所在目錄的相對路徑)到容器中的 dest 當使用本地目錄為源目錄時,推薦使用 COPY
ENTRYPOINT ENTRYPOINT ["executable", "param1", "param2"],使用指定可執行檔案執行;ENTRYPOINT command param1 param2,會在 Shell 中執行 配置容器啟動後執行的命令,並且不可被 docker run 提供的引數覆蓋。每個 Dockerfile 中只能有一個 ENTRYPOINT,當指定多個時,只有最後一個起效。
VOLUME VOLUME ["/data"] 建立一個可以從本地主機或其他容器掛載的掛載點,一般用來存放資料庫和需要保持的資料等
USER USER daemon 指定執行容器時的使用者名稱或 UID,後續的 RUN 也會使用指定使用者
WORKDIR WORKDIR /path/to/workdir 為後續的 RUNCMDENTRYPOINT 指令配置工作目錄 可以使用多個 WORKDIR 指令,後續命令如果引數是相對路徑,則會基於之前命令指定的路徑

當服務不需要管理員許可權時,可以通過該命令指定執行使用者。並且可以在之前建立所需要的使用者,例如:RUN groupadd -r postgres && useradd -r -g postgres postgres;要臨時獲取管理員許可權可以使用 gosu,而不推薦 sudo

RUN、CMD 和 ENTRYPOINT 這三個 Dockerfile 指令看上去很類似,很容易混淆。RUN 執行命令並建立新的映象層,RUN 經常用於安裝軟體包。CMD 設定容器啟動後預設執行的命令及其引數,但 CMD 能夠被 docker run 後面跟的命令列引數替換。ENTRYPOINT 配置容器啟動時執行的命令。我們經常可以使用 ENTRYPOINT 指定固定命令,使用 CMD 動態傳入引數。

ENTRYPOINT ["/bin/echo", "Hello"]
CMD ["world"]

# docker run -it <image>
# Hello world
# docker run -it <image> John
# Hello John
複製程式碼

Docker 推薦的是單個容器執行單個任務的關注點分離策略,容器的主程式會負責管理所有的子程式。如果我們未在自定義初始化指令碼中考慮太多子程式生命週期管理的操作,那麼可以使用 --init 引數來允許 Docker 自動注入 init 程式作為主程式,其會在容器關閉時候自動處理所有的派生的子程式,並且相對於完整的 sysvinit 或者 systemd 更為輕量級。如果我們希望在單個容器中執行多個程式,則可以使用 supervisord 或者自定義指令碼:

FROM ubuntu:latest
RUN apt-get update && apt-get install -y supervisor
RUN mkdir -p /var/log/supervisor
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY my_first_process my_first_process
COPY my_second_process my_second_process
CMD ["/usr/bin/supervisord"]
複製程式碼

Registry

Docker 允許我們建立私有的 Registry 來存放於管理映象,直接執行如下命令即可建立私有 Registry:

$ docker run -d -p 5000:5000 --restart=always --name registry registry:2
複製程式碼

參考上文描述我們可知,映象名的字首即表示該映象所屬的 Registry 地址,因此我們可以通過 tag 方式將某個映象推送到私有倉庫:

# 拉取公共映象
$ docker pull ubuntu:16.04

# 為映象新增 Registry 資訊
$ docker tag ubuntu:16.04 custom-domain:5000/my-ubuntu

# 將其推送到私有映象庫
$ docker push custom-domain:5000/my-ubuntu

# 從私有映象庫中拉取映象
$ docker pull custom-domain:5000/my-ubuntu
複製程式碼

我們也可以指定映象庫的存放地址:

-v /mnt/registry:/var/lib/registry
複製程式碼

很多情況下我們的內部倉庫並不會配置 HTTPS,如果希望以 HTTP 方式訪問,那麼需要在任何需要推送/拉取映象的機器上配置非安全域名:

{ "insecure-registries": ["myregistry.example.com:5000"] }
複製程式碼

有時候我們也需要為私有倉庫配置許可權認證,那麼首先需要新增 TLS 支援,並且配置認證檔案:

$ mkdir auth
$ docker run \
  --entrypoint htpasswd \
  registry:2 -Bbn cscan cscancscan > ~/auth/htpasswd

$ openssl req -new -newkey rsa:4096 -days 365 \
                -subj "/CN=localhost" \
                -nodes -x509  \
                -keyout ~/certs/domain.key \
                -out ~/certs/domain.crt
複製程式碼

然後可以使用 Compose 檔案來描述所需要的 TLS 以及 AUTH 引數:

registry-srv:
  restart: always
  image: registry:2
  ports:
    - 5000:5000
  environment:
    REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt
    REGISTRY_HTTP_TLS_KEY: /certs/domain.key
    REGISTRY_AUTH: htpasswd
    REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
    REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
  volumes:
    - /opt/registry:/var/lib/registry
    - ~/certs:/certs
    - ~/auth:/auth
複製程式碼

接下來使用 Docker Compose 命令啟動服務:

$ docker-compose up -d

# 登入到映象伺服器
$ docker login myregistrydomain.com:5000
複製程式碼

容器

Docker 中映象是隻讀的, 建立容器時只是在映象上面新建一個可寫層, 不需要複製整個檔案系統, 因而可以實現毫秒級建立。

image

啟停控制

  • docker create: 建立一個容器但是不啟動。
  • docker rename: 允許重新命名容器。
  • docker run: 在同一個操作中建立並啟動一個容器。
  • docker rm: 刪除容器。
  • docker update: 更新容器的資源限制。

容器會在結束命令之後自動退出,使用以下的命令選項可以將容器保持在啟用狀態:

  • -i 即使在沒有附著的情況下依然保持 STDIN 處於開啟。單純使用 -i 命令是不會出現 root@689d580b6416:/ 這種字首

  • -t 分配一個偽 TTY 控制檯

# 建立,並且啟動某個容器以執行某個命令
$ docker run -ti --name container_name image_name command

# 建立,啟動容器執行某個命令然後刪除該容器
$ docker run --rm -ti image_name command

# 建立,啟動容器,並且對映卷與埠,同時設定環境變數
$ docker run -it --rm -p 8080:8080 -v /path/to/agent.jar:/agent.jar -e JAVA_OPTS=”-javaagent:/agent.jar” tomcat:8.0.29-jre8

# 建立容器,指定網路
$ docker run --network=<NETWORK>
複製程式碼

預設情況下,建立容器時,它不會將其任何埠釋出到外部世界。要使埠可用於 Docker 之外的服務或未連線到容器網路的 Docker 容器,請使用 --publish 或 -p 標誌。這會建立一個防火牆規則,將容器埠對映到 Docker 主機上的埠。

標誌值 描述
-p 8080:80 將容器的 80 埠對映到 Docker 主機的 8080 埠(TCP)
-p 8080:80/udp 將容器的 80 埠對映到 Docker 主機的 8080 埠(UDP)
-p 8080:80/tcp -p 8080:80/udp 將容器的 80 埠對映到 Docker 主機的 8080 埠(TCP 和 UDP)
# 啟動/停止某個容器
$ docker [start|stop] container_name

# 在某個容器內執行某條命令
$ docker exec -ti container_name command.sh

# 檢視某個容器的輸出日誌
$ docker logs -ft container_name
複製程式碼

狀態查詢

  • docker ps 檢視執行中的所有容器。
  • docker logs 從容器中獲取日誌。(你也可以使用自定義日誌驅動,不過在 1.10 中,它只支援 json-file 和 journald)
  • docker inspect 檢視某個容器的所有資訊(包括 IP 地址)。
  • docker events 從容器中獲取事件(events)。
  • docker port 檢視容器的公開埠。
  • docker top 檢視容器中活動程式。
  • docker stats 檢視容器的資源使用情況統計資訊。
  • docker diff 檢視容器的 FS 中有變化檔案資訊。
# 根據條件過濾查詢
$ docker ps --filter "name=nostalgic"

# 顯示正在執行的容器列表
$ docker stats --all
複製程式碼

管理配置

建立容器時也可以容器的重啟策略,即是當容器出錯退出或者宿主機重啟時候,容器的應對策略;重啟策略同樣會保證相關聯的容器以正確的順序重啟,避免意外的錯誤。

  • no: 不進行重啟
  • on-failure: 當容器以非零狀態碼退出時重啟容器
  • unless-stopped: 當某個容器被顯性關閉或者 Docker 本身關閉或重啟時重啟
  • always: 無論出現任何情況都重啟容器
# 設定重啟策略
# Off, On-failure, Unless-stopped, Always
$ docker run -dit — restart unless-stopped [CONTAINER]
複製程式碼

可以通過多種過濾條件來進行容器的移除:

# 關閉所有正在執行的容器
$ docker kill $(docker ps -q)

# 移除所有停止的容器
$ docker rm $(docker ps -a -q)

# 根據狀態移除
$ docker rm $(docker ps -q -f 'status=exited')

# 根據標籤移除
$ docker rm $(docker ps -a | grep rabbitmq | awk '{print $1}')

$ docker rm $(docker ps -a | grep "46 hours ago")
複製程式碼

我們也可以對容器中的檔案進行匯入匯出操作:

  • docker cp 在容器和本地檔案系統之間複製檔案或資料夾。
  • docker export 將容器的檔案系統切換為壓縮包(tarball archive stream)輸出到 STDOUT。

資源配額

我們可以使用 docker stats 命令來檢視 Docker 容器的效能狀態與資源佔用:

$ docker stats redis1 redis2

CONTAINER           CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O
redis1              0.07%               796 KB / 64 MB        1.21%               788 B / 648 B       3.568 MB / 512 KB
複製程式碼

Memory

$ docker run -it -m 300M ubuntu:14.04 /bin/bash
複製程式碼

CPU

線上上環境中,我們即希望能夠儘量避免 CPU 空間時間片,最大化資源利用率,也要保障重點業務的資源佔用,避免因某個異常容器佔用或不合理使用整機 CPU 資源,造成宿主機上大量容器異常;此時單容器的 CPU 資源約束及上限將變得非常重要,既不能限得太死,又不能限不住,將通過核心和排程系統的限制機制來保障資源穩定性。Docker 允許使用 cpus, cpuset-cpus, cpu-shares 等來限制容器的計算資源佔用:

# 指定容器可佔用的 CPU 核編號,0-3 表示佔用四個核,1,3 表示佔用兩個核
$ docker run -it --cpuset-cpus="1,3" ubuntu /bin/bash

# 最多允許佔用單 CPU 50% 的計算資源,如果雙核 CPU,則可以設定為 1.5 等
$ docker run -it --cpus=".5" ubuntu /bin/bash

# 不同的值能夠指定不同的容器權重,用於動態分配 CPU 資源
$ docker run -it --cpu-shares="512" ubuntu /bin/bash
複製程式碼
  • CPU Set 保障了容器的 CPU 核數,安全性較強,但是整體資源利用率低。如果容器實際並不需要如此多核的 CPU 資源來處理任務則會造成資源浪費,並且導致其他容器上的任務無法利用該容器上的 CPU 空閒時間片,人為阻斷了 CPU 閒時複用的能力。

  • CPU Share 允許通過共享的方式獲得 CPU 資源,不同的容器共享一定總量的 CPU 計算能力,每個容器都繫結全量核,而每個容器獲取一定份額的 CPU 計算力。該模式下,每個容器的 CPU 資源分配不再以整核分配,而是可精細到 CPU 時間片份額的粒度,並且是連續的 CPU 核能力值。當整機閒時,可以讓較為繁忙的業務獲得整機空閒。採用 CPU 資源共享的機制,其資源隔離性沒有 set 模式強,對於極個別 CPU 資源敏感型業務,有可能出現偶爾等待 CPU 時間片的情況,而影響業務穩定性。對於極少數的這類業務,我們容許繼續使用 CPU set 模式。

Storage

如果使用 Device Mapper 作為底層儲存驅動,則可以通過 Docker daemon 的如下引數來全侷限制單個容器佔用空間的大小:

# 限制單個容器最多佔用 20G 空間,將應用於任何新建容器。
$ --storage-opt dm.basesize=20G
複製程式碼

如果是 btrfs 儲存驅動,可使用其提供的 subvolume 功能來實現。一個容器會對應一個 subvolume。針對容器對應的 subvolume 啟用並配置 quota 即可限制其磁碟空間:

$ btrfs qgroup limit -e 50G /var/lib/docker/btrfs/subvolumes/<CONTAINER_ID>
複製程式碼

授予對單個裝置訪問許可權:

docker run -it --device=/dev/ttyUSB0 debian bash
複製程式碼

授予所有裝置訪問許可權:

docker run -it --privileged -v /dev/bus/usb:/dev/bus/usb debian bash
複製程式碼

資源配置

Volume | 資料卷

容器執行時應該儘量保持容器儲存層不發生寫操作,對於資料庫類需要儲存動態資料的應用,其資料庫檔案應該儲存於卷(Volume)中。為了防止執行時使用者忘記將動態檔案所儲存目錄掛載為卷,在 Dockerfile 中,我們可以事先指定某些目錄掛載為匿名卷,這樣在執行時如果使用者不指定掛載,其應用也可以正常執行,不會向容器儲存層寫入大量資料。

資料卷是一個可供一個或多個容器使用的特殊目錄,它繞過 UFS,可以提供很多有用的特性:

  • 資料卷可以在容器之間共享和重用

  • 對資料卷的修改會立馬生效

  • 對資料卷的更新,不會影響映象

  • 卷會一直存在,直到沒有容器使用

  • 資料卷的使用,類似於 Linux 下對目錄或檔案進行 mount。

For example,

# the following creates a tmpfs volume called foo with a size of 100 megabyte and uid of 1000.
$ docker volume create --driver local \
    --opt type=tmpfs \
    --opt device=tmpfs \
    --opt o=size=100m,uid=1000 \
    foo
複製程式碼

nother example that uses nfs to mount the /path/to/dir in rw mode from 192.168.1.1:

$ docker volume create --driver local \
    --opt type=nfs \
    --opt o=addr=192.168.1.1,rw \
    --opt device=:/path/to/dir \
    foo
複製程式碼
$ docker run -d \
  -it \
  --name devtest \
  -v myvol2:/app \
  nginx:latest
複製程式碼
"Mounts": [
    {
        "Type": "volume",
        "Name": "myvol2",
        "Source": "/var/lib/docker/volumes/myvol2/_data",
        "Destination": "/app",
        "Driver": "local",
        "Mode": "",
        "RW": true,
        "Propagation": ""
    }
],
複製程式碼

Docker -v 標記也可以指定掛載一個本地主機的目錄 / 檔案到容器中去:

# 掛載目錄
$ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py

# 掛載檔案
$ sudo docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash

# Docker 掛載資料卷的預設許可權是讀寫,使用者也可以通過 `:ro` 指定為只讀。
$ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp:ro
training/webapp python app.py
複製程式碼

注意:Dockerfile 中不支援這種用法,這是因為 Dockerfile 是為了移植和分享用的。然而,不同作業系統的路徑格式不一樣,所以目前還不能支援。

VOLUME /data
複製程式碼

Network | 網路

Linux 在網路棧中引入網路名稱空間,將獨立的網路協議棧隔離到不同的命令空間中,彼此間無法通訊;Docker 利用這一特性,實現不容器間的網路隔離,並且引入 Veth 裝置對來實現在不同網路名稱空間的通訊。Linux 系統包含一個完整的路由功能,當 IP 層在處理資料傳送或轉發的時候,會使用路由表來決定發往哪裡。Netfilter 負責在核心中執行各種掛接的規則(過濾、修改、丟棄等),執行在核心模式中;Iptables 模式是在使用者模式下執行的程式,負責協助維護核心中 Netfilter 的各種規則表;通過二者的配合來實現整個 Linux 網路協議棧中靈活的資料包處理機制。Docker 的網路子系統採用了基於驅動的可插拔機制,其預設包含了如下驅動模式:

  • bridge: 預設的網路驅動,常用於多個應用執行與獨立容器中並且需要相互通訊的時候。
  • host: 移除容器與 Docker 主機之間的網路隔離,直接使用宿主機所在的網路。底層與宿主機共用一個 Network Namespace,容器將不會虛擬出自己的網路卡,配置自己的 IP 等,而是使用宿主機的 IP 和埠。
  • overlay: Overlay 網路用語連線多個 Docker Daemon,保證 Docker Swarm 服務的正常執行;獨立的容器與 Swarm 服務,或者不同宿主機上的容器同樣能夠通過 Overlay 進行通訊。
  • none: 對於指定容器禁止所有的網路通訊。
  • macvlan: Macvlan 網路會允許直接為容器分配 MAC 地址,使其作為真正的物理裝置接入到宿主機所在的網路中。

我們使用 network 命令可以檢視到預設的網路:

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
f707aa0ef50d        bridge              bridge              local
97dd7a032d96        host                host                local
d5a1bed0b12d        none                null                local
複製程式碼

橋接模式下,當 Docker 啟動時,會自動在主機上建立一個 docker0 虛擬網橋,即軟體交換機,在掛載到它的網口之間進行轉發。同時,Docker 隨機分配一個本地未佔用的私有網段(在 RFC1918 中定義)中的一個地址給 docker0 介面。比如典型的 172.17.42.1 ,掩碼為 255.255.0.0 。此後啟動的容器內的網口也會自動分配一個同一網段( 172.17.0.0/16)的地址。

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

# 建立新的網路
$ docker network create --driver bridge isolated

# 指定網段,宿主機會作為預設閘道器
$ docker network create --driver=bridge --subnet=192.168.2.0/24 --gateway=192.168.2.10 new_subnet

# 建立時將某個容器連線到網路
$ docker run --network=isolated -itd --name=docker-nginx nginx

# 將某個執行中容器連線到某個網路
$ docker network connect multi-host-network container1
複製程式碼

DNS

預設情況下,容器從 Docker 守護程式繼承 DNS 設定,包括 /etc/hosts 和 /etc/resolv.conf。可以基於每個容器覆蓋這些設定。

  • -h HOSTNAME or --hostname=HOSTNAME 設定容器的主機名,它會被寫到容器內的 /etc/hostname 和 /etc/hosts 。但它在容器外部看不到,既不會在 docker ps 中顯示,也不會在其他的容器的 /etc/hosts 看到。
  • --link=CONTAINER_NAME:ALIAS 選項會在建立容器的時候,新增一個其他容器的主機名到 /etc/hosts 檔案中,讓新容器的程式可以使用主機名 ALIAS 就可以連線它。
  • --dns=IP_ADDRESS 新增 DNS 伺服器到容器的 /etc/resolv.conf 中,讓容器用這個伺服器來解析所有不在 /etc/hosts 中的主機名。
  • --dns-search=DOMAIN 設定容器的搜尋域,當設定搜尋域為 .example.com 時,在搜尋一個名為 host 的 主機時,DNS 不僅搜尋 host,還會搜尋 host.example.com 。 注意:如果沒有上述最後 2 個選項, Docker 會預設用主機上的 /etc/resolv.conf 來配置容器。

空間清理

Docker 使用過程中,可能會發現宿主節點的磁碟容量持續增長,譬如 volume 或者 overlay2 目錄佔用了大量的空間;如果任其發展,可能將磁碟空間耗盡進而引發宿主機異常,進而對業務造成影響。Docker 的內建 df 指令可用於查詢映象(Images)、容器(Containers)和本地卷(Local Volumes)等空間使用大戶的空間佔用情況。而容器的佔用的總空間,包含其最頂層的讀寫層(writable layer)和底部的只讀映象層(base image layer,read-only),我們可以使用 ps -s 引數來顯示二者的空間佔用情況:

# 檢視當前目錄下的檔案空間佔用
$ du -h --max-depth=1 | sort

# 空間佔用總體分析
$ docker system df

# 輸出空間佔用細節
$ docker system df -v

# 輸出容器的空間佔用
$ docker ps -s
複製程式碼

docker system prune 指令能夠進行自動地空間清理,其預設會清除已停止的容器、未被任何容器所使用的卷、未被任何容器所關聯的網路、所有懸空映象:

# 一併清除所有未使用的映象和懸空映象
$ docker system prune --all

# 列舉懸空映象
$ docker images -f dangling=true

# 刪除全部懸空映象
$ docker image prune
# 刪除所有未被使用的映象
$ docker image prune -a

# 刪除指定模式的映象
$ docker images -a | grep "pattern" | awk '{print $3}' | xargs docker rmi

# 刪除全部映象
$ docker rmi $(docker images -a -q)

# 刪除全部停止的容器
$ docker rm $(docker ps -a -f status=exited -q)

# 根據指定模式刪除容器
$ docker rm $(docker ps -a -f status=exited -f status=created -q)
$ docker rm $(docker ps -a | grep rabbitmq | awk '{print $1}')

# 刪除全部容器
$ docker stop $(docker ps -a -q)
$ docker rm $(docker ps -a -q)

# 列舉並刪除未被使用的卷
$ docker volume ls -f dangling=true
$ docker volume prune

# 根據指定的模式刪除卷
$ docker volume prune --filter "label!=keep"

# 刪除未被關聯的網路
$ docker network prune
$ docker network prune --filter "until=24h"
複製程式碼

我們也可以手動指定日誌檔案的尺寸或者清空日誌檔案:

# 設定日誌檔案最大尺寸
$ dockerd ... --log-opt max-size=10m --log-opt max-file=3

# 清空當前日誌檔案
truncate -s 0 /var/lib/docker/containers/*/*-json.log
複製程式碼

服務治理

Docker Compose

Docker Compose 是用於定義和執行復雜 Docker 應用的工具。你可以在一個檔案中定義一個多容器的應用,然後使用一條命令來啟動你的應用,然後所有相關的操作都會被自動完成;簡單的 Compose 檔案定義如下:

# 指定 Docker Compose 檔案版本
version: '3'
services:
  web:
    # 指定從本地目錄進行編譯
    build: .

    # 指定匯出埠
    ports:
      - '5000:5000'

    # 替換預設的 CMD 命令
    command: python app.py

    # 將本地目錄繫結到容器內目錄
    volumes:
      - .:/code

  redis:
    # 映象的 ID
    image: 'redis:alpine'
複製程式碼

這裡用到的 Python Web 應用的 Dockerfile 如下:

FROM python:3.4-alpine
ADD . /code
WORKDIR /code
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
複製程式碼

值得注意的是,我們在程式碼中直接使用服務名作為連線地址,即可訪問到 Redis 資料庫:

cache = redis.Redis(host='redis', port=6379)
複製程式碼

然後使用 docker-compose 命令啟動:

# 互動式啟動
$ docker-compose up

# 守護程式式啟動
$ docker-compose up -d

# 檢視執行情況
$ docker-compose ps

# 關閉
$ docker-compose stop

# 移除內部卷
$ docker-compose down --volumes
複製程式碼

在涉及到資料儲存的場景下,我們同樣可以指定 docker-compose 建立命名資料卷,並將其掛載到容器中:

version: "3.2"
services:
  web:
    image: nginx:alpine
    volumes:
      - type: volume
        source: mydata
        target: /data
        volume:
          nocopy: true
      - type: bind
        source: ./static
        target: /opt/app/static

  db:
    image: postgres:latest
    volumes:
      - "/var/run/postgres/postgres.sock:/var/run/postgres/postgres.sock"
      - "dbdata:/var/lib/postgresql/data"

volumes:
  mydata:
  dbdata:
複製程式碼

Docker Swarm

Swarm 是 Docker 公司在 2014 年 12 月初發布的一套較為簡單的工具,用來管理 Docker 叢集,它將一群 Docker 宿主機變成一個單一的,虛擬的主機。Swarm 使用標準的 Docker API 介面作為其前端訪問入口,換言之,各種形式的 Docker Client(dockerclient in go, docker_py, docker 等)均可以直接與 Swarm 通訊。

Swarm Deamon 只是一個排程器(Scheduler)和路由器(Router),Swarm 自己不執行容器,它只是接受 Docker 客戶端傳送過來的請求,排程適合的節點來執行容器,這意味著,即使 Swarm 由於某些原因掛掉了,叢集中的節點也會照常執行,當 Swarm 重新恢復執行之後,它會收集重建叢集資訊。並且 Swarm 提供的路由匹配(服務發現、負載均衡、跨容器通訊)非常可靠。在單個埠上執行一個服務,Swarm 節點的任意主機都可以訪問,負載均衡完全在後臺實現。

image

Swarm 在 Scheduler 節點執行容器的時候,會根據指定的策略來計算最適合執行容器的節點,目前支援的策略有:Random, Binpack, Spread。

Random 顧名思義,就是隨機選擇一個 Node 來執行容器,一般用作除錯用。Spread 和 Binpack 策略會根據各個節點的可用的 CPU, RAM 以及正在執行的容器的數量來計算應該執行容器的節點。在同等條件下,Spread 策略會選擇執行容器最少的那臺節點來執行新的容器,binpack 策略會選擇執行容器最集中的那臺機器來執行新的節點。

使用 Spread 策略會使得容器會均衡的分佈在叢集中的各個節點上執行,一旦一個節點掛掉了只會損失少部分的容器。Binpack 策略最大化的避免容器碎片化,就是說 Binpack 策略儘可能的把還未使用的節點留給需要更大空間的容器執行,儘可能的把容器執行在一個節點上面。

# 建立一個新的服務
$ docker service create \
--image nginx \
--replicas 2 \
nginx

# 更新服務
$ docker service update \
--image nginx:alpine \
nginx

# 刪除服務
$ docker service rm nginx

# 縮容,而不是直接刪除服務
$ docker service scale nginx=0

# 擴容
$ docker service scale nginx=5

# 列出所有的服務
$ docker service ls

# 列出一個服務的所有例項(包括服務的健康狀況)
$ docker service ps nginx

# 服務的詳細資訊
$ docker service inspect nginx
複製程式碼

我們也可以使用 Docker Compose 的指令碼來進行批次部署:

$ docker stack deploy application
複製程式碼
version: '3'
services:
  web:
    image: registry.gitlab.com/example/example # you need to use external image
    command: npm run prod
    ports:
      - 80:80
    deploy:
      replicas: 6
      update_config:
        parallelism: 2
        delay: 10s
      restart_policy:
        condition: on-failure
複製程式碼

Further Reading

相關文章