Docker 是個劃時代的開源項目,它完全釋放了計算虛擬化的威力,極大提升了應用的維護效率,下降了雲計算應用開發的成本!使用 Docker,可讓應用的部署、測試和分發都變得史無前例的高效和輕鬆!html
不管是應用開發者、運維人員、仍是其餘信息技術從業人員,都有必要認識和掌握 Docker,節約有限的生命。前端
本文是筆者以一個前端工程師的視角學習 Docker 過程當中的筆記,若是對您有所幫助,榮幸之至。vue
Docker 使用 Google
公司推出的 Go 語言 進行開發實現,基於 Linux
內核的 cgroup,namespace,以及 OverlayFS 類的 Union FS 等技術,對進程進行封裝隔離,屬於 操做系統層面的虛擬化技術。因爲隔離的進程獨立於宿主和其它的隔離的進程,所以也稱其爲容器。最初實現是基於 LXC,從 0.7 版本之後開始去除 LXC
,轉而使用自行開發的 libcontainer,從 1.11 開始,則進一步演進爲使用 runC 和 containerd。node
DevOps(Development和Operations的組合詞)是一種重視軟件開發人員(Dev)和IT運維技術人員(Ops)之間溝通合做的文化、運動或慣例。透過自動化「軟件交付」和「架構變動」的流程,來使得構建、測試、發佈軟件可以更加地快捷、頻繁和可靠。python
DevOps 的引入能對產品交付、測試、功能開發和維護(包括曾經罕見但現在已家常便飯的「熱補丁」)起到意義深遠的影響。在缺少 DevOps 能力的組織中,開發與運營之間存在着信息「鴻溝」。例如運營人員要求更好的可靠性和安全性,開發人員則但願基礎設施響應更快,而業務用戶的需求則是更快地將更多的特性發布給最終用戶使用。這種信息鴻溝就是最常出問題的地方。linux
容器有效地將由單個操做系統管理的資源劃分到孤立的組中,以更好地在孤立的組之間平衡有衝突的資源使用需求。與虛擬化相比,這樣既不須要指令級模擬,也不須要即時編譯。容器能夠在覈心 CPU 本地運行指令,而不須要任何專門的解釋機制。此外,也避免了準虛擬化(para-virtualization)和系統調用替換中的複雜性。nginx
在計算機技術中,虛擬化是一種資源管理技術,是將計算機中的各類實體資源,如服務器、網絡、內存及存儲等,予以抽象、轉換後呈現出來,打破實體結構間的不可切割的障礙,使用戶能夠用比原來的組態更好的方式來應用這些資源。git
特性 | 容器 | 虛擬機 |
---|---|---|
啓動 | 秒級 | 分鐘級 |
硬盤使用 | 通常爲 MB |
通常爲 GB |
性能 | 接近原生 | 弱於 |
系統支持量 | 單機支持上千個容器 | 通常幾十個 |
以下圖,虛擬機是在硬件層面實現虛擬化,須要額外的虛擬機管理應用和虛擬機操做系統層。Docker容器是在操做系統層面上實現虛擬化,直接複用本地主機的操做系統,所以更加輕量級。github
咱們都知道,操做系統分爲內核和用戶空間。對於 Linux 而言,內核啓動後,會掛載 root
文件系統爲其提供用戶空間支持。而 Docker 鏡像(Image),就至關因而一個 root
文件系統。好比官方鏡像 ubuntu:18.04
就包含了完整的一套 Ubuntu 18.04 最小系統的 root
文件系統。golang
Docker 鏡像是一個特殊的文件系統,除了提供容器運行時所需的程序、庫、資源、配置等文件外,還包含了一些爲運行時準備的一些配置參數(如匿名卷、環境變量、用戶等)。鏡像不包含任何動態數據,其內容在構建以後也不會被改變。
分層存儲
由於鏡像包含操做系統完整的 root
文件系統,其體積每每是龐大的,所以在 Docker 設計時,就充分利用 Union FS 的技術,將其設計爲分層存儲的架構。因此嚴格來講,鏡像並不是是像一個 ISO 那樣的打包文件,鏡像只是一個虛擬的概念,其實際體現並不是由一個文件組成,而是由一組文件系統組成,或者說,由多層文件系統聯合組成。
鏡像構建時,會一層層構建,前一層是後一層的基礎。每一層構建完就不會再發生改變,後一層上的任何改變只發生在本身這一層。好比,刪除前一層文件的操做,實際不是真的刪除前一層的文件,而是僅在當前層標記爲該文件已刪除。在最終容器運行的時候,雖然不會看到這個文件,可是實際上該文件會一直跟隨鏡像。所以,在構建鏡像的時候,須要額外當心,每一層儘可能只包含該層須要添加的東西,任何額外的東西應該在該層構建結束前清理掉。
分層存儲的特徵還使得鏡像的複用、定製變的更爲容易。甚至能夠用以前構建好的鏡像做爲基礎層,而後進一步添加新的層,以定製本身所需的內容,構建新的鏡像。
鏡像(Image
)和容器(Container
)的關係,就像是面向對象程序設計中的 類
和 實例
同樣,鏡像是靜態的定義,容器是鏡像運行時的實體。容器能夠被建立、啓動、中止、刪除、暫停等。
容器的實質是進程,但與直接在宿主執行的進程不一樣,容器進程運行於屬於本身的獨立的 命名空間。所以容器能夠擁有本身的 root
文件系統、本身的網絡配置、本身的進程空間,甚至本身的用戶 ID 空間。容器內的進程是運行在一個隔離的環境裏,使用起來,就好像是在一個獨立於宿主的系統下操做同樣。這種特性使得容器封裝的應用比直接在宿主運行更加安全。也由於這種隔離的特性,不少人初學 Docker 時經常會混淆容器和虛擬機。
前面講過鏡像使用的是分層存儲,容器也是如此。每個容器運行時,是以鏡像爲基礎層,在其上建立一個當前容器的存儲層,咱們能夠稱這個爲容器運行時讀寫而準備的存儲層爲 容器存儲層。
容器存儲層的生存週期和容器同樣,容器消亡時,容器存儲層也隨之消亡。所以,任何保存於容器存儲層的信息都會隨容器刪除而丟失。
按照 Docker 最佳實踐的要求,容器不該該向其存儲層內寫入任何數據,容器存儲層要保持無狀態化。全部的文件寫入操做,都應該使用 數據卷(Volume)、或者綁定宿主目錄,在這些位置的讀寫會跳過容器存儲層,直接對宿主(或網絡存儲)發生讀寫,其性能和穩定性更高。
數據卷的生存週期獨立於容器,容器消亡,數據卷不會消亡。所以,使用數據卷後,容器刪除或者從新運行以後,數據卻不會丟失。
一個 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)是一種在後臺執行的電腦程序。此類程序會被以進程的形式初始化。守護進程程序的名稱一般以字母」d「結尾:例如,syslogd
就是指管理系統日誌的守護進程。
一般,守護進程沒有任何存在的父進程(即PPID=1),且在 UNIX 系統進程層級中直接位於 init 之下。守護進程程序一般經過以下方法是本身成爲守護進程:對一個子進程進行 fork,而後使其父進程當即終止,使得這個子進程能在 init 下運行。這種方法一般被稱爲」脫殼「。
系統一般在啓動時一同引導守護進程。守護進程爲對網絡請求,硬件活動等進行響應,或其餘經過某些任務對其餘應用程序的請求進行迴應提供支持。守護進程也可以對硬件進行配置(如某些Linux系統上的devfsd),運行計劃任務(例如cron),以及運行其餘任務。
在 DOS 環境中,此類應用程序被稱爲駐留程序(TSR)。在 Windows 系統中,由稱爲 Windows服務的應用程序來履行守護進程的職責。
在本來的 Mac OS 系統中,此類應用程序被稱爲」extensions「。而做爲 Unux-like 的 Mac OS X 有守護進程。
uname -a
查看$ 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
:
編輯 /etc/default/grub
文件
$ nano /etc/default/grub
複製代碼
找到 GRUB_CMDLINE_LINUX=
配置項,並追加 cgroup_enable=memory swapaccount=1
。
保存文件後執行一下命令:sudo update-grub
重啓服務器:reboot
$ 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/
複製代碼
執行 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 pull [選項] [Docker Registry 地址[:端口號]/][用戶名]<倉庫名>[:TAG]
-a
,--all-tags=true|false
:是否獲取倉庫中的全部鏡像,默認爲否--disable-content-trust
:取消鏡像的內容校驗,默認爲真registry.hub.docker.com
library
,也就是官方鏡像latest
docker image ls
|docker images
鏡像的大小信息只是表示了該鏡像的邏輯體積大小,實際上因爲相同的鏡像層本地只會存儲一份,物理上佔用的存儲空間會小於各鏡像邏輯體積之和。
docker tag ubuntu:latest myubuntu:latest
爲了方便在後續工做中使用特定鏡像,還可使用 docker tag
命令來爲本地鏡像任意添加新的標籤。
docker inspect <倉庫>
使用 docker inspect
命令能夠獲取該鏡像的詳細信息,包括製做者、適應架構、各層的數字摘要等。
docker history <REPOSITORY>[:TAG]
或docker history <IMAGE ID>
注意,過長的命令會被自動截斷了,可使用 --no-trunc
選項來輸出完整命令。
使用標籤刪除鏡像
docker rmi <IMAGE> [IMAGE...]
或docker image rm <IMAGE> [IMAGE...]
使用鏡像 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
複製代碼
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
複製代碼
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]
發佈新版本流程:
docker push youngjuning/alpine:latest
docker tag youngjuning/alpine:latest youngjuning/alpine:1.0.0
docker push youngjuning/alpine:1.0.0
能夠查看https://hub.docker.com/r/youngjuning/alpine項目查看我發佈的基於aliyun鏡像的 Aplpine Docker Image
$ docker run -it ubuntu:18.04 /bin/bash
複製代碼
其中, -t
選項讓 Docker 分配一個僞終端(pseudo-tty)並綁定到容器的標準輸入上,-i
則讓容器的標準輸入保持打開。
當利用 docker run
來建立容器時,Docker 在後臺運行的標準操做包括:
一些經常使用選項:
-d
,--detach=true|false
:是否在後臺運行容器,默認爲false
-i
,--interactive=true|false
:保持標準輸入打開,默認爲 false
-p
,--publish=[]
:指定如何映射到本地主機端口,例如 -p 9000:9000
--restart="no"
:容器的重啓策略,包括 no
、on-failure[:max-retry]
、always
、unless-stopped
等--rm=true|false
:容器退出後是否自動刪除,不能跟 -d
同時使用-t
,--tty=true|false
:是否分配一個僞終端,默認爲 false
-v [HOST-DIR:]<CONTAINER-DIR>[:OPTIONS]
,--volume=[HOST-DIR:]<CONTAINER-DIR>[:OPTIONS]
:掛在主機上的文件捲到容器內--name=""
:指定容器的別名能夠利用 docker start <CONTAINER ID>
命令,直接將一個已經終止的容器啓動運行。
要獲取容器的輸出信息,能夠經過 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
複製代碼
$ docker inspect [OPTIONS] <CONTAINER ID>
複製代碼
$ docker top [OPTIONS] <CONTAINER ID>
複製代碼
$ docker stats [OPTIONS] <CONTAINER ID>
複製代碼
$ docker update --restart=always <CONTAINER ID>
複製代碼
$ docker rename <old name> <new name>
複製代碼
$ docker logs -f <CONTAINER ID>
複製代碼
$ 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/;
}
}
複製代碼
數據卷
是一個可供一個或多個容器使用的特殊目錄,它繞過 UFS,能夠提供不少有用的特性:
數據卷
能夠在容器之間共享和重用數據卷
的修改會立馬生效數據卷
的更新,不會影響鏡像數據卷
默認會一直存在,即便容器被刪除$ docker volume create my-vol
複製代碼
除了 create
子命令外,docker volume 還支持 inspect
(查看詳細信息)、ls
(列出已有數據卷)、prune
(清理無用數據卷)、rm
(刪除數據卷)
--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 起,這會致使報錯誤信息。因此推薦的方式是直接掛載文件所在的目錄到容器內。
# crontab -e
# 天天凌晨強制刪除無用鏡像,不光是臨時鏡像;天天凌晨清理無用的數據卷
59 23 * * * docker image prune -af && docker volume prune -f
複製代碼
做者微信 | 知識星球 | 讚揚做者 |
---|---|---|
![]() |
![]() |
![]() |