Mistrzu strategii! Senator Cicero powiada: "Imperium nigdy nie śpi, a drogi muszą być zawsze otwarte." Tak jak rzymskie legiony zmieniały wartę bez pozostawiania murów bez obrony, tak nowoczesne aplikacje muszą być aktualizowane bez przerw w działaniu. Czas poznać zaawansowane strategie wdrażania!
Blue-Green Deployment to technika, w której utrzymujemy dwa identyczne środowiska produkcyjne (blue i green). W danym momencie tylko jedno obsługuje ruch, a drugie jest gotowe na nową wersję. Przełączenie następuje natychmiastowo - bez przestojów.
Wyobraź sobie dwie bramy do miasta Imperium. Jedna (Blue) jest aktualnie otwarta i obsługuje podróżnych. Druga (Green) jest przygotowywana z nową wersją fortyfikacji. Gdy Green jest gotowe, strażnicy przekierowują ruch - zero przestojów!
1// src/deployment/blue-green.config.ts
2interface DeploymentSlot {
3 name: 'blue' | 'green';
4 port: number;
5 version: string;
6 status: 'active' | 'standby' | 'deploying';
7 healthCheckUrl: string;
8 startedAt: Date;
9}
10
11interface BlueGreenConfig {
12 activeSlot: 'blue' | 'green';
13 slots: {
14 blue: DeploymentSlot;
15 green: DeploymentSlot;
16 };
17 loadBalancerUrl: string;
18 healthCheckInterval: number; // ms
19 healthCheckRetries: number;
20 rollbackTimeout: number; // ms
21}
22
23const deploymentConfig: BlueGreenConfig = {
24 activeSlot: 'blue',
25 slots: {
26 blue: {
27 name: 'blue',
28 port: 3001,
29 version: '2.3.0',
30 status: 'active',
31 healthCheckUrl: 'http://blue.imperium.internal:3001/health',
32 startedAt: new Date(),
33 },
34 green: {
35 name: 'green',
36 port: 3002,
37 version: '2.4.0',
38 status: 'standby',
39 healthCheckUrl: 'http://green.imperium.internal:3002/health',
40 startedAt: new Date(),
41 },
42 },
43 loadBalancerUrl: 'http://lb.imperium.internal',
44 healthCheckInterval: 5000,
45 healthCheckRetries: 3,
46 rollbackTimeout: 30000,
47};Rolling updates to inna strategia - stopniowe zastępowanie starych instancji nowymi, jedna po drugiej, jak zmiana warty centurionów. Dzięki temu zawsze część instancji obsługuje ruch.
1// src/deployment/rolling-update.service.ts
2import { Injectable, Logger } from '@nestjs/common';
3
4interface Instance {
5 id: string;
6 version: string;
7 status: 'running' | 'updating' | 'ready' | 'failed';
8 port: number;
9}
10
11@Injectable()
12export class RollingUpdateService {
13 private readonly logger = new Logger(RollingUpdateService.name);
14
15 async performRollingUpdate(
16 instances: Instance[],
17 newVersion: string,
18 maxUnavailable: number = 1,
19 ): Promise<void> {
20 this.logger.log(
21 `Rozpoczynam rolling update do wersji ${newVersion}`
22 );
23 this.logger.log(
24 `Instancje: ${instances.length}, max niedostepnych: ${maxUnavailable}`
25 );
26
27 // Aktualizuj instancje partiami
28 for (let i = 0; i < instances.length; i += maxUnavailable) {
29 const batch = instances.slice(i, i + maxUnavailable);
30
31 for (const instance of batch) {
32 instance.status = 'updating';
33 this.logger.log(`Aktualizuje instancje ${instance.id}...`);
34
35 // 1. Wylacz instancje z load balancera
36 await this.removeFromLoadBalancer(instance);
37
38 // 2. Poczekaj az biezace requesty sie zakoncza
39 await this.drainConnections(instance);
40
41 // 3. Zaktualizuj do nowej wersji
42 await this.deployNewVersion(instance, newVersion);
43
44 // 4. Sprawdz health check
45 const healthy = await this.waitForHealthy(instance);
46
47 if (healthy) {
48 instance.status = 'running';
49 instance.version = newVersion;
50 await this.addToLoadBalancer(instance);
51 this.logger.log(`Instancja ${instance.id} zaktualizowana`);
52 } else {
53 instance.status = 'failed';
54 this.logger.error(
55 `Instancja ${instance.id} - rollback!`
56 );
57 await this.rollback(instance);
58 }
59 }
60 }
61 }
62
63 private async removeFromLoadBalancer(instance: Instance) {
64 // Usun instancje z puli load balancera
65 }
66
67 private async drainConnections(instance: Instance) {
68 // Poczekaj az aktywne polaczenia sie zakoncza (graceful)
69 }
70
71 private async deployNewVersion(
72 instance: Instance, version: string
73 ) {
74 // Wdrozenie nowej wersji na instancji
75 }
76
77 private async waitForHealthy(instance: Instance): Promise<boolean> {
78 const maxRetries = 10;
79 for (let i = 0; i < maxRetries; i++) {
80 try {
81 // Sprawdz endpoint /health
82 const healthy = true; // await fetch health check
83 if (healthy) return true;
84 } catch {
85 await new Promise(r => setTimeout(r, 2000));
86 }
87 }
88 return false;
89 }
90
91 private async addToLoadBalancer(instance: Instance) {
92 // Dodaj instancje z powrotem do puli
93 }
94
95 private async rollback(instance: Instance) {
96 // Przywroc poprzednia wersje
97 }
98}Health checki to zwiadowcy Imperium - sprawdzają, czy nowa fortyfikacja jest gotowa do przyjęcia ruchu zanim otworzymy bramy.
1// src/health/deployment-health.controller.ts
2import { Controller, Get } from '@nestjs/common';
3import {
4 HealthCheck, HealthCheckService,
5 MongooseHealthIndicator, MemoryHealthIndicator,
6 DiskHealthIndicator,
7} from '@nestjs/terminus';
8
9@Controller('health')
10export class DeploymentHealthController {
11 constructor(
12 private health: HealthCheckService,
13 private mongoose: MongooseHealthIndicator,
14 private memory: MemoryHealthIndicator,
15 private disk: DiskHealthIndicator,
16 ) {}
17
18 // Liveness - czy aplikacja zyje?
19 @Get('live')
20 @HealthCheck()
21 checkLiveness() {
22 return this.health.check([
23 () => this.memory.checkHeap('memory_heap', 200 * 1024 * 1024),
24 ]);
25 }
26
27 // Readiness - czy gotowa na ruch?
28 @Get('ready')
29 @HealthCheck()
30 checkReadiness() {
31 return this.health.check([
32 () => this.mongoose.pingCheck('mongodb'),
33 () => this.memory.checkHeap('memory_heap', 200 * 1024 * 1024),
34 () => this.disk.checkStorage('disk', {
35 thresholdPercent: 0.9, path: '/',
36 }),
37 ]);
38 }
39
40 // Startup - czy aplikacja wystartowala poprawnie?
41 @Get('startup')
42 @HealthCheck()
43 checkStartup() {
44 return this.health.check([
45 () => this.mongoose.pingCheck('mongodb'),
46 ]);
47 }
48}Migracje bazy danych to najtrudniejszy element zero-downtime deployment. Stary i nowy kod muszą współistnieć z tą samą bazą danych.
1// src/database/migration-strategy.ts
2
3// ZASADA: Migracje musza byc KOMPATYBILNE WSTECZ
4// Stara wersja kodu musi dzialac z nowa baza danych
5
6// Przyklad: Zmiana nazwy kolumny
7// ZLE (powoduje downtime):
8// Krok 1: ALTER TABLE legions RENAME COLUMN name TO legion_name;
9// --> Stary kod szuka "name" i pada!
10
11// DOBRZE (zero-downtime, 3 kroki):
12interface MigrationStrategy {
13 // Wdrozenie 1: Dodaj nowa kolumne
14 step1_addColumn: string;
15 // Wdrozenie 2: Kod czyta z obu, pisze do obu
16 step2_dualWrite: string;
17 // Wdrozenie 3: Usun stara kolumne (po pelnej migracji)
18 step3_removeOldColumn: string;
19}
20
21const renameMigration: MigrationStrategy = {
22 step1_addColumn: `
23 // Migracja 1: Dodaj kolumne legion_name
24 db.collection('legions').updateMany(
25 {},
26 [{ $set: { legion_name: '$name' } }]
27 );
28 // Stary kod dalej czyta 'name' - dziala!
29 `,
30 step2_dualWrite: `
31 // Kod v2: Zapisuj do obu kolumn
32 async updateLegion(id: string, name: string) {
33 await this.model.updateOne(
34 { _id: id },
35 { $set: { name, legion_name: name } }
36 );
37 }
38 // Czytaj z nowej kolumny
39 async getLegion(id: string) {
40 const doc = await this.model.findById(id);
41 return doc.legion_name || doc.name; // Fallback
42 }
43 `,
44 step3_removeOldColumn: `
45 // Migracja 3: Usun stara kolumne (po pelnym wdrozeniu v2)
46 db.collection('legions').updateMany(
47 {},
48 { $unset: { name: '' } }
49 );
50 `,
51};Feature flags to jak tajne rozkazy cesarza - pozwalają włączać i wyłączać funkcjonalności bez wdrażania nowego kodu.
1// src/features/feature-flag.service.ts
2import { Injectable } from '@nestjs/common';
3
4interface FeatureFlag {
5 name: string;
6 enabled: boolean;
7 rolloutPercentage: number; // 0-100
8 allowedUsers: string[];
9}
10
11@Injectable()
12export class FeatureFlagService {
13 private flags: Map<string, FeatureFlag> = new Map();
14
15 isEnabled(flagName: string, userId?: string): boolean {
16 const flag = this.flags.get(flagName);
17 if (!flag) return false;
18
19 // Flaga calkowicie wylaczona
20 if (!flag.enabled) return false;
21
22 // Uzytkownik na liscie dozwolonych (np. testerzy)
23 if (userId && flag.allowedUsers.includes(userId)) {
24 return true;
25 }
26
27 // Stopniowe wdrazanie (canary release)
28 if (flag.rolloutPercentage < 100) {
29 const hash = this.hashUserId(userId || 'anonymous');
30 return (hash % 100) < flag.rolloutPercentage;
31 }
32
33 return true;
34 }
35
36 private hashUserId(userId: string): number {
37 let hash = 0;
38 for (let i = 0; i < userId.length; i++) {
39 hash = ((hash << 5) - hash) + userId.charCodeAt(i);
40 hash |= 0;
41 }
42 return Math.abs(hash);
43 }
44}
45
46// Uzycie w kontrolerze
47@Controller('legions')
48class LegionsController {
49 constructor(private features: FeatureFlagService) {}
50
51 @Get()
52 async getLegions(@Req() req) {
53 if (this.features.isEnabled('new-ranking-system', req.user?.id)) {
54 return this.getNewRanking();
55 }
56 return this.getOldRanking();
57 }
58}PM2 w trybie cluster to jak rozmieszczenie wielu garnizonów - wykorzystuje wszystkie rdzenie CPU i zapewnia automatyczny restart oraz zero-downtime reload.
1// ecosystem.config.js - konfiguracja PM2
2module.exports = {
3 apps: [{
4 name: 'imperium-api',
5 script: 'dist/main.js',
6 instances: 'max', // Uzyj wszystkich rdzeni CPU
7 exec_mode: 'cluster', // Tryb klastrowy
8 autorestart: true,
9 watch: false,
10 max_memory_restart: '500M',
11
12 // Zero-downtime reload
13 wait_ready: true, // Czekaj na process.send('ready')
14 listen_timeout: 10000, // Max czas na gotowość
15 kill_timeout: 5000, // Czas na graceful shutdown
16
17 env_production: {
18 NODE_ENV: 'production',
19 PORT: 3000,
20 },
21 }],
22};
23
24// W main.ts - sygnalizacja gotowosci
25async function bootstrap() {
26 const app = await NestFactory.create(AppModule);
27 await app.listen(3000);
28
29 // Powiadom PM2 ze aplikacja jest gotowa
30 if (process.send) {
31 process.send('ready');
32 }
33}Komendy PM2 dla zero-downtime:
1# Reload bez downtime (graceful)
2pm2 reload imperium-api
3
4# Status instancji
5pm2 status
6
7# Monitorowanie w czasie rzeczywistym
8pm2 monitBlue-Green Deployment, rolling updates i feature flags to potężne narzędzia w arsenale każdego architekta Imperium. Dzięki nim Twoje wdrożenia będą tak płynne jak zmiana warty w najlepiej zorganizowanym legionie!