AWS Türkçe Blog

Taşınabilir Lambda İşlevleri Geliştirme

Orijinal Makale: Link (Uri Segev)

Yeni uygulamalar geliştirirken veya mevcut uygulamaları modernize ederken bir ikilemle karşılaşabilirsiniz: hangi bilgi işlem teknolojisini kullanmalı? AWS Lambda gibi sunucusuz bir bilgi işlem hizmeti mi yoksa container’lar mı? Otomatik ölçeklendirme, yerleşik yüksek kullanılabilirlik ve istek üzerine fiyatlandırma modeli sayesinde genellikle sunucusuz bilgi işlem daha iyi bir yaklaşım olabilir. Ancak, aşağıdaki gibi nedenlerle sunucusuz seçimi yapmakta tereddüt edebilirsiniz:

  • Daha yüksek maliyet algısı veya maliyeti tahmin etmede zorluk
  • Bunun bilgi boşluğunu kapatmayı öğrenmeyi gerektiren bir paradigma değişimi olması
  • Lambda yetenekleri ve kullanım durumları hakkında yanlış kanılar
  • Lambda kullanmanın, sonradan altyapı değişikliğine engel olacağı endişesi
  • Sunucusuz olmayan platformlara ve araçlara yapılan mevcut yatırımlar

Bu blog gönderisi, daha sonra tercih ederseniz kodunuzu container’lara kolayca taşımanıza olanak tanıyan taşınabilir Lambda işlevleri geliştirmeye yönelik en iyi uygulamaları önerir. Bunu yaparak, altyapı değişikliğine uygun olarak sunucusuz yaklaşımı risksiz bir şekilde deneyebilirsiniz.

Bu blog gönderisinin her bölümü, taşınabilir kod yazarken göz önünde bulundurmanız gerekenleri ve daha sonra yapmayı seçerseniz bu kodu Lambda’dan container’lara geçirmek için gereken adımları açıklar.

Taşınabilir Lambda işlevleri için en iyi pratikler

Ayrı iş mantığı ve Lambda işleyici

Lambda işlevleri, doğası gereği olay güdümlüdür. Belirli bir olay gerçekleştiğinde, handler işleyici metodunu çağırarak Lambda işlevini çalıştırır. İşleyici metodu, işlev çağırma nedeni ile ilgili bilgileri içeren bir event olay nesnesi alır. İşlev işletimi tamamlandığında, işlev işleyici metoddan geri döner. İşleyiciden ne döndürülürse, işlevin dönüş değeri o olur.

Taşınabilir kod yazmak için işleyici metodunu yalnızca Lambda çalışma zamanı (runtime) (olay nesnesi) ile iş mantığı arasında bir arabirim olarak kullanmanızı öneririz. Hexagonal mimari terminolojisini kullanacak olursak işleyici, iş mantığı tarafından sunulan arabirim olan bağlantı noktasına çağrılar yapan bir sürücü adaptörü olmalıdır. İşleyici, gerekli tüm bilgileri olay nesnesinden çıkarmalı ve ardından iş mantığını uygulayan ayrı bir metodu çağırmalıdır.

Method geri dönüşünde, işleyici sonucu işlevi çağıran tarafından beklenen biçimde oluşturur ve döndürür. Ayrıca işleyici kodunu ve iş mantığı kodunu ayrı dosyalara ayırmanızı öneririz. Daha sonra container’a taşımayı seçerseniz, iş mantığı kodu içeren dosyalarınızı hiçbir ek değişiklik yapmadan kolayca taşıyabilirsiniz.

Aşağıdaki pseudocode sözde kod, olay nesnesinden bilgi çıkaran ve iş mantığını çağıran bir Lambda işleyicisini gösterir. İş mantığı tamamlandığında işleyici, yanıtı fonksiyonun dönüş değerine yerleştirir:

import business_logic

# The Lambda handler extracts needed information from the event
# object and invokes the business logic
handler(event, context) {
  # Extract needed information from event object payload = event[‘payload’]

  # Invoke business logic
  result = do_some_logic(payload)
  
  # Construct result for API Gateway
  return {
    statusCode: 200,
	body: result
  }
}
JavaScript

Aşağıdaki sözde kod, iş mantığını gösterir. Ayrı bir dosyada bulunur ve bir Lambda fonksiyonundan çağrıldığından habersizdir.

# This is the business logic. It knows nothing about who invokes it.
do_some_logic(data) {
result = "This is my result."
  return result
}
JavaScript

