# DevOps Audit Report — GoodGo POS Platform

**Auditor**: DevOps Engineer (TechBi Company)
**Date**: 2026-03-20
**Scope**: Docker configs, CI/CD pipelines, Kubernetes manifests, health checks, observability
**Branch**: master

---

## Executive Summary

Infrastructure cơ bản đã được triển khai tốt: multi-stage Docker builds, non-root container users, health probes trên tất cả K8s deployments, và CI/CD pipeline có environment approval cho production. Tuy nhiên có **2 vấn đề critical** cần fix ngay trước khi go-live: production K8s manifests hardcode `:latest` image tag, và Alertmanager chưa được cấu hình dẫn đến toàn bộ alert rules không có đích nhận.

---

## Critical Issues

### CRIT-1: Production K8s manifests hardcode `:latest` image tag

**File**: `deployments/production/kubernetes/iam-service.yaml:45`
```yaml
image: goodgo/iam-service-net:latest  # WRONG
```

**Impact**: Nếu ai chạy `kubectl apply -f` trực tiếp (không qua pipeline), pod sẽ pull `:latest` — không reproducible, không rollback được. Mâu thuẫn hoàn toàn với nguyên tắc "NEVER use :latest tag in production" trong CLAUDE.md và logic pipeline `deploy-production.yml:327`.

**Affected files**: Cần kiểm tra tất cả files trong `deployments/production/kubernetes/` — khả năng cao tất cả service manifests đều bị.

**Fix**: Dùng placeholder `IMAGE_TAG` và sed-replace trong pipeline, hoặc dùng Kustomize/Helm để inject SHA tag.

---

### CRIT-2: Alertmanager không được cấu hình — toàn bộ alert rules im lặng

**File**: `infra/observability/prometheus/prometheus.yml:29`
```yaml
alerting:
  alertmanagers:
    - static_configs:
        - targets: []   # EMPTY — no alerts will fire
```

**Impact**: Toàn bộ `alert-rules.yml` và `rules/service-alerts.yml` định nghĩa đúng (ServiceDown, HighErrorRate, HighLatencyP95...) nhưng **không có receiver nào**. Production incident xảy ra sẽ không có ai được notify.

**Fix**: Deploy Alertmanager và uncomment `targets: ['alertmanager:9093']`. Configure receivers (Slack/PagerDuty/email) cho ít nhất kênh critical.

---

### CRIT-3: `docker-build.yml` push `:latest` tag lên Docker Hub từ branch `main`

**File**: `.github/workflows/docker-build.yml:99-103`
```bash
if [ "$BRANCH" = "main" ]; then
  echo "tags=${IMAGE}:latest,${IMAGE}:${SHA}" >> $GITHUB_OUTPUT
```

**Impact**: `:latest` tag bị overwrite mỗi lần push lên `main`. Mâu thuẫn với `deploy-production.yml` (dùng SHA). Tạo race condition nếu pipeline chạy song song. Tag `:latest` trên Docker Hub không ổn định.

**Fix**: Bỏ `:latest` khỏi production tags. Chỉ dùng `${SHA}` + `production` mutable tag nếu cần.

---

## Warnings

### WARN-1: `redis-exporter` được Prometheus scrape nhưng không tồn tại trong docker-compose

**File**: `infra/observability/prometheus/prometheus.yml:132`
```yaml
- job_name: 'redis'
  static_configs:
    - targets: ['redis-exporter:9121']  # service không tồn tại
```

`deployments/local/docker-compose.yml` không có service `redis-exporter`. Prometheus sẽ báo `target missing` liên tục. Cần thêm `oliver006/redis_exporter` vào compose hoặc xóa scrape job này.

---

### WARN-2: Nhiều microservices trong docker-compose nhưng không có trong CI/CD pipeline

**Missing từ `deploy-staging.yml` và `deploy-production.yml`**:
- `chat-service-net`, `social-service-net`, `mining-service-net`, `mission-service-net`
- `promotion-service-net`, `booking-service-net` (staging), `membership-service-net`
- Toàn bộ 5 ads services (`ads-manager`, `ads-serving`, `ads-billing`, `ads-tracking`, `ads-analytics`)
- `mkt-facebook`, `mkt-whatsapp`, `mkt-x`, `mkt-zalo`

