Skip to main content

2 posts tagged with "homelab"

View All Tags

· 8 min read
Wei Ji
info

OCI Image 實際上不是單一檔案,而是一層一層的 layer 構成的,但是為了簡化描述,本文使用 image 一詞時可能是在描述 layer(s)。

背景

我在 Homelab 中架設了簡易的 Docker Registry 作為鏡像/代理,來節省我工作筆電的硬碟空間和下載時間,更具體的細節請見另外一篇文章:我的 Homelab

起因

一切都源自於上班的時候需要用到這個 image: quay.io/keycloak/keycloak,拉了幾次卻發現我的鏡像站沒有快取這個 image。

下班之後在 homelab 開了另外一個 service:

registry-server-2:
image: registry:2.8.2
environment:
- REGISTRY_PROXY_REMOTEURL="https://quay.io/v2"
volumes:
- quay-data:/var/lib/registry
networks:
- default
- traefik-public
deploy:
labels:
- traefik.enable=true
- traefik.docker.network=traefik-public
- traefik.http.routers.registry-quay-mirror.entrypoints=web
- traefik.http.routers.registry-quay-mirror.rule=Host(`mirror.quay.oci-image.arachne`)
- "traefik.http.services.registry-quay-mirror.loadbalancer.server.port=5000"

發現依然不會快取。

No, this is a limitation of the docker engine, registry-mirrors is only for Docker Hub. You can follow issue 18818 for more details.1

Unfortunately and infuriatingly, it is not possible. This is a most desired feature, by me, and by many others. However, Docker, on purpose, does not implement this feature.2

這似乎是 Docker 本身的設計所致,不過在我發現這個限制以前,以為是 distribution/distribution 的限制因此做了一些嘗試。

Sonatype Nexus Repository

我先是嘗試 sonatype/nexus3,然後看到了這個:

蛤?為什麼我用自架的開源軟體還要有額度限制?下一個。下一個。

接著試了 sonatype/nexus:oss

不支援 Docker?好吧。

Harbor

當我在考慮自架 OCI Registry 時,Harbor 不在考慮範圍的原因是我在官方網站只找得到兩種安裝方式:

我目前不是使用 K8s,Helm Chart 自然是不考慮。安裝精靈又可能導向裸機 (baremetal) 安裝,讓我沒辦法用 Docker 管理叢集。

後來經過調查才發現安裝精靈最後是產生 Docker Compose 的組態來執行的,可以在原始碼中發現 Docker Compose YAML 的樣板:harbor/make/photon/prepare/templates/docker_compose/docker-compose.yml.jinja

不過很遺憾,我使用 Docker Swarm 來編排 homelab 的服務,Compose 的 YAML 沒辦法直接拿來使用。

分而治之

Harbor 由八個微服務構成:

  • proxy (goharbor/nginx-photon:v2.13.1)
  • core (goharbor/harbor-core:v2.13.1)
  • registry (goharbor/registry-photon:v2.13.1)
  • redis (goharbor/redis-photon:v2.13.1)
  • postgresql (goharbor/harbor-db:v2.13.1)
  • portal (goharbor/harbor-portal:v2.13.1)
  • jobservice (goharbor/harbor-jobservice:v2.13.1)
  • registryctl (goharbor/harbor-registryctl:v2.13.1)

我試著以最小仰賴的方式,一個一個服務跑起來試試看,然後用自己寫的腳本完成必要的配置,而不是使用 Harbor 安裝精靈。細節就不在本文贅述,最後建起來 Docker Compose YAML 在這裡:

https://github.com/FlySkyPie/harbor-docker-compose

在 Harbor 官方的版本中,所有組態都是仰賴 volumes 或是 env_file 完成的,這在 Swarm 模式是行不通得。在 Compose 模式下可以用本地的相對路徑來組織檔案或是資料夾,而 Swarm 模式必須抱持著「一切都發生在遠端」的思路:機敏資訊由 secrets 實例儲存、設定檔以 configs 實例儲存...。完成 Compose 版本後再轉成 Swarm 的版本:

