[工作筆記] 軟體工程的檢傷分類

前情提要

回想一下你手頭上的工作,你如何決定你的工作順序?
常見的有緊急/重要四象限法。
另外在一些方法論上,利如 Scrum 交由特定角色排序,
而急診室的檢傷分類如下。

1
2
3
4
5
6
7
8
9
第一級(復甦急救,立即處理-病況危急生命或肢體,需立即處理。如:心跳、呼吸停止、肢體及嘴唇發青、發紫,體溫高於41度或低於32度,無意識或意識混亂、持續抽搐且無意識。

  第二級(危急,可能等候時間10分鐘)-潛在性危及生命、肢體及器官功能,需快速控制與處理。如:急性意識狀態改變、持續胸悶、胸痛且冒冷汗、低血糖(<40mg/dl)、外傷造成之大量出血,頭頸軀幹骨盆部位血流不止、高處墜落、車禍(乘客被拋出車外)、頭部撞擊後曾失去意識等。

  第三級(緊急,可能等候時間30分鐘)-病況可能持續惡化,需急診處置;病人可能伴隨明顯不適症狀,影響日常生活。如:無法控制的腹瀉或嘔吐、外傷後肢體腫脹變形疑似骨折/脫臼、高血壓(收縮壓>200mmHg或舒張壓>110mmHg)且沒有任何症狀等。

  第四級(次緊急,可能等候時間60分鐘)-病況可能為慢性病及急性發作,或某些疾病之合併症相關,需在1至2小時做處置避免惡化。如:局部蜂窩性組織炎、急性咳嗽但沒有發燒、發燒但無其他不適、反覆性疼痛或暈眩等。

  第五級(非緊急,可能等候時間120分鐘)-病況非緊急,需做鑑別性診斷或轉介門診。如:慢性噁心、嘔吐或打嗝、輕微擦傷,瘀青,軟組織受傷、螫傷或咬傷,但無發燒或疼痛不適、輕微腹瀉,無脫水現象等。

我們可以參考。以等級與具體案例作為分析。

個人的經驗是以 3+1 個維度分析,
主要為發生風險、發生頻率、影響範圍和可否重現。

判斷流程

  1. 是否為線上問題:線上問題較可能產生價值的耗損
  2. 發生頻率:高頻的問題如果不處理會帶來大量干擾、下一階段可能會麻痺,甚至讓真正的問題從眼前跑走
  3. 影響範圍:誰會感到痛苦?老闆?客戶?PM ? RD ? 營運?怎麼作可以停止或減輕他們的痛苦。
  4. 風險評估:不作會怎麼樣?作了會怎麼樣?

定義

x 的維度是風險的高低,風險高者需安排修正,風險低小者可以再觀察
y 的維度是影響範圍的大小,範圍大者需優先停損縮小範圍,範圍小者就安排修正
z 的維度是發生的頻率,高頻者需優先停損止傷,低頻者需要觀察,如果發生一次就會形成持續性或永久性的傷害即為高頻
加上可否重現分析如下:

可重現

高風險/高範圍的情況下,頻率已經不重要,重要的不能讓發生 0 次的事情發生 1次,
一但發生只能砍掉重練,具體例子如廣達被勒索病毒攻擊

維度一:高風險/高範圍/高頻率:終止營業、暫停營運的等級
維度二:高風險/高範圍/低頻率:終止營業、暫停營運的等級

應急辦法,止血: 先限縮風險、範圍、頻率至少其中之一。

低風險/高範圍的情況,即使發生了仍有轉還的空間,高頻時處理的重點在縮小範圍與暫時的降噪
一但修正,理應停止降噪的設定,低頻時安排修正計劃即可

維度三:低風險/高範圍/高頻率:部份功能停止運作。應暫時的降噪(暫時停止通止,避免有其它警告被淹沒),安排修正計劃
維度四:低風險/高範圍/低頻率:部份功能停止運作。儘速安排修正計劃。

在高風險的情況,如果有小範圍的異常,都應立即處理,重點在於能不能根除問題,或是緩解發生頻次

維度五:高風險/低範圍/高頻率:根除問題,或是緩解發生頻次
維度六:高風險/低範圍/低頻率:根除問題,或是緩解發生頻次

在低風險/低範圍的情況下,要小心避免讓人習慣這類型的錯誤(麻痺),或是產生過多雜訊影響判斷。
偶一為之但是知道原因的話可以不處理

維度七:低風險/低範圍/高頻率:降噪或是設定提示水位
維度八:低風險/低範圍/低頻率:不處理

不可重現

不知道自已不知道是最可怕的,如果發生時是高風險/高範圍大多也沒救了,
就死前能不能有所收獲

維度一:高風險/高範圍/高頻率:終止營業、暫停營運的等級
維度二:高風險/高範圍/低頻率:終止營業、暫停營運的等級

低風險/高範圍的情況,即使發生了仍有轉還的空間,高頻時處理的重點在縮小範圍與暫時的降噪
一但修正,理應停止降噪的設定,低頻時安排修正計劃即可

維度三:低風險/高範圍/高頻率:暫時的降噪(暫時停止通止,避免有其它告警被淹沒),查明原因,安排修正計劃
維度四:低風險/高範圍/低頻率:查明原因,安排修正計劃

在高風險的情況,如果有小範圍的異常,都應立即處理,重點會在能不能根除問題,或是緩解發生頻次

維度五:高風險/低範圍/高頻率:查明原因,根除問題,或是緩解發生頻次
維度六:高風險/低範圍/低頻率:查明原因,根除問題,或是緩解發生頻次

在低風險/低範圍的情況下,要小心避免讓人習慣這類型的錯誤(麻痺),或是產生過多雜訊影響判斷。
偶一為之但是不知道原因的話要特別小心標記,這很可能是別處發生問題的潛兆

維度七:低風險/低範圍/高頻率:查明原因,降噪或是設定提示水位
維度八:低風險/低範圍/低頻率:查明原因

小結

幾個重點,預防勝於治療,
真的發生時儘可能讓風險降低、範圍縮小、頻率減少,
十萬火急時可以先用暫解去處理,減少傷害。
但是回到正常流程就是要找出根本原因,並用正確的方式治療。

我作了個表以供參考

情境 風險 範圍 頻率 行動
可重現情況(Reproducible)
1 立即處理,確保不再發生。
2 立即處理,確保不再發生。
3 暫時降噪並制定修正計劃。
4 制定修正計劃。
5 根除問題或減少頻率。
6 根除問題或減少頻率。
7 降噪或設定提示水位。
8 不處理。
不可重現情況(Non-Reproducible)
9 查明原因,可能無法挽救。
10 查明原因,可能無法挽救。
11 暫時降噪,查明原因,制定修正計劃。
12 查明原因,制定修正計劃。
13 查明原因,根除問題或減少頻率。
14 查明原因,根除問題或減少頻率。
15 查明原因,降噪或設定提示水位。
16 查明原因,特別小心標記可能是其他問題的潛在預兆。

(fin)

[生活筆記] 一些程式轉職相關的問答

前情提要

一直有在作程式相關的助教、導師相關的工作,有些學生的問題很棒,也是我的盲點。
沒有心思好好整理,至少記錄下來,給未來的自已一絲反芻機會。

問題

Express.js在業界的實用性如何? 優點和缺點是什麼? 有需要再學習第二種語言/框架嗎?,建議的選擇是?

算常見的 Nodejs 的 Web 框架,優點就是夠主流,資源好找。
缺點就太簡單。

語言/框架可以多學,自已判斷。不知道就先跟著公司或社群走。

參考文章10 Best Nodejs Frameworks for App Development in 2023

Google 關鍵字: Nodejs Web Framework 2023

思路:不論什麼樣的程式語言,我的本質是 Web 開發,不同的語言與框架有不同的場景,總不缺乏新的挑戰者,
可以觀望、研究、測試、入坑,沒有一定的正確的答案,能夠快速作出判斷與取捨是重要的,但這需要經驗。

2. 如果想要在自有主機上部署Express.js,比較適合搭配的web伺服器軟體是?

這裡的”web伺服器軟體”是指什麼? Nginx 或 Apache 嗎?
選擇適合的場景就好。目前業界主流是 Nginx,思路參考第一題

3. 樣板引擎在業界的使用度高嗎? 有使用的話會是在什麼情況呢? 有比較主流的選擇嗎? 還是大部分被前端框架取代了?

註:學生這裡指的是 express + handlebars 的開發方式
大部份都前後分離端了,維護舊案可以能會碰到。
樣板引擎主要會發生在 Web Form 這類的舊專案,主流可能是 Php 或 Asp.Net 的某些專案。
或是在 2010 很主流的 MVC 開發方式,這段時間各家語言也有自已的解決方案。  

思路:理解為什麼會有前後端分離的發生,某些情況的小專案,樣板引擎的開發方式會比前後端分離有效率。

4. AC一個練習教案的規模,大概有幾十個到百個檔案要維護。實際業界專案的檔案數量應該更多,檔案間的關係複雜,維護人員要如何弄得清楚呢? 有沒有什麼比較好的管理工具?

檔案是指程式碼的部份嗎?  
學會架構分層,基本就三層式架構、MVC、MVVM、MVP、MV* 等,
使用 OOP 語言的話可能也要依 Pattern 分層,現在比較流行 DDD、六角架構等…
再大一點會是微服務的情況。

思路:想一下檔案間的關係複雜,那你會怎麼作?基本上就是分門別類,
好的分類可以讓錯誤發生時可以快速定位、實作功能時可以職責分離。
舉個生活化的例子,就是像是整理你的房間。

5. 在前後端的協作中,如何決定一項功能應由前端製作或後端製作? 舉例來說,要加上分頁的功能,本質上只是資料的整理重排,這樣的功能應該由前端或後端做呢?

Case by Case, 以我來說跟畫面有關的歸前端、其它歸後端。  
分頁通常會由後端處理、但是有些情況前端也要處理。
Ex: 避免太頻繁的 request 到 Server,一次性回傳大量資料,由前端自行製作分頁。

思路:還是以需求為依歸、預設一個簡單的原則。團隊合作的話,最理想是團隊共識,次之由 Leader 決定,不要發生由某端強勢主導的情況。
舉例說明,欄位檢查,前後端都要作,前端的目的是引導使用者正確操作,後端是真正的防護。

6. 求職網站中許多職缺要求會其他程式語言如: C# or PHP,會建議再去學其他語言嗎? 有推薦的學習平台或課程?

我會想學 TypeScript、Python。
程式在 web 開發上有類似的作法,更多領域其實也需要程式人才,
例如:手機 App 開發、IoT 開發、AI 工程師等…
建議找一個自已可以生存的領域,專研打磨裡面的技術會建議
社群活動可以用來搜集關鍵字,
Udemy 的課程很便宜,YT 也有很多大神的頻道可以學習。

思路:唯一的問題是學不完,怎麼整合應用變現,這個時候就不只是求職了,公司只是某一些技術棧整合的結果,
當你學的更多時,或許可以有別的出路?

7. 建議如何練習LeetCode? 才不會好像只是背答案,而不是懂背後的邏輯。有聽說 NeetCode 平台不錯用?不知道助教是否推薦

就是多刷多累績經驗,看題目、看答案。
兩個重點,把題目看懂、把答案看懂,試著把自已的思路轉化成程式
平台很多:NeetCodeLeetcodeHackerrank

思路:助教用 TDD 刷 LeetCode,實務上蠻常遇到面試造火箭、入職擰螺絲的狀態,
但是仍然不知道要學什麼或作什麼樣的 Side Project 時,刷題是一個好的選擇。

8. Junior工程師除了準備作品集,LeetCode 與程式語言的觀念外,通常面試還會考什麼嗎?

我主要會問架構與流程題,如果能把自已開發的架構與流程說明清楚,就可以同時觀察到技術能力與溝通能力。

思路:常常去面試就會知道現在業界常問什麼了。

10. 職場上在開發專案時,後端工程師的實作範圍會是全部一手包辦還是還會細分工作?或者工作架構會是怎麼分配的?

看公司,建議去有分工的公司,一手包辦很可能會很雷,學到的可能也是一堆大泥球的作法

12. 除了現有AC課程之外,助教建議Junior後端工程師還需要提升哪方面的技術

掌握好版控與協作流程/自動化與容器化技術/實用精實的開發實踐

  • Git、Docker、AWS/Azure/GCP、K8S
  • TDD (BDD、ATDD)/ Refactoring /Design Pattern

13. 目前軟體業有受到景氣影響而減少職缺數嗎或停止招聘

國外有一波大裁員,有影響到,但台灣本身還好
至少我還好

14. 請問助教平常寫程式會如何結合AI加速開發,有建議相關課程嗎

Github Copilot+TDD

15. 網站若有多國語言版本,技術上要怎麼實作呢?

關鍵字i18n

1
2
3
4
5
6
7
8
{
"tw":{
"login":"登入"
},
"en":{
"login":"Login"
}
}

16. 手機版和電腦版的網站要怎麼分割呢?

我知道的兩種作法

結語

部份題目涉及專案內容,所以排除掉。
但是有關從無到有開發一個專案,或是與人合作開發專案,是整個職涯中會很常發生的事。
現在不作,以後也會遇到,可以儘量去嚐試。

(fin)

[實作筆記] Websocket 初體驗

前情提要

tl;dr, websocket 至少是一個 10 年以上的技術了,但是筆者一直沒有機會實作它。
大部份的時候是應用場景不符合,或是簡單 Long Polling 已經足夠,甚至就算用 Polling 這個方法也無法把 Server 打掛。
另一個情況是團隊已經很成熟,有專門負責的部門在統一處理這塊邏輯,
而通常這塊邏輯會與主要的核心功能作切分,所以我也沒有機會接觸到。
這次難得有個小型的專案,有機會實作,故稍作記錄一下。

需求

使用者會停留在某些頁面等待系統的資料狀態更新,大約每 10 秒要作一次 Polling,
而平均資料狀態更新需要 3~10 分鐘不等。在這個情況下要改用 websocket,
(註:我不認為這是一個很好的應用場景,但是牽扯更多未揭露的調整故不展開討論,
EX:Heartbeat、Keep-Alive 等等…機制也未討論到)

WebSocket Server 實作

使用的技術:Typescript + Express + ServerSocket

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// import library
import express, { Request, Response } from "express";
import { Server as ServerSocket } from "ws"; // 引用 Server

if (process.env.NODE_ENV !== "production") {
require("dotenv").config();
}
// form env 指定一個 port
const PORT: number = Number(process.env.WS_PORT);
// 設定斷開時間
const CT: number = 1000 * Number(process.env.CONNECTION_TIMEOUT);
const app = express();
const server = app.listen(PORT, () =>
console.log(
`[Server] Listening on http://localhost:${PORT} , timeout is ${CT} ms`
)
);
const wsServer = new ServerSocket({ server });

// Connection opened
wsServer.on("connection", (ws: WebSocket, req: Request) => {
// Connection 建立時發生的邏輯

// Listen for messages from client
ws.on("message", (data: string) => {
try {
// 發送 Message 時發生的邏輯
} catch (error) {
console.error("Invalid JSON format: ", data);
}
// Get clients who has connected
const clients = wsServer.clients;
// Use loop for sending messages to each client
clients.forEach((client) => {
client.send(JSON.stringify(docStatus));
});
});
// 設定 ping 時間間隔,用來讓連線太久的 Client 斷開
const interval = setInterval(() => {
if (ws.alive) {
ws.terminate();
clearInterval(interval);
return;
}

ws.alive = false;
ws.ping("", false, () => {
console.log("[Timeout]", ws.key);
});
}, CT); // 30 秒

// Connection closed
ws.on("close", () => {
// Connection 關閉時發生的邏輯
console.log("[Close connected]", ws.key);
});
});

// 新增一個路由處理器
app.get("/", (req: Request, res: Response) => {
// health and version check
res.send(`WebSocket Running Version ${process.env.WS_VERSION}`);
});

前端實作,收送雙向

使用的技術:JavaScript
以下內容大多參考於神 Q 超人的文章,
作為參考用,實務上會搭配使用的前端框架作修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
var ws;

// 監聽 click 事件
document.querySelector("#connect")?.addEventListener("click", (e) => {
console.log("[click connect]");
connect();
});

document.querySelector("#disconnect")?.addEventListener("click", (e) => {
console.log("[click disconnect]");
disconnect();
});

document.querySelector("#sendBtn")?.addEventListener("click", (e) => {
const msg = document.querySelector("#sendMsg");
sendMessage(msg?.value);
});

function connect() {
// Create WebSocket connection
ws = new WebSocket("wss://localhost:8088");
// 在開啟連線時執行
ws.onopen = () => {
// Listen for messages from Server
ws.onmessage = (event) => {
// 收到訊息的 Logic
console.log(`[Message from server]:\n %c${event.data}`, "color: yellow");
};
};
}

function disconnect() {
ws.close();
// 在關閉連線時執行
ws.onclose = () => console.log("[close connection]");
}

// 監聽 click 事件
document.querySelector("#sendBtn")?.addEventListener("click", (e) => {
const msg = document.querySelector("#sendMsg");
sendMessage(msg?.value);
});

// Listen for messages from Server
function sendMessage(msg) {
// Send messages to Server
ws.send(msg);
console.log("[send message]", msg);
}

後端實作,只送不收

使用技術: php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
include "./vendor/autoload.php";

$client = new WebSocket\Client("wss://localhost:8088");

$message = json_encode(array(
"your_property" => "Your Data",
"complex_property" => array(
"no" => 1,
"status" => "Hello word"
)
));

$client->text($message);
$client->close();
?>

後記

實務上在股票看盤即時更新、多人聊天室、多人網頁遊戲上或許十分有用,
但實際上在這此的案例上就有些大材小用了,先當作學習了。
在查找資料的過程有一段話很受用,
我稍作總結如下:
當討論到 WebSocket 時,不應用 Http 的標準去審視它,
更應該關注這些 Connection 會持續連接多久? Connection 之間交互的行為是什麼?  
是運算密集的行為還是讀寫密集的行為?…等等,才是你決策的關鍵。

參考

(fin)

[踩雷筆記] Gitlab CI 執行 Command 結果與本地環境不一致

前情提要

我們建立 CI 的一個目標是讓繁鎖的工作流程自動化,
而自動化的前題是標準化,而自動化的未來是可以規模化。
每一次的自動化,就像是在為工作流程添加柴火,讓未來的的每一步可以走的更穩更快。

問題

一直以來我認為只要在本機環境上可以執行的指令(command),就一定可以在 CI 中執行,
沒想到這次不一樣。

下面這個語法是我 CI 流程的一部份,主要的目的是要重啟 pm2 的服務

1
pm2 restart app.js

這段在 Gitlab-runner CI 的寫法如下,主要的目的就在 Gitlab-runner 中將指令送到指定的機器$VM

1
- ssh -i ~/.ssh/id_rsa gitlab-runner@$VM 'pm2 restart app.j'

不過我會收到錯誤訊息

1
bash: pm2: command not found

當我直接在$VM執行時,確又不會有錯誤

根本原因

原來在 Unix-like 系統中,shell 分為 Interactive Shell(互動式)和 Non-Interactive Shell(非互動式)。
兩者環境變數和 PATH 不同,導致在 CI 或遠程機器上執行命令可能與本地不一致

查詢了一下兩者的 PATH 如下

Interactive Shell

/home/gitlab-runner/.nvm/versions/node/v20.4.0/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

Non-Interactive Shell

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

明顯可見的差異在於NVM的路徑設定,這個 PATH 是在安裝 NVM 時被加上去的

解法

我們可以選擇重新 export NVM_DIR 這個作法

1
2
- ssh -i ~/.ssh/id_rsa gitlab-runner@$VM 'export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" &&
pm2 restart app.js

另一種作法會多上一層 shell,並再次執行互動式的 bash 我覺得較不易理解
不好維護,故不採用。

1
- ssh -i ~/.ssh/id_rsa gitlab-runner@$VM 'bash -i -l -c "pm2 restart app.js"'

參考

(fin)

[生活筆記] 2023 半年工作回顧

前情提要

這份是 2023 年, 3 至 6 月間,我在工作上獲得的成果,
以理性務實的態度推動各項工作,以下是在這段時間內所取得的重要成果:

敏捷觀念的引進與實踐

我引進了迭代開發要事優先的敏捷觀念,
同時建立了 PBI(產品待辦項目)與工作看板,以確保團隊的工作透明度
我們每日召開立會,及時更新資訊,並建立了文件系統,以便於知識的分享與傳承。

前後端分離的優化

我推動了前後端分離的工作方式,使專業得以適當分工,減少了彼此等待的時間。
這樣的優化使得服務可以被重複使用,避免了重複開發,節省了寶貴的時間與資源。

自動化的部署機制引入(CI/CD)

我們成功建立了自動化的部署機制,每次部署都節省了至少 10~30 分鐘的時間,
同時大幅減少了人工操作可能帶來的錯誤風險
這項優化為團隊帶來了高效率與更穩定的工作環境。
這是持續性的正面效益,假設每周部署 10 人次以上,以換算時薪為 600 計算,
每年每位 RD 約可以省下等值於 8 萬的工作時間。並大幅下降風險。

準時交付 N 系統,完成參展目標

我們順利地在新加坡、法國、台北和高雄四地參展,並提供了展場現場的支援,
這已超出我們本職專業的範疇。
但我們成功地達成目標,贏得了更多的曝光與商機。

帶回自主開發的 I 上鏈系統,省下外包費用

為了節省成本,我們將原本要外包的 I 上鏈系統帶回自主開發,從而節省了公司大筆的費用與時間。
這項舉措不僅讓我們獲得了更多的控制權,還提升了整體效率。
此項目,粗估至少擁有 300 萬以上的產值。

其他突破與調整

除了以上成果,我還做了以下貢獻:

  • 解決了公司 domain 數量達到上限的問題,通過與系統商的溝通,成功解除了限制。
  • 幫助查找了未關閉的 AWS 資源,避免了資源的浪費。
  • 調整了資安需求,提高了系統的安全性。
  • 協助調解了系統與需求之間的衝突,確保了順利推進工作。
  • 未來的發展計劃
  • 為了持續推動工作的進步,我擬定了以下計劃:

下一步

  • 將預計外包的 P 系統帶回自主開發,進一步節省公司費用。粗估其價值為 110~800 萬之間。
  • 積極招募人才,引進新血,提升團隊實力。
  • 推進單雲轉多雲的轉型,增加靈活性與彈性。
  • 針對資安問題進行調整,包括關閉公開 IP 和加密資料等措施。
  • 處理舊系統 L 的大泥球,逐步優化系統結構,提高整體效率。

結論

整體而言,在短短三個月內,接手的專案已為公司撙節了 150 萬 左右的支出,
而在自動化工具,用一個粗略評估的方式,假設每周每個 RD 會部署 5 次,
一年 52 周,一次部署可以省下 10 分鐘的情況下,目前公司 5 位 RD:
5*5*52*10= 13000 分鐘,大約可以省下 1.25 個人月,用來作更有意義的事。
而其它工作在長久看來也會持續帶來收益,整體而言,這三個月的產值已超過 500 萬
希望在未來的日子裡,能夠持續取得進步,為公司的發展做出更大的貢獻。

(fin)

[實作筆記] GitHub Actions:停用 save-state 和 set-output 命令的措施

前情提要

GitHub Actions 是一個強大的自動化工作流程工具,可讓您在 GitHub 存儲庫中執行各種自動化任務。
它允許您根據事件觸發工作流程,例如提交代碼或創建拉取請求。
您可以使用預設的操作或自定義操作來建立工作流程,並將其用於自動化測試、部署、持續集成等開發流程。
GitHub Actions 提供了一個靈活、可擴展和可自訂的方式來增強您的開發工作流程。

而我實務上的情境是用來寫 Blog,並進行自動部署,而我注意到了一個警告訊息。

1
Warning: The `set-output` command is deprecated and will be disabled soon. Please upgrade to using Environment Files. For more information see: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/

問題

在 Github 的官方 Blog 指出,自 CI Runner 版本 2.298.2 起,
如果用戶在使用 save-state 或 set-output 將開始發出警告。
並計劃在 2023 年 5 月 31 日完全禁用它們。
從 2023 年 6 月 1 日開始,使用 save-stateset-output 命令的工作流程將因錯誤而失敗。

補充

實際上根據 Github 觀測數據顯示,這些命令的使用率相當高。
考慮到受影響的客戶數量,而推遲了移除的時間。

我的情況

為什麼我會用到這值呢?看看我的 CD 部署檔

1
2
3
4
5
6
7
8
9
10
11
12
13
## Skip ...
step:
# Deploy hexo blog website.
- name: Deploy
id: deploy
uses: marsen/[email protected]
with:
deploy_key: ${{ secrets.DEPLOY_KEY }}
- name: Get the output
run: |
echo "${{ steps.deploy.outputs.notify }}"

## Skip ...

在我這裡有兩步驟,Deploy  與 Get the output
Deploy 直接用了我的另一專案進行部署,內部的工作簡單的說明如下:

  1. 用 Dockerfile 建立執行環境
  2. 拉取 Hexo Blog
  3. 執行相關指令 ex: hexo g
  4. 部署完成印出成功訊息

問題就出在這個第四步,在 Github Runner 啟動的 image 執行過程的輸出,
並不會顯示在 Action Steps 的 Output 資料之中。
所以在這個專案多作一步 Get the output 來印出這個訊息。

1
2
3
- name: Get the output
run: |
echo "${{ steps.deploy.outputs.notify }}"

可以知道我們的訊息是放在 steps.deploy.outputs.notify 之中,
而原始接出資料的方式如下,這個作法即將被逃汰了(為了避免無意間的資訊外洩)

1
echo ::set-output name=notify::"Deploy complate."

解決方式

如果官方 Blog 所說,你只要將 key-value 用以下的形式設定到環境變數 $GITHUB_OUTPUT 即可,

1
echo "{key}={value}" >> $GITHUB_OUTPUT

舉例來說:

1
2
result="Deploy complate."
echo "notify=$result" >> $GITHUB_OUTPUT

參考

(fin)

[踩雷筆記] 移除 VM 外部 IP 後 GCS 檢查異常的問題

前情提要

我們使用 GCP Compute Engine 建立 VM,並且配置了一個對外的 IP(External IP),
同時設定了一個排程,定期將 VM 上產生的資料轉存到 GCS(Google Cloud Storage),
我們主要使用的工具是 gcloud 並且在 VM 上設定了某個 Service Account,
並且一切運作順利。

某日因為資安考量我們移除了 VM 的 External IP,而搬檔到 GSC 的排程就異常了。
我們簡單使用以下指令作檢查

1
gcloud storage ls

這個指令會列出我們 GCS 上的資源,
當 VM 有 External IP 時一切運作正常,而移除時會豪無回應。

解決方法

這裡只需要修改 GCP 的 VPC 設定,
選擇 subnet (ex:asia-east1,看你的 VM 在哪個 Zone)
啟用 Private Google access 為 On 然後存檔

參考

(fin)

[實作筆記] GCP 使用 IAP 連線 VM

簡介 IAP(Identity-Aware Proxy

GCP 的 IAP(Identity-Aware Proxy)是一個身份驗證和授權服務,
用於保護和管理對 Google Cloud 資源的訪問。
它提供了對虛擬機器(VM)實例的安全連接和控制,並可用於 SSH、RDP 等流量的安全轉發(Forwarding)。

通過 IAP,我們可以實現零信任訪問模型,僅允許經過身份驗證和授權的用戶訪問資源,
並且可以根據用戶的身份和上下文進行細粒度的訪問控制。
這提高了資源的安全性並減少了潛在的安全風險。

零信任訪問模型

零信任訪問模型(Zero Trust Access Model)是一種安全設計方法,該方法不假設內部網絡是可信的,
並對每個訪問請求進行驗證和授權,無論該請求來自內部還是外部網絡。
在零信任模型中,所有訪問都需要通過驗證和授權,並且需要進一步的身份驗證和授權步驟,
以確定用戶是否具有訪問資源的權限,更多可以查看參考資料。

比較 VPN 與 RDP

其他常見的連線方式包括傳統的虛擬專用網絡(VPN)和遠程桌面協議(RDP),
它們通常用於在企業內部建立安全連接,但對於雲環境和遠程用戶來說,這些方法可能不夠靈活和安全。

IAP 提供了一個更強大和安全的連接方式。
它在零信任訪問模型下工作,通過驗證和授權機制確保只有經過驗證的用戶可以訪問資源。
以下是 IAP 的優勢:

  • 精確的身份驗證和授權:IAP 可以根據用戶的身份和上下文進行細粒度的訪問控制,僅允許授權的用戶訪問資源。
  • 無需公開 IP 地址:IAP 通過提供 IAP 隧道和代理服務,不需要公開資源的實際 IP 地址,增強了安全性。
  • 雲原生和易於使用:IAP 是 GCP 的原生服務,與其他 GCP 服務整合,易於設置和管理。

前情提要

客戶在 GCP 上部署了多台 VM,每個 VM 都有公開的外部 IP 地址,並允許開發者使用 SSH 進行連線。
這樣的設置存在著安全風險,也需要為這些公開的 IP 付出額外的成本。

為了提高安全性並解決這個問題,我們使用 GCP 的 IAP(Identity-Aware Proxy)。
透過 IAP,我可以實現更安全的連線方式,不需要使用 Public IP 。
IAP 提供了精確的身份驗證和授權,只有經過驗證的使用者才能訪問 VM。
這種設置符合零信任訪問模型,提高了資源的安全性,同時減少了潛在的安全風險。

實務操作

我有一台測試用的 VM beta 已經拔除了 public IP,

  1. 首先啟用(enable) Cloud Identity-Aware Proxy API,「Security」->「Identity-Aware Proxy」

  2. 在 IAM > Permissions 找到需要登入 VM 主機的帳戶加上「IAP-secured Tunnel User」這個角色

  3. 防火牆規則,IP Range: 35.235.240.0/20, 開通指定的 Protocols and ports,比如我們要 SSH 連線,就開通 tcp:22,Targets 設為 Specified targets tags Target tags 為 ingress-from-iap,

  4. 需要登入 VM 加上 tag ingress-from-iap

  5. 可以用以下語法測試連線

    1
    gcloud compute ssh beta --project=my-project --zone=asia-east1-c --troubleshoot --tunnel-through-iap
  6. 排除所有問題後嚐試連線

    1
    gcloud compute ssh beta --project=my-project --zone=asia-east1-c --tunnel-through-iap

補充說明

  • 開發環境在連線時已經透過 gcloud auth login 取得權限了。
  • 連線的目標主機仍然需要設定 SSH 金鑰
  • 35.235.240.0/20 是 GCP 中 IAP 使用的特定 IP 範圍,不可修改

20230711 補充

透過 IAP 連線會出現 “Increasing the IAP TCP upload bandwidth” 的警告
可以參考官方文件

下載 Numpy

1
$(gcloud info --format="value(basic.python_location)") -m pip install numpy

設定環境變數

1
export CLOUDSDK_PYTHON_SITEPACKAGES=1

參考

(fin)

[實作筆記] MongoDB 解決方案評估-- Mongo Atlas

前情提要

最近在 VM 部署了 MongoDB, 不知道為什麼同事在 QA 與正式環境採取了兩個不同的作法,
因此產生了一些版本不一致的問題。
為此需要更換 MongoDB 版本,進而有一些討論,  
決策的考量主要有兩個面向,一、維運的成本。二、實際應付的帳務成本。
可行的方案比較如下,

  • VM 部署 MongoDB
    • GCP
    • Azure
    • Other Cloud …
  • Mongo Atlas(Start from GCP Marketing)
    • GCP VM with MongoDB(Pay as You Go)
    • Azure VM with MongoDB
  • Azure CosmosDB

實作隨筆:Mongo Atlas(Start from GCP Marketing)

測試規劃

mongo atlas 連線的測試架構

如圖,為了不影響原有的環境,我打算建一組新的 VPC Network 進行實驗,
並在這個網路中建立一台實體的 VM 機器,待 Mongo Atlas 設定完成後,
進行連線的測試。

測試的方法,由於 MongoDB Atlas 採用一種安全性較高的連線政策,
必需使用以下連線集群的三種方式才可以連到資料庫:
第一種,IP Access List,使用 GUI 建立 Cluster 的當下會自動加入一組你所在網路對外的 IP,
如果不是固定 IP 可能會有問題
第二種,Peering ,要付錢的版本 M10 以上才可用,也是我們這次實作的重點目標
最後一種,Create a Private Endpoint,也是
M10 以上才可用,但是不是我們這次的主要實作項目,所以不過多的展開。

從 GCP Marketing 建立 Mongo Atlas

在 GCP 的 Marketing 搜尋並訂閱 Mongo Atlas 後點擊 MANAGE ON PROVIDER
在 Mongo Atlas 建立 Cluster,也可以建立 Project 與 User 作更細緻的管控。
這時候可以到 Network Access 查看 IP Access List 的清單,應該會有你網路上設定的對外 IP,
這個流程是自動化的,但是我個人認為不是固定 IP 的話可能會有問題,如果有人可以給我一些提點會十分感激。

IP Access List

建立 VM

GCP 建立 VM 是十分簡單的,就不多作說明。
同時記得安裝我們的測試工具 - mongosh

Mongo DB 相關

切換 db

1
use mydb

查詢目前 DB 狀態

1
db.status()

Create User

1
db.createUser({ user: "username", pwd: "password", roles: [{ role: "roleName", db: "databaseName" }] });

Drop User

1
db.dropUser("username");

查詢 User

1
db.getUsers()

User 加入角色

1
db.grantRolesToUser("username", [{ role: "readWriteAnyDatabase", db: "mydb" }])

User 移除角色

1
db.revokeRolesFromUser("usernmae", [{ role: "readWrite", db: "admin" }])

前置作業: GCP VPC 與 Firewall Rules 設定

為了避免影響原有的系統,
建立 GCP 一組新的 VPC Network : vpc-lab,一般來說 GCP 的專案會有自動建立一組 default VPC Network,
default VPC Network 會預設建立以下的防火牆規則

  • default-allow-icmp – 允許來自任何來源對所有網路 IP 進行存取。ICMP 協議主要用於對目標進行 ping 測試。
  • default-allow-internal – 允許在任何埠口上的實例之間建立連接。
  • default-allow-rdp – 允許從任何來源連接到 Windows 伺服器的 RDP 會話。
  • default-allow-ssh – 允許從任何來源連接到 UNIX 伺服器的 SSH 會話。

與此對應,我也建立相同的規則給vpc-lab,如下:

  • vpc-lab-allow-icmp
  • vpc-lab-allow-internal
  • vpc-lab-allow-rdp
  • vpc-lab-allow-ssh

可以用以下的語法測試一下網路是否能連,如果可以連線再進行 mongodb connection 的測試

1
ping {ip address}
1
telnet {ip address} {port}
1
nc -zv {ip address}

測試連線用的語法

如果網路測試沒有問題,再進行 mongodb 的連線,由於目前沒有設定 Peering 連線,
所以在開發機上可以(網路環境需要在 IP Access List 內),而使用 GCP VM 會無法連線,
開發機連線 GCP MongoDB 語法

1
mongosh mongodb://{user:pwd}@{mongodb_ip}:27017/my_db

開發機連線 MongoDB Atlas 語法

1
mongosh mongodb+srv://{user:pwd}@{atlas_cluster_name}.mongodb.net/my_db

Peering 實作

首先需要在 Mongo Atlas 進行設定,
Network Access > Peering > Add Peering Connection
在 Cloud Provider 中選擇 GCP,

Mongo Atlas Setting

設定 Project ID、VPC Name 與 Atlas CIDR,
比較特殊的是 Atlas CIDR 在 GUI 的說明是

An Atlas GCP CIDR block must be a /18 or larger.
You cannot modify the CIDR block if you have an existing cluster.

但我遇到的狀況是,無法修改預設值為 192.168.0.0/16
建立後會產生一組 Peering 的資料,請記住 Atlas GCP Project ID 與 Atlas VPC Name

Mongo Atlas Peering

接下來到 GCP > VPC Network > GCP Network Peering 選擇 Create peering connection
在 Peered VPC network 中選擇 Other Project,並填入上面的 Atlas GCP Project ID 與 Atlas VPC Name

大概等待一下子就會生效了(網路上寫 10 分鐘,實測不到 3 分鐘)

參考

(fin)

[實作筆記] RWD 設計與 100vh 在行動裝置瀏覽器上的誤區

前情提要

參考圖片

RWD 設計與 100vh 在行動裝置瀏覽器上的誤區

我的網頁有作 RWD 的設計,需求大概是這樣,
綠色是在網頁底部懸浮的選單功能,
紅色區塊是一個控制面版,許多的功能、按鈕、連結都設定在上面。

我最一開始的設定方法如下,

1
height: 100vh;

在瀏覽器上使用模擬器顯示正常,但是在手機上就會出現異常
主要是手機上的 Chrome 和 Firefox 瀏覽器通常在頂部有一個 UI(例如導覽列及網址列等)  
而 Safari 更不同網址列在底部,這使得情況變得更加棘手。
不同的瀏覽器擁有不同大小的視窗,手機會計算瀏覽器視窗為(頂部工具列 + 文件 + 底部工具列)= 100vh。
使用 100vh 將整個文件填充到頁面上時可能導致顯示問題,因為內容可能超出視窗範圍或被遮擋。
因此,在移動設備上進行響應式設計時,應該避免使用 100vh 單位。

解決方法

解決的方法有好幾種,

JS 計算

使用 JavaScript 監聽事件,動態計算高度,缺點是效能較差,在主流的框架(EX:React)要小心觸發重新渲染的行為

1
2
3
4
5
6
const documentHeight = () => {
const doc = document.documentElement
doc.style.setProperty('--doc-height', `${window.innerHeight}px`)
}
window.addEventListener(‘resize’, documentHeight)
documentHeight()

CSS 變數

使用 CSS 變數

1
2
3
4
5
6
7
8
9
10
11
:root {
--doc-height: 100%;
}

html,
body {
padding: 0;
margin: 0;
height: 100vh; /* fallback for Js load */
height: var(--doc-height);
}

min-height 與 overflow-y

留言有人提到使用min-heightoverflow-y
但我的情境不適合,而且這個作法會產生捲軸

1
min-height: 100vh;
1
2
height: 100vh;
overflow-y: scroll;

進一步依照不同的使用情境也許我們需要 @supports

1
2
3
4
@supports (-moz-appearance: meterbar) {
/* We're on Mozilla! */
min-height: calc(100vh - 20px);
}

或是 Browser Hacks 的手法

1
2
3
4
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
/* We are on Internet Explorer! */
min-height: calc(100vh - 10px);
}

position:fixed 的作法

下面的方法可以將元素固定在畫面底部

1
2
3
4
position: fixed;
left: 0;
right: 0;
bottom: 0;

這是我最後選擇的方法,原文的討論相當精彩有趣,
但是我需要的其實是置底,而不是捲軸。
順帶一提我的專是基於 vue 與 tailwindcss,所以下面是 vue 與 tailwindcss 的寫法

1
2
3
<div class="fixed bottom-0 top-0">
<!--HERE THE FEATURES-->
</div>

參考

(fin)

Please enable JavaScript to view the LikeCoin. :P