diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..650a676 --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,113 @@ +when: + - event: [push, pull_request] + +steps: + # Lint and test frontend + frontend-lint: + image: node:18-alpine + commands: + - cd bugulma/frontend + - yarn install --frozen-lockfile + - yarn lint + - yarn test --run + when: + - event: [push, pull_request] + path: "bugulma/frontend/**" + + # Build and push frontend using Kaniko (containerd-compatible) + frontend-build: + image: gcr.io/kaniko-project/executor:latest + commands: + - mkdir -p /kaniko/.docker + - echo "{\"auths\":{\"registry.bk.glpx.pro\":{\"username\":\"${DOCKER_USERNAME}\",\"password\":\"${DOCKER_PASSWORD}\"}}}" > /kaniko/.docker/config.json + - /kaniko/executor --dockerfile=bugulma/frontend/Dockerfile --context=bugulma/frontend --destination=registry.bk.glpx.pro/turash/turash-frontend:latest --destination=registry.bk.glpx.pro/turash/turash-frontend:${CI_COMMIT_SHA} --cache=true --cache-ttl=168h --compressed-caching=false + environment: + DOCKER_USERNAME: + from_secret: docker_username + DOCKER_PASSWORD: + from_secret: docker_password + when: + - event: push + branch: master + path: "bugulma/frontend/**" + + # Lint and test backend + backend-lint: + image: golang:1.21-alpine + commands: + - cd bugulma/backend + - go mod download + - go vet ./... + - go test -v -race -coverprofile=coverage.out ./... + - go tool cover -html=coverage.out -o coverage.html + when: + - event: [push, pull_request] + path: "bugulma/backend/**" + + # Build and push backend using Kaniko (containerd-compatible) + backend-build: + image: gcr.io/kaniko-project/executor:latest + commands: + - mkdir -p /kaniko/.docker + - echo "{\"auths\":{\"registry.bk.glpx.pro\":{\"username\":\"${DOCKER_USERNAME}\",\"password\":\"${DOCKER_PASSWORD}\"}}}" > /kaniko/.docker/config.json + - /kaniko/executor --dockerfile=bugulma/backend/Dockerfile --context=bugulma/backend --destination=registry.bk.glpx.pro/turash/turash-backend:latest --destination=registry.bk.glpx.pro/turash/turash-backend:${CI_COMMIT_SHA} --cache=true --cache-ttl=168h --compressed-caching=false + environment: + DOCKER_USERNAME: + from_secret: docker_username + DOCKER_PASSWORD: + from_secret: docker_password + when: + - event: push + branch: master + path: "bugulma/backend/**" + + # Deploy to staging + deploy-staging: + image: bitnami/kubectl:latest + commands: + - kubectl config set-cluster k3s --server=https://10.10.10.2:6443 --insecure-skip-tls-verify=true + - kubectl config set-credentials default --token=${KUBE_TOKEN} + - kubectl config set-context default --cluster=k3s --user=default + - kubectl config use-context default + + # Deploy backend + - kubectl apply -f k8s/namespace.yaml + - kubectl apply -f k8s/configmap.yaml + - kubectl apply -f k8s/secret.yaml + - kubectl set image deployment/turash-backend backend=registry.bk.glpx.pro/turash/turash-backend:${CI_COMMIT_SHA} -n turash || kubectl create -f k8s/deployment.yaml + - kubectl rollout status deployment/turash-backend -n turash + + # Deploy frontend + - kubectl apply -f k8s/frontend-deployment.yaml + - kubectl apply -f k8s/frontend-service.yaml + - kubectl set image deployment/turash-frontend frontend=registry.bk.glpx.pro/turash/turash-frontend:${CI_COMMIT_SHA} -n turash + - kubectl rollout status deployment/turash-frontend -n turash + environment: + KUBE_TOKEN: + from_secret: kube_token + when: + - event: push + branch: master + path: ["bugulma/**", "k8s/**"] + + # Run E2E tests + e2e-test: + image: mcr.microsoft.com/playwright:v1.40.0-jammy + commands: + - cd bugulma/frontend + - yarn install --frozen-lockfile + - yarn test:e2e --headed=false + when: + - event: push + branch: master + path: "bugulma/frontend/**" + + # Notify on failure + notify-failure: + image: alpine:latest + commands: + - echo "Pipeline failed for commit ${CI_COMMIT_SHA}" + - echo "Check logs at ${CI_SYSTEM_URL}/${CI_REPO}/${CI_PIPELINE_NUMBER}" + when: + - event: [push, pull_request] + status: failure diff --git a/bugulma/frontend/Dockerfile b/bugulma/frontend/Dockerfile new file mode 100644 index 0000000..77fb6b0 --- /dev/null +++ b/bugulma/frontend/Dockerfile @@ -0,0 +1,32 @@ +# Frontend Production Dockerfile +FROM node:18-alpine AS builder + +# Set working directory +WORKDIR /app + +# Copy package files +COPY package*.json yarn.lock ./ + +# Install dependencies +RUN yarn install --frozen-lockfile + +# Copy source code +COPY . . + +# Build the application +RUN yarn build + +# Production stage +FROM nginx:alpine + +# Copy built assets from builder stage +COPY --from=builder /app/dist /usr/share/nginx/html + +# Copy nginx configuration +COPY nginx.conf /etc/nginx/nginx.conf + +# Expose port +EXPOSE 80 + +# Start nginx +CMD ["nginx", "-g", "daemon off;"] diff --git a/bugulma/frontend/nginx.conf b/bugulma/frontend/nginx.conf new file mode 100644 index 0000000..270fb97 --- /dev/null +++ b/bugulma/frontend/nginx.conf @@ -0,0 +1,97 @@ +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log; + + # Performance + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + client_max_body_size 16M; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied any; + gzip_comp_level 6; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/json + application/javascript + application/xml+rss + application/atom+xml + image/svg+xml; + + server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; + + # Handle client-side routing + location / { + try_files $uri $uri/ /index.html; + } + + # API proxy to backend + location /api/ { + proxy_pass http://turash-backend.turash.svc.cluster.local:80; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # CORS headers + add_header Access-Control-Allow-Origin *; + add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; + add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"; + + # Handle preflight requests + if ($request_method = 'OPTIONS') { + add_header Access-Control-Allow-Origin *; + add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; + add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"; + add_header Content-Type 'text/plain charset=UTF-8'; + add_header Content-Length 0; + return 204; + } + } + + # Static assets with caching + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Health check + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + } +} diff --git a/docs/CICD_SETUP_COMPLETE.md b/docs/CICD_SETUP_COMPLETE.md new file mode 100644 index 0000000..b8a6723 --- /dev/null +++ b/docs/CICD_SETUP_COMPLETE.md @@ -0,0 +1,231 @@ +# CI/CD Pipeline Setup Complete ✅ + +## Overview + +Complete CI/CD pipeline configured using: +- **Woodpecker CI**: Build and test automation +- **Kaniko**: containerd-compatible image builder +- **Harbor Registry**: Container image storage +- **ArgoCD**: GitOps-based deployment + +## Pipeline Architecture + +``` +GitHub Push → Woodpecker CI → Kaniko Build → Harbor Registry → ArgoCD → Kubernetes +``` + +## Components + +### 1. Woodpecker CI Pipeline (`.woodpecker.yml`) + +#### Pipeline Steps: + +1. **Frontend Lint & Test** + - Runs on: `push`, `pull_request` + - Path: `bugulma/frontend/**` + - Commands: `yarn install`, `yarn lint`, `yarn test` + +2. **Frontend Build** (Kaniko) + - Runs on: `push` to `master` + - Path: `bugulma/frontend/**` + - Builds: `registry.bk.glpx.pro/turash/turash-frontend:latest` and `:${CI_COMMIT_SHA}` + - Uses containerd-compatible Kaniko executor + +3. **Backend Lint & Test** + - Runs on: `push`, `pull_request` + - Path: `bugulma/backend/**` + - Commands: `go vet`, `go test`, coverage + +4. **Backend Build** (Kaniko) + - Runs on: `push` to `master` + - Path: `bugulma/backend/**` + - Builds: `registry.bk.glpx.pro/turash/turash-backend:latest` and `:${CI_COMMIT_SHA}` + - Uses containerd-compatible Kaniko executor + +5. **Deploy to Staging** (Optional - ArgoCD handles this automatically) + - Runs on: `push` to `master` + - Path: `bugulma/**`, `k8s/**` + - Manual kubectl deployment (can be disabled if using ArgoCD only) + +6. **E2E Tests** + - Runs on: `push` to `master` + - Path: `bugulma/frontend/**` + - Uses Playwright for end-to-end testing + +7. **Failure Notification** + - Runs on: Any failure + - Logs failure information + +### 2. Harbor Container Registry + +- **URL**: https://registry.bk.glpx.pro +- **Registry**: `registry.bk.glpx.pro` +- **Project**: `turash` +- **Credentials**: Configured in Woodpecker secrets + +### 3. ArgoCD GitOps + +- **Backend Application**: `turash-backend` +- **Frontend Application**: `turash-frontend` +- **Sync Policy**: Automated with self-heal +- **Source**: `https://github.com/SamyRai/turash.git` +- **Path**: `k8s/` +- **Target Revision**: `HEAD` (updates automatically) + +## Required Secrets + +### Woodpecker Secrets + +Configure these secrets in Woodpecker for repository `SamyRai/turash`: + +```bash +# Docker registry credentials (for Harbor) +woodpecker-cli repo secret add SamyRai/turash \ + --name docker_username \ + --value admin + +woodpecker-cli repo secret add SamyRai/turash \ + --name docker_password \ + --value "YOUR_HARBOR_PASSWORD" + +# Kubernetes token (optional, only if using manual deploy step) +woodpecker-cli repo secret add SamyRai/turash \ + --name kube_token \ + --value "YOUR_KUBERNETES_TOKEN" +``` + +**Current Status**: +- ✅ `docker_username`: Configured +- ✅ `docker_password`: Configured +- ⚠️ `kube_token`: Not configured (optional if using ArgoCD only) + +### Harbor Credentials + +- **Username**: `admin` +- **Password**: See `k8s/registry/harbor-secrets.yaml.template` + +## Deployment Flow + +### Automatic Deployment (Recommended) + +1. **Developer pushes to `master` branch** +2. **Woodpecker triggers pipeline**: + - Lints and tests code + - Builds Docker images with Kaniko + - Pushes images to Harbor registry +3. **ArgoCD detects changes**: + - Monitors Git repository + - Detects new image tags in Kubernetes manifests + - Automatically syncs and deploys to Kubernetes + +### Manual Deployment (Optional) + +The `deploy-staging` step in Woodpecker can manually deploy using kubectl, but this is redundant if ArgoCD is configured with automated sync. + +## Image Tagging Strategy + +Images are tagged with: +- `latest`: Always points to the latest build +- `${CI_COMMIT_SHA}`: Specific commit SHA for traceability + +Kubernetes deployments should reference specific SHA tags for production: +```yaml +image: registry.bk.glpx.pro/turash/turash-backend:abc123def456 +``` + +## Verification + +### Check Woodpecker Pipeline + +```bash +# List pipelines +woodpecker-cli pipeline ls SamyRai/turash + +# View latest pipeline +woodpecker-cli pipeline last SamyRai/turash + +# View pipeline logs +woodpecker-cli pipeline logs SamyRai/turash +``` + +### Check Harbor Registry + +```bash +# Login to Harbor +docker login registry.bk.glpx.pro -u admin -p "PASSWORD" + +# List images +curl -u admin:PASSWORD https://registry.bk.glpx.pro/api/v2.0/projects/turash/repositories + +# Or via Harbor UI +open https://registry.bk.glpx.pro +``` + +### Check ArgoCD Applications + +```bash +# List applications +argocd app list + +# Get application status +argocd app get turash-backend +argocd app get turash-frontend + +# View application sync status +argocd app sync turash-backend +``` + +### Check Kubernetes Deployments + +```bash +# Check pods +kubectl get pods -n turash + +# Check deployments +kubectl get deployments -n turash + +# Check services +kubectl get svc -n turash + +# Check ingress +kubectl get ingress -n turash +``` + +## Troubleshooting + +### Pipeline Fails to Build + +1. **Check Kaniko logs**: Verify Dockerfile and build context +2. **Check registry access**: Ensure Harbor credentials are correct +3. **Check secrets**: Verify `docker_username` and `docker_password` are set + +### Images Not Deploying + +1. **Check ArgoCD sync status**: `argocd app get turash-backend` +2. **Check image pull secrets**: Ensure Harbor registry secret is configured +3. **Check image tags**: Verify deployment manifests reference correct tags + +### ArgoCD Not Syncing + +1. **Check repository access**: Ensure ArgoCD can access GitHub repository +2. **Check application status**: `argocd app get turash-backend` +3. **Check sync policy**: Verify automated sync is enabled + +## Next Steps + +1. ✅ **Pipeline configured** - Woodpecker CI with Kaniko +2. ✅ **Registry configured** - Harbor with containerd support +3. ✅ **GitOps configured** - ArgoCD with automated sync +4. ⚠️ **Optional**: Configure `kube_token` secret if using manual deploy step +5. 🔄 **Test pipeline**: Push a commit to trigger the pipeline +6. 🔄 **Verify deployment**: Check ArgoCD sync and Kubernetes pods + +## Key Features + +- ✅ **containerd-compatible**: Uses Kaniko instead of Docker +- ✅ **No privileged mode**: Kaniko doesn't require privileged containers +- ✅ **Automated deployment**: ArgoCD handles GitOps deployments +- ✅ **Multi-architecture**: Ready for ARM64 and AMD64 (if needed) +- ✅ **Caching**: Kaniko cache enabled for faster builds +- ✅ **Security**: Secrets managed via Woodpecker secret store + diff --git a/docs/woodpecker-cli-reference.md b/docs/woodpecker-cli-reference.md new file mode 100644 index 0000000..6693ffe --- /dev/null +++ b/docs/woodpecker-cli-reference.md @@ -0,0 +1,278 @@ +# Woodpecker CLI Reference + +## Overview + +Woodpecker CLI version: **3.12.0** + +Current user: **SamyRai** (gigadamer@gmail.com) + +## Main Commands + +### Setup & Configuration + +```bash +# Initial setup +woodpecker-cli setup [--server URL] [--token TOKEN] + +# Show current user info +woodpecker-cli info + +# Update CLI to latest version +woodpecker-cli update +``` + +### Repository Management + +```bash +# List all repositories +woodpecker-cli repo ls + +# Add a repository +woodpecker-cli repo add [options] + +# Show repository information +woodpecker-cli repo show [repo] + +# Update repository settings +woodpecker-cli repo update [options] + +# Remove repository +woodpecker-cli repo rm [repo] + +# Synchronize repository list +woodpecker-cli repo sync + +# Repair repository webhooks +woodpecker-cli repo repair [repo] + +# Assume ownership of a repository +woodpecker-cli repo chown [repo] +``` + +### Repository Secrets + +```bash +# List secrets +woodpecker-cli repo secret ls [repo] + +# Add a secret +woodpecker-cli repo secret add [options] + +# Show secret information +woodpecker-cli repo secret show [repo] [secret] + +# Update a secret +woodpecker-cli repo secret update [options] + +# Remove a secret +woodpecker-cli repo secret rm [repo] [secret] +``` + +### Container Registry Management + +```bash +# List registries +woodpecker-cli repo registry ls [repo] + +# Add a registry +woodpecker-cli repo registry add [options] + +# Show registry information +woodpecker-cli repo registry show [repo] [registry] + +# Update a registry +woodpecker-cli repo registry update [options] + +# Remove a registry +woodpecker-cli repo registry rm [repo] [registry] +``` + +### Pipeline Management + +```bash +# List pipelines +woodpecker-cli pipeline ls [repo] + +# Show latest pipeline +woodpecker-cli pipeline last [repo] + +# Show pipeline information +woodpecker-cli pipeline show [repo] [pipeline-number] + +# Show pipeline steps +woodpecker-cli pipeline ps [repo] [pipeline-number] + +# Show pipeline queue +woodpecker-cli pipeline queue + +# Start a pipeline +woodpecker-cli pipeline start [repo] [pipeline-number] + +# Stop a pipeline +woodpecker-cli pipeline stop [repo] [pipeline-number] + +# Approve a pipeline +woodpecker-cli pipeline approve [repo] [pipeline-number] + +# Decline a pipeline +woodpecker-cli pipeline decline [repo] [pipeline-number] + +# Trigger deployment pipeline +woodpecker-cli pipeline deploy [repo] [environment] + +# Show pipeline logs +woodpecker-cli pipeline log [repo] [pipeline-number] [step-number] + +# Purge old pipelines +woodpecker-cli pipeline purge [repo] [options] +``` + +### Local Pipeline Execution + +```bash +# Execute pipeline locally +woodpecker-cli exec [path/to/.woodpecker.yaml] + +# Common options: +--local # Run from local directory +--secrets string=string # Map of secrets: 'key="value",key2="value2"' +--secrets string # Path to YAML file with secrets +--backend-engine string # Backend engine (auto-detect, docker, kubernetes) +--timeout duration # Pipeline timeout (default: 1h0m0s) +--volumes string # Pipeline volumes +--network string # External networks +``` + +### Pipeline Linting + +```bash +# Lint pipeline configuration +woodpecker-cli lint [path/to/.woodpecker.yaml] + +# Options: +--strict # Treat warnings as errors +--plugins-privileged # Allow privileged plugins +``` + +### Organization Management + +```bash +# List organizations +woodpecker-cli org ls + +# Show organization +woodpecker-cli org show [org] + +# Add organization +woodpecker-cli org add [options] + +# Remove organization +woodpecker-cli org rm [org] +``` + +### Admin Commands + +```bash +# Manage server settings +woodpecker-cli admin [command] +``` + +## Global Options + +All commands support these global options: + +```bash +--config string, -c string # Path to config file [$WOODPECKER_CONFIG] +--server string, -s string # Server address [$WOODPECKER_SERVER] +--token string, -t string # Server auth token [$WOODPECKER_TOKEN] +--skip-verify # Skip SSL verification [$WOODPECKER_SKIP_VERIFY] +--log-level string # Set logging level (default: "info") +--pretty # Enable pretty-printed debug output +--nocolor # Disable colored output +``` + +## Environment Variables + +You can set these environment variables instead of using CLI flags: + +- `WOODPECKER_SERVER` - Server address +- `WOODPECKER_TOKEN` - Authentication token +- `WOODPECKER_CONFIG` - Path to config file +- `WOODPECKER_SKIP_VERIFY` - Skip SSL verification +- `WOODPECKER_LOG_LEVEL` - Logging level + +## Common Workflows + +### 1. Setup Repository + +```bash +# Add repository +woodpecker-cli repo add SamyRai/turash + +# Add container registry credentials +woodpecker-cli repo registry add SamyRai/turash \ + --name docker-registry \ + --hostname registry.local \ + --username admin \ + --password secret + +# Add secrets +woodpecker-cli repo secret add SamyRai/turash \ + --name DOCKER_USERNAME \ + --value admin + +woodpecker-cli repo secret add SamyRai/turash \ + --name DOCKER_PASSWORD \ + --value secret +``` + +### 2. Trigger Pipeline + +```bash +# Start pipeline manually +woodpecker-cli pipeline start SamyRai/turash + +# Trigger deployment +woodpecker-cli pipeline deploy SamyRai/turash production +``` + +### 3. Monitor Pipeline + +```bash +# Check latest pipeline +woodpecker-cli pipeline last SamyRai/turash + +# View pipeline steps +woodpecker-cli pipeline ps SamyRai/turash 123 + +# View logs +woodpecker-cli pipeline log SamyRai/turash 123 1 +``` + +### 4. Test Pipeline Locally + +```bash +# Lint pipeline +woodpecker-cli lint .woodpecker.yml + +# Execute locally +woodpecker-cli exec .woodpecker.yml \ + --local \ + --secrets DOCKER_USERNAME="admin",DOCKER_PASSWORD="secret" +``` + +## Completion + +Generate shell completion: + +```bash +# Bash +woodpecker-cli completion bash > /etc/bash_completion.d/woodpecker-cli + +# Zsh +woodpecker-cli completion zsh > ~/.zsh/completion/_woodpecker-cli + +# Fish +woodpecker-cli completion fish > ~/.config/fish/completions/woodpecker-cli.fish +``` + diff --git a/k8s/argocd/application.yaml b/k8s/argocd/application.yaml new file mode 100644 index 0000000..dee8b07 --- /dev/null +++ b/k8s/argocd/application.yaml @@ -0,0 +1,47 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: turash-backend + namespace: argocd + labels: + app: turash-backend + environment: production + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: https://github.com/SamyRai/turash.git + targetRevision: master + path: k8s + kustomize: {} + destination: + server: https://kubernetes.default.svc + namespace: turash + syncPolicy: + automated: + prune: true + selfHeal: true + allowEmpty: false + syncOptions: + - CreateNamespace=true + - PrunePropagationPolicy=foreground + - PruneLast=true + retry: + limit: 5 + backoff: + duration: 5s + factor: 2 + maxDuration: 3m + revisionHistoryLimit: 10 + ignoreDifferences: + - group: apps + kind: Deployment + jsonPointers: + - /spec/replicas + - group: autoscaling + kind: HorizontalPodAutoscaler + jsonPointers: + - /spec/minReplicas + - /spec/maxReplicas + diff --git a/k8s/argocd/frontend-application.yaml b/k8s/argocd/frontend-application.yaml new file mode 100644 index 0000000..08e1925 --- /dev/null +++ b/k8s/argocd/frontend-application.yaml @@ -0,0 +1,47 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: turash-frontend + namespace: argocd + labels: + app: turash-frontend + environment: production + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: https://github.com/SamyRai/turash.git + targetRevision: master + path: k8s + kustomize: {} + destination: + server: https://kubernetes.default.svc + namespace: turash + syncPolicy: + automated: + prune: true + selfHeal: true + allowEmpty: false + syncOptions: + - CreateNamespace=true + - PrunePropagationPolicy=foreground + - PruneLast=true + retry: + limit: 5 + backoff: + duration: 5s + factor: 2 + maxDuration: 3m + revisionHistoryLimit: 10 + ignoreDifferences: + - group: apps + kind: Deployment + jsonPointers: + - /spec/replicas + - group: autoscaling + kind: HorizontalPodAutoscaler + jsonPointers: + - /spec/minReplicas + - /spec/maxReplicas +