[學習筆記] SQL 軟刪除與索引 (MySQL/MSSQL/PostgreSQL)

前情提要

最近需要實現軟刪除功能,但在設計索引時遇到了一些問題。
商務情境如下,
有效的 ACCOUNT 必須是唯一的。
然而,帳戶有可能會被軟刪除,所以被刪除的 Account 可能會有多筆相同的資料。

表格設計

1
2
3
4
5
CREATE TABLE User (
Id SERIAL PRIMARY KEY,
Username VARCHAR(255) NOT NULL,
IsDeleted BOOLEAN DEFAULT TRUE
);

簡化設計的用戶表格包含 Id、Username 和 IsDeleted 欄位。
依商業需求,當 IsDeleted 為 0 時,Username 必須唯一。
而且很有可能會有多筆相同的帳號被刪除,當 IsDeleted 為 1 時,Account 不會限制只有一筆(允許多筆)

後端工程師建議使用觸發器(Trigger)或在應用層(Backend)實現這個約束,
根據我的記憶,在微軟的 SQL Server 中,有一種稱為「條件約束」的設定,能夠針對特定條件創建索引。
可以大幅節省開發成本,我認為這類的功能不應該只有微軟專有,故作了一些搜尋後,特別以此篇記錄。

實作

我找到一個測試的網站,你可以直接在這裡測試,不需要花費額外心力建立 SQL Server

MySQL

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
-- 創建表格
CREATE TABLE Users (
Id INT AUTO_INCREMENT PRIMARY KEY,
Username VARCHAR(255) NOT NULL,
IsDeleted BOOLEAN DEFAULT FALSE
);

-- 設置 AUTO_INCREMENT 起始值為 1000
ALTER TABLE Users AUTO_INCREMENT = 1000;

-- 創建唯一索引,只針對 IsDeleted = FALSE 的行
CREATE UNIQUE INDEX unique_active_account ON Users ((CASE WHEN IsDeleted THEN Username END));
-- 插入數據
INSERT INTO Users (Username, IsDeleted) VALUES ('user1', FALSE); -- 成功
INSERT INTO Users (Username, IsDeleted) VALUES ('user2', FALSE); -- 成功
INSERT INTO Users (Username, IsDeleted) VALUES ('user3', TRUE); -- 成功,因為 IsDeleted = TRUE 不受唯一索引限制
INSERT INTO Users (Username, IsDeleted) VALUES ('user4', FALSE); -- 成功
INSERT INTO Users (Username, IsDeleted) VALUES ('user5', TRUE); -- 成功,因為 IsDeleted = TRUE 不受唯一索引限制

-- 測試重複的 Username 插入,應該失敗
INSERT INTO Users (Username, IsDeleted) VALUES ('user1', FALSE); -- 失敗,因為 user1 已經存在且 IsDeleted = FALSE
-- 測試重複的 Username 插入,但 IsDeleted = TRUE,應該成功
INSERT INTO Users (Username, IsDeleted) VALUES ('user1', TRUE); -- 成功,因為 IsDeleted = TRUE 不受唯一索引限制

-- 檢查插入結果
SELECT * FROM Users;

PostgreSQL

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
-- 創建表
CREATE TABLE Users (
Id SERIAL PRIMARY KEY,
Username VARCHAR(255) NOT NULL,
IsDeleted BOOLEAN DEFAULT FALSE
);

ALTER SEQUENCE users_id_seq RESTART WITH 1000;
-- 創建部分索引,只針對 IsDeleted = FALSE 的行
CREATE UNIQUE INDEX unique_active_username ON Users (Username)
WHERE IsDeleted = FALSE;


-- 插入範例數據
INSERT INTO Users (Username, IsDeleted) VALUES ('user1', FALSE); -- 成功
INSERT INTO Users (Username, IsDeleted) VALUES ('user2', FALSE); -- 成功
INSERT INTO Users (Username, IsDeleted) VALUES ('user3', TRUE); -- 成功,因為 IsDeleted = TRUE 不受唯一索引限制
INSERT INTO Users (Username, IsDeleted) VALUES ('user4', FALSE); -- 成功
INSERT INTO Users (Username, IsDeleted) VALUES ('user5', TRUE); -- 成功,因為 IsDeleted = TRUE 不受唯一索引限制

-- 測試重複的 Username 插入,應該失敗
-- INSERT INTO Users (Username, IsDeleted) VALUES ('user1', FALSE); -- 失敗,因為 user1 已經存在且 IsDeleted = FALSE
-- 測試重複的 Username 插入,但 IsDeleted = TRUE,應該成功
INSERT INTO Users (Username, IsDeleted) VALUES ('user1', TRUE); -- 成功,因為 IsDeleted = TRUE 不受唯一索引限制

