一個前端工程師的Docker學習筆記【持續更新】

俊寧發表於2020-03-31

Docker 是個劃時代的開源專案,它徹底釋放了計算虛擬化的威力,極大提高了應用的維護效率,降低了雲端計算應用開發的成本!使用 Docker,可以讓應用的部署、測試和分發都變得前所未有的高效和輕鬆!

無論是應用開發者、運維人員、還是其他資訊科技從業人員,都有必要認識和掌握 Docker,節約有限的生命。

本文是筆者以一個前端工程師的視角學習 Docker 過程中的筆記,如果對您有所幫助,榮幸之至。

基礎入門

Docker 使用 Google 公司推出的 Go 語言 進行開發實現,基於 Linux 核心的 cgroupnamespace,以及 OverlayFS 類的 Union FS 等技術,對程式進行封裝隔離,屬於 作業系統層面的虛擬化技術。由於隔離的程式獨立於宿主和其它的隔離的程式,因此也稱其為容器。最初實現是基於 LXC,從 0.7 版本以後開始去除 LXC,轉而使用自行開發的 libcontainer,從 1.11 開始,則進一步演進為使用 runCcontainerd

概念

DevOps

DevOps(Development和Operations的組合詞)是一種重視軟體開發人員(Dev)和IT運維技術人員(Ops)之間溝通合作的文化、運動或慣例。透過自動化“軟體交付”和“架構變更”的流程,來使得構建、測試、釋出軟體能夠更加地快捷、頻繁和可靠。

DevOps 的引入能對產品交付、測試、功能開發和維護(包括曾經罕見但如今已屢見不鮮的“熱補丁”)起到意義深遠的影響。在缺乏 DevOps 能力的組織中,開發與運營之間存在著資訊“鴻溝”。例如運營人員要求更好的可靠性和安全性,開發人員則希望基礎設施響應更快,而業務使用者的需求則是更快地將更多的特性發布給終端使用者使用。這種資訊鴻溝就是最常出問題的地方。

容器

容器有效地將由單個作業系統管理的資源劃分到孤立的組中,以更好地在孤立的組之間平衡有衝突的資源使用需求。與虛擬化相比,這樣既不需要指令級模擬,也不需要即時編譯。容器可以在核心 CPU 本地執行指令,而不需要任何專門的解釋機制。此外,也避免了準虛擬化(para-virtualization)和系統呼叫替換中的複雜性。

虛擬化

在計算機技術中,虛擬化是一種資源管理技術,是將計算機中的各種實體資源,如伺服器、網路、記憶體及儲存等,予以抽象、轉換後呈現出來,打破實體結構間的不可切割的障礙,使使用者可以用比原來的組態更好的方式來應用這些資源。

Docker 與虛擬機器比較

特性 容器 虛擬機器
啟動 秒級 分鐘級
硬碟使用 一般為 MB 一般為 GB
效能 接近原生 弱於
系統支援量 單機支援上千個容器 一般幾十個

如下圖,虛擬機器是在硬體層面實現虛擬化,需要額外的虛擬機器管理應用和虛擬機器作業系統層。Docker容器是在作業系統層面上實現虛擬化,直接複用本地主機的作業系統,因此更加輕量級。

一個前端工程師的Docker學習筆記【持續更新】

Docker核心概念

映象(Image)

我們都知道,作業系統分為核心和使用者空間。對於 Linux 而言,核心啟動後,會掛載 root 檔案系統為其提供使用者空間支援。而 Docker 映象(Image),就相當於是一個 root 檔案系統。比如官方映象 ubuntu:18.04 就包含了完整的一套 Ubuntu 18.04 最小系統的 root 檔案系統。

Docker 映象是一個特殊的檔案系統,除了提供容器執行時所需的程式、庫、資源、配置等檔案外,還包含了一些為執行時準備的一些配置引數(如匿名卷、環境變數、使用者等)。映象不包含任何動態資料,其內容在構建之後也不會被改變。

