建構多平台的 container image

前言 最近在搞 side project 時,常常需要在不同的 CPU 架構上跑我的應用程式。例如,開發用的 MacBook 是 ARM 架構 (Apple Silicon),開發用的 Desktop 是 x86 架構(Ryzen 5900X),而部署的伺服器可能是 x86 (AMD64),有時候甚至想在 Raspberry Pi (ARM) 上跑些小東西。每次都要為不同平台分別建構 image 實在有點麻煩,而且 Registry 上一堆 xxxapp-amd64, xxxapp-arm64 的 tag 看了也很礙眼。經過一番研究與嘗試,是時候接觸 Docker Buildx 了。 為什麼需要多平台映像檔 在 wintel 的商業策略下,以及大家對高性能伺服器的普遍認知,主要用 x86/amd64,但現在 ARM 架構越來越普及,從 Apple Silicon 的 Mac、AWS Graviton 處理器、各種 IoT 設備到你的 Raspberry Pi,ARM 無所不在。如果你的 container image 只支援 amd64,那它就無法在這些 ARM 設備上原生運行 (需要模擬,效能差)。為了Build Once, Run Anywhere,多平台映像檔 (Multi-platform images) 就是 meta。 OCI 多平台映像檔架構簡述 其實不複雜。傳統的單一平台 image,它的 manifest 指向一組設定檔和一堆 layer。而多平台 image 則是透過一個 manifest list (索引) 指向多個特定平台的 manifest。每個特定平台的 manifest 才各自指向該平台的設定檔和 layer。...

2025-03-30 · 12 min · 2404 words

使用 go work 在本地開發解決同時開發 module 的問題

在 Golang 1.18 中 go workspace 的提案釋出後,golang 的官方文件或多或少也提到應該要怎麼做 multi module 的開發。相較於過去需要不斷的替換 go.mod 內的 replace 指令,go work 大幅改善了 multi module 的開發體驗。 為什麼需要 go work 專案逐漸變大 當你在維護一個小工具 side project 時,單一的 module 就能夠滿足所有需求,但當專案逐漸變大,會需要將專案拆分成多個 module。 可以透過一個例子來理解: 假設我們有一個專案,拆分成以下兩個 module: common-lib-golang: 存放所有專案都會用到的 function,例如 retry, logger, tracer 等等 backend: 實際提供 http api 的程式碼 最開始的檔案架構為: . ├── backend │ ├── go.mod │ ├── go.sum │ └── main.go └── common-lib-golang ├── go.mod ├── go.sum └── util.go 隨著專案變大,我們在開發 backend 時,時常會需要修改 common-lib-golang 內的程式碼,這時候就需要 go work 來協助我們。...

2025-02-20 · 1 min · 208 words

在習慣 go mod 後重新學習 git submodule

前言 使用 Golang 作為主要開發已經有五年的時間。最近因工作需要接觸到 Python 專案,並且該專案使用 git submodule 的方式來引用共同函式庫 common-lib-python。對於長久使用 Golang 的 go mod 的我來說,submodule 是一個相對陌生的概念。藉這個機會撰寫一篇文章,整理並紀錄一下 git submodule 的用法,也作為未來的參考。 這篇文章會假設已經對 git 的基本操作有一定程度的了解,並著重在 submodule 的概念、使用情境以及與 Golang 的 go mod 的差異比較。文章內容會以下列流程來呈現: 建立一個新的 Python 專案 my-python-repo 建立一個 Python 模組 my-python-module 作為 submodule 在 my-python-repo 中使用 my-python-module 作為 submodule 模擬需求變更,同時修改 my-python-repo 與 my-python-module,並分別發送 PR 與 Golang 的 go mod 進行比較 建立 Python 專案與模組 先建立兩個新的 git repo,分別是 my-python-repo 與 my-python-module。 # 建立 my-python-repo mkdir my-python-repo cd my-python-repo git init touch main....

2025-01-24 · 3 min · 548 words

Golang Composition over Inheritance