https://github.com/FlySkyPie/harbor-docker-swarm

最後有些密碼還是用 environment 傳遞,因為服務本身可能沒有設計支援 Secret File,我也不想花太多時間在這件事上折騰,就先這樣將就著用吧。

另外,安裝精靈建立的當中其實還有一個 log 容器用來從其他微服務蒐集 log,我的版本並沒有包含它,一來是它不是執行服務必要的功能,二來是如果我有蒐集 log 的需求會再搭配 Promtail 和 Grafana Loki 之類專門用來做這檔事的工具。

附上成果圖:

資料轉移

為了把舊的鏡像資料遷移到 Harbor,我做了一些措施,雖然整體而言稱不上完美。

建立 image 清單

首先要建立鏡像站快取過得清單:

  1. GET /v2/_catalog 取得 N 個 repositories 的資料。
  2. GET/v2/${repository}/tags/list 取得每一個 repository 的 tag 資料,要注意的是 distribution/distribution 鏡像站可能快取了 tag 但是還沒有快取映像檔本身。
  3. GET /v2/${repo}/manifests/${tag} 取得 manifest 資訊
  4. HEAD /v2/${repository}/blobs/${digest} 根據 manifest 判斷 layers 有沒有被快取。
    • 這裡要注意,必須暫時關閉 REGISTRY_PROXY_REMOTEURL 的設定,不然會直接觸發代理鏡像機制,關閉代理之後,沒有被快取過得檔案會直接 404 作為判斷有沒有被快取過得機制。

關於更多 API 操作的細節可以閱讀官方的文件3

整個過程完成會得到類似這樣的清單:

goharbor/nginx-photon:v2.13.1
goharbor/redis-photon:v2.11.0
goharbor/registry-photon:v2.13.1
grafana/loki:latest
grafana/loki:2.7.1
...

設定 Harbor

接著在 Harbor 設定 registriesprojects 指向舊的鏡像站。

要注意的是,設定 Proxy Cache 之後既不能關閉也不能修改。

從 Harbor 鏡像本地鏡像站

接著使用腳本從 Harbor 新設定的 Project 依照清單拉取 image

docker pull harbor.arachne/docker-hub-proxy/getpinry/pinry:latest

這樣就可以把一部分 image 從舊的鏡像站轉移到 Harbor 去了,雖然在其他 Project 不會顯示 image 的存在,但是 image 的檔案在 Harbor 是跨 Project 儲存的,之後如果從其他 Project 拉取 image 時有已經快取過的檔案,Harbor 會直接使用已經存在的檔案。

限制

我這個作法是 「不完美」而且只能遷移「一部分」的原因在於,像 docker.io/library/mysql 這種 image,因為安全性的問題,同一個 tag 其實會一直更新,因此有些 image 在我的鏡像站上是過期的,所以當腳本拿著最新的 manifest 去檢查快取的時候,是有可能全部 404 的。

Podman

還記得我前面提到的「Docker 本身的設計就不允許設定 docker.io 以外的鏡像站」嗎?於是我試著使用 Podman,發現很輕易的就設定好本地鏡像站了(/etc/containers/registries.conf):

[[registry]]
location = "quay.io"
[[registry.mirror]]
location = "harbor.arachne/quay-proxy"
insecure = false

[[registry]]
location = "docker.io"
[[registry.mirror]]
location = "harbor.arachne/docker-hub-proxy"
insecure = false

當然,「使用 Podman 解決非 docker.io 本地鏡像站問題」與「使用 Harbor 取代 distribution/distribution」變成兩個獨立事件了,不過既然我都已經折騰完 Harbor 了,那就乾脆直接當作非預期的升級計畫換過去吧。

