前端高級進階:使用 docker 高效部署你的前端應用

這是山月關於高級前端進階暨前端工程系列文章的第 M 篇文章 (M 隨便打的,畢竟也不知道能寫多少篇),關於前 M-1 篇文章,能夠從個人 github repo shfshanyue/blog 中找到,若是點進去的話能夠捎帶~點個贊~,若是沒有點進去的話,那就給這篇文章點個贊。javascript

本篇文章地址在 前端工程化系列,歡迎訂閱。html

  1. 前端高級進階:javascript 代碼是如何被壓縮
  2. 前端高級進階:如何更好地優化打包資源
  3. 前端高級進階:網站的緩存控制策略最佳實踐及注意事項
  4. 前端高級進階:在生產環境中使你的 npm i 速度提高 50%
  5. 前端高級進階:使用 docker 高效部署你的前端應用

我在 github 上新建了一個倉庫 每日一題,天天一道面試題,歡迎交流。前端


Docker 變得愈來愈流行,它能夠輕便靈活地隔離環境,進行擴容,運維管理。對於業務開發者而言,隨着持續集成的發展,對代碼質量及快速迭代的要求也愈來愈高。java

對於前端而言,在 CI 環境中使用也更容易集成開發,測試與部署。好比能夠爲流水線(Pipeline)設置 Lint/Test/Security/Audit/Deploy/Artifact 等任務,更好地把控項目質量。node

如今不管是前端,後端仍是運維,都很強調 devops 的理念,接下來我將會寫一系列關於 devops 在前端中應用的文章。你能夠在個人博客 https://github.com/shfshanyue... 中或者個人公衆號 【全棧成長之路】中訂閱更多文章。react

這裏將介紹如何使用 Docker 部署前端應用,千里之行,始於足下。始於足下的意思就是,先讓它可以跑起來。webpack

先讓它跑起來

首先,簡單介紹一下一個典型的前端應用部署流程nginx

  1. npm install, 安裝依賴
  2. npm run build,編譯,打包,生成靜態資源
  3. 服務化靜態資源,如 nginx

介紹完部署流程後,簡單寫一個 Dockerfilegit

FROM node:10-alpine

# 表明生產環境
ENV PROJECT_ENV production

# 許多 package 會根據此環境變量,作出不一樣的行爲
# 另外,在 webpack 中打包也會根據此環境變量作出優化,可是 create-react-app 在打包時會寫死該環境變量
ENV NODE_ENV production

WORKDIR /code
ADD . /code
RUN npm install && npm run build && npm install -g http-server
EXPOSE 80

CMD http-server ./public -p 80

如今這個前端服務已經跑起來了,接下來你能夠完成部署的其它階段了。github

通常狀況下,如下就成了運維的工做了,不過,拓展本身的知識邊界老是沒錯的。其它階段介紹以下

  • 使用 nginx 或者 traefik 作反向代理。在我內部集羣中使用了 traefik,詳見 traefik 簡易入門
  • 使用 kubernetes 或者 docker compose 作容器編排。在我內部集羣中使用了 compose,詳見 docker compose 簡易入門
  • 使用 gitlab cidrone ci 或者 github actions 等作 CI/CD 自動部署。在我內部集羣中使用了 github actions,詳見 github actions 簡易入門

這時鏡像存在兩個問題,致使每次部署時間過長,不利於產品的快速交付,沒有快速交付,也就沒有敏捷開發 (Agile)

  • 構建鏡像時間過長
  • 構建鏡像大小過大,多時甚至 1G+

利用鏡像緩存

咱們注意到,相對於項目的源文件來說,package.json 是相對穩定的。若是沒有新的安裝包須要下載,則再次構建鏡像時,無需從新構建依賴。則能夠在 npm install 上節省一半的時間。

對於 ADD 來說,若是須要添加的文件內容的 checksum 沒有發生變化,則能夠利用緩存。把 package.json/package-lock.json 與源文件分隔開寫入鏡像是一個很好的選擇。目前,若是沒有新的安裝包更新的話,能夠節省一半時間

FROM node:10-alpine

ENV PROJECT_ENV production
ENV NODE_ENV production

# http-server 不變更也能夠利用緩存
RUN npm install -g http-server

WORKDIR /code

# 首次添加此兩個文件,充分利用緩存
ADD package.json package-lock.json /code
RUN npm install --production

ADD . /code
RUN npm run build
EXPOSE 80

CMD http-server ./public -p 80

關於利用緩存有更多細節,須要特別注意一下。如 RUN git clone <repo>,若是命令字符串沒有更新,則將使用緩存,當命令是非冪等性時,這將有可能致使問題。

關於緩存及可能致使的問題,能夠參考個人文章 Dockerfile 最佳實踐

CI 環境下的優化

FROM node:10-alpine

ENV PROJECT_ENV production
ENV NODE_ENV production

# http-server 不變更也能夠利用緩存
RUN npm install -g http-server

WORKDIR /code

# 首次添加此兩個文件,充分利用緩存
ADD package.json package-lock.json /code
RUN npm ci

ADD . /code
RUN npm run build
EXPOSE 80

CMD http-server ./public -p 80

在 CI 環境下主要作了一點改動:使用 npm ci 代替 npm i,經實驗,npm ci 能夠減小將近一半的的依賴安裝時間。

