Azure Kubernetes Service üzerinde Azure Files Kullanarak Persistent Volume’ler ile Çalışmak

Cloud-native olarak geliştirdiğimiz uygulamalarımızın zaman zaman bir storage üzerinde bir takım data’lara erişebilmeleri, paylaşabilmeleri veya depolayabilmeleri gibi farklı ihtiyaçları olabilmektedir.

Bildiğimiz gibi containerize olarak Kubernetes üzerinde host ettiğimiz uygulamalarımız, doğası gereği ephemeral(kısa ömürlü) ve stateless‘dır. Yani persistent bir storage çözümü kullanmadan container içerisinde depolayacağımız herhangi bir data, container silinene kadar hayatta kalacaktır.

Bu makale kapsamında ise Azure‘un bizlere fully managed olarak sunduğu File shares hizmeti ile bir çok container tarafından erişilebilir persistent bir volume nasıl oluşturabileceğimize ve container’larımıza bu volume’ü nasıl mount edebileceğimize kısaca değineceğiz. Ayrıca oluşturacak olduğumuz persistent volume’ün, dışarıdan gelebilecek potansiyel güvenlik tehdit’lerine karşı nasıl güvenli bir hale getirilebileceğinden de kısaca bahsedeceğiz.

Başlamadan

Bu makale için aşağıdaki ortam ve tool’lar gerekmektedir.

  • Azure Kubernetes Service
  • Azure CLI
  • Kubectl

Kubernetes Volumes

Kubernetes data kaybı gibi bir çok problemleri çözen iyi bir volume abstraction model’ine ve farklı volume type’larını kullanabilmemize olanak sağlayan bir yapıya sahiptir. Bu esnek yapısı ile pod’lar içerisinde farklı type’lara sahip volume’leri aynı anda kullanabilmekteyiz.

Volume’leri Kubernetes içerisinde Ephemeral ve Persistent olarak kategorize edebiliriz. Ephemeral’ın yaşam döngüsü bir pod’a bağlıyken, persistent’ın yaşam döngüsü ise herhangi bir pod’a bağlı değildir. Ek olarak Kubernetes volume’leri oluşturulurken statically olarak bir cluster administrator’ı tarafından veya dynamically olarak Kubernetes API‘ı tarafından oluşturulabilmektedir.

Bu makale kapsamında ise PersistentVolume ve PersistentVolumeClaim resource’larını kullanarak statically olarak oluşturulmuş Azure File shares hizmetini bir pod’a nasıl mount edebileceğimize bakacağız.

Azure File Share’i Oluşturalım

Azure Files bizlere SMB veya NFS protokolleri üzerinden erişilebilen, fully managed bir file share hizmeti sunmaktadır. Özellikle bir cloud ortamına migration sürecinde isek, Azure Files‘ın on-prem ortamlarımızda da çalışabilme esnekliği veya SMB protokolü üzerinden uygulamalarımıza mount edilebilmesi sayesinde uygulamalarımızın kolay bir şekilde cloud ortamına migrate olabilme süreçlerini kolaylaştırmaktadır.

Azure File shares hizmeti için öncelikle bir storage account oluşturmamız gerekmektedir. Ardından File shares‘i oluşturabileceğiz. Bu işlemleri Azure CLI üzerinden aşağıdaki gibi gerçekleştirelim.

az storage account create -n STORAGE_ACCOUNT_NAME -g RESOURCE_GROUP -l LOCATION --sku Standard_LRS
Ben bu noktada örnek olması açısından “Standard_LRSSKU‘sunu kullandım. Eğer AKS hizmetini yüksek erişilebilirlik açısından Availability Zones ile kullanıyorsanız, storage account’u oluştururken “Locally Redundant” yerine “Zone RedundantSKU‘ları tercih etmenizi öneririm. Aksi bir durumda resoruce’lar farklı zone’lar üzerinde available olabileceğinden dolayı, volume mount işlemleri sırasında problemlerle karşılaşabilirsiniz. Ek olarak production workload’ları için de, SSD destekli “Premium” seçeneklere yönelebilirsiniz.
Storage account’u istediğimiz bir isim ile oluşturduktan sonra, File shares hizmetini oluşturabilmek için connection string’ini export edelim ve ardından File shares hizmetini oluşturalım.
export AZURE_STORAGE_CONNECTION_STRING=$(az storage account show-connection-string -n STORAGE_ACCOUNT_NAME -g RESOURCE_GROUP -o tsv)

