API / plans /ci-cd-devops.md
sshinmen's picture
Update HF Spaces deployment with latest changes
2e6b65c
|
Raw
History Blame Contribute Delete
26.1 kB

CI/CD & DevOps Enhancement Plan

Current State

Existing:

  • Basic Go build workflow
  • Dockerfile for containerization
  • Docker Compose setup
  • GoReleaser configuration

Missing:

  • Comprehensive testing in CI
  • Security scanning
  • Multi-environment deployment
  • Infrastructure as Code
  • Automated rollback
  • Release automation

Phase 1: Enhanced CI Pipeline (Week 1)

1.1 Main CI Workflow

File: .github/workflows/ci.yml

name: CI

on:
  push:
    branches: [main, develop]
    tags: ['v*']
  pull_request:
    branches: [main, develop]

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version: '1.24'
          cache: true

      - name: golangci-lint
        uses: golangci/golangci-lint-action@v6
        with:
          version: latest
          args: --timeout=5m --out-format=colored-line-number

      - name: Check formatting
        run: |
          fmt_files=$(gofmt -l .)
          if [ -n "$fmt_files" ]; then
            echo "The following files need formatting:"
            echo "$fmt_files"
            exit 1
          fi

  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: test
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

      redis:
        image: redis:7-alpine
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 6379:6379

      minio:
        image: minio/minio:latest
        env:
          MINIO_ROOT_USER: minioadmin
          MINIO_ROOT_PASSWORD: minioadmin
        ports:
          - 9000:9000
        options: >-
          server /data

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version: '1.24'
          cache: true

      - name: Download dependencies
        run: go mod download

      - name: Run unit tests
        run: go test -race -coverprofile=coverage.out ./internal/... ./sdk/...
        env:
          CGO_ENABLED: 1

      - name: Run integration tests
        run: go test -v -tags=integration ./test/integration/...
        env:
          TEST_POSTGRES_URL: postgres://postgres:postgres@localhost:5432/test?sslmode=disable
          TEST_REDIS_URL: redis://localhost:6379
          TEST_MINIO_ENDPOINT: localhost:9000
          TEST_MINIO_ACCESS_KEY: minioadmin
          TEST_MINIO_SECRET_KEY: minioadmin

      - name: Generate coverage report
        run: |
          go tool cover -html=coverage.out -o coverage.html
          go tool cover -func=coverage.out

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage.out
          fail_ci_if_error: true
          verbose: true

  build:
    runs-on: ubuntu-latest
    needs: [lint, test]
    strategy:
      matrix:
        include:
          - platform: linux/amd64
            goos: linux
            goarch: amd64
          - platform: linux/arm64
            goos: linux
            goarch: arm64
          - platform: darwin/amd64
            goos: darwin
            goarch: amd64
          - platform: darwin/arm64
            goos: darwin
            goarch: arm64
          - platform: windows/amd64
            goos: windows
            goarch: amd64
            extension: .exe

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version: '1.24'
          cache: true

      - name: Build binary
        env:
          GOOS: ${{ matrix.goos }}
          GOARCH: ${{ matrix.goarch }}
          CGO_ENABLED: 0
        run: |
          go build -ldflags "-s -w -X main.version=${{ github.ref_name }} -X main.commit=${{ github.sha }} -X main.date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
            -o dist/cliproxy-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.extension }} \
            ./cmd/server

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: cliproxy-${{ matrix.goos }}-${{ matrix.goarch }}
          path: dist/cliproxy-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.extension }}
          retention-days: 7

  docker:
    runs-on: ubuntu-latest
    needs: [lint, test]
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        if: github.event_name != 'pull_request'
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Login to GHCR
        if: github.event_name != 'pull_request'
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: |
            ${{ secrets.DOCKER_USERNAME }}/cliproxy
            ghcr.io/${{ github.repository }}
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha,prefix=,suffix=,format=short

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          build-args: |
            VERSION=${{ github.ref_name }}
            COMMIT=${{ github.sha }}
            DATE=${{ github.event.head_commit.timestamp }}

      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ghcr.io/${{ github.repository }}:${{ github.sha }}
          format: 'sarif'
          output: 'trivy-results.sarif'

      - name: Upload Trivy results
        uses: github/codeql-action/upload-sarif@v2
        if: always()
        with:
          sarif_file: 'trivy-results.sarif'

1.2 PR Automation

