1. きっかけと本記事の概要
担当案件で障害が発生したが、営業日外では対応が難しい。
しかし放置しておくとユーザー影響など取り返しがつかない被害が・・・
そこである条件をトリガーにEC2インスタンスを自動再起動するような設定をしたいと思った。
※実際にはサーバーの再起動後にプロセスの自動起動も必要なのだがそこは置いておいて。
サーバーの自動再起動に妥当な条件を設定するためには、実績としての過去の監視データが必要だ。
過去のデータがない状態で再起動の条件を設定しても根拠がない。
過去データから条件を検討し、誤作動する可能性や誤作動してしまったユーザー影響との兼ね合いも考えつつ納得のいく設定をしたいところだ。
1年間はほしい。その上で検討したい。
本記事では、以下の要件を満たす監視システムを構築するにあたって収集した情報のまとめ、および実際に構築した記録である。
若干案件に依存した要件になっているが、そこは適宜置き換えることで他のAWSインスタンスの監視や自動再起動設定にも応用が効くだろう。
2. システム要件
前提
監視対象EC2インスタンスは「app」、「worker」、「es」という役割を持ったグループに所属し、監視したいプロセスが異なる。
時間帯に応じて再起動する・しない判断もいずれはさせたいが、ロジックの検討、実装に対するコスパがあまりよくないので今は置いておく。
以上を踏まえ、2つの主な要件がある。
2.1. 自動再起動条件の検討のための監視データ収集ができること
2.1.1. EC2インスタンスに対する以下の監視項目のデータを1年間以上収集できること
- CPU使用状況(システム全体、プロセス単位)
- 物理メモリ領域使用状況(システム全体、プロセス単位)
- スワップ領域使用状況(システム全体、プロセス単位)
- 死活監視(プロセス単位)
- ディスク使用率(サーバー自動再起動には不要だが、おまけ)
プロセス単位で見る項目は以下を対象にする
- 全グループ共通
- sshd
- app
- nginx
- unicorn
- worker
- sidekiq
- cron
- es
- elasticsearch
2.1.2 収集した監視データについては後からグラフで視覚的に確認できる状態であること
2.2. 自動再起動の設定
2.1. で収集した監視データに基づいて適切な閾値を設定し、EC2を再起動できること
3. 情報収集
まず、AWSの各サービスを監視するにはCloudWatch(AWSの各サービスを監視するサービス)を使うのが一般的らしい。
4. 最終的に目指す「EC2監視・自動再起動システム」の構成図
5. システムの設定
上記のシステムを実現するには以下が必要になる。
5.1. EC2内でのカスタムメトリクスの採取およびCloudWatchへの送信
- SSMがEC2へ、EC2がCloudWatchへアクセスするためのIAMロール設定
- SSMエージェントのインストール
- Cloudwatchエージェントのインストール
- CloudWatchエージェントの設定
- collectdのセットアップ
- CloudWatchエージェントの起動
5.2. Lambda関数の設定
5.1. EC2内でのカスタムメトリクスの採取およびCloudWatchへの送信
参考記事:http://blog.serverworks.co.jp/tech/2020/01/28/cloudwtach-agent-ssm-centos/
5.1.1. SSMがEC2へ、EC2がCloudWatchへアクセスするためのIAMロール設定
5.1.1.1. IAMロールの作成
IAMまとめ-ロールの作成に従って実施。ポリシーは以下を選択する。
- CloudWatchAgentAdminPolicy
- CloudWatchAgentServerPolicy
- AmazonSSMManagedInstanceCore
5.1.1.2. IAMロールのアタッチ
IAMまとめ-ロールのアタッチに従って実施。
ロールをアタッチするEC2インスタンスはカスタムメトリクス採取対象のもの。
5.1.2. SSMエージェントのインストール
SSMまとめ-SSMエージェントのセットアップに従って実施。
5.1.3. Cloudwatchエージェントのインストール
SSMまとめ-SSMコンソールからEC2へ任意のエージェントをセットアップに従って実施。
5.1.4. CloudWatchエージェントの設定
CloudWatchまとめ-CloudWatchエージェントの設定に従って実施。
ウィザードへは以下のように回答した。
=============================================================
= Welcome to the AWS CloudWatch Agent Configuration Manager =
=============================================================
On which OS are you planning to use the agent?
1. linux
2. windows
default choice: [1]:
1< EC2内のOSはUbuntuなので
Trying to fetch the default region based on ec2 metadata...
Are you using EC2 or On-Premises hosts?
1. EC2
2. On-Premises
default choice: [1]:
1< 監視対象はEC2インスタンスなので
Which user are you planning to run the agent?
1. root
2. cwagent
3. others
default choice: [1]:
1< 特にデフォルトから変える理由がなかったので
Do you want to turn on StatsD daemon?
1. yes
2. no
default choice: [1]:
2 < StatsDは使わないので(理由は後述)
Do you want to monitor metrics from CollectD?
1. yes
2. no
default choice: [1]:
1 < システム全体のメモリ使用率とディスク使用率がほしく、CollectDで取得できるので
Do you want to monitor any host metrics? e.g. CPU, memory, etc.
1. yes
2. no
default choice: [1]:
1 < 上の回答と同様
Do you want to monitor cpu metrics per core? Additional CloudWatch charges may apply.
1. yes
2. no
default choice: [1]:
2 < 別にコア別のまではCPUのメトリクスいらないので
Do you want to add ec2 dimensions (ImageId, InstanceId, InstanceType, AutoScalingGroupName) into all of your metrics if the info is available?
1. yes
2. no
default choice: [1]:
2 < ディメンジョン増やしすぎると、CloudWatchからAPIでデータ取得するときめんどくさいことになるので
Would you like to collect your metrics at high resolution (sub-minute resolution)? This enables sub-minute resolution for all metrics, but you can customize for specific metrics in the output json file.
1. 1s
2. 10s
3. 30s
4. 60s
default choice: [4]:
4 < カスタムメトリクスの最長間隔は1分だけどそれで十分なので。っていうか1分にしとかないと2週間すらCloudWatch上でデータを確認することができなくなるので。
Which default metrics config do you want?
1. Basic
2. Standard
3. Advanced
4. None
default choice: [1]:
2 < システム全体でのスワップがあったかを見ておきたいが、Basicだと取れないから
Current config as follows:
{
"agent": {
"metrics_collection_interval": 60,
"run_as_user": "root"
},
"metrics": {
"metrics_collected": {
"collectd": {
"metrics_aggregation_interval": 60
},
"cpu": {
"measurement": [
"cpu_usage_idle",
"cpu_usage_iowait",
"cpu_usage_user",
"cpu_usage_system"
],
"metrics_collection_interval": 60,
"totalcpu": false
},
"disk": {
"measurement": [
"used_percent",
"inodes_free"
],
"metrics_collection_interval": 60,
"resources": [
"*"
]
},
"diskio": {
"measurement": [
"io_time"
],
"metrics_collection_interval": 60,
"resources": [
"*"
]
},
"mem": {
"measurement": [
"mem_used_percent"
],
"metrics_collection_interval": 60
},
"swap": {
"measurement": [
"swap_used_percent"
],
"metrics_collection_interval": 60
}
}
}
}
Are you satisfied with the above config? Note: it can be manually customized after the wizard completes to add additional items.
1. yes
2. no
default choice: [1]:
1 < これまでのウィザードで回答してきた選択肢によって作られた上記の設定値に満足しているので
Do you have any existing CloudWatch Log Agent (http://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AgentReference.html) configuration file to import for migration?
1. yes
2. no
default choice: [2]:
2 < 既存のCloudWatch Logエージェントの設定ファイルをもっていないので
Do you want to monitor any log files?
1. yes
2. no
default choice: [1]:
2 < CloudWatch Logsへ収集したいログはないので
Saved config file to /opt/aws/amazon-cloudwatch-agent/bin/config.json successfully.
Current config as follows:
{
"agent": {
"metrics_collection_interval": 60,
"run_as_user": "root"
},
"metrics": {
"metrics_collected": {
"collectd": {
"metrics_aggregation_interval": 60
},
"cpu": {
"measurement": [
"cpu_usage_idle",
"cpu_usage_iowait",
"cpu_usage_user",
"cpu_usage_system"
],
"metrics_collection_interval": 60,
"totalcpu": false
},
"disk": {
"measurement": [
"used_percent",
"inodes_free"
],
"metrics_collection_interval": 60,
"resources": [
"*"
]
},
"diskio": {
"measurement": [
"io_time"
],
"metrics_collection_interval": 60,
"resources": [
"*"
]
},
"mem": {
"measurement": [
"mem_used_percent"
],
"metrics_collection_interval": 60
},
"swap": {
"measurement": [
"swap_used_percent"
],
"metrics_collection_interval": 60
}
}
}
}
Please check the above content of the config.
The config file is also located at /opt/aws/amazon-cloudwatch-agent/bin/config.json.
Edit it manually if needed.
Do you want to store the config in the SSM parameter store?
1. yes
2. no
default choice: [1]:
1 < パラメータストアに保存して、パラメータストア上で編集したいので
What parameter store name do you want to use to store your config? (Use 'AmazonCloudWatch-' prefix if you use our managed AWS policy)
default choice: [AmazonCloudWatch-linux]
<パラメータストア上に保存する設定ファイルの名前を入力>
Trying to fetch the default region based on ec2 metadata...
Which region do you want to store the config in the parameter store?
default choice: [ap-northeast-1]
ap-northeast-1 < 東京リージョンなので
Which AWS credential should be used to send json config to parameter store?
1. XXXXXXXXXXXXXX(From SDK)
2. Other
default choice: [1]:
1 < ここはよくわかんなかった・・・でもデフォルトでできた。
Successfully put config to parameter store XXXXXXXX
Program exits now.
パラメータストア上でprocstat(後述)でプロセス単位のカスタムメトリクス設定も追加し、
最終的にはCloudWatchエージェントの設定ファイルは以下の形になった。
{
"agent": {
"metrics_collection_interval": 60,
"run_as_user": "root"
},
"metrics": {
"metrics_collected": {
"procstat": [
{
"pattern": "sshd.*-D",
"measurement": [
"cpu_usage",
"memory_rss",
"memory_swap",
"pid_count"
]
},
{
"pattern": "^cron$",
"measurement": [
"cpu_usage",
"memory_rss",
"memory_swap",
"pid_count"
]
},
{
"pattern": "^unicorn",
"measurement": [
"pid_count"
]
},
{
"pattern": "^unicorn.*master",
"measurement": [
"cpu_usage",
"memory_rss",
"memory_swap"
]
},
{
"pattern": "^unicorn.*worker\\[0\\]",
"measurement": [
"cpu_usage",
"memory_rss",
"memory_swap"
]
},
{
"pattern": "^unicorn.*worker\\[1\\]",
"measurement": [
"cpu_usage",
"memory_rss",
"memory_swap"
]
},
{
"pattern": "^unicorn.*worker\\[2\\]",
"measurement": [
"cpu_usage",
"memory_rss",
"memory_swap"
]
},
{
"pattern": "^unicorn.*worker\\[3\\]",
"measurement": [
"cpu_usage",
"memory_rss",
"memory_swap"
]
},
{
"pattern": "^unicorn.*worker\\[4\\]",
"measurement": [
"cpu_usage",
"memory_rss",
"memory_swap"
]
},
{
"pattern": "^nginx",
"measurement": [
"pid_count"
]
},
{
"pattern": "^nginx.*master",
"measurement": [
"cpu_usage",
"memory_rss",
"memory_swap"
]
},
{
"pattern": "^nginx.*worker",
"measurement": [
"cpu_usage",
"memory_rss",
"memory_swap"
]
},
{
"pattern": "sidekiq.*busy",
"measurement": [
"cpu_usage",
"memory_rss",
"memory_swap",
"pid_count"
]
},
{
"pattern": "/usr/bin/java.*elasticsearch",
"measurement": [
"cpu_usage",
"memory_rss",
"memory_swap",
"pid_count"
]
}
],
"collectd": {
"metrics_aggregation_interval": 60
},
"cpu": {
"measurement": [
"cpu_usage_idle",
"cpu_usage_iowait",
"cpu_usage_user",
"cpu_usage_system"
],
"metrics_collection_interval": 60,
"resources": [
"*"
],
"totalcpu": false
},
"disk": {
"measurement": [
"used_percent",
"inodes_free"
],
"metrics_collection_interval": 60,
"resources": [
"*"
]
},
"diskio": {
"measurement": [
"io_time"
],
"metrics_collection_interval": 60,
"resources": [
"*"
]
},
"mem": {
"measurement": [
"mem_used_percent"
],
"metrics_collection_interval": 60
},
"swap": {
"measurement": [
"swap_used_percent"
],
"metrics_collection_interval": 60
}
}
}
}
5.1.5. collectdのセットアップ
CloudWatchまとめ-3. CloudWatchエージェントの設定-3.3. collectdのセットアップに従って実施。
5.1.6. CloudWatchエージェントの起動
SSMまとめ-SSMコンソールからEC2の任意のエージェントを起動に従って実施。
なお、パラメータストアからCloudWatchエージェントに適用した設定ファイルは
/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.d/ssm_<設定ファイル名>
として監視対象のEC2インスタンス内に保存されている模様。
5.2. Lambda関数の設定
Lambdaまとめ-使い方-Lambda関数の新規作成に従って実施。
主な設定は以下のようにした。
- トリガー
- AWSサービス:CloudWatch Events/EventBridge
- 条件:スケジュール式(0 16 * * ? *) ※UTC時間で設定する(すなわちJSTの9h前)にする必要あり
- 基本設定
- メモリ:1024MB
- タイムアウト:5分
- 実行ロール
- ポリシーテンプレート
- AmazonS3FullAccessf
- CloudWatchReadOnlyAccess
- ポリシーテンプレート
- 関数コード
import os
import json
import boto3
from botocore.exceptions import ClientError
import csv
import operator
import pathlib
from datetime import datetime, date, timedelta, timezone
# 保存先のS3バケット名
BUCKET_NAME = "<バケット名>"
# メトリクス統計のCSVを保存するS3バケットのベースディレクトリ
BASE_DIR_NAME = "cloudwatch_metrics"
# 監視対象のインスタンス表示名
## appサーバグループのインスタンス表示名群
app_hosts = [{"InstanceName":"<インスタンス名>", "InstanceId":"<インスタンスID>"}]
## workerサーバグループのインスタンス表示名群
worker_hosts = [{"InstanceName":"<インスタンス名>", "InstanceId":"<インスタンスID>"}]
## esサーバグループのインスタンス表示名群
es_hosts = [{"InstanceName":"<インスタンス名>", "InstanceId":"<インスタンスID>"}]
# 監視対象プロセスの実行コマンド内文字列パターン
PROCESS_PATTERN_SSHD = "sshd.*-D"
PROCESS_PATTERN_SIDEKIQ = "sidekiq.*busy"
PROCESS_PATTERN_CRON = "^cron$"
PROCESS_PATTERN_NGINX = "^nginx"
PROCESS_PATTERN_NGINX_MASTER = "^nginx.*master"
PROCESS_PATTERN_NGINX_WORKER = "^nginx.*worker"
PROCESS_PATTERN_UNICORN = "^unicorn"
PROCESS_PATTERN_UNICORN_MASTER = "^unicorn.*master"
PROCESS_PATTERN_UNICORN_WORKER_0 = "^unicorn.*worker\[0\]"
PROCESS_PATTERN_UNICORN_WORKER_1 = "^unicorn.*worker\[1\]"
PROCESS_PATTERN_UNICORN_WORKER_2 = "^unicorn.*worker\[2\]"
PROCESS_PATTERN_UNICORN_WORKER_3 = "^unicorn.*worker\[3\]"
PROCESS_PATTERN_UNICORN_WORKER_4 = "^unicorn.*worker\[4\]"
PROCESS_PATTERN_ELASTICSEARCH = "/usr/bin/java.*elasticsearch"
# 監視対象プロセス名
PROCESS_NAME_SSHD = "sshd"
PROCESS_NAME_SIDEKIQ = "bundle"
PROCESS_NAME_CRON = "cron"
PROCESS_NAME_NGINX = "nginx"
PROCESS_NAME_UNICORN = "ruby"
PROCESS_NAME_ELASTICSEARCH = "java"
# 監視対象プロセスのファイル名
FILE_NAME_SSHD = "sshd"
FILE_NAME_SIDEKIQ = "sidekiq"
FILE_NAME_CRON = "cron"
FILE_NAME_NGINX = "nginx"
FILE_NAME_NGINX_MASTER = "nginx_master"
FILE_NAME_NGINX_WORKER = "nginx_worker"
FILE_NAME_UNICORN = "unicorn"
FILE_NAME_UNICORN_MASTER = "unicorn_master"
FILE_NAME_UNICORN_WORKER_0 = "unicorn_worker_0"
FILE_NAME_UNICORN_WORKER_1 = "unicorn_worker_1"
FILE_NAME_UNICORN_WORKER_2 = "unicorn_worker_2"
FILE_NAME_UNICORN_WORKER_3 = "unicorn_worker_3"
FILE_NAME_UNICORN_WORKER_4 = "unicorn_worker_4"
FILE_NAME_ELASTICSEARCH = "elasticsearch"
# 本lamda関数は1:00(JST)(lambda内ではUTCであり、前日の16:00の時刻になる)に実行する想定
# メトリクス取得対象日の日時
today = datetime.today()
# メトリクス取得対象日前日の日時
yesterday = today - timedelta(days=1)
# データ取得開始日時
start_time = datetime.strftime(yesterday, '%Y-%m-%dT15:00:00Z')
# データ取得終了日時
end_time = datetime.strftime(today, '%Y-%m-%dT15:00:00Z')
# lambda内でに一次的に出力するCSVファイルのパス
temp_csv_file_path = "/tmp/tmp_" + datetime.strftime(today, '%Y%m%d%H%M%S') + ".csv"
# メインメソッド
def lambda_handler(event, context):
print("バケット名:" + BUCKET_NAME)
print("本日の日付:" + str(today))
print("昨日の日付:" + str(yesterday))
print("ファイル名に含む日付部分文字列" + datetime.strftime(today, '%Y%m%d'))
print("データ取得開始日時(UTC):" + start_time)
print("データ取得終了日時(UTC):" + end_time)
# 全インスタンス表示名群
all_hosts = []
all_hosts.extend(app_hosts)
all_hosts.extend(worker_hosts)
all_hosts.extend(es_hosts)
all_hosts = list(map(json.loads, set(map(json.dumps, all_hosts))))
# システム全体のメトリクスについて処理
print("[Start]system metrics ")
for host in all_hosts:
print("generateSystemMetricsList():host_name:" + host['InstanceName'] )
system_metrics_list = generateSystemMetricsList(host)
for metrics in system_metrics_list:
# CSVファイル名生成
file_name = generateSystemMetricsFileName(host['InstanceName'], metrics)
# 共通処理実行
execCommon(metrics, file_name)
# appサーバグループのプロセス単位のメトリクスについて処理
print("[Start] app server group's process metrics")
for host in app_hosts:
print("generateAppProcessMetricsList():host_name:" + host['InstanceName'] )
process_metrics_list = generateAppProcessMetricsList(host)
for metrics in process_metrics_list:
# CSVファイル名生成
file_name = generateProcessMetricsFileName(host['InstanceName'], metrics)
# 共通処理実行
execCommon(metrics, file_name)
# workerサーバグループのプロセス単位のメトリクスについて処理
print("[Start] worker server group's process metrics")
for host in worker_hosts:
print("generateWorkerProcessMetricsList():host_name:" + host['InstanceName'] )
process_metrics_list = generateWorkerProcessMetricsList(host)
for metrics in process_metrics_list:
# CSVファイル名生成
file_name = generateProcessMetricsFileName(host['InstanceName'], metrics)
# 共通処理実行
execCommon(metrics, file_name)
# esサーバグループのプロセス単位のメトリクスについて処理
print("[Start] es server group's process metrics")
for host in es_hosts:
print("generateEsProcessMetricsList():host_name:" + host['InstanceName'] )
process_metrics_list = generateEsProcessMetricsList(host)
for metrics in process_metrics_list:
# CSVファイル名生成
file_name = generateProcessMetricsFileName(host['InstanceName'], metrics)
# 共通処理実行
execCommon(metrics, file_name)
return {
'statusCode': 200,
'body': json.dumps('finish lambda')
}
# システム全体のメトリクスリストを生成
def generateSystemMetricsList(host):
# システム全体のメトリクス
system_metrics_list = [
# CPU使用率
{
'NameSpace' : 'AWS/EC2',
'MetricName':'CPUUtilization',
'Dimensions':[{"Name" : "InstanceId","Value" : host['InstanceId']}],
'Statistics' : 'Average'
},
# メモリ使用率
{
'NameSpace' : 'CWAgent',
'MetricName':'mem_used_percent',
'Dimensions':[{"Name" : "host","Value" : host['InstanceName']}],
'Statistics' : 'Average'
},
# スワップ使用率
{
'NameSpace' : 'CWAgent',
'MetricName':'swap_used_percent',
'Dimensions':[{"Name" : "host","Value" : host['InstanceName']}],
'Statistics' : 'Average'
}
]
return system_metrics_list
# appサーバグループのプロセス単位のメトリクスリストを生成
def generateAppProcessMetricsList(host):
# プロセス単位のメトリクス
process_metrics_list = [
# sshd
# プロセス数
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_lookup_pid_count',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_SSHD},
{"Name" : "pid_finder","Value" : "native"},
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_SSHD
},
# CPU使用率
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_cpu_usage',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_SSHD},
{"Name" : "process_name","Value" : PROCESS_NAME_SSHD}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_SSHD
},
# 物理メモリ使用量(RSS)
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_rss',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_SSHD},
{"Name" : "process_name","Value" : PROCESS_NAME_SSHD}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_SSHD
},
# スワップ使用量
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_swap',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_SSHD},
{"Name" : "process_name","Value" : PROCESS_NAME_SSHD}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_SSHD
},
# nginx
# プロセス数
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_lookup_pid_count',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_NGINX},
{"Name" : "pid_finder","Value" : "native"}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_NGINX
},
# nginx master
# CPU使用率
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_cpu_usage',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_NGINX_MASTER},
{"Name" : "process_name","Value" : PROCESS_NAME_NGINX}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_NGINX_MASTER
},
# 物理メモリ使用量(RSS)
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_rss',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_NGINX_MASTER},
{"Name" : "process_name","Value" : PROCESS_NAME_NGINX}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_NGINX_MASTER
},
# スワップ使用量
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_swap',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_NGINX_MASTER},
{"Name" : "process_name","Value" : PROCESS_NAME_NGINX}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_NGINX_MASTER
},
# nginx worker
# CPU使用率
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_cpu_usage',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_NGINX_WORKER},
{"Name" : "process_name","Value" : PROCESS_NAME_NGINX}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_NGINX_WORKER
},
# 物理メモリ使用量(RSS)
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_rss',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_NGINX_WORKER},
{"Name" : "process_name","Value" : PROCESS_NAME_NGINX}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_NGINX_WORKER
},
# スワップ使用量
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_swap',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_NGINX_WORKER},
{"Name" : "process_name","Value" : PROCESS_NAME_NGINX}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_NGINX_WORKER
},
# unicorn
# プロセス数
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_lookup_pid_count',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_UNICORN},
{"Name" : "pid_finder","Value" : "native"}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_UNICORN
},
# unicorn master
# CPU使用率
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_cpu_usage',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_UNICORN_MASTER},
{"Name" : "process_name","Value" : PROCESS_NAME_UNICORN}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_UNICORN_MASTER
},
# 物理メモリ使用量(RSS)
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_rss',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_UNICORN_MASTER},
{"Name" : "process_name","Value" : PROCESS_NAME_UNICORN}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_UNICORN_MASTER
},
# スワップ使用量
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_swap',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_UNICORN_MASTER},
{"Name" : "process_name","Value" : PROCESS_NAME_UNICORN}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_UNICORN_MASTER
},
# unicorn worker[0]
# CPU使用率
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_cpu_usage',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_UNICORN_WORKER_0},
{"Name" : "process_name","Value" : PROCESS_NAME_UNICORN}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_UNICORN_WORKER_0
},
# 物理メモリ使用量(RSS)
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_rss',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_UNICORN_WORKER_0},
{"Name" : "process_name","Value" : PROCESS_NAME_UNICORN}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_UNICORN_WORKER_0
},
# スワップ使用量
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_swap',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_UNICORN_WORKER_0},
{"Name" : "process_name","Value" : PROCESS_NAME_UNICORN}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_UNICORN_WORKER_0
},
# unicorn worker[1]
# CPU使用率
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_cpu_usage',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_UNICORN_WORKER_1},
{"Name" : "process_name","Value" : PROCESS_NAME_UNICORN}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_UNICORN_WORKER_1
},
# 物理メモリ使用量(RSS)
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_rss',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_UNICORN_WORKER_1},
{"Name" : "process_name","Value" : PROCESS_NAME_UNICORN}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_UNICORN_WORKER_1
},
# スワップ使用量
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_swap',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_UNICORN_WORKER_1},
{"Name" : "process_name","Value" : PROCESS_NAME_UNICORN}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_UNICORN_WORKER_1
},
# unicorn worker[2]
# CPU使用率
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_cpu_usage',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_UNICORN_WORKER_2},
{"Name" : "process_name","Value" : PROCESS_NAME_UNICORN}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_UNICORN_WORKER_2
},
# 物理メモリ使用量(RSS)
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_rss',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_UNICORN_WORKER_2},
{"Name" : "process_name","Value" : PROCESS_NAME_UNICORN}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_UNICORN_WORKER_2
},
# スワップ使用量
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_swap',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_UNICORN_WORKER_2},
{"Name" : "process_name","Value" : PROCESS_NAME_UNICORN}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_UNICORN_WORKER_2
},
# unicorn worker[3]
# CPU使用率
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_cpu_usage',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_UNICORN_WORKER_3},
{"Name" : "process_name","Value" : PROCESS_NAME_UNICORN}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_UNICORN_WORKER_3
},
# 物理メモリ使用量(RSS)
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_rss',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_UNICORN_WORKER_3},
{"Name" : "process_name","Value" : PROCESS_NAME_UNICORN}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_UNICORN_WORKER_3
},
# スワップ使用量
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_swap',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_UNICORN_WORKER_3},
{"Name" : "process_name","Value" : PROCESS_NAME_UNICORN}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_UNICORN_WORKER_3
},
# unicorn worker[4]
# CPU使用率
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_cpu_usage',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_UNICORN_WORKER_4},
{"Name" : "process_name","Value" : PROCESS_NAME_UNICORN}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_UNICORN_WORKER_4
},
# 物理メモリ使用量(RSS)
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_rss',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_UNICORN_WORKER_4},
{"Name" : "process_name","Value" : PROCESS_NAME_UNICORN}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_UNICORN_WORKER_4
},
# スワップ使用量
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_swap',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_UNICORN_WORKER_4},
{"Name" : "process_name","Value" : PROCESS_NAME_UNICORN}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_UNICORN_WORKER_4
}
]
return process_metrics_list
# workerサーバグループのプロセス単位のメトリクスリストを生成
def generateWorkerProcessMetricsList(host):
# プロセス単位のメトリクス
process_metrics_list = [
# sshd
# プロセス数
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_lookup_pid_count',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_SSHD},
{"Name" : "pid_finder","Value" : "native"},
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_SSHD
},
# CPU使用率
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_cpu_usage',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_SSHD},
{"Name" : "process_name","Value" : PROCESS_NAME_SSHD}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_SSHD
},
# 物理メモリ使用量(RSS)
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_rss',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_SSHD},
{"Name" : "process_name","Value" : PROCESS_NAME_SSHD}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_SSHD
},
# スワップ使用量
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_swap',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_SSHD},
{"Name" : "process_name","Value" : PROCESS_NAME_SSHD}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_SSHD
},
# sidekiq
# プロセス数
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_lookup_pid_count',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_SIDEKIQ},
{"Name" : "pid_finder","Value" : "native"}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_SIDEKIQ
},
# CPU使用率
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_cpu_usage',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_SIDEKIQ},
{"Name" : "process_name","Value" : PROCESS_NAME_SIDEKIQ}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_SIDEKIQ
},
# 物理メモリ使用量(RSS)
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_rss',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_SIDEKIQ},
{"Name" : "process_name","Value" : PROCESS_NAME_SIDEKIQ}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_SIDEKIQ
},
# スワップ使用量
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_swap',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_SIDEKIQ},
{"Name" : "process_name","Value" : PROCESS_NAME_SIDEKIQ}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_SIDEKIQ
},
# cron
# プロセス数
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_lookup_pid_count',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_CRON},
{"Name" : "pid_finder","Value" : "native"}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_CRON
},
# CPU使用率
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_cpu_usage',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_CRON},
{"Name" : "process_name","Value" : PROCESS_NAME_CRON}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_CRON
},
# 物理メモリ使用量(RSS)
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_rss',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_CRON},
{"Name" : "process_name","Value" : PROCESS_NAME_CRON}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_CRON
},
# スワップ使用量
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_swap',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_CRON},
{"Name" : "process_name","Value" : PROCESS_NAME_CRON}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_CRON
}
]
return process_metrics_list
# esサーバグループのプロセス単位のメトリクスリストを生成
def generateEsProcessMetricsList(host):
# プロセス単位のメトリクス
process_metrics_list = [
# sshd
# プロセス数
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_lookup_pid_count',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_SSHD},
{"Name" : "pid_finder","Value" : "native"},
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_SSHD
},
# CPU使用率
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_cpu_usage',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_SSHD},
{"Name" : "process_name","Value" : PROCESS_NAME_SSHD}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_SSHD
},
# 物理メモリ使用量(RSS)
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_rss',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_SSHD},
{"Name" : "process_name","Value" : PROCESS_NAME_SSHD}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_SSHD
},
# スワップ使用量
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_swap',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_SSHD},
{"Name" : "process_name","Value" : PROCESS_NAME_SSHD}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_SSHD
},
# elasticsearch
# プロセス数
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_lookup_pid_count',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_ELASTICSEARCH},
{"Name" : "pid_finder","Value" : "native"}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_ELASTICSEARCH
},
# CPU使用率
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_cpu_usage',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_ELASTICSEARCH},
{"Name" : "process_name","Value" : PROCESS_NAME_ELASTICSEARCH}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_ELASTICSEARCH
},
# 物理メモリ使用量(RSS)
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_rss',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_ELASTICSEARCH},
{"Name" : "process_name","Value" : PROCESS_NAME_ELASTICSEARCH}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_ELASTICSEARCH
},
# スワップ使用量
{
'NameSpace' : 'CWAgent',
'MetricName':'procstat_memory_swap',
'Dimensions':[
{"Name" : "host","Value" : host['InstanceName']},
{"Name" : "pattern", "Value" : PROCESS_PATTERN_ELASTICSEARCH},
{"Name" : "process_name","Value" : PROCESS_NAME_ELASTICSEARCH}
],
'Statistics' : 'Average',
'FileName' : FILE_NAME_ELASTICSEARCH
}
]
return process_metrics_list
# 空ファイル作成
def outputEmptyFile(lambda_file_path):
empty_file = pathlib.Path(lambda_file_path)
empty_file.touch()
# S3にファイルが存在するか
def existInS3(bucket_name, s3_file_path):
s3_client = boto3.client('s3')
result = s3_client.list_objects(Bucket=bucket_name, Prefix=s3_file_path)
if "Contents" in result:
return True
else:
return False
# S3からファイルをダウンロード
def downloadFromS3(bucket_name, s3_file_path, lambda_file_path):
s3_client = boto3.client("s3")
s3_client.download_file(bucket_name, s3_file_path, lambda_file_path)
# S3へファイルをアップロード
def uploadToS3(lambda_file_path, bucket_name, s3_file_path):
s3_client = boto3.client('s3')
with open(lambda_file_path, 'r') as f:
data = f.read()
result = s3_client.put_object(
ACL='private',
Body=data,
Bucket=bucket_name,
Key=s3_file_path
)
print(result)
# cloudwatchからメトリクス統計を取得
def getMetricStatistics(metrics):
cloudwatch_client = boto3.client('cloudwatch', region_name='ap-northeast-1')
metric_statistics = cloudwatch_client.get_metric_statistics(
Namespace=metrics["NameSpace"],
MetricName=metrics["MetricName"],
Dimensions=metrics["Dimensions"],
StartTime=start_time,
EndTime=end_time,
Period=60,
Statistics=[metrics["Statistics"]]
)
return metric_statistics
# CSVへ変換してファイル出力(上書き)
def convertCSV(datapoints, lambda_file_path):
with open(lambda_file_path,'a') as f:
if len(datapoints) == 0:
print("Empty")
pass
else:
# 日時でソート
datapoints.sort(key=lambda x: x['Timestamp'])
print(datapoints)
# CSVの方言設定:全カラムクォートで囲む
csv.register_dialect('quote_all', quoting=csv.QUOTE_ALL)
# CSVとして出力(ヘッダなし)
writer = csv.DictWriter(f, fieldnames=datapoints[0].keys(), dialect="quote_all")
for row in datapoints:
# 単位カラムを削除
row.pop('Unit')
# タイムゾーンをUTC→JSTへ変換
row['Timestamp'] = row['Timestamp'].replace(tzinfo=None).astimezone(tz=timezone(timedelta(hours=+9)))
writer.writerow(row)
# CSVをソートして出力
def sortCSV(lambda_file_path):
# CSVファイルを読み込んでソート
with open(lambda_file_path,'r') as f:
reader = csv.reader(f)
print(reader.type())
result = sorted(reader, key=operator.itemgetter(0))
# 上書き出力
with open(lambda_file_path, 'w') as f:
data = csv.writer(f,delimiter=',')
for row in result:
data.writerow(row)
# システム全体のメトリクスを保存するファイル名を生成
def generateSystemMetricsFileName(instance_name, metrics):
file_name = instance_name + "_" + metrics['MetricName'] + "_" + datetime.strftime(today, '%Y%m') + ".csv"
return file_name
# プロセス単位のメトリクスを保存するファイル名を生成
def generateProcessMetricsFileName(instance_name, metrics):
file_name = instance_name + "_" + metrics['MetricName'] + "_" + metrics['FileName'] + "_" + datetime.strftime(today, '%Y%m') + ".csv"
return file_name
# プロセス単位のメトリクス、システム単位のメトリクス共通の処理
def execCommon(metrics, file_name):
# CSVファイルを保存するS3上のパス
s3_file_path = BASE_DIR_NAME + "/" + file_name
# CSVファイルを一次的に保存するlambda上のパス
lambda_file_path = "/tmp/" + file_name
print("file_name:" + file_name)
print("s3_file_path:" + s3_file_path)
print("lambda_file_path:" + lambda_file_path)
# S3にファイルがあるかチェック
if existInS3(BUCKET_NAME, s3_file_path):
print("exist in S3:" + s3_file_path)
# S3からファイルをダウンロード
downloadFromS3(BUCKET_NAME, s3_file_path, lambda_file_path)
else:
print("not exist in S3:" + s3_file_path)
# 空ファイルを作成
outputEmptyFile(lambda_file_path)
print("output empty file:" + lambda_file_path)
# CloudWatchからメトリクス統計を取得
metric_statistics = getMetricStatistics(metrics)
print("getMetricStatics:")
print(metric_statistics)
# CSVへ変換して保存
convertCSV(metric_statistics['Datapoints'], lambda_file_path)
# CSVをS3へアップロード
uploadToS3(lambda_file_path, BUCKET_NAME, s3_file_path)
print("upload to s3:" + s3_file_path)
参考
https://business.ntt-east.co.jp/content/cloudsolution/column-42.html
https://qiita.com/sot528/items/ae6534114d349d20798d
https://www.hinemos.info/technology/special_content/cloud_01
https://aws.amazon.com/jp/about-aws/whats-new/2016/11/cloudwatch-extends-metrics-retention-and-new-user-interface/
https://www.slideshare.net/marcyterui/jtf-cw
https://qiita.com/Kept1994/items/aa76ae065d8d16c557a8
https://qiita.com/kapioz/items/f2de33075d7f88f00ffe
https://dev.classmethod.jp/articles/cloudwatch-can-be-seen-graph/
https://qiita.com/n0bisuke/items/1ea245318283fa118f4a