努力工做,而後進入夢鄉,「工做」和「作夢」之間好像沒有任何關聯;編寫代碼,而後部署應用,這二者彷佛也是天各一邊。然而果然如此嗎?這篇文章將經過《盜夢空間》的方式打開 Docker,讓你實現從「作夢」到「築夢」的實質性轉變。在原先的「作夢」階段(手動配置和部署),一切都充滿了隨機性和不可控性,你有時甚至都沒法回憶起具體作的每一步;而在「築夢」階段(藉助 Docker),你將經過自動化、高度可重複且可追蹤的方式輕鬆實現任何配置和部署任務。但願讀完這篇文章的你,也能成爲一個優秀的「築夢師」!html
不少朋友跟咱們反饋說,「一杯茶」純粹就是忽悠人,寫那麼長,怎麼可能在一杯茶的時間內看完?實際上,「飲茶」的方式因人而異,不一樣的讀者自有不一樣的節奏。你徹底能夠選擇一目十行、甚至只瀏覽一下插圖,幾分鐘的時間便能看完;也能夠選擇跟着咱們一步一步動手實踐,甚至在有些地方停下來思考一番,雖然須要花更多的時間,可是咱們相信這份投入的時間必定是值得的。前端
其次,咱們想確認你是不是這篇文章的受衆:node
最後,每一個小節的結構都是實戰演練 + 回憶與昇華。回憶與昇華部分是筆者花了很多時間對優質資源進行蒐集和整合而成,並結合了自身使用容器的經驗,相信可以進一步加深你的理解,若是你趕時間的話,也能夠略過哦。nginx
在正式閱讀這篇文章以前,咱們但願你已經具有如下條件:git
如今假定你手頭已經有了一個 React 編寫的「夢想清單」項目,以下面這個動圖所示:程序員
咱們將在這篇文章中教你一步步用 Docker 將這個應用容器化,用 Nginx 服務器提供構建好的靜態頁面。github
固然咯,這篇文章做爲一篇入門性質的教程,如下進階內容不會涉及:docker
以上進階知識咱們會立刻推出相關教程,敬請期待。數據庫
咱們推薦各個平臺用如下方式安裝 Docker(通過咱們反覆測試哦)。npm
菜鳥教程中詳細介紹了 Win7/8 以及 Win10 的不一樣推薦安裝方法。注意 Win10 建議開啓 Hyper-V 虛擬化技術。
可經過點擊官方下載連接下載並安裝 DMG 文件(若是速度慢的話能夠把連接複製進迅雷哦)。安裝完畢以後,點擊 Docker 應用圖標便可打開。
對於各大 Linux 發行版(Ubuntu、CentOS 等等),咱們推薦用官方腳本進行安裝,方便快捷:
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
複製代碼
而後推薦將 docker
的權限移交給非 root 用戶,這樣使用 docker
就不須要每次都 sudo
了:
sudo usermod -aG docker $USER
複製代碼
註銷用戶或者重啓以後就會生效。而後經過 systemd
服務配置 Docker 開機啓動:
sudo systemctl enable docker
複製代碼
默認的鏡像倉庫 Docker Hub 在國外,國內拉取速度比較感人。建議參考這篇文章進行配置。
鏡像(Image)和容器(Container)是 Docker 中最爲基礎也是最爲關鍵的兩個概念,前者就是築夢師的圖紙,根據這張圖紙的內容,就可以生成徹底可預測的夢境(也就是後者)。
提示
若是你以爲這個比喻難以理解,那麼能夠經過面向對象編程中「類」(class)和「實例」(instance)這兩個概念進行類比,「類」就至關於「鏡像」,「實例」就至關於「容器」。
在略微接觸了鏡像與容器這兩個基礎概念以後,咱們打算暫停理論的講解,而先來一波小實驗讓你快速感覺一下。
按照歷史慣例,咱們運行一下來自 Docker 的 Hello World,命令以下:
docker run hello-world
複製代碼
輸出以下:
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
1b930d010525: Pull complete
Digest: sha256:fb158b7ad66f4d58aa66c4455858230cd2eab4cdf29b13e5c3628a6bfc2e9f05
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
...
複製代碼
不就打印了一個字符串而後退出嗎,有這麼神奇?其實 Docker 爲咱們默默作了如下事情:
hello-world:latest
鏡像(latest
是鏡像標籤,後面會細講),若是沒有,執行第 2 步,不然直接執行第 3 步hello-world:latest
鏡像建立一個新的容器並運行其中的程序感受太簡單?咱們來嘗試一個高級一點的:運行一個 Nginx 服務器。運行如下命令
docker run -p 8080:80 nginx
複製代碼
運行以後,你會發現一直卡住,也沒有任何輸出,但放心你的電腦並無死機。讓咱們打開瀏覽器訪問 localhost:8080
:
這時候熟悉 Nginx 的朋友可能就坐不住了:就一個簡簡單單的 docker run
命令,就搞定了 Nginx 服務器的安裝和部署??沒錯,你能夠繼續訪問一些不存在的路由,好比 localhost:8080/what
,一樣會提示 404。這時候咱們再看 Docker 容器的輸出,就有內容(服務器日誌)了:
總結一下剛纔 Docker 作的事情:
nginx:latest
鏡像(關於 latest
標籤,後面會細講),若是沒有,執行第 2 步,不然直接執行第 3 步nginx:latest
鏡像建立一個新的容器,並經過 -p
(--publish
)參數創建本機的 8080 端口與容器的 80 端口之間的映射,而後運行其中的程序提示
端口映射規則的格式爲
<本機端口>:<容器端口>
。Nginx 容器默認開放了 80 端口,咱們經過設置8080:80
的端口映射規則,就能夠在本機(容器以外)經過訪問localhost:8080
訪問,甚至能夠在同一局域網內經過內網 IP 訪問,這篇文章的最後會演示哦。
看上去很酷,不過像 Nginx 服務器這樣的進程咱們更但願把它拋到後臺一直運行。按 Ctrl + C 退出當前的容器,而後再次運行如下命令:
docker run -p 8080:80 --name my-nginx -d nginx
複製代碼
注意到與以前不一樣的是,咱們:
--name
,用於指定容器名稱爲 my-nginx
-d
(--detach
),表示「後臺運行」警告
容器的名稱必須是惟一的,若是已經存在同一名稱的容器(即便已經再也不運行)就會建立失敗。若是遇到這種狀況,能夠刪除以前不須要的容器(後面會講解怎麼刪除)。
Docker 會輸出一串長長的 64 位容器 ID,而後把終端的控制權返回給了咱們。咱們試着訪問 localhost:8080
,還能看到那一串熟悉的 Welcome to nginx!,說明服務器真的在後臺運行起來了。
那咱們怎麼管理這個服務器呢?就像熟悉的 UNIX ps
命令同樣,docker ps
命令可讓咱們查看當前容器的狀態:
docker ps
複製代碼
輸出結果是這樣的:
提示
因爲
docker ps
的輸出比較寬,若是你以爲結果不直觀的話能夠把終端(命令行)拉長,以下圖所示:
從這張表中,就能夠清晰地看到了咱們在後臺運行的 Nginx 服務器容器的一些信息:
0bddac16b8d8
(你機器上的可能不同)nginx
nginx -g 'daemon of...
,這個是 Nginx 鏡像自帶的運行命令,暫時不用關心0.0.0.0:8080->80/tcp
,意思是訪問本機的 0.0.0.0:8080
的全部請求會被轉發到該容器的 TCP 80 端口my-nginx
若是咱們要讓容器停下來,經過 docker stop
命令指定容器名稱或 ID 進行操做便可,命令以下:
docker stop my-nginx
# docker stop 0bddac16b8d8
複製代碼
注意
若是指定容器 ID 的話,記得要換成本身機器上真實的 ID 哦。此外,在沒有衝突的狀況下,ID 能夠只寫前幾位字符,例如寫
0bd
也是能夠的。
在過了一把 Nginx 服務器的癮以後,咱們再來體驗一下 Docker 容器的另外一種打開方式:交互式運行。運行如下命令,讓咱們進入到一個 Ubuntu 鏡像中:
docker run -it --name dreamland ubuntu
複製代碼
能夠看到咱們加了 -it
選項,等因而同時指定 -i
(--interactive
,交互式模式)和 -t
(--tty
,分配一個模擬終端) 兩個選項。以上命令的輸出以下:
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
2746a4a261c9: Pull complete
4c1d20cdee96: Pull complete
0d3160e1d0de: Pull complete
c8e37668deea: Pull complete
Digest: sha256:9207fc49baba2e62841d610598cb2d3107ada610acd4f47252faf73ed4026480
Status: Downloaded newer image for ubuntu:latest
root@94279dbf5d93:/#
複製代碼
等下,咱們怎麼被拋在了一個新的命令行裏面?沒錯,你如今已經在這個 Ubuntu 鏡像構築的「夢境」之中,你能夠隨意地「遊走」,運行一些命令:
root@94279dbf5d93:/# whoami
root
root@94279dbf5d93:/# ls
bin dev home lib64 mnt proc run srv tmp var
boot etc lib media opt root sbin sys usr
複製代碼
例如咱們在上面運行了 whoami
和 ls
命令,你基本上能夠肯定如今已經在「夢境」(容器)之中了。這時候打開一個新的終端(命令行),運行 docker ps
命令,就能夠看到正在運行中的 Ubuntu 鏡像:
回到以前的容器中,按 Ctrl + D (或者輸入 exit
命令)便可退出。你能夠在以前查看 docker ps
的終端再次檢查容器是否已經被關閉了。
築夢師不免會有失敗的做品,而咱們剛纔建立的 Docker 容器也只是用於初步探索,後續不會再用到。因爲 Docker 容器是直接存儲在咱們本地硬盤上的,及時清理容器也可以讓咱們的硬盤壓力小一些。咱們能夠經過如下命令查看全部容器(包括已經中止的):
docker ps -a
複製代碼
-a
(--all
)用於顯示全部容器,若是不加的話只會顯示運行中的容器。能夠看到輸出以下(這裏我把終端拉寬了,方便你看):
提示
你也許觀察到,以前的實驗一和實驗二中咱們沒有指定容器名稱,Docker 爲咱們取了頗爲有趣的默認容器名稱(好比
hardcore_nash
),格式是一個隨機的形容詞加上一位著名科學家/程序員的姓氏(運氣好的話,你可能會看到 Linux 之父torvalds
哦)。
相似 Shell 中的 rm
命令,咱們能夠經過 docker rm
命令銷燬容器,例如刪除咱們以前建立的 dreamland
容器:
docker rm dreamland
# 或者指定容器 ID,記得替換成本身機器上的
# docker rm 94279dbf5d93
複製代碼
但若是咱們想要銷燬全部容器怎麼辦?一次次輸入 docker rm
刪除顯然不方便,能夠經過如下命令輕鬆刪除全部容器:
docker rm $(docker ps -aq)
複製代碼
docker ps -aq
會輸出全部容器的 ID,而後做爲參數傳給 docker rm
命令,就能夠根據 ID 刪除全部容器啦。
危險!
執行以前必定要仔細檢查是否還有有價值的容器(特別是業務數據),由於容器一旦刪除沒法再找回(這裏不討論硬盤恢復這種黑科技)!
可能有些同窗仍是沒有徹底理解「端口映射」的概念,以 8080:80
這一條映射規則爲例,咱們能夠用「傳送門」的比喻來理解(下面的圖是《傳送門2》遊戲的封面):
仍是把容器比做「夢境」,把本機環境比做「現實」,經過創建端口映射,訪問本機的 8080
端口的請求就會被「傳送」到容器的 80
端口,是否是很神奇呢。
跟着作完上面四個小實驗以後,你或許已經對 Docker 容器有了很是直觀的感覺和理解了。是時候祭出這張十(sang)分(xin)經(bing)典(kuang)的 Docker 容器生命週期圖了(來源:docker-saigon.github.io/post/Docker…
這張圖乍一看頗具視覺衝擊力,甚至會讓你感受不知所措。沒事,咱們大體地解讀這張圖裏面的四類元素:
docker
開頭的文字):包括 docker run
、docker create
、docker stop
等等create
、start
、die
、stop
還有 OOM
(內存耗盡)等等OK,這張圖仍是很難一會兒理解,不過還記得剛纔咱們作的四個小實驗嗎?咱們實際上走了一共兩條路徑(也是平常使用中走的最多的路),接下來將一一進行分析。
如上圖所示:
docker run
命令,直接建立(create)並啓動(start)一個容器,進入到運行狀態(Running)docker rm
命令銷燬容器,進入到被刪除狀態(Deleted)docker run
命令,直接建立(create)並啓動(start)一個容器,進入到運行狀態(Running)docker stop
命令殺死容器中的程序(die)並中止(stop)容器,最終進入到中止狀態(Stopped)docker rm
命令銷燬容器,進入到被刪除狀態(Deleted)提示
有些眼尖的讀者可能發現
docker kill
和docker stop
的功能很是類似,它們以前存在細微的區別:kill
命令向容器內運行的程序直接發出 SIGKILL 信號(或其餘指定信號),而stop
則是先發出 SIGTERM 再發出 SIGKILL 信號,屬於優雅關閉(Graceful Shutdown)。
生命週期圖其實有一條捷徑沒有畫出來:直接從運行中(或暫停中)到被刪除,經過給 docker rm
命令加上選項 -f
(--force
,強制執行)就能夠實現:
# 假設 dreamland 還在運行中
docker rm -f dreamland
複製代碼
一樣地,咱們能夠刪除全部容器,不管處於什麼狀態:
docker rm -f $(docker ps -aq)
複製代碼
你盡能夠自由探索其餘咱們沒走過的路線,例如嘗試再次啓動以前已經中止的容器(docker start
),或者暫停正在運行的容器(docker pause
)。幸運的是,docker help
命令能夠爲咱們提供探索的指南針,例如咱們想了解 start
命令的使用方法:
$ docker help start
Usage: docker start [OPTIONS] CONTAINER [CONTAINER...]
Start one or more stopped containers
Options:
-a, --attach Attach STDOUT/STDERR and forward signals
--checkpoint string Restore from this checkpoint
--checkpoint-dir string Use a custom checkpoint storage directory
--detach-keys string Override the key sequence for
detaching a container
-i, --interactive Attach container's STDIN 複製代碼
讀到這裏,相信你已經瞭解瞭如何利用現有的鏡像創造容器,並進行管理。在接下來,咱們將帶你建立本身的 Docker 鏡像,開始成爲一名標準的「築夢師」!
在以前的步驟中,咱們體驗了別人爲咱們提早準備好的鏡像(例如 hello-world
、nginx
和 ubuntu
),這些鏡像均可以在 Docker Hub 鏡像倉庫中找到。在這一步,咱們將開始築夢之旅:學習如何容器化(Containerization)你的應用。
正如開頭所說,咱們將容器化一個全棧的」夢想清單「應用,運行如下命令來獲取代碼,而後進入項目:
git clone -b start-point https://github.com/tuture-dev/docker-dream.git
cd docker-dream
複製代碼
在這一步中,咱們將容器化這個用 React 編寫的前端應用,用 Nginx 來提供前端頁面的訪問。
容器化包括三個階段:
構建 Docker 鏡像主要包括兩種方式:
docker commit
命令根據修改後的容器建立新的鏡像docker build
命令直接建立鏡像因爲篇幅有限,這篇文章只會講解使用最爲普遍的第二種建立鏡像的方式。
咱們先把前端項目 client
構建成一個靜態頁面。確保你的機器上已經安裝 Node 和 npm(點擊這裏下載,或使用 nvm
),而後進入到 client
目錄下,安裝全部依賴,並構建項目:
cd client
npm install
npm run build
複製代碼
等待一陣子後,你應該能夠看到 client/build
目錄,存放了咱們要展現的前端靜態頁面。
建立 Nginx 配置文件 client/config/nginx.conf
,代碼以下:
server {
listen 80;
root /www;
index index.html;
sendfile on;
sendfile_max_chunk 1M;
tcp_nopush on;
gzip_static on;
location / {
try_files $uri $uri/ /index.html;
}
}
複製代碼
不熟悉 Nginx 配置的同窗不用擔憂哦,直接複製粘貼就能夠了。上面的配置大體意思是:監聽 80 端口,網頁根目錄在 /www
,首頁文件是 index.html
,若是訪問 /
則提供文件 index.html
。
而後就是這一步驟中最重要的代碼:Dockerfile!建立 client/Dockerfile
文件,代碼以下:
FROM nginx:1.13
# 刪除 Nginx 的默認配置
RUN rm /etc/nginx/conf.d/default.conf
# 添加自定義 Nginx 配置
COPY config/nginx.conf /etc/nginx/conf.d/
# 將前端靜態文件拷貝到容器的 /www 目錄下
COPY build /www 複製代碼
能夠看到咱們用了 Dockerfile 中的三個指令:
FROM
用於指定基礎鏡像,這裏咱們基於 nginx:1.13
鏡像做爲構建的起點RUN
命令用於在容器內運行任何命令(固然前提是命令必須存在)COPY
命令用於從 Dockerfile 所在的目錄拷貝文件到容器指定的路徑是時候來構建咱們的鏡像了,運行如下命令:
# 若是你已經在 client 目錄中
#(注意最後面有個點,表明當前目錄)
docker build -t dream-client .
# 若是你回到了項目根目錄
docker build -t dream-client client
複製代碼
能夠看到咱們指定了 -t
(--tag
,容器標籤)爲 dream-client
,最後指定了構建容器的上下文目錄(也就是 當前目錄 .
或 client
)。
運行以上的命令以後,你會發現:
Sending build context to Docker daemon:66.6MB
複製代碼
並且這個數字還在不斷變大,就像黑客科幻電影中的場景同樣,最後應該停在了 290MB 左右。接着運行了一系列的 Step(4 個),而後提示鏡像構建成功。
爲啥這個構建上下文(Build Context)這麼大?由於咱們把比「黑洞」還「重」的 node_modules 也加進去了!(忍不住想起了下面這張圖)
Docker 提供了相似 .gitignore 的機制,讓咱們能夠在構建鏡像時忽略特定的文件或目錄。建立 client/.dockerignore
文件(注意 dockerignore 前面有一個點):
node_modules
複製代碼
很簡單,咱們只想忽略掉可怕的 node_modules。再次運行構建命令:
docker build -t dream-client .
複製代碼
太好了!此次只有 1.386MB,並且速度也明顯快了不少!
終於到了容器化的最後一步——建立並運行咱們的容器!經過如下命令運行剛纔建立的 dream-client
鏡像:
docker run -p 8080:80 --name client -d dream-client
複製代碼
與以前相似,咱們仍是設定端口映射規則爲 8080:80
,容器名稱爲 client
,而且經過 -d
設置爲後臺運行。而後訪問 localhost:8080
:
成功了!一開始定下的三個夢想也都完成了!
提示
甚至,咱們已經能夠經過內網來訪問「夢想清單」了。Linux 或 macOS 的同窗能夠在終端輸入
ifconfig
命令查詢本機內網 IP,Windows 的同窗則是在 CMD 輸入ipconfig
查詢本機內網 IP,通常是以10
、172.16
~172.31
或192.168
開頭。例如個人內網 IP 是192.168.0.2
,那麼在同一局域網下(通常是 WiFi),能夠用其餘設備(好比說你的手機)訪問192.168.0.2:8080
。
在剛纔的實戰中,你也許已經注意到在拉取和構建鏡像時,Docker 老是會爲咱們加上一個 :latest
標籤,這個 :latest
的含義即是「最新」的意思。和軟件的版本機制同樣,鏡像也能夠經過標籤實現「版本化」。
注意
latest
字面上的意思的確是「最新的」,但也只是一個普通的標籤,並不能確保真的是「最新的」,更不會自動更新。更多討論請參考這篇文章。
實際上,咱們徹底能夠在拉取或構建鏡像時指定標籤(一般被認爲是一種好的作法):
docker pull nginx:1.13
docker build -t dream-client:1.0.0
複製代碼
還能夠給現有的鏡像打上標籤:
# 把默認的 latest 鏡像打上一個 newest 標籤
docker tag dream-client dream-client:newest
# 甚至能夠同時修改鏡像的名稱和標籤
docker tag dream-client:1.0.0 dream-client2:latest
複製代碼
能夠看到,標籤未必必定是版本,還能夠是任何字符串(固然最好要有意義,不然過了一陣子你也不記得這個打了這個標籤的容器有什麼做用了)。
Dockerfile 其實是默認名稱,咱們固然能夠取一個別的名字,例如 myDockerfile
,而後在構建鏡像時指定 -f
(--file
)參數便可:
docker build -f myDockerfile -t dream-client .
複製代碼
這裏舉兩個經典的使用場景:
Dockerfile.dev
用於構建開發鏡像,建立 Dockerfile.prod
構建生產環境下的鏡像;Dockerfile.cpu
用於構建用 CPU 訓練的鏡像,建立 Dockerfile.gpu
構建用 GPU 訓練的鏡像。通過剛纔的容器化實戰,相信你對鏡像和容器的關係又有了新的理解。請看下面這張圖:
在以前的「小試牛刀」環節中(用綠色箭頭標出),咱們:
docker pull
從 Docker 鏡像倉庫拉取鏡像到本地docker run
命令,根據鏡像建立並運行容器docker stop
等命令操做容器,使其發生各類狀態轉變而在這一節的容器化實戰中(用紅色箭頭標出),咱們:
docker build
命令,根據一個 Dockerfile 文件構建鏡像docker tag
命令,給鏡像打上標籤,獲得一個新鏡像docker commit
命令,將一個現有的容器轉化爲鏡像至此,這篇 Docker 快速入門實戰教程也就結束啦,但願你已經對 Docker 的概念和使用有了初步的理解。後續咱們還會發表 Docker 進階的內容(例如 Network 網絡、Volume 數據卷、Docker Compose 等等),手把手帶你們部署一個全棧應用(先後端和數據庫)到雲主機(或任何你可以登陸的機器),敬請期待~
想要學習更多精彩的實戰技術教程?來圖雀社區逛逛吧。