分層儲存

因為映象包含作業系統完整的 root 檔案系統,其體積往往是龐大的,因此在 Docker 設計時,就充分利用 Union FS 的技術,將其設計為分層儲存的架構。所以嚴格來說,映象並非是像一個 ISO 那樣的打包檔案,映象只是一個虛擬的概念,其實際體現並非由一個檔案組成,而是由一組檔案系統組成,或者說,由多層檔案系統聯合組成。

映象構建時,會一層層構建,前一層是後一層的基礎。每一層構建完就不會再發生改變,後一層上的任何改變只發生在自己這一層。比如,刪除前一層檔案的操作,實際不是真的刪除前一層的檔案,而是僅在當前層標記為該檔案已刪除。在最終容器執行的時候,雖然不會看到這個檔案,但是實際上該檔案會一直跟隨映象。因此,在構建映象的時候,需要額外小心,每一層儘量只包含該層需要新增的東西,任何額外的東西應該在該層構建結束前清理掉。

分層儲存的特徵還使得映象的複用、定製變的更為容易。甚至可以用之前構建好的映象作為基礎層,然後進一步新增新的層,以定製自己所需的內容,構建新的映象。

容器(Container)

映象(Image)和容器(Container)的關係,就像是物件導向程式設計中的 例項 一樣,映象是靜態的定義,容器是映象執行時的實體。容器可以被建立、啟動、停止、刪除、暫停等。

容器的實質是程式,但與直接在宿主執行的程式不同,容器程式執行於屬於自己的獨立的 名稱空間。因此容器可以擁有自己的 root 檔案系統、自己的網路配置、自己的程式空間,甚至自己的使用者 ID 空間。容器內的程式是執行在一個隔離的環境裡,使用起來,就好像是在一個獨立於宿主的系統下操作一樣。這種特性使得容器封裝的應用比直接在宿主執行更加安全。也因為這種隔離的特性,很多人初學 Docker 時常常會混淆容器和虛擬機器。

前面講過映象使用的是分層儲存,容器也是如此。每一個容器執行時,是以映象為基礎層,在其上建立一個當前容器的儲存層,我們可以稱這個為容器執行時讀寫而準備的儲存層為 容器儲存層

容器儲存層的生存週期和容器一樣,容器消亡時,容器儲存層也隨之消亡。因此,任何儲存於容器儲存層的資訊都會隨容器刪除而丟失。

按照 Docker 最佳實踐的要求,容器不應該向其儲存層內寫入任何資料,容器儲存層要保持無狀態化。所有的檔案寫入操作,都應該使用 資料卷(Volume)、或者繫結宿主目錄,在這些位置的讀寫會跳過容器儲存層,直接對宿主(或網路儲存)發生讀寫,其效能和穩定性更高。

資料卷的生存週期獨立於容器,容器消亡,資料卷不會消亡。因此,使用資料卷後,容器刪除或者重新執行之後,資料卻不會丟失。

倉庫註冊伺服器(Registry)

一個 Docker Registry 中可以包含多個 倉庫Repository);每個倉庫可以包含多個 標籤Tag);每個標籤對應一個映象。

通常,一個倉庫會包含同一個軟體不同版本的映象,而標籤就常用於對應該軟體的各個版本。我們可以通過 <倉庫名>:<標籤> 的格式來指定具體是這個軟體哪個版本的映象。如果不給出標籤,將以 latest 作為預設標籤。

Ubuntu 映象 為例,ubuntu 是倉庫的名字,其內包含有不同的版本標籤,如,16.04, 18.04。我們可以通過 ubuntu:16.04,或者 ubuntu:18.04 來具體指定所需哪個版本的映象。如果忽略了標籤,比如 ubuntu,那將視為 ubuntu:latest

