AWS Lambdaの構成管理のためにterraformを導入してみた

FASTALERT開発チームバックエンドエンジニアの鈴木(泰)です。

本記事は、AWS Lambdaの構成管理のためにTerraformを導入してみたというお話です。

TL;DR

  • FASTALERTチームの開発文化とTerraformの導入に至った背景
  • AWS Lambda関数をTerraformでどう管理しているか

もくじ

本対応の背景

具体的なAWS Lambda x Terraformの話に入る前に、FASTALERTチーム、workerレポジトリ、今回Terraformの導入に至った動機について、少しだけお話しさせてください。

FASTALERTとチーム文化

FASTALERTは、事件・事故・災害などのリスク情報をAIが自動収集し、収集された情報をお客様にとって使いやすい形で提供するBtoB向けのSaaSです。まだまだ成長過程のサービスであるため、顧客からのフィードバックを含め、寄せられる要望へ迅速に対応しなければなりません。

上記のような事情から、FASTALERTのバックグラウンド処理の内容は規模が小さく多岐に渡り(広く浅い)、「開発速度」が重要となってきます。

様々なバックグラウンド処理

  • データやシステムの監視とメトリクス算出、データベース中の防災情報の管理、防災情報へのメタ情報、社内システム連携
  • トリガーも様々。イベント駆動、Web API、運用者が不定期的に手動で実行する、等。

開発速度を重要視するチームの慣習

  • ほぼ全てのコンポネントがCD化されている、本番稼働するシステムの全ソースコードはGitで管理されている、Gitのタグを付与するだけのお手軽リリース、IaCの導入、スクラム開発

workerレポジトリ

FASTALERTチームにはworkerという名前のレポジトリがあります。 このレポジトリは、様々なバックグラウンド処理を小さい工数で提供し、かつ、ソースコード管理システムでソースコードを管理し、CI/CDを完備することを目的とし、Lambda製の様々なバックグラウンド処理を一元的に管理しており、Lambda関数を簡単に追加できるようにしています。具体的には(1)ソースコードを書く(2)デプロイ用のコードを追加(3)Gitのタグを付与する。これだけで新しいLambda関数をリリース可能です。本記事執筆時においては約50個のLambda関数が管理されており、ソースコードの追加・更新が頻繁に行なわれています。

apexのメンテナンス停止

workerレポジトリでは、Lambda関数の構成管理ツールとしてこれまではapexを利用していました。apexが2019年にメンテナンスを停止しました。今回のTerraform導入は、このapexのEOLを受けてのことです。

Lambdaの構成管理ツールとしてTerraformを採用した理由

Terraform以外の構成管理ツールの候補としてAWS SAMがありました。Terraformを採用することとなった決め手となったのは、以下の点です。

  • AWS上の実際のリソース設定とTerraformのstateを柔軟に解消できる。
  • FASTALERT開発チームのメンバーはTerraformを使い慣れている。

AWS上の実際のリソース設定とTerraformのstateを柔軟に解消できる

過去の経験上、SAMはリソース定義(yamlファイル)と実際のインフラ設定の間に生じた差分の解決がとても面倒臭い、という印象があります(昔、SAMを利用していたとき、差分が生じて、CloudFormationが動かなくなり、CloudFormationに紐づくリソースを丸ごと削除して新しく作り直す、というような危険な運用をしたことが苦い経験として記憶に焼き付いていたりします)。

IaCを進める過程において、構成管理用のソースコードと実際のインフラ設定の間に差分が生まれるということはよくあります。FASTALERTチームのように開発速度が求められる現場においては、そのような差分が発生する可能性はとても高いです。

Terraformは、CloudFormationを使用しておらず、かつ、実際のインフラ設定との差分を解消するためのimportコマンドがあるため、差分の発生に対して柔軟に対処することができます。

FASTALERT開発チームのメンバーはTerraformを使い慣れている

Terraformを採用したもう1つの理由は、FASTALERT開発チームメンバーがTerraformに慣れているという点です。最近FASTALERTチームでは、構成管理をTerraform化することを推奨しており、Terraformを使えるメンバーが増えつつあります。

AWS Lambdaの構成管理のためにTerraformを導入(詳細)

それでは、我々がどのようにしてTerraform化を実現したかについてお伝えいたします。

本記事で紹介している詳細実装は、Python3.8、Terraform1.1.7で動作することを前提としています。

コードサンプルはこちらにあります。

ディレクトリ構造

workerレポジトリは以下のようなディレクトリ構造です。

functions/
terraform/
build-pkg.sh
Pipfile
Pipfile.lock

Pipfile, Pipfile.lockは依存ライブラリを管理します。

本記事の要となる、functions/, terraform/ディレクトリについての詳細です。build-pkg.shはLambda用のzipを作るためのスクリプトです(後述)。

# functionsディレクトリ配下にはLambda関数のエントリーポイント群があります。
functions/
functions/lambda_process_checker.py
functions/lambda_export_api_log.py
...以下省略...

# 複数のLambda関数から共通して使われるソースコードは
# functions/worker_lib/ ディレクトリ配下にあります
functions/worker_lib/
functions/worker_lib/common.py
...以下省略...

# Terraformディレクトリ配下にはTerraformのコードがあります。
terraform/