Footnotes

  1. mirroring - docker registry-mirrors is not used for fqdn images - Server Fault. Retrieved 2025-07-05 from https://serverfault.com/a/1125577

  2. Configure dockerd to use a regsitry mirror for a private registry - General - Docker Community Forums. Retrieved 2025-07-05 from https://forums.docker.com/t/configure-dockerd-to-use-a-regsitry-mirror-for-a-private-registry/135180/2

  3. HTTP API V2 | CNCF Distribution. Retrieved 2025-07-05 from https://distribution.github.io/distribution/spec/api/

· 15 min read
Wei Ji

從我開始計畫建一個 Homelab 至今已經超過半年了,如今它的規模也算是有點東西可以拿出來講了。

什麼是 Homelab?

Homelab 就是資訊工程師平常在公司管伺服器管得不夠爽,所以在自己家裡蓋一個資料中心繼續玩。

好吧,對某些人來說或許是這樣,不過要稱得上是 Homelab 的門檻其實很低:只要在家中放一台伺服器,它可以是一般的桌機、NAS、筆電或是樹莓派。

硬體架構

黑線是乙太網路、藍線是其他(主要是 USB):

目前我的 Homelab 硬體配置大概如下:

  • 4G 路由器
    • TOTOLINK LR350 4G LTE
  • 交換器
    • Zyxel GS-108B v3
  • 樹莓派
    • Raspberry Pi 3B
  • 筆電(伺服器)
    • Lenovo Yoga Slim 7 Carbon 13ITL5
  • DAS (Direct-Attached Storage)
    • QNAP TR-004U 4-bay
  • USB Dock
    • j5create JCD533
  • USB 網路卡
    • j5create JCE145
  • 筆電
    • Framework Laptop 13 (13th Intel i7-1360P)

入口網站

當自架的服務越來越多,一個簡單的入口網站來尋找需要的服務是十分必要的。目前我是使用 Dashy,關於其他最後沒使用的候選方案我補充在另外一篇貼文

目前我把所有服務大致分成四類:

  • 數位基礎設施
    • 一些情況不會直接面對終端使用者 (End User),而是在背景默默工作的東西會放在這裡。
  • 專案 (Side Project) 基礎設施
    • 「管理」相關的東西會放在這裡。
  • 生活
    • 比較屬於個人會使用到的服務會放在這裡。
  • 開發工具
    • 「研發工具」之類的東西會放在這裡。

經濟學裡裡的生產要素被分成四類:自然資源、人力資源、資本、企業才能。如果用經濟學裡生產要素的概念來比喻,「數位基礎設施」就像是自然資源的陽光、空氣、水;「專案基礎設施」則是資本裡的機器、設備、廠房。

「生活」是不直接涉及生產東西,比較像是個人消耗品。「開發工具」則是尺度比較小的工具,像是電鑽、板手...之類的。

數位基礎設施

Pi Hole 與 DNS

架設的服務一多,單靠埠號去分辨服務其實不理想,而為了實現這個需求就需要 DNS 和反向代理 (Reverse Proxy),反向代理的部份稍後再回來談,我使用 Pi-hole 來擔任我的 DNS 伺服器。

Pi-hole 除了 DNS 另外一個功能是封鎖黑名單內的域名,如此一來就可以在 DNS 層級封鎖掉一些廣告。

DHCP

因為我會帶著主力筆電出門,如果每次都還要重新設定 DNS 同樣不夠理想,因此 DNS 能夠直接由 DHCP 發送完成設定是十分必要的,最後是成功完成了,然而過程中有遇到一些小阻礙,在此紀錄一下。

容器內的 Pi-hole DHCP

Pi-hole 本身就支援 DHCP,但是我是把它跑在 Docker 容器內,因此需要一些額外的處理1,然而在這方面我沒有成功讓容器內的 Pi-hole DHCP 正常運作。

Host 上的 isc-dhcp-server

無奈之下我只好在主機 (Host) 上直接安裝 DHCP 伺服器,然而這讓我碰到另外一個問題:

receive_packet failed on enx00051b52140f: Network is down

要排除問題必須先切到固定 IP 配置遠端進 homelab (或是直接物理訪問伺服器),把 DHCP 的 Daemon 重新啟動:

sudo service isc-dhcp-server restart

問題發生的時候 Daemon 本身顯示正常,所以我也找不到其他方法偵測問題來自動重起 Daemon。

最後懷疑是 USB 網路卡 j5create JCE145 不夠可靠、掉封包造成 DHCP 故障。

至於為什麼需要掛一個 USB 網路卡是因為擔任伺服器的筆電是薄型筆電,只有三個 USB Type-C。

樹莓派上的 isc-dhcp-server

剛好之前借給親戚當文書電腦的樹莓派被反還了,索性試著把 DHCP 伺服器轉移到樹莓派上,然後那個討厭的問題就再也沒有出現過了。

DHCP 伺服器,結案。

Traefik 與反向代理

ApacheNginx 做反向代理的組態檔我都寫過,因此當我第一次接觸到 Traefik 的時候是十分的驚豔,確實感受到來自開發團隊所謂的:

We forgot what was impossible so we could build it!2

整個流程大概是這樣的,在 Docker 新增一個網路:

docker network create --driver=overlay --attachable traefik-public

開一個容器跑 Traefik 的服務然後接到剛剛開網路:

---
services:
proxy:
image: traefik:latest
command:
- --api.insecure=true
- --providers.docker
- --entrypoints.ep1.address=:80
- --entrypoints.ep2.address=:9090
ports:
- "80:80"
- "127.0.0.1:8080:8080"
- "9090:9090"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- traefik-public

networks:
traefik-public:
external: true

對於想要反向代理的服務則是在它的組態上加上反向代理的資訊像這樣:

app1:
image: docker.io/alpinelinux/darkhttpd:latest
labels:
- traefik.http.routers.app1.entrypoints=ep1
- traefik.http.routers.app1.rule=Host(`example.com`)

如此一來就不用每次更改規則的時候都要去修改或重新啟動反向代理的配置與伺服器,又或是目標服務因為某些原因掉線時候讓反向代理伺服器找不目標而出錯,使用上十分方便。

OCI Registry

OCI (Open Container Initiative) Registry,或是通俗的講 Docker 庫,用來存放 Docker 映像檔 (Image) 的地方。

我使用一個庫搭配遺一個 Web UI 的方案:

https://github.com/distribution/distribution

即使短期內沒有自己的映像檔要佈署,當成鏡像站也很好用,在 client 端的 /etc/docker/daemon.json 加入3

{
"registry-mirrors": ["https://<my-docker-mirror-host>"]
}

這樣 pull 的時候就會留一份副本在鏡像站,下次拉取一樣的東西就可以直接從 homelab 下載。

這對我而言有用是因為一些條件:

  1. 我對外網路是無線網路,速度本來就十分有限,能夠快取映像檔,之後重複拉取可以節省我的時間。
  2. 我偶爾還會處理像 GitLab 或是 SideFX Houdini 那種一個 1 GB 起跳的映像檔。
  3. 我使用 Docker 的時候經常會接觸不同的映像檔,會不定期清理筆電上的映像檔,不然硬碟空間會被吃光。

NPM Registry

作為一個 (Web) 前端仔,自架一個私有 NPM 庫是再合理不過的事,跟 Docker 的情況相同,就算沒有要佈署東西,作為鏡像站依然有它的作用,並且同樣使用一個庫一個 Web UI 的搭配:

Verdaccio 雖然有 Web UI 能夠管理私人 (private) 套件,卻不會顯示快取的套件。filebrowser 是一個簡單的服務,只要把 Volume 掛給它就能透過瀏覽器查看檔案系統的檔案以及簡單的管理,包含上傳、下載、新增資料夾、刪除、重新命名...等。

在網路不穩定的情況 Verdaccio 有機會快取失敗留下損壞的組態檔,這時就能從瀏覽器刪除損壞的組態讓它重新下載。

S3 (Simple Storage Service) 物件儲存

info