File: .github/workflows/pr-automation.yml

name: PR Automation

on:
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review]

jobs:
  assign:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Auto-assign PR
        uses: actions/github-script@v7
        with:
          script: |
            const pr = context.payload.pull_request;
            if (pr.user.type === 'User') {
              github.rest.issues.addAssignees({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number,
                assignees: [pr.user.login]
              });
            }

  size-label:
    runs-on: ubuntu-latest
    steps:
      - name: Label based on size
        uses: codelytv/pr-size-labeler@v1
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          xs_label: 'size/xs'
          xs_max_size: 50
          s_label: 'size/s'
          s_max_size: 200
          m_label: 'size/m'
          m_max_size: 500
          l_label: 'size/l'
          l_max_size: 1000
          xl_label: 'size/xl'
          fail_if_xl: false

  dependency-review:
    runs-on: ubuntu-latest
    if: github.event_name == 'pull_request'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/dependency-review-action@v3
        with:
          fail-on-severity: high

Phase 2: Release Automation (Week 2)

2.1 Release Workflow

File: .github/workflows/release.yml

name: Release

on:
  push:
    tags:
      - 'v*'

permissions:
  contents: write
  packages: write

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/setup-go@v5
        with:
          go-version: '1.24'
          cache: true

      - name: Generate changelog
        id: changelog
        uses: mikepenz/release-changelog-builder-action@v4
        with:
          configuration: .github/changelog-config.json
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Run GoReleaser
        uses: goreleaser/goreleaser-action@v5
        with:
          distribution: goreleaser
          version: latest
          args: release --clean
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}

  deploy-hf:
    runs-on: ubuntu-latest
    needs: release
    steps:
      - uses: actions/checkout@v4

      - name: Trigger Hugging Face Space update
        run: |
          curl -X POST \
            -H "Authorization: Bearer ${{ secrets.HF_TOKEN }}" \
            https://huggingface.co/api/spaces/${{ secrets.HF_SPACE }}/sync

      - name: Wait for deployment
        run: |
          sleep 60
          curl -s https://huggingface.co/api/spaces/${{ secrets.HF_SPACE }} | jq '.runtime.stage'

2.2 GoReleaser Config

File: .goreleaser.yml

version: 2

before:
  hooks:
    - go mod tidy
    - go generate ./...

builds:
  - id: cliproxy
    main: ./cmd/server
    binary: cliproxy
    env:
      - CGO_ENABLED=0
    goos:
      - linux
      - darwin
      - windows
    goarch:
      - amd64
      - arm64
    goarm:
      - '7'
    ldflags:
      - -s -w
      - -X main.version={{.Version}}
      - -X main.commit={{.Commit}}
      - -X main.date={{.Date}}

archives:
  - id: cliproxy
    name_template: >-
      {{ .ProjectName }}_
      {{- title .Os }}_
      {{- if eq .Arch "amd64" }}x86_64
      {{- else if eq .Arch "386" }}i386
      {{- else }}{{ .Arch }}{{ end }}
    format_overrides:
      - goos: windows
        format: zip
    files:
      - README.md
      - LICENSE
      - config.example.yaml

dockers:
  - image_templates:
      - "ghcr.io/echyai/cliproxy:{{ .Tag }}-amd64"
      - "echyai/cliproxy:{{ .Tag }}-amd64"
    dockerfile: Dockerfile
    use: buildx
    build_flag_templates:
      - --platform=linux/amd64
      - --label=org.opencontainers.image.title={{ .ProjectName }}
      - --label=org.opencontainers.image.description={{ .ProjectName }}
      - --label=org.opencontainers.image.url=https://github.com/echyai/cliproxy
      - --label=org.opencontainers.image.source=https://github.com/echyai/cliproxy
      - --label=org.opencontainers.image.version={{ .Version }}
      - --label=org.opencontainers.image.created={{ .Date }}
      - --label=org.opencontainers.image.revision={{ .FullCommit }}
  - image_templates:
      - "ghcr.io/echyai/cliproxy:{{ .Tag }}-arm64"
      - "echyai/cliproxy:{{ .Tag }}-arm64"
    dockerfile: Dockerfile
    use: buildx
    goarch: arm64
    build_flag_templates:
      - --platform=linux/arm64
      - --label=org.opencontainers.image.title={{ .ProjectName }}
      - --label=org.opencontainers.image.description={{ .ProjectName }}
      - --label=org.opencontainers.image.url=https://github.com/echyai/cliproxy
      - --label=org.opencontainers.image.source=https://github.com/echyai/cliproxy
      - --label=org.opencontainers.image.version={{ .Version }}
      - --label=org.opencontainers.image.created={{ .Date }}
      - --label=org.opencontainers.image.revision={{ .FullCommit }}

