#author("2020-08-15T17:44:12+00:00","","")
#mynavi(Azureメモ)
#setlinebreak(on);

#html(){{
<style>
.lh2,.lh2 * { line-height: 1.5;}
</style>
}}

* 概要 [#scb27b26]
#html(<div class="pl10">)
#html(<div class="pl10 lh2">)

仮想マシンに Grafana をインストールして公開する手順。

&color(red){''【注意】''};
 この記事で作成している構成はVM(Grafana)自体のスケールにも、SSL通信にも対応していない。
 これらを考慮する場合の構成は、[[Azure VM上のGrafanaをカスタムドメインで公開]] を参照。

構築内容は以下の通り。
- Azureが提供する DNS 名を使用して SSLでアクセス可能なサーバを構築する。
- SSL証明書には Lets Encrypt を使用。(90日後に無効になるので更新が必要)
- Grafana のローカルDB は MySQL を採用。( Grafana をスケールを考慮 )
- サンプルデータ作成用に Influxdb も入れているが、当記事内では使用していない。
- スケールやカスタムドメイン対応については、[[Azure上のGrafanaをカスタムドメインで公開]] を参照。
#html(</div>)

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

* TODO [#kb552ad8]
* 作成する環境 [#j6868bd3]
#html(<div class="pl10">)
 ・仮想マシン
  ・docker
   ・InfluxDB
    ・マウント設定(データディレクトリ)
   ・Grafana
    ・マウント設定(ローカルDB)
    ・マウント設定(pluginsディレクトリ)

下図のような環境を構築する。
#html(<div class="ib border ml10 mb20">)
&ref(azure_grafana_vm2.png,nolink);
#html(</div>)


* 作成する環境 [#j6868bd3]
#html(<div class="pl10">)
下図のような環境を構築する。
#TODO

#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>
  </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

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


#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
  echo "az group create"
  az group create --name $resourceGroup --location $region

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

  # Grafana用のNSGルール
  echo "az network nsg rule create(grafana)"
  az network nsg rule create \
    --resource-group $resourceGroup --nsg-name $nsgName --name ${nsgPubRuleName}Http \
    --access Allow --protocol Tcp --direction Inbound --priority 120 \
    --source-address-prefix Internet --source-port-range "*" --destination-port-range "80"

  echo "az network nsg rule create(grafana)"
  az network nsg rule create \
    --resource-group $resourceGroup --nsg-name $nsgName --name ${nsgPubRuleName}Https \
    --access Allow --protocol Tcp --direction Inbound --priority 130 \
    --source-address-prefix Internet --source-port-range "*" --destination-port-range "443"

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

  # 仮想ネットワーク 及び サブネット作成
  echo az network vnet create
  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

  # 仮想マシンの作成
  rm -rf  ~/.ssh/id_rsa
  rm -rf  ~/.ssh/id_rsa.pub
  echo az vm create
  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

  # ポート開放
  az vm open-port --resource-group $resourceGroup --name $vmName --port 3000
  # VMポート開放
  echo "az vm open-port(3306)"
  az vm open-port --resource-group $resourceGroup --name $vmName --port 3306 --priority 910
  echo "az vm open-port(8086)"
  az vm open-port --resource-group $resourceGroup --name $vmName --port 8086 --priority 920
  echo "az vm open-port(80)"
  az vm open-port --resource-group $resourceGroup --name $vmName --port 80 --priority 930
  echo "az vm open-port(443)"
  az vm open-port --resource-group $resourceGroup --name $vmName --port 443 --priority 940

  # 生成された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

  lowerVmName=`echo $vmName | tr [A-Z] [a-z]`
  echo "Grafana URL: https://${lowerVmName}.${region}.cloudapp.azure.com"
fi

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

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

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

このシェルはVMの作成時に実行される。( az vm create の custom-data として指定しているもの )
※日本語が含まれているとアップロード出来ない為、全て半角で入力している。( 'latin-1' codec can't encode characters in position XXX-XXX: ordinal not in range(256) と怒られる )
※詳細は後述の 「[[処理内容の補足>#m271e507]]」 を参照。

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

#--------------------------------------
# create work directory and cd.
#--------------------------------------
mkdir /tmp/docker_container && cd /tmp/docker_container

#--------------------------------------
# install docker and docker compose.
#--------------------------------------
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

#--------------------------------------
# create influxdb.conf.
#--------------------------------------
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_

#--------------------------------------
# set environments.
#--------------------------------------
# first vm
mysql_ip="172.1.0.5"

# Second and subsequent VMs ( Specify the IP of the first VM )
#mysql_ip=$vmIpAddress

# grafana server url
region=japanwest
hostname=`hostname | tr [A-Z] [a-z]`
server_name="${hostname}.${region}.cloudapp.azure.com"

#--------------------------------------
# wait until azure dns name is available.
# (If it is NG after waiting 5 minutes, give up..)
#--------------------------------------
for i in `seq 30`; do
  x=`host $server_name`
  if [ "$?" == "0" ]; then
    break
  fi
  sleep 10
done

#--------------------------------------
# get ssl certificate.
#--------------------------------------
sudo docker run --rm -p 80:80 \
    -v /etc/letsencrypt:/etc/letsencrypt \
    -v /etc/letsencrypt/logs:/var/log/letsencrypt \
    certbot/certbot certonly --standalone \
    -d $server_name \
    --register-unsafely-without-email \
    --non-interactive --agree-tos \
    --force-renewal \
    --renew-by-default \
    --preferred-challenges http

#--------------------------------------
# create default.conf for nginx.
#--------------------------------------
cat <<_EO_DEFAULT_CONF_>default.conf
upstream backend {
    server grafana_container:3000;
}

# http
server{
    listen 80;
    listen [::]:80;
    server_name ${server_name};
    return 301 https://\$host\$request_uri;
}

# https
server{
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name  ${server_name};
    ssl_certificate     /etc/letsencrypt/live/${server_name}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/${server_name}/privkey.pem;
    proxy_set_header    Host               \$host;
    proxy_set_header    X-Real-IP          \$remote_addr;
    proxy_set_header    X-Forwarded-Host   \$host;
    proxy_set_header    X-Forwarded-Server \$host;
    proxy_set_header    X-Forwarded-For    \$proxy_add_x_forwarded_for;

    location / {
        proxy_pass http://backend;
    }
}
_EO_DEFAULT_CONF_

#--------------------------------------
# create directories for docker volumes.
#--------------------------------------
mkdir -p data/influxdb && chmod 777 data/influxdb
mkdir -p data/mysql && chmod 777 data/mysql

#--------------------------------------
# create setup command for influxdb.
#--------------------------------------
cat <<_EO_INFLUXDB_ >influxdb_setup.sh
influx --execute "create database sampledb"
influx --execute "create user sample with password 'sample' WITH ALL PRIVILEGES"
_EO_INFLUXDB_

#--------------------------------------
# create Dockerfile for grafana.
#--------------------------------------
cat <<_EO_GRAFANA_DOCKERFILE_ >Dockerfile-grafana
FROM grafana/grafana:7.1.1
COPY ./grafana-delay-start.sh /grafana-delay-start.sh
_EO_GRAFANA_DOCKERFILE_

#--------------------------------------
# create startup script for grafana.
#--------------------------------------
cat <<_EO_GRAFANA_INIT_SH_>grafana-delay-start.sh
#!/bin/bash

# Wait for mysql to start (waiting time is rough)
sleep 30

# Start grafana
exec /run.sh

_EO_GRAFANA_INIT_SH_

chmod 755 grafana-delay-start.sh

#--------------------------------------
# create docker-compose.yml
#--------------------------------------
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
      - ./influxdb_setup.sh:/docker-entrypoint-initdb.d/setup.sh
    ports:
      - "8086:8086"
    networks:
      mynetwork:
        ipv4_address: 172.1.0.4
    restart: always
  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
    volumes:
      - ./data/mysql:/var/lib/mysql
    networks:
      mynetwork:
        ipv4_address: 172.1.0.5
    restart: always
  grafana:
    image: grafana/grafana:7.1.1
    hostname: grafana_sample
    container_name: grafana_sample
    build:
      context: .
      dockerfile: Dockerfile-grafana
    hostname: grafana_container
    container_name: grafana_container
    depends_on:
      - mysql
    ports:
      - "3000:3000"
    environment:
      GF_SERVER_ROOT_URL: "https://${server_name}"
      GF_SECURITY_ADMIN_PASSWORD: "admin"
      GF_DATABASE_TYPE: "mysql"
      GF_DATABASE_HOST: "${mysql_ip}:3306"
      GF_DATABASE_NAME: "grafana"
      GF_DATABASE_USER: "grafana"
      GF_DATABASE_PASSWORD: "grafana"
    entrypoint:
      - /grafana-delay-start.sh
    networks:
      mynetwork:
        ipv4_address: 172.1.0.6
    restart: always
  nginx:
    image: nginx:latest
    hostname: grafana_proxy
    container_name: grafana_proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./data/grafana:/var/lib/grafana
    environment:
      GF_SECURITY_ADMIN_PASSWORD: admin
      - ./default.conf:/etc/nginx/conf.d/default.conf
      - /etc/letsencrypt:/etc/letsencrypt
    networks:
      mynetwork:
        ipv4_address: 172.1.0.7
    restart: always

networks:
  mynetwork:
    driver: bridge
    ipam:
      config:
        - subnet: 172.1.0.0/24
_EOYML_

mkdir -p data/influxdb && chmod 777 data/influxdb
mkdir -p data/grafana && chmod 777 data/grafana

# start influxdb
#--------------------------------------
# start containers
#--------------------------------------
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"
}}

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


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

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

** リソースを作成 [#h492e87b]
#myterm2(){{
./1_vm_resources.sh --create
  :
  :
Grafana URL: https://VM名.リージョン.cloudapp.azure.com
}}

** リソースを削除 [#q9822b32]
#myterm2(){{
./1_vm_resources.sh --delete
}}

#html(</div>)

* 処理内容の補足 [#m271e507]
#html(<div class="pl10 lh2">)

2_vm_setup.sh はVMの作成時に実行されるものだが、いろいろやっていたら結構大きなスクリプトになってしまった為、別途解説を記載しておく。

** set environments. の処理内容 [#m4857d22]
#html(<div class="pl10">)

以下では grafana のDBとして使用する MySQL のIPアドレスをしてしている。
当記事で作成している環境では Grafana と MySQL は同じ VM の docker ネットワークに存在している為、docker ネットワーク内の IP を指定している。(docker ホスト名だとなぜか繋がらない)
#mycode2(){{
mysql_ip="172.1.0.5"

# Second and subsequent VMs ( Specify the IP of the first VM )
#mysql_ip=$vmIpAddress

# grafana server url
region=japanwest
hostname=`hostname | tr [A-Z] [a-z]`
server_name="${hostname}.${region}.cloudapp.azure.com"
}}

#html(</div>)

* 動作確認 [#n9269eef]
** wait until azure dns name is available. の処理内容 [#b0722aa7]
#html(<div class="pl10">)

VMのIPアドレスを確認
#myterm2(){{
az vm list-ip-addresses -o table
パプリックIPが有効なVMを作成した場合は、「VM名.リージョン.cloudapp.azure.com」 というDNS名が割り当てられるが、
ここでは、DNS名が名前解決できるようになるまで待っている。(名前解決できない状態だと、この後の Lets Encrypt でSSL証明書を取得する際にコケる  )
※5分待ってもダメな時は諦めている。(その場合、Lets Encrypt の処理でコケるので、証明書が作られず nginx が機能しない )

#mycode2(){{
for i in `seq 30`; do
  x=`host $server_name`
  if [ "$?" == "0" ]; then
    break
  fi  
  sleep 10
done
}}

あとは http://確認したアドレス:3000 にアクセスして動作を確認するだけ。
#html(</div>)

** get ssl certificate. の処理内容 [#nc7d3245]
#html(<div class="pl10">)

コンテナ版の certbot を使用して Lets Encrypt の SSL証明書を取得している。
実行時には 80 番ポートが通信できる状態になっている必要がある為、nginx の起動前に行っている。
※各引数の説明は https://certbot.eff.org/docs/using.html を参照。

#mycode2(){{
sudo docker run --rm -p 80:80 \
    -v /etc/letsencrypt:/etc/letsencrypt \
    -v /etc/letsencrypt/logs:/var/log/letsencrypt \
    certbot/certbot certonly --standalone \
    -d $server_name \
    --register-unsafely-without-email \
    --non-interactive --agree-tos \
    --force-renewal \
    --renew-by-default \
    --preferred-challenges http
}}

#html(</div>)

** create default.conf for nginx. の処理内容 [#oa5f58bc]
#html(<div class="pl10">)

ここでは nginx 用の設定ファイルを作成している。

- 80番ポート(HTTP)へのアクセスは 443 にリダイレクト。
- 443番ポート(HTTPS) へにアクセスは grafana の 3000番ポートに転送。
- SSL証明書周りのファイルは Lets Encrypt で取得したディレクトリを参照させている。( ディレクトリは docker volumes で マウント )
※grafana は 同じ docker ネットワーク内にある為、docker-compose で指定したサーバ名を指定している。

#mycode2(){{
cat <<_EO_DEFAULT_CONF_>default.conf
upstream backend {
    server grafana_container:3000;
}

# http
server{
    listen 80;
    listen [::]:80;
    server_name ${server_name};
    return 301 https://\$host\$request_uri;
}

# https
server{
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name  ${server_name};
    ssl_certificate     /etc/letsencrypt/live/${server_name}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/${server_name}/privkey.pem;
    :
    location / {
        proxy_pass http://backend;
    }
}
_EO_DEFAULT_CONF_
}}

#html(</div>)

** create Dockerfile for grafana  及び create startup script for grafana の処理内容 [#lb2f0696]
#html(<div class="pl10 lh2">)

ここでは Grafana用の Dockerfile 及び 起動スクリプトを作成している。
Grafana のローカルDB として MySQL を使用する為、MySQL が使用可能になるまで待ってから Grafana を起動しているだけ。(待ち時間はザックリ)
docker-compose.yml では depend_on で起動順を制御してはいるが、これはあくまでもコンテナの起動順を制御するだけなので、対象のDBが使用可能であるかどうかは、別途チェックする必要がある為。
※ https://docs.docker.jp/compose/startup-order.html で示されているように厳密にチェックできなくもないが、ここではこれで十分と判断。

#html(</div>)

#html(</div>)


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

リソース作成時に表示された Grafana URL にアクセスして確認するだけ。
パブリックIPを有効な状態で作成したVMは 「VM名.リージョン.cloudapp.azure.com」というDNS名が割り当てられる。
※ VM名は全て小文字に変換。

#html(</div>)


トップ   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS