概要 †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作成時に自動生成) リソース作成/デプロイ用のシェル作成 †リソース作成用のシェルを以下の通り作成する。 このシェルでは作成する各リソースの名前の定義を行っている。 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 このシェルでは各リソースを作成する。 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の初期化スクリプト。 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" このシェルでは関数アプリのビルド/デプロイを行う。 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 上に InfluxDB が正しく作成されているか確認 †作成した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["blobData"] 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" : "blobData", "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 |