ASP.NET Core Serisi 04: Kubernetes Üzerinde Serverless API Backend Tasarlama

Cloud-based sistemlerin çoğalması ile birlikte, son dönemlerde kendisinden oldukça fazla söz ettiren bir kavram açıkcası serverless. Bir şeylerin hızla geliştiği bu günümüz çağında, time to market’e daha da önem verilmesiyle beraber, bazen tek ihtiyacımız olan şey sadece bir function olabiliyor. Durum böyle olunca, bu makale kapsamında ise araştırmalarım ve deneyimlerim doğrultusunda serverless(Function-as-a-Service) kavramından biraz bahsedip, daha sonra bir serverless framework’ü olan Fission ile Kubernetes üzerinde örnek bir serverless API backend’i geliştireceğiz.

Serverless Architecture Giriş

Özellikle scalability kavramının önemli olduğu durumlarda, sunucu configuration’ları, management işlemleri ve hatta hata yönetimi gibi konulara takılmadan uygulamalarımızı yada tek bir function’ı çalıştırabilmeye dayanan bir mimaridir aslında. Serverless demek, kısaca her şeyin birer servis olarak kullanılması demektir diyebiliriz sanırım. Bir başka değişle serverless computing paradigması long-running virtual machine’lerin aksine, istek üzerine açılan(event-driven) geçici short-running hesaplama birimlerinin, isteği tamamlaması ve ardından kapanması mantığına dayanır.

Örneğin kullanıcıların beğendikleri ürünleri favori listelerine ekleyebilme özelliği, e-ticaret web sitelerinin vazgeçilmez özelliklerinden birisidir sanırım. Bu özellik genelde ekleme, çıkartma ve listeleme function’larına sahip oluyor. Bu üç function’ın birer event sonucu tetiklenen ayrı birer function olduğunu düşünebiliriz.

Bir developer olarak, bir backend server’ını maintain etme derdi olmadan, geliştirdiğimiz kodu serverless mimarisi ile on demand olarak deploy edebilmek ve çalıştırabilmek, kulağa hoş geliyor değil mi?  Bununla birlikte eğer bir public cloud kullanıcısı iseniz en güzel tarafı ise, gerçekten kullandığınız kadar ücretlendiriliyor olmanızdır.

Tabi bunun yapısını da iyi tasarlamak gerekir.

Serverless mimarisini kullanabilmemiz için Azure FunctionsAmazon Lambda ve Google Cloud Functions bizlere gerekli altyapıları sunmaktadır. Ben bu makale kapsamında ise bu üç büyük provider’dan ziyade serverless mimarisini, kubernetes üzerinde bir serverless framework’ü olan fission ile nasıl kullanabiliriz konusu üzerinde durmaya çalışacağım. Ayrıca ASP.NET Core ile nasıl bir serverless backend API‘ı geliştirebiliriz konusuna da değiniyor olacağız.

İlk olarak serverless mimarisi özelinde dikkat etmemiz gereken bir kaç noktaya değinmek istiyorum.

Nelere Dikkat Etmeliyiz?

  • Function’ların “idempotent” olmasına dikkat edilmesi gerekmektedir. Yani ilgili provider, oluşan bir event sonucu, ilgili function’ı birden fazla kez çalıştırmaya çalışabilir. Bu konu hakkında çok fazla detaya girmeyeceğim. Bununla ilgili burada güzel bir yazı var.
  • Long-running işler, bir başka dikkate almamız gereken bir noktadır. Örneğin Amazon Lambda, execution için maximum 5 dakika sağlamaktadır.
  • Response time’ın önemli olduğu işler. Warm-up durumları sebebiyle latency’ler yaşanabilmektedir. Tecrübelerim bu yönde oldu. (Bu gibi durumlar için geliştirilmiş serverless warmup plugin’leri mevcut, ama deneyimlemedim)
  • En önemli konulardan biriside provider bağımlılığı. Eğer düzgün bir abstraction gerçekleştirmedi isek, bir başka provider’a geçmek çok da kolay olmayacaktır. Bunların hepsi ise bizlere “ekstra efor” olarak yansıyacaktır.

Open-Source Serverless Framework’leri

Serverless kavramından biraz bahsettik. İnternet üzerinde hali hazırda bir çok harika makale mevcut olduğu için bu konu hakkında daha fazla detaya girmek istemedim.

Peki, serverless mimarisini kullanabilmemiz için Azure, Amazon ve Google gibi dev provider’ların bizlere gerekli altyapıları sağladıklarına değindik. Ayrıca bu makale kapsamında ise kubernetes üzerinde bir serverless framework’ü olan fission ile bir backend API‘ı geliştireceğimizden de bahsettik.

Şimdi Azure FunctionsAmazon Lambda ve Google Cloud Functions’a alternatif olan open-source serverless framework’lerinden biraz bahsetmek istiyorum.

