refactor(specs): merge 08-infrastructure into canonical 04-06 dirs
All checks were successful
Build and Deploy / deploy (push) Successful in 1m0s
All checks were successful
Build and Deploy / deploy (push) Successful in 1m0s
- Append live QNAP configs to 04-01-docker-compose.md (Appendix A) - MariaDB+PMA, Redis+Elasticsearch, NPM, Gitea, n8n, App stack - Append SSH setup + Secrets management to 04-06-security-operations.md - Appendix A: SSH key setup, config, hardening, port forwarding - Appendix B: .env structure, secret generation, rotation, GPG backup - Append QNAP/Gitea CI/CD docs to 04-04-deployment-guide.md - Appendix A: Container Station deployment steps - Appendix B: Gitea Actions CI/CD pipeline setup - Appendix C: act_runner (ASUSTOR) installation - Move Git_command.md -> 05-Engineering-Guidelines/05-05-git-cheatsheet.md - Move docker-compose-app.yml, lcbp3-monitoring.yml, lcbp3-registry.yml, grafana/ -> 04-Infrastructure-OPS/ - Archive lcbp3-db.md -> 99-archives/ - Remove all legacy 08-infrastructure/* files from git - Remove Google OAuth client_secret JSON from git index (security) - Add .gitignore rules: *client_secret*.json, *service_account*.json, specs/08-infrastructure/ - Update 04-Infrastructure-OPS/README.md with new file index
This commit is contained in:
@@ -1387,3 +1387,456 @@ networks:
|
||||
-- Export audit logs for compliance
|
||||
SELECT *
|
||||
FROM document_numbering
|
||||
|
||||
---
|
||||
|
||||
# Appendix A — Live QNAP Production Configs
|
||||
|
||||
> 🖥️ **Server:** QNAP TS-473A · Path: `/share/np-dms/`
|
||||
> These are the **actual running Docker Compose configurations** on QNAP Container Station (v1.8.0).
|
||||
> They differ from the generic Blue-Green templates above in that they use the real `lcbp3` Docker external network.
|
||||
|
||||
---
|
||||
|
||||
## A.1 Database Stack (`lcbp3-db`)
|
||||
|
||||
> **Path:** `/share/np-dms/mariadb/docker-compose.yml`
|
||||
> **Application name:** `lcbp3-db` — Services: `mariadb`, `pma`
|
||||
|
||||
### Pre-requisite: File Permissions
|
||||
|
||||
```bash
|
||||
chown -R 999:999 /share/np-dms/mariadb
|
||||
chmod -R 755 /share/np-dms/mariadb
|
||||
setfacl -R -m u:999:rwx /share/np-dms/mariadb
|
||||
|
||||
# phpMyAdmin (UID 33)
|
||||
chown -R 33:33 /share/np-dms/pma/tmp
|
||||
chmod 755 /share/np-dms/pma/tmp
|
||||
|
||||
# Add Prometheus exporter user
|
||||
# docker exec -it mariadb mysql -u root -p
|
||||
# CREATE USER 'exporter'@'%' IDENTIFIED BY '<PASSWORD>' WITH MAX_USER_CONNECTIONS 3;
|
||||
# GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'%';
|
||||
# FLUSH PRIVILEGES;
|
||||
```
|
||||
|
||||
### Add Databases for NPM & Gitea
|
||||
|
||||
```sql
|
||||
-- Nginx Proxy Manager
|
||||
CREATE DATABASE npm;
|
||||
CREATE USER 'npm'@'%' IDENTIFIED BY 'npm';
|
||||
GRANT ALL PRIVILEGES ON npm.* TO 'npm'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
|
||||
-- Gitea
|
||||
CREATE DATABASE gitea CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_unicode_ci';
|
||||
CREATE USER 'gitea'@'%' IDENTIFIED BY '<PASSWORD>';
|
||||
GRANT ALL PRIVILEGES ON gitea.* TO 'gitea'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
```
|
||||
|
||||
```yaml
|
||||
# /share/np-dms/mariadb/docker-compose.yml
|
||||
x-restart: &restart_policy
|
||||
restart: unless-stopped
|
||||
|
||||
x-logging: &default_logging
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "5"
|
||||
|
||||
networks:
|
||||
lcbp3:
|
||||
external: true
|
||||
|
||||
services:
|
||||
mariadb:
|
||||
<<: [*restart_policy, *default_logging]
|
||||
image: mariadb:11.8
|
||||
container_name: mariadb
|
||||
stdin_open: true
|
||||
tty: true
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "2.0"
|
||||
memory: 4G
|
||||
reservations:
|
||||
cpus: "0.5"
|
||||
memory: 1G
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: "<ROOT_PASSWORD>"
|
||||
MYSQL_DATABASE: "lcbp3"
|
||||
MYSQL_USER: "center"
|
||||
MYSQL_PASSWORD: "<PASSWORD>"
|
||||
TZ: "Asia/Bangkok"
|
||||
ports:
|
||||
- "3306:3306"
|
||||
networks:
|
||||
- lcbp3
|
||||
volumes:
|
||||
- "/share/np-dms/mariadb/data:/var/lib/mysql"
|
||||
- "/share/np-dms/mariadb/my.cnf:/etc/mysql/conf.d/my.cnf:ro"
|
||||
- "/share/np-dms/mariadb/init:/docker-entrypoint-initdb.d:ro"
|
||||
healthcheck:
|
||||
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
pma:
|
||||
<<: [*restart_policy, *default_logging]
|
||||
image: phpmyadmin:5-apache
|
||||
container_name: pma
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "0.25"
|
||||
memory: 256M
|
||||
environment:
|
||||
TZ: "Asia/Bangkok"
|
||||
PMA_HOST: "mariadb"
|
||||
PMA_PORT: "3306"
|
||||
PMA_ABSOLUTE_URI: "https://pma.np-dms.work/"
|
||||
UPLOAD_LIMIT: "1G"
|
||||
MEMORY_LIMIT: "512M"
|
||||
ports:
|
||||
- "89:80"
|
||||
networks:
|
||||
- lcbp3
|
||||
volumes:
|
||||
- "/share/np-dms/pma/config.user.inc.php:/etc/phpmyadmin/config.user.inc.php:ro"
|
||||
- "/share/np-dms/pma/tmp:/var/lib/phpmyadmin/tmp:rw"
|
||||
depends_on:
|
||||
mariadb:
|
||||
condition: service_healthy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## A.2 Cache + Search Stack (`services`)
|
||||
|
||||
> **Path:** `/share/np-dms/services/docker-compose.yml`
|
||||
> **Application name:** `services` — Services: `cache` (Redis 7.2), `search` (Elasticsearch 8.11)
|
||||
|
||||
### Pre-requisite: File Permissions
|
||||
|
||||
```bash
|
||||
mkdir -p /share/np-dms/services/cache/data
|
||||
mkdir -p /share/np-dms/services/search/data
|
||||
|
||||
# Redis (UID 999)
|
||||
chown -R 999:999 /share/np-dms/services/cache/data
|
||||
chmod -R 750 /share/np-dms/services/cache/data
|
||||
|
||||
# Elasticsearch (UID 1000)
|
||||
chown -R 1000:1000 /share/np-dms/services/search/data
|
||||
chmod -R 750 /share/np-dms/services/search/data
|
||||
```
|
||||
|
||||
```yaml
|
||||
# /share/np-dms/services/docker-compose.yml
|
||||
x-restart: &restart_policy
|
||||
restart: unless-stopped
|
||||
|
||||
x-logging: &default_logging
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "5"
|
||||
|
||||
networks:
|
||||
lcbp3:
|
||||
external: true
|
||||
|
||||
services:
|
||||
cache:
|
||||
<<: [*restart_policy, *default_logging]
|
||||
image: redis:7-alpine
|
||||
container_name: cache
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "1.0"
|
||||
memory: 2G
|
||||
reservations:
|
||||
cpus: "0.25"
|
||||
memory: 512M
|
||||
environment:
|
||||
TZ: "Asia/Bangkok"
|
||||
ports:
|
||||
- "6379:6379"
|
||||
networks:
|
||||
- lcbp3
|
||||
volumes:
|
||||
- "/share/np-dms/services/cache/data:/data"
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
search:
|
||||
<<: [*restart_policy, *default_logging]
|
||||
image: elasticsearch:8.11.1
|
||||
container_name: search
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "2.0"
|
||||
memory: 4G
|
||||
reservations:
|
||||
cpus: "0.5"
|
||||
memory: 2G
|
||||
environment:
|
||||
TZ: "Asia/Bangkok"
|
||||
discovery.type: "single-node"
|
||||
xpack.security.enabled: "false"
|
||||
# Heap locked at 1GB per ADR-005
|
||||
ES_JAVA_OPTS: "-Xms1g -Xmx1g"
|
||||
ports:
|
||||
- "9200:9200"
|
||||
networks:
|
||||
- lcbp3
|
||||
volumes:
|
||||
- "/share/np-dms/services/search/data:/usr/share/elasticsearch/data"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -s http://localhost:9200/_cluster/health | grep -q '\"status\":\"green\"\\|\"status\":\"yellow\"'"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## A.3 Nginx Proxy Manager (`lcbp3-npm`)
|
||||
|
||||
> **Path:** `/share/np-dms/npm/docker-compose.yml`
|
||||
> **Application name:** `lcbp3-npm` — Services: `npm`, `landing`
|
||||
|
||||
### NPM Proxy Host Config Reference
|
||||
|
||||
| Domain | Forward Host | Port | Cache | Block Exploits | WebSocket | SSL |
|
||||
| :-------------------- | :----------- | :--- | :---- | :------------- | :-------- | :--- |
|
||||
| `backend.np-dms.work` | `backend` | 3000 | ❌ | ✅ | ❌ | ✅ |
|
||||
| `lcbp3.np-dms.work` | `frontend` | 3000 | ✅ | ✅ | ✅ | ✅ |
|
||||
| `git.np-dms.work` | `gitea` | 3000 | ✅ | ✅ | ✅ | ✅ |
|
||||
| `n8n.np-dms.work` | `n8n` | 5678 | ✅ | ✅ | ✅ | ✅ |
|
||||
| `chat.np-dms.work` | `rocketchat` | 3000 | ✅ | ✅ | ✅ | ✅ |
|
||||
| `npm.np-dms.work` | `npm` | 81 | ❌ | ✅ | ✅ | ✅ |
|
||||
| `pma.np-dms.work` | `pma` | 80 | ✅ | ✅ | ❌ | ✅ |
|
||||
|
||||
```yaml
|
||||
# /share/np-dms/npm/docker-compose.yml
|
||||
x-restart: &restart_policy
|
||||
restart: unless-stopped
|
||||
|
||||
x-logging: &default_logging
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "5"
|
||||
|
||||
networks:
|
||||
lcbp3:
|
||||
external: true
|
||||
giteanet:
|
||||
external: true
|
||||
name: gitnet
|
||||
|
||||
services:
|
||||
npm:
|
||||
<<: [*restart_policy, *default_logging]
|
||||
image: jc21/nginx-proxy-manager:latest
|
||||
container_name: npm
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "1.0"
|
||||
memory: 512M
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "81:81"
|
||||
environment:
|
||||
TZ: "Asia/Bangkok"
|
||||
DB_MYSQL_HOST: "mariadb"
|
||||
DB_MYSQL_PORT: 3306
|
||||
DB_MYSQL_USER: "npm"
|
||||
DB_MYSQL_PASSWORD: "npm"
|
||||
DB_MYSQL_NAME: "npm"
|
||||
DISABLE_IPV6: "true"
|
||||
networks:
|
||||
- lcbp3
|
||||
- giteanet
|
||||
volumes:
|
||||
- "/share/np-dms/npm/data:/data"
|
||||
- "/share/np-dms/npm/letsencrypt:/etc/letsencrypt"
|
||||
- "/share/np-dms/npm/custom:/data/nginx/custom"
|
||||
|
||||
landing:
|
||||
image: nginx:1.27-alpine
|
||||
container_name: landing
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- "/share/np-dms/npm/landing:/usr/share/nginx/html:ro"
|
||||
networks:
|
||||
- lcbp3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## A.4 Gitea (`git`)
|
||||
|
||||
> **Path:** `/share/np-dms/git/docker-compose.yml`
|
||||
> **Application name:** `git` — Service: `gitea` (rootless)
|
||||
|
||||
### Pre-requisite: File Permissions
|
||||
|
||||
```bash
|
||||
chown -R 1000:1000 /share/np-dms/gitea/
|
||||
setfacl -m u:1000:rwx /share/np-dms/gitea/etc \
|
||||
/share/np-dms/gitea/lib \
|
||||
/share/np-dms/gitea/backup
|
||||
```
|
||||
|
||||
```yaml
|
||||
# /share/np-dms/git/docker-compose.yml
|
||||
networks:
|
||||
lcbp3:
|
||||
external: true
|
||||
giteanet:
|
||||
external: true
|
||||
name: gitnet
|
||||
|
||||
services:
|
||||
gitea:
|
||||
image: gitea/gitea:latest-rootless
|
||||
container_name: gitea
|
||||
restart: always
|
||||
environment:
|
||||
USER_UID: "1000"
|
||||
USER_GID: "1000"
|
||||
TZ: Asia/Bangkok
|
||||
GITEA__server__ROOT_URL: https://git.np-dms.work/
|
||||
GITEA__server__DOMAIN: git.np-dms.work
|
||||
GITEA__server__SSH_DOMAIN: git.np-dms.work
|
||||
GITEA__server__START_SSH_SERVER: "true"
|
||||
GITEA__server__SSH_PORT: "22"
|
||||
GITEA__server__SSH_LISTEN_PORT: "22"
|
||||
GITEA__server__LFS_START_SERVER: "true"
|
||||
GITEA__server__HTTP_PORT: "3000"
|
||||
GITEA__server__TRUSTED_PROXIES: "127.0.0.1/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
|
||||
GITEA__database__DB_TYPE: mysql
|
||||
GITEA__database__HOST: mariadb:3306
|
||||
GITEA__database__NAME: "gitea"
|
||||
GITEA__database__USER: "gitea"
|
||||
GITEA__database__PASSWD: "<PASSWORD>"
|
||||
GITEA__packages__ENABLED: "true"
|
||||
GITEA__security__INSTALL_LOCK: "true"
|
||||
volumes:
|
||||
- /share/np-dms/gitea/backup:/backup
|
||||
- /share/np-dms/gitea/etc:/etc/gitea
|
||||
- /share/np-dms/gitea/lib:/var/lib/gitea
|
||||
- /share/np-dms/gitea/gitea_repos:/var/lib/gitea/git/repositories
|
||||
- /share/np-dms/gitea/gitea_registry:/data/registry
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
ports:
|
||||
- "3003:3000"
|
||||
- "2222:22"
|
||||
networks:
|
||||
- lcbp3
|
||||
- giteanet
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## A.5 n8n Automation
|
||||
|
||||
> **Path:** `/share/np-dms/n8n/docker-compose.yml`
|
||||
|
||||
```bash
|
||||
chown -R 1000:1000 /share/np-dms/n8n
|
||||
chmod -R 755 /share/np-dms/n8n
|
||||
```
|
||||
|
||||
```yaml
|
||||
# /share/np-dms/n8n/docker-compose.yml
|
||||
x-restart: &restart_policy
|
||||
restart: unless-stopped
|
||||
|
||||
x-logging: &default_logging
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "5"
|
||||
|
||||
networks:
|
||||
lcbp3:
|
||||
external: true
|
||||
|
||||
services:
|
||||
n8n:
|
||||
<<: [*restart_policy, *default_logging]
|
||||
image: n8nio/n8n:latest
|
||||
container_name: n8n
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "1.5"
|
||||
memory: 2G
|
||||
reservations:
|
||||
cpus: "0.25"
|
||||
memory: 512M
|
||||
environment:
|
||||
TZ: "Asia/Bangkok"
|
||||
NODE_ENV: "production"
|
||||
N8N_PUBLIC_URL: "https://n8n.np-dms.work/"
|
||||
WEBHOOK_URL: "https://n8n.np-dms.work/"
|
||||
N8N_HOST: "n8n.np-dms.work"
|
||||
N8N_PORT: 5678
|
||||
N8N_PROTOCOL: "https"
|
||||
N8N_PROXY_HOPS: "1"
|
||||
N8N_DIAGNOSTICS_ENABLED: 'false'
|
||||
N8N_SECURE_COOKIE: 'true'
|
||||
# N8N_ENCRYPTION_KEY should be kept in .env (gitignored)
|
||||
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: 'true'
|
||||
DB_TYPE: mysqldb
|
||||
DB_MYSQLDB_DATABASE: "n8n"
|
||||
DB_MYSQLDB_USER: "center"
|
||||
DB_MYSQLDB_HOST: "mariadb"
|
||||
DB_MYSQLDB_PORT: 3306
|
||||
ports:
|
||||
- "5678:5678"
|
||||
networks:
|
||||
lcbp3: {}
|
||||
volumes:
|
||||
- "/share/np-dms/n8n:/home/node/.n8n"
|
||||
- "/share/np-dms/n8n/cache:/home/node/.cache"
|
||||
- "/share/np-dms/n8n/scripts:/scripts"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:5678/"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## A.6 Application Stack (`lcbp3-app`)
|
||||
|
||||
> **Path:** `/share/np-dms/app/docker-compose.yml`
|
||||
> See the annotated version in [`04-Infrastructure-OPS/docker-compose-app.yml`](./docker-compose-app.yml)
|
||||
|
||||
This stack contains `backend` (NestJS) and `frontend` (Next.js).
|
||||
Refer to [04-04-deployment-guide.md](./04-04-deployment-guide.md) for full deployment steps and CI/CD pipeline details.
|
||||
|
||||
|
||||
@@ -935,3 +935,159 @@ docker exec lcbp3-mariadb mysql -u root -p -e "
|
||||
**Version:** 1.8.0
|
||||
**Last Updated:** 2025-12-02
|
||||
**Next Review:** 2026-06-01
|
||||
|
||||
---
|
||||
|
||||
# Appendix A — QNAP Container Station Deployment
|
||||
|
||||
> 🖥️ **Platform:** QNAP TS-473A · Container Station · Docker Compose App path: `/share/np-dms/app/`
|
||||
|
||||
## A.1 Prerequisites Checklist
|
||||
|
||||
Before deploying `lcbp3-app`, ensure these services are **healthy**:
|
||||
|
||||
| Service | Container Name | Stack |
|
||||
| -------------- | -------------- | ----------------------------- |
|
||||
| MariaDB | `mariadb` | `lcbp3-db` |
|
||||
| Redis | `cache` | `services` |
|
||||
| Elasticsearch | `search` | `services` |
|
||||
| NPM | `npm` | `lcbp3-npm` |
|
||||
| Docker Network | `lcbp3` | `docker network create lcbp3` |
|
||||
|
||||
## A.2 Directory Setup (QNAP SSH)
|
||||
|
||||
```bash
|
||||
mkdir -p /share/np-dms/data/uploads/temp
|
||||
mkdir -p /share/np-dms/data/uploads/permanent
|
||||
mkdir -p /share/np-dms/data/logs/backend
|
||||
mkdir -p /share/np-dms/app
|
||||
|
||||
# UID 1001 = non-root nestjs user in container
|
||||
chown -R 1001:1001 /share/np-dms/data/uploads
|
||||
chown -R 1001:1001 /share/np-dms/data/logs/backend
|
||||
chmod -R 750 /share/np-dms/data/uploads
|
||||
```
|
||||
|
||||
## A.3 Deploy via Container Station UI
|
||||
|
||||
1. เปิด **Container Station** → **Applications** → **Create**
|
||||
2. ตั้งชื่อ Application: `lcbp3-app`
|
||||
3. วาง content จาก `specs/04-Infrastructure-OPS/docker-compose-app.yml`
|
||||
4. แก้ไข Environment Variables ตามต้องการ (secrets ต้องไม่อยู่ใน git)
|
||||
5. กด **Create**
|
||||
|
||||
ตรวจสอบ Container Status: Applications → `lcbp3-app`
|
||||
- ✅ `backend` → Running (healthy)
|
||||
- ✅ `frontend` → Running (healthy)
|
||||
|
||||
## A.4 Verify Deployment
|
||||
|
||||
```bash
|
||||
# Backend health (inside Docker network)
|
||||
docker exec frontend wget -qO- http://backend:3000/health
|
||||
|
||||
# Via NPM
|
||||
curl -I https://lcbp3.np-dms.work
|
||||
curl -I https://backend.np-dms.work/api
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Appendix B — Gitea Actions CI/CD Pipeline
|
||||
|
||||
> 🔄 Automated Build + Deploy on every push to `main`
|
||||
|
||||
## B.1 Setup Gitea Secrets
|
||||
|
||||
Gitea → Repository → Settings → Actions → Secrets → **Add New Secret**:
|
||||
|
||||
| Secret Name | Value | Description |
|
||||
| ----------- | -------------- | ----------------------------- |
|
||||
| `HOST` | `192.168.10.8` | QNAP IP (VLAN 10) |
|
||||
| `PORT` | `22` | SSH Port |
|
||||
| `USERNAME` | `admin` | SSH user with Docker access |
|
||||
| `PASSWORD` | `***` | SSH password (or use SSH Key) |
|
||||
|
||||
## B.2 Pipeline Flow
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Push to main] --> B[Gitea Runner picks up job]
|
||||
B --> C[SSH to QNAP]
|
||||
C --> D[git pull latest code]
|
||||
D --> E[Build Backend Image]
|
||||
E --> F[Build Frontend Image]
|
||||
F --> G[docker compose up -d]
|
||||
G --> H[Cleanup old images]
|
||||
H --> I[Deploy complete ✅]
|
||||
```
|
||||
|
||||
## B.3 Manual Trigger (Re-deploy without code change)
|
||||
|
||||
1. Go to repository → **Actions** tab (top menu)
|
||||
2. Select workflow **"Build and Deploy"**
|
||||
3. Click **"Run workflow"** → Select branch `main` → **Run**
|
||||
|
||||
## B.4 Troubleshooting
|
||||
|
||||
| Error | Cause | Fix |
|
||||
| ---------------------------------------------- | ------------------------------- | --------------------------------------------- |
|
||||
| `No matching runner with label: ubuntu-latest` | Runner not registered / offline | Register act_runner per Appendix C |
|
||||
| `SSH Timeout` | QNAP firewall / ACL | Check VLAN 10 ACL allows runner IP on port 22 |
|
||||
| `Disk Full` | Old images accumulate | `docker image prune -a` on QNAP |
|
||||
| `Build failed: ENOENT .bin/ts-script` | pnpm deploy symlink error | Use `--shamefully-hoist` flag in Dockerfile |
|
||||
|
||||
---
|
||||
|
||||
# Appendix C — Gitea Runner (act_runner) on ASUSTOR
|
||||
|
||||
> **Platform:** ASUSTOR AS5403T · Path: `/volume1/np-dms/gitea-runner/`
|
||||
> **Note:** Gitea is on QNAP, Runner is on ASUSTOR (per Server Role Separation)
|
||||
|
||||
## C.1 Get Registration Token
|
||||
|
||||
Gitea Web UI → **Site Administration** → **Actions** → **Runners** → **Create new Runner** → Copy token
|
||||
|
||||
## C.2 Setup Directory
|
||||
|
||||
```bash
|
||||
ssh asustor
|
||||
mkdir -p /volume1/np-dms/gitea-runner/data
|
||||
```
|
||||
|
||||
## C.3 Docker Compose
|
||||
|
||||
```yaml
|
||||
# /volume1/np-dms/gitea-runner/docker-compose.yml
|
||||
services:
|
||||
runner:
|
||||
image: gitea/act_runner:latest
|
||||
container_name: gitea-runner
|
||||
restart: always
|
||||
environment:
|
||||
GITEA_INSTANCE_URL: https://git.np-dms.work
|
||||
GITEA_RUNNER_REGISTRATION_TOKEN: <paste-token-here>
|
||||
GITEA_RUNNER_NAME: asustor-runner
|
||||
# Label must match runs-on in deploy.yaml
|
||||
GITEA_RUNNER_LABELS: ubuntu-latest:docker://node:18-bullseye,self-hosted:docker://node:18-bullseye
|
||||
volumes:
|
||||
- /volume1/np-dms/gitea-runner/data:/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
```
|
||||
|
||||
```bash
|
||||
cd /volume1/np-dms/gitea-runner
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## C.4 Verify
|
||||
|
||||
Gitea → **Settings** → **Actions** → **Runners** — should show **Total: 1** with green indicator next to `asustor-runner`.
|
||||
|
||||
## C.5 Maintenance
|
||||
|
||||
```bash
|
||||
# Cleanup old build images periodically
|
||||
docker image prune -a # on ASUSTOR (runner images)
|
||||
ssh qnap "docker image prune -a" # on QNAP (app images)
|
||||
```
|
||||
|
||||
@@ -442,3 +442,180 @@ echo "Account compromise response completed for User ID: $USER_ID"
|
||||
**Version:** 1.8.0
|
||||
**Last Review:** 2025-12-01
|
||||
**Next Review:** 2026-03-01
|
||||
|
||||
---
|
||||
|
||||
# Appendix A — SSH Setup (QNAP & ASUSTOR)
|
||||
|
||||
> คู่มือการตั้งค่าและใช้งาน SSH สำหรับ NAS ทั้ง 2 เครื่อง
|
||||
|
||||
## A.1 Connection Info
|
||||
|
||||
| Item | QNAP (TS-473A) | ASUSTOR (AS5403T) |
|
||||
| ------------- | ------------------ | ----------------------- |
|
||||
| **Role** | Application Server | Infrastructure / Backup |
|
||||
| **IP** | `192.168.10.8` | `192.168.10.9` |
|
||||
| **SSH Port** | `22` | `22` |
|
||||
| **SSH Alias** | `qnap` | `asustor` |
|
||||
|
||||
## A.2 Enable SSH on NAS
|
||||
|
||||
**QNAP:** QTS Web UI → Control Panel → Network & File Services → Telnet/SSH → Enable SSH (port 22)
|
||||
|
||||
**ASUSTOR:** ADM Web UI → Settings → Terminal & SNMP → Enable SSH service (port 22)
|
||||
|
||||
## A.3 SSH Key Setup (Client → NAS)
|
||||
|
||||
```powershell
|
||||
# Create key (once on dev machine)
|
||||
ssh-keygen -t ed25519 -C "nattanin@np-dms"
|
||||
|
||||
# Copy public key to NAS
|
||||
ssh-copy-id -i ~/.ssh/id_ed25519.pub nattanin@192.168.10.8 # QNAP
|
||||
ssh-copy-id -i ~/.ssh/id_ed25519.pub nattanin@192.168.10.9 # ASUSTOR
|
||||
```
|
||||
|
||||
## A.4 SSH Config (`~/.ssh/config`)
|
||||
|
||||
```ssh-config
|
||||
Host gitea
|
||||
HostName git.np-dms.work
|
||||
User git
|
||||
Port 2222
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
IdentitiesOnly yes
|
||||
|
||||
Host qnap
|
||||
HostName 192.168.10.8
|
||||
User nattanin
|
||||
Port 22
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
IdentitiesOnly yes
|
||||
|
||||
Host asustor
|
||||
HostName 192.168.10.9
|
||||
User nattanin
|
||||
Port 22
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
IdentitiesOnly yes
|
||||
```
|
||||
|
||||
## A.5 SSH Hardening (`/etc/ssh/sshd_config` on NAS)
|
||||
|
||||
```bash
|
||||
PasswordAuthentication no # Key-only login
|
||||
PermitRootLogin no
|
||||
AllowUsers nattanin
|
||||
|
||||
# Restart SSH
|
||||
/etc/init.d/login_server.sh restart # QNAP
|
||||
/etc/init.d/sshd restart # ASUSTOR
|
||||
```
|
||||
|
||||
> ⚠️ **ตรวจสอบว่า SSH Key ใช้งานได้ก่อนปิด PasswordAuthentication**
|
||||
|
||||
## A.6 SSH Port Forwarding (Useful Tunnels)
|
||||
|
||||
```powershell
|
||||
# MariaDB local tunnel
|
||||
ssh -L 3306:localhost:3306 qnap
|
||||
|
||||
# Elasticsearch tunnel
|
||||
ssh -L 9200:localhost:9200 qnap
|
||||
|
||||
# Grafana tunnel (from ASUSTOR)
|
||||
ssh -L 3000:localhost:3000 asustor
|
||||
```
|
||||
|
||||
## A.7 SSH Troubleshooting
|
||||
|
||||
| Problem | Cause | Fix |
|
||||
| ------------------------------- | ------------------------ | ------------------------------------------------------ |
|
||||
| `Connection refused` | SSH not enabled on NAS | Enable SSH via Web UI |
|
||||
| `Permission denied (publickey)` | Wrong key or permissions | `chmod 700 ~/.ssh`, `chmod 600 ~/.ssh/authorized_keys` |
|
||||
| `Host key verification failed` | IP changed, stale key | `ssh-keygen -R 192.168.10.8` |
|
||||
| `Connection timed out` | Firewall / wrong IP | Check ACL and ping |
|
||||
|
||||
---
|
||||
|
||||
# Appendix B — Secrets Management (QNAP Deployment)
|
||||
|
||||
> ⚠️ **Security Level: CONFIDENTIAL** — ห้าม commit secrets ลง Git
|
||||
|
||||
## B.1 Secret Categories
|
||||
|
||||
| Category | Examples | Storage |
|
||||
| -------------------- | ------------------------------ | ---------------------- |
|
||||
| Database Credentials | `MYSQL_ROOT_PASSWORD` | `.env` (gitignored) |
|
||||
| API Keys | `JWT_SECRET`, `REDIS_PASSWORD` | `.env` (gitignored) |
|
||||
| SSL Certificates | Let's Encrypt | NPM volume |
|
||||
| SSH Keys | Backup access keys | ASUSTOR secure storage |
|
||||
|
||||
## B.2 Environment File (QNAP)
|
||||
|
||||
```bash
|
||||
# File: /share/np-dms/.env
|
||||
# ⚠️ MUST be in .gitignore
|
||||
|
||||
# === Database ===
|
||||
MYSQL_ROOT_PASSWORD=<strong-password>
|
||||
MYSQL_DATABASE=lcbp3
|
||||
MYSQL_USER=center
|
||||
MYSQL_PASSWORD=<strong-password>
|
||||
|
||||
# === Redis ===
|
||||
REDIS_PASSWORD=<strong-password>
|
||||
|
||||
# === Application ===
|
||||
JWT_SECRET=<random-64-hex> # openssl rand -hex 64
|
||||
JWT_REFRESH_SECRET=<random-64-hex>
|
||||
|
||||
# === Monitoring ===
|
||||
GRAFANA_PASSWORD=<admin-password>
|
||||
|
||||
# === External Services ===
|
||||
LINE_CHANNEL_SECRET=<line-secret>
|
||||
LINE_CHANNEL_ACCESS_TOKEN=<line-token>
|
||||
```
|
||||
|
||||
## B.3 Generate Strong Secrets
|
||||
|
||||
```bash
|
||||
openssl rand -base64 32 # Strong password (24+ chars)
|
||||
openssl rand -hex 64 # JWT Secret (64 hex chars)
|
||||
```
|
||||
|
||||
## B.4 Rotation Schedule
|
||||
|
||||
| Secret | Period | Impact |
|
||||
| ----------------- | -------------------- | ----------------------- |
|
||||
| JWT Secret | 90 days | Users re-login required |
|
||||
| Database Password | 180 days | Service restart needed |
|
||||
| Redis Password | 180 days | Service restart needed |
|
||||
| SSL Certificates | Auto (Let's Encrypt) | None |
|
||||
|
||||
## B.5 Encrypted Secret Backup
|
||||
|
||||
```bash
|
||||
# Encrypt and back up to ASUSTOR
|
||||
gpg --symmetric --cipher-algo AES256 /share/np-dms/.env -o /tmp/env.gpg
|
||||
scp /tmp/env.gpg admin@192.168.10.9:/volume1/backup/secrets/
|
||||
rm /tmp/env.gpg
|
||||
|
||||
# Restore
|
||||
scp admin@192.168.10.9:/volume1/backup/secrets/env.gpg /tmp/
|
||||
gpg --decrypt /tmp/env.gpg > /share/np-dms/.env
|
||||
rm /tmp/env.gpg
|
||||
```
|
||||
|
||||
## B.6 Gitea Actions Secrets (CI/CD)
|
||||
|
||||
Configure at: Gitea → Repository → Settings → Actions → Secrets
|
||||
|
||||
| Secret Name | Description |
|
||||
| ----------- | ----------------------------- |
|
||||
| `HOST` | QNAP IP (`192.168.10.8`) |
|
||||
| `PORT` | SSH Port (`22`) |
|
||||
| `USERNAME` | SSH user with Docker access |
|
||||
| `PASSWORD` | SSH password (prefer SSH Key) |
|
||||
|
||||
|
||||
@@ -16,15 +16,24 @@ It consolidates what was previously split across multiple operations and specifi
|
||||
|
||||
## 📂 Document Index
|
||||
|
||||
| File | Purpose | Key Contents |
|
||||
| ------------------------------------------------------------------------ | ---------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| **[04-01-docker-compose.md](./04-01-docker-compose.md)** | Core Environment Setup | `.env` configs, Blue/Green Docker Compose, MariaDB & Redis optimization |
|
||||
| **[04-02-backup-recovery.md](./04-02-backup-recovery.md)** | Disaster Recovery | RTO/RPO strategies, QNAP to ASUSTOR backup scripts, Restic/Mysqldump config |
|
||||
| **[04-03-monitoring.md](./04-03-monitoring.md)** | Observability | Prometheus metrics, AlertManager rules (inclusive of Document Numbering DB), Grafana alerts |
|
||||
| **[04-04-deployment-guide.md](./04-04-deployment-guide.md)** | Production Rollout | Step-by-step Blue-Green deployment scripts, rollback playbooks, Nginx Reverse Proxy |
|
||||
| **[04-05-maintenance-procedures.md](./04-05-maintenance-procedures.md)** | Routine Care | Log rotation, dependency zero-downtime updates, scheduled DB optimizations |
|
||||
| **[04-06-security-operations.md](./04-06-security-operations.md)** | Hardening & Audit | User access review scripts, SSL renewals, vulnerability scanning procedures |
|
||||
| **[04-07-incident-response.md](./04-07-incident-response.md)** | Escalation | P0-P3 classifications, incident commander roles, Post-Incident Review (PIR) |
|
||||
| File | Purpose | Key Contents |
|
||||
| ------------------------------------------------------------------------ | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **[04-01-docker-compose.md](./04-01-docker-compose.md)** | Core Environment Setup | `.env` configs, Blue/Green Docker Compose, MariaDB & Redis optimization, **Appendix A: Live QNAP configs** (MariaDB, Redis/ES, NPM, Gitea, n8n) |
|
||||
| **[04-02-backup-recovery.md](./04-02-backup-recovery.md)** | Disaster Recovery | RTO/RPO strategies, QNAP to ASUSTOR backup scripts, Restic/Mysqldump config |
|
||||
| **[04-03-monitoring.md](./04-03-monitoring.md)** | Observability | Prometheus metrics, AlertManager rules, Grafana alerts |
|
||||
| **[04-04-deployment-guide.md](./04-04-deployment-guide.md)** | Production Rollout | Blue-Green deployment scripts, **Appendix A: QNAP Container Station**, **Appendix B: Gitea Actions CI/CD**, **Appendix C: act_runner setup** |
|
||||
| **[04-05-maintenance-procedures.md](./04-05-maintenance-procedures.md)** | Routine Care | Log rotation, dependency updates, scheduled DB optimizations |
|
||||
| **[04-06-security-operations.md](./04-06-security-operations.md)** | Hardening & Audit | User access review, SSL renewals, vulnerability scanning, **Appendix A: SSH Setup**, **Appendix B: Secrets Management** |
|
||||
| **[04-07-incident-response.md](./04-07-incident-response.md)** | Escalation | P0-P3 classifications, incident commander roles, Post-Incident Review |
|
||||
|
||||
### 🐳 Live Docker Compose Files (QNAP)
|
||||
|
||||
| File | Application | Path on QNAP |
|
||||
| ------------------------------------------------------ | ---------------------------------------------- | ----------------------------- |
|
||||
| **[docker-compose-app.yml](./docker-compose-app.yml)** | `lcbp3-app` (backend + frontend) | `/share/np-dms/app/` |
|
||||
| **[lcbp3-monitoring.yml](./lcbp3-monitoring.yml)** | `lcbp3-monitoring` (Prometheus, Grafana, etc.) | `/volume1/np-dms/monitoring/` |
|
||||
| **[lcbp3-registry.yml](./lcbp3-registry.yml)** | `lcbp3-registry` (Docker Registry) | `/volume1/np-dms/registry/` |
|
||||
| **[grafana/](./grafana/)** | Grafana dashboard JSON configs | Imported via Grafana UI |
|
||||
|
||||
---
|
||||
|
||||
|
||||
122
specs/04-Infrastructure-OPS/docker-compose-app.yml
Normal file
122
specs/04-Infrastructure-OPS/docker-compose-app.yml
Normal file
@@ -0,0 +1,122 @@
|
||||
# File: /share/np-dms/app/docker-compose.yml
|
||||
# DMS Container v1.7.0: Application Stack (Backend + Frontend)
|
||||
# Application name: lcbp3-app
|
||||
# ============================================================
|
||||
# ⚠️ ใช้งานร่วมกับ services อื่นที่รันอยู่แล้วบน QNAP:
|
||||
# - mariadb (lcbp3-db)
|
||||
# - cache (services)
|
||||
# - search (services)
|
||||
# - npm (lcbp3-npm)
|
||||
# ============================================================
|
||||
|
||||
x-restart: &restart_policy
|
||||
restart: unless-stopped
|
||||
|
||||
x-logging: &default_logging
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
options:
|
||||
max-size: '10m'
|
||||
max-file: '5'
|
||||
|
||||
networks:
|
||||
lcbp3:
|
||||
external: true
|
||||
|
||||
services:
|
||||
# ----------------------------------------------------------------
|
||||
# 1. Backend API (NestJS)
|
||||
# Service Name: backend (ตามที่ NPM อ้างอิง → backend:3000)
|
||||
# ----------------------------------------------------------------
|
||||
backend:
|
||||
<<: [*restart_policy, *default_logging]
|
||||
image: lcbp3-backend:latest
|
||||
container_name: backend
|
||||
stdin_open: true
|
||||
tty: true
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '2.0'
|
||||
memory: 1536M
|
||||
reservations:
|
||||
cpus: '0.5'
|
||||
memory: 512M
|
||||
environment:
|
||||
TZ: 'Asia/Bangkok'
|
||||
NODE_ENV: 'production'
|
||||
# --- Database ---
|
||||
DB_HOST: 'mariadb'
|
||||
DB_PORT: '3306'
|
||||
DB_DATABASE: 'lcbp3'
|
||||
DB_USERNAME: 'center'
|
||||
DB_PASSWORD: 'Center#2025'
|
||||
# --- Redis ---
|
||||
REDIS_HOST: 'cache'
|
||||
REDIS_PORT: '6379'
|
||||
REDIS_PASSWORD: 'Center2025'
|
||||
# --- Elasticsearch ---
|
||||
ELASTICSEARCH_HOST: 'search'
|
||||
ELASTICSEARCH_PORT: '9200'
|
||||
# --- JWT ---
|
||||
JWT_SECRET: 'eebc122aa65adde8c76c6a0847d9649b2b67a06db1504693e6c912e51499b76e'
|
||||
JWT_EXPIRATION: '8h'
|
||||
JWT_REFRESH_SECRET: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2'
|
||||
# --- Numbering ---
|
||||
NUMBERING_LOCK_TIMEOUT: '5000'
|
||||
NUMBERING_RESERVATION_TTL: '300'
|
||||
# --- File Upload ---
|
||||
UPLOAD_TEMP_DIR: '/app/uploads/temp'
|
||||
UPLOAD_PERMANENT_DIR: '/app/uploads/permanent'
|
||||
MAX_FILE_SIZE: '52428800'
|
||||
networks:
|
||||
- lcbp3
|
||||
volumes:
|
||||
# Two-Phase Storage: จัดเก็บไฟล์นอก container
|
||||
- '/share/np-dms/data/uploads/temp:/app/uploads/temp'
|
||||
- '/share/np-dms/data/uploads/permanent:/app/uploads/permanent'
|
||||
- '/share/np-dms/data/logs/backend:/app/logs'
|
||||
healthcheck:
|
||||
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 2. Frontend Web App (Next.js)
|
||||
# Service Name: frontend (ตามที่ NPM อ้างอิง → frontend:3000)
|
||||
# ----------------------------------------------------------------
|
||||
frontend:
|
||||
<<: [*restart_policy, *default_logging]
|
||||
image: lcbp3-frontend:latest
|
||||
container_name: frontend
|
||||
stdin_open: true
|
||||
tty: true
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '2.0'
|
||||
memory: 2G
|
||||
reservations:
|
||||
cpus: '0.25'
|
||||
memory: 512M
|
||||
environment:
|
||||
TZ: 'Asia/Bangkok'
|
||||
NODE_ENV: 'production'
|
||||
HOSTNAME: '0.0.0.0'
|
||||
PORT: '3000'
|
||||
# --- NextAuth ---
|
||||
AUTH_SECRET: 'eebc122aa65adde8c76c6a0847d9649b2b67a06db1504693e6c912e51499b76e'
|
||||
AUTH_URL: 'https://lcbp3.np-dms.work'
|
||||
networks:
|
||||
- lcbp3
|
||||
healthcheck:
|
||||
test: ['CMD', 'curl', '-f', 'http://localhost:3000/']
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
@@ -0,0 +1,810 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "grafana",
|
||||
"uid": "-- Grafana --"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": 18,
|
||||
"links": [],
|
||||
"liveNow": false,
|
||||
"panels": [
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 18,
|
||||
"panels": [],
|
||||
"title": "Logs",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "loki",
|
||||
"uid": "a78950eb-fe7b-48c5-bbb5-7ef22a250c29"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 41,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 1
|
||||
},
|
||||
"id": 6,
|
||||
"options": {
|
||||
"dedupStrategy": "none",
|
||||
"enableLogDetails": true,
|
||||
"prettifyLogMessage": false,
|
||||
"showCommonLabels": false,
|
||||
"showLabels": false,
|
||||
"showTime": true,
|
||||
"sortOrder": "Descending",
|
||||
"wrapLogMessage": false
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "loki",
|
||||
"uid": "a78950eb-fe7b-48c5-bbb5-7ef22a250c29"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "{container=~\"$container\"} |= ``",
|
||||
"queryType": "range",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Container Logs",
|
||||
"type": "logs"
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 42
|
||||
},
|
||||
"id": 16,
|
||||
"panels": [],
|
||||
"title": "CPU",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "bd6668d2-9dc4-40a7-8cd2-8b8c9719b1fb"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "percent"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 43
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "bd6668d2-9dc4-40a7-8cd2-8b8c9719b1fb"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "rate(container_cpu_usage_seconds_total{image!=\"\",name=~\"$container\",instance=~\"$host\"}[5m]) * 100",
|
||||
"legendFormat": "{{name}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "CPU Usage (%)",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 51
|
||||
},
|
||||
"id": 22,
|
||||
"panels": [],
|
||||
"title": "Memory",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "bd6668d2-9dc4-40a7-8cd2-8b8c9719b1fb"
|
||||
},
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 52
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 23,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
"avg": true,
|
||||
"current": false,
|
||||
"max": true,
|
||||
"min": false,
|
||||
"rightSide": true,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": true
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null as zero",
|
||||
"options": {
|
||||
"alertThreshold": true
|
||||
},
|
||||
"percentage": false,
|
||||
"pluginVersion": "10.2.2",
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": true,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "bd6668d2-9dc4-40a7-8cd2-8b8c9719b1fb"
|
||||
},
|
||||
"expr": "sum(container_memory_rss{instance=~\"$host\",name=~\"$container\",name=~\".+\"}) by (name)",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "{{name}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeRegions": [],
|
||||
"title": "Memory Usage",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"mode": "time",
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"$$hashKey": "object:606",
|
||||
"format": "bytes",
|
||||
"logBase": 1,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"$$hashKey": "object:607",
|
||||
"format": "short",
|
||||
"logBase": 1,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "bd6668d2-9dc4-40a7-8cd2-8b8c9719b1fb"
|
||||
},
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 52
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 24,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
"avg": true,
|
||||
"current": false,
|
||||
"max": true,
|
||||
"min": false,
|
||||
"rightSide": true,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": true
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null as zero",
|
||||
"options": {
|
||||
"alertThreshold": true
|
||||
},
|
||||
"percentage": false,
|
||||
"pluginVersion": "10.2.2",
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": true,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "bd6668d2-9dc4-40a7-8cd2-8b8c9719b1fb"
|
||||
},
|
||||
"expr": "sum(container_memory_cache{instance=~\"$host\",name=~\"$container\",name=~\".+\"}) by (name)",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "{{name}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeRegions": [],
|
||||
"title": "Memory Cached",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"mode": "time",
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"$$hashKey": "object:606",
|
||||
"format": "bytes",
|
||||
"logBase": 1,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"$$hashKey": "object:607",
|
||||
"format": "short",
|
||||
"logBase": 1,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 60
|
||||
},
|
||||
"id": 21,
|
||||
"panels": [],
|
||||
"title": "Network",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "bd6668d2-9dc4-40a7-8cd2-8b8c9719b1fb"
|
||||
},
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 61
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 25,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
"avg": true,
|
||||
"current": false,
|
||||
"hideEmpty": false,
|
||||
"hideZero": false,
|
||||
"max": true,
|
||||
"min": false,
|
||||
"rightSide": true,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": true
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null",
|
||||
"options": {
|
||||
"alertThreshold": true
|
||||
},
|
||||
"percentage": false,
|
||||
"pluginVersion": "10.2.2",
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "bd6668d2-9dc4-40a7-8cd2-8b8c9719b1fb"
|
||||
},
|
||||
"expr": "sum(rate(container_network_receive_bytes_total{instance=~\"$host\",name=~\"$container\",name=~\".+\"}[5m])) by (name)",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "{{name}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeRegions": [],
|
||||
"title": "Received Network Traffic",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"mode": "time",
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"$$hashKey": "object:674",
|
||||
"format": "Bps",
|
||||
"logBase": 1,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"$$hashKey": "object:675",
|
||||
"format": "short",
|
||||
"logBase": 1,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "bd6668d2-9dc4-40a7-8cd2-8b8c9719b1fb"
|
||||
},
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 61
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 26,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
"avg": true,
|
||||
"current": false,
|
||||
"max": true,
|
||||
"min": false,
|
||||
"rightSide": true,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": true
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null",
|
||||
"options": {
|
||||
"alertThreshold": true
|
||||
},
|
||||
"percentage": false,
|
||||
"pluginVersion": "10.2.2",
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "bd6668d2-9dc4-40a7-8cd2-8b8c9719b1fb"
|
||||
},
|
||||
"expr": "sum(rate(container_network_transmit_bytes_total{instance=~\"$host\",name=~\"$container\",name=~\".+\"}[5m])) by (name)",
|
||||
"interval": "",
|
||||
"legendFormat": "{{name}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeRegions": [],
|
||||
"title": "Sent Network Traffic",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"mode": "time",
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"$$hashKey": "object:832",
|
||||
"format": "Bps",
|
||||
"logBase": 1,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"$$hashKey": "object:833",
|
||||
"format": "short",
|
||||
"logBase": 1,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 69
|
||||
},
|
||||
"id": 19,
|
||||
"panels": [],
|
||||
"title": "Misc",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "bd6668d2-9dc4-40a7-8cd2-8b8c9719b1fb"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"align": "auto",
|
||||
"cellOptions": {
|
||||
"type": "auto"
|
||||
},
|
||||
"filterable": false,
|
||||
"inspect": false
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "id"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.width",
|
||||
"value": 260
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Running"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "d"
|
||||
},
|
||||
{
|
||||
"id": "decimals",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.cellOptions",
|
||||
"value": {
|
||||
"type": "color-text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"fixedColor": "dark-green",
|
||||
"mode": "fixed"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 10,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 70
|
||||
},
|
||||
"id": 20,
|
||||
"options": {
|
||||
"cellHeight": "sm",
|
||||
"footer": {
|
||||
"countRows": false,
|
||||
"fields": "",
|
||||
"reducer": [
|
||||
"sum"
|
||||
],
|
||||
"show": false
|
||||
},
|
||||
"showHeader": true,
|
||||
"sortBy": []
|
||||
},
|
||||
"pluginVersion": "10.2.2",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "bd6668d2-9dc4-40a7-8cd2-8b8c9719b1fb"
|
||||
},
|
||||
"expr": "(time() - container_start_time_seconds{instance=~\"$host\",name=~\"$container\",name=~\".+\"})/86400",
|
||||
"format": "table",
|
||||
"instant": true,
|
||||
"interval": "",
|
||||
"legendFormat": "{{name}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Containers Info",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "filterFieldsByName",
|
||||
"options": {
|
||||
"include": {
|
||||
"names": [
|
||||
"container_label_com_docker_compose_project",
|
||||
"container_label_com_docker_compose_project_working_dir",
|
||||
"image",
|
||||
"instance",
|
||||
"name",
|
||||
"Value",
|
||||
"container_label_com_docker_compose_service"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {
|
||||
"excludeByName": {},
|
||||
"indexByName": {},
|
||||
"renameByName": {
|
||||
"Value": "Running",
|
||||
"container_label_com_docker_compose_project": "Label",
|
||||
"container_label_com_docker_compose_project_working_dir": "Working dir",
|
||||
"container_label_com_docker_compose_service": "Service",
|
||||
"image": "Registry Image",
|
||||
"instance": "Instance",
|
||||
"name": "Name"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "table"
|
||||
}
|
||||
],
|
||||
"refresh": "10s",
|
||||
"schemaVersion": 38,
|
||||
"tags": [
|
||||
"docker",
|
||||
"monitoring",
|
||||
"lcbp3"
|
||||
],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"current": {
|
||||
"selected": false,
|
||||
"text": "All",
|
||||
"value": "$__all"
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "bd6668d2-9dc4-40a7-8cd2-8b8c9719b1fb"
|
||||
},
|
||||
"definition": "label_values(container_cpu_usage_seconds_total{image!=\"\"}, instance)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": "Host",
|
||||
"multi": false,
|
||||
"name": "host",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "label_values(container_cpu_usage_seconds_total{image!=\"\"}, instance)",
|
||||
"refId": "StandardVariableQuery"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "/(.*)/",
|
||||
"skipUrlSync": false,
|
||||
"sort": 1,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"allValue": ".+",
|
||||
"current": {
|
||||
"selected": true,
|
||||
"text": [
|
||||
"All"
|
||||
],
|
||||
"value": [
|
||||
"$__all"
|
||||
]
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "bd6668d2-9dc4-40a7-8cd2-8b8c9719b1fb"
|
||||
},
|
||||
"definition": "label_values(container_cpu_usage_seconds_total{instance=~\"$host\", image!=\"\"}, name)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": "Container",
|
||||
"multi": true,
|
||||
"name": "container",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "label_values(container_cpu_usage_seconds_total{instance=~\"$host\", image!=\"\"}, name)",
|
||||
"refId": "StandardVariableQuery"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "/^\\/?(.*)$/",
|
||||
"skipUrlSync": false,
|
||||
"sort": 1,
|
||||
"type": "query"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "Data Center Overview (Loki + Prometheus)",
|
||||
"uid": "lcbp3-docker-metrics-logs",
|
||||
"version": 5,
|
||||
"weekStart": ""
|
||||
}
|
||||
232
specs/04-Infrastructure-OPS/lcbp3-monitoring.yml
Normal file
232
specs/04-Infrastructure-OPS/lcbp3-monitoring.yml
Normal file
@@ -0,0 +1,232 @@
|
||||
# File: /volume1/np-dms/monitoring/docker-compose.yml
|
||||
# DMS Container v1.8.0: Application name: lcbp3-monitoring
|
||||
# Deploy on: ASUSTOR AS5403T
|
||||
# Services: prometheus, grafana, node-exporter, cadvisor, uptime-kuma, loki, promtail
|
||||
|
||||
x-restart: &restart_policy
|
||||
restart: unless-stopped
|
||||
|
||||
x-logging: &default_logging
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "5"
|
||||
|
||||
networks:
|
||||
lcbp3:
|
||||
external: true
|
||||
|
||||
services:
|
||||
# ----------------------------------------------------------------
|
||||
# 1. Prometheus (Metrics Collection & Storage)
|
||||
# ----------------------------------------------------------------
|
||||
prometheus:
|
||||
<<: [*restart_policy, *default_logging]
|
||||
image: prom/prometheus:v2.48.0
|
||||
container_name: prometheus
|
||||
stdin_open: true
|
||||
tty: true
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "1.0"
|
||||
memory: 1G
|
||||
reservations:
|
||||
cpus: "0.25"
|
||||
memory: 256M
|
||||
environment:
|
||||
TZ: "Asia/Bangkok"
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
- '--storage.tsdb.path=/prometheus'
|
||||
- '--storage.tsdb.retention.time=30d'
|
||||
- '--web.enable-lifecycle'
|
||||
ports:
|
||||
- "9090:9090"
|
||||
networks:
|
||||
- lcbp3
|
||||
volumes:
|
||||
- "/volume1/np-dms/monitoring/prometheus/config:/etc/prometheus:ro"
|
||||
- "/volume1/np-dms/monitoring/prometheus/data:/prometheus"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "http://localhost:9090/-/healthy"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 2. Grafana (Dashboard & Visualization)
|
||||
# ----------------------------------------------------------------
|
||||
grafana:
|
||||
<<: [*restart_policy, *default_logging]
|
||||
image: grafana/grafana:10.2.2
|
||||
container_name: grafana
|
||||
stdin_open: true
|
||||
tty: true
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "1.0"
|
||||
memory: 512M
|
||||
reservations:
|
||||
cpus: "0.25"
|
||||
memory: 128M
|
||||
environment:
|
||||
TZ: "Asia/Bangkok"
|
||||
GF_SECURITY_ADMIN_USER: admin
|
||||
GF_SECURITY_ADMIN_PASSWORD: "Center#2025"
|
||||
GF_SERVER_ROOT_URL: "https://grafana.np-dms.work"
|
||||
GF_INSTALL_PLUGINS: grafana-clock-panel,grafana-piechart-panel
|
||||
ports:
|
||||
- "3000:3000"
|
||||
networks:
|
||||
- lcbp3
|
||||
volumes:
|
||||
- "/volume1/np-dms/monitoring/grafana/data:/var/lib/grafana"
|
||||
depends_on:
|
||||
- prometheus
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget --spider -q http://localhost:3000/api/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 3. Uptime Kuma (Service Availability Monitoring)
|
||||
# ----------------------------------------------------------------
|
||||
uptime-kuma:
|
||||
<<: [*restart_policy, *default_logging]
|
||||
image: louislam/uptime-kuma:1
|
||||
container_name: uptime-kuma
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "0.5"
|
||||
memory: 256M
|
||||
environment:
|
||||
TZ: "Asia/Bangkok"
|
||||
ports:
|
||||
- "3001:3001"
|
||||
networks:
|
||||
- lcbp3
|
||||
volumes:
|
||||
- "/volume1/np-dms/monitoring/uptime-kuma/data:/app/data"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:3001/api/entry-page || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 4. Node Exporter (Host Metrics - ASUSTOR)
|
||||
# ----------------------------------------------------------------
|
||||
node-exporter:
|
||||
<<: [*restart_policy, *default_logging]
|
||||
image: prom/node-exporter:v1.7.0
|
||||
container_name: node-exporter
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "0.5"
|
||||
memory: 128M
|
||||
environment:
|
||||
TZ: "Asia/Bangkok"
|
||||
command:
|
||||
- '--path.procfs=/host/proc'
|
||||
- '--path.sysfs=/host/sys'
|
||||
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
|
||||
ports:
|
||||
- "9100:9100"
|
||||
networks:
|
||||
- lcbp3
|
||||
volumes:
|
||||
- /proc:/host/proc:ro
|
||||
- /sys:/host/sys:ro
|
||||
- /:/rootfs:ro
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "http://localhost:9100/metrics"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 5. cAdvisor (Container Metrics - ASUSTOR)
|
||||
# ----------------------------------------------------------------
|
||||
cadvisor:
|
||||
<<: [*restart_policy, *default_logging]
|
||||
image: gcr.io/cadvisor/cadvisor:v0.47.2
|
||||
container_name: cadvisor
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "0.5"
|
||||
memory: 256M
|
||||
environment:
|
||||
TZ: "Asia/Bangkok"
|
||||
ports:
|
||||
- "8088:8088"
|
||||
networks:
|
||||
- lcbp3
|
||||
volumes:
|
||||
- /:/rootfs:ro
|
||||
- /var/run:/var/run:ro
|
||||
- /sys:/sys:ro
|
||||
- /var/lib/docker/:/var/lib/docker:ro
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8080/healthz"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 6. Loki (Log Aggregation)
|
||||
# ----------------------------------------------------------------
|
||||
loki:
|
||||
<<: [*restart_policy, *default_logging]
|
||||
image: grafana/loki:2.9.0
|
||||
container_name: loki
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "0.5"
|
||||
memory: 512M
|
||||
environment:
|
||||
TZ: "Asia/Bangkok"
|
||||
command: -config.file=/etc/loki/local-config.yaml
|
||||
ports:
|
||||
- "3100:3100"
|
||||
networks:
|
||||
- lcbp3
|
||||
volumes:
|
||||
- "/volume1/np-dms/monitoring/loki/data:/loki"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3100/ready"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# 7. Promtail (Log Shipper)
|
||||
# ----------------------------------------------------------------
|
||||
promtail:
|
||||
<<: [*restart_policy, *default_logging]
|
||||
image: grafana/promtail:2.9.0
|
||||
container_name: promtail
|
||||
user: "0:0"
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "0.5"
|
||||
memory: 256M
|
||||
environment:
|
||||
TZ: "Asia/Bangkok"
|
||||
command: -config.file=/etc/promtail/promtail-config.yml
|
||||
networks:
|
||||
- lcbp3
|
||||
volumes:
|
||||
- "/volume1/np-dms/monitoring/promtail/config:/etc/promtail:ro"
|
||||
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
||||
- "/var/lib/docker/containers:/var/lib/docker/containers:ro"
|
||||
depends_on:
|
||||
- loki
|
||||
58
specs/04-Infrastructure-OPS/lcbp3-registry.yml
Normal file
58
specs/04-Infrastructure-OPS/lcbp3-registry.yml
Normal file
@@ -0,0 +1,58 @@
|
||||
# File: /volume1/np-dms/registry/docker-compose.yml
|
||||
# DMS Container v1.8.0: Application name: lcbp3-registry
|
||||
# Deploy on: ASUSTOR AS5403T
|
||||
# Services: registry
|
||||
# ============================================================
|
||||
# ⚠️ ข้อกำหนด:
|
||||
# - ต้องสร้าง Docker Network ก่อน: docker network create lcbp3
|
||||
# - Registry ใช้ Port 5000 (domain: registry.np-dms.work)
|
||||
# - Portainer มีอยู่แล้วบน ASUSTOR (ไม่ต้อง deploy ซ้ำ)
|
||||
# ============================================================
|
||||
|
||||
x-restart: &restart_policy
|
||||
restart: unless-stopped
|
||||
|
||||
x-logging: &default_logging
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
options:
|
||||
max-size: '10m'
|
||||
max-file: '5'
|
||||
|
||||
networks:
|
||||
lcbp3:
|
||||
external: true
|
||||
|
||||
services:
|
||||
# ----------------------------------------------------------------
|
||||
# 1. Docker Registry (Private Container Image Storage)
|
||||
# ----------------------------------------------------------------
|
||||
registry:
|
||||
<<: [*restart_policy, *default_logging]
|
||||
image: registry:2
|
||||
container_name: registry
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.5'
|
||||
memory: 256M
|
||||
reservations:
|
||||
cpus: '0.1'
|
||||
memory: 64M
|
||||
environment:
|
||||
TZ: 'Asia/Bangkok'
|
||||
# --- Storage ---
|
||||
REGISTRY_STORAGE_DELETE_ENABLED: 'true'
|
||||
# --- Garbage Collection (ลบ layer ที่ไม่ใช้) ---
|
||||
REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /var/lib/registry
|
||||
ports:
|
||||
- '5000:5000'
|
||||
networks:
|
||||
- lcbp3
|
||||
volumes:
|
||||
- '/volume1/np-dms/registry/data:/var/lib/registry'
|
||||
healthcheck:
|
||||
test: ['CMD', 'wget', '--spider', '-q', 'http://localhost:5000/v2/']
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
Reference in New Issue
Block a user