AWS Lambda ile Serverless ASP.NET Core Uygulaması Geliştirme

Merhaba, bu yazımızda AWS’in sunucusuz uygulama mimarisi (serverless architecture) sunan Lambda hizmeti ile ASP.NET Core web uygulaması nasıl geliştirilir, deploy edilir ve çalışma mantığı neye dayanır gibi soruları kısaca cevaplamaya çalışacağız.

Öncelikle sunucusuz mimariye kısaca göz atarsak, bu kavramın aslında sunuculara ihtiyacı n olmadığı, hatta sunucunun olmadığı bir dünya değil; sunucular üzerindeki yük dağıtımı, konfigürasyonlar, ölçeklendirme, deployment, test gibi konuları yönetme zor(unlu)luğunu ortadan kaldıran bir yaklaşım olarak ortaya çıkıyor. Aşırı yük altında çalışan, yüksek network trafiğine sahip web uygulamalarında, yazılan iş mantığı ile birlikte bu tip konuların da önemi artıyor. Özetle sunucusuz mimari; geliştiriciye, uygulamanın iş mantığını ortaya çıkaracak fonsiyonları yaz, run-time konusuna kadar her şeyi soyutlayıp kendi tarafımda yöneteceğim diyor. Bunu popüler bir kısaltma olarak FaaS (Function as a Service) şeklinde adlandırabiliriz.

Peki neden AWS Lambda vb. servisler tercih edilebilir? FaaS yaklaşımının geliştiriciden yukarıda bahsedilen konuları soyutlaması, verim ve maliyet olarak geri dönüyor. Aynı zamanda kullanım başına ödeme (pay-per-use-pricing) yaklaşımı, fonksiyon çağırımlarına kadar inmiş durumda. Bu durum maliyet açısından, kullanılan ram ve işlemci seviyesine göre ücretlendirmeyle kıyaslandığında oldukça avantajlı.

Mevcut bir ASP.NET Core uygulamasının çalışma mantığına baktığımızda, clienttan gelen requestin, IIS ya da Nginx gibi bir web sunucu üzerinden Kestrel’e ve oradan Web API controllerlarına ulaştığını görüyoruz.

Ancak uygulamamızı, AWS Lambda üzerinde yayınlarsak durum değişiyor. Gelen istekler, AWS API Gateway üzerinden geçip daha sonra Lambda tarafından karşılanıyor.

 

AWS Lambda hizmetini kullanmaya karar verdik ve ASP.NET Core ile bir web uygulaması geliştirmek istiyoruz. İlk olarak AWS Toolkit Visual Studio kuruyor ve AWS Explorer üzerinden profil bilgilerini giriyoruz. (Bu sonraki deployment aşamasında işe yarayacak.)

Daha sonra yeni bir AWS Serverless Application oluşturuyoruz.

Oluşturulan proje şablonunda, klasik Web API şablonundan farklı olarak S3ProxyController, entry pointleri içeren sınıflar ve serverless.template dosyası göreceksiniz. Aslında önemli fark, uygulamanın Main() fonksiyonu içeren Program.cs üzerinden başlaması değil; LambdaEntryPoint.cs sınıfı ile ASP.NET Core frameworkünün ayağa kaldırılmasından başlıyor. Bu sınıf aynı zamanda, uygulamanın AWS kaynaklarını nasıl kullanacağını belirten serverless.template dosyasında da Handler olarak yazıyor.

"Get" : {
      "Type" : "AWS::Serverless::Function",
      "Properties": {
        "Handler": "AWSServerless2::AWSServerless2.LambdaEntryPoint::FunctionHandlerAsync",
        "Runtime": "dotnetcore1.0",
        "CodeUri": "",
        "MemorySize": 256,
        "Timeout": 30,
        "Role": null,
        "Policies": [ "AWSLambdaFullAccess" ],
        "Environment" : {
          "Variables" : {
            "AppS3Bucket" : { "Fn::If" : ["CreateS3Bucket", {"Ref":"Bucket"}, { "Ref" : "BucketName" } ] }
          }
        },
        "Events": {
          "PutResource": {
            "Type": "Api",
            "Properties": {
              "Path": "/{proxy+}",
              "Method": "ANY"
            }
          }
        }
      }
    }

Bununla birlikte LocalEntryPoint.cs sınıfını Handler olarak varsayabilir ve localde çalışabilirsiniz. Controllers klasörü içinde gelen S3ProxyController, GET, POST gibi HTTP metotlarının, deploy edilmiş projenin konfigürasyon dosyasını içerecek S3 bucket’a AWS SDK.NET tarafından nasıl çağrıda bulunulduğunu gösteriyor. Localde çalışırken appsetting.json dosyasından farklı bir bucket adı verebiliyoruz.

{
  "Lambda.Logging": {
    "LogLevel": {
      "Default": "Debug",
      "Microsoft": "Information"
    }
  },
  "AppS3Bucket": ""
}

Şimdi mevcut uygulamamızı, toolkit yardımıyla deploy etmeye çalışalım. Bu noktada yardımımıza proje için oluşturulmuş serverless.template koşuyor. AWS’in bir diğer toolu olan Cloud Formation, bir uygulamanın çalıştırılmadan önce hangi AWS kaynaklarını (EC2, DynamoDB, S3, Cache, VPC vs.) nasıl kullanacağını json ya da YAML formatında tasarlamamızı sağlıyor. Yapmamız gereken AWS Explorer -> AWS Cloud Formation -> Create Stack -> Select a file yolunu izleyerek serverless.template dosyasını göstermek ve oluşturacağımız stacke yeni bir isim vermek. 

Publish to AWS Lambda kısmını başarıyla geçtikten sonra domain adınız yoksa AWS, Serverless URL oluşturacaktır.

Linkler:

https://aws.amazon.com/blogs/compute/container-reuse-in-lambda/
https://read.acloud.guru/how-to-keep-your-lambda-functions-warm-9d7e1aa6e2f0
https://start.jcolemorrison.com/aws-lambda-vs-the-world/

AWS S3 Storage ile Dosya İşlemleri

Bu yazımızda, AWS’nin storage hizmeti olan Amazon S3 Storage ve .NET SDK’sını kullanarak dosyalarımızı nasıl yöneteceğimizi inceleyeceğiz.

Bir web uygulamasında, sıklıkla dosya yükleme kullanılıyorsa, sunucuda tutulan bu dosyaların saklama, arşivleme ve hatta yükleme işlerinin bir süre sonra baş ağrıtan bir problem haline gelme ihtimali epey yüksektir. Amazon’un S3 Storage hizmeti ya da benzeri ürünler, scalability, arşivleme ve analitik konularında pratik ve kolay uygulanabilir çözümleri güvenli bir yolla kullanıma sunuyor. Bu yüzden, web uygulamalarının deploy edildiği klasörler içinde yazma – okuma işini, S3 bucket’larına devredebiliriz.

AWS Console üzerinden S3 servisine gidiyoruz ve yeni bir bucket oluşturuyoruz. Bucket adımız, AWS üzerindeki tüm bucketlar içinde tekil olmalıdır.

createbucket[0]

AWS, bize bir sonraki kısımda versiyonlama, loglama ve tag kullanıp kullanmayacağımızı soruyor, şimdi her birinin ne işe yaradığına kısaca bakalım;

Versiyonlama

Versiyonlamayı aktif etmek demek,  bucket içinde tuttuğumuz bir objenin farklı versioning_Enabledçeşitlerinin bir versionID ile birlikte tutulması anlamına gelir. Bu özellik ile aynı objenin tekrar yüklenmesi ve silinmesi durumunda ilk haline dönüşü sağlanabilir. Aşağıdaki görselde “h0721u2562x0134v3959” keyName’e sahip bir fotoğrafın ilk haline ulaşacağımız link, version ID ve son değiştirilme tarihi görülmektedir.

createbucket[1]

Loglama

Yüklenen dosyaların daha detaylı bilgilerine erişmek isteniyorsa loglama özelliği ücretsiz bir şekilde aktif edilebilir. (Ancak AWS log dosyalarını başka bir yere taşımak isterseniz data transfer ile bir tutarak ücret kesiyor.)

Bucket Owner Id
79
a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be
Bucket Name
mybucket
Create Date
[06/Feb/2014:00:00:38 +0000]
Remote IP
192.0.2.3
Requester
79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be
Request Id (Amazon tarafından her request için oluşturulur.)
3E57427F3EXAMPLE
File Name
keyname
Operation
REST.GET.VERSIONING –
Key
“GET /mybucket?versioning
HTTP/1.1”

200
Bytes Sent
113
Total Time
7
Error Message
“-”
“S3Console/0.4”

Tag Ekleme

Dosya yükleme esnasında, dosyayla bir ya da daha fazla tag ilişkilendirilerek kategorize edilebilir.

3. adımda kullanıcıların bucket içindeki dosyalara olan erişim kurallarını belirlememiz gerekiyor. Bu kısım varsayılan olarak geçilerek bucket’ı oluşturuyoruz ve içine girip Permissions sekmesindeki Bucket Policy alanına giriyoruz. Burada yer alan Policy Generator aracından faydalanarak erişim kurallarımızı belirleyebiliriz.

createbucket[12].PNG

Burada oluşturduğumuz JSON formatındaki kural setini, Bucket Policy içine kopyalayaıp kaydediyoruz. Artık bucket’ımızı kullanmaya hazırız.

Bu örnekte stream ve path kullanarak fotoğraf yükleme ve silme işlemlerini yapacağız. İlk olarak projemize AWSSDK.S3 paketini yükleyerek başlayabiliriz. Byte dizisi şeklindeki bir fotoğrafı yüklemek istiyorsak, MemoryStream sınıfı ile basit bir şekilde gerçekleştirebiliriz.

public static string UploadImageToAws(string keyName, string bucketName, byte[] imageArray)
        {
            try
            {
                using (var ms = new MemoryStream())
                {
                    client = new AmazonS3Client(ConfigurationManager.AppSettings["AWSAccessKey"],
                    ConfigurationManager.AppSettings["AWSSecretAccessKey"], RegionEndpoint.EUWest1);
                    var tu = new Amazon.S3.Transfer.TransferUtility(client);

                    ms.Write(imageArray, 0, imageArray.Length);
                    ms.Position = 0;
                    var req = new Amazon.S3.Transfer.TransferUtilityUploadRequest();
                    req.BucketName = bucketName;
                    req.Key = keyName;
                    req.InputStream = ms;
                    tu.Upload(req);

                    return "https://s3.amazonaws.com/" + bucketName + "/" + keyName;
                }
            }
            catch (AmazonS3Exception amazonS3Exception)
            {
                Console.WriteLine(amazonS3Exception.Message);
            }
            catch (Exception ex)
            {
                throw (ex);
            }
            return string.Empty;
        }

Mevcut bir fotoğrafı silmek içinse aşağıdaki gibi DeleteObject() metodunu çağırmak yeterli.

public static bool DeleteImageToAws(string keyName, string bucketName)
        {
            using (client = new AmazonS3Client(ConfigurationManager.AppSettings["AWSAccessKey"],
                   ConfigurationManager.AppSettings["AWSSecretAccessKey"], RegionEndpoint.EUWest1))
            {
                DeleteObjectRequest dor = new DeleteObjectRequest() { BucketName = bucketName, Key = keyName };
                client.DeleteObject(dor);
            }

            return true;
        }