# Kubernetes Deployment

This guide covers deploying the STA Connector (Elixir/Phoenix) on Kubernetes.

## Prerequisites

- Kubernetes cluster (1.24+)
- kubectl configured
- Helm 3 (optional, for Helm deployment)

## Quick Start

```bash
# Create namespace
kubectl create namespace sta-connector

# Apply secrets
kubectl apply -f k8s/secrets.yaml -n sta-connector

# Apply configuration
kubectl apply -f k8s/configmap.yaml -n sta-connector

# Deploy PostgreSQL
kubectl apply -f k8s/postgres.yaml -n sta-connector

# Deploy application
kubectl apply -f k8s/ -n sta-connector
```

## Kubernetes Manifests

### Namespace

`k8s/namespace.yaml`:

```yaml
apiVersion: v1
kind: Namespace
metadata:
  name: sta-connector
  labels:
    app.kubernetes.io/name: sta-connector
    app.kubernetes.io/component: pix-integration
```

### Secrets

`k8s/secrets.yaml`:

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: sta-connector-secrets
  namespace: sta-connector
type: Opaque
stringData:
  # Phoenix Application Secrets
  SECRET_KEY_BASE: "your_secret_key_base_at_least_64_characters_long_generate_with_mix_phx_gen_secret"

  # Database Configuration
  DATABASE_URL: "ecto://connector:your_db_password@postgres:5432/sta_connector"
  DB_PASSWORD: "your_db_password"

  # STA WebService Credentials
  STA_USERNAME: "your_sisbacen_username"
  STA_PASSWORD: "your_sisbacen_password"

  # API Authentication
  OUTBOUND_API_KEY: "your_api_key"
  ADMIN_PASSWORD: "your_admin_password"
  MONETARIE_STA_TOKEN: "your_monetarie_sta_token"
```

Create from command line:

```bash
# Generate a secret key base
SECRET_KEY_BASE=$(mix phx.gen.secret)

kubectl create secret generic sta-connector-secrets \
  --from-literal=SECRET_KEY_BASE=$SECRET_KEY_BASE \
  --from-literal=DATABASE_URL="ecto://connector:your_password@postgres:5432/sta_connector" \
  --from-literal=DB_PASSWORD=your_password \
  --from-literal=STA_USERNAME=your_username \
  --from-literal=STA_PASSWORD=your_password \
  --from-literal=OUTBOUND_API_KEY=your_api_key \
  --from-literal=ADMIN_PASSWORD=your_admin_password \
  --from-literal=MONETARIE_STA_TOKEN=your_token \
  -n sta-connector
```

### ConfigMap

`k8s/configmap.yaml`:

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: sta-connector-config
  namespace: sta-connector
data:
  # Phoenix Runtime Configuration
  PHX_HOST: "sta.example.com"
  PHX_PORT: "4000"
  PHX_SERVER: "true"

  # Pool Size (adjust based on database connections)
  POOL_SIZE: "10"

  # STA Configuration
  STA_ENVIRONMENT: "production"
  STA_INSTITUTION_CODE: "12345"
  STA_TIMEOUT_SECONDS: "30"
  STA_MAX_RETRIES: "3"

  # Poller Configuration
  POLLER_ENABLED: "true"
  POLLER_INTERVAL_SECONDS: "30"
  POLLER_SYSTEMS: "CCS,SPI,DICT"
  POLLER_BATCH_SIZE: "10"

  # Release Configuration
  RELEASE_NODE: "sta_connector@sta-connector"
  RELEASE_DISTRIBUTION: "name"
  RELEASE_COOKIE: "sta_connector_cookie"

  # Erlang VM Configuration
  ERL_MAX_PORTS: "65536"
  ERL_CRASH_DUMP_SECONDS: "0"
```

### Deployment