docker_manifests:
  - name_template: "echyai/cliproxy:{{ .Tag }}"
    image_templates:
      - "echyai/cliproxy:{{ .Tag }}-amd64"
      - "echyai/cliproxy:{{ .Tag }}-arm64"
  - name_template: "echyai/cliproxy:latest"
    image_templates:
      - "echyai/cliproxy:{{ .Tag }}-amd64"
      - "echyai/cliproxy:{{ .Tag }}-arm64"
  - name_template: "ghcr.io/echyai/cliproxy:{{ .Tag }}"
    image_templates:
      - "ghcr.io/echyai/cliproxy:{{ .Tag }}-amd64"
      - "ghcr.io/echyai/cliproxy:{{ .Tag }}-arm64"
  - name_template: "ghcr.io/echyai/cliproxy:latest"
    image_templates:
      - "ghcr.io/echyai/cliproxy:{{ .Tag }}-amd64"
      - "ghcr.io/echyai/cliproxy:{{ .Tag }}-arm64"

changelog:
  use: github
  sort: asc
  filters:
    exclude:
      - '^docs:'
      - '^test:'
      - '^chore:'
      - '^ci:'
      - Merge pull request
      - Merge branch

release:
  github:
    owner: echyai
    name: cliproxy
  draft: false
  prerelease: auto
  mode: replace
  header: |
    ## CLIProxyAPI {{ .Tag }}

    ### Docker Images
    ```bash
    docker pull echyai/cliproxy:{{ .Tag }}
    docker pull ghcr.io/echyai/cliproxy:{{ .Tag }}
    ```

  footer: |
    ## Quick Start

    ```bash
    # Download and run
    curl -L https://github.com/echyai/cliproxy/releases/download/{{ .Tag }}/cliproxy_Linux_x86_64.tar.gz | tar xz
    ./cliproxy --config config.yaml
    ```

Phase 3: Infrastructure as Code (Week 3)

3.1 Terraform for AWS

File: infrastructure/terraform/main.tf

terraform {
  required_version = ">= 1.5"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }

  backend "s3" {
    bucket         = "cliproxy-terraform-state"
    key            = "infrastructure/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "cliproxy-terraform-locks"
  }
}

provider "aws" {
  region = var.aws_region

  default_tags {
    tags = {
      Project     = "cliproxy"
      Environment = var.environment
      ManagedBy   = "terraform"
    }
  }
}

# VPC
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name = "${var.project_name}-${var.environment}"
  cidr = var.vpc_cidr

  azs             = var.availability_zones
  private_subnets = var.private_subnets
  public_subnets  = var.public_subnets

  enable_nat_gateway   = true
  single_nat_gateway   = var.environment != "production"
  enable_dns_hostnames = true
  enable_dns_support   = true

  public_subnet_tags = {
    "kubernetes.io/role/elb" = "1"
  }

  private_subnet_tags = {
    "kubernetes.io/role/internal-elb" = "1"
  }
}

# EKS Cluster
module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 19.0"

  cluster_name    = "${var.project_name}-${var.environment}"
  cluster_version = "1.29"

  vpc_id                         = module.vpc.vpc_id
  subnet_ids                     = module.vpc.private_subnets
  control_plane_subnet_ids       = module.vpc.intra_subnets
  cluster_endpoint_public_access = true

  eks_managed_node_groups = {
    general = {
      desired_size = var.node_desired_size
      min_size     = var.node_min_size
      max_size     = var.node_max_size

      instance_types = var.node_instance_types
      capacity_type  = var.environment == "production" ? "ON_DEMAND" : "SPOT"

      labels = {
        workload = "general"
      }

      update_config = {
        max_unavailable_percentage = 25
      }
    }
  }

  # Fargate profiles for serverless workloads
  fargate_profiles = {
    default = {
      name = "default"
      selectors = [
        { namespace = "kube-system" },
        { namespace = "default" }
      ]
    }
  }
}

