İçeriğe geç

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

Bir önceki makale serisinde Dapr projesinden ve faydalarından bahsedip, local ortamda self-hosted mode olarak .NET 6 ile iki adet örnek microservice geliştirmiştik. (Eğer ilk seriyi okumadı iseniz, buradan ulaşabilirsiniz.)

Bu makale kapsamında ise bir önceki makalede geliştirmiş olduğumuz örnek ShoppingCart ve Recommendation microservice’lerinin default olan Dapr component’lerini Azure resource’ları ile değiştirerek, Azure Container Apps üzerine deployment işlemini gerçekleştireceğiz.

Öncelikle konuya Azure Container Apps ile ilgili küçük bir giriş yaparak başlayalım.

Azure Container Apps

Azure Container Apps, Microsoft‘un Mayıs 2022’de GA olarak duyurduğu uygulama odaklı olarak geliştirilmiş bir fully managed serverless container runtime’ıdır.

Azure Kubernetes Service üzerinde çalışıyor olup, temelinde KEDA, Envoy ve Dapr gibi güçlü projeler bulunmaktadır. Yani Azure Container Apps kullanarak, tüm Dapr API‘larından managed bir şekilde yararlanabilmekteyiz. Bu sayede bizlere güvenli, modüler ve ölçeklenebilir bir alt yapı sağlayarak, bizlerin daha çok uygulama tarafına odaklanabilmesini sağlamaktadır. Ayrıca KEDA sayesinde bizlere event-driven auto scaling ve zero-scaling sağlayarak cost optimizasyonu yapabilmemize de olanak tanımaktadır.

Bildiğimiz gibi kubernetes tarafında HPA, CPU ve memory gibi resource metriclerine dayanarak horizontally auto scaling sağlamaktadır. KEDA ile ise birden çok metriği baz alarak horizontally auto scaling gerçekleştirebilmekteyiz. KEDA hakkında bilgi edinebilmek için ise, daha önce burada ele almış olduğum makaleye bir göz atabilirsiniz.

Eğer sizlerde benim gibi aktif olarak uygulamalarınızı kubernetes ortamında manage ediyorsanız, güzel noktalarını es geçersek, infrastructure management ve configuration kısmının uğraştırıcı olduğunu biliyorsunuzdur. Azure Container Apps ise kubernetes tarafındaki complexity’i ve gereken learning curve minimize edilerek, bizlere microservice-based uygulamalarımızı kolayca build ve host edebileceğimiz fully managed bir ortam sağlamaktadır.

Azure Container Apps‘i bir kubernetes pod’u gibi düşünebiliriz aslında. Her bir Azure Container App içerisinde aynı life-cycle’ı paylaşan birden çok container barındırılabilmektedir. Ayrıca revision’lar ile de versioning sağlanmaktadır. Kısacası bir container app deploy ettiğimizde, otomatik olarak bir revision oluşturulmaktadır. Ayrıca herhangi bir değişiklik veya update işlemi gerçekleştirdiğimizde de revision’lar oluşturulmaktadır. Oluşturulan revision’lar ise immutable’dır. Birden çok farklı revision’ı aynı anda çalıştırabilmek ve trafiği istediğimiz gibi aralarında bölebilmek de mümkündür. Default olarak ise single revision mode’unda çalışmaktadır.

Ayrıca monitoring ve observability ise Log Analytics aracılığıyla kolay bir şekilde gerçekleştirilebilmektedir. Özetle Azure Container Apps kullanarak cloud infra management veya container orchestration gibi konulara kafa yormadan, microservice-based uygulamalarımızı kolay bir şekilde build ve host edebiliriz.

Component’ler ile Başlayalım

Bir önceki makalede state-store ve pub/sub için default gelen component’leri kullanmıştık. Dapr‘ın pluggable bir yapıya sahip olduğundan ve component’leri herhangi bir kod değişikliği yapmadan bir başka component ile değiştirebilmeceğimizden de bahsetmiştik.

