LoginSignup
12
6

More than 3 years have passed since last update.

Azure Functions ゼンゼンワカラナイ

Last updated at Posted at 2018-11-30

はじめに

今回はこちらの記事にあるように、AzureVMでFreezeが発生したときは少しの間一時停止するだけだよ問題ないよって言うけど、Freezeイベント前後でサーバー内で通信がちゃんと行われているよねっていうのを確認するためにネットワークのキャプチャをします。

「マイクロソフトのサーバーレスは全て筋肉で解決します」
「サーバーは要らんけど筋肉は必要」
とMSの人が言うのでAzureFunctionsを使ってできないかと探してみたところ

この記事を発見しました。
VMのサーバー内で検知してAzureFunctionsでNetworkWatcherを起動させるという仕組みの肝となる部分はそのままで部分的に環境やバージョンに合わせてアレンジして構築してみようと思います。

環境情報

  • Azure Virtual Machine Scale Set
    • Ubuntu
    • Load BalancerによりPublicIPは1つでポートによる振り分け

サーバー内ではbashのシェルスクリプト
AzureFunctionsはv1でHttp TrrigerをPowershell
で実装していきます。

事前作業

  • 最新バージョンの Azure PowerShellをインストール
  • VMにNetworkWatcherAgentの拡張機能を導入
  • 下記コマンドでNetwork Watcher のインスタンスを作成(VMの配置先リージョンに作成する)
    • New-AzureRmNetworkWatcher -Name “適当な名前" -ResourceGroupName “ForPacketCapture" -Location "VMと同じとこ"
  • Capture配置先Storageの作成(Functions用Storageと兼用でも可)

リソース作成

AzureFunctionsのサービスを作成していきます

Azure Portal で、[リソースの作成] > [コンピューティング] > [Function App] の順に選択します。

image.png

[Function App] ブレードで、任意の値を入力して [OK] を選び、アプリを作成します。

image.png

上図のように入力し作成します。

関数の作成

AzureFunctionsをV1対応に変更します。

image.png

関数の追加でcreate your own custom function. のリンクをクリック

image.png

Enableに変更する

image.png

HTTP triggerのPowershellのリンクをクリック

image.png

必要に応じ関数名を変更し作成

image.png

これで空っぽのFunctionsが一つできました。

モジュール追加

Network Watcher PowerShell コマンドレットを使うには、最新の PowerShell モジュールを Function App にアップロードします。

ローカルPCのモジュールの配置場所の確認をしておく

  • Azure PowerShell モジュールの以下のモジュールをアップロードします
    • AzureRM.Network
    • AzureRM.Profile
    • AzureRM.Resources
  • (Get-Module AzureRM.Network).Pathを実行し、モジュールのパスを取得しておきます。

ポータルに戻ります。

Platform features > App Service Editorを選択

image.png

作成したカスタム関数の名前を右クリックし、azuremodules という名前のフォルダーを作成します。

image.png

必要なモジュールごとにサブフォルダーを作成します。
image.png

Azure モジュールに移動します。 ローカルの AzureRM.Network フォルダーで、フォルダー内のすべてのファイルを選びます。

image.png

AzureRM.Profile と AzureRM.Resources についても、同様の手順を繰り返します。

image.png

認証情報の作成

  • PowerShellのAzureコマンドレットを使用するには、認証する必要があります。
  • 認証を構成するには、環境変数を構成して、暗号化されたキー ファイルを Function App にアップロードする必要があります。

暗号化された資格情報の作成

  • PowerShellでキー ファイルを作成します。
  • 指定されているパスワードの暗号化バージョンも提供します。このパスワードは、認証に使われるAzure Active Directory アプリケーション用に定義されたパスワードと同じです。
crePassword.ps1
# Variables
$keypath = "C:\temp\PassEncryptKey.key"
$AESKey = New-Object Byte[] 32
$Password = "<insert a password here>"

