1. Docker1.1 什麼是Docker
Docker是開源的,基於Linux容器技術的引擎,統一了被隔離的應用程式訪問系統核心的API。試圖解決開發者的世紀難題在我的機器上可以跑。
前端同學可以視映象為npm包,倉庫為npm倉庫。這樣更方便理解。
1.2 為什麼使用DockerDocker是一種類似虛擬機器技術的縮減版,由於虛擬機器啟動過程過於漫長與虛擬化之後的硬體在執行程式的時候,並不能很好的契合物理機,比較典型的例子就是移動端開發,啟動虛擬系統的時候,過程十分的漫長。
我們經常開啟一個虛擬機器僅僅是需要隔離一個應用,但是虛擬機器建立佔用了一套完整的系統資源(guest os),存在著大材小用的問題,成本也息息相關。
而Docker隨著Linux功能的更新出現了,Docker本質僅隔離應用程式,共享當前系統核心。
下圖為虛擬機器與Docker架構對比:
下圖為容器虛擬機器功能對比:
這樣的話,Docker就可以進行秒啟動,因為Docker跳過了系統初始化(kernel init),直接使用了當前系統核心。但是這個也是有弊病的,比如 虛擬機器熱遷移 這個功能,Docker就做的不是很好。
使用Docker可以快速的搭建配置應用環境,簡化操作,確保執行環境一致性“一次編譯 到處執行”,應用級隔離,彈性伸縮,快速拓展。
1.3 Docker基本概念1.3.1 映象映象是一個特殊的檔案系統,除了提供容器執行時所需的程式、庫、資源、配置等檔案外,還包含了一些為執行時準備的一些配置引數(如匿名卷、環境變數、使用者等)。 映象不包含任何動態資料,其內容在構建之後也不會被改變。
映象利用(union file system)提供應用執行的只讀模板,它可以只提供一個功能,也可以由多個映象疊加建立多個功能服務。
1.3.2 容器映象僅僅是定義隔離應用執行所需要的東西,容器則是執行這些映象的程序。在容器內部,提供了完整的 檔案系統、網路、程序空間等等。完全隔離於外部環境,不會受到其他應用的侵擾。
容器的讀寫必須使用**Volume**,或者宿主的儲存環境,容器在重啟或者關閉之後,存在於執行容器內部的資料將會丟失,每次啟動容器,都是透過映象建立一個新的容器。
1.3.3 倉庫Docker 倉庫是集中存放映象檔案的場所。映象構建完成後,可以很容易的在當前宿主上執行,但是, 如果需要在其它伺服器上使用這個映象,我們就需要一個集中的儲存、分發映象的服務,Docker Registry (倉庫註冊伺服器)就是這樣的服務。有時候會把倉庫 (Repository) 和倉庫註冊伺服器 (Registry) 混為一談,並不嚴格區分。Docker 倉庫的概念跟 Git 類似,註冊伺服器可以理解為 GitHub 這樣的託管服務。實際上,一個 Docker Registry 中可以包含多個倉庫 (Repository) ,每個倉庫可以包含多個標籤 (Tag),每個標籤對應著一個映象。所以說,映象倉庫是 Docker 用來集中存放映象檔案的地方類似於我們之前常用的程式碼倉庫。
通常,一個倉庫會包含同一個軟體不同版本的映象,而標籤就常用於對應該軟體的各個版本 。我們可以透過<倉庫名>:<標籤>的格式來指定具體是這個軟體哪個版本的映象。如果不給出標籤,將以 latest 作為預設標籤.。
倉庫又可以分為兩種形式:
public(公有倉庫)private(私有倉庫)1.3.4 Docker clientDocker client是一個泛稱,用來向指定的Docker Engine發起請求,執行相應的容器管理操作.它既可以是Docker命令列工具,也可以是任何遵循了Docker API的客戶端.目前, 社群中維護著的Docker client種類非常豐富,涵蓋了包括C#(支援 Windows)、Java、Go、Ruby、JavaScript等常用語言,甚至還有使用Angular庫編寫的WebU格式的客戶端,足以滿足大多數使用者的需求。
1.3.5 Docker EngineDocker Engine是Docker最核心的後臺程序,它負責響應來自Docker client的請求,然後將這些請求翻譯成系統呼叫完成容器管理操作。該程序會在後臺啟動一個API Server,負責接收由Docker client傳送的請求;接收到的請求將透過Docker Engine內部的一個路由分發排程,再由具體的函式來執行請求。
2. 實戰Docker##2.1 安裝Docker
本文所有環境均執行在centos7下。
首先,移除所有老版本的Docker。
sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine
如果,是一個全新環境,那可以跳過這一步。
為國內一些原因,所以按照官網安裝docker-ce大機率是裝不上的,所以,我們需要國內映象來加速安裝。下面我們透過aliyun加速安裝。
# step 1: 安裝必要的一些系統工具sudo yum install -y yum-utils device-mapper-persistent-data lvm2# Step 2: 新增軟體源資訊sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo# Step 3: 更新並安裝 Docker-CEsudo yum makecache fastsudo yum -y install docker-ce# Step 4: 開啟Docker服務sudo service docker start
安裝完成之後可以執行docker version檢查是否安裝成功。
➜ ~ docker versionClient: Docker Engine - Community Version: 19.03.3 API version: 1.40 Go version: go1.12.10 Git commit: a872fc2f86 Built: Tue Oct 8 00:58:10 2019 OS/Arch: linux/amd64 Experimental: falseServer: Docker Engine - Community Engine: Version: 19.03.3 API version: 1.40 (minimum version 1.12) Go version: go1.12.10 Git commit: a872fc2f86 Built: Tue Oct 8 00:56:46 2019 OS/Arch: linux/amd64 Experimental: false containerd: Version: 1.2.10 GitCommit: b34a5c8af56e510852c35414db4c1f4fa6172339 runc: Version: 1.0.0-rc8+dev GitCommit: 3e425f80a8c931f88e6d94a8c831b9d5aa481657 docker-init: Version: 0.18.0 GitCommit: fec3683
##2.2 獲取一個映象
現在我們需要拉取一個nginx映象,部署一個nginx應用。
➜ ~ docker pull nginxUsing default tag: latestlatest: Pulling from library/nginx68ced04f60ab: Pull complete 28252775b295: Pull complete a616aa3b0bf2: Pull complete Digest: sha256:2539d4344dd18e1df02be842ffc435f8e1f699cfc55516e2cf2cb16b7a9aea0bStatus: Downloaded newer image for nginx:latestdocker.io/library/nginx:latest
拉取完成之後,使用docker image ls檢視當前docker本地映象列表。
➜ ~ docker image lsREPOSITORY TAG IMAGE ID CREATED SIZEnginx latest 6678c7c2e56c 13 hours ago 127MB
重新執行相同的命令docker pull nginx會更新本地映象。
##2.3 執行一個Docker容器
建立一個shell指令碼檔案,寫入以下內容:
docker run \ # 指定容器停止後的重啟策略: # no:容器退出時不重啟 # on-failure:容器故障退出(返回值非零)時重啟 # always:容器退出時總是重啟 --restart=always \ # 指定docker執行在後臺,如果不加-d,在執行完這條命令之後 # 你退出命令列也會將這個docker容器退掉 -d \ # 將宿主機埠號繫結至容器埠上 -p 8080:80 \ # 指定容器暴露的埠,即修改映象的暴露埠 --expose=80 \ # 對映宿主目錄至 -v /wwwroot:/usr/share/nginx/html \ # 指定容器名字,後續可以透過名字進行容器管理,links特性需要使用名字 --name=testdocker \ # 用哪個映象初始化這個容器 nginx:lastest
我們要明確一點docker的容器網路是與宿主機隔離的,除非指定容器網路模式依託宿主機,否則是無法直接訪問的。
現在我們執行這個指令碼,接著開啟瀏覽器,輸入http://ip:8080就可以看到使用nginx映象執行的應用程式了。
2.3.1 命令引數簡明版Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] 02. 03. -d, --detach=false 指定容器運行於前臺還是後臺,預設為false 04. -i, --interactive=false 開啟STDIN,用於控制檯互動 05. -t, --tty=false 分配tty裝置,該可以支援終端登入,預設為false 06. -u, --user="" 指定容器的使用者 07. -a, --attach=[] 登入容器(必須是以docker run -d啟動的容器) 08. -w, --workdir="" 指定容器的工作目錄 09. -c, --cpu-shares=0 設定容器CPU權重,在CPU共享場景使用 10. -e, --env=[] 指定環境變數,容器中可以使用該環境變數 11. -m, --memory="" 指定容器的記憶體上限 12. -P, --publish-all=false 指定容器暴露的埠 13. -p, --publish=[] 指定容器暴露的埠 14. -h, --hostname="" 指定容器的主機名 15. -v, --volume=[] 給容器掛載儲存卷,掛載到容器的某個目錄 16. --volumes-from=[] 給容器掛載其他容器上的卷,掛載到容器的某個目錄 17. --cap-add=[] 新增許可權,許可權清單詳見:http://linux.die.net/man/7/capabilities 18. --cap-drop=[] 刪除許可權,許可權清單詳見:http://linux.die.net/man/7/capabilities 19. --cidfile="" 執行容器後,在指定檔案中寫入容器PID值,一種典型的監控系統用法 20. --cpuset="" 設定容器可以使用哪些CPU,此引數可以用來容器獨佔CPU 21. --device=[] 新增主機裝置給容器,相當於裝置直通 22. --dns=[] 指定容器的dns伺服器 23. --dns-search=[] 指定容器的dns搜尋域名,寫入到容器的/etc/resolv.conf檔案 24. --entrypoint="" 覆蓋image的入口點 25. --env-file=[] 指定環境變數檔案,檔案格式為每行一個環境變數 26. --expose=[] 指定容器暴露的埠,即修改映象的暴露埠 27. --link=[] 指定容器間的關聯,使用其他容器的IP、env等資訊 28. --lxc-conf=[] 指定容器的配置檔案,只有在指定--exec-driver=lxc時使用 29. --name="" 指定容器名字,後續可以透過名字進行容器管理,links特性需要使用名字 30. --net="bridge" 容器網路設定: 31. bridge 使用docker daemon指定的網橋 32. host //容器使用主機的網路 33. container:NAME_or_ID >//使用其他容器的網路,共享IP和PORT等網路資源 34. none 容器使用自己的網路(類似--net=bridge),但是不進行配置 35. --privileged=false 指定容器是否為特權容器,特權容器擁有所有的capabilities 36. --restart="no" 指定容器停止後的重啟策略: 37. no:容器退出時不重啟 38. on-failure:容器故障退出(返回值非零)時重啟 39. always:容器退出時總是重啟 40. --rm=false 指定容器停止後自動刪除容器(不支援以docker run -d啟動的容器) 41. --sig-proxy=true 設定由代理接受並處理訊號,但是SIGCHLD、SIGSTOP和SIGKILL不能被代理
2.4 出入容器我們可以使用docker exec -it [docker container id] /bin/bash 來進入正在執行的容器。
而要退出容器,有兩種方式:
直接在命令列輸入exit就退出使用快捷鍵ctrl+P Q也會退出以上兩種方式均可從容器中退出,並且保持容器在後臺執行。
##2.5 自定義一個映象Dockerfile
Dockerfile 分為四部分:基礎映象資訊、維護者資訊、映象操作指令和容器啟動時執行指令。
這裡我使用了一份簡單的node啟動開發環境的Dockerfile。
# 1. 設定來源的基礎映象FROM node:12.0# 指定後續 RUN、CMD、ENTRYPOINT 指令的工作目錄WORKDIR /workspace# 在上一次透過WORKDIR指定的目錄執行 RUN 後續命令RUN npm install --registry=https://registry.npm.taobao.org# 初始化暴露 8080 8001 8800埠號# 以下埠號 也可以在docker run時暴露出去EXPOSE 8080EXPOSE 8001EXPOSE 8800# 預設執行的命令,如果在宿主機透過docker run -it /bin/bash進入時,以下命令不會被執行# 完全不被覆蓋的指令為 ENTRYPOINTCMD ["npm","run","dev-server"]
儲存退出編輯,執行docker build -t nodeapp:v1.0 . 注意 最後一個.表示當前目錄。
在執行完成之後,使用docker image ls檢視是否有已經編譯完成的映象即可。
這個時候,可能有些人有個提問,就是每次都需要npm install安裝檔案嗎?
其實,如果你的node應用包不會變化,而你這個映象又是專為此應用開發的,可以考慮使用ADD指令,將node_modules追加至docker映象中。(現實基本不會如此處理,因為外部如果映射了資料捲進來會覆蓋目錄,此處只是為了演示像映象追加檔案。)
##2.6 多容器啟動:Docker-compose
Docker-compose 需要單獨安裝。
我們來假設一個場景,我們啟動了一個前端專案。需要啟動nginx執行前臺專案,啟動一個數據庫來記錄資料,保證整個應用的完整。那麼這就是docker-compose的用武之地了。
首先要知道,docker-compose由以下兩種型別組成:
服務 (service):一個應用容器,實際上可以執行多個相同映象的例項。專案 (project):由一組關聯的應用容器組成的一個完整業務單元。我們回到之前建立Dockerfile的目錄中,編寫一個docker-compose.yml檔案,配置多容器。
version: '1'services: web: build: . ports: - "8080:80" volumes: - /wwwroot:/usr/share/nginx/html redis: image: "redis:alpine"
執行命令docker-compose up之後,我們透過docker stats可以看到兩個(web、redis)docker已經啟動了。
訪問http://ip:8080就可以看到跟之前一樣的網頁了。
2.7 網路由於應有隔離,你無法在外網直接訪問到宿主機上的docker容器。所以我們需要將宿主機上的埠繫結到容器上。
2.3已經介紹瞭如何繫結埠與匯出容器埠。我們這裡瞭解一下容器互聯。
# 執行命令建立一個docker網路$ docker network create -d bridge my-net# 建立兩個容器 加入my-net網路$ docker run -it --rm --name busybox1 --network my-net busybox sh$ docker run -it --rm --name busybox2 --network my-net busybox sh# 接著我們進入busybox1$ docker exec -it busybox1 /bin/bash# ping 另外一個容器,可以看到他的IP資訊$ root@busybox1:ping busybox2PING busybox2 (172.19.0.3): 56 data bytes64 bytes from 172.19.0.3: seq=0 ttl=64 time=0.072 ms64 bytes from 172.19.0.3: seq=1 ttl=64 time=0.118 ms
3. 拓展知識3.1 Docker原理Docker使用Go語言編寫,並且使用了一系列Linux核心提供的特性來實現其功能。
一個能執行Docker的系統分為兩大部分:
Linux的核心元件Docker相關元件Docker使用的Linux核心模組功能包括下列各項:
Cgroup – 用來分配硬體資源Namespace – 用來隔離不同Container的執行空間AUFS(chroot) – 用來建立不同Container的檔案系統SELinux – 用來確保Container的網路的安全Netlink – 用來讓不同Container之間的行程進行溝通Netfilter – 建立Container埠為基礎的網路防火牆封包過濾AppArmor – 保護Container的網路及執行安全Linux Bridge – 讓不同Container或不同主機上的Container能溝通3.2 mac window執行Docker原理使用虛擬機器執行Linux,然後在Linux中執行Docker Engine。在本機執行Docker client。
3.3 不可使用 CMD ['node']啟動Docker前端同學關注點
為什麼不能用CMD ['node','app.js']作為預設啟動,因為在 Node.js 的官方最佳實踐裡有寫到 "Node.js was not designed to run as PID 1 which leads to unexpected behaviour when running inside of Docker."。下圖來自 github。
這個問題涉及linux執行機制,簡單的說,就是 linux pid為1的程序是系統守護的程序,將會接收所有孤兒程序。並且在適當的時候傳送關閉訊號給這些程序。
但是,docker中pid 1的程序為node,而node並沒有做回收孤兒程序的事情。所以,如果你的應用跑類似爬蟲之類的應用,執行完畢之後將程序掛到pid 1 上,慢慢的容器就會BOOM。
解決方案:
1. 用`/bin/bash`啟動。2. 在`docker run`後面追加`--init`用於初始化一個docker的程序為pid 1。docker提供的程序可以回收所有孤兒程序。