Files
ROLAC/docs/INFRASTRUCTURE.md
Chris Chen 9b28fbcfb6 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>
2026-05-24 20:54:10 -07:00

12 KiB
Raw Permalink Blame History

ROLAC — Infrastructure Design

平台: Microsoft Azure (Nonprofit $2,000 Credit)
文件版本: v0.1 (2026-05-24)


目錄

  1. Azure 資源規劃
  2. VM 容器架構
  3. Docker Compose 服務清單
  4. 網路與 DNS
  5. Nginx 反向代理路由
  6. CI/CD Pipeline (Jenkins + Gitea)
  7. Azure Storage Account 使用規劃
  8. 備份策略
  9. 成本估算 (Monthly)
  10. 部署步驟清單

1. Azure 資源規劃

資源 規格 用途 估計月費
Azure VM Standard B2s (2 vCPU / 4 GB RAM) 主機:所有 Docker 容器 ~$3035/月
Azure Storage Account LRS, Cool tier Blob 儲存(照片/PDF/媒體) ~$25/月
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 volumesDB、Gitea、Jenkins ~$11/月

總估算: ~$5765/月,年約 $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 APKiOS .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 main branch
  • Jenkins 自動觸發 Build → Test → Deploy
  • 確認 Health Check endpoint 回應正常
  • 必要時執行 EF Core Migration: dotnet ef database update