Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions .github/workflows/deploy-downstream.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
name: "Deploy: Downstream Clusters"

on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: 'Image tag to deploy (e.g. 1.1.0)'
required: true
default: 'latest'

jobs:
update-sandbox:
name: Update Sandbox Cluster
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.get_tag.outputs.TAG }}
steps:
- name: Checkout App
uses: actions/checkout@v4

- name: Get Release Tag
id: get_tag
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "TAG=${{ inputs.tag }}" >> $GITHUB_OUTPUT
else
echo "TAG=${GITHUB_REF:11}" >> $GITHUB_OUTPUT
fi

- name: Checkout Sandbox Cluster
uses: actions/checkout@v4
with:
repository: CodeForPhilly/cfp-sandbox-cluster
token: ${{ secrets.BOT_GITHUB_TOKEN }}
path: sandbox

- name: Update Sandbox Image Tag
working-directory: sandbox/balancer
run: |
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
./kustomize edit set image ghcr.io/codeforphilly/balancer-main/app:${{ steps.get_tag.outputs.TAG }}
rm kustomize

- name: Create Sandbox PR
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.BOT_GITHUB_TOKEN }}
path: sandbox
commit-message: "Deploy balancer ${{ steps.get_tag.outputs.TAG }} to sandbox"
title: "Deploy balancer ${{ steps.get_tag.outputs.TAG }}"
body: "Updates balancer image tag to ${{ steps.get_tag.outputs.TAG }}"
branch: "deploy/balancer-${{ steps.get_tag.outputs.TAG }}"
base: main
delete-branch: true

update-live:
name: Update Live Cluster
needs: update-sandbox
runs-on: ubuntu-latest
steps:
- name: Checkout Live Cluster
uses: actions/checkout@v4
with:
repository: CodeForPhilly/cfp-live-cluster
token: ${{ secrets.BOT_GITHUB_TOKEN }}
path: live

- name: Update Live Image Tag
working-directory: live/balancer
run: |
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
./kustomize edit set image ghcr.io/codeforphilly/balancer-main/app:${{ needs.update-sandbox.outputs.tag }}
rm kustomize

- name: Create Live PR
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.BOT_GITHUB_TOKEN }}
path: live
commit-message: "Deploy balancer ${{ needs.update-sandbox.outputs.tag }} to live"
title: "Deploy balancer ${{ needs.update-sandbox.outputs.tag }}"
body: "Updates balancer image tag to ${{ needs.update-sandbox.outputs.tag }}"
branch: "deploy/balancer-${{ needs.update-sandbox.outputs.tag }}"
base: main
delete-branch: true
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ Tools used for development:
Start the Postgres, Django REST, and React services by starting Docker Desktop and running `docker compose up --build`

#### Postgres

The application supports connecting to PostgreSQL databases via:

1. **CloudNativePG** - Kubernetes-managed PostgreSQL cluster (for production/sandbox)
2. **AWS RDS** - External PostgreSQL database (AWS managed)
3. **Local Docker Compose** - For local development

See [Database Connection Documentation](./docs/DATABASE_CONNECTION.md) for detailed configuration.