-- 檢查插入結果
SELECT * FROM Users;

MSSQL

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
-- 設置正確的 SET 選項
SET QUOTED_IDENTIFIER ON;
SET ANSI_NULLS ON;

-- 創建表
CREATE TABLE [User] (
Id INT IDENTITY(1000, 1) PRIMARY KEY,
Username VARCHAR(255) NOT NULL,
IsDeleted BIT DEFAULT 0
);

-- 創建篩選唯一索引
CREATE UNIQUE INDEX unique_active_account ON [User](Username)
WHERE IsDeleted = 0;

-- 插入範例數據
INSERT INTO [User] (Username, IsDeleted) VALUES ('user1', 0); -- 成功
INSERT INTO [User] (Username, IsDeleted) VALUES ('user2', 0); -- 成功
INSERT INTO [User] (Username, IsDeleted) VALUES ('user3', 1); -- 成功,因為 IsDeleted = 1 不受唯一索引限制
INSERT INTO [User] (Username, IsDeleted) VALUES ('user4', 0); -- 成功
INSERT INTO [User] (Username, IsDeleted) VALUES ('user5', 1); -- 成功,因為 IsDeleted = 1 不受唯一索引限制

-- 測試重複的 Username 插入,應該失敗
INSERT INTO [User] (Username, IsDeleted) VALUES ('user1', 0); -- 失敗,因為 user1 已經存在且 IsDeleted = 0

-- 測試重複的 Username 插入,但 IsDeleted = 1,應該成功
INSERT INTO [User] (Username, IsDeleted) VALUES ('user1', 1); -- 成功,因為 IsDeleted = 1 不受唯一索引限制

-- 檢查插入結果

SELECT * FROM [User];

小結

不太需要複雜的後端程式或是 DB Trigger,只需要在建立索引時加上條件,
特別注意 MySQL 的語法是使用 CASE WHEN,其他 DB 是使用 WHERE,
這與 DB 版本也有關係,使用前應該進一步去查詢官方文件。

另外關於遞增欄位,在不同的 DB 也有不同的實作方式。
實務上通常不用了解這麼多 DB 的差異,僅僅是我個人的好奇補充罷了,
業界主推還是 PostgreSQL,我個人不夠專業,但三種都略有碰過,最熟的還是 MSSQL。
僅為個人學習記錄,如果要在三者中擇一還是需要多方考慮自身的 Context 再作決定。

參考

(fin)

[實作筆記] Bash 輸出彩色技巧

前情提要

在 Bash 腳本中,有時我需要以提高日誌的可讀性或突顯重要資訊。
通過 ANSI escape codes 改變文本的顏色、背景顏色和樣式。
可以讓腳本的輸出更加直觀且友善。

本文

在 Bash 中,我們可以使用 ANSI escape codes 來實現文本的高亮顯示和顏色選擇。
以下是一些常見的 ANSI 顏色碼:

前景顏色

  • 黑色\033[30m
  • 紅色\033[31m
  • 綠色\033[32m
  • 黃色\033[33m
  • 藍色\033[34m
  • 洋紅色\033[35m
  • 青色\033[36m
  • 白色\033[37m

背景顏色

  • 黑色\033[40m
  • 紅色\033[41m
  • 綠色\033[42m
  • 黃色\033[43m
  • 藍色\033[44m
  • 洋紅色\033[45m
  • 青色\033[46m
  • 白色\033[47m

樣式

  • 粗體\033[1m
  • 下劃線\033[4m
  • 反向\033[7m
  • 重置\033[0m

透過這些代碼,你可以靈活地控制文本的外觀。例如,使用 \033[1;31m 可以讓文本變為紅色粗體,使用 \033[0m 可以重置樣式回到預設。

示例

下面是如何使用這些顏色碼來高亮顯示 echo 輸出的示例:

1
2
3
4
5
6
7
echo -e "\033[1;31m這是一段紅色粗體文本\033[0m"
echo -e "\033[1;32m這是一段綠色粗體文本\033[0m"
echo -e "\033[1;33m這是一段黃色粗體文本\033[0m"
echo -e "\033[1;34m這是一段藍色粗體文本\033[0m"
echo -e "\033[1;35m這是一段洋紅色粗體文本\033[0m"
echo -e "\033[1;36m這是一段青色粗體文本\033[0m"
echo -e "\033[1;37m這是一段白色粗體文本\033[0m"

參考

ANSI escape codes - Wikipedia

(fin)

[實作筆記] 初體驗設定 Nvidia GPU 的 Azure VM -- 錯誤排除

前情提要

RD 們反應因為 Driver 更新導致服務異常,停止運作。
重新安裝時又遇到一些奇怪的問題,例如:
錯誤:

1
2
3
4
5
sudo apt install -y linux-modules-nvidia-550-azure nvidia-driver-550
Reading package lists... Done
Building dependency tree
Reading state information... Done
E: Unable to locate package linux-modules-nvidia-550-azure

或是

1
2
Failed to initialize NVML: Driver/library version mismatch
NVML library version: 535.183

依實際狀況與 RD 討論,發現他們沒有一個標準的作業程序,調研時也沒有任何記錄,
東作一塊,西作一塊,沒有辦法很清楚了交互的作用關係。
故由我重新實作一遍,記錄並排除相關錯誤。

實作記錄

  1. Azure 開機,指定作業系統為 Ubuntu 22.04,並且需注意 A100 系列機器只在特定的 Zone 才有資源開機,本次使用東日本 zone2 的資源

  2. 安裝 CUDA, 參考官方文件 3.9. Ubuntu

  3. 安裝 ubuntu-drivers-common

    • sudo apt install ubuntu-drivers-common
    • sudo apt install alsa-utils #音效相關,不一定要裝,但是可以減少指令時的錯誤訊息
    • ubuntu-drivers device
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    $ ubuntu-drivers devices
    == /sys/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0004:00/VMBUS:00/00000041-0001-0000-3130-444532304235/pci0001:00/0001:00:00.0 ==
    modalias : pci:v000010DEd000020B5sv000010DEsd00001533bc03sc02i00
    vendor : NVIDIA Corporation
    driver : nvidia-driver-555-open - third-party non-free
    driver : nvidia-driver-470-server - distro non-free
    driver : nvidia-driver-535-server-open - distro non-free
    driver : nvidia-driver-555 - third-party non-free recommended
    driver : nvidia-driver-535-server - distro non-free
    driver : nvidia-driver-470 - distro non-free
    driver : nvidia-driver-550 - third-party non-free
    driver : nvidia-driver-550-open - third-party non-free
    driver : nvidia-driver-535-open - distro non-free
    driver : nvidia-driver-545-open - third-party non-free
    driver : xserver-xorg-video-nouveau - distro free builtin
  4. 更新套件

    1. 加上drivers的 repo 路徑

      sudo add-apt-repository ppa:graphics-drivers/ppa

    2. 更新

      sudo apt update

    3. 安裝趨動, 讓系統自已判斷安裝什麼版本, 參考 NVIDIA drivers installation | Ubuntu

      sudo ubuntu-drivers install

    4. 重開機

      sudo reboot

    5. 查一下,他幫你裝哪個版本

      dpkg -l | grep nvidia-driver

    6. 再安裝指定版本的 Ubuntu Azure Package,也可以至 Ubuntu 網站 搜尋確認

      sudo apt install -y linux-modules-nvidia-<version>-azure

確認是否成功

1
nvidia-smi

 看到下面的畫面就是成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ nvidia-smi
Tue Jul 30 08:07:18 2024
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.90.07 Driver Version: 550.90.07 CUDA Version: 12.4 |
|-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA A100 80GB PCIe Off | 00000001:00:00.0 Off | 0 |
| N/A 32C P0 43W / 300W | 1MiB / 81920MiB | 0% Default |
| | | Disabled |
+-----------------------------------------+------------------------+----------------------+

+-----------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=========================================================================================|
| No running processes found |
+-----------------------------------------------------------------------------------------+

參考

(fin)

[實作筆記] 初體驗設定 Nvidia GPU 的 Azure VM

前情提要

隨著深度學習和 AI 的普及,許多工作和研究需要強大的運算能力,
而 GPU 提供了相較於傳統 CPU 更高效的計算能力。
因此,我選擇在 Azure 上設定 Nvidia GPU 虛擬機來滿足這些需求。
這篇文章將分享我在 Azure 上設定 Nvidia GPU 虛擬機的初體驗,並記錄實作過程中的一些重點。

前置作業

在開始設定 GPU 虛擬機之前,需要先完成以下準備工作:

  1. Azure 帳戶:確保你已經擁有 Azure 的帳戶,並且帳戶中有足夠的配額來創建 GPU 虛擬機。
  2. 選擇適合的虛擬機規格:Azure 提供多種 GPU 虛擬機型號,如 NV 系列和 NC 系列,根據需求選擇合適的型號。
  3. 安裝 Azure CLI:透過 Azure CLI 可以更方便地管理和配置虛擬機,可以在本地環境中安裝並配置 Azure CLI。
  4. 創建資源群組與虛擬機
1
az group create --name <myResourceGroup> --location eastus
1
2
3
4
5
6
7
az vm create \
--resource-group myResourceGroup \
--name myT4VM \
--image UbuntuLTS \
--size Standard_NC6s_v3 \
--admin-username azureuser \
--generate-ssh-keys

實作步驟

可以參考官方手冊),
本文大量引用 3. Package Manager Installation 中 3.9 的篇幅

登入到虛擬機後,安裝 Nvidia 驅動程式

1
2
3
sudo apt-get update
sudo apt-get install -y nvidia-driver-470
sudo reboot

安裝當前運行的內核版本所需的 Linux 標頭文件

1
sudo apt-get install linux-headers-$(uname -r)

刪除過時的金鑰(實作上,跟本沒有這個金鑰,所以跳過也沒關係)

1
sudo apt-key del 7fa2af80

查詢作業系統與晶片架構

1
2
3
4
5
6
7
8
> lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 22.04.4 LTS
Release: 22.04
Codename: jammy
> uname -m
x86_64

安裝 CUDA-Keyring

1
wget https://developer.download.nvidia.com/compute/cuda/repos/$distro/$arch/cuda-keyring_1.1-1_all.deb

參考前一步驟將 $distro 換成 ubuntu2204$arch 換成 x86-64,
也可以直接到此 https://developer.download.nvidia.com/compute/cuda/repos 找到你合適的 deb 檔
下載:

1
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86-64/cuda-keyring_1.1-1_all.deb

安裝:

1
sudo dpkg -i cuda-keyring_1.1-1_all.deb

安裝 CUDA SDK

1
sudo apt-get install cuda-toolkit

安裝 Nvidia GDS 驅動,提升 GPU 和存儲間的高效數據傳輸性能

1
sudo apt-get install nvidia-gds

重啟主機

1
sudo reboot

確認是否安裝成功

1
nvidia-smi

 看到下面的畫面就是成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
> nvidia-smi
Mon Jul 1 09:21:24 2024
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 535.183.01 Driver Version: 535.183.01 CUDA Version: 12.2 |
|-----------------------------------------+----------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+======================+======================|
| 0 Tesla T4 Off | 00000001:00:00.0 Off | Off |
| N/A 31C P8 9W / 70W | 140MiB / 16384MiB | 0% Default |
| | | N/A |
+-----------------------------------------+----------------------+----------------------+

+---------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=======================================================================================|
| 0 N/A N/A 1095 G /usr/lib/xorg/Xorg 130MiB |
| 0 N/A N/A 1356 G /usr/bin/gnome-shell 7MiB |
+---------------------------------------------------------------------------------------+

20240730 更新

追加異常記錄

參考

(fin)

[實作筆記] 建立 Azure VM 的快照(Snapshot)

前情提要

這次記錄實作 Azure VM 的快照(Snapshot)與還原,與 VM images 不同,
快照是針對磁碟的即時捕捉,而不是整個虛擬機器的範本。
VM images 用於創建新的虛擬機器,是在固定的狀態下保存整個 VM,
包括操作系統、應用程式和所有配置。

相比之下,快照則僅保存特定磁碟的狀態,適合於快速還原或備份目前正在運行的 VM。
選擇快照主要因為它的快速性和簡便性,可以在短時間內恢復 VM 到指定狀態,
以我的例子來說,我因為 AI 需求建了昂貴的 Azure VM ,使用 Image 會會佔用我們的 Quotas。
所以 Snapshot 會是較好的還擇

前置作業

在開始建立快照之前,請確保以下條件已經準備妥當:

  1. 已經擁有一個可操作的 Azure 帳戶並能夠登入 Azure 入口網站。
  2. 目標 VM 已經啟動並運行正常,且確定需要為其建立快照。
  3. 已經分配足夠的儲存空間來保存快照數據。

實作步驟

建立 Snapshot

  1. 登入 Azure 入口網站 並進入虛擬機器(Virtual Machines)頁面。
  2. 從虛擬機器列表中,選擇您想要建立快照的 VM。
  3. 在左側導航欄中,找到並點擊 “磁碟(Disks)”。
  4. 在磁碟頁面中,選擇想要建立快照的磁碟(通常是 OS Disk)。
  5. 點擊上方的 “快照(Snapshot)” 按鈕,進入快照配置頁面。
  6. 在快照配置頁面中,輸入快照名稱,並選擇儲存帳戶和資源群組。
  7. 檢查所有設定無誤後,點擊 “建立(Create)” 按鈕,開始建立快照。這個過程可能需要幾分鐘。
  8. 快照建立完成後,就可以在資源群組或儲存帳戶中找到該快照,並隨時使用它來還原 VM 的狀態。

從 Snapshot 到 OS Disk 到 VM

  1. 在 Azure 入口網站中,進入 “快照(Snapshots)” 頁面,選擇您之前建立的快照。
  2. 點擊快照名稱進入詳細資訊頁面,然後選擇 “建立磁碟(Create Disk)”。
  3. 在磁碟配置頁面中,輸入磁碟名稱,並選擇合適的儲存帳戶和資源群組。
  4. 檢查所有設定無誤後,點擊 “建立(Create)” 按鈕,開始將快照轉換為磁碟。
  5. 磁碟建立完成後,進入 “磁碟(Disks)” 頁面,選擇您剛建立的磁碟。  
  6. 在磁碟詳細資訊頁面中,點擊上方的 “建立 VM(Create VM)” 按鈕。
  7. 在虛擬機器配置頁面中,完成其他配置,如名稱、大小、網路設定等,然後點擊 “檢閱 + 建立(Review + create)” 按鈕。  
  8. 檢查所有設定無誤後,點擊 “建立(Create)” 按鈕,開始建立新的虛擬機器。  
  9. 新的虛擬機器建立完成後,登入並驗證其狀態是否與快照拍攝時一致。

  

問題

我發現使用 Security Type 為 Trusted Launch 的 VM 所建立的 Snapshot;  
與其建立的 Disk 與 VM 其 Security Type 也會是 Trusted Launch。  
但是當我嚐試透過 SSH using Azure CLI 連線 VM,
會遇到無法連線的問題,同樣的步驟,Security Type 為 Standard 就不會有問題。  
待確認原因…  

參考

  1. Microsoft 官方文件: 建立和管理虛擬機器磁碟的快照
  2. Azure 入口網站指南
  3. Azure 虛擬機器文檔

(fin)

[生活筆記] 2024 常用工具整理

輸入法

  • 無蝦米 (加速打字,未來不太看好,考慮更換中…)

網站

資訊收集/廢文發佈/社群媒體

觀察名單(待汰換)

  • Slant 工具比較
  • 文字比較工具 - Win Merge(Window限定)
  • Evernote
    • 專案分類
    • 職涯規劃
    • 已完成的項目

Macbook 工具

  • Git GUI
    • Fork
  • KeyCastr
    • 顯示鍵盤點擊歷程
  • 跨 PC 存放檔案
    • Dropbox
  • 截圖錄影工具
    • QuickTime
  • Terminal

筆記工具

  • Notion
    • GTD
    • 周記劃
  • HackMD
    • 暫存的記錄
    • 會議/社群活動即時記錄
    • Blog 草稿
    • 共筆
  • Blog
    • 技術實作記錄
    • 社群活動記錄
  • NotebookLM
    • RAG AI 整合筆記

瀏覽器 & 外掛

都使用 Chromium 內核相關

  • Brave
  • Chrome
  • Edge
  • FireFox
  • Arc 觀察中

Chrome

  • tampermonkey
    • 撰寫網頁小工具
  • One Tab
    • 快速收攏大量分頁,常用在特殊主題搜尋的暫存
  • Trancy
    • 沉浸式翻譯與影片翻譯
  • Bitbucket Diff Tree
    • Bitbucket PR 差異比較
  • JSONView
    • 當 response 為 json 時更為好讀
  • Bitwarden
    • 密碼管理
  • Wappalyzer
    • 分析網站使用的框架與技術
  • cVim
    • 使用 Vim 的習慣操作網頁

開發工具

  • Vim
  • VSCode
  • JetBrain 全系列
  • Docker Desktop

(fin)

[實作筆記] Azure Communication Services email

前情提要

隨著現代企業在數位轉型中的步伐加快,電子郵件仍然是最重要的溝通工具之一。
公司現在的寄信服務採取了一個特別的解決方案,透過 App registrations 進行郵件發送。
實作的細節不展開,但這樣作有幾個問題:

  • App registrations 首次需人工授權取得 refresh_token
  • 有了 refresh_token 仍需交換到 access_token 才能寄信
  • 更新 refresh_token 失敗時,就需要人工重新介入

而 Microsoft Azure 提供了強大的 Email Communication Service,可以幫助企業輕鬆、有效地發送大量電子郵件。
優點有整合簡單、高可擴展、安全性高、可靠性強等等優點…

本篇文章將記錄我如何在 Azure 中實作 Email Communication Service。

實作步驟

  1. 建立 Azure Communication Services 資源
    首先,登入 Azure 入口網站,然後依序進行以下步驟:
    點選「建立資源」按鈕,搜尋並選擇「Communication Services」。
    點選「建立」按鈕,填寫必要的資訊如資源名稱、訂閱和資源群組等。
    選擇地區並設定其餘選項後,點選「檢閱 + 建立」,檢查設定並點選「建立」。

  2. 配置電子郵件通道
    在 Communication Services 資源建立完成後,需要進行電子郵件通道的配置:
    在資源概覽頁面中,找到並點選「Email」。
    點選「新增郵件域」,並按照提示設定 SMTP 資訊及其他相關設定。
    驗證郵件域並完成配置。
    這裡你需要有 domain 管理者的權限,用來在 DNS Records 建立相關的記錄(CNAME、TXT)

  3. 生成 API 金鑰
    接下來,我們需要生成 API 金鑰,以便應用程式能夠通過此金鑰進行認證和發送電子郵件:
    在 Communication Services 資源頁面中,找到並點選「密鑰」。
    點選「生成/管理密鑰」,生成新的 API 金鑰並保存。

  4. 發送電子郵件
    有了 API 金鑰和配置好的電子郵件通道,現在可以使用 Azure 提供的 SDK 或 REST API 發送電子郵件。
    以下範例展示了如何使用 Nodejs 發送郵件:

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
import { EmailClient, type EmailMessage } from '@azure/communication-email'
//...
sendMail = async (req: Request, res: Response): Promise<void> => {
const connectionString = env('COMMUNICATION_SERVICES_CONNECTION_STRING')
const senderAddress = env('EMAIL_SENDER_ADDRESS')
const client = new EmailClient(connectionString)
const { to, subject, html } = req.body
const attachments = (req.files != null)
? (req.files as Express.Multer.File[]).map(file => ({
name: file.originalname,
contentType: file.mimetype,
contentInBase64: file.buffer.toString('base64')
}))
: []
const emailMessage: EmailMessage = {
senderAddress,
content: {
subject,
html
},
attachments,
recipients: {
to: [{ address: to }],
bcc: [{ address: '[email protected]' }]
}
}

const poller = await client.beginSend(emailMessage)
const result = await poller.pollUntilDone()
console.log('result:', result)
res.status(200).json({ message: `Email ${subject} Sent!` })
}

在 Azure Portal 執行整合測試

在 Azure Portal > Communication Service > Email > Try Email,
非常貼心的提供了 C#、JavaScript、Java、Python、cUrl 的範本,
也可以取得連線字串。

使用上限與新增寄件帳號

原則上預設的使用量對開發人員來說,是非常足夠的
但是如果想增加上限,或是新增其它的寄件者帳號,需要開 support ticket 進行升級,
這是為了避免郵件的濫用,另外需新增 MX Record 可以避免當成垃圾郵件。
升級後就可以在 Azure Portal > Email Communication Service > Provision domains > MailFrom addresses
新增寄件者,實際上 Azure Communication Service 只會寄件無法收件,
即使在 O365 有相同的帳號,在寄件備份中也看不到透過 ECS 寄出的郵件。  

小結

實作上比較有風險大概是 DNS Records 的設定,最久大約需等到 1 小時生效。
而開發上非常的容易,甚至程式範例都整合到 Portal,非常方便。  
但是其它方面需要開票等待 Azure 協力升級與設定,就會比較麻煩。  

參考

(fin)

[踩雷筆記] Gitlab CI/CD 與 GCP - 靜態網站部署整合 404 與 `/`

前情提要

最近將公司的所有靜態網站轉換到 GCS 上了,遇到一些情境,特此記錄
這篇會以 Vue 與 Nuxt 的角度出發。
在前後端分離的情況下,SEO 變成一個問題,SSR 可以解決這個問題,
我們可以使用 Nuxt 建立我們的專案,並透過 generate 生成靜態檔案。

再進一步來說,我們可以整合 GCS 進行靜態網站的部署

題外話

Nuxt 的幾種建置方式

  1. npx nuxt build
    The build command creates a .output directory with all your application, server and dependencies ready for production.
    這個命令會建立一個 .output 目錄,裡面包含了你的應用程式、伺服器和所有必要的依賴,準備好用於生產環境。
    預設的行為,可以實作 SSR(Server Side Render),適用有 SEO 需求,且變化快速的網站,Ex: 電商產品頁

  2. npx nuxt build --prerender
    –prerender false Pre-render every route of your application. (note: This is an experimental flag. The behavior might be changed.) .
    –prerender false 會對應用程式的每個路由進行預渲染。(注意:這是一個實驗性的標誌。行為可能會更改。);
    它會先產生好 HTML,適合 SSG 網站(內容不常改動,但有 SEO 需求)。Ex: BLOG

  3. npx nuxt generate

The generate command pre-renders every route of your application and stores the result in plain HTML files that you can deploy on any static hosting services. The command triggers the nuxi build command with the prerender argument set to true
這個命令會對你的應用程式的每個路由進行預渲染,並將結果存儲在普通的 HTML 文件中,你可以部署到任何靜態托管服務上。  
該命令會觸發 nuxi build 命令,並將 prerender 參數設置為 true。
它適合純靜態的網頁(SPA)。Ex: Landing Page. 與前者最大的差異是,前者會建置成不同的 HTML,

問題

當我們在建置好靜態網站並部署到 GCS 上時,我遇到了一個異常的問題,
當我複製貼上網址時會發生 404 Not Found,主因是當我的網址結尾不是/時,
瀏覽器會轉導到 /index.html
舉例說明:

https://marsen.me/sample 複製貼上,會轉導到 https://marsen.me/sample/index.html
而這頁不存在,導致產生 404 的錯誤

解法

採取升級 Nuxt 到 v3.8.0 以上,
這是實驗性的功能,雖然目前有效,仍需觀注未來更新的狀況。
若不打算升級,另外有用 middleware 處理重定向,
可參考此 issue 的討論串,
要寫比較多,而且留言者在部署到 vercel 有遇到其他問題。故採升級的方式解決此題。

參考

(fin)

[實作筆記] 清理 CI/CD (Gitlab-runner)

寫在前面

NM;DR
沒有什麼意義,不需要讀這篇

緣由

  • CI/CD 執行失敗,檢查錯誤發現是 Gitlab-runner[*1] 主機空間不足。

處理歷程

  • 連線 Gitlab-runner 主機
  • 查詢空間使用狀況,找到佔磁區的資料

    marsen@gr00:~$ sudo du -h --max-depth=1 /
    24K /tmp
    0 /sys
    74M /boot
    … skip …
    24K /root
    94G /var
    97G /

  • 用指令作深層的搜尋 sudo du -h --max-depth=3 /var
  • 發現 docker 的問題最大

    252K /var/lib/docker/containers
    4.0K /var/lib/docker/trust
    4.0K /var/lib/docker/runtimes
    91G /var/lib/docker

  • 不選擇清理磁碟,而用 docker 指令檢查 docker images
  • 批次刪除指令 docker images -f "dangling=true" -q | xargs docker rmi
  • 再次檢查

    marsen@gr00:~$ sudo du -h --max-depth=1 /
    24K /tmp
    0 /sys
    74M /boot
    … skip …
    24K /root
    16G /var
    20G /

後續

  • 可能要建立一個排程定期去清理 CI/CD 無用的 image
    • 可以的話想整入 CI/CD 作業中,但在 DinD[*2] 的環境不確定怎麼實作
  • log 與 cache 也有看到較高的資料成長曲線,未來也要納入評估

20240829 更新設置定期清理腳本

建立 cleanup.sh,並將其放置在 root 根目錄下:

1
2
sudo cp /path/to/cleanup.sh /root/
sudo chmod +x /root/cleanup.sh

cleanup.sh 的內容:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash

# 清理未使用的 Docker 資源
sudo docker system prune -a --force --volumes

# 清理未使用的 Docker 卷
sudo docker volume prune --force

# 縮減系統日誌大小
sudo journalctl --vacuum-time=30d

設置排程

以 root 用戶創建定期排程以每月執行清理腳本:

打開 cron 編輯器:

1
sudo crontab -e

在 cron 編輯器中添加以下行:

1
0 0 1 * * /root/cleanup.sh > /root/cleanup.log 2>&1
  • 0 0 1 * *:每月的第一天午夜執行。
  • /root/cleanup.sh:執行清理腳本。
  • /root/cleanup.log 2>&1:將輸出和錯誤重定向到 /root/cleanup.log。

驗證和檢查

手動運行清理腳本以驗證它是否按預期工作:

1
sudo /root/cleanup.sh

查看 cron 日誌

檢查 cron 日誌以確保定期任務已成功運行:

在 Ubuntu/Debian 系統上:

1
sudo grep CRON /var/log/syslog

使用 journalctl(如果使用 systemd):

1
sudo journalctl -u cron

註解

  1. Gitlab-Runner 是 Gitlab Solution 中執行工作的實體,可以是多台,此案只有單台
  2. Dind: Docker In Docker,如同字面意思,在 Docker 中跑 Docker,是 Gitlab-runner 的實作方法之一

(fin)

[實作筆記] Gitlab CI/CD 與 GCP - Cloud Run 方案選擇過程記錄

前情提介

在我的實務經驗上,很常與新創團隊合作,而架構選擇常常是第一個問題,
最常見的解法是,由技術負責人選擇最熟悉的技術…
這樣真的好嗎?
比如說,T 社早期由某群工程人員開發,
選擇了 Go、Nodejs、C# 等多語言與雲端 GKE(K8S) 作為開發架構,
事後不負責任離職後,對持續的運作與維護都造成了困難
這件事對我的警鐘是 — 作為管理者,如果不具備識別專業的能力,就只能透過信賴某人或團隊;這是非常脆弱且危險的。

執行環境的選擇

作為 Web 開發者,最常用的執行環境由簡入繁如下:

地端到雲端到

  • 開發機就是正式機: 通常只是用來展示,開發者最常這樣作
  • 地端的主機: 沒有技術能力公司,很有可能都是這樣的架構,直接向開發商買主機放機房或是由開發商部署
  • 地端的虛擬機(VM): 會用較高規格的機器部署多台 VM,可以更有效使用資源
  • 雲端的虛擬機(VM): 使用上與上面差不多,但是由大廠來維持高可用性,學習門檻低
  • 雲端的 Server Less: 需要熟悉不同雲的相關產品,算是有點門檻,完成後可以精準控制預算、減少人力成本、透過雲維持高可用性
  • 雲端的 K8s: 門檻較上面更高,也有相同的優點,但是會有更高的可控性,適合有一定使用規模後,需要細部彈性調整資源的公司。
  • 再回地端: 如果規模再繼續成長,雲端的成本變得高不可攀時,或控制力下降,在有一定的技術水準情況下,有的公司會選擇回到地端。參考 37signals 的例子

GCP Cloud Run

回歸本文,我選擇了 GCP Cloud Run,這是一種雲端的 Server Less 的解決方案。
產品的規模沒有大到需要 K8s,團隊成員的具備足夠的能力,
類似的方案還有 Cloud Function,
作為一個 Web API Base 的輕量專案,我評估 Cloud Run 更適合
Cloud Function 較適合 Event Driven 的片段行為
Cloud Run 較適合有點複雜度,但是可以容器化的應用。

相關作業

首先準備好我的程式,這是一個透過 Azure 寄信程式,需要在 Azure Registered APP 設定,
基本的讀取組態、寫 Log 到雲端與錯誤處理(Error Handle)、相依注入與測試等…
其中一個功能需要將 token 存儲在 GCS(gcloud storage) 裡,
並透過 Cloud Scheduler 定期更新,所以需要為其提供必要的權限與帳號(GCP Service Account).

使用 Cloud Run 前,Docker 也是必要的前置知識,
你會需要撰寫 Dockerfile ,並且需要在 GCP 上建立一個 Artifacts Registry
如此一來,就可以透過 Cloud Build 建立並部署 Cloud Run
用 GCP Workload Identity Federation 管理 Service Account
管理的對像有 2 個,CI 的 Service Account 與執行程式的 Cloud Run 的 Service Account.

後續有考慮透過 Cloud Run 提供 Swagger 之類的 API 文檔,但現階段先共享 Postman 資訊處理。

20240427 更新

我的 CI 腳本如下

1
2
3
export CLOUDSDK_AUTH_ACCESS_TOKEN={provider by workload identify federation}
gcloud builds submit --tag your-registry/your-app:latest .
gcloud run services update your-app --image=your-registry/your-app:latest

我的 Gitlab Runner 是透過 GCP 上 A Project 的 VM 建立並註冊的,
而我的 Cloud Run 需要部署並運作在 GCP B Project 上,
預設執行的身份會是 AProj 的 Compute Engine default service account,以下簡稱 A_CE_Account,
CLOUDSDK_AUTH_ACCESS_TOKEN 代表的是 B Project 的 Gitlab Runner Service Account,以下簡稱 B_GR_Service_Account,
另外在運作 B Project 的 Cloud Run 需要的是 B Project 的 Cloud Run Service Account,以下簡稱 B_CR_Service_Account,

Gitlab Runner 在執行時,執行 gcloud auth list 會顯示 A_CE_Account
但是由於有設定 CLOUDSDK_AUTH_ACCESS_TOKEN,所以相關命令的執行身份是 B_GR_Service_Account
這會有足夠的權限執行 gcloud builds submit --tag your-registry/your-app:latest .
但是沒有權限執行 gcloud run services update your-app --image=your-registry/your-app:latest
原因是 Cloud Run 的執行身份是 B_GR_Service_Account
為此,我們需要賦予 B_GR_Service_Account 角色 Service Account User

參考關鍵字

參考

(fin)

Please enable JavaScript to view the LikeCoin. :P