15+ services không có automated deployment. Phải deploy thủ công.

---

### WARN-3: `pr-checks.yml` chỉ kiểm tra Node.js — không có .NET build/test

**File**: `.github/workflows/pr-checks.yml`

PR checks chỉ chạy `pnpm lint`, `pnpm typecheck`, `pnpm build`. Không có step nào build hoặc test .NET services. Broken .NET code có thể merge vào branch mà không bị phát hiện.

**Fix**: Thêm matrix build cho .NET services, hoặc ít nhất `dotnet build` cho các service thay đổi trong PR.

---

### WARN-4: Redis deploy trong K8s là single instance — SPOF

**File**: `deployments/staging/kubernetes/redis.yaml:14`
```yaml
replicas: 1
```

Redis là SignalR backplane cho `chat-service`, `fnb-engine`, `mining-service`. Redis down = toàn bộ real-time features down. Cần Redis Sentinel (HA) hoặc migrate sang Redis Cluster.

---

### WARN-5: Không có Kubernetes NetworkPolicy

Không tìm thấy `NetworkPolicy` manifest nào trong `deployments/staging/kubernetes/` hay `deployments/production/kubernetes/`. Theo principle least-privilege, mặc định tất cả pods có thể communicate với nhau (và ra internet nếu không có egress restriction).

**Fix**: Thêm default-deny NetworkPolicy + whitelist explicit inter-service communication.

---

### WARN-6: MinIO dùng credentials mặc định trong local dev

**File**: `deployments/local/docker-compose.yml:78-79`
```yaml
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=minioadmin123
```

Credentials mặc định well-known. Dù là local dev, nên dùng `.env` file để tránh habit bad practice. Thêm vào `.gitignore` nếu chưa có.

---

### WARN-7: Distributed tracing (Jaeger) bị comment out

**File**: `deployments/local/docker-compose.yml:1230-1241`

Jaeger config được comment out. Không có distributed tracing cho request correlation cross-service. Với 26+ microservices, đây là gap observability nghiêm trọng khi debug production issues.

**Fix**: Enable Jaeger hoặc dùng Grafana Tempo (đã có Loki/Grafana stack).

---

### WARN-8: Traefik Dashboard exposed không có authentication

**File**: `deployments/local/docker-compose.yml:121`
```yaml
- "--api.insecure=true"
```

Traefik dashboard (`http://localhost:8080`) accessible không cần auth. OK cho local dev nhưng cần đảm bảo `api.insecure=false` ở staging/production (K8s ingress không expose dashboard port nên staging OK, nhưng cần verify production Traefik config).

---

### WARN-9: `storage-service` naming inconsistency

Trong `docker-compose.yml` service được gọi là `storage-service` (line 152) nhưng build context là `../../services/storage-service-net`. Trong Traefik routes và K8s manifests cũng không nhất quán. Cần chuẩn hóa.

---

### WARN-10: `Jwt__RequireHttpsMetadata=false` trong staging configmap

**File**: `deployments/staging/kubernetes/configmap.yaml:21`
```yaml
Jwt__RequireHttpsMetadata: "false"
```

Staging đã có TLS (cert-manager + letsencrypt), nhưng JWT validation không require HTTPS. Nếu token bị intercept qua HTTP (misconfigured client), không bị phát hiện.

---

## Improvements

### IMP-1: Áp dụng GitOps với ArgoCD

Hiện tại CI/CD dùng `kubectl apply` trực tiếp từ GitHub Actions. Recommend deploy ArgoCD và dùng GitOps: Git là source of truth, ArgoCD tự sync K8s state. Lợi ích: drift detection, automatic rollback, audit trail, không cần expose KUBECONFIG trong GitHub Secrets.

---

### IMP-2: Thêm PodDisruptionBudget cho production services