Open-source dünyasında Fission, OpenWhisk ve Kubeless gibi harika alternatif framework’ler mevcut durumda. Bu framework’lerin en güzel tarafı ise, cloud provider agnostik olmalarıdır sanırım. Örneğin fission ve kubeless, kubernetes‘in çalışabildiği her yerde çalışabilmektedir. Public cloud, private cloud veya kendi laptop’ınızda farketmez, istediğiniz yerde çalıştırabilirsiniz. Commercial provider’lara göre open-source serverless framework’leri, doğası gereği bizlere daha geniş bir 3th party integration alanı ve özgürlük de sunmaktadır.

Open-source mu, yoksa commercial mı seçeneği tamamen size kalmış durumda. Serverless mimarisinin, firmalara cazip gelen asıl tarafı herhangi bir ekstra efor ve infrastructure ile uğraşmadan zaman’dan ve paradan tasarruf etmek olunca, elbette burada da seçim için bir tradeoff söz konusu oluyor.

Fission – Serverless Framework

Fission open-source bir serverless framework’ü olup, high-level olarak kubernetes için bir abstraction layer sağlamaktadır. Yukarıdaki bölümde de bahsettiğim gibi fission‘ın en güzel tarafı ise, kubernetes‘in çalışabildiği her yerde çalışabilmesidir. Fission “Python”, “Go”, “.NET”, “.NET 2.0”, “NodeJS”, “Perl” ve “Ruby” gibi geniş bir environment’a sahiptir.

Fission function’ların startup time’ını hızlandırabilmek için, built-in bir pre warm up mekanizmasına sahiptir. Bunun için küçük dynamic loader’ların bulunuduğu “warm” container’ları maintain etmektedir. Cold-started anında ise “warm” container’lardan birisi seçilir ve function load edilir. Bu sayede cold-start latency’leri ortalama “100msec” civarındadır. Bununla ilgili detaylı bilgiye ise, buradan ulaşabilirsiniz.

Fission‘ı kullanmaya başlamadan, genel olarak bir konseptine göz atalım.

  • Environment: Webserver, dynamic loader ve bir function’ın runtime spesifik parçalarını içeren bir container’dır. Bir function oluşturmadan önce, bir environment oluşturmamız gerekmektedir.
  • Function: Execute olacak olan kod parçamız.
  • Trigger: Function’ları invoke eden bir event. “HTTP“, “Time” ve “MQ” olmak üzere üç adet trigger bulunmaktadır. Biz örneğimizde “HTTP” trigger’ını kullanacağız.

Bu ön bilgilerin ardından, artık uygulamaya geçebiliriz.

Fission’ı Kullanmak

Fission‘ı kullanabilmemiz için öncelikle bir kubernetes cluster’ına ihtiyacımız var. Bunun için Minikube’u veya Docker‘ın edge channel’ını kullanarak hızlıca bir kubernetes cluster’ına sahip olabiliriz. Detaylı bilgiye ise buradan ulaşabilirsiniz.

Kubernetes cluster kurulumunun ardından, sırasıyla “Kubernetes CLI“, “Helm” ve son olarak “Fission” kurulumunu gerçekleştirmemiz gerekmektedir. İlgili kurulum adımlarını ise buradan takip edebilirsiniz.

NOT: Gerekli kurulumların ardından, “Run an example” kısmındaki örneğin çalıştığından emin olun.

Şimdi en basit haliyle fission ile bir function’ı nasıl oluşturabilir ve deploy edebiliriz kısmına bir bakalım. Bunun için, ilk olarak fission üzerinde aşağıdaki komut satırı ile “dotnetcore-env” adında bir .NET Core 2.0 environment’ı oluşturalım.

fission env create --name dotnetcore-env --image fission/dotnet20-env

NOT: Environment oluştururken, CPU ve memory resource limit’lerini de belirtebilmek mümkündür. Örneğin: “–mincpu 40 –maxcpu 80 –minmemory 64 –maxmemory 128

Fission‘ın .NET Core 2.0 environment’ı içerisinde, upload edilen function’ı compile edebilmek için Roslyn ve host işlemlerini gerçekleştirebilmek için ise Kestrel ve Nancy‘i barındırmaktadır.

Artık oluşturmuş olduğumuz bu environment’ı kullanarak, toplama işlemi yapan basit bir function oluşturabiliriz. Bunun için, fission‘ın built-in olarak bize sunmuş olduğu .NET Core 2.0 environment’ının function template’ini kullanacağız.

VS Code‘u açalım ve “FissionFunction” isminde, aşağıdaki gibi bir class oluşturalım.

using System;
using Fission.DotNetCore.Api;

public class FissionFunction
{
    public string Execute(FissionContext context)
    {
        int x = Convert.ToInt32(context.Arguments["x"]);
        int y = Convert.ToInt32(context.Arguments["y"]);

        return (x + y).ToString();
    }
}

Built-in olarak sunulan .NET Core 2.0 environment’ı, yukarıdaki gibi bir convention ile çalışabilmektedir. İçerisinde “Execute” method’unun yer aldığı, yukarıdaki gibi bir “FissionFunction” isminde class oluşturmamız gerekmektedir.

Oluşturmuş olduğumuz “FissionFunction” class’ına bakarsak, query-string ile gelen değerlere “context.Arguments” üzerinden erişebilmekteyiz. Body içerisinden gelecek olan değerlere ise, “context.Request.Body” stream’i üzerinden erişebilmek de mümkündür.