`k8s/deployment.yaml`:

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sta-connector
  namespace: sta-connector
  labels:
    app: sta-connector
    app.kubernetes.io/name: sta-connector
    app.kubernetes.io/component: backend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sta-connector
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: sta-connector
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "4000"
        prometheus.io/path: "/admin/metrics"
    spec:
      serviceAccountName: sta-connector
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        runAsGroup: 1000
        fsGroup: 1000
      terminationGracePeriodSeconds: 30
      initContainers:
        - name: wait-for-postgres
          image: busybox:1.36
          command: ['sh', '-c', 'until nc -z postgres 5432; do echo waiting for postgres; sleep 2; done;']
        - name: run-migrations
          image: sta-connector:latest
          command: ["/app/bin/migrate"]
          envFrom:
            - configMapRef:
                name: sta-connector-config
            - secretRef:
                name: sta-connector-secrets
      containers:
        - name: sta-connector
          image: sta-connector:latest
          imagePullPolicy: Always
          ports:
            - name: http
              containerPort: 4000
              protocol: TCP
          envFrom:
            - configMapRef:
                name: sta-connector-config
            - secretRef:
                name: sta-connector-secrets
          env:
            - name: POD_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP
            - name: RELEASE_NODE
              value: "sta_connector@$(POD_IP)"
          resources:
            limits:
              cpu: "2"
              memory: "1Gi"
            requests:
              cpu: "500m"
              memory: "512Mi"
          livenessProbe:
            httpGet:
              path: /admin/health
              port: http
            initialDelaySeconds: 30
            periodSeconds: 30
            timeoutSeconds: 5
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /admin/health
              port: http
            initialDelaySeconds: 10
            periodSeconds: 10
            timeoutSeconds: 5
            failureThreshold: 3
          startupProbe:
            httpGet:
              path: /admin/health
              port: http
            initialDelaySeconds: 5
            periodSeconds: 5
            timeoutSeconds: 3
            failureThreshold: 30
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "sleep 5"]
```

### Service

`k8s/service.yaml`:

```yaml
apiVersion: v1
kind: Service
metadata:
  name: sta-connector
  namespace: sta-connector
  labels:
    app: sta-connector
spec:
  type: ClusterIP
  selector:
    app: sta-connector
  ports:
    - name: http
      port: 4000
      targetPort: 4000
      protocol: TCP
---
# Headless service for Erlang clustering
apiVersion: v1
kind: Service
metadata:
  name: sta-connector-headless
  namespace: sta-connector
  labels:
    app: sta-connector
spec:
  type: ClusterIP
  clusterIP: None
  selector:
    app: sta-connector
  ports:
    - name: epmd
      port: 4369
      targetPort: 4369
    - name: distribution
      port: 9000
      targetPort: 9000
```

### Ingress

`k8s/ingress.yaml`:

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: sta-connector
  namespace: sta-connector
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    # WebSocket support for Phoenix LiveView
    nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
    nginx.ingress.kubernetes.io/upstream-hash-by: "$remote_addr"
spec:
  tls:
    - hosts:
        - sta.example.com
      secretName: sta-connector-tls
  rules:
    - host: sta.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: sta-connector
                port:
                  number: 4000
```

### PersistentVolumeClaim

`k8s/pvc.yaml`:

```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: sta-connector-data
  namespace: sta-connector
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: standard
```

### ServiceAccount

`k8s/serviceaccount.yaml`:

```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: sta-connector
  namespace: sta-connector
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: sta-connector
  namespace: sta-connector
rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list"]
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: sta-connector
  namespace: sta-connector
subjects:
  - kind: ServiceAccount
    name: sta-connector
    namespace: sta-connector
roleRef:
  kind: Role
  name: sta-connector
  apiGroup: rbac.authorization.k8s.io
```

### HorizontalPodAutoscaler

`k8s/hpa.yaml`:

```yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: sta-connector
  namespace: sta-connector
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: sta-connector
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 10
          periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
        - type: Percent
          value: 100
          periodSeconds: 15
        - type: Pods
          value: 4
          periodSeconds: 15
      selectPolicy: Max
```

### PodDisruptionBudget

`k8s/pdb.yaml`:

```yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: sta-connector
  namespace: sta-connector
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app: sta-connector
```

## PostgreSQL StatefulSet

`k8s/postgres.yaml`:

```yaml
apiVersion: v1
kind: Service
metadata:
  name: postgres
  namespace: sta-connector
spec:
  type: ClusterIP
  selector:
    app: postgres
  ports:
    - port: 5432
      targetPort: 5432
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
  namespace: sta-connector
spec:
  serviceName: postgres
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:16-alpine
          ports:
            - containerPort: 5432
          env:
            - name: POSTGRES_DB
              value: sta_connector
            - name: POSTGRES_USER
              value: connector
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: sta-connector-secrets
                  key: DB_PASSWORD
            - name: PGDATA
              value: /var/lib/postgresql/data/pgdata
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
          resources:
            limits:
              cpu: "2"
              memory: "1Gi"
            requests:
              cpu: "500m"
              memory: "512Mi"
          livenessProbe:
            exec:
              command: ["pg_isready", "-U", "connector", "-d", "sta_connector"]
            initialDelaySeconds: 30
            periodSeconds: 10
          readinessProbe:
            exec:
              command: ["pg_isready", "-U", "connector", "-d", "sta_connector"]
            initialDelaySeconds: 5
            periodSeconds: 5
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 20Gi
```