az storage share create -n STORAGE_ACCOUNT_NAME --connection-string $AZURE_STORAGE_CONNECTION_STRING
İşlemleri tamamladıktan sonra Azure Portal üzerinden oluşturmuş olduğunuz storage account’u ve File shares hizmetini kontrol edebiliriz.

Microsoft Defender for Storage

Şimdi ise oluşturmuş olduğumuz File shares hizmetini, dışarıdan gelebilecek tehdit’lere, istenmeyen erişimlere karşı Microsoft Defender ile nasıl güvenli bir hale getirebileceğimize hızlıca bir bakalım.

Microsoft Defender, hem Azure-native hem de hybrid ortamlarımızdaki güvenlik tehditlerine karşı bir korumaya sahip olabileceğimiz gelişmiş bir tehdit koruma tool’udur. Biz oluşturmuş olduğumuz File shares hizmetini koruma altına alabilmek için ise, Microsoft Defender’ın storage için olan native akıllı güvenlik katmanından yararlanacağız.

Kısaca bu akıllı katman, bir güvenlik uzmanı yeteneklerine sahip olmadan dışarıdan gelebilecek potansiyel tehdit’lere karşı bir korumaya sahip olmamızı sağlamaktadır. Bu işlemi ise Azure Blob Storage ve Azure Files hizmeti tarafından üretilen telemetry verilerini sürekli analyze ederek gerçekleştirmektedir. Herhangi bir güvenlik tehdit’i tespit ettiğinde ise, portal üzerinde güvenlik uyarıları oluşturmaktadır.
Microsoft Defender for Storage‘ı ister subscription seviyesinde, istersek de seçeceğimiz resource’lar seviyesinde etkinleştirebilmek mümkündür. Şimdi aşağıdaki komutları kullanarak, oluşturmuş olduğumuz storage account özelinde gelişmiş güvenlik korumasını etkinleştirelim.
az security atp storage update --resource-group RESOURCE_GROUP --storage-account STORAGE_ACCOUNT_NAME --is-enabled true
Gördüğümüz gibi “mydocumentstrg” adında oluşturmuş olduğum storage account için gelişmiş güvenlik koruması etkinleştirilmiş durumda.
Microsoft Defender for Storage‘ın sevdiğim tarafı, portal üzerinden ilgili storage account’un “Security” sekmesine girdiğimizde, o storage account’un genel bir güvenlik durumu hakkında hızlı bir şekilde bilgi sahibi olabiliyor veya nasıl daha güvenli bir hale getirebileceğimiz hakkındaki önerilere erişebiliyoruz. Ayrıca integration’lar oluşturarak herhangi bir uyarı karşısında farklı aksiyonlar da gerçekleştirebilmek mümkündür.

Zararlı içeriklere karşı koruma açısından hoşlanmadığım yönü ise, upload edilen her bir dosyayı tek tek taramamasıdır. Onun yerine Azure Blob Storage ve Azure Files hizmetleri tarafından üretilen telemetry verilerini analyze ederek, bilinen virüs veya trojan türleri için hash scanning yapmaktadır. Ayrıca SMB protokolü üzerinden yapılan upload işlemlerini de şimdilik desteklememektedir.

PersistentVolume ve PersistentVolumeClaim ile Çalışmak

PersistentVolume, normal bir volume’ün aksine herhangi bir pod’dan bağımsız olarak kendi lifecycle’ına sahip olan bir Kubernetes storage resource’udur. PersistentVolumeClaim ise bir pod’a PersistentVolume‘ün mount edilebilmesi için kullanılmaktadır.

Kısacası bir uygulamanın persistent bir storage’a ihtiyacı olduğunda, ilgili kullanıcı PersistentVolumeClaim‘i kullanarak arka plandaki storage hakkında detaylı bir bilgi sahibi olmadan ilgili uygulama için bir storage request edebilmektedir.

İlk olarak Kubernetes oluşturmuş olduğumuz File shares‘e erişebilmek için, onun credential bilgilerine ihtiyaç duymaktadır. Bunun için aşağıdaki gibi Kubernetes cluster’ı içerisinde storage’ın credential bilgilerini içeren, “storage-account” adında bir secret objesi oluşturalım.
STORAGE_KEY=$(az storage account keys list --resource-group RESOURCE_GROUP --account-name mydocumentstrg --query "[0].value" -o tsv)

