本記事のゴール
Azure FunctionsとAzure Database For MySQLをVnet統合を使って連携してデータの読み書きを行える状態を目指します。
キーワード
- Vnet統合
準備するもの
- Azure Functions(Function PremiumかApp Service のStandardプラン以上)
- Azure Database For MySQL
- 踏み台仮想マシン
- vnet
- Vnet統合に使うFunction用のsubnet
Vnet統合とは
仮想ネットワーク内のリソースにWebAppやFunctionAppからアクセスするための機能です
アプリを Azure 仮想ネットワークと統合する - Azure App Service
Q. なぜDBとの連携でVnet統合が必要なのか
Azure上でDatabaseを作成する際、DBインターネットに公開したくないのでプライベートアクセスに制限することがほとんどかと思います。プライベートアクセスに絞ってDBを作成した場合、DBは指定したVnetの中のSubnetに配置されます。その場合、通常はVnet外にいるWebAppやFunctionからDBにアクセスできなくなります。
このようにVnetに隔離されたDBにVnet外のリソースからアクセスするための機能がVnet統合です。Vnet統合では中継用のSubnetを用意してそれをWebAppやFunctionと連携させることでVnet内のリソースにアクセスできるようになります。
DBを作成する
-
Portal上でAzure Database For MySQLのリソースを作成します
以下のドキュメントに従ってDBをデプロイします。かなり丁寧に書かれているのでドキュメントに従えば、DBだけでなくVMの作成とVMからのSSH接続までできるようになります。Azure portal でプライベート アクセスを使用して Azure Database for MySQL フレキシブル サーバーに接続する
参考までに自分の設定も載せておきます。
迷いポイント解説
- dbインスタンスの種類(シングルサーバーor フレキシブルサーバー)
フレキシブルサーバーとシングルサーバーを選ぶように言われますが、ドキュメントでは新規でアプリケーションを作る場合はフレキシブルが推奨とのこと。
またシングルサーバーだとMySQLの8系を選択できなかったりします。 - ネットワークの接続方法
これはプライベートアクセスを選択してください - 仮想ネットワークとSubnet
仮想ネットワークは任意のもの、subnetはdefaultで大丈夫です - コンピューティングとストレージ
試験目的なので一番安いもので大丈夫です。
-
DBインスタンスにアクセスするために、仮想マシンをデプロイする
DB作成時に参照したドキュメントに従ってデプロイします。
実は仮想マシンをデプロイする以外にAzureBastionという機能を使う方法もあります。こちらは楽ではありますがかなり高額なので仮想マシンをお勧めします。ドキュメントにも書いてありますが、仮想マシンは必ず先程作成したDBと同じVnetに所属させてください!DBがプライベートアクセス(同じVnet内からのアクセス)しか受け付けないためです。
-
テスト用のデータベース、テーブルを作成
DBにアクセスしてデータベースとテーブルを作成しますCREATE DATABASE test_database; CREATE TABLE test_table ( id INTEGER AUTO_INCREMENT PRIMARY KEY, message VARCHAR(255), created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );
これでDB側の準備は終わりです!
Function用のSubnetを作成する
Functionと連携する中継用のsubnetをあらかじめ作っておきます。
先程DBに紐付けたvnetのページにいき、サイドメニューの”サブネット”を選択し、サブネットの追加を行います。名前は適当なもので構いません。サブネットアドレス範囲は自動で入力されるので名前さえ入力できればあとは保存して終了です!
Azure Functionsを作成する
-
AzurePortal上でFunctionAppを作成する
Vnet統合が使えるのはFunctions Premium
かAppServiceプランのPremium
以上だけなのでそこだけ注意してください。 -
Azure Functions Core Tools
のfuncコマンドで関数を作成.コマンド実行後、runtimeと言語を聞かれるので今回はnode,typescriptを選択します
func new --name HttpTrigger --template "HTTP trigger" --authlevel "anonymous"
※
Azure Functions Core Tools
のインストール方法はこちらを参照以下のようなディテクトリ構造の雛形が作成されるはずです
. ├── HttpTrigger │ ├── function.json │ └── index.ts ├── dist │ └── HttpTrigger │ ├── index.js │ └── index.js.map ├── host.json ├── local.settings.json ├── package-lock.json ├── package.json └── tsconfig.json
後々必要になるので一旦最低限必要なパッケージをインストールしておきます。
npm i
-
DBに接続するためにmysqlクライアントパッケージを追加
今回は、型定義もあり、async/awaitも使えるのでmysql2をインストールします。npm i mysql2
-
雛形を修正
まず、変更後にprocess.envを利用するので警告が出ないように以下をインストールしておきますnpm i --save-dev @types/node
次に既存の雛形を以下のように修正します。
import { AzureFunction, Context, HttpRequest } from "@azure/functions" import {createConnection, QueryError, RowDataPacket} from 'mysql2/promise'; const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> { // ①接続情報オブジェクトを作成し、コネクションを確立する const config = { host: process.env['DB_HOST'], user: process.env['DB_USER_NAME'], password: process.env['DB_PASSWORD'], database: 'test_database', port: 3306, ssl : { rejectUnauthorized: false } }; const conn = await createConnection(config); //②DBへinsertとselectを行い、selectの結果をresponseとして返す try { const [rows,fields] = await conn.query("insert into test_table (message) values ('Hello,world!')") .catch(e=>{throw Error(e)}); context.log(fields) const [results,] = await conn.query("select * from test_table") .catch(e=>{throw Error(e)}); context.res = { body: { ok:true, data:results } }; } catch(e) { context.res = { body: { ok:false, data:'failed to insert' } }; } finally { conn.end(); } }; export default httpTrigger;
順番に説明していきます。
①接続情報オブジェクトを作成し、コネクションを確立する
ここでは環境変数を参照しつつDBへの接続情報をオブジェクトとしてconfigに格納し、mysql2のcreateConnection関数に渡しDBと接続します。
各種環境変数はAzurePortal上で後ほど設定します。AzureKeyVaultに入れたDBパスワードを参照したい場合はこちらの記事を参考にしてくださいconst config = { host: process.env['DB_HOST'], user: process.env['DB_USER_NAME'], password: process.env['DB_PASSWORD'], database: 'test_database', port: 3306, ssl : { rejectUnauthorized: false } }; const conn = await createConnection(config);
②DBへinsertとselectを行い、selectの結果をresponseとして返す
mysql2ではquery関数に生のSQLを渡すことができるので以下それぞれをquery関数に渡して実行します(代入時はセミコロンは不要です)
-
insert
insert into test_table (message) values ('Hello,world!');
-
select
select * from test_table;
そして、context.resで成功すればselectの結果を,どこかでエラーが出ればエラーメッセージをレスポンスとして返します。
context.resに渡すオブジェクトのbody部分がhttpレスポンスのbodyに相当します。context.res = { body: { ok:true, data:results } };
-
-
デプロイ
ここまででコードは完成したのでデプロイします。
デプロイ前にビルドするのを忘れずにnpm run build func azure functionapp publish ${FunctionsAppの名前}
以下のような表示が出ればデプロイ完了です!
-
AzurePortalで環境変数を設定する
作成した関数アプリのサイドメニューから「構成」を選びます。
そこで”新しいアプリケーション設定”をクリックして,DB_HOST,DB_USER_NAME,DB_PASSWORDを作成します。
実行してみる
デプロイ成功時に表示されたエンドポイントに対してcurlを投げてみます
curl https://${FunctionAppの名前}.azurewebsites.net/api/${関数名}
下記のようにレスポンスのdataにデータの配列が入っていれば成功です!
おわりに
これでAzure FunctionsからDBに対して操作を行えるようになりました!
今回紹介したVnet統合はFunctionだけでなくWebAppでも使えるので同様のやり方でWebAppにデプロイしたAPIサーバーからDBにアクセスするといったこともできます。こちらも後日記事にできればと考えています。お楽しみに!