Bu yaklaşım, olay nesneleri oluşturmaya ve Lambda işlevini çağırmaya gerek kalmadan iş mantığı üzerinde birim testleri çalıştırmayı da kolaylaştırır.

Container’lara daha sonra geçiş yaparsanız, iş mantığı dosyalarını container’a aşağıdaki bölümde açıklandığı gibi yeni arayüz koduyla dahil edersiniz.

Olay kaynağı entegrasyonu

Lambda işlevlerinin avantajlarından biri de olay kaynağı entegrasyonudur. Örneğin, Lambda’yı Amazon Simple Queue Service (Amazon SQS) ile entegre ederseniz, Lambda servisi kuyruğu sorgulama, Lambda işlevini çağırma ve işlevin işletimi bittiğinde mesajları kuyruktan silme işlemlerini otomatik olarak yönetir. Bu entegrasyonu kullanarak, daha az kod yazmış olursunuz. Olay kaynağıyla entegrasyona değil, yalnızca iş mantığını uygulamaya odaklanabilirsiniz.

Aşağıdaki sözde kod, Lambda işleyicisinin bir SQS olay kaynağı için nasıl göründüğünü gösterir:

import business_logic

handler(event, context) {
  entries = []
  # Iterate over all the messages in the event object
  for message in event[‘Records’] {
    # Call the business logic to process a single message
    success = handle_message(message)

    # Start building the response
    if Not success {
      entries.append({
      'itemIdentifier': message['messageId']
      })
    }
  }

  # Notify Lambda about failed items.
  if (let(entries) > 0) {
    return {
      'batchItemFailures': entries
    }
  }
}
JavaScript

Kodda da görebileceğiniz üzere, Lambda işlevinin SQS’den çağrıldığına dair neredeyse hiçbir bilgisi yoktur. Herhangi bir SQS API çağrısı da yoktur. Lambda işlevi yalnızca olay nesnesinin SQS’e özgü yapısını bilir.

Uygulamayı bir container’a taşırken, entegrasyon sorumluluğu Lambda hizmetinden geliştirici olarak size geçer. AWS’de farklı olay kaynakları vardır ve bunların her biri, olayları tüketmek ve iş mantığını başlatmak için farklı bir yaklaşım gerektirecektir. Örneğin, olay kaynağı Amazon API Gateway ise uygulamanızın iş mantığını çağırmak için bir HTTP bağlantı noktasını dinleyen ve gelen istekleri bekleyen bir HTTP sunucusu oluşturması gerekir.

Olay kaynağı Amazon Kinesis Data Streams ise, uygulamanızın parçalardaki kayıtları okuyan bir okuyucu çalıştırması, işlenen kayıtları takip etmesi, akıştaki parça sayısındaki değişiklik durumunu ele alması, hataları yeniden denemesi gibi işleri yönetmesi gerekir. Olay kaynağı ne olursa olsun, önceki önerileri uygularsanız iş mantığı kodunda herhangi bir değişiklik yapmanız gerekmez.

Aşağıdaki sözde kod, SQS ile entegrasyonun bir container’da nasıl görüneceğini gösterir. Batching, filtreleme ve tabii ki otomatik ölçeklendirme gibi bazı özellikleri kaybedeceğinizi unutmayın.

import aws_sdk
import business_logic

QUEUE_URL = os.environ['QUEUE_URL']
BATCH_SIZE = os.environ.get('BATCH_SIZE', 1)
sqs_client = aws_sdk.client('sqs')

main() {
  # Infinite loop to poll for messages from SQS
  while True {

    # Receive a batch of messages from the queue
    response = sqs_client.receive_message(
      QueueUrl = QUEUE_URL,
      MaxNumberOfMessages = BATCH_SIZE,
      WaitTimeSeconds = 20 )

    # Loop over the messages in the batch
    entries = []
    i = 1
    for message in response.get('Messages',[]) {
      # Process a single message
      success = handle_message(message)

      # Append the message handle to an array that is later
      # used to delete processed messages
      if success {
        entries.append(
          {
            'Id': f'index{i}',
            'ReceiptHandle': message['receiptHandle']
          }
        )
        i += 1
      }
    }

    # Delete all the processed messages
    if (len(entries) > 0) {
      sqs_client.delete_message_batch(
        QueueUrl = QUEUE_URL,
        Entries = entries
      )
    }
  }
}
JavaScript