# RDS PostgreSQL
module "rds" {
  source  = "terraform-aws-modules/rds/aws"
  version = "~> 6.0"

  identifier = "${var.project_name}-${var.environment}"

  engine               = "postgres"
  engine_version       = "16"
  family               = "postgres16"
  major_engine_version = "16"
  instance_class       = var.db_instance_class

  allocated_storage     = var.db_allocated_storage
  max_allocated_storage = var.db_max_allocated_storage

  db_name  = var.db_name
  username = var.db_username
  port     = 5432

  multi_az               = var.environment == "production"
  db_subnet_group_name   = module.vpc.database_subnet_group
  vpc_security_group_ids = [aws_security_group.rds.id]

  backup_retention_period = var.environment == "production" ? 7 : 1
  backup_window          = "03:00-04:00"
  maintenance_window     = "Mon:04:00-Mon:05:00"

  deletion_protection = var.environment == "production"

  enabled_cloudwatch_logs_exports = ["postgresql"]

  performance_insights_enabled = var.environment == "production"
}

# ElastiCache Redis
module "redis" {
  source  = "terraform-aws-modules/elasticache/aws"
  version = "~> 1.0"

  cluster_id = "${var.project_name}-${var.environment}"

  engine          = "redis"
  engine_version  = "7.1"
  node_type       = var.redis_node_type
  num_cache_nodes = 1

  subnet_group_name  = aws_elasticache_subnet_group.redis.name
  security_group_ids = [aws_security_group.redis.id]

  at_rest_encryption_enabled = true
  transit_encryption_enabled = true
}

# Application Load Balancer
module "alb" {
  source  = "terraform-aws-modules/alb/aws"
  version = "~> 9.0"

  name = "${var.project_name}-${var.environment}"

  load_balancer_type = "application"

  vpc_id  = module.vpc.vpc_id
  subnets = module.vpc.public_subnets

  security_groups = [aws_security_group.alb.id]

  listeners = {
    https = {
      port            = 443
      protocol        = "HTTPS"
      certificate_arn = aws_acm_certificate.main.arn

      fixed_response = {
        content_type = "text/plain"
        message_body = "OK"
        status_code  = "200"
      }
    }
  }
}

# Route53
resource "aws_route53_record" "main" {
  zone_id = var.route53_zone_id
  name    = var.domain_name
  type    = "A"

  alias {
    name                   = module.alb.dns_name
    zone_id                = module.alb.zone_id
    evaluate_target_health = true
  }
}

3.2 Kubernetes Manifests

File: infrastructure/k8s/base/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: cliproxy
  labels:
    app: cliproxy
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 10%
  selector:
    matchLabels:
      app: cliproxy
  template:
    metadata:
      labels:
        app: cliproxy
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8080"
        prometheus.io/path: "/metrics"
    spec:
      serviceAccountName: cliproxy
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        fsGroup: 1000
      containers:
        - name: cliproxy
          image: echyai/cliproxy:latest
          imagePullPolicy: Always
          ports:
            - name: http
              containerPort: 8080
              protocol: TCP
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop:
                - ALL
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "1Gi"
              cpu: "1000m"
          livenessProbe:
            httpGet:
              path: /health
              port: http
            initialDelaySeconds: 30
            periodSeconds: 10
            timeoutSeconds: 5
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /ready
              port: http
            initialDelaySeconds: 5
            periodSeconds: 5
            timeoutSeconds: 3
            failureThreshold: 3
          env:
            - name: CONFIG_TYPE
              value: "postgres"
            - name: POSTGRES_HOST
              valueFrom:
                secretKeyRef:
                  name: cliproxy-db
                  key: host
            - name: POSTGRES_PORT
              valueFrom:
                secretKeyRef:
                  name: cliproxy-db
                  key: port
            - name: POSTGRES_DATABASE
              valueFrom:
                secretKeyRef:
                  name: cliproxy-db
                  key: database
            - name: POSTGRES_USERNAME
              valueFrom:
                secretKeyRef:
                  name: cliproxy-db
                  key: username
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: cliproxy-db
                  key: password
          volumeMounts:
            - name: tmp
              mountPath: /tmp
            - name: cache
              mountPath: /cache
      volumes:
        - name: tmp
          emptyDir: {}
        - name: cache
          emptyDir:
            sizeLimit: 500Mi
