寫給前端工程師的Docker入門
為什么我們要用 docker ?
過(guò)去的我們,當(dāng)業(yè)務(wù)發(fā)展需要部署新的應(yīng)用時(shí),DevOps 小伙伴通常會(huì)去買一臺(tái)服務(wù)器,但是卻不知道這個(gè)新應(yīng)用具體需要多高的配置,往往都會(huì)造成資源浪費(fèi)。
當(dāng)虛擬機(jī)出現(xiàn)后,它可以讓我們?cè)谝慌_(tái)服務(wù)器上運(yùn)行多個(gè)應(yīng)用,但是卻有一個(gè)缺陷。每個(gè) VM 需要運(yùn)行一整個(gè)的操作系統(tǒng)。每個(gè) OS 又需要 CPU、RAM 等等,需要打補(bǔ)丁、安裝證書,這些反過(guò)來(lái)又增加了成本和彈性。
Google 在很久之前就開始使用容器模型來(lái)解決 VM 模式的弊端。簡(jiǎn)單來(lái)說(shuō)容器模型允許我們?cè)谕慌_(tái)主機(jī)上運(yùn)行多個(gè)容器,而且共用主機(jī)的 CPU、RAM 等資源。
那么它對(duì)開發(fā)者來(lái)說(shuō)意味著是么呢?
它可以保證對(duì)所有的開發(fā)者和服務(wù)器來(lái)說(shuō),我們的工作環(huán)境都是一致的。比如: 生產(chǎn)環(huán)境、仿真環(huán)境、測(cè)試環(huán)境。
任何人都可以分分鐘配置好項(xiàng)目,無(wú)需亂搞配置、安裝庫(kù)和設(shè)置依賴。
簡(jiǎn)單來(lái)說(shuō),docker 是一個(gè)平臺(tái),它允許我們使用容器來(lái)開發(fā)、部署、運(yùn)行應(yīng)用程序。
讓我們退一步來(lái)看,容器系統(tǒng)在物理上是什么樣子的,以及與 VM 有什么區(qū)別。
可以看出來(lái),宿主機(jī)的資源在容器化的使用后是共享的,但是在 VM 中卻被分割開了。
接下來(lái),我們來(lái)深入一些。
如何使用 docker ?
為此我們需要先熟悉一些術(shù)語(yǔ)。
Docker image: 它是一個(gè)可執(zhí)行文件,包含了運(yùn)行一個(gè)應(yīng)用程序的操作系統(tǒng)配置和所有的庫(kù)。它有多個(gè)層疊在一起,并表示為單個(gè)對(duì)象。docker image 是通過(guò) docker file 來(lái)創(chuàng)建的,我們稍后再講。
Docker Container: 它是 docker image 的一個(gè)運(yùn)行實(shí)例。同一個(gè) docker image 可以有多個(gè)運(yùn)行的 container。
容器化 Node.js 應(yīng)用
我們來(lái)嘗試容器化一個(gè)簡(jiǎn)單的 node.js 應(yīng)用,然后創(chuàng)建一個(gè) image:
你的 Node.js 應(yīng)用
先創(chuàng)建一個(gè) my-node-app 文件夾,
- mkdir my-node-app
- cd my-node-app
然后創(chuàng)建一個(gè) index.js 來(lái)啟動(dòng)一個(gè) node server:
- // 我們用 require 引入 express
- var express = require('express')
- var app = express()
- // 對(duì)根 URL 做一個(gè)響應(yīng)
- app.get('/', function (req, res) {
- res.send('Hello World!')
- })
- // 讓服務(wù)器監(jiān)聽 8081 端口
- app.listen(8081, function () {
- console.log('app listening on port 8081!')
- })
然后我們創(chuàng)建一個(gè) package.json 文件,可以通過(guò) npm init -y 來(lái)快速生成:
- {
- "name": "helloworld",
- "version": "1.0.0",
- "description": "Dockerized node.js app",
- "main": "index.js",
- "author": "",
- "license": "ISC",
- "dependencies": {
- "express": "^4.16.4"
- }
- }
到這一步我們甚至不需要 express 或者 npm 安裝在自己的機(jī)器,因?yàn)?dockerfile 可以為我們配置和安裝這些依賴。
DockerFile
讓我們創(chuàng)建一個(gè) dockerfile,然后保存到 my-node-app 文件夾。這個(gè)文件沒有擴(kuò)展名,它的名字就叫作 Dockerfile,這是里面的內(nèi)容:
- # Dockerfile
- FROM node:8
- WORKDIR /app
- COPY package.json /app
- RUN npm install
- COPY . /app
- EXPOSE 8081
- CMD node index.js
下面解釋一下里面的命令:
FROM node:8 -- 從 docker hub 拉取 node.js docker 鏡像,可以在這里找到 node 的鏡像:https://hub.docker.com/_/node/
WORKDIR /app -- 設(shè)置鏡像中的工作目錄,可以與下面的命令一起使用: COPY,RUN 和 CMD
COPY package.json /app -- 將 package.json 從宿主機(jī)的 my-node-app 目錄復(fù)制到了鏡像中的 /app 目錄
RUN npm install -- 在鏡像中運(yùn)行此命令來(lái)安裝 node 包
COPY . /app -- 復(fù)制 my-node-app 目錄中的所有文件到鏡像中的 /app 目錄
EXPOSE 8081 -- 這條命令告訴 container 要暴露一個(gè)端口號(hào),這個(gè)端口號(hào)正是我們?cè)?index.js 中寫的那個(gè)。默認(rèn)情況下,容器會(huì)忽略對(duì)它所有的請(qǐng)求。
構(gòu)建 Docker 鏡像
注意看啦~ 打開控制臺(tái),到 my-node-app 目錄下,執(zhí)行以下命令:
- # Build a image docker build -t <image-name> <relative-path-to-your-dockerfile>
- docker build -t hello-world .
這條命令在我們宿主機(jī)創(chuàng)建了一個(gè) hello-world 鏡像
-t 用來(lái)為我們的鏡像指定一個(gè)名字,這里就是 hello-world
. 是用來(lái)指明 docker file 的路徑,由于我們已經(jīng)在 my-node-app 中,所以路徑用 . 就可以了
你可以在控制臺(tái)看到類似于以下的輸出:
- Sending build context to Docker daemon 4.096kB
- Step 1/7 : FROM node:8
- ---> 4f01e5319662
- Step 2/7 : WORKDIR /app
- ---> Using cache
- ---> 5c173b2c7b76
- Step 3/7 : COPY package.json /app
- ---> Using cache
- ---> ceb27a57f18e
- Step 4/7 : RUN npm install
- ---> Using cache
- ---> c1baaf16812a
- Step 5/7 : COPY . /app
- ---> 4a770927e8e8
- Step 6/7 : EXPOSE 8081
- ---> Running in 2b3f11daff5e
- Removing intermediate container 2b3f11daff5e
- ---> 81a7ce14340a
- Step 7/7 : CMD node index.js
- ---> Running in 3791dd7f5149
- Removing intermediate container 3791dd7f5149
- ---> c80301fa07b2
- Successfully built c80301fa07b2
- Successfully tagged hello-world:latest
可以看到,它根據(jù) docker file 中的命令依次運(yùn)行,然后輸出了一個(gè) docker 鏡像。當(dāng)你第一次運(yùn)行的時(shí)候可能會(huì)需要一些時(shí)間,下次就可以使用緩存來(lái)加快速度了。現(xiàn)在我們來(lái)看下剛才 build 的鏡像:
- # Get a list of images on your host
- docker images
這個(gè)命令會(huì)顯示在你電腦上存在的 docker 鏡像。其中會(huì)有一條:
- REPOSITORY TAG IMAGE ID CREATED SIZE
- hello-world latest c80301fa07b2 22 minutes ago 896MB
運(yùn)行 Docker 容器
既然我們已經(jīng)創(chuàng)建了鏡像,下面我們就從這個(gè)鏡像運(yùn)行一個(gè) docker 容器:
- # Default command for this is docker container run <image-name>
- docker container run -p 4000:8081 hello-world
這條命令用來(lái)創(chuàng)建和運(yùn)行一個(gè) docker 容器
-p 4000:8081 -- 是一個(gè)發(fā)布(publish)標(biāo)識(shí),它將本機(jī)的 4000 端口映射到了容器中的 8081 端口。現(xiàn)在所有對(duì)本機(jī) 4000 端口的訪問(wèn),都會(huì)被容器中的 8081 端口監(jiān)聽。
hello-world -- 這個(gè)名字就是剛才用 docker build 命令時(shí)指定的鏡像名稱。
你將會(huì)得到以下輸出:
- app listening on port 8081!
如果你需要進(jìn)入容器并且掛載一個(gè) bash 終端,可以運(yùn)行:
- # Enter the container
- docker exec -ti <container id> /bin/bash
為了檢查我們的容器是否運(yùn)行,打開另一個(gè)命令行,然后輸入:
- docker ps
可以看到以下輸出:
- CONTAINER ID IMAGE COMMAND CREATED
- `<container id>` hello-world "/bin/sh -c 'node in…" 11 seconds ago
- STATUS PORTS NAMES
- Up 11 seconds 0.0.0.0:4000->8081/tcp some-random-name
這里可以看我們從 hello-world 鏡像創(chuàng)建的容器,以及它的 <container id> ,它正在運(yùn)行,并且監(jiān)聽了 8081 端口號(hào)。
現(xiàn)在我們這個(gè)簡(jiǎn)單的 Node.js 應(yīng)用就已經(jīng)完全容器化了。你可以在瀏覽器訪問(wèn) http://localhost:4000 ,應(yīng)該可以看到以下畫面:



