Şimdi ise state-store için Azure Cosmos DB‘yi, pub/sub için ise Azure Service Bus‘ı kullanacağız. Azure Service Bus‘ı oluşturabilmek için burayı, Azure Cosmos DB‘yi oluşturabilmek için ise burayı takip edebilirsiniz. Azure Cosmos DB‘yi oluştururken “NoSQL API’” seçeneğini seçelim ve ardından “DaprShop” database’ini ve “ShoppingCart” container’ını da oluşturalım. Ayrıca partition key olarak ise “/id” seçtiğimizden emin olalım. Ayrıca Dapr‘ın pub/sub component’i için topic’lere ihtiyacımız olacağı için, Azure Service Bus‘ı oluştururken ise tier olarak “standard” ı seçmemiz gerekmektedir.

Azure service’lerini oluşturduktan sonra öncelikle örnek projenin root klasörü olan “DaprShop” klasörü altında “Components” adında bir yeni klasör oluşturalım. Bu klasör içerisinde microservice’lerimiz özelinde ihtiyacımız olan component’leri tanımlayacağız.

İlk olarak pub/sub component’i ile başlayalım. Bunun için klasör içerisinde “pubsub.yaml” adında aşağıdaki gibi bir dosya oluşturalım.

componentType: pubsub.azure.servicebus
version: v1
metadata:
  - name: connectionString
    secretRef: connection-string-key
secrets:
- name: connection-string-key
  value: "Endpoint=sb://dapr-poc-sbus.servicebus.windows.net/;SharedAccessKeyName=YOUR_SHARED_ACCESS_KEU_NAME;SharedAccessKey=YOUR_SHARED_ACCESS_KEY"

Eğer Dapr component schema’sını bir önceki makaleden hala hatırlıyorsak, bu sefer schema’nın biraz daha sade olduğunu fark etmişizdir. Azure Container Apps daha sadeleştirilmiş bir component schema’sı kullanmaktadır.

NOT: Schema ile ilgili daha detaylı kullanım bilgilerine ise, buradan erişebilirsiniz.

Oluşturmuş olduğumuz bu pub/sub component’i içerisinde tek güncellememiz gereken nokta, Azure Service Bus‘ın “connectionStringKey” bilgisidir.

Ayrıca Azure-hosted resource’lara erişirken dilersek managed identity de kullanabiliriz. Azure-hosted olmayan resource’lar için ise, Dapr secret store component’inden yararlanabilir ve Azure Key Vault integration’ı ile birlikte secret’ları kolay bir şekilde yönetebiliriz. Böylece secret bilgilerini component içerisinde açık bir şekilde tanımlamamıza gerek kalmayacaktır.

Şimdi ise “statestore.yaml” adında bir dosya oluşturalım.

componentType: state.azure.cosmosdb
version: v1
metadata:
- name: url
  value: https://dapr-poc-cosmosstate.documents.azure.com:443/
- name: masterkey
  secretRef: cosmos-master-key
- name: database
  value: DaprShop
- name: collection
  value: ShoppingCart
secrets:
- name: cosmos-master-key
  value: "YOUR_COSMOS_MASTER_KEY"

Burada ise oluşturmuş olduğumuz Azure Cosmos DB‘nin ilgili “url“, “masterKey“, “database” ve “collection” gibi bilgilerini belirtmemiz, state-store olarak kullanabilmemiz için yeterli olacaktır.

Gördüğümüz gibi farklı component’leri tanımlayabilmek bu kadar kolay. Ayrıca component’ler özelinde “scope” lar belirleyebilmek de mümkündür. Böylece Dapr sidecar sadece ilgili scope’daki container app’ler için ilgili component’leri load etmektedir. Bu işlemi ise aşağıdaki şekilde gerçekleştirebiliriz.

scopes:
  - [DAPR-APP-ID-1]
  - [DAPR-APP-ID-2]

Scope belirtilmediğinde ise aynı environment içerisindeki Azure Container App’ler deploy edilmiş tüm component’leri default olarak load etmektedir.

Şimdi Azure Container Apps environment ve Azure Container App oluşturma işlemlerine geçebiliriz ve ardından oluşturmuş olduğumuz component’lerin ve microservice’lerimizin deployment işlemlerini gerçekleştireceğiz.

Azure Container Apps Env ve Container App Oluşturalım