倉庫名經常以 兩段式路徑 形式出現,比如 jwilder/nginx-proxy,前者往往意味著 Docker Registry 多使用者環境下的使用者名稱,後者則往往是對應的軟體名。但這並非絕對,取決於所使用的具體 Docker Registry 的軟體或服務。

公有 Docker Registry:

私有 Docker Registry:

守護程式 daemon

在一個多工的電腦作業系統中,守護程式(daemon)是一種在後臺執行的電腦程式。此類程式會被以程式的形式初始化。守護程式程式的名稱通常以字母”d“結尾:例如,syslogd 就是指管理系統日誌的守護程式。

通常,守護程式沒有任何存在的父程式(即PPID=1),且在 UNIX 系統程式層級中直接位於 init 之下。守護程式程式通常通過如下方法是自己成為守護程式:對一個子程式進行 fork,然後使其父程式立即終止,使得這個子程式能在 init 下執行。這種方法通常被稱為”脫殼“。

系統通常在啟動時一同引導守護程式。守護程式為對網路請求,硬體活動等進行響應,或其他通過某些任務對其他應用程式的請求進行回應提供支援。守護程式也能夠對硬體進行配置(如某些Linux系統上的devfsd),執行計劃任務(例如cron),以及執行其他任務。

在 DOS 環境中,此類應用程式被稱為駐留程式(TSR)。在 Windows 系統中,由稱為 Windows服務的應用程式來履行守護程式的職責。

在原本的 Mac OS 系統中,此類應用程式被稱為”extensions“。而作為 Unux-like 的 Mac OS X 有守護程式。

安裝配置

解除安裝舊版本

$ apt remove docker docker-engine docker.io containerd runc
複製程式碼

通過軟體包安裝

# step 1: 安裝必要的一些系統工具
sudo apt-get update
sudo apt-get -y install apt-transport-https ca-certificates curl gnupg-agent pass software-properties-common
# step 2: 安裝GPG證照
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
# Step 3: 寫入軟體源資訊
sudo add-apt-repository "deb [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"
# Step 4: 更新並安裝Docker-CE
sudo apt-get -y update
sudo apt-get -y install docker-ce
複製程式碼

通過指令碼安裝

$ curl -fsSL https://get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh --mirror Aliyun
複製程式碼

安裝成功後,會自動啟動 Docker 服務。使用者可以使用 systemctl is-enabled docker 來確認 Docker 服務是否是開機自啟動。

可選配置

解決 WARNING: Your kernel does not support cgroup swap limit capabilities

  1. 編輯 /etc/default/grub 檔案

    $ nano /etc/default/grub
    複製程式碼
  2. 找到 GRUB_CMDLINE_LINUX= 配置項,並追加 cgroup_enable=memory swapaccount=1

  3. 儲存檔案後執行一下命令:sudo update-grub

  4. 重啟伺服器:reboot

測試 Docker 是否安裝正確

$ docker run hello-world
複製程式碼

執行以上命令,若能正常輸出以下資訊,則說明安裝成功。

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
1b930d010525: Pull complete 
Digest: sha256:f9dfddf63636d84ef479d645ab5885156ae030f611a56f3a7ac7f2fdd86d7e4e
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/
複製程式碼

Docker Deamon 配置

執行 nano /etc/docker/daemon.json 中寫入如下內容:

{
  "ip": "127.0.0.1",
  "experimental": false,
  "registry-mirrors": [
    "https://registry.docker-cn.com",
    "https://mirror.ccs.tencentyun.com",
    "http://docker.mirrors.ustc.edu.cn",
    "http://hub-mirror.c.163.com"
  ]
}
複製程式碼

重新啟動服務:

$ systemctl daemon-reload
$ systemctl restart docker.service
複製程式碼

使用Docker映象

獲取映象