Şimdi oluşturmuş olduğumuz “FissionFunction” class’ını, fussion üzerinde aşağıdaki gibi bir function olarak tanımlamamız gerekmektedir.

fission fn create --name addition --env dotnetcore-env --code [Your_Work_Dir]/FissionFunction.cs

Yukarıdaki komut satırı ile “addition” isminde, “dotnetcore-env” environment’ını kullanan ve “FissionFunction” class’ını execute edecek bir function tanımlamış olduk.

Şimdi ise oluşturmuş olduğumuz “addition” function’ının execute olabilmesi için bir trigger oluşturmamız gerekmektedir. Bunun için ise aşağıdaki komut satırını çalıştırmamız yeterli olacaktır.

fission ht create --method GET --url /addition --function addition

Yukarıdaki komut satırı ile “addition” function’ını “http://localhost/additionURL‘i üzerinden GET edildiğinde çalıştırması için bir HTTP trigger’ı oluşturduk.

Hepsi bu kadar.

Geriye sadece test işlemi kaldı. Test edebilmek için “http://localhost/addition?x=10&y=5URL‘ine bir GET isteğinde bulunalım.


Sonuç ise yukarıda gördüğümüz gibi. HTTP trigger “addition” function’ını tetikledi ve “FussionFunction” class’ı içerisindeki “Execute” method’u çalıştırıldı.

Örneğimizde basit olarak fission’ın bize sunmuş olduğunu built-in .NET Core 2.0 environment’ı ile bir function oluşturduk ve çalıştırdık. Peki farklı ihtiyaçlar karşısında ise, nasıl bir function oluşturabiliriz sorusunu duyar gibiyim. Örneğin bir function içerisinde bir kaç farklı NuGet package’ını kullanmak isteyebiliriz.

Malesef fission‘ın eksisi, şuanda sadece “compiled language” ler için multiple file özelliğini desteklememektedir. Bu gibi durumlar için built-in olarak sunulan environment yerine, buradaki projeyi kullanarak kendi environment’ımızı (docker image) kolaylıkla oluşturabiliriz.

Environment’ı oluşturduktan sonra ise tek yapmamız gereken şey, aşağıdaki gibi onu kullanmak olacaktır:

fission env create --name your-dotnetcore-env --image your/dotnet20-env

Sonuç

FaaS, son dönemlerde beni heyecanlandıran nadir konulardan birisidir. Bence short-lived function’lar oluşturup ona özel küçük kod parçaları yazabilmek, infra işlerine harcanan zaman ve eforu azaltabilmek, teknolojinin hızla geliştiği ve değiştiği günümüz çağında özellikle büyük bir önem taşımaktadır. Fission gibi open-source serverless framework’leri, özellikle provider agnostik oldukları için sanırım beni daha çok cezbediyorlar. Bulunduğum ortamlarda çok da fazla FaaS kullanımı görmesemde, cloud provider’larının bu alana yatırım yapmalarını görmek sevindirici. Sanırım gelecek zamanlarda daha çok görüyor olacağım. Umarım. 🙂

Bazı Referanslar

https://docs.fission.io/0.6.0/concepts/
https://medium.com/@PaulDJohnston/when-not-to-use-serverless-jeff-6d054d0e7098
http://www.datacenterknowledge.com/archives/2017/01/18/open-source-serverless-computing-frameworks-matter

Gökhan Gökalp

Recent Posts

Containerized Uygulamaların Supply Chain’ini Güvence Altına Alarak Güvenlik Risklerini Azaltma (Güvenlik Taraması, SBOM’lar, Artifact’lerin İmzalanması ve Doğrulanması) – Bölüm 1

{:tr}Bildiğimiz gibi modern yazılım geliştirme ortamında containerization'ın benimsenmesi, uygulamaların oluşturulma ve dağıtılma şekillerini oldukça değiştirdi.…

8 ay ago

Identity & Access Management İşlemlerini Azure AD B2C ile .NET Ortamında Gerçekleştirmek

{:tr}Bildiğimiz gibi bir ürün geliştirirken olabildiğince farklı cloud çözümlerinden faydalanmak, harcanacak zaman ve karmaşıklığın yanı…

1 yıl ago

Azure Service Bus Kullanarak Microservice’lerde Event’ler Nasıl Sıralanır (FIFO Consumers)

{:tr}Bazen bazı senaryolar vardır karmaşıklığını veya eksi yanlarını bildiğimiz halde implemente etmekten kaçamadığımız veya implemente…

2 yıl ago

.NET Microservice’lerinde Outbox Pattern’ı ile Eventual Consistency için Atomicity Sağlama

{:tr}Bildiğimiz gibi microservice architecture'ına adapte olmanın bir çok artı noktası olduğu gibi, maalesef getirdiği bazı…

2 yıl ago

Dapr ve .NET Kullanarak Minimum Efor ile Microservice’ler Geliştirmek – 02 (Azure Container Apps)

{:tr}Bir önceki makale serisinde Dapr projesinden ve faydalarından bahsedip, local ortamda self-hosted mode olarak .NET…

2 yıl ago