#mynavi(Azureメモ)
#setlinebreak(on);

* 概要 [#cb2ae081]
#html(<div class="pl10">)
App Service は スケール等にも簡単に対応可能な為、アプリケーションの公開には非常に有用なサービスだが、
Grafana の様にデフォルトでローカルにデータを保存するようなアプリケーションの場合、「''スケール時や再起動時にデータを引き継ぐことが出来ない''」。
そこで、MySQL を使用して外部にデータを保存/利用する方法を記載する。
#html(</div>)

* 目次 [#xaebdc2a]
#contents
- 関連
-- [[Azureメモ]]
-- [[Azure上のGrafanaでAD認証(App Service版)]]
-- [[Azure VM上にGrafanaをデプロイ]]
-- [[Azure上のGrafanaをカスタムドメインで公開]]
-- [[Azure上のGrafanaでAD認証(VM版)]]

* 作成する環境 [#j6868bd3]
#html(<div class="pl10">)

下図のような環境を構築する。

#html(<div class="ib border ml10 mb20">)
&ref(azure_grafana_app_service.png,nolink);
#html(</div>)


** Grafana のローカルDBに MySQL を採用した経緯 [#r52cf943]
#html(<div class="pl10">)

Azure App Service のアプリケーションで使用するファイルを外部化する方法として、Azure Files(ファイル共有) や Azure Disk 等があるが、
Azure Files(ファイル共有) で Grafana の内部データである SQLite のファイルを外部化(共有化)しようとしたが、これは出来なかった(※)。
 ※「Server shutdown" logger=server reason="Service init failed: Migration failed err: database is locked"」 となる。
 ※ Azure Disk は単純にコストの問題で採用を見送った。

調べてみると同様の事象は報告されており(※)、こちらでは AzureDisk を使用して回避したとの事だが、当記事では MySQL を使用して外部化する事とした。
 ※ https://github.com/kubernetes/kubernetes/issues/59755
#html(</div>)

** 仮想マシンについて [#xaba1f5a]
#html(<div class="pl10">)
仮想マシンには以下の環境を構築する。(docker-compose を使用して構築)
- Grafana に表示するグラフデータ用の InfluxDB
- Grafana自体のデータを保持する為の MySQL
#html(</div>)

#html(</div>)


* リソースの作成 [#s32d67cc]
#html(<div class="pl10">)

いつも通りシェル化しておく。(最終的には ARM に移行)

#html(){{
<div id="tabs1">
  <ul>
    <li><a href="#tabs1-0">0_env.sh</a></li>
    <li><a href="#tabs1-1">1_vm_resources.sh</a></li>
    <li><a href="#tabs1-2">2_vm_setup.sh</a></li>
    <li><a href="#tabs1-3">3_app_resources.sh</a></li>
  </ul>
}}

// START tabs1-0
#html(<div id="tabs1-0">)

#mycode2(){{
#!/bin/bash

PREFIX=リソース名の頭に付ける接頭文字
subscriptionId=サブスクリプションID
region=japanwest

# リソースグループ
resourceGroup=${PREFIX}Resources

# 仮想ネットワーク
vnetName=${PREFIX}VNet
vnetPrefix=10.1.0.0/16

# セキュリティグループ名
nsgName=${vnetName}SecGrp
nsgPubRuleName=${nsgName}PubRule

# 仮想マシンを配置するサブネット
vmSubnetName=${PREFIX}VmSubnet
vmSubnetPrefix=10.1.1.0/24

# VNet統合用のサブネット
exSubnetName=${PREFIX}ExSubnet
exSubnetPrefix=10.1.2.0/24

# 仮想マシンの情報
vmName=${PREFIX}Vm
vmImage=UbuntuLTS
vmIpAddress=10.1.1.5
vmUser=sample

# App Service(Grafana)用
registryName=${PREFIX}registry
grafanaImageName=${PREFIX}-grafana
webappName=${PREFIX}-grafana
}}


#html(</div>)
// END tabs1-0

// START tabs1-1
#html(<div id="tabs1-1">)

#mycode2(){{
#!/bin/bash

# 設定の読み込み
source 0_env.sh

# リソース作成
if [ "$1" == "--create" ]; then

  # リソースグループの作成
  echo "az group create"
  az group create --name $resourceGroup --location $region

  # NSG(ネットワークセキュリティグループの)作成
  echo "az network nsg create"
  az network nsg create --resource-group $resourceGroup --name $nsgName

  # InfluxDB接続用のNSGルール  ※TODO: source-port-range は App Service 側の送信IPに合わせて絞っておいた方が良い
  echo "az network nsg rule create(influxdb)"
  az network nsg rule create \
    --resource-group $resourceGroup --nsg-name $nsgName --name ${nsgPubRuleName}1\
    --access Allow --protocol Tcp --direction Inbound --priority 100 \
    --source-address-prefix Internet --source-port-range "*" --destination-port-range "8086"

  # MySQL接続用のNSGルール  ※TODO: source-port-range は App Service 側の送信IPに合わせて絞っておいた方が良い
  echo "az network nsg rule create(mysql)"
  az network nsg rule create \
    --resource-group $resourceGroup --nsg-name $nsgName --name ${nsgPubRuleName}2 \
    --access Allow --protocol Tcp --direction Inbound --priority 101 \
    --source-address-prefix Internet --source-port-range "*" --destination-port-range "3306"

  # SSH接続用のNSGルール
  echo "az network nsg rule create(ssh)"
  az network nsg rule create \
    --resource-group $resourceGroup --nsg-name $nsgName --name ${nsgPubRuleName}3 \
    --access Allow --protocol Tcp --direction Inbound --priority 1000 \
    --source-address-prefix Internet --source-port-range "*" --destination-port-range "22"

  # 仮想ネットワーク 及び サブネット作成
  echo "az network vnet create"
  az network vnet create \
    --name $vnetName --resource-group $resourceGroup \
    --address-prefixes $vnetPrefix --network-security-group $nsgName \
    --subnet-name $vmSubnetName --subnet-prefixes $vmSubnetPrefix

  # App Service とのVNet統合用のサブネットを作成
  echo "az vnet subnet create for vnet integration"
  az network vnet subnet create \
    --name $exSubnetName \
    --resource-group $resourceGroup \
    --vnet-name $vnetName \
    --address-prefixes $exSubnetPrefix

  # 仮想マシンの作成
  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 \
    --vnet-name $vnetName --subnet $vmSubnetName \
    --private-ip-address $vmIpAddress --admin-username $vmUser \
    --public-ip-address-dns-name `echo $vmName | tr '[A-Z]' '[a-z]'` \
    --custom-data 2_vm_setup.sh

  # ポート開放
  echo "az vm open-port(3306)"
  az vm open-port --resource-group $resourceGroup --name $vmName --port 3306 --priority 901
  echo "az vm open-port(8086)"
  az vm open-port --resource-group $resourceGroup --name $vmName --port 8086 --priority 902

  # 生成されたSSH鍵を退避しておく
  mkdir -p pem
  if [ -e ~/.ssh/id_rsa ]; then
    mv ~/.ssh/id_rsa     ./pem/id_rsa_${vmName}
    mv ~/.ssh/id_rsa.pub ./pem/id_rsa_${vmName}.pub
  fi

fi

# リソース削除
if [ "$1" == "--delete" ]; then
  echo az group delete
  az group delete --name $resourceGroup
fi
}}

