はじめに
本記事はDatadog Advent Calendar 2019の12/23(月)分の記事です。
別アドベントカレンダーで、Datadogを全社的に導入した時にやったことという記事も書いたので、見ていただけると幸いです。
導入背景
VISITSでは、GCPを使ったプロダクトが増えており、基本的になアーキテクチャは同じ場合が多いです。
各プロダクトは、DatadogのManaging Multiple-Organization機能を使用して、
オーガニゼーションを分けています。
この場合、各オーガニゼーションで設定するMonitors(以下、アラート)は、基本的に同じである場合が多く、
であればコード化して汎用性のあるものにしようと考え、terraform化しました。
Monitoring as Codeという言うらしいです。
結果
まず最初にterraform化して感じたメリデメをご紹介。
メリット
- 使い回しができる。オーガニゼーションが増えても簡単にアラート設定が可能。(もちろん作りによります)
- アラート再現性がある。(仮に私が退職しても誰でもアラート設定できる)
- アラートの文言を統一できる。(仮に1人がアラート設定してても文言はバラバラになることが多い)
- 各プロダクトで得られた知見を取り込むことで、横展開が可能となる。(このアラート設定は他のオーガニゼーションでも使えるね!等)
- コード管理しているという満足感。(自己満)
デメリット
- 1つのアラートを修正したら全オーガニゼーションに適用し直す必要がある。(差分が出てしまうので)
- terraformを使える人があまりいないので、terraformのアップロードが属人化してしまう。(適用は簡単だが、更新はできない等)
設計思想
- tfファイルは共通化して
variables
で各オーガニゼーションごとのパラメーターを調整する。 - チームで運用することを想定して、tfstateファイルはGCSで管理。(事前にGCSバケットを作る必要があります)
- 一旦
terraform plan
とterraform apply
のみにしぼる(Importとかできない)
ファイル構成
├── README.md
├── <プロダクト名>
│ ├── datadog.tfvars # プロダクトの毎の変数(パラメーター)を設定
│ ├── monitor_service_list.txt #アラート設定したいテンプレートを指定
│ ├── secrets.tfvars #Datadogの機密情報(API key等)を設定。gitignoreでGit管理対象外とする。
│ └── tmp #作業ディレクトリ。ここにtfファイルをコピーして適用
├── templates #各アラートのテンプレート
│ ├── apm.tf
│ ├── cloudfunction.tf
│ ├── cloudsql.tf
│ ├── gae.tf
│ ├── gae_log.tf
│ ├── gce.tf
│ ├── gce_docker_containers.tf
│ ├── gce_proces_nginx.tf
│ ├── lb_log.tf
│ ├── provider.tf
│ ├── synthetics.tf
│ ├── synthetics_vi.tf
│ ├── tfstate.tf
│ └── variables.tf
├── terraform.sh #terraformのラッパースクリプト
以下に各ファイル、ディレクトリの補足説明をします。
datadog.tfvars
プロダクトの毎のパラメーターを設定しています。
例えば、Slackのチャンネル名、GCPプロジェクトID、アラートの閾値、外形監視するURL等。
例)
## Slack Channel Name
slacke_channel = "<your slack channel>"
## GCP Project ID
gcp_project_id = "<your gcp project id>"
## Production ENV Name
env_name = "<your env>"
monitor_service_list.txt
templatesの中からアラート設定を適用するファイル名(.tfより前)を指定します。
gce
gce_proces_nginx
gae
gae_log
cloudsql
lb_log
synthetics
apm_ruby
secrets.tfvars
DatadogのAPI KeyやAPP Keyといった秘匿性のある情報を管理します。
このファイルは、.gitignore
でignoreしましょう。
datadog_api_key = "*************"
datadog_app_key = "*************"
tmp(ディレクトリ)
後述のterraformラッパースクリプトがこのディレクトリにtfファイルやvariablesをコピーしてapplyします。
各アラートのテンプレート
各サービスのごとのアラート設定ファイルをおきます。
以下、Datadog APM監視のSampleです。
# apm.tf
##########################################################
# 説明
# APMを監視するtfファイルです。
#
# 動作要件
# 以下のvariableを各プロダクトの `datadog.tfvars` に設定してください。
##########################################################
##############################
## variable
##############################
variable "apm_service_web" {}
variable "apm_service_db" {}
variable "apm_service_web_error_rate_critical" {}
variable "apm_service_db_error_rate_critical" {}
variable "apm_service_web_latency_critical" {}
variable "apm_service_db_latency_critical" {}
variable "apm_exclude_condition" {}
variable "env_name" {}
##############################
## APM web Error Rate 監視
##############################
resource "datadog_monitor" "apm_error_rate_web" {
name = "[APM] Service ${var.apm_service_web} has a high error rate"
type = "query alert"
message = <<EOF
Service ${var.apm_service_web}のError RateがSLOを超過しています。
詳細を[Datadog Logs](https://app.datadoghq.com/logs)から確認してください。
@${var.slack_channel}
EOF
query = "avg(last_10m):( avg:trace.rack.request.errors{service:${var.apm_service_web},env:${var.env_name}} / avg:trace.rack.request.hits{service:${var.apm_service_web},env:${var.env_name}} ) > ${var.apm_service_web_error_rate_critical}"
escalation_message = ""
thresholds = {
critical = "${var.apm_service_web_error_rate_critical}"
}
notify_no_data = false
new_host_delay = 300
renotify_interval = 0
timeout_h = 0
include_tags = true
require_full_window = true
notify_audit = false
tags = []
}
##############################
## APM db Error Rate 監視
##############################
resource "datadog_monitor" "apm_error_rate_db" {
name = "[APM] Service ${var.apm_service_db} has a high error rate"
type = "query alert"
message = <<EOF
Service ${var.apm_service_db}のError RateがSLOを超過しています。
詳細を[Datadog Logs](https://app.datadoghq.com/logs)から確認してください。
@${var.slack_channel}
EOF
query = "avg(last_10m):( avg:trace.mysql2.query.errors{service:${var.apm_service_db},env:${var.env_name}} / avg:trace.mysql2.query.hits{service:${var.apm_service_db},env:${var.env_name}} ) > ${var.apm_service_db_error_rate_critical}"
escalation_message = ""
thresholds = {
critical = "${var.apm_service_db_error_rate_critical}"
}
notify_no_data = false
new_host_delay = 300
renotify_interval = 0
timeout_h = 0
include_tags = true
require_full_window = false
notify_audit = false
tags = []
}
##############################
## APM web latency 監視
##############################
resource "datadog_monitor" "apm_request_latency_web" {
name = "[APM] Service ${var.apm_service_web} has a high p90 latency"
type = "metric alert"
message = <<EOF
Service ${var.apm_service_web}のRequest LatencyがSLOを超過しています。
詳細を[Datadog APM](https://app.datadoghq.com/apm/search)から確認してください。
@${var.slack_channel}
EOF
query = "avg(last_10m):avg:trace.rack.request.duration.by.service.90p{service:${var.apm_service_web},env:${var.env_name}${var.apm_exclude_condition}} > ${var.apm_service_web_latency_critical}"
escalation_message = ""
thresholds = {
critical = "${var.apm_service_web_latency_critical}"
}
notify_no_data = false
new_host_delay = 300
renotify_interval = 0
timeout_h = 0
include_tags = true
require_full_window = true
notify_audit = false
tags = []
}
##############################
## APM db latency 監視
##############################
resource "datadog_monitor" "apm_request_latency_db" {
name = "[APM] Service ${var.apm_service_db} has a high p90 latency"
type = "metric alert"
message = <<EOF
Service ${var.apm_service_db}のRequest LatencyがSLOを超過しています。
詳細を[Datadog APM](https://app.datadoghq.com/apm/search)から確認してください。
@${var.slack_channel}
EOF
query = "avg(last_10m):avg:trace.mysql2.query.duration.by.service.90p{service:${var.apm_service_db},env:${var.env_name}} > ${var.apm_service_db_latency_critical}"
escalation_message = ""
thresholds = {
critical = "${var.apm_service_db_latency_critical}"
}
notify_no_data = false
new_host_delay = 300
renotify_interval = 0
timeout_h = 0
include_tags = true
require_full_window = false
notify_audit = false
tags = []
}
terraform.sh
terraformのラッパースクリプトのサンプルです。
bash terraform <プロダクト名>
といった感じで実行し、terraformをapplyします。(雑ですいません)
#!/bin/bash
set -u
# Check argument
if [ $# -ne 1 ]; then
echo "実行するには1つの引数が必要です。" 1>&2
echo "引数には各プロダクト名を指定してください。" 1>&2
echo "例) bash terraform.sh <プロダクト名>" 1>&2
exit 1
fi
PRODUCT_NAME=$1
ls -d ${PRODUCT_NAME} 1>/dev/null 2>/dev/null
if [ $? -ne 0 ]; then
echo "${PRODUCT_NAME}のディレクトリが存在しません。terraformを適用するプロダクト毎にディレクトリを作成してください。"
exit 1
fi
# prepare
gsutil 1>/dev/null 2>/dev/null
if [ $? -ne 0 ]; then
echo "gsutilがインストールされていません。"
exit 1
fi
gcloud version 1>/dev/null 2>/dev/null
if [ $? -ne 0 ]; then
echo "gcloudがインストールされていません。"
exit 1
fi
ls -d ${PRODUCT_NAME}/tmp 1>/dev/null 2>/dev/null
if [ $? -ne 0 ]; then
mkdir ${PRODUCT_NAME}/tmp
else
rm -rf ${PRODUCT_NAME}/tmp/*.tf
rm -rf ${PRODUCT_NAME}/tmp/*.tfvars
fi
# setup gcp project
gcloud config set project visits-technologies-share
if [ $? -ne 0 ]; then
echo "GCP <Your GCP Project Name> プロジェクトが設定出来ませんした。GCPで権限を確認してください。"
exit 1
fi
# copy tf files
if [ -e ${PRODUCT_NAME}/monitor_service_list.txt ]; then
while read line
do
echo "cp -pr templates/${line}.tf ${PRODUCT_NAME}/tmp/"
cp -pr templates/${line}.tf ${PRODUCT_NAME}/tmp/
done < ${PRODUCT_NAME}/monitor_service_list.txt
else
echo "${PRODUCT_NAME}/monitor_service_list.txtが見つかりません。"
fi
## copy PRODUCT tfvars
cp -p ${PRODUCT_NAME}/*.tfvars ${PRODUCT_NAME}/tmp/
## copy common tf files
cp -p templates/provider.tf ${PRODUCT_NAME}/tmp/
cp -p templates/tfstate.tf ${PRODUCT_NAME}/tmp/
cp -p templates/variables.tf ${PRODUCT_NAME}/tmp/
# setup tfstate
gsutil ls gs://visits-${PRODUCT_NAME}-terraform-state
if [ $? -ne 0 ]; then
echo "tfstate用のGCSバケットが作成されていません。tfstate.tfファイルを確認してください。"
exit 1
fi
if [ -e ${PRODUCT_NAME}/tmp/tfstate.tf ]; then
sed -i '' "s/__PRODUCT_NAME__/${PRODUCT_NAME}/g" ${PRODUCT_NAME}/tmp/tfstate.tf
fi
# init
cd ${PRODUCT_NAME}/tmp
terraform init
# plan
terraform plan -var-file=datadog.tfvars -var-file=secrets.tfvars
if [ $? -ne 0 ]; then
echo "terraform planでエラーが発生しました。詳細を確認してください。"
exit 1
fi
# apply
terraform apply -var-file=datadog.tfvars -var-file=secrets.tfvars
まとめ
まだまだ改善の余地しかないですが、一応最低限は運用できています。
再現性のあるものができたとは思いつつ、監視を変更するたびにyamlを書き換える必要があるので、なかなか運用が面倒だなーというのが正直な感想です。
このあたりは、もう少しPDCA回して最適化していきたいです。