Burada dikkat edilmesi gereken bir diğer nokta da Lambda hedefleridir. İşleviniz eşzamansız olarak çağrıldıysa ve işleviniz için bir hedef yapılandırdıysanız, bunu arayüz koduna eklemeniz gerekir. Herhangi bir iş mantığı hatasını yakalaması ve buna bağlı olarak doğru hedefi çağırması gerekecektir.

Container olarak paket işlevleri

Lambda, işlevleri zip dosyaları ve container görüntüleri gibi paketlemeyi destekler. Taşınabilir kod geliştirmek için, varsayılan paketleme yönteminiz olarak container görüntüleri kullanmanızı öneririz. İşlevi bir container görüntüsü olarak paketleseniz bile Amazon Elastic Container Service (Amazon ECS) veya Amazon Elastic Kubernetes Service (EKS) gibi diğer kapsayıcı platformlarında çalıştıramazsınız. Ancak, bu şekilde paketlerseniz, daha sonra aynı araçları kullandığınız ve minimum değişiklik gerektiren bir Dockerfile oluşturduğunuz için container’lara geçiş daha kolay olacaktır.

Lambda için örnek bir Dockerfile:

FROM public.ecr.aws/lambda/python:3.9
COPY *.py requirements.txt ./
RUN python3.9 -m pip install -r requirements.txt -t .
CMD ["app.lambda_handler"]
Bash

Daha sonra container’lara geçerseniz, farklı bir temel görüntü kullanmak için Dockerfile’ı değiştirmeniz ve uygulamanın nasıl başlatılacağını tanımlayan CMD satırını uyarlamanız gerekir. Bu, önceki bölümde açıklanan kod değişikliklerine ek olarak yapılır.

Container için ilgili Dockerfile aşağıdakine benzer olacaktır:

FROM python:3.9
COPY *.py requirements.txt ./
RUN python3.9 -m pip install -r requirements.txt -t .
CMD ["python", "./app.py"]
Bash

Farklı bir hedefe dağıttığımız için dağıtım işlem hattının da değişmesi gerekir. Ancak, yapıtları (artifacts) oluşturma süreci aynıdır.

Sunucu başına tek çağrı

Lambda işlevleri, kendi yalıtılmış çalışma zamanı ortamlarında çalışır. Her ortam, Lambda için harika olan tek seferde tek bir isteği işleme yöntemini kullanır. Ancak, uygulamanızı kapsayıcılara taşırsanız, iş mantığını aynı anda tek bir işlemde birden çok iş parçacığından çağırırsınız.

Bu bölüm, aynı süreçte tek bir çağrıdan birden fazla eşzamanlı çağrıya geçmenin yönlerini tartışır.

Statik değişkenler

Statik değişkenler, bir kez başlatılan ve ardından birden çok çağrıda yeniden kullanılan değişkenlerdir. Bu tür değişkenler için veritabanı bağlantıları veya yapılandırma bilgileri örnek verilebilir.

İşlev optimizasyonu için ve özellikle ilk başlatma ve sıcak işlev çağrılarının süresini azaltmak için, tüm statik değişkenleri işlev işleyicinin dışında başlatmanızı ve bunları daha sonraki fonksiyon çağrımlarında yeniden kullanılması için genel değişkenlerde saklamanızı öneririz.

İş mantığı modülünün bir parçası olarak yazdığınız ve işleyicinin dışından çağırdığınız bir başlatma işlevini kullanmanızı öneririz. Bu işlev, iş mantığı kodunun çağrılar arasında yeniden kullandığı genel değişkenlerdeki bilgileri kaydeder.

Aşağıdaki sözde kod, Lambda işlevini gösterir:

import business_logic

# Call the initialization code
initialize()

handler(event, context) {
  ...
  # Call the business logic
  ...
}
JavaScript

Ve iş mantığı kodu da şu şekildedir:

# Global variables used to store static data
var config

initialize() {
  config = read_Config()
}

do_some_logic(data) {
  # Do something with config object
  ...
}
JavaScript

Aynı durum container’lar için de geçerlidir. Statik değişkenleri genellikle her bir istek için değil, süreç başladığında başlatırsınız. Container’lara taşırken tek yapmanız gereken, ana uygulama döngüsünü başlatmadan önce başlatma işlevini çağırmaktır.

import business_logic

# Call the initialization code
initialize()

main() {
  while True {
    ...
    # Call the business logic
    ...
  }
}
JavaScript

Gördüğünüz gibi iş mantığı kodunda herhangi bir değişiklik bulunmamaktadır.

Veritabanı bağlantıları

