#mynavi(Azureメモ) #setlinebreak(on); #html(){{ <style> .lh2,.lh2 * { line-height: 1.5;} </style> }} * 概要 [#scb27b26] #html(<div class="pl10 lh2">) 仮想マシンに 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上のGrafanaをカスタムドメインで公開]] * 作成する環境 [#j6868bd3] #html(<div class="pl10">) 下図のような環境を構築する。 #TODO #html(<div class="ib border ml10 mb20">) &ref(azure_grafana_vm2.png,nolink); #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> </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" az group create --name $resourceGroup --location $region # NSG(ネットワークセキュリティグループの)作成 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" 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" 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 # 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" 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 #-------------------------------------- # 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: 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: - ./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_ #-------------------------------------- # start containers #-------------------------------------- docker-compose up -d }} #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>) ** wait until azure dns name is available. の処理内容 [#b0722aa7] #html(<div class="pl10">) パプリック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 }} #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>)