# Keys
[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($AESKey) 
Set-Content $keypath $AESKey

# Get encrypted password
$secPw = ConvertTo-SecureString -AsPlainText $Password -Force
$AESKey = Get-content $KeyPath
$Encryptedpassword = $secPw | ConvertFrom-SecureString -Key $AESKey
$Encryptedpassword

実行結果

image.png

image.png

Function App の App Service エディターで、keysフォルダーを作成し、作成した PassEncryptKey.key ファイルをアップロードします。

image.png

環境変数の値に設定する認証情報の取得

  • 認証用の値にアクセスするために必要な環境変数を設定
  • 下記を取得します
    • AzureClientID
    • AzureTenant
    • AzureCredPassword

AzureClientID

  • Azure Active Directory 内のアプリケーションのアプリケーション ID

下記スクリプトを実行します

creAzureClientID.ps1
$secPw = ConvertTo-SecureString -AsPlainText “先ほどのPW -Force
$app = New-AzureRmADApplication -DisplayName “アプリ名” -HomePage https://exampleapp.com -IdentifierUris https://exampleapp1.com/ExampleFunctionsAccount -Password $secPw
New-AzureRmADServicePrincipal -ApplicationId $app.ApplicationId

Start-Sleep 15

New-AzureRmRoleAssignment -RoleDefinitionName Contributor -ServicePrincipalName $app.ApplicationId

Azure Portal で、[サブスクリプション] を選びます。 使うサブスクリプションを選び、[アクセス制御 (IAM)] を選択

image.png

使うアカウントを選び、[プロパティ] を選びます。 アプリケーション ID をコピーします。

image.png

AzureTenant

  • 次の PowerShell サンプルを実行して、テナント ID を取得します。

(Get-AzureRmSubscription -SubscriptionName "<subscriptionName>").TenantId

AzureCredPassword

  • AzureCredPassword 環境変数の値は、前の暗号化された資格情報セクションで示したものと同じです。
  • 必要な値は、$Encryptedpassword 変数の出力です。
  • これは、PowerShell スクリプトを使って暗号化したサービス プリンシパル パスワードです。

環境変数を格納する

Function App に移動します。 [Function App の設定] > [アプリケーション設定の構成] の順に選びます。

image.png

環境変数とその値をアプリの設定に追加し、[保存] を選びます。

image.png

関数へのPSスクリプトの追加

スクリプト上部にある変数は各環境に合わせたリソースの情報に置き換えます。

functions.ps1
# Process request body
$requestBody = Get-Content $req -Raw | ConvertFrom-Json

#Azure vars
$VMresourceGroupName = "XXXXXX"
$StorageresourceGroupName = "XXXXXX"
$storageAccountName = "XXXXXXXX"
$region = "XXXXXXX"
$AzureFunctionName = "XXXXXXXXXX"
#vars
$PCBaseName = "pc_" # "" + InstanceName Is PacketCaputureName
$EventType = "Freeze"#Freeze
$packetCaptureLimit = 30 #number limit in one networkwatcher
$packetCaptureDuration = 1800 #Second 30min=1800sec

# Import Azure PowerShell modules required to make calls to Network Watcher
Import-Module "D:\home\site\wwwroot\$AzureFunctionName\azuremodules\AzureRM.Profile\AzureRM.Profile.psd1" -Global
Import-Module "D:\home\site\wwwroot\$AzureFunctionName\azuremodules\AzureRM.Network\AzureRM.Network.psd1" -Global
Import-Module "D:\home\site\wwwroot\$AzureFunctionName\azuremodules\AzureRM.Resources\AzureRM.Resources.psd1" -Global

if($requestBody){
    if($requestBody.Events){

        #Credentials
        $tenant = $env:AzureTenant
        $pw = $env:AzureCredPassword
        $clientid = $env:AzureClientId
        $keypath = "D:\home\site\wwwroot\$AzureFunctionName\keys\PassEncryptKey.key"
        #Authentication
        $secpassword = ConvertTo-SecureString -String $pw -Key (Get-Content $keypath)
        $credential = New-Object System.Management.Automation.PSCredential ($clientid, $secpassword)
        Connect-AzureRmAccount -ServicePrincipal -Tenant $tenant -Credential $credential #-WarningAction SilentlyContinue | out-null
        #Storage account ID to save captures in
        $storageaccountid = (Get-AzureRmResource -ResourceGroupName $StorageresourceGroupName -ResourceType Microsoft.Storage/storageAccounts -ResourceName $storageAccountName -ApiVersion 2017-10-01).ResourceId
        #Get the Network Watcher in the VM's region
        $nw = Get-AzurermResource | Where {$_.ResourceType -eq "Microsoft.Network/networkWatchers" -and $_.Location -eq $region }
        $networkWatcher = Get-AzureRmNetworkWatcher -Name $nw.Name -ResourceGroupName $nw.ResourceGroupName

        #Check RequestBody
        foreach ($event in $requestBody.Events) {

            if($event.EventType -eq $EventType){
                Write-Output "Freeze Event Will Occur"
                #リソースID取得(Jsonからリソース名取得して_を/に置換して検索)
                $TargetVmResourceName = ($event.Resources).Replace("_", "/") 
                $VMResourceInfo = Get-AzureRmResource -ResourceGroupName $VMresourceGroupName -ResourceType Microsoft.Compute/virtualMachineScaleSets/virtualMachines -ResourceName $TargetVmResourceName -ApiVersion 2018-06-01
                $VMResourceID = $VMResourceInfo.ResourceId

                #Packet capture vars
                $packetcapturename = $PCBaseName + $VMResourceInfo.Name

                #Get existing packetCaptures
                $packetCaptures = Get-AzureRmNetworkWatcherPacketCapture -NetworkWatcher $networkWatcher -PacketCaptureName $packetCaptureName -ErrorAction SilentlyContinue

                #Remove existing packet capture created by the function (if it exists)
                if($packetCaptures)
                { 
                    Write-Output $packetCaptures.PacketCaptureStatus 

                    Stop-AzureRmNetworkWatcherPacketCapture -NetworkWatcher $networkWatcher -PacketCaptureName $packetCaptureName
                    Start-Sleep -Seconds 3
                    Remove-AzureRmNetworkWatcherPacketCapture -NetworkWatcher $networkWatcher -PacketCaptureName $packetCaptureName
                    Start-Sleep -Seconds 3
                }

                #Initiate packet capture on the VM that fired the alert
                if ((Get-AzureRmNetworkWatcherPacketCapture -NetworkWatcher $networkWatcher -ErrorAction SilentlyContinue).Count -lt $packetCaptureLimit)
                {
                    Write-Output "Initiating Packet Capture"
                    New-AzureRmNetworkWatcherPacketCapture -NetworkWatcher $networkWatcher -TargetVirtualMachineId $VMResourceID -PacketCaptureName $packetCaptureName -StorageAccountId $storageaccountid -TimeLimitInSeconds $packetCaptureDuration
                    Start-Sleep -Seconds 3
                }
            }else
            {
                Write-Output "$event.EventType Event Will Occur"
            }
        }  
        Out-File -Encoding Ascii -FilePath $res -inputObject "Packet Capture created"
    }else{
        Write-Output "Nothing Events!!"
    }
}else{
    Write-Output "No RequestBody"
}

関数の URL を取得する

関数を作成した後は、その関数に関連付けられた URL を呼び出すようにシェルを構成します。 この値を取得するには、Function App から関数の URL をコピーします。

image.png

image.png

シェルスクリプトの書き換え

chkMetaDataService.sh
#!/bin/sh

while true
do
    metadatajson=$(curl -H Metadata:true http://169.254.169.254/metadata/scheduledevents?api-version=2017-08-01)
    echo $metadatajson

    if [[ "$metadatajson" =~ "EventType" ]]; then
        #if [[ "$metadatajson" =~ "Freeze" ]]; then
            echo "Some Event Will Occur"

            #AzureFunctionsにWebHook
            HOOK="https://X.azurewebsites.net/api/X?code=X"
            curl -X POST -H "Content-Type: application/json" -d "$metadatajson" "$HOOK" 

            sleep 30m
        #fi
    fi

    sleep 10s
done

exit 0

これで実行する準備が完了です。

実際に動かしてみる

Freezeはいつ発生するかわからないのでRebootに書き換えてVMを再起動させての稼働確認です。

Linux側

まずはLinuxの方でbashを実行させておきます。

image.png

それではポータルからsshしているVMの再起動を行ってみます。

image.png

すると、、、

image.png

再起動イベントが15分後に行われますというレスポンスが返ってきます。

Functions側

image.png

無事にFunctions側が起動してPacketCaptureを作成してくれたようです。

NetworkWatcher側

image.png

実際にキャプチャされていました。

終わり

V2がGAされた中いまさらV1かよって感じもあります、V1もいつまで存続するかわからないので、実は代替案としてAzureFunctionsの代わりにAzureAutomationで動かすよう構成を考え直していたりしています。AzureAutomationでもやることはほぼ同じ設定値系と認証系を少し変更するだけで同じように動きました。

12
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
6