Golang 是一門簡潔有力的程式語言,相較於其他程式語言,更傾向於使用組合(composition)而不是繼承(inheritance),語言設計之初更是沒有提供繼承的關鍵字,這種設計哲學讓 Golang 在現代軟體開發中脫穎而出。 繼承固然有其優點,但在建構複雜的物件關係時,容易產生過於龐大的繼承層級結構。這使得程式碼難以閱讀和維護,就像是一棵盤根錯節的大樹,牽一髮而動全身。 過深的繼承層級會導致知名的「脆弱基類問題」(fragile base class problem),使得程式碼難以修改和擴展。 組合則不同,它鼓勵建立小型、專注的 struct,然後像樂高積木一樣,將這些 struct 組合成更大的結構。這種方式讓程式碼模組化,更容易理解和修改。 彈性 Golang 的 type system 支援我們靈活地組合各種 struct。可以建立一個新的 struct,並在其中「嵌入」其他 struct 作為其欄位。 type Car struct { make string model string year int } type Driver struct { name string car Car } func main() { myCar := Car{"Toyota", "Camry", 2020} driver := Driver{"John", myCar} fmt.Println(driver.name) // 輸出: John fmt.Println(driver.car.make) // 輸出: Toyota } 在這個例子中,Driver 透過組合 Car 來建立更豐富的資料結構。Driver 「has-a」 Car,而不是 「is-a」 Car,這提供了更高的彈性,讓 Driver 可以更專注在自身的邏輯。...

2025-01-11 · 3 min · 634 words

Golang 1.22 中 http routing 的改進

Golang 作為一個偏向 server 應用的程式語言,一般的 web server 並不會直接使用原生的 package net/http,而更多的使用 gin-gonic/gin 或是 gorilla/mux,後來也有 labstack/echo 以及 go-chi/chi 等等選擇,在效能、輕量、好維護、好擴充中,都能找到對應的 third party package,其中的原因不外乎是原生的 package 提供的功能過於簡潔。 好在 1.22 中,官方改進了 net/http 中對於多工器、路由,甚至出了一篇部落格,現在更可以「大膽的」直接使用 standard library。 Path Parameter 若要將應用的 Web API 定義成 RESTful,我們會使用 /資源/{資源唯一識別符}/子資源/{子資源唯一識別符} 來定義路徑。假如要獲取一個使用者的訂單,則會使用 GET /users/1/orders 來獲取。在 1.22 以前,我們只能定義到 /users,再自行解析往後的 path: http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { subPath := strings.TrimPrefix(req.URL.Path, "/users/") if len(subPath) == 0 { xxx } else { ooo } ... }) 而在 1.22 中新增了 net....

2024-10-30 · 4 min · 656 words

使用 TinyGo 與 Raspberry pi pico 實現溫濕度感測器

前言 作為嵌入式系統學習的微小專案,我決定使用 TinyGo 來實現一個簡單的溫濕度感測器。過去我大多使用 Arduino 作為微控制器(MCU)開發平台,但這次想嘗試使用 TinyGo 來進行單片機的學習。 TinyGo 是 Go 語言的一個子集,專門針對小型設備和微控制器進行了優化,使得我們可以在資源受限的硬體上運行 Go 程式。 全部的程式碼都可以到 omegaatt36/pico-bme280 中找到。 硬體選擇 Raspberry Pi Pico 我選擇了 Raspberry Pi Pico 作為本專案的主控板。Pico 是一款基於 RP2040 晶片的微控制器開發板,具有以下特點: 雙核 ARM Cortex-M0+ 處理器,時脈可達 133 MHz 264KB 的 SRAM 和 2MB 的板載閃存 支援 USB 1.1 主機和設備功能 低功耗睡眠和休眠模式 可編程 I/O(PIO)狀態機 30 個 GPIO 引腳 便宜 價格僅需要 5 美元,後繼款的 Pico 2 仍然維持 5 美元,還額外增加了 RISC-V 架構的支援,可以一次玩到兩種處理器架構。 Pico 的這些特性使其非常適合用於各種嵌入式專案,包括我們的溫濕度感測器。 BME280 感測器 最初我購買了 BMP280 感測器,但後來發現它只能測量溫度和氣壓,無法測量濕度。因此,我轉而選擇了 BME280 感測器,它可以同時測量溫度、濕度和氣壓。...

2024-10-02 · 2 min · 337 words

透過內建的 pprof 工具來分析 Golang 發生的 memory leak

前言 某天下午,公司的 cronjob daemon 無預警的被 GCP OOM Kill 了,且程式碼沒有看出明顯的原因。 根據過去的經驗,local 開發時會使用 go tool pprof 來分析 CPU profile 或是 memory 與 trace 的問題,詳細可以參考 Go 官方文件。 由於我們的程式碼是一個基於 gin 的 http service,因此可以使用 gin 提供的 pprof 來快速建立 endpoint。 gin pprof gin 的 pprof package 提供了數個基於 net/http/pprof 的 endpoint,可以分別為: /: 基本的 pprof 的 static page,可以分析 CPU 與 memory 的問題。 /cmdline: 分析 command line 的問題。 /profile: 分析 CPU profile 的問題,可以透過 query string 來指定 CPU profile 的 duration。 /symbol: 分析 symbol table 的問題。 /trace: 分析 trace 的問題。 /goroutine: 分析 goroutine 的問題,這也是本文中重點查看的 endpoint。 /heap: 分析 heap 的問題。 可以在 http server 中加入以下程式碼來啟用 pprof:...

2024-08-17 · 3 min · 441 words

基於 Golang 的 Grafana Dashboard 與 JWT 認證的前後端實作

