Skip to content

Repository files navigation

Meme M109

A CICD pipeline to deploy this artillery to GKE

我們希望每次 push code 的時候 CICD 幫我們做的事

  1. 把專案包成 docker image 並上傳到 dockerhub
  2. 生成 k8s deployment yaml,並在裡面選用 (1) 包好的 image
  3. 用 kubectl apply (2) 生成的 deployment 文件,完成 deploy

專案準備 ( 可以用滑鼠點的部分 )

  • 開好 Gitlab repo
  • 開好 GCP project ( 並記下 project_id )
  • Enable GKE API ( 點進 project 的 Kubernetes Engine 標籤就會自動 enable 了 )

事前作業

  1. 安裝 gcloud CLI 在自己電腦上 Google Cloud SDK 安裝方法

    install 完之後如果gcloud指令不能用要 reload shell 設定 ( .bashrc, .zshrc, etc. )

     source ~/.bashrc //也不一定是 .bashrc,總之就是 terminal rc file
    
  2. 這個指令可以讓你用互動式的方式,初始化你的 gcloud sdk

    gcloud init
    

:::info 等同於以下三個指令

gcloud auth login
gcloud config set project [project-id]
gcloud config set compute/zone [compute-zone]

:::

  1. 開一個cluster
    gcloud container clusters create [cluster-名稱] --num-nodes=[node 數量] ```
    
  2. 讓 gcloud 自動幫你做 kubectl 設定,連接到 cluster
    gcloud container clusters get-credentials [cluster-名稱]
    
  3. 在 GCP 開一個服務帳戶 (sa-name: 6~30個字,小寫)
    gcloud iam service-accounts create [sa-name] --description="[sa-description]" --display-name="[sa-display-name]"
    
  4. 給他 deploy 到 k8s 的權限 (這個指令會讓剛才設好的服務帳戶成為 kubernetes developer)
    gcloud projects add-iam-policy-binding [project-id] --member="serviceAccount:[sa-name]@[project-id].iam.gserviceaccount.com" --role='roles/container.developer'
    
  5. 生成一組剛才的 service account 的 key,存成 service-account.json
    gcloud iam service-accounts keys create --iam-account "[sa-name]@[project-id].iam.gserviceaccount.com" service-account.json
    
  6. CICD 環境變數設定

環境變數設定

進入 gitlab repo > settings > CICD > Variables

type key value
Variable DOCKER_HUB_USERNAME dockerhub 帳號
Variable DOCKER_HUB_PASSWORD dockerhub 密碼
Variable DOCKER_HUB_REGISTRY https://index.docker.io/v1/
Variable DOCKER_HUB_REPO dockerhub repo
File serviceAccountKey 前面生成的 json key

正式開始

先在自己電腦上把 service 跟 ingress 架設好(一次性的不需要用到 CICD)

kubectl apply -f service.yaml
kubectl apply -f ingress.yaml

生成 kubeconfig 供 CICD 使用

  1. 把取得 cluster 的資訊用的 command 存成變數,以便接下來的工作 GET_CMD="gcloud container clusters describe [cluster 名稱] --zone=[cluster 地區]"
  2. 以下指令會以現在用的 cluster 資訊生成一個 kubeconfig.yaml 的設定檔
cat > kubeconfig.yaml <<EOF
apiVersion: v1
kind: Config
current-context: my-cluster
contexts: [{name: my-cluster, context: {cluster: cluster-1, user: user-1}}]
users: [{name: user-1, user: {auth-provider: {name: gcp}}}]
clusters:
- name: cluster-1
  cluster:
    server: "https://$(eval "$GET_CMD --format='value(endpoint)'")"
    certificate-authority-data: "$(eval "$GET_CMD --format='value(masterAuth.clusterCaCertificate)'")"
EOF

:::info 這個檔案裡面的資訊只是用來把 kubectl 指向 cluster,光是這樣還不能夠用 kubectl 來操作你的 GKE cluster,所以可以放心把它放在 repo 裡。 我有過這個疑慮,所以把他 decode 出來,確定是 public key。 :::

gitlab-ci.yaml

這個檔案分兩個階段

  1. 包成 docker image push 到 dockerhub
  2. 把 docker image deploy 到 GKE

docker-hub

  • before script:用設定裡的環境變數登入 dockerhub
  • script:build & push

k8s-deploy

  • before script:安裝 kubectl, 把 service account key 跟 kubeconfig 呼叫出來
  • script:生成 deploy.yaml 然後 apply

這樣就可以自動 deploy 到 GKE 了。

Cucumber BDD

BDD & TDD ?

TDD (Test-Driven Development)

先寫測試,當通過所有測試即開發完成。

BDD (Behaviour-Driven Development)

先設定用戶行為的場景,以滿足用戶行為撰寫測試,當滿足所有用戶行為即開發完成。

Cucumber 是什麼?

cucumber .feature檔範例 (支援中文)

#language: zh-TW

功能: 根據輸入文字查找圖片路徑

  場景大綱: 使用者輸入文字
    當 使用者輸入 "<文字>"
    那麼 查找對應的圖片 "<路徑>"

  例子:
    | 文字 | 路徑 |
    | 王金平 | 王金平.png |
    | 賴清德 | 賴清德.jpg |
    | 習近平 | 習近平.jpg |
    | 韓冰 | 韓冰.jpg |
    | 馬英九 | 馬英九.jpg |

  場景: 圖庫中 有 對應的迷因
    假如 資料庫中有對應的路徑
    那麼 根據路徑產生圖片

  場景: 圖庫中 沒有 對應的迷因
    假如 資料庫中 沒有 對應的路徑
    那麼 告訴使用者沒有資料

:::success 以淺顯易懂的語言撰寫須滿足的用戶行為場景,有利於與非資訊人員的溝通(PM 或客戶等等)。 並且.feature檔是可執行的測試檔案,當通過所有的用戶行為測試,專案中的功能即開發完成。 :::

cucumber 常用中英文關鍵字對照

英文關鍵字 中文關鍵字
feature Text
scenario 場景
scenario_outline 場景大綱
examples 例子
given * , 假如
when * , 當
then * , 那麼
and * , 而且
but * , 但是

:::info 有 * 部份代表使用關鍵字只是為了增加可閱讀性,也可以都寫 * 。 :::

使用 Cucumber 實做 BDD

確認 Node.js 已安裝

node -v
npm -v

進入當前專案目錄

mkdir meme
cd meme

npm 初始設定

// --yes 使用 deafult 設定
npm init --yes

安裝 cucumber

sudo npm install cucumber --save -dev

將 package.json 中的 "test" 更改為 "cucumber-js"

{
  "name": "hellocucumber",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "cucumber-js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "cucumber": "^5.0.3"
  }
}

準備文件結構

mkdir -p features/step_definitions

在專案根目錄中新增 cucumber.js,寫入以下內容

module.exports = {
  default: `--format-options '{"snippetInterface": "synchronous"}'`
}

新增 features/step_definitions/stepdefs.js文件,寫入以下內容

const assert = require('assert'); // js 用來報錯的模組
const { Given, When, Then } = require('cucumber');

目前文件結構

:::info 資料夾名稱根據 cucumber 的預設,檔名自定義。 這樣我們就有了一個最簡單的cucumber 專案。 :::

驗證各元件是否運作正常

// 在專案根目錄中執行
npm test

Cucumber 告訴我們它沒有發現能運行的場景。

開始撰寫用戶行為的場景(.feature)

以我們的範例來說,當用戶輸入文字,必須產生相對應的圖片。

新增 features/meme.feature 空文件,預備用來撰寫場景

#language: zh-TW

功能: 根據輸入文字查找圖片路徑
  場景: 使用者輸入文字
    當 使用者輸入 "文字"
    那麼 查找對應的圖片 "路徑"

:::info 功能(Feature)的部份,代表我們目前需要滿足的用戶行為。 場景(Scenario)的部份,代表我們目前假設的操作。 當(When)以及 那麼(Then),則是我們目前要測試的步驟(Step)。 " " 雙引號中的內容為變數,前面要空格。 :::

再執行一次測試 npm test

會得到以下結果

cucumber 告訴我們目前的兩個 Step 皆為 Undefined 的狀態,並提示我們該如何撰寫。 :::success 複製提示的程式碼片段,在features/step_definitions/stepdefs.js 中貼上。 :::

我們再執行一次npm test 看看有什麼不同

前次的 UU 變為 P-,表示 cucumber 找到我們定義的 step 並執行,第一個 step 是 pending 的狀態,而第二個則是被跳過。

下個步驟為撰寫 step 的內容

const assert = require('assert'); // js 用來報錯的模組
const { Given, When, Then } = require('cucumber');

// 建立圖片路徑資料庫
let imgData = new Map();
imgData.set("王金平", "王金平.png")
imgData.set("賴清德", "賴清德.jpg")
imgData.set("習近平", "習近平.jpg")
imgData.set("韓冰", "韓冰.jpg")
imgData.set("馬英九", "馬英九.jpg")

// 根據輸入文字查找檔案名稱
function findImg(text) {
  return imgData.get(text);
}

// scenario #1
Given('使用者輸入 {string}', function (inputText) {
  this.text = inputText;
});

Then('查找對應的圖片 {string}', function (expectedPath)  {
  // 實際上我們的程式找到的檔名
  this.actualPath = findImg(this.text);
  // 如果與預期不相符則報錯
  assert.equal(this.actualPath, expectedPath);
});

在執行一次測試 npm test

兩個 step 目前是無法連動的,可以看到 undefined == '路徑', step2 沒有正確拿到 step1 中的 "文字",而且 cucumber 還不知道什麼應該是預期的 output。

加入 input 與 output 對應的表格

#language: zh-TW

功能: 根據輸入文字查找圖片路徑
  場景大綱: 使用者輸入文字
    當 使用者輸入 "<文字>"
    那麼 查找對應的圖片 "<路徑>"

  例子:
    | 文字 | 路徑 |
    | 王金平 | 王金平.png |
    | 賴清德 | 賴清德.jpg |
    | 習近平 | 習近平.jpg |
    | 韓冰  | 韓冰.jpg   |
    | 馬英九 | 馬英九.jpg |

:::warning "場景"改為"場景大綱",兩個 step 才會連動。 變數的部份加上<>,才能對應到表格。 :::

再測試 npm test

一個 scenario,兩個 step,測試五組資料都通過。

實做產品 html,加入通過測試的程式碼

<!DOCTYPE html>
<html>
<head>
  <script type="text/javascript" src="imgData.js"></script>
  <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
  <script src=" ../index.bundle.js "></script>
</head>

<body style="background-color:pink">

<div style="text-align: center; padding-top: 64px;">
  <input id="text" type="text" value="" size="15" placeholder="輸入文字" style="font-size: 48px;">
  <button onclick="generateImg()">出來吧</button>

  <div>
    <img id="img" src="https://i.imgur.com/f5cWWxT.png" style="max-height: 600px; max-width: 800px; padding-top: 32px;">
  </div>
</div>

<script>
function generateImg() {
  // get input text
  text = document.getElementById("text").value;
  // then find the meme's path in imgData
  path = imgData.get(text);
  console.log(path);
  // generate image in html
  document.getElementById("img").src = path;
}

</script>

</body>
</html>

git push 後自動 Test, Build 以及 Deploy

:::success 大功告成,往後更新時,都將會自動的測試以及發布。 :::

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages