Dapr ve .NET Kullanarak Minimum Efor ile Microservice’ler Geliştirmek – Bölüm 2 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/
Eline sağlık.