當我開發自己的產品或與單人團隊快速搭建時,我對 CI/CD (持續整合/持續部署) 流程的看法通常很不一樣。企業專案中看到的那些龐大、分層、精心設計的管道,在獨立創業者世界裡往往是不必要的負擔。我相信對於獨立創業者來說,CI/CD 的主要目標應該是為我節省時間,讓我從重複性任務中解放出來,並實現快速價值創造。
最近,在更新我開發金融計算器的副專案基礎設施時,我盡力簡化我的 CI/CD 執行管道。我想分享我在這個過程中遇到的和經歷的一些要點。我的目標不是建立「最好」的 CI/CD,而是解釋我是如何實現一個對我來說「最好」的持續、便宜且低維護的工作流程。
一個簡單的 CI/CD 管道應該看起來怎樣?
作為獨立開發者,我的首要優先級是快速迭代。測試想法、收集用戶反饋並持續改進產品至關重要。因此,我的 CI/CD 流程不應妨礙我;相反,它應該促進我的工作。對我來說,一個簡單的 CI/CD 流程應該自動化關鍵步驟,同時避免不必要的複雜性。
基本上,我需要在我寫程式碼時自動執行測試,然後是應用程式的建立和部署到某處。這些步驟之外的任何層級通常在專案規模增長或團隊規模增大的情況下才會顯得重要。對於小規模專案,有意識地將一些可能看起來「手動」的步驟排除在自動化之外,可以顯著降低總成本(包括時間和金錢)。例如,雖然一個企業ERP系統可能每次提交都會運行數十個整合測試,但我對我的個人專案只限制在單元測試和一些整合測試。這給了我速度和成本上的優勢。
快速自動化測試
測試是CI/CD的核心。我通常期望在執行時測試能立即運行git push。這是最有效的方法來及早發現錯誤,尤其是在小型專案中。如果測試運行得很慢,我注意到最終會放棄運行它們,甚至我的提交品質也下降。這在2018年的一次客戶專案經驗中變得非常清楚。在重構期間,測試運行了超過20分鐘,所以我沒有選擇等待每次變更,而是開始在本地推送看起來像能工作的代碼。結果:測試失敗了,我們不得不回滾。因此,快速的測試是我的首要優先。
我的測試策略通常包括:
- 單元測試:在最小的單位測試應用程式邏輯,幾秒鐘內完成。
- 整合測試:測試與外部依賴性互動,如資料庫或 API,較慢但仍然在合理時間內完成。
- 靜態程式碼分析/Linting: 檢查程式碼品質和風格,在構建之前捕捉錯誤.
💡 快速測試的小技巧
盡可能並行運行您的測試。像
pytest-xdist這樣的 Python 工具或Jest在 JavaScript 中非常實用。此外,快速在記憶體中啟動和銷毀測試數據庫(例如 SQLite)或 Docker 容器內可以縮短時間。在一個企業 ERP 系統中,複製測試數據庫以前需要 15 分鐘;移動到 Docker 將這個時間縮短到 30 秒。
自動建構和工件管理
測試通過後,應用程式需要被建立並準備部署。由於我的大多數專案都在 Docker 容器上運行,這個步驟通常只是docker build 命令。對於我自己的專案,我直接在我的伺服器上編譯這個映像,或者對於小型專案,把它推送到免費的註冊中心,如 Docker Hub。雖然我見過許多工件管理解決方案,從 Maven 倉庫到 Nexus,在企業 ERP 系統中,這種簡便性對我自己的專案已經足夠了。重要的是,編譯是可重複的,我可以隨時輕鬆恢復到以前的版本。
# A simple Docker build and push process
# An example I use on my own VPS
#!/bin/bash
APP_VERSION=$(git rev-parse --short HEAD)
REPO_NAME="my-indie-app"
REGISTRY="my.private.registry.com" # or Docker Hub
echo "Building Docker image for version: $APP_VERSION"
docker build -t $REGISTRY/$REPO_NAME:$APP_VERSION .
if [ $? -eq 0 ]; then
echo "Image built successfully. Pushing to registry..."
docker push $REGISTRY/$REPO_NAME:$APP_VERSION
echo "Image pushed. Deleting local image to save space."
docker rmi $REGISTRY/$REPO_NAME:$APP_VERSION
else
echo "Docker build failed!"
exit 1
fi
這個簡單的腳本為我提供了版本控制,並防止不必要的空間使用。上個月,在建立過程中,我的 VPS 磁碟滿到了 100%,而且建立失敗了,出現了 OOM (Out Of Memory) 錯誤。我那時才意識到沒有清理舊的影像。通過添加這個腳本,我解決了這個問題.
過度複雜的隱藏成本
陷入「愈自動化愈好」的陷阱對獨立開發者而言可能成本高昂。在公司世界裡,自動化的成本通常與人力成本相較之下可以忽略不計。然而,在單人團隊中,設計、設定和維護 CI/CD 管道完全落在我肩上。這直接搶走了我本應專注於產品開發的寶貴時間.
維護負擔
一個複雜的持續整合/持續交付(CI/CD)流程,隨著時間的推移,本身可能就變成一個專案。依賴更新、工具版本差異、配置變更——有時候找到"為什麼這個流程會崩潰?"的答案可能需要花費數小時。我曾在某次花了兩天時間調試,為什麼npm install 命令在不同執行器上表現不同,並因客戶專案上的快取問題導致部署失敗。這種時間損失對我的專案來說是不可接受的。因此,我努力建立一個移動部分盡可能少的系統.
- 相依更新:當
Node.js或Python版本變更時,CI/CD 環境也需要更新。 - 工具變更:從 Jenkins 迁移到 GitLab CI 意味著一個新的學習曲線和重寫現有的流程
- 環境因素:外部因素如建構伺服器的磁碟空間、網路問題或 API 限制
⚠️ 無限調試迴圈
記住,雖然自動化讓我們的生活更輕鬆,但自動化工具本身是軟體,可能會包含錯誤。當複雜的流程中出現問題時,要找出問題來源是程式碼、測試、建置還是部署工具,可能會像一個真正的偵探工作。這可能會令人沮喪和失去動力,特別是當你獨自一人時.
資源消耗
CI/CD 服務通常根據您使用的資源(CPU、RAM、儲存空間)來計費。GitHub Actions、GitLab CI 和 CircleCI 等受歡迎的服務提供免費層級,上限達到一定的使用量,但在大型專案或頻繁提交的情況下,這些上限可能很快被超出。在我的其中一個副專案中,當我需要為多個平台(網站、移動端)在每次提交時進行構建時,我意外地發現我的每月 GitHub Actions 計費飆升。這可能會變成一個顯著的成本項,特別是當您的專案尚未產生收入時。
在self-hosted runners上使用我自己的 VPS 已經成為我採用的方法來控制這些成本。在一個企業環境中,我甚至看到由於 CI/CD 伺服器的高 CPU 消耗,每月電費帳單增加了。在我自己的小 VPS 上,我必須有效地使用資源來將這些成本保持在最低。例如,我實施優化,如智能管理建構快取或僅重新編譯改變的模組。
學習曲線與鎖定
每一款新的 CI/CD 工具或技術都帶來學習曲線。學習在 Kubernetes 上部署 Helm chart、使用 Argo CD 實現 GitOps,或整合不同雲供應商的 CI/CD 服務,對單獨的團隊來說都需要大量的時間投入。這個學習過程有時候需要的時間比開發產品的核心理功能還要長。
此外,過度依賴特定的 CI/CD 平台或技術(供應商鎖定)也帶來風險。如果平台變更或定價政策改變,我可能必須重寫整個流水線。對於我自己的個人專案,我嘗試盡量減少這種依賴。我盡可能使用標準工具(Docker、Bash 腳本,systemd 個單位) 以便如果有一天我決定切換到另一個 VPS 提供商,我不會頭痛。在我的關於 [相關: 我的 VPS 迁移經驗] 的文章中,我詳細描述了這些迁移過程有多痛苦。
我的獨立 CI/CD 選擇
憑藉多年累積的經驗,我在我的獨立專案中發展出了針對 CI/CD 的特定選擇。這些選擇為我帶來了速度和成本優勢,同時最小化了維護開銷。
Git 鈎子和簡單腳本
大多數情況下,最簡單的解決方案是最有效的。在我的專案中,我使用 git hooks 和簡單腳本實現本地 CI。bash 腳本。例如,使用 pre-commit 鈎子,我會自動執行 lint 和 format 動作,在推送我的程式碼之前。這可防止髒亂的程式碼進入儲存庫,並讓我能在遠端 CI 上從一個更乾淨的畫布開始。
# A simple hook example I added to .git/hooks/pre-commit
#!/bin/sh
# Lint staged Python files
echo "Running flake8 on staged Python files..."
git diff --cached --name-only --diff-filter=ACM | grep '\.py$' | xargs flake8
if [ $? -ne 0 ]; then
echo "Flake8 issues found. Please fix them before committing."
exit 1
fi
# Format staged files (e.g., with Black)
echo "Running black formatter on staged Python files..."
git diff --cached --name-only --diff-filter=ACM | grep '\.py$' | xargs black
if [ $? -ne 0 ]; then
echo "Black formatter failed. Please check."
exit 1
fi
# Run tests
echo "Running unit tests..."
pytest --ignore=integration_tests/
if [ $? -ne 0 ]; then
echo "Unit tests failed. Aborting commit."
exit 1
fi
exit 0
感謝這個鉤子,我在提交階段捕捉錯誤,並在將它們發送到遠端 CI 之前解決它們。這節省了我的 CI 時間,並防止了有缺陷的提交。我個人看到了在開發企業 ERP 系統時,這樣的本地自動化對於提高代碼質量是多麼有價值的。
自托管運行器和 VPS 經濟學
如前所述,雲端 CI/CD 服務的成本有時會出現意外增加。Self-hosted runners 在我自己的 VPS 上運行讓我能夠控制這些成本。即使是小小的 VPS(例如 2 核心,4GB RAM)對於大多數獨立專案來說也足夠並行運行多個 CI/CD 任務。這是一個顯著的優勢,特別是對於注重成本的獨立創業者。
我用我自己的方式管理這些運行者。systemd 單位。我確保我的 VPS 維運穩定,透過 journald 監控日誌,並使用 cgroup 限制來控制資源。上個月,我無意中讓一個腳本進入無限迴圈,因為寫了 sleep 360,導致它被 OOM-killed。感謝軟限制的cgroup memory.high,我其他的服務沒有受到影響,我迅速識別並解決了問題。這些小細節增加了系統的整體韌性.
部署策略:「逐步重啟」和一鍵回滾
對獨立專案而言,我通常不需要像藍綠部署或金絲雀部署這樣的大規模且複雜的部署策略。對我來說最實際的方法是停止現有的Docker容器並啟動新版本,本質上是一個簡單的「滾動重啟」。這個過程通常只需要幾秒鐘,我願意接受應用程式的短暫停機。
然而,事情並非總是按計劃進行。因此,擁有一鍵回滾機制至關重要。如果新部署出現問題,我必須能夠快速切換回先前的穩定版本。在我的腳本中,我將最後一個成功的影像標籤存儲在一個變量中,以便在必要時重新部署此影像。
# Summary of a simple deploy and rollback script
#!/bin/bash
APP_NAME="my-indie-app"
CURRENT_VERSION=$(cat /path/to/app_version.txt)
NEW_VERSION=$1
if [ -z "$NEW_VERSION" ]; then
echo "Usage: $0 <new_version>"
exit 1
fi
echo "Deploying $APP_NAME version $NEW_VERSION..."
# Pull the new image
docker pull my.private.registry.com/$APP_NAME:$NEW_VERSION
if [ $? -ne 0 ]; then
echo "Failed to pull new image. Aborting deploy."
exit 1
fi
# Stop and remove the current container
docker compose -f /path/to/docker-compose.yml down
# Bring up the new version
sed -i "s/$CURRENT_VERSION/$NEW_VERSION/g" /path/to/docker-compose.yml # Update image tag in docker-compose.yml
docker compose -f /path/to/docker-compose.yml up -d
if [ $? -eq 0 ]; then
echo "$APP_NAME version $NEW_VERSION deployed successfully."
echo "$NEW_VERSION" > /path/to/app_version.txt
echo "Old version was: $CURRENT_VERSION. You can rollback using: $0 $CURRENT_VERSION"
else
echo "Deployment failed! Rolling back to $CURRENT_VERSION..."
# Rollback by redeploying the previous version
sed -i "s/$NEW_VERSION/$CURRENT_VERSION/g" /path/to/docker-compose.yml
docker compose -f /path/to/docker-compose.yml up -d
echo "Rolled back to $CURRENT_VERSION."
exit 1
fi
這段腳本為我提供安全的部署和快速回滾功能。我還記得在為一家銀行開發內部平台時,每次部署後都會經歷數小時的壓力監控會議。透過這個簡單的方法,我減少了自身專案上的壓力。
監控與回饋:基礎要素
無論 CI/CD 流程多麼簡單,了解應用程式部署後的行為至關重要。我從不採用「部署後忘記」的作法。一個簡單卻有效的監控和回饋機制幫助我及早發現潛在問題.
記錄追蹤和簡單警報
定期追蹤應用程式記錄是排錯的第一步。在我的專案中,我使用journald 和在需要時使用 grep。即使是非常簡單的 tail -f /var/log/syslog | grep "ERROR" 也讓我能夠立即看到許多問題。對於更先進的專案,我使用輕量級解決方案,如 Promtail 和 Loki。
我也設置了簡單的警報以處理關鍵錯誤。例如,在用工具如fail2ban,我使用自己寫的簡單腳本,當應用程式回傳特定錯誤碼時,會發送郵件或Telegram訊息給我。某夜03:14發生WAL旋轉警報時,這個簡單的警報讓我能在隔天早上及時介入。這種主動的措施可避免重大危機。
效能指標
監控應用程式表現直接影響使用者體驗。對我來說,追蹤基本指標如CPU使用率、記憶體消耗、硬碟I/O、網路流量和API回應時間就足夠了。我在自己的VPS上安裝和使用node_exporter和Prometheus這些工具。我也用Grafana建立簡單的儀表板來觀察整體狀態。
在我其中一個 side project 的後端,我注意到 Redis 的記憶體使用量出現了難以解釋的增加。在檢查指標後,我意識到由於 OOM eviction policy,特定的數據集一直在 Redis 中被清除並重新載入。我透過將策略從 allkeys-lru 改變為 volatile-lru 來解決這個問題。這種觀察只有在監控的情況下才可能實現。
應該尋找更複雜的解決方案時
到目前為止我所討論的一切,適合獨立開發者獨自一人或與一個非常小的團隊開始的場景。然而,每個專案都有成長潛力。所以,我的當前簡單的持續整合/持續交付流程開始變得不足夠的時候是什麼時候,我應該轉向更複雜的、企業級的解決方案?
团隊規模和專案範圍
隨著您的團隊成長,流程的協調和標準化變得更加重要。當多個開發者同時在開發不同的功能時,每一個提交都需要自動整合和測試。在這種情況下,一個更先進的 CI 系統(例如,並行測試執行、基於分支的管線)變得不可避免。當我們有超過 10 個開發者在開發一個企業 ERP 系統時,手動流程完全癱瘓。一個自動化、全面的 CI/CD 是當時我們的救星。
隨著專案範圍擴展,不同的服務和微服務架構可能會派上用場。為每個服務建立 CI/CD 管道會增加整體複雜性,但允許每個服務獨立部署。這是在單體與微服務決策中常見的取捨。在我的文章《[相關: 從單體過渡到微服務]》中,我詳細討論了這些決策。
法規和安全需求
若您在金融、醫療或政府等受監管產業經營,您的 CI/CD 流程可能需要符合特定的安全與合規標準。這意味著更嚴格的訪問控制、安全掃描(SAST/DAST)、審計日誌和批准流程。例如,內部銀行平台上的每個部署都需要至少兩位不同的人批准。這些要求超出了簡單的腳本解決方案,需要更企業級的 CI/CD 平台。
追蹤安全漏洞 (CVEs),以及像核心模組黑名單 (例如,algif_aead,CVE-2026-31431) 等問題變得重要,特別是處理敏感資料時。將這些安全檢查整合到 CI/CD 執行管線中,比手動進行要可靠和自動化得多。
結論
作為獨立開發者,我對 CI/CD 的看法始終圍繞著「足夠好且可持續」的原則。企業專案中常見的大型流水線,對於我自己的小型但敏捷的專案來說,往往是不必要的負擔。根據我的經驗,簡單的 git 橙子、基本的 bash 腳本,以及在我自己的 VPS 上自托管的工作者,讓我能夠保持成本低廉並快速迭代。
重要的在於要記住,持續整合與持續交付(CI/CD)是一個工具,不是一個目標。我們的目標是快速創造價值並達到用戶。在這條路上,避免任何讓我們慢下來或消耗不必要資源的複雜性,可以是一個獨立開發者的最大優勢。記住,最好的持續整合與持續交付,是那個花費你最少時間並提供最多好處的。