## Managed Database Alternative

For production environments, consider using a managed PostgreSQL service:

### AWS RDS

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: sta-connector-secrets
  namespace: sta-connector
type: Opaque
stringData:
  DATABASE_URL: "ecto://connector:password@your-rds-instance.region.rds.amazonaws.com:5432/sta_connector?ssl=true"
  SECRET_KEY_BASE: "your_secret_key_base"
```

### Google Cloud SQL

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: sta-connector-secrets
  namespace: sta-connector
type: Opaque
stringData:
  DATABASE_URL: "ecto://connector:password@/sta_connector?socket=/cloudsql/project:region:instance"
  SECRET_KEY_BASE: "your_secret_key_base"
```

Add Cloud SQL Proxy sidecar:

```yaml
containers:
  - name: cloud-sql-proxy
    image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.8.0
    args:
      - "--structured-logs"
      - "--port=5432"
      - "project:region:instance"
    securityContext:
      runAsNonRoot: true
    resources:
      requests:
        memory: "256Mi"
        cpu: "100m"
```

## Helm Chart (Basic)

### Chart Structure

```
helm/sta-connector/
├── Chart.yaml
├── values.yaml
├── templates/
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── configmap.yaml
│   ├── secret.yaml
│   ├── pvc.yaml
│   ├── hpa.yaml
│   └── pdb.yaml
```

### Chart.yaml

```yaml
apiVersion: v2
name: sta-connector
description: STA Connector (Elixir/Phoenix) for BCB file exchange
type: application
version: 1.0.0
appVersion: "1.0.0"
```

### values.yaml

```yaml
replicaCount: 2

image:
  repository: sta-connector
  tag: latest
  pullPolicy: Always

service:
  type: ClusterIP
  port: 4000

ingress:
  enabled: true
  className: nginx
  annotations:
    nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
  hosts:
    - host: sta.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: sta-connector-tls
      hosts:
        - sta.example.com

resources:
  limits:
    cpu: "2"
    memory: 1Gi
  requests:
    cpu: 500m
    memory: 512Mi

autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70
  targetMemoryUtilizationPercentage: 80

# Phoenix Configuration
phoenix:
  host: "sta.example.com"
  port: 4000
  poolSize: 10

# STA Configuration
sta:
  environment: production
  institutionCode: "12345"
  timeoutSeconds: 30
  maxRetries: 3

# Poller Configuration
poller:
  enabled: true
  intervalSeconds: 30
  systems:
    - CCS
    - SPI
    - DICT
  batchSize: 10

# Secrets (use external secrets manager in production)
secrets:
  secretKeyBase: ""
  databaseUrl: ""
  staUsername: ""
  staPassword: ""
  outboundApiKey: ""
  adminPassword: ""
  monetarieStaToken: ""

# PostgreSQL subchart configuration
postgresql:
  enabled: true
  auth:
    database: sta_connector
    username: connector
    password: ""
```

### Install with Helm

```bash
# Add values
cat > my-values.yaml <<EOF
phoenix:
  host: "sta.mycompany.com"

secrets:
  secretKeyBase: "$(mix phx.gen.secret)"
  databaseUrl: "ecto://connector:mypassword@postgres:5432/sta_connector"
  staUsername: "your_username"
  staPassword: "your_password"
  outboundApiKey: "your_api_key"
  adminPassword: "your_admin_password"
  monetarieStaToken: "your_token"

postgresql:
  auth:
    password: "mypassword"
EOF

# Install
helm install sta-connector ./helm/sta-connector \
  -f my-values.yaml \
  -n sta-connector \
  --create-namespace

# Upgrade
helm upgrade sta-connector ./helm/sta-connector \
  -f my-values.yaml \
  -n sta-connector
```

## Operations

### Scaling

