Initial commit: k8s cluster manifests and scripts
This commit is contained in:
35
apps/codimd/backup.sh
Executable file
35
apps/codimd/backup.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
set -o pipefail
|
||||
|
||||
APP_NAME="codimd"
|
||||
BACKUP_DIR="./backup"
|
||||
TIMESTAMP=$(date +%Y%m%d-%H%M)
|
||||
DB_BACKUP_FILE="${BACKUP_DIR}/${APP_NAME}-db-${TIMESTAMP}.sql.gz"
|
||||
UPLOADS_BACKUP_FILE="${BACKUP_DIR}/${APP_NAME}-uploads-${TIMESTAMP}.tar.gz"
|
||||
|
||||
# 1. 取得 Pod 名稱
|
||||
DB_POD=$(kubectl get pod -l app=codimd-db -o jsonpath="{.items[0].metadata.name}" 2>/dev/null)
|
||||
APP_POD=$(kubectl get pod -l app=codimd -o jsonpath="{.items[0].metadata.name}" 2>/dev/null)
|
||||
|
||||
mkdir -p $BACKUP_DIR
|
||||
|
||||
# --- A. 備份資料庫 ---
|
||||
echo "--- 正在備份資料庫... ---"
|
||||
if kubectl exec $DB_POD -- pg_dump -U codimd codimd | gzip > $DB_BACKUP_FILE; then
|
||||
echo "✅ 資料庫備份成功: $(basename $DB_BACKUP_FILE) ($(stat -c%s $DB_BACKUP_FILE) bytes)"
|
||||
else
|
||||
echo "❌ 資料庫備份失敗!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- B. 備份上傳檔案 (Uploads) ---
|
||||
echo "--- 正在備份上傳圖片 (Uploads)... ---"
|
||||
# 使用 tar 打包 Pod 內的 uploads 資料夾,並傳回本機
|
||||
if kubectl exec $APP_POD -- tar zcf - -C /home/hackmd/app/public uploads > $UPLOADS_BACKUP_FILE; then
|
||||
echo "✅ 圖片備份成功: $(basename $UPLOADS_BACKUP_FILE) ($(stat -c%s $UPLOADS_BACKUP_FILE) bytes)"
|
||||
else
|
||||
echo "❌ 圖片備份失敗!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "--- ✨ 備份任務完成! ---"
|
||||
BIN
apps/codimd/backup/codimd-db-20260108-0543.sql.gz
Normal file
BIN
apps/codimd/backup/codimd-db-20260108-0543.sql.gz
Normal file
Binary file not shown.
BIN
apps/codimd/backup/codimd-uploads-20260108-0543.tar.gz
Normal file
BIN
apps/codimd/backup/codimd-uploads-20260108-0543.tar.gz
Normal file
Binary file not shown.
2
apps/codimd/deploy.sh
Executable file
2
apps/codimd/deploy.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
kubectl apply -f manifests/
|
||||
14
apps/codimd/manifests/00-codimd-middleware.yaml
Normal file
14
apps/codimd/manifests/00-codimd-middleware.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: codimd-cors
|
||||
namespace: default
|
||||
spec:
|
||||
headers:
|
||||
accessControlAllowMethods: ["GET", "OPTIONS", "PUT", "POST"]
|
||||
accessControlAllowOriginList: ["*"]
|
||||
accessControlAllowHeaders: ["*"]
|
||||
accessControlMaxAge: 100
|
||||
addVaryHeader: true
|
||||
customResponseHeaders:
|
||||
Cache-Control: "no-cache, no-store, must-revalidate"
|
||||
8
apps/codimd/manifests/01-codimd-secrets.yaml
Normal file
8
apps/codimd/manifests/01-codimd-secrets.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: codimd-secrets
|
||||
type: Opaque
|
||||
stringData:
|
||||
POSTGRES_PASSWORD: "8xG94tL0uKlGiBEd"
|
||||
CMD_DB_URL: "postgres://codimd:8xG94tL0uKlGiBEd@codimd-db/codimd"
|
||||
21
apps/codimd/manifests/02-codimd-pvc.yaml
Normal file
21
apps/codimd/manifests/02-codimd-pvc.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: codimd-db-pvc
|
||||
spec:
|
||||
storageClassName: longhorn
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: codimd-upload-pvc
|
||||
spec:
|
||||
storageClassName: longhorn
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi
|
||||
38
apps/codimd/manifests/03-codimd-db-deployment.yaml
Normal file
38
apps/codimd/manifests/03-codimd-db-deployment.yaml
Normal file
@@ -0,0 +1,38 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: codimd-db
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate # 確保 RWO 磁碟順利切換節點
|
||||
selector:
|
||||
matchLabels:
|
||||
app: codimd-db
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: codimd-db
|
||||
spec:
|
||||
containers:
|
||||
- name: postgres
|
||||
image: postgres:17-alpine
|
||||
env:
|
||||
- name: POSTGRES_USER
|
||||
value: "codimd"
|
||||
- name: POSTGRES_DB
|
||||
value: "codimd"
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: codimd-secrets
|
||||
key: POSTGRES_PASSWORD
|
||||
- name: PGDATA
|
||||
value: "/var/lib/postgresql/data/pgdata"
|
||||
volumeMounts:
|
||||
- mountPath: "/var/lib/postgresql/data"
|
||||
name: db-data
|
||||
volumes:
|
||||
- name: db-data
|
||||
persistentVolumeClaim:
|
||||
claimName: codimd-db-pvc
|
||||
13
apps/codimd/manifests/04-codimd-db-svc.yaml
Normal file
13
apps/codimd/manifests/04-codimd-db-svc.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: codimd-db
|
||||
labels:
|
||||
app: codimd-db
|
||||
spec:
|
||||
selector:
|
||||
app: codimd-db
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5432
|
||||
targetPort: 5432
|
||||
43
apps/codimd/manifests/05-codimd-app-deployment.yaml
Normal file
43
apps/codimd/manifests/05-codimd-app-deployment.yaml
Normal file
@@ -0,0 +1,43 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: codimd-app
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: codimd
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: codimd
|
||||
spec:
|
||||
securityContext: # <-- 這裡開始
|
||||
runAsUser: 0
|
||||
fsGroup: 0 # <-- 結束
|
||||
containers:
|
||||
- name: codimd
|
||||
image: hackmdio/hackmd:latest
|
||||
env:
|
||||
- name: CMD_DB_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: codimd-secrets
|
||||
key: CMD_DB_URL
|
||||
- name: CMD_USECDN
|
||||
value: "false"
|
||||
- name: CMD_DOMAIN
|
||||
value: "codimd.lotimmy.com"
|
||||
- name: CMD_PROTOCOL_USESSL
|
||||
value: "true"
|
||||
- name: CMD_CSP_ENABLE
|
||||
value: "false"
|
||||
volumeMounts:
|
||||
- mountPath: "/home/hackmd/app/public/uploads"
|
||||
name: upload-data
|
||||
volumes:
|
||||
- name: upload-data
|
||||
persistentVolumeClaim:
|
||||
claimName: codimd-upload-pvc
|
||||
14
apps/codimd/manifests/06-codimd-app-svc.yaml
Normal file
14
apps/codimd/manifests/06-codimd-app-svc.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: codimd-service
|
||||
labels:
|
||||
app: codimd
|
||||
spec:
|
||||
selector:
|
||||
app: codimd
|
||||
ports:
|
||||
- protocol: TCP
|
||||
name: http
|
||||
port: 3000
|
||||
targetPort: 3000
|
||||
28
apps/codimd/manifests/07-codimd-ingress.yaml
Normal file
28
apps/codimd/manifests/07-codimd-ingress.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: codimd-ingress
|
||||
labels:
|
||||
app: codimd
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web, websecure
|
||||
traefik.ingress.kubernetes.io/router.middlewares: default-codimd-cors@kubernetescrd
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- codimd.192.168.42.120.nip.io
|
||||
secretName: nip-io-cert
|
||||
rules:
|
||||
- host: codimd.lotimmy.com
|
||||
http: &http_rules
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: codimd-service
|
||||
port:
|
||||
number: 3000
|
||||
- host: codimd.192.168.42.120.nip.io
|
||||
http: *http_rules
|
||||
67
apps/codimd/restore.sh
Executable file
67
apps/codimd/restore.sh
Executable file
@@ -0,0 +1,67 @@
|
||||
#!/bin/bash
|
||||
# ==========================================
|
||||
# CodiMD 一鍵完整還原腳本 (DB + Uploads)
|
||||
# ==========================================
|
||||
|
||||
# 1. 自動偵測最新的備份檔案
|
||||
LATEST_DB=$(ls -t ./backup/codimd-db-*.sql.gz 2>/dev/null | head -1)
|
||||
LATEST_IMG=$(ls -t ./backup/codimd-uploads-*.tar.gz 2>/dev/null | head -1)
|
||||
|
||||
if [ -z "$LATEST_DB" ] || [ -z "$LATEST_IMG" ]; then
|
||||
echo "❌ 錯誤: 在 ./backup 找不到完整的備份檔 (需要 .sql.gz 和 .tar.gz)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "--- 📋 準備還原以下備份 ---"
|
||||
echo "📅 資料庫: $LATEST_DB"
|
||||
echo "🖼️ 圖片檔: $LATEST_IMG"
|
||||
echo "------------------------------------------"
|
||||
read -p "⚠️ 警告:這將會覆蓋目前容器內的所有資料!確定要執行嗎?(y/n) " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "操作已取消。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 2. 取得 Pod 名稱
|
||||
DB_POD=$(kubectl get pod -l app=codimd-db -o jsonpath="{.items[0].metadata.name}" 2>/dev/null)
|
||||
APP_POD=$(kubectl get pod -l app=codimd -o jsonpath="{.items[0].metadata.name}" 2>/dev/null)
|
||||
|
||||
if [ -z "$DB_POD" ] || [ -z "$APP_POD" ]; then
|
||||
echo "❌ 錯誤: 找不到運作中的 CodiMD Pod,請確認 K8s 資源狀態。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- A. 還原資料庫 (含清空邏輯) ---
|
||||
echo "--- 🗄️ 正在還原資料庫結構與內容... ---"
|
||||
# 先清空 Schema 以避免重複匯入導致的鍵值衝突
|
||||
kubectl exec -i $DB_POD -- psql -U codimd -d codimd -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;" > /dev/null
|
||||
|
||||
zcat "$LATEST_DB" | kubectl exec -i $DB_POD -- psql -U codimd -d codimd > /dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ 資料庫還原成功。"
|
||||
else
|
||||
echo "❌ 資料庫還原發生錯誤。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- B. 還原圖片檔案 ---
|
||||
echo "--- 🖼️ 正在同步圖片檔案 (Uploads)... ---"
|
||||
# 清空目前目錄避免新舊檔案混雜
|
||||
kubectl exec $APP_POD -- sh -c "rm -rf /home/hackmd/app/public/uploads/*"
|
||||
|
||||
# 解壓縮並傳入 Pod
|
||||
cat "$LATEST_IMG" | kubectl exec -i $APP_POD -- tar zxf - -C /home/hackmd/app/public/
|
||||
|
||||
# 修正權限確保 CodiMD 服務 (UID 1500) 擁有存取權
|
||||
kubectl exec $APP_POD -- chown -R 1500:1500 /home/hackmd/app/public/uploads
|
||||
echo "✅ 圖片還原與權限修正完成。"
|
||||
|
||||
# 3. 重啟服務以套用變更
|
||||
echo "--- 🔄 正在重啟應用程式容器... ---"
|
||||
kubectl rollout restart deployment codimd-app
|
||||
kubectl rollout status deployment codimd-app
|
||||
|
||||
echo "------------------------------------------"
|
||||
echo "✨ 還原程序已執行完畢。"
|
||||
echo "目前資料已同步至備份檔日期:$(echo $LATEST_DB | grep -oE '[0-9]{8}-[0-9]{4}')"
|
||||
44
apps/codimd/status.sh
Executable file
44
apps/codimd/status.sh
Executable file
@@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
|
||||
APP_NAME="codimd"
|
||||
|
||||
echo "=== [ $APP_NAME 資源狀態 ] ==="
|
||||
|
||||
# 1. 檢查 Pods
|
||||
echo "--- Pods ---"
|
||||
kubectl get pods -l "app in (codimd, codimd-db)" -o wide
|
||||
|
||||
echo ""
|
||||
|
||||
# 2. 檢查 Services
|
||||
echo "--- Services ---"
|
||||
kubectl get svc -l "app in (codimd, codimd-db)"
|
||||
|
||||
echo ""
|
||||
|
||||
# 3. 檢查 PVC (儲存)
|
||||
echo "--- Storage (PVC) ---"
|
||||
kubectl get pvc | grep codimd
|
||||
|
||||
echo ""
|
||||
|
||||
# 4. 檢查存取入口 (Ingress)
|
||||
echo "--- Access Points (Ingress) ---"
|
||||
kubectl get ingress -l "app=codimd" 2>/dev/null || kubectl get ingress | grep codimd
|
||||
|
||||
echo ""
|
||||
|
||||
# 5. 檢查資料庫連通性 (如果 Pod 在 Running)
|
||||
DB_POD=$(kubectl get pod -l app=codimd-db -o jsonpath="{.items[0].metadata.name}" 2>/dev/null)
|
||||
POD_STATUS=$(kubectl get pod $DB_POD -o jsonpath="{.status.phase}" 2>/dev/null)
|
||||
|
||||
if [ "$POD_STATUS" == "Running" ]; then
|
||||
echo "--- Database Connection Check ---"
|
||||
if kubectl exec $DB_POD -- psql -U codimd -d codimd -c "SELECT 1;" > /dev/null 2>&1; then
|
||||
echo "✅ Database is Ready and Accessible."
|
||||
else
|
||||
echo "❌ Database is Running but Connection Failed."
|
||||
fi
|
||||
else
|
||||
echo "⏳ Waiting for Database Pod to be Ready..."
|
||||
fi
|
||||
12
apps/codimd/stop.sh
Executable file
12
apps/codimd/stop.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
echo "--- 安全停止 CodiMD (保留資料) ---"
|
||||
|
||||
# 只刪除 Deployment, Service, Ingress, Middleware
|
||||
# 故意跳過 02-codimd-pvc.yaml 和 01-codimd-secrets.yaml
|
||||
kubectl delete -f manifests/03-codimd-db-deployment.yaml --ignore-not-found
|
||||
kubectl delete -f manifests/05-codimd-app-deployment.yaml --ignore-not-found
|
||||
kubectl delete -f manifests/04-codimd-db-svc.yaml --ignore-not-found
|
||||
kubectl delete -f manifests/06-codimd-app-svc.yaml --ignore-not-found
|
||||
kubectl delete -f manifests/07-codimd-ingress.yaml --ignore-not-found
|
||||
|
||||
echo "✅ 服務已停止,Longhorn 磁碟資料安全保留。"
|
||||
55
apps/codimd/tmpdir/codimd/Makefile
Normal file
55
apps/codimd/tmpdir/codimd/Makefile
Normal file
@@ -0,0 +1,55 @@
|
||||
# Makefile: 適用於 docker-compose 專案,自動取用當前資料夾為專案名
|
||||
|
||||
COMPOSE := docker-compose
|
||||
COMPOSE_FILE := docker-compose.yml
|
||||
PROJECT_NAME := $(notdir $(CURDIR)) # 依照當前資料夾自動命名
|
||||
|
||||
# 可選自訂 .env 變數(預設 docker-compose 自動載入)
|
||||
# ENV_FILE := .env
|
||||
|
||||
.PHONY: help up down build restart logs ps exec clean prune
|
||||
|
||||
help:
|
||||
@echo "🔧 Docker Compose Makefile 指令:"
|
||||
@echo " make up - 啟動所有服務 (背景執行)"
|
||||
@echo " make up-fg - 前景執行(方便除錯)"
|
||||
@echo " make down - 停止並移除服務容器"
|
||||
@echo " make build - 重新建構服務映像檔"
|
||||
@echo " make restart - 重啟所有服務"
|
||||
@echo " make logs - 查看服務日誌"
|
||||
@echo " make ps - 顯示服務狀態"
|
||||
@echo " make exec - 進入主要容器 shell"
|
||||
@echo " make clean - 清理所有服務資料(含 volume)"
|
||||
@echo " make prune - 清除未使用的資源"
|
||||
|
||||
up:
|
||||
$(COMPOSE) -p $(PROJECT_NAME) -f $(COMPOSE_FILE) up -d
|
||||
|
||||
up-fg:
|
||||
$(COMPOSE) -p $(PROJECT_NAME) -f $(COMPOSE_FILE) up
|
||||
|
||||
down:
|
||||
$(COMPOSE) -p $(PROJECT_NAME) -f $(COMPOSE_FILE) down
|
||||
|
||||
build:
|
||||
$(COMPOSE) -p $(PROJECT_NAME) -f $(COMPOSE_FILE) build
|
||||
|
||||
restart:
|
||||
$(COMPOSE) -p $(PROJECT_NAME) -f $(COMPOSE_FILE) down
|
||||
$(COMPOSE) -p $(PROJECT_NAME) -f $(COMPOSE_FILE) up -d
|
||||
|
||||
logs:
|
||||
$(COMPOSE) -p $(PROJECT_NAME) -f $(COMPOSE_FILE) logs -f --tail=100
|
||||
|
||||
ps:
|
||||
$(COMPOSE) -p $(PROJECT_NAME) -f $(COMPOSE_FILE) ps
|
||||
|
||||
exec:
|
||||
$(COMPOSE) -p $(PROJECT_NAME) -f $(COMPOSE_FILE) exec app sh
|
||||
|
||||
clean:
|
||||
$(COMPOSE) -p $(PROJECT_NAME) -f $(COMPOSE_FILE) down -v --remove-orphans
|
||||
|
||||
prune:
|
||||
docker system prune -af --volumes
|
||||
|
||||
0
apps/codimd/tmpdir/codimd/backup_uploads/.gitkeep
Normal file
0
apps/codimd/tmpdir/codimd/backup_uploads/.gitkeep
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 555 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 247 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 252 KiB |
BIN
apps/codimd/tmpdir/codimd/codimd_backup.dump
Normal file
BIN
apps/codimd/tmpdir/codimd/codimd_backup.dump
Normal file
Binary file not shown.
42
apps/codimd/tmpdir/codimd/docker-compose.yml
Normal file
42
apps/codimd/tmpdir/codimd/docker-compose.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
services:
|
||||
database:
|
||||
image: postgres:17-alpine
|
||||
container_name: codimd_database
|
||||
environment:
|
||||
- POSTGRES_USER=codimd
|
||||
- POSTGRES_PASSWORD=C0d1MdP@ss2025
|
||||
- POSTGRES_DB=codimd
|
||||
volumes:
|
||||
- "database-data:/var/lib/postgresql/data"
|
||||
restart: always
|
||||
labels:
|
||||
- "com.centurylinklabs.watchtower.enable=true"
|
||||
networks:
|
||||
- codimd_net
|
||||
|
||||
codimd:
|
||||
image: hackmdio/hackmd:latest
|
||||
container_name: codimd_app
|
||||
environment:
|
||||
- CMD_DB_URL=postgres://codimd:C0d1MdP@ss2025@database/codimd
|
||||
- CMD_USECDN=false
|
||||
depends_on:
|
||||
- database
|
||||
ports:
|
||||
- "3001:3000"
|
||||
volumes:
|
||||
- upload-data:/home/hackmd/app/public/uploads
|
||||
restart: always
|
||||
labels:
|
||||
- "com.centurylinklabs.watchtower.enable=true"
|
||||
networks:
|
||||
- codimd_net
|
||||
|
||||
volumes:
|
||||
database-data: {}
|
||||
upload-data: {}
|
||||
|
||||
networks:
|
||||
codimd_net:
|
||||
driver: bridge
|
||||
|
||||
BIN
apps/codimd/tmpdir/codimd_20251229150418.tar.gz
Normal file
BIN
apps/codimd/tmpdir/codimd_20251229150418.tar.gz
Normal file
Binary file not shown.
Reference in New Issue
Block a user