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

Docker 是個劃時代的開源項目,它完全釋放了計算虛擬化的威力,極大提升了應用的維護效率,下降了雲計算應用開發的成本!使用 Docker,可讓應用的部署、測試和分發都變得史無前例的高效和輕鬆!html

不管是應用開發者、運維人員、仍是其餘信息技術從業人員,都有必要認識和掌握 Docker,節約有限的生命。前端

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

基礎入門

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

概念

DevOps

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

DevOps 的引入能對產品交付、測試、功能開發和維護(包括曾經罕見但現在已家常便飯的「熱補丁」)起到意義深遠的影響。在缺少 DevOps 能力的組織中,開發與運營之間存在着信息「鴻溝」。例如運營人員要求更好的可靠性和安全性,開發人員則但願基礎設施響應更快,而業務用戶的需求則是更快地將更多的特性發布給最終用戶使用。這種信息鴻溝就是最常出問題的地方。linux

容器

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

虛擬化

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

Docker 與虛擬機比較

特性 容器 虛擬機
啓動 秒級 分鐘級
硬盤使用 通常爲 MB 通常爲 GB
性能 接近原生 弱於
系統支持量 單機支持上千個容器 通常幾十個

以下圖,虛擬機是在硬件層面實現虛擬化,須要額外的虛擬機管理應用和虛擬機操做系統層。Docker容器是在操做系統層面上實現虛擬化,直接複用本地主機的操做系統,所以更加輕量級。github

Docker核心概念

鏡像(Image)

咱們都知道,操做系統分爲內核和用戶空間。對於 Linux 而言,內核啓動後,會掛載 root 文件系統爲其提供用戶空間支持。而 Docker 鏡像(Image),就至關因而一個 root 文件系統。好比官方鏡像 ubuntu:18.04 就包含了完整的一套 Ubuntu 18.04 最小系統的 root 文件系統。golang

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
複製代碼

擴展閱讀

聯繫做者

做者微信 知識星球 讚揚做者
相關文章
相關標籤/搜索