Cloud i DevOps
Fundamenty chmury i DevOps: Docker, CI/CD, Kubernetes, Infrastructure as Code, twelve-factor app, wdrożenia blue-green/canary i trzy filary observability.
Cloud i DevOps
Fundamenty chmury i DevOps: Docker, CI/CD, Kubernetes, Infrastructure as Code, twelve-factor app, wdrożenia blue-green/canary i trzy filary observability.
Docker: obrazy, warstwy i multi-stage buildy
na egzaminieKontener nie jest lekką maszyną wirtualną: dzieli jądro systemu hosta i izoluje procesy przez namespaces oraz cgroups. Obraz Docker to niezmienny szablon zbudowany z warstw; kontener to jego uruchomiona instancja z własną, zapisywalną warstwą na wierzchu.
Obraz (image): Niezmienny, wielowarstwowy szablon plikowy plus metadane (ENTRYPOINT, ENV, EXPOSE); budowany raz, uruchamiany wielokrotnie jako kontener.
Warstwa (layer): Każda instrukcja Dockerfile (FROM, RUN, COPY...) tworzy osobną, cache'owalną warstwę tylko-do-odczytu; obraz to stos tych warstw połączony przez union filesystem (np. overlay2).
Docker cache'uje warstwy między buildami: jeśli instrukcja i jej wejścia się nie zmieniły, warstwa jest reużywana. Zmiana warstwy unieważnia cache tej i wszystkich kolejnych warstw, dlatego kolejność instrukcji w Dockerfile bezpośrednio wpływa na czas builda.
FROM node:22
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
CMD ["node", "dist/server.js"] COPY . . przed RUN npm install unieważnia cache zależności przy KAŻDEJ zmianie w kodzie źródłowym, bo cache warstwy COPY zależy od zawartości katalogu. Najpierw kopiuj pliki manifestu zależności (package.json), instaluj, dopiero potem kopiuj resztę kodu.
# --- Etap budowania ---
FROM node:22 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# --- Etap uruchomieniowy ---
FROM node:22-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"] Mniej zainstalowanych pakietów systemowych to mniejsza powierzchnia ataku: mniej CVE do skanowania i łatania. Mniejszy rozmiar to szybszy pull przy skalowaniu/deployu i szybszy cold start w środowiskach serverless czy Kubernetes.
| Obraz bazowy | Rozmiar względem alternatyw | Kompromis |
|---|---|---|
| ubuntu:24.04 / debian:bookworm | Duży, pełny system pakietów | Wygodny do debugowania (shell, apt), ale duża powierzchnia ataku |
| node:22-slim / python:3.13-slim | Średni | Mniej pakietów niż pełny obraz, wciąż ma shell i podstawowe narzędzia |
| gcr.io/distroless/* | Mały, brak shella i menedżera pakietów | Najmniejsza powierzchnia ataku, trudniejszy debugging (brak sh do exec) |
Warstwy Dockera jak commity w Gicie: każda kolejna to diff na poprzedniej. Docker (jak Git) potrafi reużyć niezmieniony fragment historii zamiast przeliczać wszystko od nowa.
- Sortuj instrukcje od najrzadziej do najczęściej zmienianych (zależności przed kodem źródłowym)
- Używaj .dockerignore, by nie kopiować node_modules/.git do kontekstu builda
- Pinuj wersję obrazu bazowego (node:22.14-slim, nie node:latest) dla powtarzalnych buildów
- Uruchamiaj proces jako użytkownik nie-root (USER) w finalnym etapie
- Łącz RUN apt-get update && apt-get install w jedną instrukcję, by nie zostawiać cache'u pakietów w osobnej warstwie
Build jednoetapowy
- Finalny obraz zawiera kompilator/dev-dependencies, których produkcja nie potrzebuje
- Większa powierzchnia ataku (więcej pakietów = więcej CVE)
- Wolniejszy pull i dłuższy cold start przez zbędny balast
Multi-stage build
- Etap builder zawiera narzędzia, ale nigdy nie trafia do finalnego obrazu
- Do warstwy runtime kopiowane są tylko gotowe artefakty (np. /dist)
- Mniejszy, szczuplejszy obraz gotowy na produkcję
Pipeline CI/CD: etapy i przyczyny porażek
na egzaminiePipeline CI/CD to automatyzacja ścieżki od commitu do działającego kodu na środowisku. Celem jest wykryć problem jak najwcześniej (fail-fast) i sprawić, że wdrożenie jest nudną, powtarzalną czynnością, nie wydarzeniem wysokiego ryzyka.
Continuous Integration (CI): Częste, wielokrotnie dzienne scalanie zmian do wspólnej gałęzi, każdorazowo weryfikowane automatycznym buildem i testami, by wykryć konflikty i regresje szybko.
Continuous Delivery vs Continuous Deployment: Delivery: każdy zielony build jest gotowym do wdrożenia artefaktem, ale wymaga ręcznej decyzji o wypuszczeniu. Deployment: idzie krok dalej i wdraża automatycznie na produkcję bez interwencji człowieka.
- Build: kompilacja/pakowanie kodu do artefaktu (obraz Docker, binarka, paczka)
- Test: testy jednostkowe, integracyjne, czasem bezpieczeństwa (SAST, skan zależności)
- Deploy: wypchnięcie artefaktu na środowisko (staging, potem produkcja), zwykle z bramką jakości między etapami
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
build-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build
- run: npm test
deploy:
needs: build-test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "deploy do produkcji po zielonym build-test" Najczęstsze realne przyczyny:
- testy zależne od współdzielonego, niezaizolowanego stanu (flaky)
- niepinowane wersje zależności (build dziś różni się od builda jutro)
- brak parytetu między środowiskiem CI a produkcją
- sekrety/uprawnienia skonfigurowane inaczej niż lokalnie
'Działa u mnie na laptopie' zwykle oznacza brak reprodukowalności builda. Typowe przyczyny: niepinowane wersje w package.json, różne wersje runtime albo poleganie na stanie lokalnego cache, którego CI nie ma.
| Etap | Pyta o | Typowe narzędzie |
|---|---|---|
| Build | Czy kod się kompiluje/pakuje? | docker build, npm run build |
| Test | Czy zachowanie jest poprawne? | npm test, pytest, testy kontraktowe |
| Deploy | Czy artefakt działa na docelowym środowisku? | kubectl apply, terraform apply, helm upgrade |
Bramka jakości pipeline'u działa jak sito. Im wcześniej (build), tym grubsze oczka i tańszy koszt błędu. Im później (produkcja), tym drobniejsze oczka i drożej kosztuje przepuszczenie defektu.
- Trigger Push lub pull request uruchamia pipeline automatycznie
- Build Kod kompilowany/pakowany do artefaktu (np. obraz Docker z tagiem commit SHA)
- Test Automatyczne testy blokują dalsze etapy przy pierwszym czerwonym wyniku
- Deploy do staging Artefakt trafia na środowisko zbliżone do produkcji
- Bramka (opcjonalna) Ręczna akceptacja lub automatyczne kryteria przed produkcją
- Deploy do produkcji Ten sam, niezmieniony artefakt z wcześniejszych etapów trafia na produkcję
Kluczowa zasada: artefakt zbudowany raz na początku pipeline'u wędruje przez wszystkie środowiska bez ponownego budowania. Inaczej testujesz jedną rzecz, a wdrażasz inną.
Fundamentalne obiekty Kubernetes
na egzaminieKubernetes orkiestruje kontenery przez deklaratywne obiekty API: deklarujesz pożądany stan, a control plane (scheduler, kontrolery) nieustannie dąży do zgodności rzeczywistego stanu z zadeklarowanym (reconciliation loop).
Pod: Najmniejsza jednostka wdrożeniowa w Kubernetes: jeden lub więcej kontenerów współdzielących namespace sieciowy (jeden IP) i opcjonalnie wolumeny, zawsze planowanych razem na tym samym węźle.
Deployment: Obiekt wyższego poziomu zarządzający ReplicaSetami: deklaruje pożądaną liczbę replik Poda, wykonuje rolling update przy zmianie obrazu/konfiguracji i pozwala na rollback do poprzedniej wersji.
Service: Stabilny punkt dostępu (wirtualny IP + DNS) do dynamicznego zestawu Podów wybieranych przez selektor etykiet. Rozwiązuje problem efemeryczności Podów, których adresy IP zmieniają się przy każdym restarcie.
ConfigMap / Secret: Obiekty przechowujące konfigurację poza obrazem kontenera. ConfigMap dla danych jawnych (np. LOG_LEVEL), Secret dla wrażliwych (hasła, tokeny), domyślnie tylko zakodowanych base64, nie zaszyfrowanych.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
labels:
app: api-server
spec:
replicas: 3
selector:
matchLabels:
app: api-server
template:
metadata:
labels:
app: api-server
spec:
containers:
- name: api-server
image: registry.example.com/api-server:1.4.2
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: api-server-config
- secretRef:
name: api-server-secrets
---
apiVersion: v1
kind: Service
metadata:
name: api-server
spec:
type: ClusterIP
selector:
app: api-server
ports:
- port: 80
targetPort: 8080 apiVersion: v1
kind: ConfigMap
metadata:
name: api-server-config
data:
LOG_LEVEL: "info"
FEATURE_FLAG_NEW_CHECKOUT: "true"
---
apiVersion: v1
kind: Secret
metadata:
name: api-server-secrets
type: Opaque
stringData:
DATABASE_URL: postgres://app:changeme@db.internal:5432/app Pod bez nadzorującego kontrolera (Deployment/ReplicaSet/StatefulSet) nie ma self-healing: jeśli węzeł padnie albo proces się wysypie, nikt automatycznie go nie odtworzy. Deployment dodaje deklaratywne rolling update, rollback i utrzymanie zadanej liczby replik.
Secret w Kubernetes to nie 'szyfrowany sejf' z automatu. Dane są tylko zakodowane base64 (trywialnie odwracalne), a w etcd leżą w postaci czytelnej, dopóki administrator klastra nie włączy encryption-at-rest.
Traktuj RBAC do Secretów równie poważnie jak dostęp do produkcyjnej bazy danych.
| Obiekt | Odpowiada za | Analogia |
|---|---|---|
| Pod | Uruchomienie kontenera(-ów) jako jednej jednostki | Proces systemowy |
| Deployment | Utrzymanie N replik Poda + rolling update/rollback | Systemd unit z auto-restartem i wersjonowaniem |
| Service | Stabilny adres do zmiennego zestawu Podów | Load balancer + wewnętrzny DNS |
| ConfigMap/Secret | Konfiguracja i dane wrażliwe poza obrazem | Plik .env zamontowany z zewnątrz |
Pod to 'co działa', Deployment to 'ile i jak to utrzymać', Service to 'jak się do tego dostać'. To trzy różne, ortogonalne pytania i trzy różne obiekty API.
Infrastructure as Code: deklaratywne provisioning
na egzaminieInfrastructure as Code zamienia klikanie w konsoli chmurowej na kod. Infrastrukturę opisuje się w plikach, wersjonuje w Git i stosuje przez narzędzie, które oblicza różnicę między stanem zadeklarowanym a rzeczywistym.
IaC (Infrastructure as Code): Zarządzanie infrastrukturą (serwery, sieci, DNS, uprawnienia) przez pliki konfiguracyjne przetwarzane maszynowo, a nie przez ręczne operacje w konsoli/UI.
Deklaratywne vs imperatywne provisioning: Deklaratywne (Terraform): opisujesz pożądany stan końcowy, narzędzie samo ustala różnicę i kroki dojścia. Imperatywne (skrypt bash z aws-cli): opisujesz dokładną sekwencję komend do wykonania.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
region = "eu-central-1"
}
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t3.micro"
tags = {
Name = "web-server"
}
} - terraform init Pobiera providery (np. hashicorp/aws) i inicjalizuje backend stanu
- terraform plan Pokazuje różnicę między kodem a bieżącym stanem (co zostanie dodane/zmienione/usunięte)
- terraform apply Wykonuje zmiany z planu i zapisuje nowy stan
- terraform destroy Usuwa zasoby zarządzane przez ten kod, zgodnie z zapisanym stanem
Terraform trzyma plik stanu mapujący zasoby w kodzie na rzeczywiste zasoby w chmurze. To źródło prawdy do obliczania różnicy przy kolejnym plan/apply, dlatego stan współdzielony przez zespół trzyma się zdalnie (np. S3 + blokada), nie lokalnie na laptopie.
Ręczna zmiana zasobu w konsoli chmurowej po tym, jak zaczął nim zarządzać Terraform, powoduje drift: stan w pliku przestaje odpowiadać rzeczywistości. Kolejny apply może nieoczekiwanie cofnąć ręczną zmianę albo zgłosić konflikt.
- Każda zmiana infrastruktury przechodzi code review jak zmiana w aplikacji (pull request z terraform plan w opisie)
- Infrastruktura jest odtwarzalna 1:1 w innym regionie/koncie (disaster recovery, nowe środowisko)
- Historia zmian jest w Git, nie w pamięci osoby, która kiedyś kliknęła w konsoli
- Ten sam kod (moduł) da się sparametryzować i użyć dla dev/staging/prod
| Podejście | Przykład | Ryzyko |
|---|---|---|
| Ręczne klikanie w konsoli | AWS/GCP/Azure Console | Brak historii zmian, nieodtwarzalne 1:1, wiedza 'w głowie' jednej osoby |
| Imperatywne skrypty | aws-cli/gcloud w bash | Działa, ale trzeba samemu obsłużyć idempotencję i błędy częściowego wykonania |
| Deklaratywne IaC | Terraform, Pulumi | Idempotentne z założenia, ale wymaga zarządzania plikiem stanu |
Terraform jak Git dla infrastruktury: kod to źródło prawdy, plan to diff, apply to commit do rzeczywistości.
Dwanaście czynników aplikacji (Twelve-Factor App)
na egzaminieTwelve-Factor App (Heroku, 2011) to zestaw dwunastu praktyk budowania aplikacji SaaS łatwych do skalowania, przenoszenia między środowiskami i utrzymania bez ręcznej ingerencji. Mimo wieku, każdy punkt wprost mapuje się na dzisiejsze kontenery i Kubernetes.
- I. Codebase - jedna baza kodu w kontroli wersji, wiele wdrożeń z niej wynikających
- II. Dependencies - jawnie zadeklarowane zależności (package.json), nigdy 'zainstalowane ręcznie na serwerze'
- III. Config - konfiguracja różniąca się między środowiskami trzymana w zmiennych środowiskowych, nie w kodzie
- IV. Backing services - baza danych, kolejka, cache traktowane jako podłączane zasoby wymienne bez zmiany kodu
- V. Build, release, run - ścisłe rozdzielenie etapu budowy, złożenia z configiem i uruchomienia
- VI. Processes - procesy aplikacji bezstanowe, trwały stan żyje w backing service
- VII. Port binding - aplikacja sama nasłuchuje na porcie, nie wymaga wstrzykniętego web serwera
- VIII. Concurrency - skalowanie przez uruchamianie kolejnych instancji procesu (horyzontalnie), nie przez grubszy proces
- IX. Disposability - szybki start i graceful shutdown, procesy jednorazowe do zabicia/odtworzenia w każdej chwili
- X. Dev/prod parity - środowiska deweloperskie, staging i produkcja maksymalnie zbliżone
- XI. Logs - logi jako strumień zdarzeń na stdout, nie zarządzane przez aplikację pliki
- XII. Admin processes - zadania administracyjne (migracje, konsola) uruchamiane jako jednorazowe procesy w tym samym środowisku co aplikacja
Config (czynnik III): Wszystko, co różni się między środowiskami (URL bazy danych, klucze API, feature flagi), musi być w zmiennych środowiskowych, nigdy zahardkodowane. Dzięki temu ten sam zbudowany artefakt działa identycznie na dev/staging/prod.
// Factor III: Config - wartości środowiskowe, nie zahardkodowane w kodzie
const config = {
port: process.env.PORT ?? 3000,
databaseUrl: process.env.DATABASE_URL,
logLevel: process.env.LOG_LEVEL ?? 'info',
};
if (!config.databaseUrl) {
throw new Error('DATABASE_URL is required');
} Zahardkodowany connection string do bazy w kodzie albo w commitowanym pliku config.json łamie czynnik III i zwykle kończy się wyciekiem sekretu do historii Gita. Sekrety trzymaj w zmiennych środowiskowych wstrzykiwanych przez platformę (Vercel env, Kubernetes Secret), nie w repo.
Baza danych, broker kolejki i cache Redis to zasoby dołączane przez URL/connection string. Można je wymienić bez zmiany kodu aplikacji. Na przykład przełączenie z lokalnego Postgresa na zarządzaną instancję wymaga tylko zmiany DATABASE_URL.
Bezstanowy proces, bez trwałego stanu w pamięci czy na dysku lokalnym, można swobodnie skalować horyzontalnie, restartować i zabijać w dowolnym momencie (disposability). Można tak robić, bo utrata jednej instancji nie traci danych: stan żyje w backing service.
Aplikacja pisze zdarzenia na stdout/stderr i nie decyduje, gdzie trafiają docelowo. Agregacja, rotacja i przechowywanie logów to odpowiedzialność platformy (np. kolektor w Kubernetes), nie kodu aplikacji.
Test czynnika III: gdybyś skopiował zbudowany artefakt 1:1 na inne środowisko i podmienił tylko zmienne środowiskowe, czy zadziała bez przebudowy? Jeśli nie, konfiguracja przecieka do kodu.
| Czynnik | Skrót myślowy |
|---|---|
| I. Codebase | Jedno repo, wiele deployów |
| III. Config | Env vars, nie kod |
| VI. Processes | Bez stanu w pamięci |
| IX. Disposability | Można zabić w każdej chwili |
| XI. Logs | stdout, nie własny plik |
Strategie wdrożeń: blue-green i canary
Blue-green i canary to dwie odpowiedzi na to samo pytanie: jak wdrożyć nową wersję bez przestoju i bez ryzykowania całego ruchu produkcyjnego na raz.
Blue-green deployment: Dwa pełne, identyczne środowiska: blue (obecnie obsługuje ruch) i green (nowa wersja). Po weryfikacji green ruch przełącza się niemal natychmiastowo (np. zmiana selektora load balancera/Service), a blue zostaje jako natychmiastowa ścieżka rollbacku.
Canary deployment: Nowa wersja trafia najpierw do małego podzbioru ruchu (np. 5-10%). Jeśli metryki (błędy, latencja) pozostają zdrowe, udział ruchu stopniowo rośnie aż do 100%. Minimalizuje to blast radius ewentualnego błędu.
apiVersion: v1
kind: Service
metadata:
name: api-server
spec:
selector:
app: api-server
version: blue
ports:
- port: 80
targetPort: 8080 kubectl patch service api-server -p '{"spec":{"selector":{"version":"green"}}}' Rollback to dokładnie ta sama operacja co przełączenie w drugą stronę: zmiana selektora z powrotem na blue. Ponieważ stare środowisko wciąż działa (nie zostało jeszcze usunięte), czas przywrócenia liczy się w sekundach.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server-stable
spec:
replicas: 9
selector:
matchLabels:
app: api-server
track: stable
template:
metadata:
labels:
app: api-server
track: stable
spec:
containers:
- name: api-server
image: registry.example.com/api-server:1.4.2
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server-canary
spec:
replicas: 1
selector:
matchLabels:
app: api-server
track: canary
template:
metadata:
labels:
app: api-server
track: canary
spec:
containers:
- name: api-server
image: registry.example.com/api-server:1.5.0-rc1 Bez service mesh (Istio/Linkerd) czy Argo Rollouts najprostszy canary w czystym Kubernetes to dwa Deploymenty. Każdy ma własny unikalny selektor (track: stable / track: canary), ale oba pasują do wspólnego selektora Service (app: api-server).
Układ 9 replik stabilnej wersji i 1 repliki canary daje w przybliżeniu 10% ruchu na nową wersję. Service load-balansuje bowiem między wszystkimi pasującymi Podami, niezależnie od tego, który Deployment je stworzył.
Blue-green przy współdzielonej bazie danych wymaga wstecznie kompatybilnej migracji schematu. Stara wersja (blue) i nowa (green) muszą poprawnie działać na tym samym schemacie w trakcie przełączania. Inaczej przełączenie ruchu psuje jedną z wersji.
| Blue-green | Canary | |
|---|---|---|
| Ryzyko na start | 0% ruchu na nowej wersji przed przełączeniem | Mały procent ruchu od razu na nowej wersji |
| Szybkość rollbacku | Natychmiastowa (przełącz selektor z powrotem) | Szybka, ale wymaga też cofnięcia proporcji ruchu |
| Koszt infrastruktury | 2x zasobów przez czas przejścia | Nieznacznie większy (dodatkowe repliki canary) |
| Wymagania | Load balancer/Service z łatwo przełączalnym targetem | Możliwość precyzyjnego podziału ruchu (mesh lub proporcja replik) |
- Health checki (readiness/liveness) - bez nich orkiestrator nie wie, czy nowa wersja jest gotowa na ruch
- Metryki błędów i latencji dostępne w czasie rzeczywistym jako podstawa decyzji o zwiększeniu ruchu canary lub rollbacku
- Automatyczny trigger rollbacku po przekroczeniu progu błędów, nie tylko ręczna obserwacja
- Kompatybilność wsteczna schematu bazy danych i kontraktów API między starą a nową wersją
Blue-green: przełącznik światła (całość albo nic, ale masz zapasową żarówkę). Canary: kanarek w kopalni (mały sygnał ostrzegawczy, zanim zaszkodzi wszystkim).
Observability: logi, metryki, trace'y
na egzaminieObservability to zdolność zadawania systemowi pytań, których nie przewidziałeś przy jego budowie, opierając się na trzech uzupełniających się źródłach sygnału: logach, metrykach i trace'ach.
Logi: Dyskretne, ustrukturyzowane zdarzenia z timestampem opisujące co się stało w konkretnym momencie; najbogatsze w kontekst, ale najdroższe w przechowywaniu przy wysokim wolumenie.
Metryki: Zagregowane, numeryczne szeregi czasowe (liczniki, gauge'y, histogramy) o niskiej kardynalności; tanie w przechowywaniu, idealne do alertowania i dashboardów trendów.
Trace'y (distributed tracing): Ścieżka pojedynczego żądania przez wiele usług, złożona ze spanów połączonych wspólnym trace_id; kluczowe do debugowania mikroserwisów.
{"timestamp":"2026-07-05T10:32:14Z","level":"error","service":"checkout-api","trace_id":"4bf92f3577b34da6a3ce929d0e0e4736","message":"payment gateway timeout","order_id":"ord_88213"} # HELP http_requests_total Total number of HTTP requests
# TYPE http_requests_total counter
http_requests_total{method="GET",status="200"} 182933
http_requests_total{method="GET",status="500"} 12
# HELP http_request_duration_seconds Histogram of request duration
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="0.1"} 15234
http_request_duration_seconds_bucket{le="0.5"} 18211
http_request_duration_seconds_bucket{le="+Inf"} 18220 from opentelemetry import trace
tracer = trace.get_tracer("checkout-service")
def process_order(order_id: str) -> None:
with tracer.start_as_current_span("process_order") as span:
span.set_attribute("order.id", order_id)
charge_payment(order_id) | Filar | Kardynalność/koszt | Typowe pytanie |
|---|---|---|
| Logi | Wysoki koszt przy dużym wolumenie, bogaty kontekst | Co dokładnie się stało w tym jednym żądaniu o 10:32? |
| Metryki | Niski koszt, niska kardynalność | Czy p99 latencji rośnie w ostatniej godzinie? |
| Trace'y | Średni koszt, często samplowane | Która usługa w łańcuchu wywołań spowalnia to żądanie? |
Logowanie na poziomie DEBUG w produkcji dla całego ruchu generuje koszt (storage, indeksowanie) i szum nieproporcjonalny do wartości. W produkcji loguj na poziomie INFO/WARN/ERROR, a DEBUG włączaj punktowo, na żądanie.
W systemie rozproszonym pojedyncze żądanie użytkownika przechodzi przez wiele usług. Identyfikator trace_id propagowany w nagłówkach requestu pozwala połączyć logi i spany z każdej z tych usług w jedną spójną oś czasu.
Metryki mówią CZY jest problem (dashboard, alert). Trace mówi GDZIE w łańcuchu usług. Log mówi DLACZEGO dokładnie, ze szczegółami tego jednego żądania.
Świadomość kosztów w chmurze
Chmura rozlicza za zarezerwowaną pojemność, nie za realne wykorzystanie: uruchomiona instancja, przypięty wolumen czy działający load balancer kosztują identycznie, czy obsługują milion żądań, czy zero.
Instancja compute jest wynajęta 24/7 od momentu startu do zatrzymania/usunięcia, niezależnie od CPU utilization. To samo dotyczy load balancerów, adresów IP czy wolumenów dyskowych.
- Nieodpięte wolumeny dyskowe (EBS/Persistent Disk) po usunięciu instancji, która ich używała
- Środowiska dev/staging działające 24/7, mimo że są używane tylko w godzinach pracy
- Przewymiarowane instancje wybrane 'na zapas' zamiast na podstawie realnego profilu obciążenia
- Zapomniane zasoby testowe/PoC, których nikt nie posprzątał po zakończeniu eksperymentu
- Nieużywane elastic/static IP przypisane, ale niepodłączone do żadnego zasobu
aws ec2 describe-volumes \
--filters Name=status,Values=available \
--query 'Volumes[].{ID:VolumeId,Size:Size,AZ:AvailabilityZone}' \
--output table Środowisko staging/dev pozostawione włączone na weekend czy noc to czysty koszt bez żadnej wartości. Nikt go nie używa, a rachunek chmurowy nalicza się identycznie jak w godzinach pracy.
resource "aws_autoscaling_schedule" "scale_down_nightly" {
scheduled_action_name = "scale-down-nightly"
autoscaling_group_name = aws_autoscaling_group.app.name
recurrence = "0 20 * * MON-FRI"
min_size = 0
max_size = 0
desired_capacity = 0
} Autoscaling dopasowuje liczbę instancji do realnego obciążenia w czasie rzeczywistym (skaluje w górę pod ruchem, w dół gdy ruch spada). Right-sizing dobiera typ/rozmiar instancji na podstawie faktycznego zużycia CPU/RAM zamiast domyślnego 'dużego zapasu'.
Transfer danych między strefami dostępności (AZ) lub regionami zwykle kosztuje osobno i bywa pomijany przy szacowaniu budżetu. Architektura ignorująca lokalność danych potrafi wygenerować niepozorny, ale realny koszt sieciowy.
Zasób w chmurze to jak wynajęte mieszkanie: płacisz czynsz, dopóki nie wypowiesz umowy, niezależnie od tego, czy w nim mieszkasz.
| Model rozliczenia | Kiedy ma sens | Kompromis |
|---|---|---|
| On-demand | Nieprzewidywalne/krótkotrwałe obciążenie | Najwyższa cena jednostkowa, zero zobowiązań |
| Reserved/Savings Plan | Stabilne, przewidywalne obciążenie bazowe | Niższa cena za zobowiązanie 1-3 lata |
| Spot/Preemptible | Obciążenia odporne na przerwanie (batch, CI) | Znacznie taniej, ale instancja może zostać odebrana |
Sprawdź się
20 z 24 pytań, losowo, z wyjaśnieniem po każdym. Wynik zostaje na tym urządzeniu.
Rozpocznij quiz