Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e23102d
feat: added prometheus
kostya2505 May 11, 2026
408160c
feat: added grafana
kostya2505 May 11, 2026
04a8e57
feat: added loki
kostya2505 May 11, 2026
9375316
feat: added promtail
kostya2505 May 11, 2026
6650753
feat: created docker compose
kostya2505 May 11, 2026
d5bcb61
fix: fixed issue with wrapper in grafana
kostya2505 May 12, 2026
e7e1b2b
chore: deleted version number
kostya2505 May 12, 2026
29fba72
feat: added readme of member b
kostya2505 May 12, 2026
7332d16
fix: specified versions instead of latest
kostya2505 May 12, 2026
bd1d1bb
fix: made loki work with the new version
kostya2505 May 12, 2026
7a31662
fix: made promtail work with the new version
kostya2505 May 12, 2026
5497962
Add Docker Compose stack with NGINX load balancing and rate limits
May 13, 2026
8c0eadc
Merge remote-tracking branch 'origin/feature/member-a-nginx-compose' …
kostya2505 May 14, 2026
a6be17d
BREAKING CHANGE: merge with deletion of some files
kostya2505 May 14, 2026
16e1543
fix: prometheus targets all apps
kostya2505 May 14, 2026
d886a91
README.md changed
alberto-de-swerto May 17, 2026
a894de4
Polish observability project: fix docs, monitoring, routing, and CI
alberto-de-swerto May 17, 2026
317a461
fix(README.md): update correct links
alberto-de-swerto May 18, 2026
3b44980
Updated README.md: added powershell alternative command
alberto-de-swerto May 18, 2026
74f3268
add commands for recreating containers
alberto-de-swerto May 18, 2026
83e6164
fix issues, update docker-compose.yml
alberto-de-swerto May 18, 2026
b3be001
fix(grafana): fix dashboard
alberto-de-swerto May 18, 2026
3c1783c
pipeline fix
alberto-de-swerto May 18, 2026
97b9d92
README.md Update
alberto-de-swerto May 18, 2026
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
6 changes: 5 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ DB_CONNECT_RETRIES=20
DB_CONNECT_DELAY=2
DB_POOL_MAX_SIZE=10

NGINX_HTTP_PORT=80
NGINX_EXPORTER_PORT=9113

LOAD_DURATION=120
LOAD_CONCURRENCY=20
LOAD_INTERVAL=0.05
LOAD_WRITE_RATIO=0.35
TARGET_URL=http://app:8000
LOAD_TIMEOUT=5
LOADTEST_TARGET_URL=http://nginx
113 changes: 113 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
name: CI / CD

on:
pull_request:
push:
workflow_dispatch:

permissions:
contents: read