S3 原本是 AWS 的雲端服務,更準確的用詞應該是 S3 兼容 (S3 compatible) 服務,只是因為它在雲端世界已經實質上是一種通用標準了,以下我直接簡稱 S3。

對我而言輕量級的東西總是有特別的吸引力,因此一開始我試著使用 Garage,但是也因為它的精簡,因此並沒有內建 Web UI,加上我對 S3 的使用也稱不上熟悉,相對複雜的安裝文件最後讓我卻步而改用比較普通的 MinIO:

正如我說的,我對 S3 的使用仍然缺乏經驗,自架一個 S3 實例就是為了長經驗,另一方面是目前不少網路服務會把儲存資料或檔案的部份抽象化並且把職責交給外部的 S3 服務,例如 DVC 是我預計想玩的東西;前端使用 Git 風格的 CLI、後端則是把版控的大型檔案儲存在 S3 (或其他支援的儲存形式)。

專案基礎設施

專案管理

專案管理的部份在前一篇文章有稍微提過,那個時候使用 WeKan。

目前我選擇使用 Vikunja,雖然跟其他候選相比星星少而且發展時間也比較短,但是算是一個後起之秀,兩個我很看中的功能都有支援,連續流水號:

卡片之間的關聯:

並且整體的 UI/UX 也更貼近現代的風格,更具體地說,跟我平時工作使用的 Jira 有一定程度的相似度:

曬一下目前在 Homelab 裡紀錄的 side project:

版本控制

我使用 Gitea,因為它比較簡單。另外一個常見的自架選擇是 GitLab,也是俗稱的全家桶,不過對我而言過於笨重了,我有接觸過它的 Docker Image 不論是官方的還是第三方的,總而言之就是笨重。

物料管理

我使用 Part-DB 來管理我的零件與物品,在前一篇文章也有稍微提過。

實際上運作起來的樣子我在 FBTwitter 都有貼文分享過。

Pinry

Pinry 基本上是開源自架版本的 Pinterest。

生活

個人影音

我使用 Jellyfin 搭配 Filebrowser 的方式:

其實這是我第一個使用 filebrowser 的組合,之後才套用到其他服務去的,這方法也不是我原創的,是在網路上找到的4

資訊訂閱器

RSS (Really Simple Syndication) 是一種把網站內容建立摘要的協定,像這樣,接著其他人就可以定期的去撈 (Creep) 這個文件,有更新的話就會知道,達到類似訂閱的效果,不過定期撈資料這個動作一般交由特定的軟體完成。

我目前是使用 Tiny Tiny RSS,關於其他沒使用到的候選我放在另外一個貼文

網頁備份器

不知道你有沒有使用過 Wayback Machine 呢?這是一個快取網站,讓人可以調閱那些已經不存在的網頁的網站。ArchiveBox 就是它的自架版本。

電子書

其實我沒有看電子書的習慣,出於方便歸檔跟調閱的目的架架看的,目前使用 Kavita。跟 Jellyfin 一樣我搭配了一個 Filebrowser 使用。

開發工具

目前就是放一些上網也能用的平台,但是還是自己架一個以防萬一。

虛擬白板

Excalidraw是一個開源的協作虛擬白板,包含讓遠端工作者進行線上腦力激盪 (Brainstorming) 之類的用例。

Open API/AsynAPI 編輯器

Swagger Editor

Footnotes

  1. Docker DHCP and Network Modes - Pi-hole documentation. Retrieved 2025-04-26 from https://docs.pi-hole.net/docker/dhcp/

  2. Traefik 2.0 - The Wait is Over! Retrieved 2025-04-26 from https://traefik.io/blog/traefik-2-0-6531ec5196c2/

  3. Mirror | Docker Docs. Retrieved 2025-04-26 from https://docs.docker.com/docker-hub/image-library/mirror/

  4. Upload file from the app or site · Jellyfin Feature Requests. Retrieved 2025-04-27 from https://features.jellyfin.org/posts/543/upload-file-from-the-app-or-site#comment-5160