[适合初学者]让我们初次尝试使用Azure Functions(计时器触发器)和Node.js
首先
我們直接將內部專用的內容公開了。
我想要在Azure Functions上编写处理代码,但我应该从哪里学起呢……针对这个问题,我想一起创建一个使用Azure Functions的TimerTrigger来监视URL的服务。
我将学习这样的事情。
-
- 必要なソフトウェアのインストール
-
- nodejsの超基本中のキホン
-
- AzureFunctionsへのデプロイ
-
- AzureFunctionsをローカルで動かしてみよう
-
- モジュールの活用
-
- AzureFunctionsにおける環境変数の活用
- 学んだことを使ってURL監視サービス(証明書チェック機能付き)を作ってみる
那么,我们赶快开始吧。
必需品
- Azureアカウント
安装必要的东西。
安装 Node.js(LTS版)。
- https://nodejs.org/ja/
安装Yarn(软件包管理器)
- https://legacy.yarnpkg.com/lang/ja/docs/install/#windows-stable
安装 Azure CLI
- https://docs.microsoft.com/ja-jp/cli/azure/install-azure-cli?view=azure-cli-latest
安装 Azure Functions Core Tools
-
- v2とv3がありますが、今回はv2を使います。
- https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-run-local#v2
根据需要进行安装。
我的首選編輯器是IntelliJ,但Visual Studio Code相對容易上手,建議你試試看。
-
- VisualStudioCode
-
- Vim
- IntelliJ
首先从Node.js的基本开始。
我们创建一个名为 “azure-functions-samples” 的目录,并在其中创建 “index.js” 文件,然后尝试运行它。
function hello() {
console.log("test");
}
hello();
试试看
node index.js
我們來試試用Yarn執行一下吧。
通过执行yarn init命令可以创建package.json模板。一开始不输入任何内容也可以。
azure-functions-samples> yarn init
yarn init v1.21.1
question name (fetch-timer):
question version (1.0.0):
question description:
question entry point (index.js):
question repository url:
question author:
question license (MIT):
question private:
success Saved package.json
Done in 13.50s.
- package.jsonではライブラリの依存関係を記述したりするのですが、それは後で説明するとして、package.jsonのscriptsの部分に書けば好きなコマンドを実行できます。
{
"name": "simple-timer-function",
"version": "1.0.0",
"description": "",
"scripts": {
"start": "node index.js"
},
"author": "",
"main": "index.js",
"license": "MIT"
}
在package.json文件中编写scripts部分,可以通过运行yarn start来执行。
嗯,到目前为止一切还好吧。
关于Azure Functions
在Azure Functions中,有许多不同的功能可用。
-
- HTTPアクセスを受け取って処理を実行する
-
- CosmosDBにデータが入ったらTriggerとして起動する
-
- BlobStorageにデータが格納されたら処理を実行する
- 定期的に実行する
此外,您可以像AWS Lambda一样根据需要随时运行,也可以一直保持运行状态。
在AWS Lambda中,如果随时运行,可能会花费一些时间来进行首次启动,Azure Functions也有相同情况。如果无法接受这种情况,可以通过定期轮询或者保持一直运行来避免此问题。
因为我想要定期监视URL,所以这次我们将使用TimerTrigger(定期执行)。
简单的计时器触发器
创建一个名为simple-timer的文件夹。
$ mkdir simple-timer
在 timer 文件夹下创建一个函数的模板。
- 言語はJavaScriptを選択してください
azure-functions-samples\simple-timer> func new
Select a language: Starting from 2.0.1-beta.26 it's required to set a language for your project in your settings
'node' has been set in your local.settings.json
Select a template: Timer trigger
Function name: [TimerTrigger] simple-timer
Writing C:\Users\uzres\git\azure-functions-samples\simple-timer\simple-timer\index.js
Writing C:\Users\uzres\git\azure-functions-samples\simple-timer\simple-timer\readme.md
Writing C:\Users\uzres\git\azure-functions-samples\simple-timer\simple-timer\function.json
The function "simple-timer" was created successfully from the "Timer trigger" template.
由于我很着急,所以让它每10秒启动一次吧。
{
"bindings": [
{
"name": "myTimer",
"type": "timerTrigger",
"direction": "in",
"schedule": "*/10 * * * * *"
}
]
}
部署
我将启动CloudShell。
一旦CloudShell启动后,我们将创建一个Functions。我们应该将<适当的字符串>命名为自己可以明确识别的名称,例如uzresk。
export RESOURCE_SUFFIX=<適当な文字列>
export RG_NAME=rg-$RESOURCE_SUFFIX
export STORAGE_NAME=azfunsamples$RESOURCE_SUFFIX
export FUNCTION_NAME=azfun-samples-$RESOURCE_SUFFIX
export LOCATION=japaneast
az group create --name $RG_NAME --location $LOCATION
az storage account create \
--name $STORAGE_NAME \
--location $LOCATION \
--resource-group $RG_NAME \
--sku Standard_LRS
az functionapp create \
--name $FUNCTION_NAME \
--storage-account $STORAGE_NAME \
--consumption-plan-location $LOCATION \
--resource-group $RG_NAME
一旦功能完成后,我会在Azure界面上确认一下。
这一次我们将回到本地并进行部署。
azure-functions-samples\simple-timer> func azure functionapp publish azfun-samples-xxxxxxx
Getting site publishing info...
Creating archive for current directory...
Uploading 1.49 KB [###############################################################################]
Upload completed successfully.
Deployment completed successfully.
Syncing triggers...
Syncing triggers...
Functions in azfun-samples-xxxxxxx:
simple-timer - [timerTrigger]
当你返回Azure的控制台,并选择函数,然后查看“整合”选项,你会发现已经反映了所做的调度更改。(可能有些微不足道)
当您观察”监视”时,可以确认它每隔10秒启动一次,并且还可以看到输出的日志。
本地执行
由于每次部署都是浪费时间,所以我们可以在本地运行Azure Functions来测试。本地运行的配置信息会记录在local.settings.json文件中。
{
"IsEncrypted": true,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
"ConnectionStrings": {}
}
你不需要考虑在local.settings.json中写什么。只需执行一条命令即可从服务器获取设置。
func azure functionapp fetch-app-settings <functionAppName>
运行后,local.settings.json应该被修改如下。
{
"IsEncrypted": true,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
"FUNCTIONS_EXTENSION_VERSION": "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
"AzureWebJobsStorage": "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
"AzureWebJobsDashboard": "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
"WEBSITE_NODE_DEFAULT_VERSION": "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
"WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
"WEBSITE_CONTENTSHARE": "",
"APPINSIGHTS_INSTRUMENTATIONKEY": "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
"WEBSITE_RUN_FROM_PACKAGE": "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
"ConnectionStrings": {}
}
那么让我们在本地环境中运行一下。
打开VSCode的终端,并执行以下命令。
func start
如果设置为每10秒执行一次,那么在下面写有运行时间的消息后,应该会在达到设定时间时显示执行结果。
[2020/01/24 4:27:44] The next 5 occurrences of the 'simple-timer' schedule (Cron: '0,10,20,30,40,50 * * * * *') will be:
[2020/01/24 4:27:44] 01/24/2020 13:27:50+09:00 (01/24/2020 04:27:50Z)
[2020/01/24 4:27:44] 01/24/2020 13:28:00+09:00 (01/24/2020 04:28:00Z)
[2020/01/24 4:27:44] 01/24/2020 13:28:10+09:00 (01/24/2020 04:28:10Z)
[2020/01/24 4:27:44] 01/24/2020 13:28:20+09:00 (01/24/2020 04:28:20Z)
[2020/01/24 4:27:44] 01/24/2020 13:28:30+09:00 (01/24/2020 04:28:30Z)
添加模块
我刚才尝试直接运行了一个模板,现在我将访问外部资源并将其输出到日志中。
您不需要从头编写访问外部资源的HTTPS访问代码。您可以将库安装到本地,然后使用该库进行HTTPS访问。
创建一个名为”fetch-timer”的目录在azure-functions-samples中。
让我们像之前一样创建一个函数的模板。
PS C:\Users\uzres\git\azure-functions-samples\fetch-timer> func new
'node' has been set in your local.settings.json
Select a template: Timer trigger
Function name: [TimerTrigger] fetch-timer
Writing C:\Users\uzres\git\azure-functions-samples\fetch-timer\fetch-timer\index.js
Writing C:\Users\uzres\git\azure-functions-samples\fetch-timer\fetch-timer\readme.md
Writing C:\Users\uzres\git\azure-functions-samples\fetch-timer\fetch-timer\function.json
The function "fetch-timer" was created successfully from the "Timer trigger" template.
让我们使用yarn init创建package.json的模板。
让我们来检查一下package.json文件。
{
"name": "fetch-timer",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
}
请安装用于执行 HTTP/HTTPS 请求的 axios 模块。
PS C:\Users\uzres\git\azure-functions-samples\fetch-timer> yarn add axios
・・・
Done in 0.58s.
当再次查看package.json文件时,可以看到已经添加了axios。
{
"name": "fetch-timer",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"axios": "^0.19.2"
}
}
此外,你还会看到一个名为node_modules的目录,其中包含了所需的库。package.json文件中记录了库的依赖关系,在执行yarn init命令时,会自动安装所需的库。通过执行yarn add xxxx命令,可以自动将库的信息写入package.json文件中,但有时也需要手动编辑以固定包的版本。
首先,让我们先阅读关于 package.json 文件中的包版本规范符号(尖角符号(^)和波浪符号(~))的内容,只需要大致理解就好。
让我们尝试进行HTTPS访问。
我尝试通过访问Google Books API获取标题中含有“Azure”的数据。
让我们尝试访问这里。
通过在q后面放入要搜索的字符串,可以以JSON格式获取书籍数据。
我来写一段代码,用来访问数据并获取状态代码和JSON数据。
(让我们边查看又边打字,不要直接复制粘贴)
const axios = require('axios');
module.exports = async function (context, myTimer) {
const result = await axios(`https://www.googleapis.com/books/v1/volumes?q=Azure`)
.then(response => {
context.log('status code: ', response.status);
context.log('data', response.data);
})
};
如果在本地执行没有问题的话,我们就将其部署到Azure上进行执行试试看。
第一個問題
在响应数据中有一个包含书籍数据的数组,该数组名为items。
请尝试输出该数组的数量。
答案在下一章节,但首先,请不要查看答案。
提取标题试试看
让我们解析JSON并仅显示标题。我会使用for循环来循环并展示。
const axios = require('axios');
module.exports = async function (context, myTimer) {
const searchValue = "Azure";
const result = await axios(`https://www.googleapis.com/books/v1/volumes?q=${searchValue}`)
.then(response => {
const items = response.data.items;
for (i=0; i<items.length; i++) {
context.log(items[i].volumeInfo.title);
}
}).catch(error => {
context.log('error: ' + error);
});
};
可以使用for循环,但也可以使用forEach函数来写。
const axios = require('axios');
module.exports = async function (context, myTimer) {
const searchValue = "Azure";
const result = await axios(`https://www.googleapis.com/books/v1/volumes?q=${searchValue}`)
.then(response => {
response.data.items.forEach( item => {
context.log(item.volumeInfo.title)
})
}).catch(error => {
context.log('error: ' + error);
});
};
应该会有这样的执行结果。
[2020/01/24 4:27:49] Host lock lease acquired by instance ID '0000000000000000000000009E8328FA'.
[2020/01/24 4:27:50] Executing 'Functions.fetch-timer' (Reason='Timer fired at 2020-01-24T13:27:50.0705362+09:00', Id=a5682498-bb5a-46b3-b3fd-78bea46c3de8)
[2020/01/24 4:27:51] status code: 200
[2020/01/24 4:27:51] Azureテクノロジ入門 2019
[2020/01/24 4:27:51] PowerShell for Azure
[2020/01/24 4:27:51] Azureテクノロジ入門 2018
[2020/01/24 4:27:51] ひと目でわかるAzure Active Directory 第2版
[2020/01/24 4:27:51] Microsoft Azure導入ガイドブック
[2020/01/24 4:27:51] Windows Azure実践クラウド・プログラミングfor C#/Visual Basic/PHP
[2020/01/24 4:27:51] Microsoft Azureへの招待
[2020/01/24 4:27:51] Azure定番システム設計・実装・運用ガイド
[2020/01/24 4:27:51] ひと目でわかるAzure 基本から学ぶサーバー&ネットワーク構築
[2020/01/24 4:27:51] Windows Azure APIリファレンス
请确保在Azure上部署并验证输出。
顺便提一下,在Azure上可能由于地区设置的原因,可能无法找到日语标题的书。
在出现错误时打印日志
当连接目标错误时,数据应该无法正常获取并出现错误。
让我们试着编写一段代码,当出现错误时将日志输出。
(故意将连接目标更改为localhost)
const axios = require('axios');
module.exports = async function (context, myTimer) {
const result = await axios(`https://localhost/books/v1/volumes?q=Azure`)
.then(response => {
context.log('status code: ', response.status);
context.log('data', response.data);
}).catch(error => {
context.log('error: ' + error);
});
};
当在AzurePortal上查看日志时,我认为我们可以确认是否已经通过catch捕获到了错误。
2020-01-24T02:00:39.107 [Information] error: Error: connect EACCES 127.0.0.1:443
我记得在监视标签页上有绿灯亮起,但这是因为只有捕获(catch)并顺利结束了。为了引发异常结束,需要在捕获后进行抛出(throw)。
const axios = require('axios');
module.exports = async function (context, myTimer) {
const searchValue = "Azure";
// const result = await axios(`https://www.googleapis.com/books/v1/volumes?q=${searchValue}`)
const result = await axios(`https://localhost/books/v1/volumes?q=${searchValue}`)
.then(response => {
response.data.items.forEach( item => {
context.log(item.volumeInfo.title)
})
}).catch(error => {
context.log('error: ' + error);
throw error;
});
};
将其提取为环境变量
通过将连接的URL等拆分为环境变量,我们可以在一个函数中执行各种不同的功能。例如,如果我们创建了一个用于检查HTTP访问是否出错的函数,只需替换URL部分,就能提高它的可重用性。
const axios = require('axios');
module.exports = async function (context, myTimer) {
const result = await axios(`https://www.googleapis.com/books/v1/volume?q=Azure`)
.then(response => {
response.data.items.forEach(item => {
context.log(item.volumeInfo.title)
})
}).catch(error => {
context.log('[ERROR]: ' + error);
throw error;
});
};
让我们将Azure的部分划分为环境变量。
可以在Azure门户上进行环境变量的设置,并在源代码中使用环境变量。
点击函数应用的设置。
点击应用程序设置管理。
在这里设置环境变量。将URL设置为https://www.googleapis.com/books/v1/volume?q=Azure。
让我们不要忘记最后按下保存按钮。
我将继续进行源代码的修正。
-
- 環境変数から値を取得するにはprocess.env.[環境変数]を使います。
- 環境変数URLが設定されていない場合はエラーログを出力します。
const axios = require('axios');
module.exports = async function (context, myTimer) {
const URL = process.env.URL;
if (typeof process.env.URL === 'undefined') {
context.log("[ERROR]URL is undefined.");
}
const result = await axios.get(URL)
.then(response => {
response.data.items.forEach(item => {
context.log(item.volumeInfo.title)
})
}).catch(error => {
context.log('[ERROR]: ' + error);
throw error;
});
};
修正源代码后,我们就可以直接运行它了(开始函数)。
应该会出现这种错误。
[2020/01/24 5:42:50] [ERROR]URL is undefined.
[2020/01/24 5:42:50] URL: undefined
由于环境变量未设置,所以出现了错误。
虽然我们在本地环境中介绍了运行步骤,但fetch-app-settings是用于获取环境变量信息的命令,因此让我们再次执行该命令。
func azure functionapp fetch-app-settings azfun-samples-xxxxxxx
执行结果应该是 local.settings.json 文件中已经添加了以下的URL。
{
"IsEncrypted": true,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
"FUNCTIONS_EXTENSION_VERSION": "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
"AzureWebJobsStorage": "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
"AzureWebJobsDashboard": "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
"WEBSITE_NODE_DEFAULT_VERSION": "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
"WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
"WEBSITE_CONTENTSHARE": "",
"APPINSIGHTS_INSTRUMENTATIONKEY": "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
"WEBSITE_RUN_FROM_PACKAGE": "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
"URL": "xxxxxxxxxxxxxxxxxxxxxxx"
},
"ConnectionStrings": {}
}
在本地环境中再次运行并确认它可以正常工作。如果没有问题,我们可以部署到Azure上并进行执行。
第二個問題
让我们获取证书的剩余有效期天数吧,提示:尝试使用ssl-checker。
请确保日志输出以下内容。
[INFO]SSL daysRemaining <証明書有効期限の残り日数>
第三個問題。
让我们利用之前学到的知识来实现URL监控。
-
- 対象URLにとある間隔でアクセスし以下を確認してください。
HTTP Responseのステータスコードが200であること
ResponseBodyに特定の文字列があること
BooksAPIを使うのであればtotalItemsという文字列があるかを確認してみてください。
証明書の有効期限内であること
対象URL、証明書の有効期限残り日数は環境変数で指定できるようにしましょう
エラーの場合は文字列の先頭に[ERROR]という文字を付与しましょう。
整理房间
让我们彻底删除每个资源组。一旦删除,就无法恢复,请务必小心!
az group delete --name <リソースグループ名>
结束
请尝试使用TimerFunctions来实现自动停止和启动指定时间段内不需要的服务等功能,希望你能挑战一下。