在工作上有一個需求是需要做一些 OLAP,原訂計畫是使用 Google Looker(ver. Google Cloud Core),礙於量小不符合經濟效益,決定用 Grafana 這個較熟悉的開源套件來幫助我們做視覺化的處理。 這篇文章的範例可以在 omegaatt36/grafana-embed-example 中找到所有的 source code 我的 Use Case 為已經有一組 SHA512 產生的 Key,以下的內容為使用 HS512 進行簽名與認證。 流程 sequenceDiagram autonumber participant U as User participant B as Browser participant I as iframe participant S as Server participant G as Grafana rect rgb(236,239,244) U->>B: Open Web Page B->>S: Request JWT Token S->>B: Return JWT Token B->>I: Load iframe I->>G: Request Dashboard with JWT Token G->>I: Return Dashboard I->>B: Display Dashboard in iframe B->>U: Show Dashboard end Grafana 配置 主要是針對 grafana....

2024-06-10 · 5 min · 964 words

用 testcontainers 在本地開發 Go 應用程式

介紹 使用 testcontainers 是在本地開發 Golang 應用程式的一個高效方式。這可以讓我們在不需要依賴外部環境的情況下,模擬應用程式在實際生產環境中的運行狀況。 安裝 testcontainers 在 Go 專案中,我們可以通過以下指令來導入 testcontainers go get github.com/testcontainers/testcontainers-go 透過 Redis 實踐一個 rate limiter package user type Limiter struct { client *redis.Client limit int limitPeriod time.Duration // 1 hour for limitPeriod counterWindow time.Duration // 1 minute for example, 1/60 of the period } func NewLimiter(client *redis.Client, limit int, period, expiry time.Duration) *Limiter { return &Limiter{ client: client, limit: limit, limitPeriod: period, counterWindow: expiry, } } func (r *Limiter) AllowRequest(ctx context....

2024-05-19 · 3 min · 474 words

如何利用 Open Policy Agent 配合 Golang 建構彈性的 RBAC 模組

RBAC 概念簡介 在我們探討如何利用 Open Policy Agent (以下簡稱 OPA) 和 Golang 建立一個彈性的 RBAC 模組之前,先讓我們來了解一下 RBAC的基本概念。 RBAC(Role-Based Access Control,基於角色的訪問控制)是一種廣泛應用的訪問控制策略,在軟體安全性領域尤為重要。其核心思想是將系統訪問權限與用戶的角色(職位、責任或職務)關聯起來,而不是直接與個別用戶關聯。這意味著訪問權限被捆綁到角色上,然後將用戶分配給這些角色。舉個例子,一個「管理員」角色可能有權訪問系統的所有資源,而「員工」角色則只能訪問特定部分的資源。 RBAC 的主要優勢在於其靈活性和簡化的權限管理。當需要變更權限時,只需修改角色的訪問權限,而不需要為每個用戶單獨設定。這不僅使權限管理更為高效,也減少了錯誤配置的可能性,提高了整體的系統安全性。 在實踐中,RBAC 允許創建精細且靈活的策略,以滿足複雜的商業和安全需求。無論是大型企業還是小型團隊,RBAC 都提供了一個可靠的框架,來確保正確的用戶擁有適當的訪問權限,從而保護關鍵資源免受未授權訪問。 可以參考 Cloudflare 的文章,簡單來說就是什麼「角色」能夠對什麼「資源」做什麼「操作」。 Open Policy Agent (OPA) 介紹 OPA 是一個「Strategy as Code」的開源專案,專門設計用於統一地管理和執行跨不同系統的策略。它不僅提供了一個高級的策略語言——Rego,還支援將策略作為代碼與應用程式的其他部分一同存儲、版本控制和部署。OPA 的這種設計使其能夠輕鬆集成到微服務、Kubernetes、CI/CD 管道、API 網關等多種環境中。顯著特點是其策略的編寫方式。Rego 是一種專門為策略和規則定制的查詢語言,它使開發者能夠以聲明式方式描述策略和規則,從而確保這些策略既容易理解又易於維護。這對於建立複雜的 RBAC 系統尤為重要,因為它允許策略的靈活性和可擴展性,同時又保持了清晰和易於審查的結構。 我們可以利用 OPA 提供的 API 來評估和執行這些策略。這意味著開發者可以在 Golang 程式碼中直接嵌入策略判斷的邏輯,從而實現動態、細粒度的訪問控制。這種方法的一個優點是,它支援在 runtime 動態更新策略,或是編譯進 binary,從而提供更大的靈活性和即時性。 整合 OPA 與 Golang 透過官方案例來了解如何使用 參考了 OPA 官方的 rbac 章節,並加以修改。使用最簡單的例子: admin can read user bob is admin ------------------- bob can read user 轉化成 RBAC 模型即為:...

2024-04-04 · 4 min · 722 words