Docker CheatSheet | Docker 配置與實踐清單

image

📖 節選自 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 綜合運用了 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章