半熟前端

軟體工程師 / 台灣人 / 在前端的路上一邊探索其他領域的可能性

雜談

在公司中架設 Server 沒有我想像中的那麼簡單

在公司中架設 Server 沒有我想像中的那麼簡單

動機

在這次的專案開發當中,以往純靜態的 Landing Page 頁面,企劃端有了想要 call API 拿資料動態更新的需求,再加上頁面的互動越來越多,原本 pug+webpack+jQuery 的純靜態頁面不敷使用,我們在新版本的開發中導入了 next.js

先來說說原本 Landing Page 的架構,在跑 CI 時會打包好靜態 HTML 頁面並且上傳 CSS、JavaScript、圖片到 CDN 上,HTML 則是直接放在伺服器上,前面在掛一個 nginx 當作反向代理。

咦?都特地放到 CDN 上了怎麼 HTML 要放在伺服器?

這就要牽扯到歷史因素了,首先公司採用的是自建的 CDN,能夠支援的 custom domain 有限,因此如果將 HTML 也上傳到 CDN,domain name 也會改掉,這對企劃端來說並不件好事。

再來另外一件事則是除了前端工程師製作的 Landing Page 之外,有時候企劃端的活動會用套版 Landing Page 產生器來產生頁面,這個 domain 也是固定的無法更動。因此為了兼顧兩者(套版 Landing Page 與開發端製作的 Landing Page),放一個 nginx 做 proxy 是一個相對權衡的做法。

問題來了, next.js 跟原本的開發架構不相容,重寫全部的 Landing Page 為 React 聽起來過於不切實際,於是我們決定加入一台新的 Server,專門用來開發由 next.js 開發的 Landing Page,再透過 nginx 轉導保證 domain 相同。

問題點

再繼續本文之前,先來說說原本的靜態伺服器會有哪些問題,加入 Server 後又會有哪些改善。

1. SEO

最大的考量在於 SEO,雖然原本的靜態伺服器也能夠達成 SEO,但是在開頭有提到,如果要達到打 API 拿資料,或是像部落格文章這種需求,由於資料都是在 build time 時生成,所以要打 API 的話勢必在前端 JavaScript 當中透過 ajax 或是 fetch 來拿資料,這樣子的資料是沒辦法有效做 SEO 的。另外一個考量點則是 CORS 問題,由於 API server 跟 Landing Page 的 domain 不同,勢必會有 CORS 問題。

2. 效能

雖然說在 Landing Page 使用的資料量並不大,但畢竟能夠實現 SSR,效能上也會好一些。

3. Server 的好處

有 node.js 當作後端伺服器,未來也可以比較有彈性地實現企劃端的需求。next.js 本身也支援多種 build 方式,例如getStaticProps就先打包成靜態檔案、getServerSideProps才會用 SSR。有一個 node.js 當作後端也方便實作像快取或是資料庫存取等需求。

根據以上的原因,我們決定建立新的 server。不過沒想到接下來才是麻煩的開始。

奮鬥歷程

與 SRE 協調

想要建立 Server,在我們的專案當中必須要先跟 SRE 協調並且說明問題。或許是 SRE 特有的穩重性格,他們擔心的事情很多,也需要負責其他專案,所以在溝通上也花了很多時間。不過動機很明確,而且我事先也有把整個架構釐清,所以很快就得到許可了。

我們的雲端是私有雲,在 alpha 環境下每個開發者都可以自己建立機器,因此 alpha 的設定很快就完成了,但是 beta 與正式環境則要經過相對複雜的手續,要申請 ACL,並且透過 SRE 做架設。開機器雖然很簡單,但是安裝各種套件就麻煩了。監控套件、node.js、nginx 等等,這部分幸好有 SRE 的協助才順利安裝。

充滿歷史痕跡的 nginx 檔

Server 架設很簡單,真正困擾我的是那歷史悠久的 nginx 設定檔。裡頭有各種來源的頁面轉導,啟動維護模式時會怎麼處理,在某些特定的路徑下會做特殊的處理。

透過 Ansible Playbook 部署

SRE 幫忙處理了環境安裝的問題,但接下來還有部署。這部分就要自己釐清了,公司大多使用 ansible playbook + awx 做部署(AWX 是一個可以在網頁上透過 API 執行 playbook 的 GUI)。幸好在其他專案中有和同事一起協作,也因此有了一些 playbook 的經驗,不然那一大坨 yaml 真的是看了眼花瞭亂。