kubectl create secret generic storage-account --from-literal=azurestorageaccountname=mydocumentstrg --from-literal=azurestorageaccountkey=$STORAGE_KEY

Şimdi aşağıdaki içeriğe sahip “pv-pvc” adında bir yaml dosyası hazırlayalım.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: azurefile
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  azureFile:
    secretName: storage-account
    shareName: mydocumentstrg
    readOnly: false
  persistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: azurefile
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: ""
  resources:
    requests:
      storage: 5Gi

Yukarıda kısaca neler yaptığımıza bir bakalım.

  • PersistentVolume‘ün örnek olarak 5Gi lık bir kapasiteye sahip olduğunu belirttik.
  • Read-write olarak birden fazla node tarafından PersistentVolume‘ün mount edilebilmesi için “accessModes” seçeneğini ise “ReadWriteMany” olarak ayarladık.
  • Storage plugin’i olarak önceden oluşturmuş olduğumuz Azure Files hizmetini kullanacağımız için, File shares hizmetinin “shareName” ve “secretName” bilgilerini de referans olarak gösterdik.
  • Ayrıca “persistentVolumeReclaimPolicy” seçeneğini ise “Retain” olarak belirledik. Böylece PersistentVolumeClaim silindiğinde PersistentVolume ilgili data’lar ile beraber silinmemiş olacak.

Kısacası PersistentVolume ile cluster içerisinde 5Gi lık kapasiteye sahip bir storage resource’u oluşturmuş olacağız.

PersistentVolumeClaim‘i ise bir developer olarak istediğimiz bir pod spec’inde referans olarak göstererek, 5Gi kapasiteye sahip bir persistent storage request etmek için kullanacağız. Ayrıca “storageClassName” satırını boş set ederek, storage’ın dynamically olarak oluşturulmamasını da sağlamış olduk.

Eğer storage account’un static olarak değilde Kubernetes tarafından dynamically olarak oluşturulmasını isteseydik, PersistentVolume resource’u yerine istediğimiz kriterlere sahip bir StorageClass resource’u oluşturmamız ve bu resource’u kullanacak bir PersistentVolumeClaim tanımlamamız gerekecekti.

NOT: Eğer PersistentVolumeClaim‘in önceden oluşturmuş olduğumuz specific bir PersistentVolume ile bind olmasını istiyorsak, “volumeName” attribute’ünü set ederek bu işlemi gerçekleştirebiliriz. Örneğin farklı scalability ve performance tier’larına sahip olan hizmetlere sahip olabiliriz.

Şimdi aşağıdaki komutları terminal üzerinden execute edelim ve oluşturulan resource’lara bir göz atalım.

kubectl apply -f pv-pvc.yaml
kubectl get pv azurefile
kubectl get pvc azurefile
kubectl describe pvc azurefile

Yukarıda da gördüğümüz gibi azurefile PersistentVolumeClaim‘i, azurefile PersistentVolume‘üne başarılı bir şekilde “bound” oldu. Eğer “Used By” kısmına dikkat edersek, henüz herhangi bir pod tarafından referans edilmediği için “none” olduğunu görebiliriz.

Şimdi örnek uygulamamızı deploy etme aşamasına geçebiliriz.

Ben örnek olması açısından .NET 6 ile oluşturmuş olduğum aşağıdaki basit console uygulamasını Kubernetes üzerine deploy edeceğim.

using (StreamWriter writer = File.CreateText("/mnt/azure/mytext.txt"))
{
await writer.WriteLineAsync("hello");
} 
await Task.Delay(TimeSpan.FromHours(1));

Top-level statements ile hızlı bir şekilde küçük ve basitleştirilmiş uygulamalar geliştirebilmek, ne kadar güzel değil mi? Bu basit uygulama ile, “/mnt/azure/” path’i altında basit bir txt dosyası oluşturacağız. Bu path’e ise birazdan File shares hizmeti mount edeceğiz.

Dockerfile dosyası ise aşağıdaki gibidir.

FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base
WORKDIR /app

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

FROM build AS publish
RUN dotnet publish "MyDocumentConsoleApp.csproj" -c Release -o /app/publish

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

