9b28fbcfb6
- Add .gitignore covering C#/.NET and Angular/Node - Add placeholder structure for API (C#) and APP (Angular) - Add project docs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
12 KiB
12 KiB
ROLAC — Infrastructure Design
平台: Microsoft Azure (Nonprofit $2,000 Credit)
文件版本: v0.1 (2026-05-24)
目錄
- Azure 資源規劃
- VM 容器架構
- Docker Compose 服務清單
- 網路與 DNS
- Nginx 反向代理路由
- CI/CD Pipeline (Jenkins + Gitea)
- Azure Storage Account 使用規劃
- 備份策略
- 成本估算 (Monthly)
- 部署步驟清單
1. Azure 資源規劃
| 資源 | 規格 | 用途 | 估計月費 |
|---|---|---|---|
| Azure VM | Standard B2s (2 vCPU / 4 GB RAM) | 主機:所有 Docker 容器 | ~$30–35/月 |
| Azure Storage Account | LRS, Cool tier | Blob 儲存(照片/PDF/媒體) | ~$2–5/月 |
| Azure Static Public IP | Standard | 固定對外 IP | ~$4/月 |
| Azure DNS Zone (可選) | — | 管理 DNS 記錄 | ~$0.5/月 |
| OS Disk | Premium SSD 64 GB | VM 系統碟 | ~$10/月 |
| 資料磁碟 | Standard SSD 128 GB | Docker volumes(DB、Gitea、Jenkins) | ~$11/月 |
總估算: ~$57–65/月,年約 $700,在 $2,000 Credit 範圍內可運行約 30 個月。
若流量低,可降級至 B1ms (1 vCPU / 2 GB) 節省至 ~$15/月。
2. VM 容器架構
Azure VM (Ubuntu 22.04 LTS)
│
├── /data/ ← 資料磁碟掛載點
│ ├── postgres/ ← PostgreSQL data
│ ├── gitea/ ← Gitea repos & config
│ ├── jenkins/ ← Jenkins home
│ └── nginx/certs/ ← SSL 憑證
│
└── Docker Engine
│
├── [nginx] :80, :443 ← 反向代理 + SSL 終止
├── [angular-app] :4200 ← Angular SPA (nginx serve)
├── [rolac-api] :5000 ← ASP.NET Core API
├── [postgres] :5432 ← PostgreSQL 14+
├── [gitea] :3000, :22 ← 原始碼管理
└── [jenkins] :8080 ← CI/CD
3. Docker Compose 服務清單
# docker-compose.yml (簡化草圖)
version: '3.9'
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./nginx/certs:/etc/letsencrypt
depends_on:
- angular-app
- rolac-api
angular-app:
image: rolac/frontend:latest # 由 Jenkins build
expose:
- "80"
restart: unless-stopped
rolac-api:
image: rolac/api:latest # 由 Jenkins build
expose:
- "5000"
environment:
- ConnectionStrings__Default=Host=postgres;...
- Azure__StorageAccount__ConnectionString=...
- Jwt__Secret=${JWT_SECRET}
depends_on:
- postgres
restart: unless-stopped
postgres:
image: postgres:16-alpine
volumes:
- /data/postgres:/var/lib/postgresql/data
environment:
- POSTGRES_DB=rolac
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
expose:
- "5432"
restart: unless-stopped
gitea:
image: gitea/gitea:latest
ports:
- "2222:22" # Git SSH
expose:
- "3000" # Web UI (透過 nginx 代理)
volumes:
- /data/gitea:/data
restart: unless-stopped
jenkins:
image: jenkins/jenkins:lts-jdk21
expose:
- "8080" # Web UI (透過 nginx 代理)
volumes:
- /data/jenkins:/var/jenkins_home
- /var/run/docker.sock:/var/run/docker.sock # Jenkins 可操作 Docker
restart: unless-stopped
Secrets 管理: 使用
.env檔搭配docker-compose --env-file,不得 commit 到 Gitea。
4. 網路與 DNS
Internet
│
▼
Azure Public IP (Static)
│
▼
Azure NSG (Network Security Group)
├── Port 80 (HTTP → 轉 443)
├── Port 443 (HTTPS)
└── Port 2222 (Git SSH)
│
▼
Ubuntu VM → Docker → Nginx
DNS 記錄 — rolac.org
| 子域名 | 完整網址 | 類型 | 指向 | 用途 |
|---|---|---|---|---|
@ |
rolac.org | A | VM Public IP | 教會公開網站(根域名) |
www |
www.rolac.org | CNAME | rolac.org | 同上(重定向) |
app |
app.rolac.org | A | VM Public IP | Angular 後台管理入口 |
api |
api.rolac.org | A | VM Public IP | ASP.NET Core REST API |
git |
git.rolac.org | A | VM Public IP | Gitea 原始碼管理 |
ci |
ci.rolac.org | A | VM Public IP | Jenkins CI/CD |
5. Nginx 反向代理路由
# /nginx/conf.d/rolac.conf (草圖)
# HTTP → HTTPS redirect
server {
listen 80;
server_name *.rolac.org rolac.org;
return 301 https://$host$request_uri;
}
# 教會公開網站 (Angular SSR or static)
server {
listen 443 ssl;
server_name rolac.org www.rolac.org;
location / { proxy_pass http://angular-app:80; }
}
# 後台管理 App
server {
listen 443 ssl;
server_name app.rolac.org;
location / { proxy_pass http://angular-app:80; }
}
# API
server {
listen 443 ssl;
server_name api.rolac.org;
location / {
proxy_pass http://rolac-api:5000;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
}
}
# Gitea
server {
listen 443 ssl;
server_name git.rolac.org;
location / { proxy_pass http://gitea:3000; }
}
# Jenkins
server {
listen 443 ssl;
server_name ci.rolac.org;
location / { proxy_pass http://jenkins:8080; }
}
SSL 憑證 (Let's Encrypt)
certbot certonly --webroot -w /data/nginx/www \
-d rolac.org \
-d www.rolac.org \
-d app.rolac.org \
-d api.rolac.org \
-d git.rolac.org \
-d ci.rolac.org
6. CI/CD Pipeline (Jenkins + Gitea)
流程圖
開發者 push → Gitea
│
│ Webhook (HTTP POST)
▼
Jenkins Job
│
┌───────────┼───────────┐
▼ ▼ ▼
Build Test Lint
Docker dotnet ng lint
image test
│
▼
Push image (Web) ← rolac.org & app.rolac.org 用
docker compose up -d
│
▼ (main branch only)
Capacitor Build ← 產出 iOS .ipa / Android .apk
npx cap build ios
npx cap build android
│
▼
Health check
通知結果 (Email)
注意: Capacitor build (iOS) 需要 macOS 環境,Jenkins 跑在 Linux VM 上只能產 Android APK;iOS
.ipa需在 Mac 上另外 build 或使用 Xcode Cloud。
Jenkinsfile 草圖
pipeline {
agent any
environment {
API_IMAGE = "rolac/api:${BUILD_NUMBER}"
WEB_IMAGE = "rolac/frontend:${BUILD_NUMBER}"
}
stages {
stage('Checkout') {
steps { checkout scm }
}
stage('Test API') {
steps {
sh 'dotnet test ./backend/ROLAC.Tests'
}
}
stage('Build Images') {
parallel {
stage('API') {
steps {
sh 'docker build -t ${API_IMAGE} -t rolac/api:latest ./backend'
}
}
stage('Frontend (Web)') {
steps {
sh 'docker build -t ${WEB_IMAGE} -t rolac/frontend:latest ./frontend'
}
}
}
}
stage('Deploy Web') {
when { branch 'main' }
steps {
sh 'docker compose -f /opt/rolac/docker-compose.yml up -d --no-deps rolac-api angular-app'
}
}
stage('Build Android APK') {
when { branch 'main' }
steps {
dir('frontend') {
sh 'npm ci'
sh 'npm run build -- --configuration production'
sh 'npx cap sync android'
sh 'cd android && ./gradlew assembleRelease'
}
archiveArtifacts artifacts: 'frontend/android/app/build/outputs/apk/release/*.apk'
}
}
}
post {
failure { mail to: 'admin@rolac.org', subject: "Build FAILED: ${env.JOB_NAME} #${BUILD_NUMBER}" }
success { mail to: 'admin@rolac.org', subject: "Build OK: ${env.JOB_NAME} #${BUILD_NUMBER}" }
}
}
Branch 策略
| Branch | 說明 | 部署 |
|---|---|---|
main |
正式版本 | 自動部署 Production |
develop |
開發整合 | 自動部署 Staging (同 VM,不同 port) |
feature/* |
功能開發 | 僅跑 Build + Test,不部署 |
hotfix/* |
緊急修復 | 審核後 merge 至 main |
7. Azure Storage Account 使用規劃
Container (Blob) 結構
rolac-storage (Storage Account)
├── members/
│ ├── photos/ ← 教友大頭照
│ └── documents/ ← 個人相關文件
├── receipts/
│ └── {year}/ ← 年度奉獻收據 PDF
├── cms/
│ ├── images/ ← 網站上傳圖片
│ └── attachments/ ← 公告附件
└── reports/
└── exports/ ← 匯出的報表 PDF/CSV
存取控制
| Container | 存取層級 | 說明 |
|---|---|---|
members/ |
Private | 僅 API 透過 SAS Token 存取 |
receipts/ |
Private | API 產生限時下載連結給教友 |
cms/images/ |
Blob (Public Read) | 網站圖片可公開存取 |
reports/ |
Private | 僅管理員 API 存取 |
C# 整合
// 使用 Azure.Storage.Blobs SDK
// 上傳教友照片範例
var blobClient = new BlobContainerClient(connectionString, "members");
await blobClient.UploadBlobAsync($"photos/{memberId}.jpg", fileStream);
// 產生限時 SAS URL (收據下載)
var sasUri = blobClient.GenerateSasUri(BlobSasPermissions.Read,
DateTimeOffset.UtcNow.AddHours(1));
8. 備份策略
PostgreSQL 備份
# crontab (每日 02:00 UTC 備份)
0 2 * * * docker exec postgres pg_dump -U rolac_user rolac \
| gzip > /data/backups/rolac_$(date +%Y%m%d).sql.gz
# 保留 30 天
find /data/backups -name "*.sql.gz" -mtime +30 -delete
備份上傳至 Azure Blob
# 使用 azcopy 或 Azure CLI 將備份上傳
azcopy copy "/data/backups/rolac_$(date +%Y%m%d).sql.gz" \
"https://rolacstorage.blob.core.windows.net/backups/"
備份範圍
| 資料 | 方式 | 頻率 | 保留 |
|---|---|---|---|
| PostgreSQL DB | pg_dump → Azure Blob | 每日 | 30 天 |
| Gitea repos | volume backup → Azure Blob | 每日 | 14 天 |
| Jenkins config | volume backup | 每週 | 4 週 |
| Azure Blob 本身 | Azure Soft Delete | 自動 | 7 天 |
9. 成本估算 (Monthly)
| 項目 | 規格 | 月費 (USD) |
|---|---|---|
| Azure VM | Standard_B2s | $30 |
| OS Disk | Premium SSD 64 GB | $10 |
| 資料磁碟 | Standard SSD 128 GB | $11 |
| Static Public IP | Standard | $4 |
| Storage Account | LRS 50 GB | $2 |
| 出站流量 | ~10 GB/月 | $1 |
| 合計 | ~$58/月 |
💡 $2,000 nonprofit credit → 約 34 個月 (接近 3 年)
如規模成長,可升級 VM 規格但仍在 credit 範圍內。
10. 部署步驟清單
Phase 0 — Azure 環境建置
- 申請 Microsoft Nonprofit Azure 帳號
- 建立 Resource Group:
rg-rolac-prod - 建立 Azure VM (Ubuntu 22.04, Standard_B2s)
- 建立資料磁碟 (128 GB) 並掛載至
/data - 設定 NSG (開放 80, 443, 2222)
- 建立 Static Public IP 並綁定
- 建立 Storage Account (
rolacstorage) - 設定 DNS A 記錄指向 VM IP
Phase 0 — VM 環境初始化
- 安裝 Docker Engine + Docker Compose
- 安裝 Certbot (Let's Encrypt)
- 建立目錄結構 (
/data/postgres,/data/gitea,/data/jenkins) - 啟動 Gitea 容器,建立組織與 Repo
- 啟動 Jenkins 容器,安裝 Plugins (Git, Docker, Pipeline)
- 設定 Gitea Webhook → Jenkins
- 申請 SSL 憑證 (certbot)
- 啟動 Nginx 容器,配置反向代理
Phase 1+ — 每次 Release
- Push code to Gitea
mainbranch - Jenkins 自動觸發 Build → Test → Deploy
- 確認 Health Check endpoint 回應正常
- 必要時執行 EF Core Migration:
dotnet ef database update