---
apiVersion: v1
kind: Service
metadata:
  name: cliproxy
  labels:
    app: cliproxy
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: http
      protocol: TCP
      name: http
  selector:
    app: cliproxy
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cliproxy
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:123456789012:certificate/...
spec:
  rules:
    - host: api.cliproxy.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: cliproxy
                port:
                  number: 80

3.3 Kustomize Overlays

File: infrastructure/k8s/overlays/production/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: cliproxy-production

resources:
  - ../../base
  - hpa.yaml
  - pdb.yaml

replicas:
  - name: cliproxy
    count: 5

images:
  - name: echyai/cliproxy
    newTag: v1.2.3

patchesStrategicMerge:
  - deployment-patch.yaml

configMapGenerator:
  - name: cliproxy-config
    literals:
      - LOG_LEVEL=info
      - METRICS_ENABLED=true

secretGenerator:
  - name: cliproxy-db
    envs:
      - .env.db

commonLabels:
  environment: production

Phase 4: Monitoring Stack (Week 4)

4.1 Prometheus Rules

File: infrastructure/monitoring/prometheus-rules.yaml

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: cliproxy-alerts
spec:
  groups:
    - name: cliproxy
      rules:
        - alert: CLProxyHighErrorRate
          expr: |
            (
              sum(rate(cliproxy_http_requests_total{status=~"5.."}[5m]))
              /
              sum(rate(cliproxy_http_requests_total[5m]))
            ) > 0.05
          for: 5m
          labels:
            severity: critical
          annotations:
            summary: "High error rate detected"
            description: "Error rate is above 5% for 5 minutes"

        - alert: CLProxyHighLatency
          expr: |
            histogram_quantile(0.99,
              sum(rate(cliproxy_http_request_duration_seconds_bucket[5m])) by (le)
            ) > 2
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "High latency detected"
            description: "P99 latency is above 2 seconds"

        - alert: CLProxyProviderErrors
          expr: |
            sum(rate(cliproxy_provider_requests_total{status="error"}[5m])) by (provider) > 10
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "Provider {{ $labels.provider }} has high error rate"

        - alert: CLProxyCircuitBreakerOpen
          expr: cliproxy_circuit_breaker_state == 2
          for: 1m
          labels:
            severity: warning
          annotations:
            summary: "Circuit breaker is open"
            description: "Circuit breaker {{ $labels.name }} has opened"

        - alert: CLProxyRateLimitHits
          expr: |
            sum(rate(cliproxy_rate_limit_hits_total[5m])) > 100
          for: 5m
          labels:
            severity: info
          annotations:
            summary: "High rate limit hits"

4.2 Grafana Dashboard

File: infrastructure/monitoring/dashboard.json (simplified)

{
  "dashboard": {
    "title": "CLIProxyAPI",
    "tags": ["cliproxy", "api"],
    "timezone": "UTC",
    "panels": [
      {
        "title": "Request Rate",
        "type": "graph",
        "targets": [
          {
            "expr": "sum(rate(cliproxy_http_requests_total[5m])) by (status)"
          }
        ]
      },
      {
        "title": "Latency",
        "type": "graph",
        "targets": [
          {
            "expr": "histogram_quantile(0.99, sum(rate(cliproxy_http_request_duration_seconds_bucket[5m])) by (le))"
          }
        ]
      },
      {
        "title": "Provider Usage",
        "type": "graph",
        "targets": [
          {
            "expr": "sum(rate(cliproxy_provider_requests_total[5m])) by (provider)"
          }
        ]
      },
      {
        "title": "Active Connections",
        "type": "singlestat",
        "targets": [
          {
            "expr": "cliproxy_active_connections"
          }
        ]
      }
    ]
  }
}

Success Metrics

  • Build time < 5 minutes
  • Test coverage > 70%
  • Zero-downtime deployments
  • Automated rollback capability
  • Full observability (logs, metrics, traces)
  • Infrastructure as Code coverage 100%
  • Security scanning in CI
  • Multi-arch container images

Deployment Flow

1. Developer pushes code
   ↓
2. CI: Lint, Test, Build
   ↓
3. Docker image built (multi-arch)
   ↓
4. Security scan (Trivy)
   ↓
5. Push to registry
   ↓
6. Deploy to staging (auto)
   ↓
7. Integration tests
   ↓
8. Deploy to production (manual approval)
   ↓
9. Smoke tests
   ↓
10. Monitor and alert