Docker

為了方便性,最近的部署做法是開一台機器安裝 docker,然後在裡面做一個 systemctl service 跑 docker image。

原本以為打包起來很容易,但是實作後才發現各種問題。這又要牽扯到我們專案的歷史,當初使用的是 lerna+mono repo 架構,每個子資料夾都有對 root file 的引用,所以很難只對某個 sub folder 做打包。

lerna 主要有兩個特色:

  • 套件安裝會將其他專案中使用到的套件 hoist 到根目錄的node_modules當中
  • 在子專案當中可以用import a from 'sub-project'引用其他子專案的函數(背後原理是在node_modules裏加入一個 symbolic link)

整坨打包下來 Image 的大小來到了 1GB 之多,再次讓我感受到node_modules的可怕之處。然後也因為symbolic link的問題讓我 debug 很久,因為沒有做 symbolink 這個動作的話 npm 就會試圖到網路上尋找sub-project這個套件,想必是找不到的。

Jenkins 整合

公司使用 Jenkins 做整合,所以 Docker Image 也是在 Jenkins 打包上傳到公司內部的 Docker hub。後來發現 Jenkins 在打包時有時候會出現不明錯誤,但是在重跑幾次之後又會恢復正常。

Risk Assessment

因為架了一台新 Server,所以在程序上來說需要執行 Risk Assessment。

Security Check

雖然說每次有大專案也會有 Security Check,新 Server 架設也會需要 Security Team 來檢查資安問題,並且做出必要的修正。

對時程的錯誤評估

next.js 的導入並不是由我們團隊發起的,而是其他辦公室的同事們在其他專案導入,我原本以為 Server 已經架設完成,我們開發 Landing Page 的時候只需要沿用就好。但事實上只有用到 next.js 的 SSG 功能,然後把生成的靜態檔案丟到原本的 Landing Page 伺服器上而已。

當時雖然有點錯愕,但離 QA 測試還有一個多月,而且也只是把 next.js 的伺服器功能套上去而已,應該還是綽綽有餘才對。不過當時已經有部分功能開始 QA 測試,再加上 Landing Page 本身的開發也還沒開始,儘管頁面只花了一個禮拜就完成,然而處理 QA、與其他部門溝通、設計架構、上述的整個流程不知不覺一個月就過去,再加上新的專案也進入了評估期,上線日期迫在眉睫,忙得焦頭爛額之外也沒有人可以幫忙。

這一段期間我的壓力相當沈重,甚至還在假日加班快要撐不下去。

考察

像這種在預料之外的需求,往往需要留更多時間做準備。其實像 SEO 改善這種目的,說真的挺不吸引人的...,大概也拿不到什麼績效。哎,年紀變大了也要思考一下自己的工作方式了。

1. 我成為了瓶頸

在這次的 Server 架設當中有太多東西牽扯到非前端的知識,而且要對公司的內部工具相當熟悉才能做到。很多東西也是我一步步看著文件摸索出來的,跟前端的相關性不高很難讓團員們幫忙。自己也在想有沒有更好的做法,但這種事情就是這樣,一旦你放棄不做了,結果就是提案擱置在那邊沒有人要動手挖屎坑。不過這的確不是個好現象。

2. 忽略了架設 Server 的複雜度

與其說架設 Server 很難,不如說是跨部門的溝通與接下來的準備工作讓整個開發週期無可避免地拉得很長。老實說不走一遍還真的不知道。下次能做的事是盡量將這次的經驗整合成文件,讓之後的路可以走得更輕鬆一些。

3. 部門大多都是日本人為主

除了我們團隊外,其他部門大多都是以日本人為主,因此日文能力不足很容易導致溝通失效,所以有時候我變成溝通之間的橋樑,或是很多事情我都必須要參與其中,這其實也不是一件好事。還在想可以怎麼做比較好。

4. 事前準備不夠

當初沒有意識到沒有 Server 這件事是自己的事前調查不夠充分,這點需要檢討一下。未來如果有類似伺服器架設的需求,也盡量拉多一點時程來預防沒有設想到的情況會比較好。

5. 多個時程卡在一起

除了 Server 架設之外,在當時還有很多時程卡在一起,像是修正既有 QA、開發 Landing Page、Code Review、與 SRE 溝通、討論需求更改的方案,不斷 Context Switch 的情況下也壓縮了專注在 Server 架設的時間。