Skip to main content
  1. Posts/

Auto Shutdown Azure Container Apps

·4 mins·
azure azure
Table of Contents

Dalam banyak skenario, Anda mungkin ingin mengotomatiskan penghentian (auto-stop) Container Apps untuk mengurangi biaya atau mengoptimalkan penggunaan resource, terutama jika aplikasi hanya diperlukan pada waktu-waktu tertentu.

Terdapat dua pendekatan utama untuk melakukan Auto-Stop Container Apps:

  1. Function App Menjalankan kode PowerShell untuk menghentikan Container App berdasarkan jadwal tertentu menggunakan Timer Trigger.

  2. Time-Based Scaling dengan KEDA Mengonfigurasi KEDA agar melakukan scale down ke 0 replika di luar jam operasional.

Function App
#

1. Buat Resource Group
#

az group create \
  --name <resourceGroupName> \
  --location <REGION>

2. Buat Storage Account
#

az storage account create \
  --name <STORAGE_NAME> \
  --resource-group <resourceGroupName> \
  --location <REGION> \
  --sku Standard_RAGRS \
  --kind StorageV2 \
  --min-tls-version TLS1_2 \
  --allow-shared-key-access true

3. Buat User Assigned Managed Identity dan Tambahkan Role
#

Tambahkan role berikut ke identity:

  • Storage Blob Data Owner
  • Contributor
output=$(az identity create \
  --name "demo-app" \
  --resource-group <resourceGroupName> \
  --location <REGION> \
  --query "{userId:id, principalId: principalId, clientId: clientId}" -o json)

userId=$(echo $output | jq -r '.userId')
principalId=$(echo $output | jq -r '.principalId')
clientId=$(echo $output | jq -r '.clientId')

storageId=$(az storage account show \
  --resource-group <resourceGroupName> \
  --name <STORAGE_NAME> \
  --query 'id' -o tsv)

TARGET_RESOURCE_GROUP=$(az group show \
  --name <resourceGroupName> \
  --query 'id' -o tsv)

az role assignment create \
  --assignee-object-id $principalId \
  --assignee-principal-type ServicePrincipal \
  --role "Storage Blob Data Owner" \
  --scope $storageId

az role assignment create \
  --assignee-object-id $principalId \
  --assignee-principal-type ServicePrincipal \
  --role "Contributor" \
  --scope $TARGET_RESOURCE_GROUP

4. Buat Function App (Consumption Plan)
#

IDENTITY_ID=$(az identity show \
  --name demo-app \
  --resource-group <resourceGroupName> \
  --query 'id' -o tsv)

az functionapp create \
  --resource-group <resourceGroupName> \
  --consumption-plan-location <REGION> \
  --runtime powershell \
  --functions-version 4 \
  --os-type Windows \
  --name <APP_NAME> \
  --storage-account <STORAGE_NAME> \
  --assign-identity $IDENTITY_ID

5. Konfigurasi Environment Variables pada Function App
#

az functionapp config appsettings set \
  --name <APP_NAME> \
  --resource-group <resourceGroupName> \
  --settings \
    CONTAINER_APP_NAME=<TARGET_CONTAINER_APP> \
    RESOURCE_GROUP=<TARGET_RESOURCE_GROUP> \
    AZURE_CLIENT_ID=$(az identity show --name demo-app --resource-group <resourceGroupName> --query clientId -o tsv) \
    SUBSCRIPTION_ID=$(az account show --query id -o tsv) \
    AzureFunctionsJobHost__logging__console__isEnabled=true

6. Inisialisasi Proyek Function
#

Buat folder proyek lalu jalankan func init:

mkdir stop-container-app-function && cd stop-container-app-function
func init --worker-runtime powershell

Buat function dengan template TimerTrigger:

func new --name StopContainerApp --template "TimerTrigger"

7. Update File ./StopContainerApp/run.ps1
#

param($Timer)

$ErrorActionPreference = "Stop"

# 🔍 Load environment variables secara aman
$clientId         = [System.Environment]::GetEnvironmentVariable('AZURE_CLIENT_ID')
$containerAppName = [System.Environment]::GetEnvironmentVariable('CONTAINER_APP_NAME')
$resourceGroup    = [System.Environment]::GetEnvironmentVariable('RESOURCE_GROUP')
$subscriptionId   = [System.Environment]::GetEnvironmentVariable('SUBSCRIPTION_ID')

# ✅ Validasi environment variables
$requiredVars = @{
    AZURE_CLIENT_ID     = $clientId
    CONTAINER_APP_NAME  = $containerAppName
    RESOURCE_GROUP      = $resourceGroup
    SUBSCRIPTION_ID     = $subscriptionId
}

foreach ($key in $requiredVars.Keys) {
    if (-not $requiredVars[$key]) {
        throw "Environment variable '$key' tidak di-set"
    }
}

try {
    Write-Information "🔐 Autentikasi menggunakan Managed Identity..."
    Connect-AzAccount -Identity -AccountId $clientId

    Write-Information "🔎 Mengecek status Container App: $containerAppName"

    $currentState = Get-AzContainerAppRevision -ContainerAppName $containerAppName `
                                       -ResourceGroupName $resourceGroup `
                                       -SubscriptionId $subscriptionId

    if ($null -eq $currentState) {
        throw "Container App '$containerAppName' tidak ditemukan di resource group '$resourceGroup'"
    }

    $runningState = $($currentState.RunningState)
    Write-Information "đŸ“Ļ Status saat ini: $runningState"

    # Debugging output
    # Write-Information "📋 Dumping Get-AzContainerAppRevision output:"
    # $currentState | ConvertTo-Json -Depth 10

    if ($runningState -eq 'Running') {
        Write-Information "🛑 Menghentikan Container App: $containerAppName"
        Stop-AzContainerApp -Name $containerAppName `
                            -ResourceGroupName $resourceGroup `
                            -SubscriptionId $subscriptionId
        Write-Information "✅ Container App berhasil dihentikan"
    } else {
        Write-Information "â„šī¸ Container App sudah dalam keadaan '$runningState'"
    }

    Write-Information "⏰ Function dijalankan pada: $($Timer.ScheduleStatus.Last)"

} catch {
    Write-Error "❌ Terjadi error: $($_.Exception.Message)"
    throw
}

8. Update requirements.psd1
#

@{
    'Az.App'      = '2.0.0'
    'Az.Accounts' = '5.0.0'
}

9. Update Jadwal Timer pada ./StopContainerApp/function.json
#

{
  "bindings": [
    {
      "name": "Timer",
      "type": "timerTrigger",
      "direction": "in",
      "schedule": "0 0 17 * * *"
    }
  ]
}

10. Deploy Function App
#

func azure functionapp publish <APP_NAME>

Time-Based Scaling
#

Konfigurasi KEDA Cron Scaler agar Container App melakukan scale down ke 0 replika di luar jam kerja.

# Scale down setelah jam kerja (17:00–23:59)
az containerapp update \
  --name demo-app \
  --resource-group <NAMA_RESOURCE_GROUP> \
  --scale-rule-name "afterhours" \
  --scale-rule-type "cron" \
  --scale-rule-metadata \
    "timezone=Asia/Jakarta" \
    "start=0 0 17 * * *" \
    "end=59 59 23 * * *" \
    "desiredReplicas=0"

# Scale down pada dini hari (00:00–08:59)
az containerapp update \
  --name demo-app \
  --resource-group <NAMA_RESOURCE_GROUP> \
  --scale-rule-name "earlyhours" \
  --scale-rule-type "cron" \
  --scale-rule-metadata \
    "timezone=Asia/Jakarta" \
    "start=0 0 0 * * *" \
    "end=59 59 8 * * *" \
    "desiredReplicas=0"

Referensi:

Related

Deploy Azure Function App
·3 mins
azure azure
Creating Azure Container Apps
·6 mins
azure azure
Implementasi Autoscaling VM di Azure
·4 mins
azure azure
Azure CLI Cheat sheet
·7 mins
azure azure
Implementasi Traefik dengan CrowdSec
·3 mins
traefik traefik crowdsec
Mengubah Tanggal Modifikasi File di Linux
·1 min
linux linux