**Local Development:**
- Download a sample of papers to upload from [https://balancertestsite.com](https://balancertestsite.com/)
- The email and password of `pgAdmin` are specified in `balancer-main/docker-compose.yml`
- The first time you use `pgAdmin` after building the Docker containers you will need to register the server.
Expand Down
22 changes: 22 additions & 0 deletions config/env/dev.env.example
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
DEBUG=True
SECRET_KEY=foo

# Database Configuration
# Supports both CloudNativePG (Kubernetes service) and AWS RDS (external host)
SQL_ENGINE=django.db.backends.postgresql
SQL_DATABASE=balancer_dev
SQL_USER=balancer
SQL_PASSWORD=balancer

# Connection Type Examples:
#
# CloudNativePG (Kubernetes service within cluster):
# SQL_HOST=balancer-postgres-rw
# SQL_HOST=balancer-postgres-rw.balancer.svc.cluster.local
# (SSL typically not required within cluster)
#
# AWS RDS (External database):
# SQL_HOST=balancer-db.xxxxx.us-east-1.rds.amazonaws.com
# (SSL typically required - set SQL_SSL_MODE if needed)
#
# Local development:
# SQL_HOST=localhost
# SQL_HOST=db # Docker Compose service name
SQL_HOST=db
SQL_PORT=5432

# Optional: SSL mode for PostgreSQL connections
# Options: disable, allow, prefer, require, verify-ca, verify-full
# Default: require for external hosts (AWS RDS), disabled for CloudNativePG
# SQL_SSL_MODE=require

LOGIN_REDIRECT_URL=
OPENAI_API_KEY=
ANTHROPIC_API_KEY=
Expand Down
174 changes: 174 additions & 0 deletions docs/DATABASE_CONNECTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# Database Connection Configuration

The balancer application supports connecting to PostgreSQL databases via two methods:

1. **CloudNativePG** - Kubernetes-managed PostgreSQL cluster (within cluster)
2. **AWS RDS** - External PostgreSQL database (AWS managed)

The application automatically detects the connection type based on the `SQL_HOST` environment variable format.

## Connection Type Detection

The application determines the connection type by analyzing the `SQL_HOST` value:

- **CloudNativePG**:
- Contains `.svc.cluster.local` (Kubernetes service DNS)
- Short service name (e.g., `balancer-postgres-rw`)
- Typically no SSL required within cluster

- **AWS RDS**:
- Full domain name (e.g., `balancer-db.xxxxx.us-east-1.rds.amazonaws.com`)
- External IP address
- Typically requires SSL

## Configuration

### Environment Variables

All database configuration is done via environment variables:

- `SQL_ENGINE`: Database engine (default: `django.db.backends.postgresql`)
- `SQL_DATABASE`: Database name
- `SQL_USER`: Database username
- `SQL_PASSWORD`: Database password
- `SQL_HOST`: Database host (see examples below)
- `SQL_PORT`: Database port (default: `5432`)
- `SQL_SSL_MODE`: Optional SSL mode (see SSL Configuration below)

### CloudNativePG Configuration

When using CloudNativePG, the application connects to the Kubernetes service created by the operator.

**Example Configuration:**
```bash
SQL_ENGINE=django.db.backends.postgresql
SQL_DATABASE=balancer
SQL_USER=balancer
SQL_PASSWORD=<password-from-secret>
SQL_HOST=balancer-postgres-rw
SQL_PORT=5432
```

**Service Names:**
- `{cluster-name}-rw`: Read-write service (primary instance)
- `{cluster-name}-r`: Read service (replicas)
- `{cluster-name}-ro`: Read-only service

**Full DNS Name:**
```bash
SQL_HOST=balancer-postgres-rw.balancer.svc.cluster.local
```

### AWS RDS Configuration

When using AWS RDS, the application connects to the external RDS endpoint.

**Example Configuration:**
```bash
SQL_ENGINE=django.db.backends.postgresql
SQL_DATABASE=balancer
SQL_USER=balancer
SQL_PASSWORD=<rds-password>
SQL_HOST=balancer-db.xxxxx.us-east-1.rds.amazonaws.com
SQL_PORT=5432
SQL_SSL_MODE=require
```

## SSL Configuration

### CloudNativePG

SSL is typically **not required** for connections within the Kubernetes cluster. The application will not use SSL by default for CloudNativePG connections.

### AWS RDS

SSL is typically **required** for AWS RDS connections. The application defaults to `require` mode for external hosts, but you can override this:

**SSL Mode Options:**
- `disable`: No SSL
- `allow`: Try non-SSL first, then SSL
- `prefer`: Try SSL first, then non-SSL (default for external)
- `require`: Require SSL
- `verify-ca`: Require SSL and verify CA
- `verify-full`: Require SSL and verify CA and hostname

**Example:**
```bash
SQL_SSL_MODE=require
```

## Migration Guide

### From AWS RDS to CloudNativePG

1. Update the `SQL_HOST` environment variable in your SealedSecret:
```bash
# Old (AWS RDS)
SQL_HOST=balancer-db.xxxxx.us-east-1.rds.amazonaws.com

# New (CloudNativePG)
SQL_HOST=balancer-postgres-rw
```

2. Update database credentials to match CloudNativePG secret

3. Remove or set `SQL_SSL_MODE` to `disable` (optional, as it's auto-detected)

4. Restart the application pods

### From CloudNativePG to AWS RDS

1. Update the `SQL_HOST` environment variable:
```bash
# Old (CloudNativePG)
SQL_HOST=balancer-postgres-rw

# New (AWS RDS)
SQL_HOST=balancer-db.xxxxx.us-east-1.rds.amazonaws.com
```

2. Update database credentials to match RDS credentials

3. Set `SQL_SSL_MODE=require` (or appropriate mode)

4. Ensure network connectivity (VPC peering, security groups, etc.)

5. Restart the application pods

## Troubleshooting

### Connection Issues

1. **Verify host format**: Check that `SQL_HOST` matches the expected format for your connection type

2. **Check network connectivity**:
- CloudNativePG: Ensure pods are in the same namespace
- AWS RDS: Verify VPC peering, security groups, and network ACLs

3. **Verify credentials**: Ensure username, password, and database name are correct

4. **Check SSL configuration**: For AWS RDS, ensure SSL is properly configured

### Common Errors

**"Connection refused"**
- Verify the host and port are correct
- Check if the database service is running
- Verify network connectivity

**"SSL required"**
- Add `SQL_SSL_MODE=require` for AWS RDS connections
- Verify SSL certificates are available

**"Authentication failed"**
- Verify username and password
- Check database user permissions
- Ensure the database exists

## References

- [Django Database Configuration](https://docs.djangoproject.com/en/4.2/ref/settings/#databases)
- [CloudNativePG Documentation](https://cloudnative-pg.io/)
- [AWS RDS PostgreSQL](https://docs.aws.amazon.com/rds/latest/userguide/CHAP_PostgreSQL.html)
- [PostgreSQL SSL Configuration](https://www.postgresql.org/docs/current/libpq-ssl.html)

Loading