jobs:
validate:
name: Validate Source
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Prepare executable scripts
run: chmod +x scripts/*.sh

- name: Validate Docker Compose
run: docker compose config

- name: Validate Python syntax
run: python3 -m py_compile app/main.py loadtest/load_test.py

- name: Validate shell scripts
run: |
bash -n scripts/backup_postgres.sh
bash -n scripts/smoke_test.sh

smoke-deploy:
name: Smoke Deploy
runs-on: ubuntu-latest
needs: validate
env:
NGINX_HTTP_PORT: 8088
BASE_URL: http://localhost:8088

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Prepare executable scripts
run: chmod +x scripts/*.sh

- name: Start full stack
run: docker compose up -d --build --wait --wait-timeout 120

- name: Run smoke test
run: ./scripts/smoke_test.sh

- name: Exercise bundled load tester
run: |
docker compose --profile loadtest run --rm --no-deps \
-e LOAD_DURATION=10 \
-e LOAD_CONCURRENCY=8 \
-e LOAD_INTERVAL=0.05 \
-e LOAD_TIMEOUT=5 \
loadtester

- name: Show service status on failure
if: failure()
run: |
docker compose ps
docker compose logs --no-color

- name: Tear down stack
if: always()
run: docker compose down -v --remove-orphans

publish-app-image:
name: Publish App Image
runs-on: ubuntu-latest
needs: smoke-deploy
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write

steps:
- name: Checkout repository
uses: actions/checkout@v4

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

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository_owner }}/containerized-observability-app
tags: |
type=raw,value=latest
type=sha

- name: Build and publish app image
uses: docker/build-push-action@v6
with:
context: ./app
file: ./app/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
178 changes: 142 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,164 @@
# Member C Deliverables
# Containerized Observability App

This repository contains the application and data-layer artifacts for the team project:
## Project Topic

- A Flask demo app with PostgreSQL integration.
- A production-ready application Dockerfile.
- PostgreSQL persistence and initialization assets.
- A simple backup script based on `pg_dump`.
- A containerized load generator for dashboards and log aggregation.
**Highly Available Containerized Web App with Centralized Monitoring and Logging**

## Project layout
This project is a simple Flask web app with PostgreSQL.
It runs multiple replicas with Docker Compose, uses NGINX as a reverse proxy, and includes monitoring, centralized logging, and CI/CD.

- `app/`: Flask application source and Dockerfile.
- `loadtest/`: Async HTTP load generator and Dockerfile.
- `database/init/`: SQL executed by the PostgreSQL container on first boot.
- `scripts/backup_postgres.sh`: Creates compressed SQL backups into `./backups`.
- `compose.member-c.yml`: Compose fragment for Member A to merge into the final stack.
## Stack

## Application endpoints
- Python Flask
- PostgreSQL
- Docker Compose
- NGINX
- Prometheus
- Grafana
- Loki
- Promtail
- Node Exporter
- cAdvisor
- GitHub Actions

- `GET /`: service info, hostname, instance id, and exposed routes.
- `GET /health`: liveness endpoint.
- `GET /ready`: readiness endpoint with live PostgreSQL probe.
- `GET /api/visits`: persists a visit event and returns the running total.
- `GET /api/messages`: returns the most recent messages.
- `POST /api/messages`: inserts a new message row.
- `GET /metrics`: Prometheus scrape endpoint for request and app metrics.
## Features

The app writes JSON logs to stdout, which makes it easy for Promtail/Loki or ELK to collect and index container logs.
- Flask API
- PostgreSQL database
- 3 app replicas
- NGINX load balancing and rate limiting
- Prometheus and Grafana for metrics
- Loki and Promtail for logs
- Node Exporter and cAdvisor for host/container metrics
- GitHub Actions CI/CD
- PostgreSQL backup script

## Local run
## Run the project

1. Copy `.env.example` values into your local shell or a `.env` file.
2. Start the app and database:
```bash
cp .env.example .env
docker compose up -d --build
```

## Useful links

- App: `http://127.0.0.1/`
- Grafana: `http://127.0.0.1:3000`
- Prometheus: `http://127.0.0.1:9090`
- Loki health check: `http://127.0.0.1:3100/ready`
- NGINX exporter: `http://127.0.0.1:9113/metrics`

Grafana login:

- user: `admin`
- password: `admin`

## Quick check

```bash
curl http://127.0.0.1/health
curl http://127.0.0.1/ready
curl http://127.0.0.1/api/visits
curl http://127.0.0.1/api/messages
```

You can also run:

```bash
bash scripts/smoke_test.sh
```

## Load balancing

Default mode is `round robin`.

Check:

```bash
for i in $(seq 1 6); do
curl -s -D - http://127.0.0.1/ -o /dev/null | grep X-App-Instance
done
```

PowerShell alternative:

```powershell
1..6 | ForEach-Object { (Invoke-WebRequest http://127.0.0.1/ -UseBasicParsing).Headers["X-App-Instance"] }
```

If you want `least connections`:

```bash
docker compose -f docker-compose.yml -f docker-compose.leastconn.yml up -d --build
```

After that, run the load balancing check again, because Docker Compose recreates containers when switching the mode.

To return to the default `round robin` mode:

```bash
docker compose -f compose.member-c.yml up --build
docker compose up -d --build
```

3. Generate traffic for dashboards:
## Monitoring and logs

Prometheus collects metrics from:

- `app1`
- `app2`
- `app3`
- `nginx-exporter`
- `node-exporter`
- `cadvisor`

Grafana dashboards:

- `Flask App Dashboard`
- `Infrastructure Overview`

Logs are viewed in Grafana Explore through the Loki datasource.

In `Explore`, switch the datasource from `Prometheus` to `Loki` first.

Example Loki query in Grafana Explore:

```logql
{compose_service=~".+"}
```

## Load test

```bash
docker compose --profile loadtest run --rm --no-deps loadtester
```

## Database backup

```bash
docker compose -f compose.member-c.yml --profile loadtest up --build loadtester
bash scripts/backup_postgres.sh
```

4. Create a PostgreSQL backup:
## CI/CD

Workflow file:

`.github/workflows/ci-cd.yml`

The pipeline:

- validates the project
- starts the stack
- runs the smoke test
- publishes the app image on push to `main`

## Stop the project

```bash
./scripts/backup_postgres.sh
docker compose down
```

## Handoff notes
Full cleanup:

- Member A can merge `compose.member-c.yml` into the final `docker-compose.yml`.
- The PostgreSQL persistence volume is `postgres_data`.
- The PostgreSQL initialization scripts live in `./database/init`.
- Member B can scrape the Flask app at `http://app:8000/metrics`.
- The load generator is meant to target NGINX in the final topology, so `TARGET_URL` defaults should point to the reverse proxy in the team compose file.
```bash
docker compose down -v
```
56 changes: 0 additions & 56 deletions compose.member-c.yml

This file was deleted.

Loading
Loading