Lekcja 001 — Budowanie fundamentu DevOps dla platformy symulacji morskiej farmy wiatrowej 510 MW¶
Nawigacja lekcji
Poprzednia: Lekcja 000 — Planowanie projektu | Następna: Lekcja 002 — Infrastruktura internacjonalizacji
Faza: P0 | Język: Polski | Postęp: 2 z 19 | Wszystkie lekcje | Plan nauki
Data: 2026-02-20 Commity: 14 commitów (
f250002→9fc5c88) Zakres commitów:f2500024193bb88db74d1269612cd7c14fbe0614..9fc5c88d5b74a1070aae0c1c0a89b4cac8704cb0Faza: P0 (Fundament DevOps) Poprzednia lekcja: Brak last_commit_hash: 9fc5c88d5b74a1070aae0c1c0a89b4cac8704cb0
Czego się nauczysz¶
- Jak zorganizować monorepo dla pełnostosowej przemysłowej platformy symulacyjnej (FastAPI + React + PostgreSQL + Redis)
- Dlaczego potok CI/CD jest pierwszą rzeczą, którą buduje profesjonalny zespół inżynierski — zanim pojawi się jakakolwiek logika domenowa
- Jak Docker Compose orkiestruje architektury wielousługowe z kontrolami stanu zdrowia (health checks) i porządkowaniem zależności
- Dlaczego automatyczne skanowanie bezpieczeństwa ma znaczenie w projektach open-source i jak zbudować skaner sekretów
- Jak Dependabot i hooki pre-commit tworzą strategię „defense in depth" na rzecz jakości kodu
Sekcja 1: Architektura dokumentacji — jedyne źródło prawdy w projekcie¶
Problem z realnego świata¶
Wyobraź sobie, że jako inżynier rozruchowy przyjeżdżasz do nowej morskiej farmy wiatrowej. Zanim dotkniesz choćby jednego wyłącznika, potrzebujesz instrukcji stanowiskowej — jednego dokumentu, który podaje napięcia, nastawy przekaźników zabezpieczeniowych, przekroje kabli i procedury awaryjne. Bez niej działasz na ślepo. Projekty software'owe są takie same: bez jasnego, autorytatywnego zestawu dokumentów każdy programista inaczej zgaduje, jak system powinien działać.
Co mówią standardy¶
Nasza dokumentacja jest zgodna z hierarchiczną strukturą dokumentów IEC 61355 — szczegóły znajdziesz w filozofii dokumentacyjnej. Kluczowa zasada: jedna mapa drogowa rządzi wszystkim, a zarchiwizowane wersje zapewniają identyfikowalność.
Co zbudowaliśmy¶
Zmienione pliki:
- CLAUDE.md — „instrukcja stanowiskowa" projektu dla asystenta AI: automatycznie ładuje referencje przy każdej sesji
- docs/Project_Roadmap.md — Skonsolidowana specyfikacja (1 646 wierszy) łącząca v1 i v2 w jedno autorytatywne źródło
- docs/SKILL.md — Standardy inżynierskie i konwencje kodowania (722 wiersze)
- docs/Learning_Roadmap.md — 32-tygodniowy plan samokształcenia
- docs/archive/Project_Roadmap_v1.md i docs/archive/Project_Roadmap_v2.md — Wersje historyczne zachowane dla identyfikowalności
- .gitignore — Wyklucza pliki generowane, sekrety i duże dane z kontroli wersji
Mieliśmy dwa dokumenty roadmap (v1 i v2), które częściowo się wzajemnie wykluczały. Dokument v2 był „analizą luk", która korygowała liczbę turbin, dodawała wymagania dotyczące symulacji dynamicznych i aktualizowała odniesienia do norm. Zamiast utrzymywać dwa pliki i zmuszać każdego czytelnika do mentalnego ich łączenia, skonsolidowaliśmy je w jeden Project_Roadmap.md i zarchiwizowaliśmy oryginały.
Dlaczego to jest ważne¶
Dlaczego potrzebujemy jednego skonsolidowanego roadmapu zamiast oddzielnych dokumentów v1 i v2? Bo niejednoznaczność niszczy projekty. Jeśli jeden dokument mówi 30 turbin, a drugi 34, programista implementujący optymalizator układu losowo wybierze jedną wartość — i połowa dalszych obliczeń będzie błędna. Jedno źródło prawdy całkowicie eliminuje tę klasę błędów.
Dlaczego archiwizujemy stare wersje zamiast je usuwać? Identyfikowalność. W prawdziwych projektach farm wiatrowych każda zmiana projektowa jest śledzona (IEC 61400-1 wymaga ścieżki audytu „podstawy projektowej"). Archiwizacja pozwala zobaczyć, dlaczego decyzje się zmieniły — na przykład aktualizacja z 30 × 17 MW do 34 × 15 MW turbin wynikała z jasności edukacyjnej (bardziej realistyczny układ tablicy do analizy efektu cienia aerodynamicznego).
Przegląd kodu¶
Protokół sesji CLAUDE.md i automatycznie ładowane referencje zostały szczegółowo omówione w Lekcji 000 — Sekcja 2. Kluczowa myśl: każda sesja zaczyna się od tego samego kontekstu inżynierskiego, tak jak operator sterowni czyta dziennik stacji przy zmianie zmiany.
.gitignore jest równie ważny — zapobiega przypadkowemu commitowaniu sekretów, danych pogodowych (pliki ERA5 NetCDF mogą mieć gigabajty) oraz artefaktów kompilacji:
# ERA5 weather data (too large for git)
*.nc
*.grib
*.grib2
# Secrets — NEVER commit
.env
.env.*
!.env.example
Te wzorce chronią nas przed najczęstszymi błędami w projektach open-source: wyciekiem kluczy API i zaśmiecaniem repozytorium danymi binarnymi.
Kluczowa koncepcja: Jedyne źródło prawdy (SSOT)
Po ludzku: Zamiast przechowywać informacje rozrzucone po pięciu różnych dokumentach, które mogą się ze sobą nie zgadzać, umieszczasz je wszystkie w jednym miejscu. Jeśli ktoś pyta „ile turbin?" — jest dokładnie jedno miejsce do sprawdzenia i zawsze zawiera właściwą odpowiedź.
Analogia: Wyobraź sobie aplikację kontaktów w telefonie. Gdybyś miał trzy różne książki adresowe, a numer przyjaciela był inny w każdej z nich, traciłbyś czas na ustalenie, która jest poprawna. SSOT oznacza jedną książkę adresową, jeden numer — zawsze prawidłowy.
W tym projekcie: docs/Project_Roadmap.md jest źródłem prawdy dla całej specyfikacji farmy wiatrowej 510 MW. Każda liczba turbin, napięcie kabla i odniesienie do kodeksu sieciowego znajduje się tam. Gdy będziemy kodować optymalizator układu P1, odczytamy specyfikacje turbin właśnie z tego jednego dokumentu — nigdy z zarchiwizowanych v1 ani v2.
Sekcja 2: Szkielet backendu FastAPI — twój pierwszy mikroserwis¶
Problem z realnego świata¶
Wyobraź sobie, że budujesz dom. Zanim zainstalуjesz hydraulikę czy elektrykę, wylewasz fundamenty i stawiasz szkielet. Szkielet jeszcze nic nie robi — po prostu definiuje, gdzie będą ściany. Szkielet backendu jest taki sam: to strukturalna rama, do której cała przyszła logika dziedzinowa (modele efektu cienia, symulacje przepływu mocy, API prognozowania) będzie się podczepiać.
Co mówią standardy¶
IEC 62443 (Sieci komunikacyjne dla systemów przemysłowych — bezpieczeństwo sieci i systemów) wymaga, aby systemy SCADA i sterowania miały jasno zdefiniowaną architekturę bezpieczeństwa od pierwszego dnia — nie przykręcaną później. Choć nasz endpoint /health jest prosty, middleware CORS, konfiguracja oparta na środowisku i izolacja kontenerów, które teraz konfigurujemy, będą fundamentem bezpiecznego dostępu do API, gdy w P3 będziemy budować prawdziwy interfejs SCADA.
Co zbudowaliśmy¶
Zmienione pliki:
- backend/app/main.py — Aplikacja FastAPI z endpointem health i middleware CORS
- backend/app/config.py — Pydantic Settings do konfiguracji opartej na środowisku
- backend/pyproject.toml — Definicja pakietu Python ze wszystkimi zależnościami i konfiguracjami narzędzi
- backend/tests/test_health.py — Pierwszy test: dowodzi, że endpoint health działa
- backend/Dockerfile — Definicja obrazu kontenera dla usługi backendowej
Backend jest celowo minimalny. Ma dokładnie jeden endpoint (/health) i jedną klasę konfiguracyjną. Ale zauważ, co już zawiera: middleware CORS (potrzebny, gdy React rozmawia z FastAPI), asynchroniczny sterownik bazy danych (asyncpg) i ścisłe sprawdzanie typów (mypy w trybie strict). To nie jest przedwczesne — to strukturalna stal, do której wszystko inne się przykręca.
Dlaczego to jest ważne¶
Dlaczego zaczynamy od endpointu
/healthzamiast od razu przechodzić do API symulacji wiatrowej? Bo Docker, Kubernetes i potoki CI wszystkie potrzebują sposobu na pytanie „czy ta usługa działa?" przed skierowaniem do niej ruchu. Endpoint/healthto tętno serwisu. Bez niego orkiestrator kontenerów nie może odróżnić usługi, która uległa awarii, od usługi, która jest powolna — a w prawdziwym systemie SCADA ta różnica to różnica między kontrolowanym wyłączeniem a awarią zasilania.Dlaczego używamy Pydantic Settings zamiast bezpośredniego odczytu zmiennych środowiskowych przez
os.getenv()? Bezpieczeństwo typów i walidacja.os.getenv("DATABASE_URL")zwracaOptional[str]— musisz samodzielnie sprawdzaćNone, ręcznie rzutować liczby całkowite i parsować listy. Pydantic Settings robi to wszystko automatycznie, z czytelnymi komunikatami o błędach, jeśli brakuje wymaganej zmiennej. W morskim systemie sterowania farmą wiatrową nieprawidłowo skonfigurowany URL bazy danych powinien głośno zawieść przy starcie — nie po cichu o 3 w nocy podczas burzy.
Przegląd kodu¶
Moduł konfiguracyjny demonstruje zasadę „12-Factor App" — konfiguracja żyje w środowisku, nie w kodzie:
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
"""Application settings loaded from environment variables."""
model_config = SettingsConfigDict(
env_file=".env", # Load from .env file in development
env_file_encoding="utf-8",
case_sensitive=False, # DATABASE_URL and database_url both work
)
# Application
app_name: str = "Baltic Wind HV Control Platform"
debug: bool = False
# Database (PostgreSQL + TimescaleDB)
database_url: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/balticwind"
# Redis
redis_url: str = "redis://localhost:6379/0"
# CORS
cors_origins: list[str] = ["http://localhost:3000", "http://localhost:5173"]
settings = Settings()
Każde pole ma wartość domyślną dla lokalnego środowiska developerskiego, ale w środowisku produkcyjnym (Docker Compose, Kubernetes) zmienne środowiskowe je nadpisują. Pole cors_origins jest typu list[str], a Pydantic automatycznie parsuje ciąg JSON '["http://localhost:3000"]' — bez ręcznego parsowania.
Test health check to nasz pierwszy „smoke test" — dowodzi, że aplikacja może się uruchomić i odpowiadać:
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_health_returns_ok():
"""GET /health should return 200 with status ok."""
response = client.get("/health")
assert response.status_code == 200
assert response.json() == {"status": "ok"}
Ten test uruchamia się w CI przy każdym pushu. Jeśli aplikacja nie może się nawet uruchomić (błąd importu, brakująca zależność, awaria konfiguracji), test to wykryje w sekundach — zanim dotrze do produkcji.
Kluczowa koncepcja: Aplikacja 12-Factor — konfiguracja w środowisku
Po ludzku: Nigdy nie wpisuj haseł, adresów baz danych ani kluczy API bezpośrednio w kodzie. Zamiast tego Twój kod odczytuje je ze środowiska komputera (np. zmiennych środowiskowych), a Ty ustawiasz te wartości inaczej na laptopie, a inaczej w środowisku produkcyjnym.
Analogia: Pomyśl o systemie kluczy głównych w budynku. Mechanizm zamka (kod) jest wszędzie taki sam, ale klucz (konfiguracja) zmienia się w zależności od tego, kto uzyskuje dostęp. Woźny ma inny klucz niż dyrektor, ale drzwi działają tak samo.
W tym projekcie: Nasze config.py używa postgresql+asyncpg://postgres:postgres@localhost:5432/balticwind do celów developerskich, ale Docker Compose nadpisuje to adresem postgres:postgres@postgres:5432/balticwind (uwaga: postgres to nazwa usługi Docker, nie localhost). W prawdziwym wdrożeniu menedżer sekretów wstrzykiwałby właściwe hasło.
Sekcja 3: Docker Compose — orkiestracja sterowni¶
Problem z realnego świata¶
System sterowania farmą wiatrową nie działa na jednym komputerze. Serwer SCADA rozmawia z bazą historyczną, która rozmawia z magazynem szeregów czasowych, który rozmawia z wyświetlaczami HMI. Jeśli uruchomisz HMI zanim baza danych będzie gotowa, system się posypie. Jeśli baza danych startuje przed skonfigurowaniem sieci, nie może przyjmować połączeń. Potrzebujesz orkiestratora — czegoś, co uruchamia usługi w odpowiedniej kolejności i sprawdza, czy każda z nich jest sprawna, zanim uruchomi następną.
Co mówią standardy¶
IEC 62351 (Zarządzanie systemami elektroenergetycznymi i związana z tym wymiana informacji — bezpieczeństwo danych i komunikacji) kładzie nacisk na segmentację sieci i izolację usług dla systemów SCADA. Kontenery Docker zapewniają izolację na poziomie procesu — każda usługa działa we własnej przestrzeni nazw z własnym systemem plików, a komunikacja odbywa się tylko przez jawnie zadeklarowane porty. Jest to uproszczona wersja stref sieciowych (DMZ, strefa sterowania, strefa polowa) zdefiniowanych w IEC 62443.
Co zbudowaliśmy¶
Zmienione pliki:
- docker-compose.yml — Stos czterech usług: PostgreSQL+TimescaleDB, Redis, backend FastAPI, frontend React
Plik Docker Compose definiuje całe środowisko developerskie jako kod. Zwróć uwagę na bloki healthcheck i warunki depends_on:
services:
postgres:
image: timescale/timescaledb:latest-pg16
environment:
POSTGRES_DB: balticwind
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
backend:
build: ./backend
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
Dlaczego to jest ważne¶
Dlaczego używamy TimescaleDB zamiast zwykłego PostgreSQL? Ponieważ dane farmy wiatrowej to zasadniczo dane szeregów czasowych. Co 10 sekund każda z 34 turbin raportuje moc czynną, prędkość wiatru, skok łopat, orientację gondoli i temperaturę generatora. To tysiące punktów danych na minutę. TimescaleDB rozszerza PostgreSQL o hipertabele — automatycznie partycjonowane tabele zoptymalizowane pod kątem wstawiania szeregów czasowych i zapytań zakresowych. W P1, gdy będziemy przechowywać dane pogodowe ERA5 i symulowaną moc wyjściową, TimescaleDB sprawi, że zapytania typu „średnia moc dla każdej turbiny za ostatnie 24 godziny" będą działać 10–100 razy szybciej niż w standardowym PostgreSQL.
Dlaczego w
depends_onbackendu używamycondition: service_healthyzamiast po prostudepends_on: [postgres]? Bo „kontener uruchomiony" to nie to samo co „usługa gotowa". PostgreSQL potrzebuje kilku sekund na zainicjowanie katalogu danych, wykonanie odtwarzania WAL i rozpoczęcie akceptowania połączeń. Bezservice_healthybackend próbowałby połączyć się z PostgreSQL natychmiast, otrzymałby błąd „connection refused" i mógłby ulec awarii. Health checkpg_isreadyzapewnia, że backend startuje dopiero po tym, jak PostgreSQL faktycznie akceptuje zapytania.
Przegląd kodu¶
Wzorzec health check jest wart przestudiowania, bo będziemy go intensywnie używać w P3 (monitorowanie SCADA):
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"] # Run this command inside the container
interval: 5s # Check every 5 seconds
timeout: 5s # If the check takes >5s, consider it failed
retries: 5 # After 5 consecutive failures, mark container as unhealthy
Jest to ten sam wzorzec stosowany w prawdziwych systemach przemysłowych. Przekaźniki zabezpieczeniowe sprawdzają stan wyłącznika co kilkaset milisekund. Jeśli wyłącznik nie odpowie w ciągu limitu czasu, przekaźnik eskaluje — najpierw alarm, potem polecenie wyzwolenia. Nasze health checks Docker są uproszczoną wersją tego samego wzorca: odpytywanie → limit czasu → ponowna próba → eskalacja.
Sekwencja startowa health check w formie graficznej:
sequenceDiagram
participant DC as Docker Compose
participant PG as PostgreSQL
participant RD as Redis
participant BE as Backend (FastAPI)
participant FE as Frontend (React)
DC->>PG: Start container
DC->>RD: Start container
loop Every 5s
DC->>PG: pg_isready -U postgres
PG-->>DC: not ready
end
PG-->>DC: ready (healthy)
loop Every 5s
DC->>RD: redis-cli ping
end
RD-->>DC: PONG (healthy)
DC->>BE: Start (depends_on: healthy)
BE->>PG: Connect asyncpg
BE->>RD: Connect redis
BE-->>DC: /health → 200 OK
DC->>FE: Start (depends_on: backend)
FE->>BE: API requests
Kluczowa koncepcja: Orkiestracja usług i health checks
Po ludzku: Gdy masz wiele programów, które zależą od siebie, potrzebujesz systemu, który uruchamia je w odpowiedniej kolejności i upewnia się, że każdy faktycznie działa, zanim uruchomi kolejny.
Analogia: Pomyśl o kuchni restauracyjnej. Kucharz przygotowujący surowce najpierw kroi warzywa, potem kucharz liniowy zaczyna gotować. Kucharz liniowy nie zaczyna smażyć, dopóki przygotowanie nie jest ukończone — a szef kuchni sprawdza, czy przygotowanie jest naprawdę gotowe, a nie tylko że kucharz przygotowujący już przyszedł. Docker Compose jest szefem kuchni.
W tym projekcie: Nasz backend FastAPI nie może działać bez PostgreSQL i Redis. Docker Compose zapewnia, że PostgreSQL akceptuje połączenia (pg_isready), a Redis odpowiada na ping, zanim backend wystartuje. Gdy dodamy solver siatki Pandapower w P2, będzie on stosował ten sam wzorzec — zależny od zdrowego backendu przed uruchomieniem symulacji.
Sekcja 4: Potok CI/CD i bramki jakości — automatyczny inspektor¶
Problem z realnego świata¶
W morskiej farmie wiatrowej każdy element wyposażenia przechodzi inspekcję jakości przed instalacją. Transformator nie opuszcza fabryki bez testu rutynowego. Kabel nie jest ciągnięty bez testu megomierzem. Zasada jest prosta: wykrywaj wady zanim dotrą na pole, bo naprawianie ich na morzu kosztuje 10 razy więcej. CI/CD stosuje tę samą zasadę do kodu: wykrywaj błędy zanim dotrą do produkcji.
Co mówią standardy¶
IEC 61400-1 (Systemy wytwarzania energii wiatrowej — wymagania projektowe) nakazuje formalny proces weryfikacji i walidacji oprogramowania sterującego turbin wiatrowych. Choć nasza symulacja nie jest krytyczna dla bezpieczeństwa, przyjmujemy taką samą dyscyplinę: każda zmiana kodu musi przejść automatyczne sprawdzenia lintingu, typów i testy, zanim będzie mogła zostać scalona. Jest to defense in depth — wiele niezależnych bramek jakości, każda wychwytująca inne klasy błędów.
Co zbudowaliśmy¶
Zmienione pliki:
- .github/workflows/ci.yml — Cztery równoległe zadania CI: lint backendu, testy backendu, lint frontendu, testy frontendu
- .github/workflows/docs.yml — Automatyczne wdrażanie MkDocs na GitHub Pages
- .pre-commit-config.yaml — Lokalne bramki jakości uruchamiane przed każdym commitem
- .editorconfig — Spójne formatowanie w różnych edytorach (szerokość tabulacji, końce wierszy, końcowe spacje)
- Makefile — Uniwersalny uruchamiacz zadań z ponad 15 celami do instalacji, lintowania, testowania, Dockera i dokumentacji
- .github/dependabot.yml — Automatyczne cotygodniowe aktualizacje zależności dla pip, npm i GitHub Actions
Potok CI uruchamia cztery zadania równolegle przy każdym pushu i PR:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Backend Lint │ │ Backend Test │ │ Frontend Lint│ │Frontend Test │
│ ruff check │ │ pytest │ │ tsc --noEmit│ │ vitest run │
│ ruff format │ │ coverage │ │ eslint │ │ coverage │
│ mypy │ │ │ │ │ │ │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
Dlaczego to jest ważne¶
Dlaczego rozdzielamy lint i testy na różne zadania zamiast uruchamiać je sekwencyjnie? Równoległość i izolacja. Jeśli linting zakończy się niepowodzeniem, chcesz wiedzieć o tym natychmiast — nie czekaj na zakończenie 5-minutowego zestawu testów. A jeśli test się nie powiedzie, chcesz wiedzieć, czy to błąd logiczny (zadanie testowe) czy naruszenie stylu (zadanie lint), a nie debugować obu naraz. W systemach elektroenergetycznych jest to ta sama zasada co posiadanie oddzielnych przekaźników zabezpieczeniowych dla nadprądu i zwarcia doziemnego — każdy pilnuje innej klasy problemu.
Dlaczego używamy zarówno hooków pre-commit, jak i CI? Defense in depth. Hooki pre-commit wychwytują problemy zanim commit zostanie utworzony — dostajesz natychmiastową informację zwrotną na laptopie. CI wychwytuje problemy po pushu — weryfikuje, że kod działa w czystym środowisku (nie tylko na Twoim komputerze z konkretną wersją Pythona i zainstalowanymi pakietami). Razem tworzą dwie niezależne bramki jakości, tak jak turbina wiatrowa ma zarówno hamulec mechaniczny, jak i aerodynamiczny.
Przegląd kodu¶
Makefile zasługuje na uwagę, bo jest codziennym narzędziem programisty — jedynym punktem wejścia dla wszystkich typowych zadań:
lint: lint-backend lint-frontend ## Run all linters
lint-backend: ## Lint Python code (ruff + mypy)
cd backend && ruff check app/ tests/
cd backend && ruff format --check app/ tests/
cd backend && mypy app/
test: test-backend test-frontend ## Run all tests
test-backend: ## Run Python tests with coverage
cd backend && pytest --cov=app --cov-report=term-missing tests/
Zwróć uwagę na komentarze ## po każdym celu — są one samodokumentujące. Uruchomienie make help drukuje sformatowaną listę wszystkich dostępnych celów. To mały detal, który ogromnie poprawia doświadczenie programisty: nikt nie musi czytać Makefile, żeby wiedzieć, jakie polecenia są dostępne.
Konfiguracja pre-commit łączy ze sobą wiele narzędzi — każde wychwytuje inną klasę błędów. Jeśli check-yaml znajdzie błąd składni w Twoim workflow CI, blokuje commit zanim wyślesz uszkodzony YAML do GitHub.
Pełna konfiguracja pre-commit
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
hooks:
- id: trailing-whitespace # Catches: invisible formatting errors
- id: end-of-file-fixer # Catches: POSIX compliance issues
- id: check-yaml # Catches: broken YAML syntax (CI configs!)
- id: check-added-large-files # Catches: accidentally committed binaries
args: ['--maxkb=1000'] # Block files > 1 MB
- id: check-merge-conflict # Catches: forgotten merge conflict markers
- repo: https://github.com/astral-sh/ruff-pre-commit
hooks:
- id: ruff # Catches: Python code quality issues
args: [--fix] # Auto-fix what it can
- id: ruff-format # Catches: inconsistent formatting
Potok defense-in-depth:
graph LR
Dev["Developer<br/>writes code"] --> PC["Pre-commit<br/>Hooks"]
PC -->|"ruff, mypy,<br/>eslint"| Push["git push"]
Push --> CI["GitHub Actions<br/>CI Pipeline"]
CI -->|"4 parallel<br/>jobs"| Sec["Security<br/>Audit Skill"]
Sec -->|"13 secret<br/>patterns"| Merge["Merge to<br/>main"]
style PC fill:#4a9eff,color:#fff
style CI fill:#4a9eff,color:#fff
style Sec fill:#4a9eff,color:#fff
Kluczowa koncepcja: Defense in depth — wiele niezależnych bramek jakości
Po ludzku: Nie polegaj na jednej kontroli bezpieczeństwa. Miej wiele kontroli na różnych etapach, każda szukająca innych problemów. Jeśli jedna coś przeoczy, następna to wyłapie.
Analogia: Pomyśl o kontroli bezpieczeństwa na lotnisku. Przechodzisz przez bramkę do wykrywania metalu (hook pre-commit), potem Twoja torba prześwietlana jest rentgenem (potok CI), a osoba wizualnie sprawdza Twój paszport (przegląd kodu). Każda warstwa wychwytuje to, co inne mogą pominąć. Żadna warstwa nie jest doskonała, ale razem są bardzo skuteczne.
W tym projekcie: Programista pisze kod → hooki pre-commit sprawdzają formatowanie i typy lokalnie → CI uruchamia pełny zestaw testów w czystym środowisku → umiejętność github-push skanuje sekrety przed wysłaniem. Trzy niezależne bramki, każda wychwytująca inną klasę błędów. Gdy budujemy automatykę SCADA w P3, zobaczymy ten sam wzorzec w koordynacji przekaźników zabezpieczeniowych: zabezpieczenie podstawowe, zabezpieczenie rezerwowe i zabezpieczenie od niesprawności wyłącznika.
Sekcja 5: Wzmocnienie bezpieczeństwa — strażnik pushowania¶
Problem z realnego świata¶
Wyobraź sobie, że jesteś operatorem sterowni i ktoś prosi Cię o zamknięcie wyłącznika. Zanim to zrobisz, sprawdzasz: Czy linia jest pozbawiona napięcia? Czy uziemnik jest odłączony? Czy jest ważny program łączeń? Nie zamykasz wyłącznika tylko dlatego, że ktoś o to poprosił — najpierw weryfikujesz warunki. Umiejętność github-push jest taka sama: sprawdza wyciekłe sekrety, niebezpieczne pliki i problemy z jakością kodu, zanim pozwoli, żeby kod dotarł do publicznego repozytorium.
Co mówią standardy¶
OWASP Top 10 i CWE-798 (Użycie zakodowanych na stałe poświadczeń) identyfikują zakodowane na stałe sekrety jako jedną z najczęstszych i najniebezpieczniejszych luk w oprogramowaniu. W przypadku projektu open-source wypchnięcie hasła do bazy danych lub klucza API do publicznego repozytorium GitHub oznacza, że jest ono natychmiast dostępne dla całego internetu. Nasz audyt bezpieczeństwa ma na celu wychwycenie tego, zanim opuści komputer programisty.
Co zbudowaliśmy¶
Zmienione pliki:
- .claude/skills/github-push/SKILL.md — 7-fazowy bezpieczny workflow push ze skanerem sekretów i wykrywaniem niebezpiecznych plików
- Wzmocnienie bezpieczeństwa: dodano wzorce haseł YAML, listę dozwolonych wyjątków, obowiązkowe potwierdzenie użytkownika dla pozycji WARN
Umiejętność github-push to 250-wierszowa definicja workflow działająca jako bramka bezpieczeństwa. Uruchamia siedem faz: rozpoznanie (git status, diff, log, branch), audyt bezpieczeństwa (skaner sekretów, skaner niebezpiecznych plików, kontrole jakości kodu), werdykt (BLOCK lub WARN), staging (plik po pliku, nigdy git add .), formatowanie komunikatu commita, push i raport podsumowujący.
Dlaczego to jest ważne¶
Dlaczego potrzebujemy automatycznego skanera sekretów, skoro
.gitignorejuż zapobiega commitowaniu plików.env? Ponieważ.gitignoreobejmuje tylko pliki wymienione w nim. Programista może zakodować hasło bezpośrednio w kodzie Python (API_KEY = "sk-abc123..."), a.gitignoretego nie wyłapie — to nie jest plik z kropką, to plik.py. Skaner sekretów używa wzorców regex do przeszukiwania zawartości diffa, wychwytując zakodowane na stałe sekrety niezależnie od tego, w którym pliku się znajdują.Dlaczego dodaliśmy „dozwolone wyjątki" dla domyślnych ustawień
docker-compose.ymlzamiast po prostu ignorować wszystkie WARNy? Niuans.POSTGRES_PASSWORD: postgresw docker-compose.yml to domyślna wartość developerska nadpisywana przez zmienne środowiskowe w produkcji — to jest akceptowalne. AlePOSTGRES_PASSWORD: moje_prawdziwe_haslo_produkcyjneto prawdziwy wyciek. Tworząc jawną listę wyjątków, dokumentujemy dlaczego pewne wzorce są akceptowalne i nadal zmuszamy programistę do ich potwierdzenia. Żadnego cichego pomijania — operator zawsze widzi ostrzeżenie, nawet jeśli jest spodziewane.
Przegląd kodu¶
Audyt bezpieczeństwa używa dopasowywania wzorców do wykrywania różnych kategorii ryzyka:
| Pattern | Description | Action |
|---------|-------------|--------|
| `password\s*=\s*['"]` | Hardcoded password (code) | BLOCK |
| `PASSWORD[:=]\s*.+` (not in docker-compose) | Hardcoded password (config) | WARN |
| `sk-[a-zA-Z0-9]{20,}` | OpenAI/Anthropic API key | BLOCK |
| `ghp_[a-zA-Z0-9]{36}` | GitHub personal access token | BLOCK |
| `-----BEGIN.*PRIVATE KEY-----` | PEM private key | BLOCK |
Kluczowym rozróżnieniem między BLOCK a WARN jest odwracalność. Pozycja BLOCK (wyciekły klucz API) nie może zostać cofnięta — raz wypchnięta, klucz jest skompromitowany i musi zostać zmieniony. Pozycja WARN (domyślne hasło developerskie) jest akceptowalna, ale powinna zostać potwierdzona. Odzwierciedla to filozofię przekaźników zabezpieczeniowych: natychmiastowe wyzwolenie dla zwarć (BLOCK) vs. opóźniony alarm dla stanów nieprawidłowych (WARN).
Commit wzmacniający rozwiązał konkretny problem napotkany podczas Fazy 0: skaner bezpieczeństwa blokował docker-compose.yml, bo zawierał POSTGRES_PASSWORD: postgres. Jest to prawidłowa domyślna wartość developerska, a nie wyciek sekretu. Naprawa dodała wyjątki uwzględniające kontekst:
**Allowed exceptions (WARN only, not BLOCK):**
- `docker-compose.yml` with `POSTGRES_PASSWORD: postgres` — local dev default
- `config.py` with `localhost` defaults — overridden in production
Uczy to ważnej lekcji: narzędzia bezpieczeństwa muszą być skalibrowane. Zbyt restrykcyjne — programiści je omijają. Zbyt łagodne — przegapiają prawdziwe problemy. Złoty środek to domyślna surowość z udokumentowanymi wyjątkami.
Kluczowa koncepcja: Shift-left security — wykrywaj problemy wcześnie
Po ludzku: Zamiast sprawdzać problemy bezpieczeństwa po tym, jak kod jest już w internecie, sprawdzaj je jak najwcześniej — najlepiej zanim kod w ogóle opuści Twój komputer.
Analogia: To jak sprawdzanie pisowni podczas pisania, a nie po wysłaniu listu. Jeśli zauważysz błąd przed wysłaniem, po prostu go poprawiasz. Jeśli zauważysz po — musisz wysłać korektę, a odbiorca już widział błąd.
W tym projekcie: Umiejętność github-push skanuje każdy diff pod kątem zakodowanych na stałe sekretów przed utworzeniem commita. Jeśli znajdzie klucz API w kodzie, całkowicie blokuje push. Jest to krytyczne, bo nasze repozytorium jest publiczne — gdy sekret zostanie wypchnięty do GitHub, jest w historii git na zawsze (nawet jeśli usuniesz plik w następnym commicie). Prewencja jest nieskończenie lepsza niż naprawianie.
Sekcja 6: Automatyczne zarządzanie zależnościami — strażnik łańcucha dostaw¶
Problem z realnego świata¶
Turbina wiatrowa składa się z tysięcy komponentów od setek dostawców. Jeśli producent śrub odkryje wadę w partii, każda turbina używająca tych śrub musi zostać skontrolowana. Producent wysyła powiadomienie o wycofaniu, a operator farmy wiatrowej aktualizuje harmonogram konserwacji. Zależności oprogramowania działają tak samo: gdy biblioteka publikuje łatkę bezpieczeństwa, każdy projekt używający tej biblioteki musi zostać zaktualizowany.
Co mówią standardy¶
NIST SP 800-53 (Kontrole bezpieczeństwa i prywatności) — kontrola SA-12 wymaga od organizacji monitorowania łańcucha dostaw oprogramowania pod kątem luk i stosowania łatek w odpowiednim czasie. Dependabot automatyzuje to, skanując pyproject.toml, package.json i workflow GitHub Actions co tydzień oraz otwierając pull requesty dla aktualizacji.
Co zbudowaliśmy¶
Zmienione pliki:
- .github/dependabot.yml — Cotygodniowe automatyczne sprawdzanie aktualizacji pip, npm i GitHub Actions
- Sześć PR-ów Dependabota scalone: bumpy GitHub Actions (checkout v4→v6, setup-python v5→v6, setup-node v4→v6, cache v4→v5, upload-artifact v4→v6, upload-pages-artifact v3→v4)
- Dwa PR-y zależności: eslint-plugin-react-hooks 5.2.0→7.0.1, aktualizacje pakietów frontendu
Konfiguracja Dependabota jest elegancko prosta — 32 wiersze chroniące cały łańcuch dostaw:
Pełna konfiguracja Dependabota
version: 2
updates:
- package-ecosystem: "pip"
directory: "/backend"
schedule:
interval: "weekly"
day: "monday"
labels: ["dependencies", "python"]
commit-message:
prefix: "[DEPS]"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
labels: ["dependencies", "ci"]
commit-message:
prefix: "[CI]"
Dlaczego to jest ważne¶
Dlaczego aktualizujemy wersje GitHub Actions (checkout v4→v6), skoro stare wersje nadal działają? Ponieważ GitHub Actions działają z podwyższonymi uprawnieniami — mają dostęp do sekretów repozytorium, mogą wypychać kod i wdrażać na produkcję. Luka w wersji akcji mogłaby skompromitować cały potok CI. Pozostawanie na najnowszej wersji zapewnia najnowsze łatki bezpieczeństwa. W świecie systemów elektroenergetycznych jest to odpowiednik aktualizacji firmware w przekaźnikach zabezpieczeniowych — przekaźnik nadal działa ze starym firmware, ale znane luki są naprawione w nowszych wersjach.
Dlaczego Dependabot używa różnych prefiksów commitów (
[CI]dla akcji,[DEPS]dla pakietów)? Identyfikowalność. Gdy przeglądasz log git, możesz natychmiast zobaczyć, które commity to aktualizacje infrastruktury, a które to aktualizacje zależności lub kod funkcji. Ta konwencja bezpośrednio odpowiada naszemu standardowi komunikatów commitów i ułatwia generowanie changelogów, audytowanie zmian zależności i zrozumienie historii na pierwszy rzut oka.
Przegląd kodu¶
Przyjrzyj się faktycznym commitom Dependabota, które zostały scalone:
f85f6c5 [CI]: Bump actions/upload-pages-artifact from 3 to 4 (#1)
2b5f3a0 [CI]: Bump actions/upload-artifact from 4 to 6 (#2)
c372942 [CI]: Bump actions/setup-node from 4 to 6 (#3)
59d9ebe [CI]: Bump actions/cache from 4 to 5 (#4)
0933057 [CI]: Bump actions/setup-python from 5 to 6 (#5)
376e572 [CI]: Bump actions/checkout from 4 to 6 (#6)
e9966d8 [DEPS]: Bump eslint-plugin-react-hooks from 5.2.0 to 7.0.1 (#7)
d32d82f [DEPS] Update frontend dependencies to latest compatible versions (#12)
Każdy PR został automatycznie utworzony przez Dependabota, przejrzany i scalony. #1 do #7 to numery pull requestów — Dependabot je otworzył, CI zostało uruchomione i zostały scalone po przejściu testów. Jest to w pełni zautomatyzowany workflow aktualizacji łańcucha dostaw: wykryj → zaproponuj → przetestuj → scal.
Kluczowa koncepcja: Bezpieczeństwo łańcucha dostaw oprogramowania
Po ludzku: Twój projekt zależy od kodu napisanego przez innych ludzi (biblioteki, frameworki, narzędzia). Jeśli któryś z tych kodów ma błąd bezpieczeństwa, Twój projekt dziedziczy ten błąd. Bezpieczeństwo łańcucha dostaw oznacza śledzenie wszystkich zależności i aktualizowanie ich, gdy dostępne są poprawki.
Analogia: Pomyśl o akcji serwisowej samochodu. Twój samochód jest sprawny, ale producent poduszek powietrznych odkrył wadę. Nie pisałeś kodu poduszki powietrznej, ale nadal musisz pojechać do dealera na naprawę. Dependabot jest jak automatyczny system powiadomień o akcjach serwisowych — informuje Cię o problemie i nawet zapisuje wizytę (otwiera PR) za Ciebie.
W tym projekcie: Zależymy od FastAPI, React, sterowników PostgreSQL i dziesiątek innych pakietów. Dependabot sprawdza co poniedziałek nowe wersje, otwiera PR-y z aktualizacjami, a nasz potok CI testuje je automatycznie. Sześć bumpów GitHub Actions w pierwszym tygodniu demonstruje to w działaniu: Dependabot wykrył, że dostępna jest v6 dla actions/checkout, otworzył PR #6, CI przeszło i scaliliśmy — wszystko bez ręcznej interwencji.
Połączenia¶
Gdzie te koncepcje pojawiają się w następnych modułach:
- Health checks (Sekcja 3) → Monitorowanie SCADA w P3 rozszerzy wzorce health checks Docker do pełnego monitorowania statusu turbin i podstacji
- Konfiguracja oparta na środowisku (Sekcja 2) → P1 będzie potrzebować poświadczeń ERA5 API wstrzykiwanych przez ten sam mechanizm Pydantic Settings
- Bramki jakości CI (Sekcja 4) → Testy modelu efektu cienia P1 i walidacje przepływu mocy P2 będą uruchamiane w dokładnie tych samych zadaniach CI
- Skanowanie bezpieczeństwa (Sekcja 5) → Klucze API ERA5 i poświadczenia bazy danych w P1+ będą chronione przez ten sam skaner sekretów
- Dependabot (Sekcja 6) → Gdy dodamy PyWake, Pandapower i biblioteki ML, Dependabot będzie monitorować ich aktualizacje bezpieczeństwa
Szerszy obraz¶
Fokus tej lekcji: warstwa DevOps owijająca stos aplikacji.
graph TB
subgraph APP["Application Stack"]
FE["React 19 + TypeScript<br/>Port 3000"]
BE["FastAPI + Python 3.13<br/>Port 8000 · /health ✓"]
PG["PostgreSQL 16 + TimescaleDB<br/>Port 5432"]
RD["Redis 7<br/>Port 6379"]
end
subgraph DEVOPS["DevOps Foundation (Built in This Lesson)"]
Docker["Docker Compose<br/><i>4-service orchestration</i>"]
CI["GitHub Actions CI<br/><i>4 parallel jobs</i>"]
PC["Pre-commit Hooks<br/><i>ruff, mypy, eslint</i>"]
Deps["Dependabot<br/><i>pip + npm + Actions</i>"]
Sec["Security Audit Skill<br/><i>13 secret patterns</i>"]
Make["Makefile<br/><i>15+ targets</i>"]
end
subgraph FUTURE["Future Modules (P1 → P5)"]
P15["P1: PyWake · P2: Pandapower · P3: SCADA · P4: ML · P5: Commissioning"]
end
FE -->|REST API| BE
BE --> PG
BE --> RD
Docker --> APP
CI --> PC
CI --> Deps
CI --> Sec
Make --> Docker
FUTURE -.->|"built ON TOP of<br/>this foundation"| DEVOPS
Najważniejsze wnioski¶
- Jedno źródło prawdy eliminuje niejednoznaczność — jeden roadmap, jedna specyfikacja, jedno miejsce do sprawdzenia liczb turbin i poziomów napięcia.
- Pierwsza linia kodu powinna być health checkiem — przed jakąkolwiek logiką domenową udowodnij, że serwis może się uruchomić i odpowiadać. Docker, Kubernetes i CI wszystkie od tego zależą.
- Konfiguracja należy do środowiska, nie do kodu — Pydantic Settings daje bezpieczną typowo, zwalidowaną konfigurację bez boilerplate.
- Health checks Docker Compose zapobiegają warunkom wyścigu —
depends_on: condition: service_healthyzapewnia, że usługi startują we właściwej kolejności, a nie tylko we właściwej sekwencji. - Defense in depth oznacza wiele niezależnych bramek jakości — hooki pre-commit wychwytują problemy lokalnie, CI wychwytuje je w czystym środowisku, a skanowanie bezpieczeństwa wychwytuje sekrety, zanim dotrą do publicznego repozytorium.
- Narzędzia bezpieczeństwa muszą być skalibrowane, a nie tylko włączone — zbyt restrykcyjne blokują legalny kod (fałszywe alarmy), zbyt łagodne przegapiają prawdziwe problemy. Wzorzec „dozwolonych wyjątków" dokumentuje, dlaczego pewne wzorce są akceptowalne.
- Automatyczne zarządzanie zależnościami to bezpieczeństwo łańcucha dostaw — Dependabot monitoruje, proponuje i testuje aktualizacje, żebyś nie musiał pamiętać o ręcznym sprawdzaniu co tydzień.
Quiz — sprawdź swoją wiedzę¶
Pytania pamięciowe¶
P1: Jakie cztery usługi definiuje nasz stos Docker Compose i jakich portów używają?
Odpowiedź
Stos definiuje cztery usługi: PostgreSQL z TimescaleDB na porcie 5432, Redis 7 na porcie 6379, backend FastAPI na porcie 8000 i frontend React na porcie 3000. PostgreSQL używa obrazu `timescale/timescaledb:latest-pg16`, a Redis używa `redis:7-alpine` dla minimalnego rozmiaru.P2: Jakie kategorie reguł lintowania ruff są włączone w naszym pyproject.toml i co każda kategoria sprawdza?
Odpowiedź
Włączonych jest dziewięć kategorii: E (błędy pycodestyle), W (ostrzeżenia pycodestyle), F (pyflakes — nieużywane importy, niezdefiniowane nazwy), I (isort — kolejność importów), N (pep8-naming — konwencje nazewnictwa funkcji/klas), UP (pyupgrade — modernizacja składni Pythona), B (flake8-bugbear — typowe błędy i problemy projektowe), SIM (flake8-simplify — niepotrzebnie złożony kod) i RUF (reguły specyficzne dla Ruff). Razem wychwytują problemy stylu, potencjalne błędy i możliwości użycia nowoczesnych funkcji Pythona.P3: Jaka jest różnica między BLOCK a WARN w audycie bezpieczeństwa github-push?
Odpowiedź
Pozycje BLOCK są blokadami uniemożliwiającymi push w całości — wskazują na nieodwracalne zagrożenia bezpieczeństwa, takie jak wyciekłe klucze API lub klucze prywatne, które raz wypchnięte do publicznego repozytorium są trwale skompromitowane. Pozycje WARN to potencjalne problemy wymagające potwierdzenia użytkownika, które mogą być akceptowalne — jak domyślne hasła developerskie w docker-compose.yml nadpisywane przez zmienne środowiskowe w produkcji. Pozycje WARN są zawsze raportowane użytkownikowi; nigdy nie są cicho pomijane.Pytania rozumiejące¶
P4: Dlaczego nasz backend używa condition: service_healthy w depends_on zamiast zwykłego depends_on: [postgres]? Jakiemu trybowi awarii to zapobiega?
Odpowiedź
Samo wylistowanie `depends_on: [postgres]` zapewnia tylko, że kontener PostgreSQL *wystartował*, a nie że *jest gotowy do przyjmowania połączeń*. PostgreSQL potrzebuje kilku sekund na zainicjowanie katalogu danych, wykonanie odtwarzania WAL i otwarcie nasłuchiwacza TCP. Bez warunku health check backend próbowałby połączyć się w tym oknie inicjalizacji, otrzymałby błąd „connection refused" i potencjalnie ulegał awarii, zanim PostgreSQL byłby gotowy. Warunek `service_healthy` używa `pg_isready`, żeby sprawdzić, że PostgreSQL faktycznie akceptuje zapytania przed uruchomieniem backendu, eliminując ten warunek wyścigu.P5: Dlaczego używamy zarówno hooków pre-commit, jak i sprawdzeń potoku CI? Czy jedno nie wystarczyłoby?
Odpowiedź
Pełnią komplementarne role w strategii defense-in-depth. Hooki pre-commit uruchamiają się natychmiast na maszynie programisty, dając natychmiastową informację zwrotną przed utworzeniem commita — wychwytują problemy z formatowaniem i błędy typów w sekundy. Jednak hooki można ominąć (`--no-verify`) i działają w lokalnym środowisku programisty, które może różnić się od środowiska CI. CI działa w czystym, odtwarzalnym środowisku (Ubuntu z konkretnymi wersjami Pythona/Node) i nie można go ominąć — jest autorytatywną bramką jakości. Razem zapewniają szybką lokalną informację zwrotną ORAZ niezawodną zdalną weryfikację, tak jak turbina wiatrowa ma zarówno hamulec mechaniczny, jak i aerodynamiczny dla redundancji.P6: Dlaczego wybraliśmy Pydantic Settings do konfiguracji zamiast bezpośrednio używać os.getenv() lub pliku konfiguracyjnego YAML?
Odpowiedź
Pydantic Settings zapewnia trzy kluczowe zalety: bezpieczeństwo typów (automatycznie konwertuje `"5432"` na liczbę całkowitą, `'["http://localhost"]'` na listę), walidację (zawodzi głośno przy starcie, jeśli brakuje wymaganej zmiennej lub jest ona nieprawidłowo sformatowana, zamiast awaryjnie kończyć działanie w czasie wykonania) i dokumentację (sama klasa Settings służy jako schemat pokazujący wszystkie dostępne opcje konfiguracyjne z ich typami i wartościami domyślnymi). `os.getenv()` zwraca `Optional[str]` dla wszystkiego, wymagając ręcznej konwersji typów i sprawdzeń null rozproszonych po całym kodzie. Pliki konfiguracyjne YAML są trudniejsze do nadpisania per środowisko i ryzykują commitowanie z sekretami. Pydantic Settings podąża za zasadą 12-Factor App: konfiguracja w środowisku, walidowana na granicy.Pytanie wyzwanie¶
P7: Nasz bieżący Docker Compose używa POSTGRES_PASSWORD: postgres jako zakodowanej na stałe domyślnej wartości developerskiej. Zaprojektuj strategię konfiguracyjną, która działa dla trzech środowisk: lokalny development (prosty, bez dodatkowej konfiguracji), CI/CD (zautomatyzowany, bez interakcji człowieka) i produkcja (bezpieczna, audytowalna). Jak każde środowisko ustawiałoby hasło do bazy danych i jakich narzędzi lub mechanizmów byś użył?
Odpowiedź
**Lokalny development:** Zachowaj zakodowaną na stałe wartość domyślną `postgres:postgres` w docker-compose.yml. To udogodnienie dla programistów — mogą uruchomić `docker compose up` bez żadnej dodatkowej konfiguracji. Audyt bezpieczeństwa jawnie zezwala na to jako wyjątek WARN, bo jest nadpisywany w innych środowiskach. **CI/CD:** Użyj sekretów GitHub Actions (`${{ secrets.DB_PASSWORD }}`) wstrzykiwanych jako zmienne środowiskowe do workflow CI. Hasło jest przechowywane zaszyfrowane w ustawieniach GitHub, nigdy nie pojawia się w logach (GitHub automatycznie je maskuje) i jest dostępne tylko dla workflow działających w repozytorium. `docker-compose.yml` używałby interpolacji zmiennych: `POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}` — domyślnie `postgres`, jeśli zmienna nie jest ustawiona (lokalny development), ale używając sekretu, gdy jest (CI). **Produkcja:** Użyj menedżera sekretów (AWS Secrets Manager, HashiCorp Vault lub Azure Key Vault), który wstrzykuje poświadczenia przy starcie kontenera. Wdrożenie Kubernetes montowałoby sekret jako zmienną środowiskową przez SecretProviderClass (sterownik CSI) lub Kubernetes Secret. Hasło byłoby automatycznie rotowane przez menedżera sekretów zgodnie z harmonogramem, a cały dostęp byłby logowany dla audytowalności. Aplikacja odczytuje `DATABASE_URL` ze środowiska (Pydantic Settings obsługuje to), więc żadne zmiany kodu nie są potrzebne — różnią się tylko konfiguracje wdrożenia między środowiskami. To wielowarstwowe podejście podąża za zasadą minimalnych uprawnień: programiści dostają wygodę, CI dostaje automatyzację, a produkcja dostaje bezpieczeństwo — wszystkie używając tego samego kodu aplikacji z różnymi mechanizmami konfiguracji.Rozmowa techniczna¶
Wyjaśnij prosto¶
„Jak wyjaśniłbyś infrastrukturę DevOps dla symulacji farmy wiatrowej osobie niebędącej inżynierem?"
Budujemy symulację komputerową dużej farmy wiatrowej na Bałtyku — 34 wielkie turbiny wytwarzające wystarczająco dużo energii elektrycznej dla małego miasta. Zanim napiszemy ciekawsze rzeczy (jak wiatr przepływa między turbinami, jak elektryczność podróżuje przez kable, jak prognozować jutrzejszą produkcję energii), musimy zorganizować nasz warsztat.
Pomyśl o tym jak o budowie domu. Zanim zainstalujesz urządzenia kuchenne czy powiesisz zasłony, potrzebujesz fundamentów, szkieletu, hydrauliki i instalacji elektrycznej. To właśnie zrobiliśmy w tej fazie. Skonfigurowaliśmy cztery „pokoje" w naszym cyfrowym warsztacie: bazę danych do przechowywania danych (jak szafka na dokumenty), pamięć podręczną do szybkiego dostępu do częstych danych (jak tablica), serwer backendowy wykonujący obliczenia (jak biurko inżyniera) i frontend wyświetlający wyniki (jak ekrany w sterowni). Skonfigurowaliśmy też automatycznego inspektora, który sprawdza naszą pracę za każdym razem, gdy wprowadzamy zmianę — upewniając się, że nie wprowadziliśmy błędów ani przypadkowo nie opublikowaliśmy haseł. Wreszcie dodaliśmy automatyczny system sprawdzający, czy któreś z narzędzi, od których zależymy, zostały zaktualizowane — jak otrzymywanie powiadomień o akcjach serwisowych samochodów.
Wyjaśnij technicznie¶
„Jak wyjaśniłbyś fundament DevOps Fazy 0 komisji rekrutacyjnej?"
Ustanowiliśmy infrastrukturę monorepo klasy produkcyjnej: backend FastAPI (Python 3.13, Pydantic v2, SQLAlchemy async), frontend React 19 (TypeScript strict, Tailwind v4) i usługi orkiestrowane przez Docker Compose (PostgreSQL 16 + TimescaleDB, Redis 7).
Potok CI/CD implementuje defense in depth: hooki pre-commit (Ruff, Mypy, ESLint) dla lokalnej informacji zwrotnej, GitHub Actions (cztery równoległe zadania z buforowaniem zależności) dla weryfikacji w czystym środowisku i niestandardowa umiejętność audytu bezpieczeństwa z klasyfikacją BLOCK/WARN do skanowania sekretów. Dependabot automatyzuje zarządzanie łańcuchem dostaw w ekosystemach pip, npm i GitHub Actions co tydzień.
Cała konfiguracja podąża za zasadami 12-Factor App przez Pydantic Settings — identyczny kod aplikacji we wszystkich środowiskach z jedynie różnicami konfiguracyjnymi. Ten fundament bezpośrednio wspiera P1-P5: TimescaleDB dla danych pogodowych ERA5, Redis do buforowania symulacji i CI do automatycznej walidacji modeli efektu cienia i obliczeń przepływu mocy względem norm IEC.