CloudWatchアラーム発報をトリガーにBacklogへ課題を自動作成するシステムを作ってみた
はじめに
カスタマーサクセス部の奥井大和/やまとです。
今回は、CloudWatchアラームが発報されたタイミングで、自動的にBacklogに課題を作成する仕組みを作ってみました。
システムを自動化することでヒューマンエラーを防げるほか、障害発生から解決までの対応時間を正確に記録できる(トレーサビリティの向上)など、運用の効率化が期待できると考え、やってみました。
この記事が、皆さんの運用改善や業務自動化のヒントになれば嬉しいです。
※:本記事のコマンドやコード例はプレースホルダを使用していますので注意
システム概要と構成
今回ご紹介するシステムは、以下のAWSサービスとBacklogを連携させています。
- 虫眼鏡マーク:監視対象のリソース
- AWS CloudWatchアラーム:監視リソースのメトリックを監視し、設定されたしきい値を超過した場合にアラームを発報します。
- Amazon SNSトピック:CloudWatchアラームから通知を受け取り、それを複数のサブスクライバー(Lambda関数、Eメールなど)にファンアウトします。
- AWS Lambda関数:SNSトピックからのアラートメッセージを受信します。メッセージの内容に応じてBacklog APIを呼び出し、課題の作成または更新を行います。
- AWS Systems Manager Parameter Store:Lambda関数がBacklog APIキーやプロジェクトIDなどの設定情報を安全に取得するために使用します。
- Backlog:プロジェクト管理ツール。プロジェクト内のメンバーと課題を共有したり、連絡したりして使用します。
構築・運用フロー
このシステムを構築することで、監視対象のAWSリソースに異常が発生した場合、以下のような自動化されたフローが実現します。
①:しきい値を超過すると、CloudWatchアラームが発報し、SNSトピックに通知を送信
②:SNSトピックにサブスクライブしているLambda関数が実行
③:Lambda関数がAPIキーやIDなどの設定情報を取得
④:Lambda関数がBacklog APIを呼び出し、アラート内容を要約した課題を自動作成。
アラートが復旧すると、再度SNSを介してLambda関数が実行され、該当するBacklog課題が自動でコメントを追加、「完了」ステータスに更新される。
つまり、CloudWatchアラームが「ALARM」状態になるとBacklogに課題を自動作成し、「OK」状態に復旧すると、関連するBacklog課題を自動的に「完了」ステータスに更新してコメントを追加する(課題をクローズする)システムですね。
実際にやってみた
検証では、EC2インスタンスを監視対象とし、CloudWatchアラームとしてdev-okui-cpu-high
というアラームを設定しました。CPU使用率(CPUUtilization
)をメトリクスにしています。
CPUに負荷をかかることでCloudWatchアラームが発報。SNSを介してLambda関数が実行され、Backlogに課題が自動作成されることを確認しました。また、アラートが復旧すると、関連するBacklog課題が自動でクローズされました。
ざっくり実装手順
- Backlog側の設定
- APIキーの発行
- 各種IDの確認:BacklogのプロジェクトID、課題種別ID、担当者ID、カテゴリID、通知ユーザーID、およびアラート復旧時に課題を「完了」にするためのステータスIDを控えておく。
- 私はcurlコマンドでIDを取得しました(例:ステータスIDの取得)
curl -X GET "https://[プロジェクトのID].backlog.jp(もしくはcom)/api/v2/projects/[プロジェクトIDまたはキー]/statuses?apiKey=[APIキー]"
- AWS Systems Manager Parameter Storeの設定
- Backlog APIキーや各種IDを安全に保存するため、AWS Systems Manager Parameter Storeに以下のパラメータを登録。プレフィックスとして
/backlog/prod/
を使用。backlog-api-key
:発行したBacklog APIキーを SecureString タイプで保存。project-id
:BacklogのプロジェクトIDを保存。issue-type-id
:課題の種別IDを保存。assignee-id
:課題の担当者IDを保存。category-id
:課題のカテゴリIDを保存。notify-user-ids
:通知するユーザーのIDリストをカンマ区切りなどで保存。status-id-closed
:「完了」ステータスIDを保存。
- Backlog APIキーや各種IDを安全に保存するため、AWS Systems Manager Parameter Storeに以下のパラメータを登録。プレフィックスとして
- AWS Lambda関数の設定
- Lambda関数の作成
- 検証時の設定
- 設置リージョン:
ap-northeast-1
- ランタイム:Python 3.13
- アーキテクチャ:arm64
- 設置リージョン:
- 検証時の設定
- コードのデプロイ
- 環境変数の設定:Lambda関数に
PARAMETER_PREFIX
という環境変数を追加し、値を/backlog/prod
に設定。- SSM Parameter Storeから設定値を取得する際に、パスのプレフィックスとして使用されます。これにより、異なる環境(開発、ステージング、本番など)で同じパラメータ名を使用しながらも、異なるパスで管理することが可能になりますね。
- レイヤーの追加:
requests
ライブラリなど、コードに必要な外部ライブラリを含むLambdaレイヤーを追加。- このレイヤーはHTTPリクエストを送信するために必要なPythonの
requests
ライブラリを含んでいます。これにより、関数コードのデプロイパッケージを小さく保ち、依存関係の管理を簡素化できます。
- このレイヤーはHTTPリクエストを送信するために必要なPythonの
- Lambda関数の作成
- Amazon SNSトピックの設定
- SNSトピックの作成
- サブスクリプションの追加:作成したLambda関数をこのSNSトピックにサブスクライブする。必要に応じて、通知を受け取りたいEメールアドレスなども追加。
- AWS CloudWatchアラームの設定
- アクションの設定:アラームが「ALARM」と「OK」状態になった場合に、手順4で作成したSNSトピックに通知を送信するように設定します。
Lambda関数について
コード
import json
import requests
import os
import boto3
from datetime import datetime, timezone, timedelta
import logging
# --- Standard Configuration ---
# Set up logging to standard output
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# Set up timezone for JST
JST = timezone(timedelta(hours=+9), 'JST')
# --- Backlog API Constants ---
# {your_space_id}の部分はご自身のBacklogスペースIDに書き換えてください
BACKLOG_API_URL = "https://{your_space_id}.backlog.jp/api/v2"
# Priority: 3は検証したプロジェクトでは「中」に相当
DEFAULT_PRIORITY_ID = 3
# Status: 1は検証したプロジェクト「未対応」に相当
STATUS_ID_OPEN = 1
# HTTP request timeout in seconds
REQUEST_TIMEOUT = 30
# --- AWS Clients (initialized once per container) ---
ssm = boto3.client('ssm')
def get_parameter(name):
"""
Retrieves a parameter from AWS SSM Parameter Store.
Trims leading/trailing whitespace.
"""
try:
# 環境変数でプレフィックスを定義することで、開発/本番環境の切り替えを容易にしている
prefix = os.environ.get('PARAMETER_PREFIX', '/backlog/prod')
param_path = f"{prefix}/{name}"
response = ssm.get_parameter(Name=param_path, WithDecryption=True)
value = response['Parameter']['Value'].strip()
logger.info(f"Successfully retrieved parameter: {name}")
return value
except Exception as e:
logger.critical(f"CRITICAL: Could not retrieve required parameter '{name}'. Error: {e}")
raise
# --- Configuration Loading (runs once on Lambda cold start) ---
try:
API_KEY = get_parameter('backlog-api-key')
PROJECT_ID = get_parameter('project-id')
ISSUE_TYPE_ID = get_parameter('issue-type-id')
ASSIGNEE_ID = get_parameter('assignee-id')
CATEGORY_ID = get_parameter('category-id')
STATUS_ID_CLOSED = get_parameter('status-id-closed')
notify_users_str = get_parameter('notify-user-ids')
NOTIFY_USER_IDS = [user_id.strip() for user_id in notify_users_str.split(',') if user_id.strip()]
except Exception:
API_KEY = None
logger.critical("Failed to load one or more required parameters during initialization.")
def lambda_handler(event, context):
"""
Main handler for the Lambda function.
Parses an SNS message from a CloudWatch alarm.
Creates a Backlog issue on ALARM, closes it on OK.
"""
if not API_KEY:
logger.error("Configuration is missing or failed to load. Aborting execution.")
return {'statusCode': 500, 'body': json.dumps('Configuration error during initialization.')}
try:
sns_message = json.loads(event['Records'][0]['Sns']['Message'])
alarm_name = sns_message.get('AlarmName', 'Unknown Alarm')
logger.info(f"Processing alarm: {alarm_name}")
state = sns_message.get('NewStateValue')
if state == 'ALARM':
create_backlog_issue(sns_message)
elif state == 'OK':
close_backlog_issue(sns_message)
else:
logger.warning(f"Received unknown alarm state: {state}")
return {'statusCode': 200, 'body': json.dumps('Process completed successfully.')}
except (json.JSONDecodeError, IndexError, KeyError) as e:
logger.error(f"Failed to parse SNS message event. Error: {str(e)}. Event: {event}")
return {'statusCode': 400, 'body': json.dumps('Bad request: Invalid event structure.')}
except Exception as e:
logger.error(f"An unexpected error occurred in lambda_handler: {str(e)}", exc_info=True)
return {'statusCode': 500, 'body': json.dumps(f'Internal Server Error: {str(e)}')}
def close_backlog_issue(message):
"""
Finds the corresponding open issue for a recovered alarm and closes it.
"""
alarm_name = message.get('AlarmName', 'N/A')
logger.info(f"Attempting to close issue for recovered alarm: {alarm_name}")
try:
search_params = {
'apiKey': API_KEY,
'projectId[]': [PROJECT_ID],
'statusId[]': [STATUS_ID_OPEN],
'keyword': alarm_name,
'sort': 'updated',
'order': 'desc',
'count': 1
}
response = requests.get(f"{BACKLOG_API_URL}/issues", params=search_params, timeout=REQUEST_TIMEOUT)
response.raise_for_status()
issues = response.json()
if not issues:
logger.warning(f"No open issue found for alarm '{alarm_name}'. Nothing to close.")
return
target_issue = issues[0]
issue_key = target_issue.get('issueKey')
logger.info(f"Found open issue to close: {issue_key}")
except requests.exceptions.RequestException as e:
logger.error(f"Failed to search for Backlog issue. Status: {e.response.status_code if e.response else 'N/A'}, Response: {e.response.text if e.response else 'N/A'}")
raise
try:
jst_timestamp_for_desc = datetime.now(JST).strftime('%Y-%-m-%-d %H:%M:%S %Z')
update_data = {
'statusId': STATUS_ID_CLOSED,
'comment': f"アラートが復旧しました。\n発生時刻 (JST): {jst_timestamp_for_desc}"
}
update_params = {'apiKey': API_KEY}
response = requests.patch(
f"{BACKLOG_API_URL}/issues/{issue_key}",
params=update_params,
data=update_data,
timeout=REQUEST_TIMEOUT
)
response.raise_for_status()
logger.info(f"Successfully closed issue {issue_key}")
except requests.exceptions.RequestException as e:
logger.error(f"Failed to update Backlog issue {issue_key}. Status: {e.response.status_code}, Response: {e.response.text}")
raise
def create_backlog_issue(message):
"""
Constructs and sends a request to create a new issue in Backlog.
"""
alarm_name = message.get('AlarmName', 'N/A')
utc_timestamp_str = message.get('StateChangeTime', '')
try:
utc_dt = datetime.fromisoformat(utc_timestamp_str.replace('Z', '+00:00'))
jst_dt = utc_dt.astimezone(JST)
date_str_for_title = jst_dt.strftime('%y%m%d%H%M')
jst_timestamp_for_desc = jst_dt.strftime('%Y-%-m-%d %H:%M:%S %Z')
except (ValueError, TypeError):
logger.warning(f"Could not parse timestamp: {utc_timestamp_str}. Using current time.")
jst_dt = datetime.now(JST)
date_str_for_title = jst_dt.strftime('%y%m%d%H%M')
jst_timestamp_for_desc = jst_dt.strftime('%Y-%m-%d %H:%M:%S %Z')
title = f"【アラート発生】{date_str_for_title}-{alarm_name}"
description = f"""お世話になっております。
CloudWatchアラートの発生を検知しました。
## ■ アラート内容
{message.get('NewStateReason', '詳細不明')}
## ■ 詳細情報
- **アラーム名:** {alarm_name}
- **発生時刻 (JST):** {jst_timestamp_for_desc}
- **リージョン:** {message.get('Region', 'N/A')}
- **アカウントID:** {message.get('AWSAccountId', 'N/A')}
"""
today_jst = datetime.now(JST).strftime('%Y-%m-%d')
params = {'apiKey': API_KEY}
data = {
'projectId': PROJECT_ID,
'summary': title,
'description': description,
'issueTypeId': ISSUE_TYPE_ID,
'priorityId': DEFAULT_PRIORITY_ID,
'assigneeId': ASSIGNEE_ID,
'categoryId[]': [CATEGORY_ID],
'startDate': today_jst,
'dueDate': today_jst,
'notifiedUserId[]': NOTIFY_USER_IDS
}
try:
logger.info(f"Sending request to create issue for alarm: {alarm_name}")
response = requests.post(
f"{BACKLOG_API_URL}/issues",
params=params,
data=data,
timeout=REQUEST_TIMEOUT
)
response.raise_for_status()
issue_data = response.json()
issue_key = issue_data.get('issueKey', issue_data.get('id', 'N/A'))
logger.info(f"Successfully created Backlog issue: {issue_key}")
except requests.exceptions.HTTPError as e:
logger.error(f"Failed to create Backlog issue. Status: {e.response.status_code}, Response Body: {e.response.text}")
raise
except requests.exceptions.RequestException as e:
logger.error(f"HTTP request to Backlog failed: {str(e)}")
raise
設定したLambda関数(lambda_function.py)の説明
Lambda関数のコードは、Pythonで記述されており、主に以下の機能から構成されています。
a. ライブラリのインポートと初期設定
json
,requests
,os
,boto3
,datetime
,logging
などの標準ライブラリとAWS SDKがインポートlogging
モジュールを使用してロギングが設定され、INFO
レベル以上のログが出力JST
タイムゾーンが定義されており、ログやBacklog課題の時刻表示に利用- Backlog APIの定数
BACKLOG_API_URL
:BacklogのAPIエンドポイント({your_space_id}
は実際のスペースIDに置き換えが必要)DEFAULT_PRIORITY_ID
:課題の優先度ID(検証プロジェクトでは、3 = 中)STATUS_ID_OPEN
:未対応ステータスID(検証プロジェクトでは、1)REQUEST_TIMEOUT
:HTTPリクエストのタイムアウト時間(30秒)
- AWS Systems Manager (SSM) のクライアント (
boto3.client('ssm')
) が初期化され、Parameter Storeから設定値を取得
b. 設定値の取得 get_parameter(name)
関数
AWS Systems Manager Parameter Storeから設定値を取得する関数。
- 環境変数で指定されたプレフィックスを使用してパラメータパスを構築
WithDecryption=True
により暗号化されたパラメータも自動復号化- 取得した値の前後の空白を削除して返す
c. 初期化処理
Lambdaのコールドスタート時に一度だけ実行される設定値の取得。
- Backlog APIキー、プロジェクトID、課題タイプID、担当者ID、カテゴリID、ステータスIDなどを取得
- 通知対象ユーザーIDはカンマ区切り文字列から配列に変換(空要素は除外)
- 取得失敗時は
API_KEY = None
を設定し、実行時にチェック
d. ハンドラー
lambda_handler(event, context)
Lambda関数のメインエントリーポイント。
- 設定値が正しくロードされているか確認(失敗時は500エラーを返却)
- SNSメッセージからCloudWatchアラームの情報を解析
- アラーム状態が「ALARM」の場合:Backlogに新規課題を作成
- アラーム状態が「OK」の場合:対応するBacklog課題をクローズ
- エラーハンドリングとログ記録を実施
create_backlog_issue(message)
CloudWatchアラーム発生時にBacklogに新規課題を作成する関数。
- アラーム情報から課題のタイトルと説明文を生成
- UTCタイムスタンプをJSTに変換(解析失敗時は現在時刻を使用)
- タイトル用日時は
yymmddHHMM
形式、説明文用はゼロパディングなしの形式 - Backlog APIを使用して課題を作成し、関係者に通知
- 開始日と期限日は当日に設定
close_backlog_issue(message)
アラーム復旧時に対応するBacklog課題をクローズする関数。
- アラーム名で該当する未対応課題を検索(更新日時の降順で最新1件のみ取得)
- 見つかった課題のステータスを「完了」に更新
- 復旧時刻(JST)をコメントとして追加
- 該当課題が見つからない場合は警告ログを記録して処理終了
イベントJSON
テストイベントJSONの典型的な構造は以下の通り。
イベントJSON
{
"Records": [
{
"EventSource": "aws:sns",
"EventVersion": "1.0",
"EventSubscriptionArn": "arn:aws:sns:ap-northeast-1:XXXXXXXXXXXX:test-topic",
"Sns": {
"Type": "Notification",
"MessageId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"TopicArn": "arn:aws:sns:ap-northeast-1:XXXXXXXXXXXX:test-topic",
"Subject": "ALARM: test-alarm",
"Message": "{\"AlarmName\":\"test-alarm\",\"AlarmDescription\":\"Test alarm description\",\"AWSAccountId\":\"XXXXXXXXXXXX\",\"NewStateValue\":\"ALARM\",\"NewStateReason\":\"Threshold Crossed: 1 datapoint [5.0 (14/09/25 08:00:00)] was greater than the threshold (1.0).\",\"StateChangeTime\":\"2025-09-14T08:00:00.000Z\",\"Region\":\"ap-northeast-1\",\"OldStateValue\":\"OK\",\"Trigger\":{\"MetricName\":\"CPUUtilization\",\"Namespace\":\"AWS/EC2\",\"StatisticType\":\"Statistic\",\"Statistic\":\"AVERAGE\",\"Unit\":null,\"Dimensions\":[{\"name\":\"InstanceId\",\"value\":\"i-XXXXXXXXXXXXXXXXX\"}],\"Period\":300,\"EvaluationPeriods\":1,\"ComparisonOperator\":\"GreaterThanThreshold\",\"Threshold\":1.0}}",
"Timestamp": "2025-09-14T08:00:00.000Z",
"SignatureVersion": "1",
"Signature": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"SigningCertUrl": "https://example.com/cert",
"UnsubscribeUrl": "https://example.com/unsubscribe"
}
}
]
}
Lambdaの実行ロールについて
以下の権限が必要となります。
- CloudWatch Logsへの書き込み権限:AWS管理ポリシー
AWSLambdaBasicExecutionRole
をアタッチ - Systems Manager Parameter Storeからのパラメータ取得権限:ポリシーを作成
インラインポリシー
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ParameterStoreAccess",
"Effect": "Allow",
"Action": [
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:DescribeParameters"
],
"Resource": [
"arn:aws:ssm:ap-northeast-1:123456789012:parameter/backlog/prod/*"
]
},
{
"Sid": "KMSDecryptForSSM",
"Effect": "Allow",
"Action": "kms:Decrypt",
"Resource": "arn:aws:kms:ap-northeast-1:123456789012:key/*"
}
]
}
パラメータとそのメタデータを取得するアクションを許可しました。SecureString形式のパラメータを使用する場合は、AWS KMSのkms:Decrypt
アクションが必要となります。これらの権限は、アクセスするパラメータのパス(例:/backlog/prod/*
)に限定しました(もっと最小権限に限定することもできるはず)。
実装にあたってのQ&A
Q:CloudWatchから直接Lambdaでも構築できるが、何故SNSを使うのか?
A: SNSトピックを介することで、 ファンアウトのメリット を享受できるからです。CloudWatchから直接Lambdaを呼び出すことも可能ですが、SNSを間に挟むことで、一つのアラート通知を複数の宛先に同時に配信できるようになります。
例えば、本システムでは、Lambda関数に加えて、運用担当者のEメールアドレスにもアラート通知を送信しています。このように通知経路の拡張が容易になります。
Q:Parameter Storeを使用する理由は何ですか?
A: APIキーなどの機密情報をコードから分離し、 セキュリティを向上させる とともに、設定変更時のコード修正が不要になるため、 運用管理も容易 にする目的でParameter Storeを使用しています。
APIキーのような機密情報をLambda関数のコードにハードコーディングすることは、セキュリティ上の大きなリスクとなります。コードを共有したり、誤って公開してしまったりした場合、APIキーが漏洩し、不正利用される可能性があります。
Parameter Storeは、設定データやシークレット管理のための安全なストレージを提供します。機密情報は、SecureString
タイプとして暗号化して保存できます。より詳細な情報は、AWS Systems Managerの公式ドキュメント「AWS Lambda 関数での Parameter Store パラメーターの使用」をご参照ください。
同様の理由で、AWS Secrets Managerの利用も推奨されます。ただ、今回はコスト面と実装コードのシンプルさを追求して、Secrets ManagerやLambda 拡張機能を見送り、Parameter Storeを使用しました。
感じたメリット
1. 運用負荷が削減され効率良くなった
- 手動での課題起票が不要になり、アラート対応の作業時間を削減できた
2. 対応時間の記録が正確になった
- アラート発生から課題の起票まで タイムラグがゼロ になった
- 復旧時も自動で課題クローズ
- 発生〜復旧までの時間が正確に記録される
3. ヒューマンエラーの排除できた
- 起票漏れ・記入ミスがなくなる
- 定型フォーマットで情報の一貫性を確保
- アラートの見落としを防止
4. トレーサビリティが向上した
- Backlog上にすべての履歴が自動記録されるため、過去の障害対応の振り返りや監査対応が容易になった
運用コストの概算
本システムで利用するAWSサービス(CloudWatch、SNS、Lambda、Systems Manager Parameter Store)は、いずれも無料枠が非常に広範に設定されており、小規模なシステムや検証環境であれば、ほとんどのケースで無料枠内に収まることが多いです。
AWS Lambda:無料枠で月間100万リクエストと40万GB-秒のコンピューティング時間。
Amazon SNS:無料枠で月間100万リクエストの発行、1,000件のEメール配信。またLambda への配信は無料。
AWS CloudWatch:無料枠で10個のアラーム (標準解像度メトリクス)。またそれ以上のアラームは月毎USD 0.10/アラームメトリクスとなる(東京リージョン)。
Systems Manager Parameter Store:標準パラメータストレージは無料。
Parameter Store では、SecureString パラメータの作成には料金はかかりませんが、AWS KMS 暗号化の使用には料金がかかります。詳細については、「AWS Key Management Service の料金表」を参照してください。
AWS Key Management Service:毎月20,000 件のリクエストの無料利用枠。
これらの無料枠を考慮すると、本システムは非常に 安価で運用できる と言えるでしょう。ただし、大規模なシステムで大量のアラートが発生する場合や、頻繁なパラメータアクセスがある場合は、無料枠を超える部分に対して課金が発生します。
運用コストを詳細に見積もりたい場合は、各AWSサービスの料金ページをご確認ください。
一番伝えたいこと
今回の試みは「低コストで実現できる、運用品質を向上させる自動化」を実現します
このシステムで、以下の5つのメリットを同時に実現します:
- 安い:AWSの無料枠でほぼカバー可能(小規模システムの場合)
- 速い:アラート発生と同時に自動で課題作成
- 確実:人為的ミスや見落としがゼロに
- 安全:APIキーなどの機密情報を適切に保護
- 改善しやすい:すべての履歴が記録され、振り返りが容易
手動での課題起票から自動化した仕組みを導入することで「効率の良い運用」が実現できました。
さいごに
本記事では、AWS CloudWatchとBacklogを連携させ、アラート発生時の課題自動作成・復旧時の自動クローズを実現するシステムをご紹介しました。
この記事が、日々の運用業務に課題を感じている方の、業務自動化やシステム改善の一助となれば幸いです。ぜひ、ご自身の環境でこのシステムを試してみてください。
参考情報
- Backlog Developer API
- APIの設定 – Backlog ヘルプセンター
- ユーザーの権限 – Backlog ヘルプセンター
- Lambda Extensions と Parameter Store の統合 - AWS Systems Manager
AWS運用代行・サーバー監視のご案内
クラスメソッド マネージドサービスは、AWS国内支援実績No.1のクラスメソッドが提供する、クラウド特有の対応やクラウド技術者の不足に課題をお持ちのお客様向けのAWS運用トータル支援サービスです。
監視や運用支援にとどまらず、お客様のクラウド利用を最適化し日々の負担を最小化することで、お客様のビジネス効果の最大化を支援します。
アノテーション株式会社について
アノテーション株式会社はクラスメソッドグループのオペレーション専門特化企業です。サポート・運用・開発保守・情シス・バックオフィスの専門チームが、最新 IT テクノロジー、高い技術力、蓄積されたノウハウをフル活用し、お客様の課題解決を行っています。当社は様々な職種でメンバーを募集しています。「オペレーション・エクセレンス」と「らしく働く、らしく生きる」を共に実現するカルチャー・しくみ・働き方にご興味がある方は、アノテーション株式会社 採用サイトをぜひご覧ください。