```bash
# Manual scaling
kubectl scale deployment sta-connector --replicas=3 -n sta-connector

# View HPA status
kubectl get hpa sta-connector -n sta-connector

# View pod distribution
kubectl get pods -n sta-connector -o wide
```

### Rolling Updates

```bash
# Update image
kubectl set image deployment/sta-connector \
  sta-connector=sta-connector:v1.1.0 \
  -n sta-connector

# Watch rollout
kubectl rollout status deployment/sta-connector -n sta-connector

# Rollback
kubectl rollout undo deployment/sta-connector -n sta-connector
```

### Database Migrations

```bash
# Run migrations manually
kubectl exec -it deployment/sta-connector -n sta-connector -- /app/bin/migrate

# Or use a Job
kubectl apply -f - <<EOF
apiVersion: batch/v1
kind: Job
metadata:
  name: sta-connector-migrate
  namespace: sta-connector
spec:
  template:
    spec:
      containers:
        - name: migrate
          image: sta-connector:latest
          command: ["/app/bin/migrate"]
          envFrom:
            - configMapRef:
                name: sta-connector-config
            - secretRef:
                name: sta-connector-secrets
      restartPolicy: Never
  backoffLimit: 3
EOF
```

### Logs

```bash
# View logs
kubectl logs -f deployment/sta-connector -n sta-connector

# All pods
kubectl logs -l app=sta-connector -n sta-connector --tail=100

# Previous container
kubectl logs deployment/sta-connector -n sta-connector --previous
```

### Debug

```bash
# Exec into pod (Elixir release)
kubectl exec -it deployment/sta-connector -n sta-connector -- /app/bin/sta_connector remote

# Port forward
kubectl port-forward svc/sta-connector 4000:4000 -n sta-connector

# Check events
kubectl get events -n sta-connector --sort-by='.lastTimestamp'

# Check health endpoint
kubectl exec -it deployment/sta-connector -n sta-connector -- curl localhost:4000/admin/health
```

### IEx Remote Console

```bash
# Connect to running node
kubectl exec -it deployment/sta-connector -n sta-connector -- /app/bin/sta_connector remote

# In IEx, you can run:
# :observer_cli.start()
# Process.list() |> length()
# :sys.get_state(StaConnector.Poller)
```

## Monitoring

### Prometheus ServiceMonitor

```yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: sta-connector
  namespace: sta-connector
spec:
  selector:
    matchLabels:
      app: sta-connector
  endpoints:
    - port: http
      path: /admin/metrics
      interval: 30s
```

### Health Check Endpoint

The Phoenix application exposes `/admin/health` which returns:

```json
{
  "status": "healthy",
  "version": "1.0.0",
  "checks": {
    "database": "ok",
    "sta_connection": "ok",
    "poller": "running"
  }
}
```

### Grafana Dashboard

Import the provided Grafana dashboard from `deployments/grafana-dashboard.json`.

## Security

### Network Policies

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: sta-connector
  namespace: sta-connector
spec:
  podSelector:
    matchLabels:
      app: sta-connector
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              name: ingress-nginx
      ports:
        - port: 4000
    # Allow Erlang clustering between pods
    - from:
        - podSelector:
            matchLabels:
              app: sta-connector
      ports:
        - port: 4369
        - port: 9000
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: postgres
      ports:
        - port: 5432
    # STA WebService
    - to:
        - ipBlock:
            cidr: 0.0.0.0/0
      ports:
        - port: 443
```

### Pod Security Standards

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: sta-connector
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: sta-connector
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop:
            - ALL
      volumeMounts:
        - name: tmp
          mountPath: /tmp
  volumes:
    - name: tmp
      emptyDir: {}
```

## Erlang Clustering

For Erlang clustering in Kubernetes, use libcluster with the Kubernetes strategy:

```elixir
# config/runtime.exs
config :libcluster,
  topologies: [
    k8s: [
      strategy: Cluster.Strategy.Kubernetes,
      config: [
        mode: :ip,
        kubernetes_node_basename: "sta_connector",
        kubernetes_selector: "app=sta-connector",
        kubernetes_namespace: "sta-connector",
        polling_interval: 10_000
      ]
    ]
  ]
```

## Next Steps

- [Docker Deployment](/deployment/docker) - Docker setup
- [Troubleshooting](/troubleshooting) - Common issues
- [Configuration](/guide/configuration) - Configuration options