## 共通するリソース(IAMとか)はmain.tfに書いています。
terraform/main.tf

## Lambda関数のリソース定義は
## 可読性を向上させるために
## 基本的にはLambda関数1つにつき、1つのファイルです。
## ファイル名は /functions ディレクトリ配下のpythonのソースコードと同じ。
terraform/lambda_process_checker.tf
terraform/lambda_export_api_log.tf
...以下省略...

ファイルの中身

functions/process_checker/main.pyの中には、Lambdaのエントリーポイントがあります。

# coding: utf-8
def handler(event, context):
    print('start process_checker')
    ...以下省略...

terraform/main.tfの中には、共通するリソース定義があります。

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_iam_role" "worker-role" {
  name        = "worker-role"
  description = "Do not edit manually! This is auto generated by Terraform. Allows Lambda Function to call AWS services on your behalf."
  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      ...以下省略...

terraform/lambda_process_checker.tfの中には、Lambda関数リソースの定義があります。dists.zipは、build-pkg.sh(後述)が生成するLambda関数のパッケージです。

resource "aws_lambda_function" "aws_lambda_process_checker" {
  function_name    = "aws_lambda_process_checker"
  filename         = "dists.zip"
  source_code_hash = filebase64sha256("dists.zip")
  runtime          = "python3.8"
  role             = aws_iam_role.worker-role.arn
  handler          = "lambda_process_checker.handler"
}

Lambdaのデプロイ方法

  1. Lambda関数のパッケージを作る。
  2. terraform applyコマンドを実行する。

Lambda関数のパッケージを作る

Lambda関数用のzipファイルを作ります。全てのLambda関数のエントリーポイント、その実行に必要となる依存ライブラリを全て1つのzipファイルの中に格納します。参考 .zip ファイルアーカイブで Python Lambda 関数をデプロイする

workerレポジトリ上のbuild-pkg.shがzipファイルを作ります。build-pkg.shが正常終了するとdists.zipというファイルが生成されます。

#!/bin/bash

set -e

WORK_DIR="/tmp/lambda-pkg-$(date +%s)"
DIST_DIR="${WORK_DIR}/dists"
OUT_DIR=$(pwd)/terraform

mkdir $WORK_DIR
mkdir $DIST_DIR

