VMにアクセスする Azure Functions の開発手順を記載する。
以降の手順で作成する関数の仕様 及び リソースイメージは以下の通り。
以降の手順で作成する全ファイルは以下の通り
. ├── 0_env.sh ├── 1_create_resources.sh ├── 2_setup_influxdb.sh ├── 3_deploy_funcapp.sh ├── 9_remove_resources.sh ├── functions │ ├── BlobTrigger │ │ └── function.json │ ├── go_server_sample.exe │ ├── go_server_sample.go │ ├── host.json │ └── local.settings.json ├── pem │ ├── id_rsa │ └── id_rsa.pub
... リソース名の定義など ... リソース作成用のシェル ... VMのInfluxDBのセットアップ用シェル ... 関数アプリのデプロイ用シェル ... リソース一括削除用のシェル ... 内部関数名(トリガー名) ... 関数のトリガー定義 ... カスタムハンドラー(実行可能ファイル) ... カスタムハンドラー(ソース) ... 関数アプリの定義ファイル ... ローカル実行用の設定ファイル ... 作成するVMのSSH鍵の退避先(VM作成時に自動生成)
リソース作成用のシェルを以下の通り作成する。
当記事では全てCLIをシェル化したが、恐らく ARM(Azure Resource Manager)を使うのが正解。
※https://azure.microsoft.com/ja-jp/features/resource-manager/
このシェルでは作成する各リソースの名前の定義を行っている。
※ -p 付きで実行すればコンソールへの表示も行うようにしている。
0_env.sh
#!/bin/bash # 全てのリソース名に付与する接頭文字 (Storageアカウント名などは世界でユニークな必要があるので他ユーザと被らないような名前を付ける) PREFIX=XXXXXX # サブスクリプションID (Application Insight を使用しない場合は空) subscriptionId=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx # リージョン region=japanwest insightsRegion=japaneast # 2020/7時点では Appplication Insights で西日本(japanwest)は使用できない # リソースグループ名 # ( az group delete --name $resourceGroup で一括削除可能 ) resourceGroup=${PREFIX}ResourceGroup # ストレージアカウント名 storageAccountName=${PREFIX}straccount storageSku=Standard_LRS # Storageコンテナ名 storageContainerIn=${PREFIX}-strcontainer-i storageContainerOut=${PREFIX}-strcontainer-o # 仮想ネットワーク名 vnetName=${PREFIX}VNet vnetPrefix=10.1.0.0/16 # ネットワークセキュリティグループ nsgName=${vnetName}SecGrp nsgPubRuleName=${nsgName}PubRule nsgPubInboundPort="22" #nsgPubInboundPort="443" nsgPriRuleName=${nsgName}PriRule nsgPriInboundPort="8086" # VM用のサブネット vmSubnetName=${PREFIX}VmSubnet vmSubnetPrefix=10.1.1.0/24 # 関数アプリ用のサブネット funcSubnetName=${PREFIX}FuncSubnet funcSubnetPrefix=10.1.2.0/24 # 仮想マシン vmName=${PREFIX}Vm vmImage=UbuntuLTS # "az vm image list -o table" で利用可能なイメージの一覧を確認可能 vmIpAddress=10.1.1.5 vmUser=sample # 関数アプリ名 funcAppName=${PREFIX}FuncApp # 使用するFunctionsのバージョン funcVersion=2 # 関数アプリのプラン名 funcPlanName=${PREFIX}premiumplan funcPlanSku=EP1 # Application insights insightsName=${PREFIX}Insights insightsDays=30 if [ "$1" == "-p" ]; then echo "region : $region" echo "resourceGroup : $resourceGroup" echo "storageAccountName : $storageAccountName" echo "storageSku : $storageSku" echo "storageContainerIn : $storageContainerIn" echo "storageContainerOut : $storageContainerOut" echo "vnetName : $vnetName" echo "vnetPrefix : $vnetPrefix" echo "nsgName : $nsgName" echo "nsgPubRuleName : $nsgPubRuleName" echo "nsgPubInboundPort : $nsgPubInboundPort" echo "nsgPriRuleName : $nsgPriRuleName" echo "nsgPriInboundPort : $nsgPriInboundPort" echo "vmSubnetName : $vmSubnetName" echo "vmSubnetPrefix : $vmSubnetPrefix" echo "vmName : $vmName" echo "vmImage : $vmImage" echo "vmIpAddress : $vmIpAddress" echo "vmUser : $vmUser" echo "funcAppName : $funcAppName" echo "funcVersion : $funcVersion" echo "funcPlanName : $funcPlanName" echo "funcPlanSku : $funcPlanSku" echo "funcSubnetName : $funcSubnetName" echo "funcSubnetPrefix : $funcSubnetPrefix" echo "insightsName : $insightsName" echo "insightsDays : $insightsDays" echo "insightsRegion : $insightsRegion" fi
このシェルでは各リソースを作成する。
※関数アプリは中身が空のアプリとして作成される。(create のみ行っている)
1_create_resources.sh
#!/bin/bash source ./0_env.sh # リソースグループの作成 echo az group create az group create \ --name $resourceGroup \ --location $region # ストレージアカウントの作成 echo az storage account create az storage account create \ --name $storageAccountName \ --location $region \ --resource-group $resourceGroup \ --sku $storageSku # Storageコンテナの作成 echo "az storage container create(in)" az storage container create \ --name $storageContainerIn \ --resource-group $resourceGroup \ --account-name $storageAccountName echo "az storage container create(out)" az storage container create \ --name $storageContainerOut \ --resource-group $resourceGroup \ --account-name $storageAccountName # NSG(ネットワークセキュリティグループの)作成 echo az network nsg create az network nsg create \ --resource-group $resourceGroup \ --name $nsgName # NSGルール(Public) echo az network nsg rule create az network nsg rule create \ --resource-group $resourceGroup \ --nsg-name $nsgName \ --name $nsgPubRuleName \ --access Allow \ --protocol Tcp \ --direction Inbound \ --priority 100 \ --source-address-prefix Internet \ --source-port-range "*" \ --destination-port-range $nsgPubInboundPort # NSGルール(Private) # TODO: 同一ネットワーク内であれば不要 #echo az network nsg rule create #az network nsg rule create \ # --resource-group $resourceGroup \ # --nsg-name $nsgName \ # --name $nsgPriRuleName \ # --access Allow \ # --protocol Tcp \ # --direction Inbound \ # --priority 110 \ # --source-address-prefix VirtualNetwork \ # --source-port-range "*" \ # --destination-port-range $nsgPriInboundPort # 仮想ネットワーク 及び サブネット作成 echo az network vnet create az network vnet create \ --name $vnetName \ --resource-group $resourceGroup \ --address-prefixes $vnetPrefix \ --subnet-name $vmSubnetName \ --subnet-prefixes $vmSubnetPrefix \ --network-security-group $nsgName # 仮想マシンの作成 # TODO: 管理用の踏み台サーバは別で用意する rm -rf ~/.ssh/id_rsa rm -rf ~/.ssh/id_rsa.pub echo az vm create az vm create \ --resource-group $resourceGroup \ --name $vmName \ --image $vmImage \ --generate-ssh-keys \ --subnet $vmSubnetName \ --vnet-name $vnetName \ --private-ip-address $vmIpAddress \ --admin-username $vmUser \ --custom-data 2_setup_influxdb.sh \ --output json \ --verbose # --public-ip-address "" # TODO: 踏み台以外はPublicアクセスなしにする # 生成されたSSH鍵を移動( --ssh-dest-key-path が効かない為 ) # ※ https://docs.microsoft.com/en-us/cli/azure/vm?view=azure-cli-latest#az-vm-create rm -rf pem mkdir -p pem if [ -e ~/.ssh/id_rsa ]; then mv ~/.ssh/id_rsa ./pem/ mv ~/.ssh/id_rsa.pub ./pem/ fi # InfluxDB用のポート開放 echo az vm open-port az vm open-port \ --resource-group $resourceGroup \ --name $vmName \ --port 8086 # Application Insights 拡張が利用できない場合は追加インストール x=`az monitor app-insights --help 2>&1` if [ "$?" != "0" ]; then az extension add -n application-insights fi # Application Insights コンポーネント作成 echo az monitor app-insights component create if [ "$subscriptionId" != "" ]; then az monitor app-insights component create \ --app $insightsName \ --location $insightsRegion \ --resource-group $resourceGroup \ --query-access Enabled \ --retention-time $insightsDays \ --subscription $subscriptionId fi # プレミアムプランの作成(プレミアムプランでないと関数アプリからのVNetアクセス機能は提供されない) echo az functionapp plan create az functionapp plan create \ --name $funcPlanName \ --resource-group $resourceGroup \ --location $region \ --sku $funcPlanSku # 関数アプリの作成 echo az functionapp create if [ "$subscriptionId" != "" ]; then az functionapp create \ --name $funcAppName \ --storage-account $storageAccountName \ --plan $funcPlanName \ --resource-group $resourceGroup \ --functions-version $funcVersion \ --app-insights $insightsName else az functionapp create \ --name $funcAppName \ --storage-account $storageAccountName \ --plan $funcPlanName \ --resource-group $resourceGroup \ --functions-version $funcVersion fi # 関数アプリの環境変数の設定 echo "az functionapp config appsettings" az functionapp config appsettings set \ --name $funcAppName \ --resource-group $resourceGroup \ --settings "DB_HOST=$vmIpAddress" "DB_PORT=8086" "DB_NAME=sampledb" "DB_USER=sample" "DB_PW=sample" # 関数アプリのVNet統合用のサブネット echo az network vnet subnet create az network vnet subnet create \ --name $funcSubnetName \ --resource-group $resourceGroup \ --vnet-name $vnetName \ --address-prefixes $funcSubnetPrefix # 関数アプリのVNet統合(当コマンドはプレビュー版の為、将来で変更/削除される可能性あり) echo az functionapp vnet-integration add az functionapp vnet-integration add \ --name $funcAppName \ --resource-group $resourceGroup \ --vnet $vnetName \ --subnet $funcSubnetName
このシェルはVM作成時に実行されるVMの初期化スクリプト。
az vm create 時の --custom-data として指定している為、手動で実行する必要はない。
処理内容は InfluxDBのセットアップ。(docker を使用)
2_setup_influxdb.sh
#!/bin/bash apt-get update apt-get install -y apt-transport-https ca-certificates curl gnupg-agent software-properties-common curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - add-apt-repository \ "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) \ stable" apt-get update apt-get install -y docker-ce docker-ce-cli containerd.io curl -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose mkdir /tmp/influx_docker && cd /tmp/influx_docker cat <<_EOCONF_ >influxdb.conf [meta] dir = "/var/lib/influxdb/meta" [data] dir = "/var/lib/influxdb/data" engine = "tsm1" wal-dir = "/var/lib/influxdb/wal" [http] enabled = true flux-enabled = true _EOCONF_ cat <<_EOYML_>docker-compose.yml version: "3" services: influxdb: image: influxdb:1.8 hostname: influxdb_sample container_name: influxdb_sample volumes: - ./influxdb:/var/lib/influxdb - ./influxdb.conf:/etc/influxdb/influxdb.conf ports: - 8086:8086 _EOYML_ # start influxdb docker-compose up -d # wait until influxdb starts while [ true ]; do sleep 10 x=`sudo docker exec -i influxdb_sample influx --execute "show databases" 2>&1` if [ "$?" == "0" ]; then break fi done # create sample database and sample user. docker exec -i influxdb_sample influx --execute "create database sampledb" docker exec -i influxdb_sample influx --execute "create user sample with password 'sample' WITH ALL PRIVILEGES"
このシェルでは関数アプリのビルド/デプロイを行う。
関数アプリのOSはデフォルトのWindowsを使用している為、Windowsm用にビルドしている。
3_deploy_funcapp.sh
#!/bin/bash source 0_env.sh rm -rf functions.zip exefile=`cat functions/host.json | grep defaultExecutablePath | awk '{print $2}' | sed 's/"//g'` cd functions rm -rf $exefile #GOOS=linux GOARCH=amd64 go build -o $exefile #GOOS=windows GOARCH=386 go build -o $exefile GOOS=windows GOARCH=amd64 go build -o $exefile zip -r ../functions.zip * cd ../ az functionapp deployment source config-zip -g $resourceGroup -n $funcAppName --src functions.zip
このシェルはリソースの一括削除用のシェル。
やっている事はリソースグループを指定して削除しているだけ。
9_remove_resources.sh
#!/bin/bash source ./0_env.sh az group delete --name $resourceGroup
ログイン
az login
リソース作成
./1_create_resources.sh
リソースグループ配下に作成されたリソースをAzure Portal からみると下図の通り。
作成したVMのPublic IPを確認
az vm list-ip-addresses -o table VirtualMachine PublicIPAddresses PrivateIPAddresses ---------------- ------------------- -------------------- XXXXXXXXX XX.XX.XXX.XX 10.1.1.5
VMにSSH接続
source ./0_env.sh ssh -i ./pem/id_rsa $vmUser@確認したPublic IP
InfluxDBにサンプルDBが作成されているか確認 (作成に少し時間がかかるので数分待ってから確認する)
# dockerコンテナが起動しているか確認 sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES daffbdd72d06 influxdb:1.8 "/entrypoint.sh infl…" 10 minutes ago Up 10 minutes 0.0.0.0:8086->8086/tcp influxdb_sample # サンプルDBが作成されているか確認 sudo docker exec -i influxdb_sample influx --execute "show databases" name: databases name ---- sampledb _internal
functions/host.json
{ "version": "2.0", "httpWorker": { "description": { "defaultExecutablePath": "go_server_sample.exe" } }, "extensionBundle": { "id": "Microsoft.Azure.Functions.ExtensionBundle", "version": "[1.*, 2.0.0)" } }
functions/local.settings.json
{ "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "DB_HOST": "localhost", "DB_PORT": "8086", "DB_NAME": "sampledb", "DB_USER": "sample", "DB_PW": "sample" } }
functions/go_server_sample.go
package main import ( "context" "encoding/json" "fmt" "log" "net/http" "os" "math/rand" "time" "github.com/influxdata/influxdb-client-go" ) type ReturnValue struct { Data string } type InvokeResponse struct { Outputs map[string]interface{} Logs []string ReturnValue interface{} } type InvokeRequest struct { Data map[string]interface{} Metadata map[string]interface{} } func blobTriggerHandler(w http.ResponseWriter, r *http.Request) { var invokeReq InvokeRequest d := json.NewDecoder(r.Body) decodeErr := d.Decode(&invokeReq) if decodeErr != nil { http.Error(w, decodeErr.Error(), http.StatusBadRequest) return } fmt.Println("The JSON data is:invokeReq metadata......") fmt.Println(invokeReq.Metadata) returnValue := invokeReq.Data["triggerBlob"] invokeResponse := InvokeResponse{Logs: []string{"test log1", "test log2"}, ReturnValue: returnValue} js, err := json.Marshal(invokeResponse) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // VM(InfluxDB)へのアクセス確認 dbHost := getEnv("DB_HOST", "localhost") dbPort := getEnv("DB_PORT", "8086") dbName := getEnv("DB_NAME", "unknown") dbUser := getEnv("DB_USER", "unknown") dbPw := getEnv("DB_PW", "unknown") client := influxdb2.NewClient(fmt.Sprintf("http://%s:%s", dbHost, dbPort), fmt.Sprintf("%s:%s", dbUser, dbPw)) create_sample(client, dbName) client.Close() w.Header().Set("Content-Type", "application/json") w.Write(js) } /** * 環境変数の取得. */ func getEnv(envName string, defaultValue string) string { value, exists := os.LookupEnv(envName) if exists { return value } else { return defaultValue } } /** * サンプルデータ登録(InfluxDB). */ func create_sample(client influxdb2.Client, dbName string) { fmt.Printf("Write START\n") writeAPI := client.WriteAPIBlocking("", fmt.Sprintf("%s/autogen", dbName)) // サンプルデータ登録 for i := 0; i < 5; i++ { p := influxdb2.NewPointWithMeasurement("sample"). AddTag("tag1", "sample1"). AddTag("tag2", fmt.Sprintf("row%d", i+1)). AddField("field1", i + 1). AddField("field2", rand.Float64()). SetTime(time.Now()) err := writeAPI.WritePoint(context.Background(), p) if err != nil { fmt.Printf("Write2 error: %s\n", err.Error()) } else { fmt.Printf("Write2 success.\n") } } fmt.Printf("Write END\n") } func main() { httpInvokerPort, exists := os.LookupEnv("FUNCTIONS_HTTPWORKER_PORT") if exists { fmt.Println("FUNCTIONS_HTTPWORKER_PORT: " + httpInvokerPort) } mux := http.NewServeMux() mux.HandleFunc("/BlobTrigger", blobTriggerHandler) log.Println("Go server Listening...on httpInvokerPort:", httpInvokerPort) log.Fatal(http.ListenAndServe(":"+httpInvokerPort, mux))
functions/BlobTrigger/function.json
{ "bindings" : [ { "type" : "blobTrigger", "direction" : "in", "name" : "triggerBlob", "path" : "作成したストレージコンテナ名1/{name}", "dataType" : "binary", "connection" : "AzureWebJobsStorage" }, { "type" : "blob", "direction" : "out", "name" : "$return", "path" : "作成したストレージコンテナ名2/{name}", "dataType" : "binary", "connection" : "AzureWebJobsStorage" } ] }
デプロイ
./3_deploy_funcapp.sh
接続文字列の確認
source 0_env.sh az storage account show-connection-string --name $storageAccountName { "connectionString": "DefaultEndpointsProtocol=https;EndpointSuffix=core.windows.net;AccountName=xxxxxxxxxxx;AccountKey=xxxxxxxxxxxxxx" }
ストレージエクスプローラにStorageアカウントをアタッチ
ファイルをアップロード
VMに接続して InfluxDB にデータが登録されているか確認
ssh -i ./pem/id_rsa $vmUser@確認したPublic IP sudo docker exec -i influxdb_sample influx -database sampledb -username sample --execute "select * from sample" name: sample time field1 field2 tag1 tag2 ---- ------ ------ ---- ---- 1596015616248498800 1 0.6046602879796196 sample1 row1 1596015616436070000 2 0.9405090880450124 sample1 row2 1596015616451632700 4 0.4377141871869802 sample1 row4 1596015616451632700 3 0.6645600532184904 sample1 row3 1596015616467330200 5 0.4246374970712657 sample1 row5