Serverless Framework'ten AWS CDK'ya Geçiş: Bölüm 6 - Migration Stratejileri
Serverless Framework'ten AWS CDK'ya geçişi planlayın: blue-green deployment, rollback prosedürleri, altyapı testleri ve performans ayarı.
Bir CDK migrasyonunun son aşamasında mesele, üretilen altyapının doğruluğu değildir. Mesele, geçiş anındaki operasyonel hazırlıktır: trafiği almak için bir plan, migrasyon production’ı bozarsa geri dönmek için bir plan ve canlı yük altında ortaya çıkan drift’i gidermek için bir plan. Onlarca Lambda fonksiyonu ve birden fazla DynamoDB tablosu içeren bir migrasyonda geçiş planı, migrasyon kodunun kendisinden daha büyüktür.
Varsayılan olarak alarma bağlı otomatik rollback’li blue-green deployment’ı seçin. Bedeli, geçiş boyunca ikinci bir stack’i ayakta tutmaktır; karşılığı ise CloudFormation rollback’ini beklemeden saniyeler içinde geri alınabilen bir anahtardır. Geri kalan iş gösterişsizdir: bir stack’in güvenli olup olmadığına karar verebilen health check’ler, iş mantığını değil servis limitlerini zorlayan testler ve production trafiği ona bağlanmadan önce en az bir kez çalıştırılmış bir rollback yolu.
Seri Navigasyonu:
- Bölüm 1: Neden Geçiş Yapalım?
- Bölüm 2: CDK Environment Kurulumu
- Bölüm 3: Lambda Fonksiyonları ve API Gateway Migration
- Bölüm 4: Database ve Environment Management
- Bölüm 5: Authentication, Authorization ve IAM
- Bölüm 6: Migration Stratejileri ve Best Practice’ler (bu yazı)
Geçiş Deseni Seçimi#
Serverless migrasyonlarında üç desen öne çıkar ve her biri farklı bir yerden çatlar.
Yaklaşım #1: Big Bang Migration#
Uygulama: Tüm CDK altyapısını tek bir bakım penceresinde deploy etmek.
Nerede çatlar: Büyük bir stack’in CloudFormation deployment süresini önceden kestirmek zordur; ilk taşan şey bakım penceresi olur. Başarısız bir API Gateway stage deployment’ı ya da yarım kalan bir DynamoDB import’u, servis zaten kapalıyken rollback yapmanıza yol açar; üstelik rollback yolu deployment yolu kadar uzundur.
Karar: Demo uygulamalar ve gerçek bir bakım penceresi olan iç araçlar için uygundur. Cross-service bağımlılıkları olan production sistemler için uygun değildir.
Yaklaşım #2: Strangler Fig Pattern#
Uygulama: Fonksiyonları gruplar hâlinde taşımak; eski stack’te iş kalmayana kadar trafiği eski ve yeni API arasında bölmek.
Nerede çatlar: İki sistem, geçiş boyunca authentication ve session state’ini paylaşmak zorundadır. Stack’ler arası çağrılar, tek stack’li sürümde olmayan bir ağ atlaması ekler. Geçiş haftalarla ölçülür ve bu haftaların her biri iki deployment pipeline’ını birlikte işletmek demektir.
Karar: Uygulama tek seferde taşınamayacak kadar büyükse doğru seçimdir; yeter ki bağımlılık haritası önceden çıkarılsın ve auth katmanı kopyalanmak yerine paylaşılsın.
Yaklaşım #3: Blue-Green Deployment#
Uygulama: Tam CDK stack’ini paralel olarak ayağa kaldırmak, production’a benzeyen trafikle doğrulamak, sonra DNS’i ya da API Gateway stage’ini çevirmek.
Nerede çatlar: Her stateless kaynağın ikinci bir kopyasının bedelini ödersiniz ve bu desen state hakkında hiçbir şey söylemez. Paylaşılan tablolar iki stack tarafından da okunabilir kalmalıdır; bu da geçiş süresince şema değişikliklerini kısıtlar.
Karar: Varsayılan seçenek. Rollback, bir CloudFormation operasyonu değil ikinci bir DNS ya da stage değişikliğidir; anahtarı baskı altında geri alınabilir kılan da budur.
Production’a Hazır Migration Stratejileri#
1. Blue-Green Deployment Stack’i#
Blue-green stack’i kendi health check’ini, kendi alarmlarını ve anahtarı çeviren fonksiyonu birlikte taşır:
// lib/stacks/production-blue-green-stack.ts
import { Stack, StackProps, Tags, CfnOutput, Duration, TreatMissingData } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { RestApi, MethodLoggingLevel, LambdaIntegration } from 'aws-cdk-lib/aws-apigateway';
import { Alarm, ComparisonOperator } from 'aws-cdk-lib/aws-cloudwatch';
import { LambdaAction } from 'aws-cdk-lib/aws-cloudwatch-actions';
import { PolicyStatement } from 'aws-cdk-lib/aws-iam';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
export interface BlueGreenStackProps extends StackProps {
stage: string;
environment: 'blue' | 'green';
monitoringConfig: {
errorThreshold: number;
latencyThreshold: number;
rollbackFunction: NodejsFunction;
};
}
export class ProductionBlueGreenStack extends Stack {
public readonly api: RestApi;
public readonly healthCheckEndpoint: string;
public readonly switchOverFunction: NodejsFunction;
constructor(scope: Construct, id: string, props: BlueGreenStackProps) {
super(scope, id, props);
// Tam CDK infrastructure oluştur
this.api = new RestApi(this, 'Api', {
restApiName: `my-service-${props.stage}-${props.environment}`,
description: `Production API - ${props.environment.toUpperCase()} environment`,
deployOptions: {
stageName: props.environment,
// Migration sırasında güvenlik için agresif throttling
throttlingRateLimit: props.environment === 'green' ? 500 : 1000,
throttlingBurstLimit: props.environment === 'green' ? 1000 : 2000,
// Migration sırasında gelişmiş monitoring
metricsEnabled: true,
loggingLevel: MethodLoggingLevel.INFO,
dataTraceEnabled: true,
tracingEnabled: true,
},
});
// Tüm Lambda fonksiyonlarını deploy et ve route'ları bağla.
// createLambdaFunctions ve setupApiRoutes bu sınıfın private helper'ları,
// kısalık için buraya alınmadı.
const functions = this.createLambdaFunctions(props);
this.setupApiRoutes(functions);
// Monitoring için health check endpoint oluştur
const healthCheckFn = new NodejsFunction(this, 'HealthCheckFunction', {
entry: 'src/health/health-check.ts',
handler: 'handler',
environment: {
ENVIRONMENT: props.environment,
API_VERSION: process.env.API_VERSION || 'v1',
DEPLOYMENT_TIME: new Date().toISOString(),
},
});
const healthResource = this.api.root.addResource('health');
healthResource.addMethod('GET', new LambdaIntegration(healthCheckFn));
this.healthCheckEndpoint = `${this.api.url}health`;
// Production monitoring alarm'ları oluştur
this.createProductionAlarms(props);
// Traffic switching fonksiyonu
this.switchOverFunction = this.createSwitchOverFunction(props);
// Tanımlama için tüm kaynakları tagla
Tags.of(this).add('Environment', props.environment);
Tags.of(this).add('MigrationPhase', 'cdk-migration');
Tags.of(this).add('DeploymentTime', new Date().toISOString());
Tags.of(this).add('Version', process.env.COMMIT_SHA || 'latest');
// Kritik bilgileri export et
new CfnOutput(this, 'ApiEndpoint', {
value: this.api.url,
exportName: `${this.stackName}-api-endpoint`,
description: `API endpoint for ${props.environment} environment`,
});
new CfnOutput(this, 'HealthCheckUrl', {
value: this.healthCheckEndpoint,
exportName: `${this.stackName}-health-check`,
description: 'Health check endpoint for monitoring',
});
}
private createProductionAlarms(props: BlueGreenStackProps) {
// Error rate alarm - rollback tetikler
const errorAlarm = new Alarm(this, 'HighErrorRateAlarm', {
metric: this.api.metricServerError({
period: Duration.minutes(2),
statistic: 'Sum',
}),
threshold: props.monitoringConfig.errorThreshold,
evaluationPeriods: 2,
comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
alarmDescription: `High error rate detected in ${props.environment} environment`,
treatMissingData: TreatMissingData.NOT_BREACHING,
});
// Latency alarm - araştırma tetikler
const latencyAlarm = new Alarm(this, 'HighLatencyAlarm', {
metric: this.api.metricLatency({
period: Duration.minutes(5),
statistic: 'Average',
}),
threshold: props.monitoringConfig.latencyThreshold,
evaluationPeriods: 3,
alarmDescription: `High latency detected in ${props.environment} environment`,
});
// Alarm'ları otomatik rollback'e bağla
errorAlarm.addAlarmAction(
new LambdaAction(props.monitoringConfig.rollbackFunction)
);
// External monitoring için alarm ARN'lerini export et
new CfnOutput(this, 'ErrorAlarmArn', {
value: errorAlarm.alarmArn,
exportName: `${this.stackName}-error-alarm`,
});
}
private createSwitchOverFunction(props: BlueGreenStackProps) {
return new NodejsFunction(this, 'TrafficSwitchFunction', {
entry: 'src/deployment/traffic-switch.ts',
handler: 'handler',
timeout: Duration.minutes(5),
environment: {
CURRENT_ENVIRONMENT: props.environment,
TARGET_ENVIRONMENT: props.environment === 'blue' ? 'green' : 'blue',
HOSTED_ZONE_ID: process.env.HOSTED_ZONE_ID!,
DOMAIN_NAME: process.env.API_DOMAIN!,
SLACK_WEBHOOK_URL: process.env.SLACK_WEBHOOK_URL!,
},
initialPolicy: [
new PolicyStatement({
actions: ['route53:ChangeResourceRecordSets', 'route53:GetChange'],
resources: ['*'],
}),
],
});
}
}
// src/health/health-check.ts - Kapsamlı health validation
import { APIGatewayProxyHandler } from 'aws-lambda';
import { DynamoDBClient, DescribeTableCommand } from '@aws-sdk/client-dynamodb';
const dynamoDB = new DynamoDBClient({});
export const handler: APIGatewayProxyHandler = async () => {
const startTime = Date.now();
const checks = [];
try {
// Database connectivity check
const tableCheck = await dynamoDB.send(new DescribeTableCommand({
TableName: process.env.USERS_TABLE!,
}));
checks.push({
name: 'database',
status: tableCheck.Table?.TableStatus === 'ACTIVE' ? 'healthy' : 'unhealthy',
responseTime: Date.now() - startTime,
});
// Memory usage check
const memoryUsed = process.memoryUsage();
checks.push({
name: 'memory',
status: memoryUsed.heapUsed < 100 * 1024 * 1024 ? 'healthy' : 'warning', // 100MB threshold
details: {
heapUsed: Math.round(memoryUsed.heapUsed / 1024 / 1024) + 'MB',
heapTotal: Math.round(memoryUsed.heapTotal / 1024 / 1024) + 'MB',
},
});
const overallStatus = checks.every(check => check.status === 'healthy') ? 'healthy' : 'degraded';
return {
statusCode: overallStatus === 'healthy' ? 200 : 503,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
},
body: JSON.stringify({
status: overallStatus,
environment: process.env.ENVIRONMENT,
version: process.env.API_VERSION,
deploymentTime: process.env.DEPLOYMENT_TIME,
timestamp: new Date().toISOString(),
responseTime: Date.now() - startTime,
checks,
}),
};
} catch (error) {
return {
statusCode: 503,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
status: 'unhealthy',
error: (error as Error).message,
timestamp: new Date().toISOString(),
}),
};
}
};
2. Strangler Fig Pattern#
Ne zaman kullanılır: Sıfır kesinti gerektiren büyük uygulamalar.
// lib/constructs/migration/traffic-splitter.ts
import { Construct } from 'constructs';
import { RestApi, Deployment, Stage } from 'aws-cdk-lib/aws-apigateway';
import { Alarm } from 'aws-cdk-lib/aws-cloudwatch';
export class TrafficSplitter extends Construct {
constructor(scope: Construct, id: string, props: {
legacyApiId: string;
newApi: RestApi;
trafficPercentageToNew: number;
}) {
super(scope, id);
// Canary deployment oluştur
const deployment = new Deployment(this, 'CanaryDeployment', {
api: props.newApi,
description: `Canary deployment ${new Date().toISOString()}`,
});
const stage = new Stage(this, 'CanaryStage', {
deployment,
stageName: 'canary',
canarySettings: {
percentTraffic: props.trafficPercentageToNew,
useStageCache: false,
},
});
// Monitoring için CloudWatch alarm'ları.
// Metriği tüm API'ye değil canary stage'ine bağla.
new Alarm(this, 'CanaryErrorAlarm', {
metric: stage.metricServerError(),
threshold: 5,
evaluationPeriods: 2,
});
}
}
3. DNS Seviyesinde Traffic Switching#
Ne zaman kullanılır: Anahtarın CloudFormation dışında dönmesi gerektiğinde. Rollback o zaman tek bir kayıt değişikliğidir; araya stack güncellemesi girmez.
// lib/stacks/blue-green-stack.ts
export class BlueGreenStack extends Stack {
constructor(scope: Construct, id: string, props: {
stage: string;
version: 'blue' | 'green';
}) {
super(scope, id);
const api = new RestApi(this, 'Api', {
restApiName: `my-service-${props.stage}-${props.version}`,
deployOptions: {
stageName: props.version,
},
});
// Kolay tanımlama için kaynakları tagla
Tags.of(this).add('Deployment', props.version);
Tags.of(this).add('Version', process.env.COMMIT_SHA || 'latest');
// API endpoint'ini export et
new CfnOutput(this, 'ApiEndpoint', {
value: api.url,
exportName: `${this.stackName}-endpoint`,
});
}
}
// deployment-scripts/blue-green-switch.ts
import { Route53Client, ChangeResourceRecordSetsCommand } from '@aws-sdk/client-route53';
export async function switchTraffic(targetVersion: 'blue' | 'green') {
const route53 = new Route53Client({});
await route53.send(new ChangeResourceRecordSetsCommand({
HostedZoneId: process.env.HOSTED_ZONE_ID,
ChangeBatch: {
Changes: [{
Action: 'UPSERT',
ResourceRecordSet: {
Name: 'api.example.com',
Type: 'CNAME',
TTL: 60,
ResourceRecords: [{
Value: `api-${targetVersion}.execute-api.region.amazonaws.com`,
}],
},
}],
},
}));
}
Production İçin Etkili Test Stratejisi#
Her unit ve integration testini geçen bir suite, geçiş anında ortaya çıkan hataları yine de kaçırabilir. Bu hatalar genellikle mantıkla değil limitlerle ilgilidir.
Standart Testlerin Kaçırdıkları#
Alışıldık suite’in kapsadığı: Unit testler, integration testler ve bir load test; hepsi yeşil.
Kapsamadığı:
- Construct’lar biriktikçe CloudFormation template’inin stack başına boyut limitini aşması
- API Gateway integration timeout’unun Lambda timeout ayarlarıyla çakışması
- Yoğun trafik altında DynamoDB throttling
- JWT validation maliyetinin istek hacmiyle birlikte büyümesi
Production Odaklı Test Stratejisi#
Sentezlenen template üzerine yazılan assertion’lar, limit ve yapılandırma sorunlarını deploy öncesinde yakalar:
// test/infrastructure/api-stack.test.ts
import { Template, Match } from 'aws-cdk-lib/assertions';
import { App } from 'aws-cdk-lib';
import { ApiStack } from '../../lib/stacks/api-stack';
describe('ApiStack', () => {
let template: Template;
beforeAll(() => {
const app = new App();
const stack = new ApiStack(app, 'TestStack', {
config: testConfig,
});
template = Template.fromStack(stack);
});
test('Lambda functions have correct runtime', () => {
template.allResourcesProperties('AWS::Lambda::Function', {
Runtime: 'nodejs20.x',
});
});
test('API Gateway has throttling enabled', () => {
template.hasResourceProperties('AWS::ApiGateway::Stage', {
ThrottlingRateLimit: Match.anyValue(),
ThrottlingBurstLimit: Match.anyValue(),
});
});
test('DynamoDB tables have point-in-time recovery', () => {
template.allResourcesProperties('AWS::DynamoDB::Table', {
PointInTimeRecoverySpecification: {
PointInTimeRecoveryEnabled: true,
},
});
});
});
Integration Testing#
// test/integration/api.test.ts
import { CloudFormationClient, ListExportsCommand } from '@aws-sdk/client-cloudformation';
import axios from 'axios';
describe('API Integration Tests', () => {
let apiEndpoint: string;
let authToken: string;
beforeAll(async () => {
// Deploy edilmiş API endpoint'ini al
const cf = new CloudFormationClient({});
const exports = await cf.send(new ListExportsCommand({}));
apiEndpoint = exports.Exports?.find(
e => e.Name === 'ApiStack-endpoint'
)?.Value!;
// Auth token al
authToken = await getTestAuthToken();
});
test('Health check endpoint', async () => {
const response = await axios.get(`${apiEndpoint}/health`);
expect(response.status).toBe(200);
expect(response.data).toEqual({ status: 'healthy' });
});
test('Create and retrieve user', async () => {
// User oluştur
const createResponse = await axios.post(
`${apiEndpoint}/users`,
{ name: 'Test User', email: 'test@example.com' },
{ headers: { Authorization: `Bearer ${authToken}` } }
);
expect(createResponse.status).toBe(201);
// User'ı getir
const userId = createResponse.data.userId;
const getResponse = await axios.get(
`${apiEndpoint}/users/${userId}`,
{ headers: { Authorization: `Bearer ${authToken}` } }
);
expect(getResponse.data.name).toBe('Test User');
});
});
Load Testing#
// test/load/k6-script.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';
const errorRate = new Rate('errors');
export const options = {
stages: [
{ duration: '2m', target: 100 }, // Ramp up
{ duration: '5m', target: 100 }, // Sustain
{ duration: '2m', target: 200 }, // Spike
{ duration: '5m', target: 200 }, // Sustain spike
{ duration: '2m', target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: ['p(95)<500'], // Request'lerin %95'i 500ms altında
errors: ['rate<0.01'], // Error oranı %1 altında
},
};
export default function() {
const response = http.get(`${__ENV.API_URL}/users`);
const success = check(response, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
});
errorRate.add(!success);
sleep(1);
}
Rollback Prosedürleri#
Otomatik Rollback#
// lib/constructs/deployment/safe-deployment.ts
import { Construct } from 'constructs';
import { Alarm, TreatMissingData } from 'aws-cdk-lib/aws-cloudwatch';
import { RestApi } from 'aws-cdk-lib/aws-apigateway';
import { IFunction } from 'aws-cdk-lib/aws-lambda';
import { Topic } from 'aws-cdk-lib/aws-sns';
import { SnsAction } from 'aws-cdk-lib/aws-cloudwatch-actions';
import { LambdaSubscription } from 'aws-cdk-lib/aws-sns-subscriptions';
import { CfnOutput } from 'aws-cdk-lib';
export class SafeDeployment extends Construct {
constructor(scope: Construct, id: string, props: {
api: RestApi;
alarmThreshold: number;
rollbackFunction: IFunction;
}) {
super(scope, id);
// CloudWatch alarm oluştur
const alarm = new Alarm(this, 'DeploymentAlarm', {
metric: props.api.metricServerError(),
threshold: props.alarmThreshold,
evaluationPeriods: 2,
treatMissingData: TreatMissingData.NOT_BREACHING,
});
// Bildirimler için SNS topic
const topic = new Topic(this, 'RollbackTopic');
alarm.addAlarmAction(new SnsAction(topic));
// Otomatik rollback için Lambda
topic.addSubscription(
new LambdaSubscription(props.rollbackFunction)
);
// Manuel rollback komutu
new CfnOutput(this, 'RollbackCommand', {
value: `aws lambda invoke --function-name ${props.rollbackFunction.functionName} --payload '{"action":"rollback"}' response.json`,
});
}
}
// src/deployment/rollback-handler.ts
import { SNSEvent } from 'aws-lambda';
import { CodeDeployClient, StopDeploymentCommand } from '@aws-sdk/client-codedeploy';
// Yardımcı fonksiyonlar
async function switchTraffic(version: string): Promise<void> {
// Traffic switching implementasyonu
}
async function notifySlack(message: { channel: string; message: string }): Promise<void> {
// Slack bildirimi implementasyonu
}
export const handler = async (event: SNSEvent) => {
console.log('Initiating rollback:', JSON.stringify(event, null, 2));
const codedeploy = new CodeDeployClient({});
// Mevcut deployment'ı durdur
await codedeploy.send(new StopDeploymentCommand({
deploymentId: process.env.CURRENT_DEPLOYMENT_ID,
autoRollbackEnabled: true,
}));
// Traffic'i önceki versiyona döndür
await switchTraffic('blue'); // Green'in başarısız olduğunu varsayarak
// Ekibi bilgilendir
await notifySlack({
channel: '#alerts',
message: 'Automatic rollback initiated due to high error rate',
});
};
Performans Optimizasyonu#
Lambda Performans Tuning#
// lib/constructs/performance/optimized-function.ts
import { Construct } from 'constructs';
import { Duration, Stack } from 'aws-cdk-lib';
import { NodejsFunction, NodejsFunctionProps } from 'aws-cdk-lib/aws-lambda-nodejs';
import { Architecture, CfnFunction, CfnAlias } from 'aws-cdk-lib/aws-lambda';
// Temel fonksiyon arayüzü
interface ServerlessFunctionProps extends NodejsFunctionProps {
config: {
stage: string;
};
}
export class OptimizedFunction extends NodejsFunction {
constructor(scope: Construct, id: string, props: ServerlessFunctionProps & {
enableProvisioning?: boolean;
enableSnapStart?: boolean;
}) {
super(scope, id, {
...props,
memorySize: props.memorySize || 1024,
architecture: Architecture.ARM_64, // Daha iyi price/performance
environment: {
...props.environment,
NODE_OPTIONS: '--enable-source-maps --max-old-space-size=896',
AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',
},
});
// Kritik fonksiyonlar için provisioned concurrency
if (props.enableProvisioning && props.config.stage === 'prod') {
const version = this.currentVersion;
new CfnAlias(this, 'ProvisionedAlias', {
functionName: this.functionName,
functionVersion: version.version,
name: 'provisioned',
provisionedConcurrencyConfig: {
provisionedConcurrentExecutions: 5,
},
});
}
// Java fonksiyonları için SnapStart
if (props.enableSnapStart) {
const cfnFunction = this.node.defaultChild as CfnFunction;
cfnFunction.snapStart = {
applyOn: 'PublishedVersions',
};
}
}
}
API Gateway Optimizasyonu#
// lib/constructs/performance/cached-api.ts
import { Construct } from 'constructs';
import { Duration } from 'aws-cdk-lib';
import { RestApi, RestApiProps } from 'aws-cdk-lib/aws-apigateway';
export class CachedApi extends RestApi {
constructor(scope: Construct, id: string, props: RestApiProps & {
cacheConfig?: {
ttlMinutes: number;
encrypted: boolean;
clusterSize: string;
};
}) {
super(scope, id, {
...props,
deployOptions: {
...props.deployOptions,
cachingEnabled: true,
cacheClusterEnabled: true,
cacheClusterSize: props.cacheConfig?.clusterSize || '0.5',
cacheDataEncrypted: props.cacheConfig?.encrypted ?? true,
cacheTtl: Duration.minutes(props.cacheConfig?.ttlMinutes || 5),
methodOptions: {
'/*/*': {
cachingEnabled: true,
cacheKeyParameters: [
'method.request.path.proxy',
'method.request.querystring.page',
],
},
},
},
});
}
}
Monitoring ve Gözlemlenebilirlik#
Kapsamlı Monitoring Stack#
// lib/stacks/monitoring-stack.ts
import { Stack, StackProps, Duration } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { Dashboard, GraphWidget, Alarm } from 'aws-cdk-lib/aws-cloudwatch';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
// ApiStack için arayüz
interface ApiStack extends Stack {
api: any; // aws-apigateway'den RestApi
functions: NodejsFunction[];
}
export class MonitoringStack extends Stack {
constructor(scope: Construct, id: string, props: {
apiStack: ApiStack;
stage: string;
}) {
super(scope, id);
// Dashboard oluştur
const dashboard = new Dashboard(this, 'ServiceDashboard', {
dashboardName: `my-service-${props.stage}`,
});
// API metrikleri
dashboard.addWidgets(
new GraphWidget({
title: 'API Requests',
left: [props.apiStack.api.metricCount()],
right: [props.apiStack.api.metricLatency()],
}),
new GraphWidget({
title: 'API Errors',
left: [
props.apiStack.api.metric4XXError(),
props.apiStack.api.metric5XXError(),
],
})
);
// Lambda metrikleri
const lambdaWidgets = props.apiStack.functions.map(fn =>
new GraphWidget({
title: `${fn.functionName} Performance`,
left: [fn.metricInvocations()],
right: [fn.metricDuration()],
})
);
dashboard.addWidgets(...lambdaWidgets);
// Alarmlar
this.createAlarms(props.apiStack);
}
private createAlarms(apiStack: ApiStack) {
// API Gateway alarmları
new Alarm(this, 'HighErrorRate', {
metric: apiStack.api.metric5XXError({
period: Duration.minutes(5),
statistic: 'Sum',
}),
threshold: 10,
evaluationPeriods: 2,
});
// Lambda alarmları
apiStack.functions.forEach(fn => {
new Alarm(this, `${fn.node.id}Throttles`, {
metric: fn.metricThrottles(),
threshold: 5,
evaluationPeriods: 2,
});
new Alarm(this, `${fn.node.id}Errors`, {
metric: fn.metricErrors(),
threshold: 10,
evaluationPeriods: 2,
});
});
}
}
Distributed Tracing#
// lib/constructs/observability/tracing.ts
import { Construct } from 'constructs';
import { PolicyStatement } from 'aws-cdk-lib/aws-iam';
import { Tracing } from 'aws-cdk-lib/aws-lambda';
import { OptimizedFunction } from '../performance/optimized-function';
// Temel fonksiyon arayüzü
interface ServerlessFunctionProps {
environment?: Record<string, string>;
}
export class TracedFunction extends OptimizedFunction {
constructor(scope: Construct, id: string, props: ServerlessFunctionProps) {
super(scope, id, {
...props,
tracing: Tracing.ACTIVE,
environment: {
...props.environment,
// _X_AMZN_TRACE_ID Lambda tarafından rezerve edilmiştir ve her
// invocation'da enjekte edilir; burada set etmek deploy anında reddedilir.
AWS_XRAY_CONTEXT_MISSING: 'LOG_ERROR',
AWS_XRAY_LOG_LEVEL: 'error',
},
});
// X-Ray izinleri ekle
this.addToRolePolicy(new PolicyStatement({
actions: [
'xray:PutTraceSegments',
'xray:PutTelemetryRecords',
],
resources: ['*'],
}));
}
}
// src/libs/tracing.ts
import { Tracer } from '@aws-lambda-powertools/tracer';
const tracer = new Tracer({
serviceName: process.env.SERVICE_NAME || 'my-service',
});
export function traceMethod(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = async function(...args: any[]) {
const segment = tracer.getSegment();
const subsegment = segment?.addNewSubsegment(propertyKey);
try {
const result = await originalMethod.apply(this, args);
subsegment?.close();
return result;
} catch (error) {
subsegment?.addError(error as Error);
subsegment?.close();
throw error;
}
};
return descriptor;
}
Migration Checklist’i#
Pre-Migration#
-
Mevcut kaynakları envantere al
- Tüm Lambda fonksiyonlarını belgele
- API Gateway endpoint’lerini listele
- DynamoDB tablolarını ve indexlerini mapla
- Custom kaynakları tanımla
- Tüm environment variable’ları ve secret’ları not et
-
Dependency’leri değerlendir
- Kullanımdaki Serverless plugin’lerini gözden geçir
- Custom CloudFormation kaynaklarını kontrol et
- External servis entegrasyonlarını tanımla
- IAM role’leri ve policy’leri belgele
-
Migration stratejisini planla
- Migration pattern’ı seç (big bang, strangler fig, blue-green)
- Rollback prosedürlerini tanımla
- Başarı kriterlerini belirle
- Gerekirse maintenance window’ları planla
Migration Sırasında#
-
CDK projesini kur
- CDK ile repository’i initialize et
- Environment’ları yapılandır
- CI/CD pipeline’larını kur
- Infrastructure testlerini implement et
-
Componentleri migrate et
- Stateless kaynaklarla başla
- Mevcut stateful kaynakları import et
- Lambda fonksiyonlarını migrate et
- API Gateway’i kur
- Authentication’ı yapılandır
-
Testing
- Unit testleri çalıştır
- Integration testlerini yürüt
- Load testing yap
- Güvenlik konfigürasyonlarını validate et
Post-Migration#
-
Monitor et ve optimize et
- Kapsamlı monitoring kur
- Alert’leri yapılandır
- Performans metriklerini gözden geçir
- Cold start’ları optimize et
-
Dokümantasyon
- Runbook’ları güncelle
- Yeni deployment prosedürlerini belgele
- Mimari diyagramlar oluştur
- Ekibi CDK konusunda eğit
-
Temizlik
- Eski Serverless Framework kaynaklarını kaldır
- Kullanılmayan IAM role’leri sil
- S3 deployment bucket’larını temizle
- DNS kayıtlarını güncelle
Yaygın Tuzaklar ve Çözümler#
1. Kaynak Adlandırma Çakışmaları#
// Hardcoded isimlerden kaçın
// Kötü
const table = new Table(this, 'Table', {
tableName: 'users-table', // Varsa çakışır
});
// İyi
const table = new Table(this, 'Table', {
tableName: `${props.serviceName}-${props.stage}-users`,
});
2. State Management#
// Stateful ve stateless kaynakları ayır
import { App } from 'aws-cdk-lib';
const app = new App();
// Stateful kaynaklar ayrı stack'te
const dataStack = new DataStack(app, 'DataStack', {
terminationProtection: true,
});
// Stateless kaynaklar serbestçe güncellenebilir
const apiStack = new ApiStack(app, 'ApiStack', {
tables: dataStack.tables,
});
3. Environment Variable Migration#
// Serverless variable'larını CDK'ya mapla
import { Stack, Fn } from 'aws-cdk-lib';
const legacyMappings: Record<string, string> = {
'${self:service}': props.serviceName,
'${opt:stage}': props.stage,
'${opt:region}': Stack.of(this).region,
'${cf:OtherStack.Output}': Fn.importValue('OtherStack-Output'),
};
Serverless Framework’te Kalmanın Uygun Olduğu Durumlar#
Bazı senaryolarda Serverless Framework, CDK’dan daha uygun kalır:
- Minimal customization gerektiren basit CRUD uygulamaları
- Hızlı prototipleme gereken proof-of-concept projeler
- TypeScript deneyimi ve eğitim için zamanı olmayan ekipler
- CDK’da karşılığı olmayan ağır plugin bağımlılıkları taşıyan uygulamalar
- Yalnızca YAML’a izin veren altyapı politikaları olan organizasyonlar
Varsayılan Nerede Geçerli#
Production geçişinde varsayılan, alarma bağlı otomatik rollback’li blue-green’dir. Uygulama, bakım penceresinin bedelini dürüstçe karşılayabilecek kadar küçükse ya da auth katmanını tutarlı tutmanın tek yolu strangler fig geçişi olacak kadar büyükse bu varsayılanı değiştirin. Planın geri kalanı her iki durumda da ayakta kalır: stateful kaynakları yeniden oluşturmak yerine import edin, sentezlenen template üzerine assertion yazın ve production trafiği bağlanmadan önce rollback’i bir staging hesabında bir kez çalıştırın.
YAML yapılandırmasından TypeScript’e geçmek altyapıya derleme, test ve code review getirir. Bu kazanç ancak testler CI’da koşuyorsa ve rollback yolu en az bir kez denenmişse ortaya çıkar. Bu ikisi olmadan CDK, CloudFormation yazmanın daha pahalı bir yoludur.
Kaynaklar#
- AWS CDK v2 Geliştirici Kılavuzu (yeni sekmede açılır) - Construct’lar, stack’ler, ortamlar ve CDK v2 için en iyi uygulamaları kapsayan resmi belgeleme.
- CDK Pipelines: AWS CDK Uygulamaları için Sürekli Teslimat (yeni sekmede açılır) - CDK Pipelines ile çok ortamlı otomatik CI/CD hatları oluşturma kılavuzu.
- AWS CDK Pipelines ve AWS CodeDeploy ile Blue/Green Deployment (yeni sekmede açılır) - CDK ile sıfır kesinti süreli blue/green deployment kalıplarına ilişkin AWS DevOps blog yazısı.
- AWS Lambda En İyi Uygulamaları (yeni sekmede açılır) - Fonksiyon performansı, bellek, zaman aşımı ve başlatma kodu için resmi rehber.
- Serverless Framework AWS Lambda Fonksiyonları Kılavuzu (yeni sekmede açılır) - Migration kaynağı olarak kullanılan Serverless Framework fonksiyon yapılandırma referansı.
- AWS CDK En İyi Uygulamaları (yeni sekmede açılır) - CDK ile bulut altyapısı geliştirme ve deploy etme için önerilen kalıplar.
Serverless Framework'ten AWS CDK'ya Geçiş Rehberi
Serverless Framework'ten AWS CDK'ya tam geçiş sürecini kapsayan 6 bölümlük kapsamlı rehber. Kurulum, uygulama pattern'leri ve best practice'ler dahil.
Bu serideki tüm yazılar
İlgili yazılar
Multi-environment deployment, ölçekte performans optimizasyonu, maliyet yönetimi ve sağlam incident response pattern'ları ile monitoring.
aws-cdk · lambda · dynamodb +6
React Native Expo uygulamasına Sentry ekleme rehberi: SDK kurulumu, Expo Router enstrümantasyonu, session replay ve EAS için source map yükleme.
react-native · expo · monitoring +2
Single Table Design'da DynamoDB throttling'i önleme ve yönetme: partition key tasarımı, write sharding, kapasite modları, DAX caching ve retry pattern'leri.
dynamodb · aws · reliability +4
Global uygulamalar için AWS edge computing çözümlerini seçme ve uygulama üzerine pratik örnekler ve maliyet optimizasyonu stratejileri içeren kapsamlı teknik rehber.
aws · cloudfront · lambda +6
LangChain'i production'da çalıştırırken öğrenilen dersler: hataya yol açan anti-patternler, işe yarayan patternler, kod örnekleri ve maliyet optimizasyonu.
langchain · llm · production +5