.NET’te S3 Event’leriyle AWS Lambda’yı Tetikleyelim
Merhaba,
Bu içeriğimizde, AWS Lambda’yı S3 event’leri eşliğinde nasıl tetikleyebileceğimizi inceliyor olacağız. Tabi bunu yaparken önceden klavyeye almış olduğumuz AWS Lambda İle Serverless Computing ve AWS S3 İle Dosya İşlemleri Gerçekleştirme başlıklı makalelerimizden de istifade edecek ve bunlardan event-driven yaklaşıma sahip bir sistem oluşturmak için nasıl entegrasyonun yapılabileceğini, gerçek hayattan bir senaryo üzerinden değerlendirmede bulunacağız. O halde buyurun başlayalım…
Senaryo
Öncelikle senaryomuzun tahayyülünü gerçekleştirerek başlayalım…
Umarım çözmeye çalışacağımız sorunu tam olarak anlatabilmişimdir… Bu sorun, özellikle yüksek trafikli uygulamalar için geçerlidir. Keza, S3 event’leri eşliğinde AWS Lambda entegrasyonunu izah edebilmek için de oldukça ideal bir senaryo gibi gözükmektedir.
Ne inşa edeceğiz?
Senaryomuz gereği inşa edeceğimiz uygulamanın yapısından da bahsetmekte fayda görmekteyim. Bir resim/image dosyasını herhangi bir Amazon S3 bucket’ına yükleyecek uygulama geliştireceğiz. Bu amaç doğrultusunda kullanıcıdan aldığı resim dosyasını S3 lokasyonuna yükleyecek olan minimal api teknolojisiyle geliştirilmiş bir ortam sağlayacak ve devamında resim yüklendiğinde de S3 event notification sistemini işleyecek ve yeni yüklenen resmi getirip boyutlandırma çalışmasını gerçekleştirecek olan bir AWS Lambda uygulaması geliştirilecektir. Ee haliyle notification kısmının sağlıklı çalışabilmesi için öncelikle AWS Lambda ve S3 bucket kısımlarını önceden yapılandırmış olmamız gerekecektir. Tüm bunların dışında resim işleme sürecinde popüler olan ImageSharp paketinden istifade edeceğiz.
Evet, hazırsak eğer hadi başlayalım…
Amazon S3 Bucket Oluşturma
İlk olarak çalışmalarımıza aşağıdaki görseldeki gibi resimleri depolayacağımız example-image-storage adında bir S3 bucket oluşturarak başlayabiliriz.
Resim Yükleme API’sini Oluşturma
Ardından resimleri bu S3 bucket’a yükleyecek olan minimal api projesini oluşturabiliriz. Bunun için ben AWS.Image.Example.API adında bir Asp.NET Core projesi oluşturuyorum. Ve akabinde bu projede S3 işlemleri için kullanacağımız aşağıdaki kütüphanelerin yüklenmesini sağlıyorum;
Install-Package AWSSDK.S3
Install-Package AWSSDK.Extensions.NETCore.Setup
Ve devamında ise aşağıdaki gibi resim yükleme işleminin endpoint’ini oluşturuyorum;
using Amazon.S3;
using Amazon.S3.Model;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDefaultAWSOptions(builder.Configuration.GetAWSOptions());
builder.Services.AddAWSService<IAmazonS3>();
var app = builder.Build();
app.MapPost("/image-upload", async (IFormFile file, IAmazonS3 amazonS3) =>
{
if (!file.ContentType.StartsWith("image/"))
return Results.BadRequest("Dosya geçersizdir. Yalnız resim dosyaları desteklenmektedir.");
PutObjectRequest request = new()
{
BucketName = "example-image-storage",
Key = $"images/{Path.GetFileName(file.FileName)}",
InputStream = file.OpenReadStream()
};
request.Metadata.Add("Content-Type", file.ContentType);
await amazonS3.PutObjectAsync(request);
return Results.Ok($"Resim başarıyla yüklenmiştir. | {Path.GetFileName(file.FileName)}");
}).DisableAntiforgery();
app.Run();
Yaptığımız bu çalışmayı test edersek eğer;
Görüldüğü üzere endpoint’imiz başarıyla çalışmaktadır. S3 bucket’ı da kontrol edelim;
Resim Boyutlandırma Lambda’sının Oluşturulması
Resimleri istenen S3 bucket’ına yükleyecek bir API oluşturduğumuza göre artık gereksinimimiz açısından resim boyutlandırma servisini oluşturabiliriz. Bunun için aynı solution altında, ‘Sizing.Example’ adında yeni bir AWS Lambda Project oluşturalım.
Oluşturulan bu lambda’ya ImageSharp kütüphanesini yükleyelim ve içeriğini aşağıdaki gibi şekillendirelim;
public class Function
{
IAmazonS3 S3Client { get; set; }
public Function()
{
S3Client = new AmazonS3Client();
}
public Function(IAmazonS3 s3Client)
{
this.S3Client = s3Client;
}
public async Task FunctionHandler(S3Event evnt, ILambdaContext context)
{
var eventRecords = evnt.Records ?? new List<S3Event.S3EventNotificationRecord>();
foreach (var record in eventRecords)
{
var s3Event = record.S3;
if (s3Event == null)
{
continue;
}
try
{
var bucketName = s3Event.Bucket.Name;
var key = s3Event.Object.Key;
var response = await S3Client.GetObjectAsync(bucketName, key);
context.Logger.LogLine($"{key} orjinal resim boyutu : {response.ContentLength}");
using (var image = Image.Load(response.ResponseStream))
{
int maxWidth = 500, maxHeight = 500;
image.Mutate(x => x.Resize(new ResizeOptions
{
Mode = ResizeMode.Max,
Size = new Size(maxWidth, maxHeight)
}));
using (MemoryStream stream = new MemoryStream())
{
image.Save(stream, new SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder());
context.Logger.LogLine($"{key} boyutlandırılmış resim boyutu : {stream.Length}");
var resizedIamgesPath = $"resized-images/{key.Replace("images/", "")}";
var uploadRequest = new PutObjectRequest
{
BucketName = bucketName,
Key = resizedIamgesPath,
InputStream = stream
};
await this.S3Client.PutObjectAsync(uploadRequest);
context.Logger.LogLine($"Resim yeniden boyutlandırılmıştır.");
}
}
//Orjinal resmi silelim
await this.S3Client.DeleteObjectAsync(bucketName, s3Event.Object.Key);
context.Logger.LogLine($"Orjinal resim silinmiştir. {s3Event.Object.Key}");
}
catch (Exception e)
{
context.Logger.LogError(e.Message);
context.Logger.LogError(e.StackTrace);
throw;
}
}
}
}
Yukarıdaki kod bloğunu incelersek eğer AWS S3 üzerindeki belirttiğimiz bucket’a yüklenen resimleri yeniden boyutlandırma işlemi yürütülmektedir. Burada bazı kodların işlevselliğinden bahsetmemizin faydalı olacağı kanaatindeyim. Misal olarak; evnt.Records, S3Event nesnesindeki tüm event kayıtlarını getirmektedir. Yani tetikleyici olayların listesidir diyebiliriz. record.S3 ise her bir S3EventNotificationRecord içindeki o olaya ait S3 bilgilerini temsil etmektedir. Örneğin, hangi bucket’a hangi dosyanın yüklendiği gibi bilgiler bu nesne üzerinden alınır. Son olarak s3Event.Object.Key komutunu izah edersek eğer, bu da dosyanın S3 içindeki yolunu temsil etmektedir. İşte bu bilgiler eşliğinde bizler S3 bucket’a yüklenen resim dosyası üzerinde gerekli işlemleri gerçekleştiriyor ve orjinal dosyayı silerek, boyutlandırılmış haliyle süreci tamamlıyoruz.
Evet, artık lambda function’ımızı oluşturduğumuza göre AWS’ye publish edebiliriz.
Görüldüğü üzere SizingExample isminde bir lambda function oluşturuyoruz.
Bu aşamada rol olarak AWSLambdaExecute‘u seçmenizi tavsiye ederim. Böylece Cloudwatch’a log atabilmeniz için temel izinleri elde etmiş olacaksınız.
Ve nihai olarak bu şekilde lambda’yı publish edelim…
S3 Erişimi İçin Lambda İzinlerinin Güncellenmesi
Geliştirdiğimiz lambda tarafından S3 servisine erişip gerekli resim işlemlerinin gerçekleştirilebilmesi için doğal olarak bir takım izinlere ihtiyacımız olacaktır. Bu izinlerin düzenlenebilmesi için lambda servisi üzerinden oluşturduğumuz SizingExample isimli lambda’yı açalım ve aşağıdaki görselde olduğu gibi lambda rolüne tıklayarak erişim politikalarını düzenleyelim.
Ardından Permissions policies kısmından ‘Add policies’ -> ‘Attach policies’ diyerek lambda’ya yeni erişim izni tanımlayalım.
Burada AmazonS3FullAccess seçerek yola devam edebiliriz.
Evet, artık lambda tarafından S3 erişimi verildiğine göre AWS Lambda’yı S3 Event’leriyle tetikleyebiliriz.
AWS Lambda’yı S3 Event’leriyle Tetikleme
Artık her şey yerli yerinde olduğuna göre, yapbozun son parçasını yerleştirebilir ve S3 bucket’ı altındaki ‘images’ klasörüne yeni bir resim yüklendiği taktirde lambda’mızı tetikleyebiliriz.
Bunun için de yine lambda üzerinden aşağıdaki görselde olduğu gibi ‘Configuration’ -> ‘Triggers’ sekmesine gelerek yeni bir trigger eklememiz gerekmektedir.
Trigger’la ilgili ayarları aşağıdaki görseldeki gibi yapılandıralım;
Ve trigger’ı oluşturalım.
İşte bu kadar… Bu yaptığımız işlem neticesinde S3 bucket’ta ki ‘/images’ yoluna bir resim yüklendiği taktirde bu trigger sayesinde, S3Event meta verisi eşliğinde lambda tetiklenecektir. Vee gerekli resim boyutlandırma işlemini lambda halledecektir.
Test Edelim…
Şimdi S3 bucket’a oluşturduğumuz API aracılığıyla resim dosyasını gönderelim ve süreci CloudWatch loglarından takip edelim;
Görüldüğü üzere resim orjinal haliyle yükleniyor, yeniden boyutlandırılıyor ve orjinali silinerek yeni hali ‘resized-images/’ path’ine koyuluyor.
Hata Durumlarında İse…
Eğer ki yapılan bu işlem sürecinde bir başarısızlık durumu söz konusu olursa AWS Lambda maksimum 6 saat içerisinde 2 kez deneme gerçekleştirecektir. Eğer ki bu denemelerden bir netice alınamazsa o dosyaya karşı oluşturulan event kaybolacaktır. Bu duruma dair tüm yapılandırmaları aşağıdaki görselde olduğu gibi lambda’nın ‘Configuration’ -> ‘Asynchronous invocation’ kısmından yapabilirsiniz.
Bu durumu engelleyebilmek için Dead Letter Queue (DLQ) oluşturarak önlem alabilir ve yeniden denemeden sonra başarısız olan tüm event’leri belirtilen DLQ’ya taşıyarak, sonradan bu kuyruk üzerinden resimleri işleyebilirsiniz.
Nihai olarak;
Böylece S3 bucket’a yüklenen bir dosya üzerinden AWS Lambda’nın nasıl tetiklenebileceğini(trigger), dosya işleme mantığını ana uygulamadan ayırarak nasıl uygulanabileceğini ve genel sistem performansını iyileştirmenin temiz bir yolunu hep beraber tecrübe etmiş bulunuyoruz.
İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…
Not : Örnek çalışmaya aşağıdaki github adresinden erişebilirsiniz.
https://github.com/gncyyldz/AWS.Image.Example.API