docker pull [選項] [Docker Registry 地址[:埠號]/][使用者名稱]<倉庫名>[:TAG]

  • 預設選項
    • -a--all-tags=true|false:是否獲取倉庫中的所有映象,預設為否
    • --disable-content-trust:取消映象的內容校驗,預設為真
  • 預設 Docker Registry:registry.hub.docker.com
  • 預設使用者名稱:library,也就是官方映象
  • 預設TAG:latest

檢視映象資訊

列出本地主機上已有映象

docker image ls | docker images

映象的大小資訊只是表示了該映象的邏輯體積大小,實際上由於相同的映象層本地只會儲存一份,物理上佔用的儲存空間會小於各映象邏輯體積之和。

使用 tag 命令新增映象標籤

docker tag ubuntu:latest myubuntu:latest

為了方便在後續工作中使用特定映象,還可以使用 docker tag 命令來為本地映象任意新增新的標籤。

使用 inspect 命令檢視詳細資訊

docker inspect <倉庫>

使用 docker inspect 命令可以獲取該映象的詳細資訊,包括製作者、適應架構、各層的數字摘要等。

使用 history 命令檢視映象歷史

docker history <REPOSITORY>[:TAG]docker history <IMAGE ID>

注意,過長的命令會被自動截斷了,可以使用 --no-trunc 選項來輸出完整命令。

刪除映象

  1. 使用標籤刪除映象

    docker rmi <IMAGE> [IMAGE...]docker image rm <IMAGE> [IMAGE...]

  2. 使用映象 ID 來刪除映象

    docker rmi <IMAGE ID>

    當使用 docker rmi 命令,並且後面跟上映象的 ID(也可以是能進行區分的部分 ID 串字首)時,會先嚐試刪除所有指向該映象的標籤,然後刪除該映象檔案本身。

    注意,當有基於該映象建立的容器時,映象檔案預設是無法被刪除的。我們可以使用 docker ps -a 命令可以檢視本機上存在的所有容器。

    最佳實踐:先用 docker rm <Container ID> 刪除依賴該映象的所有容易,然後執行 docker rmi <IMAGE ID> 再來刪除映象。

清理映象

docker image prune [options]

  • -a--all:刪除所有無用映象,不光是臨時映象
  • -f,--force:強制刪除映象,而不進行提示確認

使用 Docker 一段時間後,系統中可能會遺留一些臨時的映象檔案,以及沒有使用的映象,可以通過 docker image prune 命令來進行清理。

我們可以結合 crontab 來定時清理,執行 crontab -e,寫入一下配置:

# 一定要記得在後面按 Enter 輸入換行符,否則不會生效的
59 23 * * * docker image prune -f

複製程式碼

建立映象

1. 基於已有容器建立

docker commit [OPTIONS] <CONTAINER> <REPOSITORY>[:TAG]

  • -a--author=:作者資訊
  • -m--message="":提交資訊
  • -p--pause=true:提交時暫停容器執行

首先,啟動一個 alpine 映象,並在其中進行安裝 nano 的操作,然後釋出一個新的映象:

$ docker run -it alpine bash
$ docker commit -m "install nano" -a "楊俊寧" ff3034d2ffa7 my-alpine:0.1
複製程式碼

2. 基於 Dockerfile 建立

docker build -t <IMAGE NAME> <上下文路徑/URL/->

通過 Dockerfile 建立是最常見的方式。Dockerfile 是一個文字檔案,利用指定的指令描述基於某個父映象建立新映象的過程。

下面給出 Dockerfile 的一個簡單示例,基於 alpine 映象安裝 node 環境,構成一個新的 youngjuning/alpine 映象:

FROM alpine

LABEL version="1.0" maintainer="youngjuning<youngjuning@aliyun.com>"

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
    apk update && apk upgrade && \
    apk add --no-cache nodejs yarn
複製程式碼

構建:

$ docker build -t youngjuning/alpine:latest .
複製程式碼

儲存映象

如果要匯出映象到本地檔案,可以使用 docker save 命令。該命令支援 -o <string>--output <string> 引數,匯出映象到指定的檔案中。