Lambda işlevleri, çalışma zamanı ortamları arasında hiçbir şey paylaşmadığından, container’ların aksine, ilişkisel bir veritabanına bağlanırken bağlantı havuzlarına güvenemezler. Bu nedenle, birçok işlev tarafından kullanılan merkezi bir bağlantı havuzu görevi gören Amazon RDS Proxy‘yi oluşturduk.

Taşınabilir Lambda işlevleri yazmak için, tek bağlantılı bir bağlantı havuzu nesnesi kullanmanızı öneririz. İş mantığı kodunuz, bir veritabanı isteğinde bulunurken her zaman havuzdan bir bağlantı isteyecektir. Yine de RDS Proxy kullanmanız gerekecektir.

Daha sonra container’lara geçerseniz, havuzdaki bağlantı sayısını bundan başka herhangi bir değişiklik yapmadan artırabilirsiniz ve uygulama veritabanına yük oluşturmadan ölçeklenir.

Dosya Sistemi

Lambda işlevleri, 512 MB ila 10 GB boyutunda yazılabilir bir /tmp dizini ile birlikte gelir. Her işlev örneği yalıtılmış bir çalışma zamanı ortamında çalıştığından, geliştiriciler genellikle o klasörde depolanan dosyalar için sabit dosya adları kullanır. Aynı iş mantığı kodunu birden çok iş parçacığındaki bir container’da çalıştırırsanız, farklı iş parçacıkları diğerleri tarafından oluşturulan dosyaların üzerine yazacaktır.

Bunu engellemek için her çağrıda benzersiz dosya adları kullanmanızı öneririz. Dosya adına bir UUID veya başka bir rasgele sayı ekleyin. Alanın dolmasını önlemek için, işiniz bittiğinde dosyaları silin.

Bu yöntemle ilerlediğinizde kodunuzu daha sonra container’lara taşırsanız, bu konuda yapacak ekstra bir değişiklik yoktur.

Taşınabilir web uygulamaları

Bir web uygulaması geliştiriyorsanız taşınabilirliği sağlamanın başka bir yolu daha vardır. Web uygulamanızı bir Lambda işlevi içinde barındırmak için AWS Lambda Web Adapter projesini kullanabilirsiniz. Bu şekilde, bilinen çerçevelerle (örn. Express.js, Next.js, Flask, Spring Boot, Laravel veya HTTP 1.1/1.0 kullanan herhangi bir çerçeve) bir web uygulaması geliştirebilir ve Lambda’da çalıştırabilirsiniz. Web uygulamanızı bir container olarak paketlerseniz, aynı Docker görüntüsü Lambda (web bağdaştırıcısı kullanılarak) ve container’larda çalışabilir.

Container’lardan Lambda’ya taşıma

Bu blog gönderisi, container’lara kolayca taşıyabileceğiniz taşınabilir Lambda işlevlerinin nasıl geliştirileceğini örneklemektedir. Bu önerilerin dikkate alınması, container’ların Lambda işlevlerine taşımanıza olanak tanıyan genel olarak taşınabilir kod geliştirmenize de yardımcı olabilir.

Dikkate alınması gereken bazı noktalar:

  • Container’da iş mantığını arabirim kodundan ayırın. Arayüz kodu, olay kaynakları ile etkileşime girmeli ve iş mantığını çağırmalıdır.
  • Lambda işlevleri yalnızca /tmp yazılabilir bir dizine sahip olduğundan, bu yaklaşımı (farklı konumlara yazabilseniz bile) container’larınızda da replike edin.

Sonuç

Bu blog gönderisi, kilitlenme riski olmadan sunucusuz bir yaklaşımın avantajlarından yararlanmanıza olanak tanıyan Lambda işlevleri geliştirmeye yönelik en iyi uygulamaları önerir.

İş mantığını Lambda işleyicilerinden ayırma, container olarak işlevleri paketleme, sunucu başına Lambda’nın tek çağrısını işleme gibi en iyi uygulamaları izleyerek taşınabilir Lambda işlevleri geliştirebilirsiniz. Sonuç olarak, daha sonra container’lara taşımayı seçerseniz kodunuzu Lambda’dan container’lara minimum çabayla taşıyabileceksiniz.

Bir sonraki uygulamanızı geliştirirken sunucusuz bir yaklaşımın benimsenmesini kolaylaştırmak için bu en iyi uygulamalara ve kod örneklerine bakın.

Daha fazla sunucusuz öğrenme kaynağı için Serverless Land‘i ziyaret edebilirsiniz.