pipenv lock -r > requirements.txt
pip install -r requirements.txt -t ${DIST_DIR}/
cp -r ./functions/* ${DIST_DIR}/

cd ${DIST_DIR}/ && zip -q -r dists.zip * && mv dists.zip ${OUT_DIR}/

注意点

この実装方法ですと、Lambda関数の数が増えていくのに伴い、dists.zipファイルのサイズが大きくなることが懸念されます。dists.zipファイルのサイズが大きくなることを回避するための実装方針として、サイズが大きくなりがちな変数の定義(長い文字列や配列、連想配列の定数値)はDynamoDBやS3等の永続化層へ配置するようにしています。Pythonのソースコードが増えるだけであれば、ファイルのサイズが大きくなることは回避できます。(それでも、Pythonの依存パッケージのサイズが大きくなってしまう懸念点は依然として残されています。。。もしそうなってしまった場合、AWS Lambda Layers等の利用を検討すべきかもしれません。)

terraform applyコマンドを実行する

dists.zipが生成された後、以下のコマンドを実行し、Lambda関数をデプロイします。

cd terraform && terraform init && terraform apply

以上です。

新しいLambda関数を作成する場合、Pythonのソースコード、Terraformの定義ファイルを追加するだけです。とてもお手軽に作れます。例えば、new_func1という新しいLambdaを追加する場合、以下の2つを追加するだけです。逆に既存のLambdaを削除する場合、2つのファイルを削除するだけです。

  • functions/new_func1/main.py
  • terraform/lambda_new_func1.tf

コードサンプルはこちらにあります。

所感

AWS Lambdaの構成管理ツールとしてTerraformを導入してから3ヶ月ほど経っています。本対応を終えて、振り返って思うことをお話しします。

本記事で紹介した実装方法のメリットは、シンプルな構成であることかなと思います。ソースコードとインフラのリソース定義が1つのレポジトリで完結し、全体の見通しが良いです。Lambdaの追加や削除がとても簡単にできます。

実務的に困るようなデメリットは今のところは顕在化していません。しかしながら本記事で紹介した、規模の小さなプログラムを一元管理するようなタイプのレポジトリ(上記のworkerレポジトリ)の注意点として以下のことがあげられます。本来マイクロサービスとして独立して作るべき処理を、処理の追加とリリースが簡単という理由だけで、追加してはならないということです。このタイプのレポジトリに追加して良い処理は、緊急性が必要とされ、かつ、マイクロサービスとして切り出すことの費用対効果が得られないもの(規模の小さな処理、マイクロサービスとして切り出すべきかどうか、その境界が曖昧な処理)、に限定すべきです。

あとがき

本記事では書いていないこと

  • 実際には、zipファイルの作成、terraform applyコマンドの実行は、CD環境上で自動化されています。
  • Pythonソースコードのユニットテストの実行は、CI環境上で自動化されています。
  • 我々の本番環境では、workerレポジトリ上で管理されているLambda関数は約50個あるため、functions/ディレクトリ配下には50個のLambdaのエントリーポイント、terraform/ディレクトリ配下には50個のLambda関数リソース定義があります。
  • 実際には、Terraformのリソース定義ファイルはもう少し作り込んであります(Lambdaのトリガーリソースの定義、モジュール機能、-var-fileオプション、stateファイル管理周り)。今回の記事では省略しました。
  • 50個全てのLambdaを、無停止でapexからTerraformへ切り替える作業が本当に大変なところであり、話したいところでもあるのですが(汗)、記事がとても長くなってしまうので省略しました。

Hydraで書かれたコードをVertex AIでハイパーパラメータ調整できるようにした



初めまして、JX通信社のMLチームでインターンをしている田中です。これまで物体検知や深層距離学習を使った画像分類、自然言語の分類や生成などのAI作成、MLOpsなどをやっています。今回、MLエンジニアのヨンテさんのもとで文書生成AIのハイパーパラメータ調整の並列化に挑戦しました。

Hydraで書かれたコードをVertex AIでハイパーパラメータ調整するためのサンプルコードはgithubにて公開しています! ぜひ、一度試してみてください!😊

github.com

背景

JX通信社のMLチームでは「力を使うべき場所に注力しよう」の理念のもとに、機械学習用のテンプレートコードを作成しており、その中ではPyTorch LightningやHydraなどが使用されています。

理念やテンプレートコードについてはヨンテさんが書いた解説記事があるので読んでみてください。

tech.jxpress.net tech.jxpress.net

文書生成AIの学習は、1回の学習に数時間程度かかります。

ハイパーパラメータ調整の学習を一つのマシーンで直列で回していると、図1の上の例ように1回の学習時間×学習の試行回数以上の時間がかかり、数日以上かかってしまいます。

図1 ハイパーパラメータ調整時に一つのマシーンで学習した場合 (上の例)と、複数マシーンを並行して学習したとき(下の例)の完了までにかかる時間 の比較。Vertex AI hyperparameter tuningは学習を並行化できるだけではなく、並行学習を直列につなげることで、ベイズ最適化も実施することができ、最適なパラメーターをコスパよく高精度に探索することができます。*1

JX通信社で大事にしているバリューの一つにSpeedがあり、その理念をML側からも支えるために、学習をより早く終わらせ、事業の開発スピードを早めることを目指しています。

そこで、ハイパーパラメータ調整の際は、たくさんの実験を並列で回したいです 。このような並列学習は、フルマネージドMLサービスであるVertex AIを使用すれば簡単に実現できます(図1下の例)。

一方で、Vertex AI Trainingのハイパーパラメータ調整機能と、JX通信社のテンプレートコードで用いられているHydraの相性が悪く、そのまま使えなかったので、工夫して動くようにしました!

問題点

ハイパーパラメータ調整はVertex AI側がコマンドライン引数を用いてハイパーパラメータを学習コンテナに渡し学習が行われ、学習が終わった後に最適化の評価値を専用のライブラリを使用しVertex AIに伝えることで、ハイパーパラメータの最適化が行われます。

しかし、以下のようにVertex AIが渡すコマンドライン引数の形式とHydraが対応するコマンドライン引数の形式が違うためうまくできませんでした。

Vertex AIの公式ドキュメントに記載されている形式

argparse を推奨

python3 -m my_trainer --learning_rate learning-rate-in-this-trial

⚠️ 注意

自分が調査した限り、実際渡されている形式は上の形式ではなく

--learning_rate=learning-rate-in-this-trial

でした。

推奨されている argparseだとこの形式でも受け取れます。

Hydraの形式

python3 my_trainer.py learning_rate=learning-rate-in-this-trial

解決方法

解決方法はいくつか考えられます。

  1. Vertex AIがHydra形式に対応するのを待つ

    → 一番良いが、対応されるか分からない

  2. Vertex AIのハイパーパラメータ調整機能を使わず、オープンソースのハイパーパラメータ自動最適化フレームワークOptunaを使用する

    → 並列ではなく直列でHydra+Optunaで使用しており、一つのインスタンスで完結するため、さまざまな環境で動かすことができるので便利。しかし、同一マシーンではなく並列で回す際はSQLのサーバーを立てる必要があり大変。(Optunaの並列について)

  3. argparse など対応しているコマンドライン引数解析ライブラリを使う

    → 推奨されているが、パーサーの定義が大変、設定ファイルを使用できない

  4. Vertex AIが渡す引数の形式を変換する処理を挟んで実行する

    → 強引だが、導入コストが一番低い

今回は導入コストが一番低い4を採用しました。

Hydra形式へ変換し、本来の学習コードを実行するスクリプトの作成

まずは、Vertex AIからコンテナに渡される入力(コマンドライン引数)の変換を考えます。今回、コマンドライン引数の変換にはshell scriptを使いました。

shell scriptでのコマンドライン引数の受け取りですが、一番目の引数が欲しい時は $1 で取り、n番目は $nで取れます。今回は引数の個数は可変なため $@で引数全体をそのまま文字列として受け取ります。

次に受け取った文字列を正規表現で置換を行いHydra形式に変換します。

shell scriptなのでsedコマンドを使用して正規表現の置換を行います。

以下のようなコマンドにすることで、引数の形式については

--key=value--key value 両方とも受け取れるようにします。

sed -r 's/--([^= ]*)[= ]([^ ]*)/\1=\2/g'

sedコマンドはVimの置換と同様に /で区切りs/正規表現/置換/フラグと言う形式で(expr)が \1, \2と対応しています。そして最後の gは複数回置換するフラグです。また、sedコマンドは最短一致に対応しておらず、マッチしたいものの後ろにある文字以外を繰り返すという処理で対処しています。( [^ ]* のところなど)

  • 実行例
❯ echo "--lr=0.001 --batch-size 64" | sed -r 's/--([^= ]*)[= ]([^ ]*)/\1=\2/g'

lr=0.001 batch-size=64

最後に変換した引数を使って本来の学習コードを実行できるようにします。

$(expr)はexprを実行した結果を文字列として受け取れるので、それを用いて

python train.py $(echo $@ | sed -r 's/--([^= ]*)[= ]([^ ]*)/\1=\2/g') # train.pyはHydraを用いたコード

とすると、変換した引数を本来の学習コードの実行コマンドの後ろに追加できます。

あとはこれを train.shなどでファイルを作成し、Vertex AIの設定ファイルの実行コマンドに ./train.shなどと記載すれば完成です。設定ファイルの実行コマンドの記載場所は HyperparameterTuningJob.trialJobSpec.workerPoolSpec.containerSpec.commandです(参考)。

HydraのコードでVertex AIのハイパーパラメータ調整ができた

このような工夫を行った結果、Vertex AIでハイパーパラメータ調整を並列に計算することができました(図2~4)。図3,4には、複数の色の線が同時に存在しており、これは複数インスタンスが並行して学習していることを意味してます。

これにより、これまで数日かかってしまう学習も、数時間で終えることができました!

図2 ハイパーパラメータ調整の各トライアルと、そのトライアルで利用されたハイパーパラメータの値。具体的な変数名は隠してます。

図3 Vertex AIのハイパーパラメータ調整時のCPU使用率の遷移。それぞれの線の色は各トライアルに対応している。

図4 Vertex AIのハイパーパラメータ調整時のGPU使用率の遷移。それぞれの線の色は各トライアルに対応している。

まとめ

Vertex AIのハイパーパラメータ調整ジョブの時、スクリプトを挟み正規表現でパラメータの形式を変換することで、Vertex AIから渡されるハイパーパラメータをHydraが受け取れる形式に変換することができました。

これにより、Hydraを使用した既存のコードを使用しながら並列でハイパーパラメータ調整できるようになり、学習時間の短縮ができました。 今回の内容について、sample codeをgithubに公開しています。このコードが皆さまの役に立てれば幸いです。

今回の方法以外により良い方法があるかもしれません。私自身まだまだ、勉強中なので改善点などアドバイス頂けられたら幸いです。

補足:チームでR&Dに取り組む工夫

このブログでご紹介しましたが、JX通信社ではPytorch Lightningをベースとした、テンプレートコードを用いて学習を行うことで、チームで効率よく実験ができるようにしています。

興味があれば以下のブログも読んでみてください

tech.jxpress.net tech.jxpress.net

我々とともに挑戦する仲間を求めています

我々とともに成長しながら、より良い社会のためのMLを開発したい仲間を社員・インターン問わず積極的に募集しています!また、MLエンジニアはもちろん、あらゆる職種のエンジニアを求めています!

正社員、インターン、そして副業・復業として体験的に働くことで、当社のカルチャーや働き方等を知っていただいたうえで正式入社を見極めていただくことが可能な「おためし入社」制度などもあります!ほんの少しでも興味を持たれた方はこちらを覗いてみてください!


*1:⚠️ Vertex AIを利用するさらなる利点

ハイパーパラメータ調整をする方法は主に以下の2つに分けられます。

①実験前に予め、ハイパーパラメータを決めて実験を行い、その中で最も性能の良いパラメータを採用する方法 (Grid searchなどが有名)

②とあるハイパーパラメータを与え実験し、その結果を元に更に次のハイパーパラメータ決めて実験を行い、を繰り返すことで最適なハイパーパラメータを探索する方法 (ベイズ推定などが有名)

①の場合は探索するハイパーパラメータは実験前から決まっているため、すべての実験を一度に並行で回せばよく、図1の下の例の1回目の実験で終わります。しかし、場当たり的にハイパーパラメータを選んでいるため、無駄な計算も多くなってしまい、最適なハイパーパラメータを得るためには膨大なコストがかかる可能性があります。

一方、②の方法では、戦略的にハイパーパラメータを選んでいるため最適なパラメーターをコスパよく高精度に探索することができます。しかし、前の学習結果に依存して次の実験で探索するハイパーパラメータが決まるため、全ての学習を一度に並行で回す事はできません。

並行学習を直列でつなぐことができれば、並行学習とベイス最適化の両方の恩恵を受けることができますが、一般的には実装が非常に大変です。しかし、Vertex AIを利用することで並行学習の直列化をほとんど苦労することなく実施することができます (図1 下の例)。

リスク検知SaaSを支えるマルチモーダル・マルチタスクなExplainable AI

皆様こんにちは!JX通信社で機械学習エンジニアを担っているファンヨンテです。

弊社提供のビックデータ リスク情報サービスFASTALERTでは、Deep Learningを使ってSNSの投稿をリアルタイムに解析し、火事や事故などのリスク情報の検知を行っています。

SNSの投稿には、文字だけでなく、画像、動画などの情報も含まれているため、SNS解析にはよくマルチモーダルなAIモデルが用いられます。今回は「SNS の投稿からのリスク情報の判定」というタスクをテーマに、マルチモーダルなAIモデルの判定根拠の可視化や、精度を上げるための工夫などをご紹介します。

FASTALERT(ファストアラート)について

「FASTALERT」は、SNSをはじめとする各種ビッグデータから、AIがリスク情報を検知・配信するビックデータ リスク情報サービスです。報道に必要不可欠なツールとしてNHKと全ての民放キー局、全ての一般紙に採用されるなど、国内の大半の報道機関に浸透しています。また、最近では防災やBCP、障害監視やサプライチェーンのリスク管理など広範なニーズに対応する情報ツールとして、政府・自治体やインフラ企業をはじめとする幅広い業種の顧客に導入されています。URL:https://fastalert.jp

f:id:yoooongtae:20220408110109p:plain

図1FASTALARTについて

リスク検知タスクの課題

SNSからリスク情報を検知するというタスクの性質上、AIモデルには以下の4点の性質が求められます。

  1. 画像やテキスト等あらゆる情報を統合して判定したい!(マルチモーダル)
  2. リスク度だけでなく、リスク情報のカテゴリー(災害、事件、事故など)まで予測したい!
  3. リスク情報の検知漏れが起こってはならない!
  4. モデルに説明可能性をもたせたい!

マルチモーダルでマルチタスクができるAIのモデルアーキテクチャ

冒頭にも書いた通り、SNSの投稿にはテキストや画像、動画などの情報が含まれています。

また、FASTALERTでは、リスク情報を検知して配信するだけでなく、カテゴリー(災害、事件、事故などの区分)まで解析して、フィルタリングできるようにしています。リスク度の判定+リスクのカテゴリー予測を1つのモデルでマルチタスクで予想させると、ビジネス的な要件を満たすだけでなく、リスクの検知力自体を上げる効果もあります

まとめると、SNS からのリスク検知タスクに対しては、

  • 入力データ
    • テキスト
    • 画像
  • 出力データ
    • リスク検知
    • カテゴリー(災害、事件、事故などの区分)

が可能な”マルチモーダル””マルチタスク”ができるAI】が最適です。

AIの説明可能性

リスク情報を検知するというAIの性質上、AIの判断根拠を可視化することで、人間による監視や改善に役立てることができます。しかし、一般に公開されている多くのAIは”ブラックボックス”と言われ、判断根拠をうまく示すことができません。したがって、今回は判断根拠を示せるAIを開発し、実験を行いました。

AIの判断根拠を理解するためには、”挙動の理解”または”仕組みの理解”2つの方針があります。

”挙動の理解”は何かを変化させたときの出力の変化を見ることで、判断根拠を理解する方針であり、Grad_CAM, LIME, SHAP等が有名な方法です。しかし、マルチモーダルなモデルだと根拠の解釈が直感的ではないことや、計算時間が余計にかかり、リアルタイム性が失われてしまうことなどから、実装を見送りました。

”仕組みの理解”はAIモデルの仕組みを理解する事で判断根拠を見出す方針であり、Attentionが有名な方法です。Attentionを用いると、

  • 注目箇所が可視化されるので解釈があまり困らない
  • forwardの1回の計算で完結するので、計算時間が余計にかからない

のメリットがあるため、今回はAttentionを用いてAIが着目した部分を明瞭化し、判断根拠の可視化を行うことにしました。

実験

リスク検知力について

今回作成したモデルでは、全リスク情報ツイートの内99%を検知できるような再現率を達成することができました。弊社のFASTALERTでは、AIだけでなく最終的には有人による24時間体制の監視も加えて、リスク情報を漏れなく素早く検知し、より高品質な配信を実現しています。

モデルの判断根拠について

リスク情報を検知する判断根拠となる箇所を、わかりやすく可視化することで、さらに改善のための指標を得やすいようにしました。

以下に、火事、水害、横転事故のTweetを解析した例をあげました。画像は左がオリジナルで右がAIの注目箇所を表示しています。赤みが強いほど、AIがより注目したことを意味しています。

※ 以下の事例は実際のツイートではなく、ブログでの公開用に擬似的に作成したデータです(学習自体は実際のツイートで行っています)

f:id:yoooongtae:20220408110826p:plain
図2 火事についてのツイートを模したデータ。https://www.flickr.com/photos/sbeebe/7974757077から引用(licence: CC BY 2.0)。

f:id:yoooongtae:20220408111122p:plain
図3 水害についてのツイートを模したデータ。https://pxhere.com/en/photo/852904から引用(licence: CC 0.0)

f:id:yoooongtae:20220408111350p:plain
図4 横転事故についてのツイートを模したデータ。https://pxhere.com/en/photo/619517から引用(licence: CC BY 0.0)

これら結果を眺めていると、AIは各単語の関連性や、画像の情報と総合して判断箇所を注目していることがわかります。例えば、図2の火事の情報では、テキストは”消防車”と”火事”が、画像は炎の領域が注目されていることがわかります。この結果と図4の横転事故の情報を見比べてみましょう。すると”消防車”は、横転事故のときはあまり注目度が高くないのが見て取れます。図3の大雨の情報と、図4の横転事故を見比べても”道”という単語の注目度は変化してます。更に、興味深い点として、図4の横転事故のテキストの中で、最初に現れる”事故”は注目されていますが、後半の”事故”には注目されていないことがわかります。つまり、同じ文章内でも、重要度の区別をAIができていることを意味しています。

このように単純な単語のマッチングでは対応できない、複雑な言語の表現に対応した,マルチモーダルで説明可能なAIを作成することができました。

補足1:その他の前処理の工夫

リスク情報を漏らさないことを目的として、前処理をいくつか行ってます。その一つとして、絵文字の扱いを紹介します。

SNSにはテキスト情報の中に絵文字が含まれることがあります。自然言語処理を行う際にはノイズになるため絵文字は削除されることも多いのですが、リスク情報を検知する上では絵文字は重要な情報となりえます。 例えば以下のようなテキスト情報はリスク情報を含む可能性があります。

  • 東京⚡️やばい → 落雷・豪雨
  • 神田駅前🧑‍✈️👩‍✈️いっぱいいる。なにごと? → 事件
  • 東名高速で🚕 が🔥 → 交通事故

詳細な技術の説明は省きますがFASTALARTのAIはこれらの絵文字の情報も取りこぼすことなくリスク検知に役立てることで、情報の検知漏れがないようにしています。

補足2:チームでR&Dに取り組む工夫

JX通信社のMLチームにはインターン生がおり、今回の実験に関しても、インターンの小川遼人さんにサポートしていただきました。

過去にもエンジニアブログでご紹介しましたが、Pytorch Lightningをベースとした、テンプレートコードを用いて学習を行うことで、チームで効率よく実験ができるようにしています。

興味があれば以下のブログも読んてみてください tech.jxpress.net

tech.jxpress.net

我々とともに挑戦する仲間を求めています

我々とともに成長しながら、より良い社会のためのMLを開発したい仲間を社員・インターン問わず積極的に募集しています!また、MLエンジニアはもちろん、あらゆる職種のエンジニアを求めています!

正社員、インターン、そして副業・復業として体験的に働くことで、当社のカルチャーや働き方等を知っていただいたうえで正式入社を見極めていただくことが可能な「おためし入社」制度などもあります!ほんの少しでも興味を持たれた方はこちらを覗いてみてください!

slack-goとZapierで障害対応初動を自動化した話

f:id:TatchNicolas:20220316183046p:plain

サーバサイド開発やインフラ周りをいじっているたっち(TatchNicolas)です。今回はJX通信社における障害対応フローの改善について書きます。

はじめに

TL;DR;

  • slack-goとZapierを組み合わせて、障害対応時の提携作業を自動化するツールを作った
  • 「自動化しよう」という意見がでやすい場自体を仕組みとして整備したことが改善のきっかけになった

今回の話の背景

少し前に、ビープラウドさんとのイベントにて、JX通信社NewsDigestチームのCI/CDおよび障害対応についてお話しさせていただきました。*1

そのなかで、「障害対応時に専用のSlackチャンネルを都度作成していること」「Notionテンプレを使って情報の整理をしていること」*2を紹介しました。イベント後も何か障害やヒヤリハットが起こると少しずつフローが改善されていき、またNewsDigest以外のプロダクトチームでも同様のフローが採用されたりと自然と社内に浸透していきました。

JX通信社のポストモーテムでは、「発生した障害に対する技術的な振り返り、再発防止」の他に「対応のマニュアル、対応中のメンバーの振る舞い」に対する振り返りをする項目を明示的に設けています。その結果、「この手順、自動化したいな」という声が挙がったことから、ZapierとGolangを使って障害対応の初動を自動化する仕組みを作りました

f:id:TatchNicolas:20220316175706p:plain:w400
ポストモーテムで自動化の案がでたときのメモ

何をつくったか

任意のチャンネルで @incident-bot 障害発生 <プロダクト略称> <障害のひとこと説明> のような形式でSlackに書き込むと、

  • 障害対応Slackチャンネル作成(#incident-yyyy-mm-dd-<プロダクト略称>-<障害のひとこと説明>)
  • 情報をまとめるNotionページの作成
  • 関係者への連絡(然るべき常設Slackチャンネルへ新規作成した障害対応slackチャンネルとNotionページを投下)

を自動で瞬時に行ってくれます。

f:id:TatchNicolas:20220315230506p:plain:w400 f:id:TatchNicolas:20220315230514p:plain:w600

こういった作業は障害発生時のような急いでいる時ほど速く確実に行いたいので、自動化にはもってこいですね。

どう作ったか

はじめは、以前社内勉強会*3でも取り上げた https://github.com/slack-go/slack を使って適当な場所へデプロイしてサクッと完成させようと思ったのですが、Notion APIを使ってみたところ執筆時点でページ単位のAPIキーの発行に対応しておらず、あまり気軽にキーを発行するわけにもいきませんでした。

そこで社内で相談したところ「Zapier越しになら記録もいい感じに残るし使ってOK」との助言をもらったので、「じゃあいっそのことZapierで完結させるか!チャチャっと終わらせたいし!」と作り始めました。

Zapierの困りごと

Zapierは、SlackもNotionもネイティブに対応しており、Slack連携でチャンネル作成やメッセージ送信、Notion連携でページの作成などの処理を簡単に作ることができます。なのでポチポチしていくだけで簡単に今回の用件を満たすものが作れそうだと思ったのですが、Slackチャンネル作成の機能が日本語に対応していなかったので、 冒頭の例のようなチャンネルを作ろうとすると #incident-yyyy-mm-dd-nd-_ のように日本語がアンダースコアで置き換えられてしまいました。

これにより困ることが2つあって、一つは急いでいるときにチャンネル名から何が起こっているのかを特定できないことです。障害対応中はアラートを流しているチャンネルや修正のためのCI/CDの状態を流しているチャンネルなど、いろいろなチャンネルを行き来します。なので日本語でチャンネルを検索できた方が便利ですし、チャンネルが作成されたことを通知された人も何が起こっているのかパッとみてイメージしやすいので、日本語チャンネル名対応は諦めたくありませんでした。

もう一つは、あまり考えたくないですが同じ日に同じプロダクトで障害が複数発生*4した場合、日本語部分がアンダースコアになってしまうとチャンネル名が被ってしまいます。するとチャンネル作成がエラーになってしまいますし、ランダムな接尾辞などを付与するのもなんだかイケてないので困りました。

そこで「チャンネルを作る」というアクションは今後も変わることが少ないと想定されるので、チャンネル作成の部分は当初の予定通りGolangで開発したbotに任せ、それ以外をZapierに分担させることにしました。

JXならではの工夫

JX通信社は、私が所属しているプロダクトであるニュースアプリのNewsDigestのほか、To BサービスのFASTALERT、KAIZODEがあり、それぞれが共通して使う社内基盤であるXWireというシステムも存在します。

また弊社の文化として、各プロダクトのチームは技術選定やインフラ管理などを自分たちの意思と責任で実践しており、たとえば「本番デプロイのためのAWSの権限をどう管理・取得するか」のような本当に会社全体で守るべきルール以外はかなり大きな裁量がプロダクト開発チームに与えられています

一方で、他プロダクトの良いと思った開発手法やツールなどは積極的にお互いに真似していく文化もあります。私はこれを個人的に「ゆるやかで自然発生的な標準化」と呼んでいます。

なので、「障害が発生したときの動きを自動化したい」といっても、プロダクトごとに以下の設定が異なりますし、今後も違いが出てくるかもしれません。

  • 障害対応時に作成するNotionページにどんな情報を含めるか、どんなTODOを含めるか*5
  • 障害対応Slackチャンネルに誰を招待するか
  • 障害の発生を誰に通知するか
  • その他、「追加で○○がしたい」etc...

そこで、プロダクトごとに異なる部分をZapierのPath*6を使って表現することにしました。

結果的に、ツールの細かな挙動もZapierの管理画面からポチポチと変更できるので、「障害対応チャンネルに招待する人を変えたい」「通知先を変えたい」などの変更も気軽にできるようになりました

まとめ

ノーコードでポチポチ気軽に変更できる部分と、コードを書いて作る部分を組み合わせて、「ゆるやかで自然発生的な標準化」の文化を活かしつつも、共通する部分を自動化して運用負荷を下げようと試みました。ノーコードのツールを使うのは初めてで、変更差分の管理やコメントを入れにくいなど慣れない部分もありましたが、Zapierが思ったよりも高機能で驚きました。

今後使われていく中で機能を落としたり追加したり改修が増えてくると、「やっぱり普通にbotに寄せよう」「いやZapier使い倒そう」など作り方自体を変えていくかもしれませんが、現時点では「制約のなかで欲しいものをチャチャっと作る」を実現するのにちょうどいい塩梅にできたかなと思います。幸い、今回の仕組みを作ったあとでこれが必要になるようなトラブルはまだ発生していないので、実際に使ってもらいながら改善していきたいと思います。

今回のツールは障害発生時に使われるものなので出番が少ないほど嬉しい性質のものではありますが、こういった改善のアイディアが出てくるのはポストモーテムで考える範囲を広げて「人の動き方をもっと良くするにはどうすればよいか」について議論することを明示的に組み込んだ一つの成果ではないかと思います。

障害対応フローについては他社さんでも色々な工夫をされていると思うので、「こういう事例しってるよ」「ウチではこんなことしてるよ」など知見がありましたら、ぜひ教えていただければ嬉しいです。

*1:https://speakerdeck.com/tatchnicolas/cdtozhang-hai-dui-ying

*2:詳しくは↑の註釈のURLから資料を見ていただきたいですが、アラートチャンネルでそのままコミュニケーションを始めるとアラートと人間の会話が混ざってしまいますし、Slackだけでは情報の整理という意味で不足を感じたのでSlack+Notionを障害対応時のツールとして両方使っています

*3:https://tech.jxpress.net/entry/slack-app-101

*4:対応フローを整備したときに指針として「false positiveには寛大になろう」と明文化して障害発生を宣言することのハードルを下げているため、実際は何もなくともチャンネル名が重複する可能性を考慮しました

*5:障害対応ページをまとめるページもプロダクトごとに分かれているため、挿入先データベースの指定も異なります。

*6:https://zapier.com/help/create/customize/add-branching-logic-to-zaps-with-paths

AWS 上のシステムでリージョン切り替えの避難訓練を年末にやってみた

f:id:nsmr_jx:20220104095954j:plain あけましておめでとうございます。 サーバーサイドエンジニアの @kimihiro_n です。

今日はAWSに載っているシステムの避難訓練を実施したことについて書いてみようと思います。

弊社が提供している FASTALERT というサービスでは、全国の災害や事件などを検知して報道機関や自治体、インフラを支える企業などにリスク情報として提供しています。 リスク情報を提供するという性質上、情報検知の素早さや網羅性に加えて「システムの可用性」も重要なサービスの要素となっています。

FASTALERT の多くのシステムは AWS の東京リージョンで動いており、複数データセンターを活用した冗長化(マルチAZ)がされています。 しかし、例えば大規模地震のような広域かつ被害の大きい災害の場合、東京リージョン全体にわたって問題が発生する可能性があります。 首都直下型の大きな地震は今後30年以内に70%の確率で発生すると予想されており*1東京リージョンのみに頼らない構成が必要であると考えています。

また災害に限らず、AWSの特定のサービスで障害が発生した場合でも、別のリージョンへ切り替えることで素早く復旧できるケースもあるため、マルチリージョンでシステムを構成しておくことは可用性を高めてくれます。

マルチリージョン化の進め方

AWS のマルチリージョン化については、主要なコンポーネントから少しずつ進めていきました。 FASTALERT のシステムはマイクロサービス的に機能ごとに複数のコンポーネントに分かれており、止まってしまうと困る部分を優先的にリージョン単位で冗長化していきました。

実際の作業としては、リージョン間でネットワークの相互疎通できるよう土台を整え、それからデータベースやアプリケーションの冗長化に手をつけていきました。

要となるデータベースは Amazon Auroraを利用しているので、グローバルデータベース機能が利用できました。 グローバルデータベースを活用すると、データベースのリードレプリカが別リージョンへ簡単に作成できます。 また東京リージョンで障害が発生した際には、フェイルオーバー操作によって書き込み用の Writer へと昇格することが可能です。

コンポーネントの冗長化にあたっては、費用の面もあるのでホットスタンバイするものとコールドスタンバイにしておくものなど適宜使い分けるようにしています。 データベースなどリージョン切り替えに時間を要するものはホットスタンバイとして常時動かしておき、ECSで動くサービスなど素早く起動して利用できるものは停止した状態で冗長化しています。

システム自体の冗長化に加えて、欠かせないのが移行手順のドキュメント化です。 東京リージョンがまるごと利用困難になるレベルの災害を想定すると、東京にいるエンジニアも対応できない状況になっている可能性が高いです。 こうした非常時に、リモートで動けるエンジニアへバトンタッチ出来るようしっかりとしたドキュメントを用意しておく必要があります。

避難訓練の実施

個々のコンポーネントの冗長構成と動作確認は適宜行っていたのですが、全体を通して「東京リージョンの機能がほぼ使えなくなった」シナリオでの検証はやったことがありませんでした。 シナリオを想定してみることで、どの順序で何をすべきかや、どれくらい時間がかかるかなどを洗い出すことが出来ます。

ちょうど年末年始の休みで開発のキリがいいタイミングがあったため、避難訓練を実施してみることにしました。

ちなみにAWS のマネジメントコンソールは使える仮定でやっていました。実際になってみないと何が使えて何が使えないのかは分からないですが、何もかも使えないことに対処しようとすると無限に工数と費用がかかってしまうため、出来る範囲で対処できるケースを増やしていくのが大事だと思います。

避難訓練は、メンバーの1人に画面共有してもらい、マニュアル通りにリージョンの切り替え作業を行ってもらう形で実施しました。 他のメンバーは手順が正しいことを確認しつつ、サービスの状態を監視したり、どのタイミングで何を操作したかの記録を取っていきました。 マニュアルで分かりづらい点や不具合、エラーがでた箇所なども適宜メモしています。

避難訓練を実施してみて

元の状態に戻すところまで含めて1時間ぐらいで終わるかなと思っていたのですが、実際は2時間かかってしまいました

時間がかかった要因としては、マニュアル通りにうまくいかなかったことがあったことが挙げられます。 ドキュメントを作成してからシステム自体に変更が入りそのままでは動かなくなってしまっているケースや、ターゲットのリージョンで操作すべき項目を東京リージョン側で操作してしまったケースなどがありました。 練習なので落ち着いて調査出来ましたが、実際に障害が発生しているときだと大変です。 事前に不備を訓練で洗い出すことが出来たのはよかったです。

またこうすればもっと省力化できそう、みたいなアイデアも出てきたので避難訓練を実施した価値は十分あったかなと思います。 大規模な災害は起こってほしくないですが、万が一の際でも安定して提供できるサービスを作っていきたいですね。