Hiện chưa có PDB. Khi node drain/upgrade, tất cả replicas của một service có thể bị terminate cùng lúc. Với `maxUnavailable: 0` trong RollingUpdate — đây là bảo vệ khi rolling update, nhưng không bảo vệ khi voluntary disruption.

```yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: iam-service-pdb
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app: iam-service
```

---

### IMP-3: Pin tất cả Docker image versions trong docker-compose

Hiện tại `minio/minio:latest` (line 74), `grafana/grafana:10.3.1` (OK), `prom/prometheus:v2.51.0` (OK).

Cần pin MinIO: `minio/minio:RELEASE.2024-03-15T01-07-19Z` hoặc version cụ thể.

---

### IMP-4: Thêm health check endpoint `/health/ready` cho web-client-tpos-net

**File**: `deployments/local/docker-compose.yml:1375`
```yaml
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1"]
```

Blazor WASM app chỉ có `/health` (basic), không có readiness probe riêng. K8s sẽ gửi traffic trước khi app thực sự ready.

---

### IMP-5: Thêm image vulnerability scanning vào CI pipeline

Không có bước nào trong `.github/workflows/` scan Docker images cho CVE. Recommend thêm `aquasecurity/trivy-action` sau build step.

---

### IMP-6: Migrate Grafana credentials sang K8s Secret

**File**: `deployments/local/docker-compose.yml:1278`
```yaml
GF_SECURITY_ADMIN_PASSWORD=admin
```

Password `admin` là default. Cần rotate và inject qua Secret, không hardcode trong compose file.

---

### IMP-7: Bổ sung scrape targets còn thiếu trong Prometheus

`prometheus.yml` chỉ scrape 8 services trong khi docker-compose có 20+. Missing: `promotion-service`, `booking-service`, `membership-service`, `social-service`, `mining-service`, `mission-service`, `fnb-engine`, `wallet-service`, `ads-*`.

---

### IMP-8: Thêm Kubernetes Secrets External Operator cho production

`secrets.yaml.example` note "Option 3: Use sealed-secrets or external-secrets operator (recommended for production)" nhưng chưa implement. Nên dùng [External Secrets Operator](https://external-secrets.io) với HashiCorp Vault hoặc AWS Secrets Manager.

---

## Action Items (Prioritized)

| Priority | Issue | File | Effort |
|----------|-------|------|--------|
| P0 | Fix production K8s `:latest` image tags | `deployments/production/kubernetes/*.yaml` | 2h |
| P0 | Configure Alertmanager + receivers | `infra/observability/prometheus/prometheus.yml` | 4h |
| P0 | Remove `:latest` push từ docker-build.yml | `.github/workflows/docker-build.yml:100` | 30m |
| P1 | Thêm redis-exporter vào docker-compose | `deployments/local/docker-compose.yml` | 1h |
| P1 | Thêm .NET build/test vào pr-checks.yml | `.github/workflows/pr-checks.yml` | 2h |
| P1 | Thêm NetworkPolicy manifests | `deployments/*/kubernetes/` | 4h |
| P1 | Mở rộng CI/CD pipelines cho missing services | `.github/workflows/deploy-*.yml` | 8h |
| P2 | Deploy Alertmanager + PagerDuty/Slack | `infra/observability/` | 4h |
| P2 | Redis HA (Sentinel) cho staging/production | `deployments/*/kubernetes/redis.yaml` | 4h |
| P2 | Enable distributed tracing (Jaeger/Tempo) | `deployments/local/docker-compose.yml` | 2h |
| P2 | Thêm PodDisruptionBudget cho production | `deployments/production/kubernetes/` | 2h |
| P2 | Image vulnerability scanning trong CI | `.github/workflows/docker-build.yml` | 1h |
| P3 | Pin MinIO image version | `deployments/local/docker-compose.yml:74` | 15m |
| P3 | Rotate Grafana default password | `deployments/local/docker-compose.yml:1278` | 30m |
| P3 | ArgoCD GitOps adoption | New infra | 2-3 days |
| P3 | External Secrets Operator | `deployments/production/kubernetes/` | 1 day |

---

*Report generated by DevOps Engineer agent (TEC-223) — 2026-03-20*