#html(</div>)
// END tabs1-1

// START tabs1-2
#html(<div id="tabs1-2">)

&color(red){''【注意】''};
 vm create 時の --custom-data で指定するシェルには マルチバイトが含まれていると create に失敗する。
 ※ エラーメッセージ 「'latin-1' codec can't encode characters in position XXX-XXX: ordinal not in range(256)」

#mycode2(){{
#!/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_for_grafana
    container_name: influxdb_for_grafana
    volumes:
      - ./data/influxdb:/var/lib/influxdb
      - ./influxdb.conf:/etc/influxdb/influxdb.conf
    ports:
      - "8086:8086"
  mysql:
    image: mysql:8.0.1
    hostname: mysql_for_grafana
    container_name: mysql_for_grafana
    ports:
      - "3306:3306"
    environment:
      MYSQL_DATABASE: grafana
      MYSQL_USER: grafana
      MYSQL_PASSWORD: grafana
      MYSQL_ROOT_PASSWORD: admin
_EOYML_

mkdir -p data/influxdb
chmod 777 data/influxdb

# 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`
  x=`sudo docker exec -i influxdb_for_grafana 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"
docker exec -i influxdb_for_grafana influx --execute "create database sampledb"
docker exec -i influxdb_for_grafana influx --execute "create user sample with password 'sample' WITH ALL PRIVILEGES"
}}

#html(</div>)
// END tabs1-2

// START tabs1-3
#html(<div id="tabs1-3">)

#mycode2(){{
#!/bin/bash

# リソース名の読み込み
source 0_env.sh

# リソース作成
if [ "$1" == "--create" ]; then

  # コンテナレジストリ作成
  echo "az acr create ( $registryName )"
  az acr create -n $registryName -g $resourceGroup --sku standard --admin-enabled true

  # レジストリユーザ名/パスワード取得
  acr_credential="`az acr credential show --name $registryName -o table | tail -1`"
  registryUser="`echo "$acr_credential" | awk '{print $1}'`"
  registryPwd="`echo "$acr_credential" | awk '{print $2}'`"

  cat <<_EO_DOCKERFILE_>Dockerfile
FROM grafana/grafana:7.1.1
#  オリジナルプラグインのコピー等
#COPY my_plungin/ /var/lib/grafana/plugins/my_plungin/
_EO_DOCKERFILE_

  # DockerイメージをビルドしてACRに登録
  echo "az acr build ( $grafanaImageName )"
  az acr build --registry $registryName --image ${grafanaImageName} .

  # App Service プラン作成 ( Standard プラン以上でないと VNet統合が使用できない )
  echo "az appservice plan create ( ${webappName}-plan )"
  az appservice plan create -n ${webappName}-plan -g $resourceGroup --is-linux --sku S1

 # App Service アプリ作成(Grafana)
  echo "az webapp create ( $webappName )"
  az webapp create -g $resourceGroup -p ${webappName}-plan -n $webappName \
      -i ${registryName}.azurecr.io/${grafanaImageName}:latest
      --docker-registry-server-user ${registryUser} \
      --docker-registry-server-password ${registryPwd}
  # docker-compose だとVNet統合がうまく認識されない
  # (2020/8時点で compose 対応はプレビューらしので素直に単一Dockerで行う)
  #    --multicontainer-config-type compose \
  #    --multicontainer-config-file docker-compose.yml \

  # VNet統合の作成
  echo "az webapp vnet-integration add"
  az webapp vnet-integration add \
    --name $webappName \
    --resource-group $resourceGroup \
    --subnet $exSubnetName \
    --vnet $vnetName

  # App Service 環境変数設定
  echo "az webapp config appsettings ( $webappName )"
  az webapp config appsettings set \
      -g $resourceGroup -n $webappName \
      --settings "GF_SERVER_ROOT_URL=https://${webappName}.azurewebsites.net" \
      "GF_SECURITY_ADMIN_PASSWORD=admin" "GF_DATABASE_TYPE=mysql" "GF_DATABASE_HOST=${vmIpAddress}:3306" \
      "GF_DATABASE_NAME=grafana" "GF_DATABASE_USER=grafana" "GF_DATABASE_PASSWORD=grafana" \
      "WEBSITES_PORT=3000"

  # コンテナロギングをONにする ( コンテナログがポータルから確認できる )
  echo "az webapp log config"
  az webapp log config --name $webappName --resource-group $resourceGroup --docker-container-logging filesystem --level information

  # 再起動
  echo "webapp restart"
  az webapp restart -n $webappName -g $resourceGroup

  echo "Application URL: https://${webappName}.azurewebsites.net"
fi

# リソース削除(App Service関連のみ)
if [ "$1" == "--delete" ]; then
  echo "delete webapp"
  az webapp delete -n $webappName -g $resourceGroup
  echo "delete appservice plan"
  az appservice plan delete -y -n ${webappName}-plan -g $resourceGroup
  echo "delete acr"
  az acr delete -y -n $registryName -g $resourceGroup
fi
}}



#html(</div>)
// END tabs1-3


#html(</div>)
// END tabs1

#html(<script>$(function() { $("#tabs1").tabs(); });</script>)

** VM関連のリソースを作成 [#h492e87b]
#myterm2(){{
./1_vm_resources.sh --create
}}

** App Service のリソースを作成 [#c4e8cf3a]
#myterm2(){{
./3_app_resources.sh --create
}}


#html(</div>)

* 動作確認 [#n9269eef]
#html(<div class="pl10">)

https&#58;//${webappName}.azurewebsites.net にアクセスして動作確認。

#html(</div>)

トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS