必須要解決的問題

在之前的一篇文章中,我們使用Gatsby打造出了一個寫blog的環境,但是使用沒多久就會遇到一個問題,那就是,部落格的更版實在是非常地麻煩,以我是把部落格自己放在自己在DigitalOceanVPS上來說,每次不管是有新文章,或是我有修改內容時,我都必須要

  1. 先在自己的電腦上執行npm run build指令(其實就是執行gatsby build)

  2. 把雲端server上放部落格的位置原本的所有靜態檔案全部刪掉

  3. 把自己的電腦上build完的檔案用rsyncscp之類的指令,將檔案送去雲端server上

    雖然1~3其實是有辦法寫成一個指令,然後一個enter完成,不過我後來發現這是一個很好的練習部署CI/CD pipeline的練習情境。

在git push之後自動在remote repository上固定做某些事

這邊並不想要介紹CI/CD的定義,也不想去爭辯我們做的事能不能稱得上是CI/CD這樣 高~大~上的名詞。
我們想要做到的事情很簡單,就是我只想要負責寫文章,部署我最後產生好的靜態檔案的事情我只想要在第一次時做一遍就好,以後都應該要能讓電腦自動幫我做,連gatsby build這個類似編譯的指令,我都不想在本地開發電腦做,我希望能夠有一台雲端上的機器負責做這件事情,並在編譯都正確無誤之後,能夠自動幫我把產出的東西,推送到正式環境,就是硬要耍懶DRY原則在deploy時的實現

在寫部落格時在本地我已經有gatsby develop這樣的指令可以邊寫邊確定產生出來的網頁的樣子了,其實可以不用把gatsby build這件事在我自己電腦上做。

該如何做到??

  1. 首先我們把我們的部落格在免費的git server服務上創一個repo然後code git push到它上面,像我一開始是使用BitBucket,因為它在之前是唯一能夠免費擁有private repo的服務。

  2. 接著在本地blog的repo裡創立一個檔案叫bitbucket-pipelines.yml,這個就是bitbucket pipeline機制的設定檔,bitbucket在我們把新的code git push上去之後,如果repo底下有bitbucket-pipelines.yml這個檔案,bitbucket server上的CI/CD runner就會開始解析這個yml檔裡的內容,並按照裡面來做事

那麼底下會是可以作到我們上述想做到的事的內容

# 聲明image的話 Bitcucket上的runner就會知道是要走docker container模式,這邊我們要用alpine這個linux os,然後上面已經安裝好最新版的node.js
image: node:current-alpine

pipelines:
# 只有在push到master 或 PR進master時才跑pipeline
  branches:
    master: 
      - step:
         caches:
           - node
         script:
# alpine上的OS library管理工具叫`apk` 這句script 等於Mac OS上的 brew update, 然後順便裝一下openssh
           - apk add --update --no-cache openssh
# alpinen雖然本身很輕量(只有5mb),但是它也缺少了很多一般OS上會有的程式,這裡是在安裝 tar, python, g++, make...這些東西,因為它們是我們之後gatsby build時的那些plugin可能會用到的程式
           - apk update && apk add tar python g++ make bash zlib-dev libpng-dev&& rm -rf /var/cache/apk/*
# access日本那邊的一個alpine軟體庫 以取得 libvips這個程式(一個node.js的影像處理library,它被我們的gatsby-transformer-sharp這個plugin所需要用到,因為我們在gatsby build的過程裡會對圖檔進行優化)
           - apk add --update --no-cache --repository http://ftp.tsukuba.wide.ad.jp/Linux/alpine/v3.10/main/ vips-dev
# 如果真正放blog source的資料夾跟 `bitbucket-pipelines.yml`不是同一層的話 不要忘了要再cd進去,在現在的template底下是在同一層,所以無須執行
          # - cd ./myblog
 # 給予權限,這是用來排除 `sharp EACCES: permission denied, mkdir '/root/.npm'`這個問題
           - npm config set user 0
           - npm config set unsafe-perm true
# 終於開始看到熟悉的指令,安裝寫在package.json裡的那些套件
           - npm install
# 開始build,然後不產生sourceMap,以減少網路傳輸量和部署的速度
           - npm run buildWithouSourceMap
# 用tar 把 gatsby產生出來在public(這個template gatsby build完之後放靜態檔案的資料夾)裡的檔案做壓縮,壓縮檔取名為 `release.tar.gz`
           - tar zcvf release.tar.gz public
# ssh(所以剛剛上面才要安裝openssh)到要部署的遠端server,把檔案推過去之前先刪掉之前的所有檔案,           
           - ssh your_account@your_server_ip 'rm -rf /path/to/your/blogFiles/* && exit'
# 再把現在這個container底下剛剛做好的壓縮檔scp到server上放blog檔案的路徑底下           
           - scp release.tar.gz your_account@your_server_ip:/path/to/your/blogFiles
# 再登入一次 把剛剛scp過的檔案解壓縮,從public資料夾那一層移動所有的檔案到nginx上設定好放blog檔案的那一層,最後把壓縮檔跟已經空的了public資料夾刪掉,再登出           
           - ssh your_account@your_server_ip 'cd /path/to/your/blogFiles && tar zxvf release.tar.gz && mv ./public/* ./ && rm release.tar.gz && rm -r ./public && exit'

# 壓縮後再傳輸可以大大減少scp的傳輸時間,不然檔案太多,會傳很久

因為我們要在遠端機器上要用Gatsby去用我們的repo裡的codemarkdown, assets build出我們的blog的靜態檔, Gatsby是靠node.js去驅動,要跑node.js要有作業系統,在這裡我們選用alpine
這一個微量的Linux作業系統,用了一個已經在alipne上安裝好了node.jsdocker image檔

BitBucket的CI/CD Runner因為看到我們的設定裡要使用image檔,
就會自動以docker模式來接著執行我們寫在yml檔裡的pipeline,
儘管alpine只有5mb,雖然縮短了runnerdockerHub下載docker-image的時間,但是就是因為太小了,所以作業系統裡缺少了一些我們後面要執行gatsby build時所需要用到的api,
就變成我們自己必須要知道為達到我們的目的,我們還另外必須要安裝什麼。

等到所有的準備工作都做好之後,就可以在script裡再執行我們平常在本地開發電腦上執行的指令。最後把build出來的靜態檔案,用ssh的shell送到正式環境的server上。

用這樣的方式,只要repo裡有bitbucket-pipelines.yml這個檔案,每次git push到遠端的repository上時,這個pipeline就會發動一次,作到自動部署。

要注意的是:

Github的travis CI, Gitlab上的CI/CD也是大同小異,只是yml檔的檔名不同 和 yml檔裡的設定的詞有些差異,按照其文件設定,應該就能迎刃而解。另外注意我們是使用它們的服務,所以不必自己處理runner(用來跑pipeline的電腦或process)的事情。如果是自己架起來的git server,如自己架的Gitlab, Gitea(人們稱的 self-hosted Git service )那樣的狀況,就必須要自己處理runner的機器的設定。

我的blog的部分pipeline紀錄 myblog_pipelineHistory

reference