相關文章:
- 寫給前端工程師看的Docker教程-基礎篇
- 寫給前端工程師看的Docker教程-中級篇
- 寫給前端工程師看的Docker教程-實戰篇
在前幾篇的文章裡,我們學習了Docker常用的命令和基本操作,現在可以開始實戰了。
單頁應用
前端工作中最常見的就是單頁應用了。我們首先用create-react-app
快速建立一個應用
npm i create-react-app -g
create-react-app react-app
cd react-app
npm run start
複製程式碼
可以看見正常啟動的頁面。
打包試一下
npm run build
複製程式碼
可以看到本地生成了一個build目錄,這就是最後線上執行的程式碼。
我們先在本地執行下build目錄看看
npm i http-server -g
http-server -p 4444 ./build
複製程式碼
訪問 http://localhost:4444 即可看到打包後的頁面
單頁應用Docker化
在react-app
目錄下新建Dockerfile
.dockerignore
和nginx.conf
.dockerignore
node_modules
build
複製程式碼
dockerignore
指定了哪些檔案不需要被拷貝進映象裡,類似.gitignore
。
我們知道單頁應用的路由一般都被js託管,所以對於nginx需要特別配置
nginx.conf
server {
listen 80;
server_name localhost;
location / {
root /app/build; # 打包的路徑
index index.html index.htm;
try_files $uri $uri/ /index.html; # 防止重重新整理返回404
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
複製程式碼
Dockerfile
# 基於node11
FROM node:11
# 設定環境變數
ENV PROJECT_ENV production
ENV NODE_ENV production
# 安裝nginx
RUN apt-get update && apt-get install -y nginx
# 把 package.json package-lock.json 複製到/app目錄下
# 為了npm install可以快取
COPY package*.json /app/
# 切換到app目錄
WORKDIR /app
# 安裝依賴
RUN npm install --registry=https://registry.npm.taobao.org
# 把所有原始碼拷貝到/app
COPY . /app
# 打包構建
RUN npm run build
# 拷貝配置檔案到nginx
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
# 啟動nginx,關閉守護式執行,否則容器啟動後會立刻關閉
CMD ["nginx", "-g", "daemon off;"]
複製程式碼
需要特別注意的是:
COPY package*.json /app/
RUN npm install
COPY . /app
複製程式碼
我們單獨把package.json
檔案先拷貝到app
,安裝完依賴,然後才把所有的檔案拷貝到app
,這是為什麼?
這是為了充分利用docker快取
COPY . /app
RUN npm install
複製程式碼
如果這麼寫,那麼每一次重新構建映象,都需要下載一次npm包,這是非常浪費時間的!而把package.json
與原始檔分隔開寫入映象,這樣只有當package.json
發生改變了,才會重新下載npm包。
當然快取有時候也會造成一些麻煩,比如在進行一些shell操作輸出內容時,由於快取的存在,導致新構建的映象裡的內容還是舊版本的。
我們可以指定構建映象時不使用快取
docker build --no-cache -t deepred5/react-app .
複製程式碼
最佳實踐是在檔案頂部指定一個環境變數,如果希望不用快取,則更新這個環境變數即可,因為快取失效是從第一條發生變化的指令開始。
打包映象
docker build -t deepred5/react-app .
複製程式碼
啟動容器
docker run -d --name my-react-app -p 8888:80 deepred5/react-app
複製程式碼
訪問 http://localhost:8888 即可看到頁面
訪問 http://localhost:8888/deepred5, 也可以看見頁面,說明nginx防重新整理配置生效了!
多層構建
我們之前寫的Dockerfile
其實是有些問題的: 映象基於node11,但是整個映象用到node環境的地方只是為了前端打包,真正啟動的是Nginx。映象裡的專案原始碼以及node_modules
其實根本沒有用,這些冗餘檔案造成了映象的體積變得非常龐大。
而我們僅僅需要打包出來的靜態檔案以及啟動一個靜態伺服器Nginx即可。
這時就可以使用multi-stage多層構建。
新建一個Dockerfile.multi
# node映象僅僅是用來打包檔案
FROM node:alpine as builder
ENV PROJECT_ENV production
ENV NODE_ENV production
COPY package*.json /app/
WORKDIR /app
RUN npm install --registry=https://registry.npm.taobao.org
COPY . /app
RUN npm run build
# 選擇更小體積的基礎映象
FROM nginx:alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/build /app/build
複製程式碼
這個檔案裡,我們使用了兩個FROM
基礎映象,第一個node:alpine
僅僅作為打包環境,真正的基礎映象是nginx:alpine
打包映象
# -f 指定使用Dockerfile.multi進行構建
docker build -t deepred5/react-app-multi . -f Dockerfile.multi
複製程式碼
啟動容器
docker run -d --name my-react-app-multi -p 8889:80 deepred5/react-app-multi
複製程式碼
訪問 http://localhost:8889 即可看到頁面
檢視映象大小
docker images deepred5/react-app-multi
docker images deepred5/react-app
複製程式碼
可以發現,兩者的大小相差巨大。
deepred5/react-app
映象有1G多,而deepred5/react-app-multi
只有20多M
主要原因是:deepred5/react-app
的基礎映象node:11
就有900M,而deepred5/react-app-multi
的基礎映象nginx:alpine
只有20M。由此可見多層構建對於減少映象大小是非常有幫助的。
Node應用
前端有時也會參與到Node BFF層的開發。我們來建立一個Node結合Redis的簡單專案
mkdir node-redis
cd node-redis
npm init -y
npm i koa koa-router ioredis
touch index.js
複製程式碼
node-redis/index.js
const Koa = require('koa');
const Router = require('koa-router');
const Redis = require("ioredis");
const app = new Koa();
const router = new Router();
const redis = new Redis({
port: 6379,
host: '127.0.0.1'
});
router.get('/', (ctx, next) => {
ctx.body = 'hello world.';
});
router.get('/api/json/get', async (ctx, next) => {
const result = await redis.get('age');
ctx.body = result;
});
router.get('/api/json/set', async (ctx, next) => {
const result = await redis.set('age', ctx.query.age);
ctx.body = {
status: result,
age: ctx.query.age
}
});
app
.use(router.routes())
.use(router.allowedMethods());
app.listen(3000, () => {
console.log('server start at localhost:3000');
})
複製程式碼
我們首先需要本地安裝Redis,然後啟動redis
redis-server
複製程式碼
啟動Node專案
node index.js
複製程式碼
訪問 http://localhost:3000/ 即可看到頁面
訪問 http://localhost:3000/api/json/set?age=2 ,我們就向Redis裡設定age
的值為2
訪問 http://localhost:3000/api/json/get ,我們就取得Redis裡age
的值
Node應用Docker化
首先我們來思考下,這個後端應用涉及Node和Redis。如果我們要部署到Docker裡,應該怎麼構建映象?
- 方案一:基於一個最基礎的
ubuntu
映象,然後我們在其中安裝Node和Redis,這樣Node和Redis之間就可以進行通訊了。這種方案只需要啟動一個容器,因為Node和Redis已經在這個容器裡了。 - 方案二:我們基於
Redis
映象啟動一個容器,專門用來跑Redis。基於Node
映象再啟動一個容器,專門用來跑Node。
Docker的理念更傾向於方案二。我們希望一個映象專注於做一件事,現在流行的微服務,微前端也是這種思想。
在中級篇中我們說過每個容器都是相互隔離的,通過對映埠才能訪問容器裡的網路應用。但是容器和容器之間怎麼進行通訊呢?
Docker裡使用Networking
進行容器間的通訊
Networking
# 建立一個app-test網路
docker network create app-test
複製程式碼
我們只需要把需要通訊的容器都加入到app-test
網路裡,之後容器間就可以互相訪問了。
docker run -d --name redis-app --network app-test -p 6389:6379 redis
docker run -it --name node-app --network app-test node:11 /bin/bash
複製程式碼
我們建立了兩個容器,這兩個容器都在app-test
網路裡。
我們進入node-app
容器裡,然後ping redis-app
,發現可以訪ping通,說明容器間可以通訊了!
我們修改之前的程式碼:
const redis = new Redis({
port: 6379,
host: 'db',
});
複製程式碼
redis的host
改為db
新建一個Dockerfile
FROM node:11
COPY package*.json /app/
WORKDIR /app
RUN npm install
COPY . /app
EXPOSE 3000
CMD ["node","index.js"]
複製程式碼
構建映象
docker build -t deepred5/node-redis-app .
複製程式碼
啟動容器
# 建立網路
docker network create app-test
# 啟動redis容器
docker run -d --name db --network app-test -p 6389:6379 redis
# 啟動node容器
docker run --name node-redis-app -p 4444:3000 --network app-test -d deepred5/node-redis-app
複製程式碼
訪問 http://localhost:4444/ 即可看到頁面
還記得我們之前做的react-app
單頁應用嗎?我們可以也把這個應用加入到app-test
網路裡來,這樣前端單頁應用也可以訪問後端了!
修改react-app
目錄下的nginx.conf
server {
listen 80;
server_name localhost;
location / {
root /app/build; # 打包的路徑
index index.html index.htm;
try_files $uri $uri/ /index.html; # 防止重重新整理返回404
}
location /api {
proxy_pass http://node-redis-app:3000; #後臺轉發地址
}
}
複製程式碼
重新構建映象
docker build -t deepred5/react-app-multi . -f Dockerfile.multi
複製程式碼
啟動容器
docker run -d --name my-react-app-multi --network app-test -p 9999:80 deepred5/react-app-multi
複製程式碼
訪問 http://localhost:9999/api/json/set?age=55 成功返回資料
Docker compose
我們現在這個專案有3個啟動映象:
deepred5/react-app-multi
前端單頁應用redis
資料快取deepred5/node-redis-app
後端服務,訪問redis,同時給前端提供介面
如果要把這個專案完整的啟動起來,按照順序需要這樣啟動:
# 啟動redis容器
docker run -d --name db --network app-test -p 6389:6379 redis
# 啟動node容器
docker run --name node-redis-app -p 4444:3000 --network app-test -d deepred5/node-redis-app
# 啟動前端容器
docker run -d --name my-react-app-multi --network app-test -p 9999:80 deepred5/react-app-multi
複製程式碼
這還僅僅只是3個容器的專案,如果容器再多,啟動就變得非常複雜了!
這時,就需要docker compose
出場了。
首先需要安裝docker compose,安裝完成之後
我們新建一個my-all-app
目錄,然後新建docker-compose.yml
mkdir my-all-app
cd my-all-app
touch docker-compose.yml
複製程式碼
version: '3.7'
services:
db:
image: redis
restart: always
ports:
- 6389:6379
networks:
- app-test
node-redis-app:
image: deepred5/node-redis-app
restart: always
depends_on:
- db
ports:
- 4444:3000
networks:
- app-test
react-app-multi:
image: deepred5/react-app-multi
restart: always
depends_on:
- node-redis-app
ports:
- 9999:80
networks:
- app-test
networks:
app-test:
driver: bridge
複製程式碼
# 啟動所有容器
docker-compose up -d
# 停止所有容器
docker-compose stop
複製程式碼
訪問 http://localhost:9999 檢視前端頁面
訪問 http://localhost:4444 檢視後端介面
可以看見,使用docker-compose.yml
配置完啟動步驟後,啟動多容器就變得十分簡單了。