前端一說起刀耕火種,那肯定緊隨著前端工程化這一話題。隨著 react
/vue
/angular
,es6+
,webpack
,babel
,typescript
以及 node
的發展,前端已經在逐漸替代過去 script
引 cdn
開發的方式了,掀起了工程化這一大浪潮。得益於工程化的發展與開源社群的良好生態,前端應用的可用性與效率得到了很大提高。
前端以前是刀耕火種,那前端應用部署在以前也是刀耕火種。那前端應用部署的發展得益於什麼,隨前端工程化帶來的副產品?
這只是一部分,而更重要的原因是 devops
的崛起。
為了更清晰地理解前端部署的發展史,瞭解部署時運維和前端(或者更廣泛地說,業務開發人員)的職責劃分,當每次前端部署發生改變時,可以思考兩個問題
- 快取,前端應用中http 的
response header
由誰來配?得益於工程化發展,可以對打包後得到帶有 hash 值的檔案可以做永久快取 - 跨域,
/api
的代理配置由誰來配?在開發環境前端可以開個小服務,啟用webpack-dev-server
配置跨域,那生產環境呢
這兩個問題都是前端面試時的高頻問題,但話語權是否掌握在前端手裡
時間來到 React
剛剛發展起來的這一年,這時已經使用 React
開發應用,使用 webpack
來打包。但是前端部署,仍是刀耕火種
如果本篇文章能夠對你有所幫助,可以幫我在 shfshanyue/op-note 上點個 star
刀耕火種
一臺跳板機
一臺生產環境伺服器
一份部署指令碼
前端調著他的 webpack
,開心地給運維發了部署郵件並附了一份部署指令碼,想著第一次不用套後端的模板,第一次前端可以獨立部署。想著自己基礎盤進一步擴大,前端不禁開心地笑了
運維照著著前端發過來的部署郵件,一遍又一遍地拉著程式碼,改著配置,寫著 try_files
, 配著 proxy_pass
。
這時候,前端靜態檔案由 nginx
託管,nginx
配置檔案大致長這個樣子
server {
listen 80;
server_name shanyue.tech;
location / {
# 避免非root路徑404
try_files $uri $uri/ /index.html;
}
# 解決跨域
location /api {
proxy_pass http://api.shanyue.tech;
}
# 為帶 hash 值的檔案配置永久快取
location ~* \.(?:css|js)$ {
try_files $uri =404;
expires 1y;
add_header Cache-Control "public";
}
location ~ ^.+\..+$ {
try_files $uri =404;
}
}
複製程式碼
不過...經常有時候跑不起來
運維抱怨著前端的部署指令碼沒有標好 node
版本,前端嚷嚷著測試環境沒問題
這個時候運維需要費很多心力放在部署上,甚至測試環境的部署上,前端也要費很多心力放在運維如何部署上。這個時候由於怕影響線上環境,上線往往選擇在深夜,前端和運維身心俱疲
不過向來如此
魯迅說,向來如此,那便對麼。
這個時候,無論跨域的配置還是快取的配置,都是運維來管理,運維不懂前端。但配置方式卻是前端在提供,而前端並不熟悉 nginx
使用 docker 構建映象
docker
的引進,很大程度地解決了部署指令碼跑不了這個大BUG。dockerfile
即部署指令碼,部署指令碼即 dockerfile
。這也很大程度緩解了前端與運維的摩擦,畢竟前端越來越靠譜了,至少部署指令碼沒有問題了 (笑
這時候,前端不再提供靜態資源,而是提供服務,一個 http
服務
前端寫的 dockerfile
大致長這個樣子
FROM node: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
複製程式碼
單單有 dockerfile
也跑不起來,另外前端也開始維護一個 docker-compose.yaml
,交給運維執行命令 docker-compose up -d
啟動前端應用。前端第一次寫 dockerfile
與 docker-compose.yaml
,在部署流程中扮演的角色越來越重要。想著自己基礎盤進一步擴大,前端又不禁開心地笑了
version: "3"
services:
shici:
build: .
expose:
- 80
複製程式碼
運維的 nginx
配置檔案大致長這個樣子
server {
listen 80;
server_name shanyue.tech;
location / {
proxy_pass http://static.shanyue.tech;
}
location /api {
proxy_pass http://api.shanyue.tech;
}
}
複製程式碼
運維除了配置 nginx
之外,還要執行一個命令: docker-compose up -d
這時候再思考文章最前面兩個問題
- 快取,由於從靜態檔案轉換為服務,快取開始交由前端控制 (但是映象中的
http-server
不太適合做這件事情) - 跨域,跨域仍由運維在
nginx
中配置
前端可以做他應該做的事情中的一部分了,這是一件令人開心的事情
當然,前端對於 dockerfile
的改進也是一個慢慢演進的過程,那這個時候映象有什麼問題呢?
- 構建映象體積過大
- 構建映象時間過長
使用多階段構建優化映象
這中間其實經歷了不少坎坷,其中過程如何,詳見我的另一篇文章: 如何使用 docker 部署前端應用。
其中主要的優化也是在上述所提到的兩個方面
- 構建映象體積由 1G+ 變為 10M+
- 構建映象時間由 5min+ 變為 1min (視專案複雜程度,大部分時間在構建時間與上傳靜態資源時間)
FROM node:alpine as builder
ENV PROJECT_ENV production
ENV NODE_ENV production
WORKDIR /code
ADD package.json /code
RUN npm install --production
ADD . /code
# npm run uploadCdn 是把靜態資源上傳至 oss 上的指令碼檔案,將來會使用 cdn 對 oss 加速
RUN npm run build && npm run uploadCdn
# 選擇更小體積的基礎映象
FROM nginx: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
複製程式碼
那它怎麼做的
- 先
ADD package.json /code
, 再npm install --production
之後Add
所有檔案。充分利用映象快取,減少構建時間 - 多階段構建,大大減小映象體積
另外還可以有一些小優化,如
npm cache
的基礎映象或者npm
私有倉庫,減少npm install
時間,減小構建時間npm install --production
只裝必要的包
前端看著自己優化的 dockerfile
,想著前幾天還被運維吵,說什麼磁碟一半的空間都被前端的映象給佔了,想著自己節省了前端映象幾個數量級的體積,為公司好像省了不少伺服器的開銷,想著自己的基礎盤進一步擴大,又不禁開心的笑了
這時候再思考文章最前面兩個問題
- 快取,快取由前端控制,快取在oss上設定,將會使用 cdn 對 oss 加速。此時快取由前端寫指令碼控制
- 跨域,跨域仍由運維在
nginx
中配置
CI/CD 與 gitlab
此時前端成就感爆棚,運維呢?運維還在一遍一遍地上線,重複著一遍又一遍的三個動作用來部署
- 拉程式碼
docker-compose up -d
- 重啟 nginx
運維覺得再也不能這麼下去了,於是他引進了 CI
: 與現有程式碼倉庫 gitlab
配套的 gitlab ci
CI
,Continuous Integration
,持續整合CD
,Continuous Delivery
,持續交付
重要的不是 CI/CD
是什麼,重要的是現在運維不用跟著業務上線走了,不需要一直盯著前端部署了。這些都是 CI/CD
的事情了,它被用來做自動化部署。上述提到的三件事交給了 CI/CD
.gitlab-ci.yml
是 gitlab
的 CI 配置檔案,它大概長這個樣子
deploy:
stage: deploy
only:
- master
script:
- docker-compose up --build -d
tags:
- shell
複製程式碼
CI/CD
不僅僅更解放了業務專案的部署,也在交付之前大大加強了業務程式碼的質量,它可以用來 lint
,test
,package
安全檢查,甚至多特性多環境部署,我將會在我以後的文章寫這部分事情
我的一個伺服器渲染專案 shfshanyue/shici 以前在我的伺服器中就是以 docker
/docker-compose/gitlab-ci
的方式部署,有興趣的可以看看它的配置檔案
如果你有個人伺服器的話,也建議你做一個自己感興趣的前端應用和配套的後端介面服務,並且配套 CI/CD
把它部署在自己的自己伺服器上
如果沒有的話,新人可以點選 我的連結 購買
而你如果希望結合 github
做 CI/CD
,那可以試一試 github
+ github action
使用 kubernetes 部署
隨著業務越來越大,映象越來越多,docker-compose
已經不太能應付,kubernetes
應時而出。這時伺服器也從1臺變成了多臺,多臺伺服器就會有分散式問題
一門新技術的出現,在解決以前問題的同時也會引進複雜性。
k8s 部署的好處很明顯: 健康檢查,滾動升級,彈性擴容,快速回滾,資源限制,完善的監控等等
那現在遇到的新問題是什麼?
構建映象的伺服器,提供容器服務的伺服器,做持續整合的伺服器是一臺!
需要一個私有的映象倉庫,這是運維的事情,harbor
很快就被運維搭建好了,但是對於前端部署來說,複雜性又提高了
先來看看以前的流程:
- 前端配置
dockerfile
與docker-compose
- 生產環境伺服器的
CI runner
拉程式碼(可以看做以前的運維),docker-compose up -d
啟動服務。然後再重啟nginx
,做反向代理,對外提供服務
以前的流程有一個問題: 構建映象的伺服器,提供容器服務的伺服器,做持續整合的伺服器是一臺!,所以需要一個私有的映象倉庫,一個能夠訪問 k8s
叢集的持續整合伺服器
流程改進之後結合 k8s
的流程如下
- 前端配置
dockerfile
,構建映象,推到映象倉庫 - 運維為前端應用配置
k8s
的資源配置檔案,kubectl apply -f
時會重新拉取映象,部署資源
運維問前端,需不需要再擴大下你的基礎盤,寫一寫前端的 k8s
資源配置檔案,並且列了幾篇文章
前端看了看後端十幾個 k8s 配置檔案之後,搖搖頭說算了算了
這個時候,gitlab-ci.yaml
差不多長這個樣子,配置檔案的許可權由運維一人管理
deploy:
stage: deploy
only:
- master
script:
- docker build -t harbor.shanyue.tech/fe/shanyue
- docker push harbor.shanyue.tech/fe/shanyue
- kubectl apply -f https://k8s-config.default.svc.cluster.local/shanyue.yaml
tags:
- shell
複製程式碼
這時候再思考文章最前面兩個問題
- 快取,快取由前端控制
- 跨域,跨域仍由運維控制,在後端
k8s
資源的配置檔案中控制Ingress
使用 helm 部署
這時前端與運維已不太往來,除了偶爾新起專案需要運維幫個忙以外
但好景不長,突然有一天,前端發現自己連個環境變數都沒法傳!於是經常找運維修改配置檔案,運維也不勝其煩
於是有了 helm
,如果用一句話解釋它,那它就是一個帶有模板功能的 k8s
資源配置檔案。作為前端,你只需要填引數。更多詳細的內容可以參考我以前的文章 使用 helm 部署 k8s 資源
假如我們使用 bitnami/nginx 作為 helm chart
,前端可能寫的配置檔案長這個樣子
image:
registry: harbor.shanyue.tech
repository: fe/shanyue
tag: 8a9ac0
ingress:
enabled: true
hosts:
- name: shanyue.tech
path: /
tls:
- hosts:
- shanyue.tech
secretName: shanyue-tls
# livenessProbe:
# httpGet:
# path: /
# port: http
# initialDelaySeconds: 30
# timeoutSeconds: 5
# failureThreshold: 6
#
# readinessProbe:
# httpGet:
# path: /
# port: http
# initialDelaySeconds: 5
# timeoutSeconds: 3
# periodSeconds: 5
複製程式碼
這時候再思考文章最前面兩個問題
- 快取,快取由前端控制
- 跨域,跨域由後端控制,配置在後端 Chart 的配置檔案
values.yaml
中
到了這時前端和運維的職責所在呢?
前端需要做的事情有:
- 寫前端構建的
dockerfile
,這只是一次性的工作,而且有了參考 - 使用
helm
部署時指定引數
那運維要做的事情呢
- 提供一個供所有前端專案使用的
helm chart
,甚至不用提供,如果運維比較懶那就就使用 bitnami/nginx 吧。也是一次性工作 - 提供一個基於
helm
的工具,禁止業務過多的許可權,甚至不用提供,如果運維比較懶那就直接使用helm
這時前端可以關注於自己的業務,運維可以關注於自己的雲原生,職責劃分從未這般清楚
統一前端部署平臺
後來運維覺得前端應用的本質是一堆靜態檔案,較為單一,容易統一化,來避免各個前端映象質量的參差不齊。於是運維準備了一個統一的 node
基礎映象,做了一個前端統一部署平臺,而這個平臺可以做什麼呢
CI/CD
: 當你 push 程式碼到倉庫的特定分支會自動部署http headers
: 你可以定製資源的http header
,從而可以做快取優化等http redirect/rewrite
: 如果一個nginx
,這樣可以配置/api
,解決跨域問題hostname
: 你可以設定域名CDN
: 把你的靜態資源推到 CDNhttps
: 為你準備證書Prerender
: 結合SPA
,做預渲染
前端再也不需要構建映象,上傳 CDN 了,他只需要寫一份配置檔案就可以了,大致長這個樣子
build:
command: npm run build
dist: /dist
hosts:
- name: shanyue.tech
path: /
headers:
- location: /*
values:
- cache-control: max-age=7200
- location: assets/*
values:
- cache-control: max-age=31536000
redirects:
- from : /api
to: https://api.shanyue.tech
status: 200
複製程式碼
此時,前端只需要寫一份配置檔案,就可以配置快取,配置 proxy
,做應該屬於前端做的一切,而運維也再也不需要操心前端部署的事情了
前端看著自己剛剛寫好的配置檔案,悵然若失的樣子...
不過一般只有大廠會有這麼完善的前端部署平臺,如果你對它有興趣,你可以嘗試下 netlify
,可以參考我的文章: 使用 netlify 部署你的前端應用
服務端渲染與後端部署
大部分前端應用本質上是靜態資源,剩下的少部分就是服務端渲染了,服務端渲染的本質上是一個後端服務,它的部署可以視為後端部署
後端部署的情況更為複雜,比如
- 配置服務,後端需要訪問敏感資料,但又不能把敏感資料放在程式碼倉庫。你可以在
environment variables
,consul
或者k8s configmap
中維護 - 上下鏈路服務,你需要依賴資料庫,上游服務
- 訪問控制,限制 IP,黑白名單
- RateLimit
- 等等
我將在以後的文章分享如何在 k8s 中部署一個後端
小結
隨著 devops
的發展,前端部署越來越簡單,可控性也越來越高,建議所有人都稍微學習一下 devops
的東西。
道阻且長,行則將至。
相關文章
- 個人伺服器運維指南
- 如果你想搭建一個部落格
- 當我有一臺伺服器時我做了什麼
- 使用 k8s 部署你的第一個應用: Pod,Deployment 與 Service
- 使用 k8s 為你的應用配置域名: Ingress
- 使用 k8s 為你的域名加上 https
我是山月,一個喜歡跑步與爬山的程式設計師,我會定期分享全棧文章在個人公眾號中,歡迎交流