Deployment’a başlamadan önce ilk olarak bir Azure Container Apps environment’ı oluşturmamız gerekmektedir. Azure Container Apps environment’ı birden çok Azure Container App barındıran bir secure boundary olarak düşünebiliriz. Aynı environment içerisine deploy edilen Azure Container App‘ler, aynı virtual network içerisine deploy olmaktadırlar ve aynı Azure Log Analytics workspace’ini paylaşmaktadırlar.

Şimdi öncelikle aşağıdaki gibi CLI üzerinden bir resource grup oluşturalım.

az group create --name daprshop-rg --location "westeurope"

Ardından Azure Container Apps environment’ını oluşturalım.

az containerapp env create \
--name daprshop-env \
--resource-group daprshop-rg \
--location "westeurope"

NOT: Eğer daha önce “containerapp” extension’ına sahip olmadıysanız, yukarıdaki komut öncelikle bu extension’ın kurulumunu gerçekleştirecektir. Dilerseniz bu işlemi öncesinde de gerçekleştirebilirsiniz. “az extension add –name containerapp –upgrade

Şimdi oluşturmuş olduğumuz “daprshop-env” isimli Azure Container Apps environment’ı içerisinde yukarıda tanımlamış olduğumuz pub/sub ve state-store component’lerini configure edelim.

Bunun için CLI üzerinden “DaprShop” klasörü altında bulunan “Components” path’ine gidelim ve aşağıdaki komutları kullanarak ilgili component’leri oluşturmuş olduğumuz environment için configure edelim.

az containerapp env dapr-component set \
--name daprshop-env \
--resource-group daprshop-rg \
--dapr-component-name statestore \
--yaml statestore.yaml
az containerapp env dapr-component set \
--name daprshop-env \
--resource-group daprshop-rg \
--dapr-component-name pubsub \
--yaml pubsub.yaml

Ayrıca daha önce örnek projemiz içerisinde bu component’lere erişirken default olan “statestore” ve “pubsub” isimlerini kullandığımız için, bu environment içerisinde de isimlerini “statestore” ve “pubsub” olarak aynı şekilde koruyoruz.

İlgili env için configure edilen component’lere ister aşağıdaki komutu kullanarak, istersek de Azure portal üzerinden Container Apps Environment resource’una giderek erişebiliriz.

az containerapp env dapr-component list  \
--name "daprshop-env" \
--resource-group "daprshop-rg"

Bu noktaya kadar Azure Container Apps environment’ını kullanmak istediğimiz Dapr component’leri ile birlikte hazırlamış olduk. Şimdi ise geriye sadece örnek projelerimiz olan ShoppingCart ve Recommendation microservice’lerinin Azure Container App‘e deployment işlemleri kaldı.

Deployment işlemine geçmeden önce örnek microservice’lerimizi containerize bir hale getirmemiz ve bir container registry’e push etmemiz gerekmektedir. Ben bu işlemi daha önce oluşturmuş olduğum Azure Container Registry üzerinden gerçekleştireceğim. Eğer sizde bir container registry oluşturmak isterseniz, buraya göz atabilirsiniz.

Ek olarak ShoppingCart ve Recommendation microservice’lerinin “Program.cs” dosyasına gidelim ve “app.Run();” komutu içerisinde daha önce yazdığımız specific portları silelim ve microservice’lerin default port 80 üzerinden çalışabilmelerine izin verelim.

Ayrıca kullanacak olduğum Dockerfile’lar ise aşağıdaki gibidir.

Recommendation.API

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["DaprShop.Recommendation.API/DaprShop.Recommendation.API.csproj", "DaprShop.Recommendation.API/"]
COPY ["DaprShop.Contracts/DaprShop.Contracts.csproj", "DaprShop.Contracts/"]
RUN dotnet restore "DaprShop.Recommendation.API/DaprShop.Recommendation.API.csproj"
COPY . .
WORKDIR "/src/DaprShop.Recommendation.API"
RUN dotnet build "DaprShop.Recommendation.API.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "DaprShop.Recommendation.API.csproj" -c Release -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "DaprShop.Recommendation.API.dll"]

ShoppingCart.API

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["DaprShop.ShoppingCart.API/DaprShop.ShoppingCart.API.csproj", "DaprShop.ShoppingCart.API/"]
COPY ["DaprShop.Contracts/DaprShop.Contracts.csproj", "DaprShop.Contracts/"]
RUN dotnet restore "DaprShop.ShoppingCart.API/DaprShop.ShoppingCart.API.csproj"
COPY . .
WORKDIR "/src/DaprShop.ShoppingCart.API"
RUN dotnet build "DaprShop.ShoppingCart.API.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "DaprShop.ShoppingCart.API.csproj" -c Release -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "DaprShop.ShoppingCart.API.dll"]

Containerization işlemini her iki örnek microservice için gerçekleştirdikten sonra, aşağıdaki gibi sırasıyla Azure Container App ‘e deployment işlemlerini gerçekleştirebiliriz.

az containerapp create \
  --name "recommendation-api"  \
  --resource-group "daprshop-rg" \
  --environment "daprshop-env" \
  --image "YOUR_ACR_NAME.azurecr.io/daprshop.recommendationapi:01" \
  --registry-server "YOUR_ACR_NAME.azurecr.io" \
  --target-port 80 \
  --ingress 'external' \
  --min-replicas 1 \
  --max-replicas 1 \
  --enable-dapr \
  --cpu 0.25 \
  --memory 0.5Gi \
  --dapr-app-id recommendationapi \
  --dapr-app-port 80
  az containerapp create \
  --name "shoppingcart-api"  \
  --resource-group "daprshop-rg" \
  --environment "daprshop-env" \
  --image "YOUR_ACR_NAME.azurecr.io/daprshop.shoppingcartapi:01" \
  --registry-server "YOUR_ACR_NAME.azurecr.io" \
  --target-port 80 \
  --ingress 'external' \
  --min-replicas 1 \
  --max-replicas 1 \
  --enable-dapr \
  --cpu 0.25 \
  --memory 0.5Gi \
  --dapr-app-id shoppingcartapi \
  --dapr-app-port 80

Yukarıdaki parametrelerin bazılarını kısaca açıklamak gerekirse;

  • environment” parametresi ile Azure Container App‘in hangi Azure Container Apps Env içerisinde çalışması gerektiğini belirtiyoruz.
  • registry-server” parametresi ile ise ilgili image’in hangi repository’den çekileceğini belirtiyoruz. Default olarak dockerhub’a bakmaktadır.
  • ingress” microservice’lerimizi dışarıya expose edeceğimiz için external olarak belirtiyoruz. Eğer expose etmeye gerek yoksa, “internal” olarak da set edebiliriz.
  • *-port” parametreleri ile ise microservice’lerimizin dinliyor olduğu port’u belirtiyoruz.
  • Ayrıca “min-replicas” seçeneğini de 0 olarak set edebiliriz.

Bunların dışında kullanabileceğimiz farklı parametreler de bulunmaktadır. Detaylı listeye ise buradan erişebilirsiniz.

Deployment işlemlerinin ardından ilgili microservice’lerin URL bilgilerine ister Azure portal üzerinden istersek de aşağıdaki gibi bir komut ile erişim sağlayabiliriz.

az containerapp show -n shoppingcart-api -g daprshop-rg --query properties.configuration.ingress.fqdn
az containerapp show -n recommendation-api -g daprshop-rg --query properties.configuration.ingress.fqdn

Gördüğümüz gibi Azure Container App ‘e deploy edebilmek işte bu kadar kolay.

Test Edelim

Test edebilmek için bir önceki makalede de yaptığımız gibi yine ShoppingCart API ‘ının Swagger UI‘ına erişelim ve sepet’e bir adet ürün ekleyelim veya aşağıdaki cURL komutunu çalıştıralım.

curl -X 'POST' \
  'https://YOUR_AZURE_CONTAINER_APP_URL/api/shopping-cart/123456/items' \
  -H 'accept: text/plain' \
  -H 'Content-Type: application/json' \
  -d '{
  "productId": "1",
  "productName": "Samsung Z Fold 5",
  "price": 1400,
  "quantity": 1
}'

Ardından Azure portal üzerinden ilgili Azure Cosmos DB collection’ına giderek, ürün’ün ilgili kullanıcı için sepet’e eklendiğini görebiliriz.

Ardından aşağıdaki komut ile de Recommendation API loglarına erişelim.

az containerapp logs show -n recommendation-api -g daprshop-rg --tail 100

En alttaki log mesajından görebildiğimiz gibi ürün’ün sepet’e eklenme işlemi ardından Azure Service Bus üzerine bir event publish edildi ve Recommendation API tarafından başarıyla consume edildi.

Gördüğümüz gibi microservice’lerimiz Azure Container Apps üzerine deployment işlemleri sırasında Dapr component’leri olarak farklı teknolojiler kullanmamıza rağmen, uygulama tarafında herhangi bir değişiklik yapmadık.

Ek Bilgiler

Güncelleme

Ayrıca yeni bir revision oluşturacağımızda ise tek yapmamız gereken, aşağıdaki update komutu ile birlikte değiştirmek istediğimiz parametreleri set etmek.

az containerapp update \
--name "shoppingcart-api"  \
--resource-group "daprshop-rg" \
--image "YOUR_ACR_NAME.azurecr.io/daprshop.shoppingcartapi:02"

Ek olarak “revision-suffix” parametresini de kullanarak, revision isimlerini istediğimiz gibi customize edebiliriz. Default olarak unique bir revision name oluşturulmaktadır.

Monitoring & Observability

Log’lar ise daha önce bahsettiğimiz gibi Azure Monitor Log Analytics üzeride store edilmektedir. Azure Container App environment’ını oluştururken eğer daha önceden oluşturuşmuş bir logs workspace id belirtmiyorsak, otomatik olarak bir Log Analytics workspace oluşturulmaktadır. Log’lara ise ilgili Log Analytics içerisinde bulunan custom log’lar sekmesinden erişebilmekteyiz. System log’ları için “ContainerAppSystemlogs_CL“, container log’ları için ise “ContainerAppConsoleLogs_CL” table’larına bakmamız gerekmektedir.

Ayrıca CLI üzerinden de container app ve environment log’larına erişebilmekteyiz.

az containerapp logs show -n YOUR_CONTAINERAPP_NAME -g YOUR_RG_NAME --tail 100
az containerapp env logs show -n YOUR_ENV_NAME -g YOUR_RG_NAME

Performans monitoring için ise Azure Container Apps environment’ını oluştururken “–dapr-instrumentation-key” parametresini set etmemiz gerekmektedir. Bu şekilde Dapr, service-to-service communication telemetry verilerini Azure Application Insight‘a aktarabilmektedir.

az containerapp env create \
--name daprshop-env \
--resource-group daprshop-rg \
--location "westeurope" \
--dapr-instrumentation-key YOUR_APP_INSIGHT_INSTRUMENTATION_KEY

Özellikle Application Insight‘ın “Transaction search” ve “Application map” özelliğini kullanarak, uygulamalarımız arasındaki performans sorunlarını veya hatalı noktaları kolaylıkla adresleyebiliriz.

Storage Mount

Bunların dışında bir storage mount etmek istiyorsak da, kubernetes tarafında da olduğu gibi bu işlemi Azure Container Apps ile de gerçekleştirebiliriz. Örneğin bir Azure Files share mount edebilmek için Azure Container Apps environment’ını oluşturduktan sonra ilk olarak ilgili Azure Files‘ı oluşturmuş olduğumuz environment içerisinde aşağıdaki gibi link’lememiz gerekmektedir. Bunu işlemi kubernetes tarafında bir storage class eklemek gibi düşünebiliriz sanırım.

az containerapp env storage set --name YOUR_ENV_NAME --resource-group YOUR_RG_NAME \
    --storage-name YOUR_STORAGE_NAME \
    --azure-file-account-name YOUR_STORAGE_ACCOUNT_NAME \
    --azure-file-account-key YOUR_STORAGE_ACCOUNT_KEY \
    --azure-file-share-name YOUR_FILESHARE_NAME \
    --access-mode ReadWrite

Ardından tanımlamış olduğumuz bu storage account’u mount etmek için ilgili Azure Container App içerisinde bir volume olarak tanımlamamız gerekiyor. Tıpkı kubernetes pod’ları için yaptığımız gibi.

Bu işlemi gerçekleştirebilmek için ise öncelikle deploy etmiş olduğumuz ilgili Azure Container App‘in YAML file’ını CLI üzerinden download edip, ardından gerekli düzenlemeleri yaptıktan sonra update etmemiz gerekmektedir.

Örneğin ShoppingCart API ‘ın configuration bilgilerini YAML file olarak export edelim.

az containerapp show \
  --name shoppingcart-api \
  --resource-group daprshop-rg \
  --output yaml > app.yaml

NOT: Configuration file’ı içerisinde “secrets” section’ı varsa, bu kısmı silelim. Aksi takdirde mevcut secretler sürece override edilecektir.

Bu configuration file’ı içerisinde ise volume ve volume mount tanımlama işlemini “template” section’ı altında aşağıdaki gibi gerçekleştirebiliriz.

template:
  containers:
  - image: lodoonl.azurecr.io/daprshop.shoppingcartapi:005
    name: shoppingcart-api
    volumeMounts:
    - volumeName: my-daprshop-storage
      mountPath: /mytodofolder
    resources:
      cpu: 0.25
      ephemeralStorage: 1Gi
      memory: 0.5Gi
  initContainers: null
  revisionSuffix: ''
  scale:
    maxReplicas: 1
    minReplicas: 1
    rules: null
  volumes:
  - name: my-daprshop-storage
    storageName: YOUR_STORAGE_NAME
    storageType: AzureFile

Düzenlemelerin ardından ilgili Azure Container App ‘i bu configuration file’ını kullanarak aşağıdaki gibi update etmemiz gerekmektedir.

az containerapp update \
  --name shoppingcart-api \
  --resource-group daprshop-rg \
  --yaml app.yaml \
  --output table

Umarım bu işlemleri ilerleyen dönemlerde bir kaç parametre set ederek kolay bir şekilde halledebileceğimiz Az CLI API‘ları sağlanır. Fakat şimdilik Az CLI ile bu işlemleri bu şekilde manuel olarak gerçekleştirmemiz gerekmektedir. Neyseki ARM template kullanarak deployment anında bu işlemleri gerçekleştirebilmekteyiz. ARM template detayları için ise buraya bir göz atabilirsiniz.

Son Sözler

Gördüğümüz gibi Dapr ile uyumlu bir şekilde çalışan Azure Container Apps, bizlere eforsuz bir şekilde microservice-based uygulamalarımızı build ve host edebilme imkanı sağlamaktadır. Dapr dışında arka planında bulundurduğu KEDA ve Envoy gibi güçlü projeler sayesinde de event-driven auto scaling gibi zengin yetenekleri de bizlere sunmaktadır. Fully managed bir service olması sayesinde de bizleri infrastructure/cluster management ve configuration’ı gibi işlere harcayacağımız vakitten kurtarmaktadır.

Özellikle sektörün cloud’a ve cloud-native uygulama geliştirmelerine doğru hızla kaydığı bu son bir kaç yılda, kurumların daha hızlı markete atılabilmeleri ve müşterilerine daha hızlı value üretebilmeleri açısından optimize edilmiş best practice’ler içeren fully managed bir service’i kullanmaları, oldukça avantajlı olacaktır.

References

https://learn.microsoft.com/en-us/azure/container-apps/get-started?tabs=bash
https://learn.microsoft.com/en-us/azure/container-apps/storage-mounts?pivots=aca-cli
https://learn.microsoft.com/en-us/azure/architecture/example-scenario/serverless/microservices-with-container-apps-dapr
https://docs.dapr.io/operations/monitoring/tracing/otel-collector/open-telemetry-collector-appinsights/

Kategori:.NET.NET CoreAzureContainerizingMessagingMicroservices

İlk Yorumu Siz Yapın

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Bu site, istenmeyenleri azaltmak için Akismet kullanıyor. Yorum verilerinizin nasıl işlendiği hakkında daha fazla bilgi edinin.