例如,匯出本地 alpine 映象為檔案 alpine.tar,如下所示:

$ docker save -o alpine.tar alpine
複製程式碼

之後,使用者就可以通過複製 alpine.tar 檔案將該映象分享給他人。

載入映象

可以使用 docker load 將匯出的 tar 檔案再匯入到本地映象庫。支援 -i <string>-input <string> 選項,從指定檔案中讀入映象內容。

例如,從檔案 alpine.tar 匯入映象到本地映象列表,如下所示:

$ docker load -i alpine.tar
複製程式碼

上傳映象

docker push [選項] [Docker Registry 地址[:埠號]/][使用者名稱]<倉庫名>[:TAG]

釋出新版本流程:

  • 釋出 latest 版本:docker push youngjuning/alpine:latest
  • 新增新標籤:docker tag youngjuning/alpine:latest youngjuning/alpine:1.0.0
  • 釋出 1.0.0 版本:docker push youngjuning/alpine:1.0.0

可以檢視https://hub.docker.com/r/youngjuning/alpine專案檢視我釋出的基於aliyun映象的 Aplpine Docker Image

操作 Docker 容器

  • Docker 容器是映象的一個執行例項。
  • Docker 容器是獨立執行的一個(或一組)應用,以及它們必需的執行環境

啟動容器

1. 新建並啟動

$ docker run -it ubuntu:18.04 /bin/bash
複製程式碼

其中, -t 選項讓 Docker 分配一個偽終端(pseudo-tty)並繫結到容器的標準輸入上,-i 則讓容器的標準輸入保持開啟。

當利用 docker run 來建立容器時,Docker 在後臺執行的標準操作包括:

  1. 檢查本地是否存在指定的映象,不存在就從公有倉庫下載
  2. 利用映象建立並啟動一個容器
  3. 分配一個檔案系統,並在只讀的映象層外面掛在一層可讀寫層
  4. 從宿主主機配置的網橋介面中橋接一個虛擬介面到容器中去
  5. 從地址池配置一個ip地址給容器
  6. 執行使用者指定的應用程式
  7. 執行完畢後容器被終止

一些常用選項:

  • -d--detach=true|false:是否在後臺執行容器,預設為false
  • -i--interactive=true|false:保持標準輸入開啟,預設為 false
  • -p--publish=[]:指定如何對映到本地主機埠,例如 -p 9000:9000
  • --restart="no":容器的重啟策略,包括 noon-failure[:max-retry]alwaysunless-stopped
  • --rm=true|false:容器退出後是否自動刪除,不能跟 -d 同時使用
  • -t--tty=true|false:是否分配一個偽終端,預設為 false
  • -v [HOST-DIR:]<CONTAINER-DIR>[:OPTIONS]--volume=[HOST-DIR:]<CONTAINER-DIR>[:OPTIONS]:掛在主機上的檔案捲到容器內
  • --name="":指定容器的別名

2. 啟動已終止容器

可以利用 docker start <CONTAINER ID> 命令,直接將一個已經終止的容器啟動執行。

3. 檢視容器輸出

要獲取容器的輸出資訊,可以通過 docker <CONTAINER ID> logs 命令。

終止容器

可以使用 docker stop <CONTAINER ID> 來終止一個執行中的容器。

處於終止狀態的容器,可以通過 docker container start 命令來重新啟動。

此外,docker container restart 命令會將一個執行態的容器終止,然後再重新啟動它。

exec進入容器

在使用 -d 引數時,容器啟動後會進入後臺。

某些時候需要進入容器進行操作,推薦大家使用 docker exec 命令:

$ docker run -dit alpine
$ docker ps
CONTAINER ID        IMAGE                 COMMAND             CREATED             STATUS              PORTS                      NAMES
3d95dabef801        alpine                "/bin/sh"           21 seconds ago      Up 19 seconds                                  recursing_aryabhata
複製程式碼
$ docker exec -it <CONTAINER ID>
複製程式碼