Öncelikle aşağıdaki gibi basit bir deployment spec’i tanımlayalım.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mydocumentapp-deployment
  labels:
    app: mydocumentapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mydocumentapp
  template:
    metadata:
      labels:
        app: mydocumentapp
    spec:
      containers:
      - name: mydocumentapp
        image: YOUR_ACR.azurecr.io/mydocumentapp:v1
        volumeMounts:
        - name: azurefileshare
          mountPath: /mnt/azure
      volumes:
      - name: azurefileshare
        persistentVolumeClaim:
          claimName: azurefile

Örnek uygulamamızı deploy edebilmek için tanımlamış olduğumuz bu basit spec’de, “volumes” bölümü altında daha önce oluşturmuş olduğumuz “azurefile” isimli PersistentVolumeClaim‘i kullanarak bir volume request ediyoruz. Ayrıca bu volume’ü container içerisinde mount etmek istediğimiz path’i de belirtiyoruz.

Böylece Kubernetes control plane, bu pod için PersistentVolumeClaim içerisinde tanımlamış olduğumuz kriterlere sahip bir PersistentVolume bulmaya çalışacaktır. Başarıyla uygun bir PersistentVolume bulduğunda ise, PersistentVolumeClaim‘i ilgili volume’a bind edecektir. Aksi bir durumda ise uygun bir PersistentVolume bulunamayacağı için ilgili pod schedule edilemeyecektir.

Şimdi aşağıdaki gibi uygulamayı deploy edelim ve container içerisindeki “/mnt/azure/mytext.txt” dosyanın içeriğini kontrol edelim.

kubectl apply -f ./mydocumentapp-deployment.yaml

kubectl exec -it $(kubectl get pods -l=app=mydocumentapp --output=jsonpath={.items..metadata.name}) -- cat /mnt/azure/mytext.txt

Gördüğümüz gibi uygulamamız mount ettiğimiz File shares hizmeti üzerinde tanımladığımız “mytext.txt” dosyasını başarıyla oluşturmuş. Artık oluşturulan bu dosya, persistent bir şekilde Azure File shares hizmeti üzerinde barındırılmaktadır. İlgili uygulamalar tekrardan schedule veya scale out/down edilse dahi, aynı File shares erişilebilir durumda kalacaktır.

Şimdi oluşturmuş olduğumuz PersistentVolumeClaim‘i aşağıdaki gibi tekrardan describe edelim.

kubectl describe pvc azurefile

Used By” kısmına şimdi baktığımızda ise, bu claim’in oluşturmuş olduğumuz pod tarafından kullanıldığını görebiliriz.

Toparlayalım

Kubernetes içerisindeki pod’ların, doğası gereği kolay silinebilen yani kısa ömürlü olduklarını söyledik. Pod’lar herhangi bir sebepden dolayı içlerindeki local data’lar ile birlikte silinebilir, tekrardan schedule edilebilirler. Bu sebeple pod’lar arasında paylaşılacak veya persistent olarak barındıralacak data’lar için, persistent volume’ler kullanmamız gerekmektedir.

Bu makale kapsamında persistent bir storage’a ihtiyaç duyan uygulamalarımız için, managed olarak önceden oluşturmuş olduğumuz Azure File shares hizmetini PersistentVolume ve PersistentVolumeClaim yaklaşımıyla uygulamalarımıza nasıl mount edebileceğimize kısaca bir göz attık.

PersistentVolume cluster administrator’ı tarafından cluster içerisine bir storage resource’u eklemek için kullanılırken, PersistentVolumeClaim ise bir developer’ın arkaplanda ne olup bittiğini bilmeden soyut bir şekilde uygulamasına persistent bir storage request edebilmesi için kullanılmaktadır.

Referanslar

https://docs.microsoft.com/en-us/azure/storage/files/storage-files-introduction
https://docs.microsoft.com/en-us/azure/defender-for-cloud/defender-for-storage-introduction
https://kubernetes.io/docs/concepts/storage/persistent-volumes/

Gökhan Gökalp

View Comments

Recent Posts

Event-Driven Architecture’larda Conditional Claim-Check Pattern’ı ile Event Boyut Sınırlarının Üstesinden Gelmek

{:en}In today’s technological age, we typically build our application solutions on event-driven architecture in order…

2 ay ago

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.…

9 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