$ npm install
added 1154 packages in 60s

$ npm ci
added 1154 packages in 35s

另外,當 package.jsonpackage-lock.json 版本不匹配時,npm ci 將會報出異常,提早檢測出不安全信息,及早發現問題,及早解決問題。

多階段構建

得益於緩存,如今鏡像構建時間已經快了很多。可是,此時鏡像的體積依舊過於龐大,這也將會致使部署時間的加長。緣由以下

考慮下每次 CI/CD 部署的流程

  1. 在構建服務器 (Runer) 構建鏡像
  2. 把鏡像推至鏡像倉庫服務器
  3. 在生產服務器拉取鏡像,啓動容器

顯而易見,鏡像體積過大會在前兩步上傳及下載時形成傳輸效率低下,增長每次部署的延時。

即便,構建服務器與生產服務器在同一節點下,沒有延時的問題 (基本沒可能)。減小鏡像體積也可以節省磁盤空間。

關於鏡像體積的過大,徹底是由於node_modules 臭名昭著的體積:

node_modules的體積

但最後咱們只須要構建生成的靜態資源,對於源文件以及 node_modules 下文件,佔用體積過大且沒必要要,形成浪費。

此時能夠利用 Docker 的多階段構建,僅來提取編譯後文件,即打包生成的靜態資源,對 Dockerfile 作一改進

FROM node:10-alpine as builder

ENV PROJECT_ENV production
ENV NODE_ENV production

# http-server 不變更也能夠利用緩存
WORKDIR /code

ADD package.json package-lock.json /code
RUN npm ci

ADD . /code
RUN npm run build

# 選擇更小體積的基礎鏡像
FROM nginx:10-alpine
COPY --from=builder /code/public /usr/share/nginx/html

此時,鏡像體積從 1G+ 變成了 50M+。若此時的部署僅僅是在測試環境或者多分支環境下爲了方便測試,那就大功告成,完美解決問題了。

使用對象存儲服務 (OSS)

分析一下 50M+ 的鏡像體積,nginx:10-alpine 的鏡像是16M,剩下的40M是靜態資源。生產環境的靜態資源每每會在獨立域名上維護,並使用 CDN 進行加速。

若是把靜態資源給上傳到文件存儲服務,即OSS,並使用 CDN 對 OSS 進行加速,則沒有必要打入鏡像了。而在生產環境下也有對靜態資源上 CDN 的強烈需求。

此時鏡像大小會控制在 20M 如下。雖然極大地減少了鏡像體積,可是它會增長複雜度與增長鏡像構建時間(如上傳到OSS),對於測試環境或者分支環境不必使用 OSS。

關於靜態資源,能夠分類成兩部分:

  • /build,此類文件在項目中使用 require/import 引用,會被 webpack 打包並加 hash 值,並經過 publicPath 修改資源地址。能夠把此類文件上傳至 oss,並加上永久緩存,不須要打入鏡像
  • /static,此類文件在項目中直接引用根路徑,直接打入鏡像,若是上傳至 OSS 可能增長複雜度 (批量修改 publicPath)

此時經過一個腳本命令 npm run uploadOss,來把靜態資源上傳至 OSS。更新後的 Dockerfile 以下

FROM node:10-alpine as builder

ENV PROJECT_ENV production
ENV NODE_ENV production

# http-server 不變更也能夠利用緩存
WORKDIR /code

ADD package.json package-lock.json /code
RUN npm ci

ADD . /code

# npm run uploadOss 是把靜態資源上傳至 oss 上的腳本文件
RUN npm run build && npm run uploadOss

# 選擇更小體積的基礎鏡像
FROM nginx:10-alpine
COPY --from=builder code/public/index.html code/public/favicon.ico /usr/share/nginx/html/
COPY --from=builder code/public/static /usr/share/nginx/html/static

小結

通過本篇文章總結,在前端中構建鏡像須要注意如下幾點

  1. 鏡像中使用基於 alpine 的鏡像,減少鏡像體積。
  2. 鏡像中須要鎖定 node 的版本號,儘量也鎖定 alpine 的版本號,如 node:10.19-alpine3.11。(我示例代碼中未如此詳細地指出)
  3. 選擇合適的環境變量 NODE_ENVPROJECT_ENV,如在測試環境下進行構建
  4. npm ci 替代 npm i,避免版本問題及提升依賴安裝速度
  5. package.json 單獨添加,充分利用鏡像緩存
  6. 使用多階段構建,減少鏡像體積
  7. 若有必要,靜態資源請上 CDN

與我交流

掃碼添加個人機器人微信,將會自動(自動拉人程序正在研發中)把你拉入前端高級進階學習羣

推薦一個關於大廠招聘的公衆號【互聯網大廠招聘】,做者將在公衆號裏持續推送各個大廠的招聘職位及要求,並與大廠面試官以及招聘負責人直通,感興趣的能夠直接與負責人交流。

另外,做者也將持續推送優質的大廠面試經驗,各類大廠獨家面試題以及優秀文章分享,不限於前端,後端,運維和系統設計。

我在 github 上新建了一個倉庫 每日一題,天天一道面試題,歡迎交流。

更多大廠招聘,面試面經,技能要求,請關注公衆號【互聯網大廠招聘】

相關文章
相關標籤/搜索