如果從這個 stdin 中 exit,不會導致容器的停止。

刪除容器

可以使用 docker container rm 來刪除一個處於終止狀態的容器。例如

$ docker rm  <CONTAINER ID>
# 刪除執行中的容器,並刪除容器掛載的資料卷
$ docker rm -vf
複製程式碼

如果要刪除一個執行中的容器,可以新增 -f 引數。Docker 會傳送 SIGKILL 訊號給容器。

清理所有處於終止狀態的容器

$ docker container prune
複製程式碼

匯出和匯入容器

$ docker export 7691a814370e > ubuntu.tar
$ cat ubuntu.tar | docker import - test/ubuntu:v1.0
$ docker import http://example.com/exampleimage.tgz example/imagerepo
複製程式碼

檢視容器

1. 檢視容器詳情

$ docker inspect [OPTIONS] <CONTAINER ID>
複製程式碼

2. 檢視容器內程式

$ docker top [OPTIONS] <CONTAINER ID>
複製程式碼

3. 檢視統計資訊

$ docker stats [OPTIONS] <CONTAINER ID>
複製程式碼

更新配置

$ docker update --restart=always <CONTAINER ID>
複製程式碼

重新命名容器

$ docker rename <old name> <new name>
複製程式碼

檢視容器日誌

$ docker logs -f <CONTAINER ID>
複製程式碼

Portainer 容器管理工具

$ docker volume create portainer_data
$ docker run -d -p 9000:9000 \
		-v /var/run/docker.sock:/var/run/docker.sock \
		-v portainer_data:/data \
		--name portainer \
		--restart=always \
		portainer/portainer
複製程式碼

配置 /etc/nginx/sites-enabled/dafulat 檔案:

upstream portainer {
    server 127.0.0.1:9000;
}

server {
  listen 80;

  location /portainer/ {
      proxy_http_version 1.1;
      proxy_set_header Connection "";
      proxy_pass http://portainer/;
  }
  location /portainer/ws/ {
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
      proxy_http_version 1.1;
      proxy_pass http://portainer/ws/;
  }
}
複製程式碼

Docker 資料持久化

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

  • 資料卷 可以在容器之間共享和重用
  • 資料卷 的修改會立馬生效
  • 資料卷 的更新,不會影響映象
  • 資料卷 預設會一直存在,即使容器被刪除

1. 建立資料卷

$ docker volume create my-vol
複製程式碼

除了 create 子命令外,docker volume 還支援 inspect(檢視詳細資訊)、ls(列出已有資料卷)、prune(清理無用資料卷)、rm(刪除資料卷)

2. 繫結資料卷

--mount

$ docker run -d -P \
    --name web \
    --mount source=my-vol,target=/webapp \
    training/webapp \
    python app.py
複製程式碼

-v--volume

$ docker run -d -P \
    --name web \
    -v my-vol:/wepapp \
    training/webapp \
    python app.py
複製程式碼

source 也可以是絕對路徑的任意系統位置。

如果直接掛載一個檔案到容器,使用檔案編輯工具,包括 vi 或者 sed --in-place 的時候,可能會造成檔案 inode 的改變,從 Docker 1.1 起,這會導致報錯誤資訊。所以推薦的方式是直接掛載檔案所在的目錄到容器內。

Docker 相關的定時任務

# crontab -e
# 每天凌晨強制刪除無用映象,不光是臨時映象;每天凌晨清理無用的資料卷
59 23 * * * docker image prune -af && docker volume prune -f
複製程式碼

擴充套件閱讀

聯絡作者

作者微信 知識星球 讚賞作者
一個前端工程師的Docker學習筆記【持續更新】 一個前端工程師的Docker學習筆記【持續更新】 一個前端工程師的Docker學習筆記【持續更新】

相關文章