Initial commit: monorepo scaffold for ROLAC
- 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>
This commit is contained in:
@@ -0,0 +1,462 @@
|
||||
# ROLAC — Infrastructure Design
|
||||
|
||||
**平台:** Microsoft Azure (Nonprofit $2,000 Credit)
|
||||
**文件版本:** v0.1 (2026-05-24)
|
||||
|
||||
---
|
||||
|
||||
## 目錄
|
||||
|
||||
1. [Azure 資源規劃](#1-azure-資源規劃)
|
||||
2. [VM 容器架構](#2-vm-容器架構)
|
||||
3. [Docker Compose 服務清單](#3-docker-compose-服務清單)
|
||||
4. [網路與 DNS](#4-網路與-dns)
|
||||
5. [Nginx 反向代理路由](#5-nginx-反向代理路由)
|
||||
6. [CI/CD Pipeline (Jenkins + Gitea)](#6-cicd-pipeline-jenkins--gitea)
|
||||
7. [Azure Storage Account 使用規劃](#7-azure-storage-account-使用規劃)
|
||||
8. [備份策略](#8-備份策略)
|
||||
9. [成本估算 (Monthly)](#9-成本估算-monthly)
|
||||
10. [部署步驟清單](#10-部署步驟清單)
|
||||
|
||||
---
|
||||
|
||||
## 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 服務清單
|
||||
|
||||
```yaml
|
||||
# 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
|
||||
# /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)**
|
||||
```bash
|
||||
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 草圖
|
||||
|
||||
```groovy
|
||||
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# 整合
|
||||
|
||||
```csharp
|
||||
// 使用 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 備份
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# 使用 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 `main` branch
|
||||
- [ ] Jenkins 自動觸發 Build → Test → Deploy
|
||||
- [ ] 確認 Health Check endpoint 回應正常
|
||||
- [ ] 必要時執行 EF Core Migration: `dotnet ef database update`
|
||||
Reference in New Issue
Block a user