SSHからRESTへ: セキュリティ主導で進めるSlackのEMRデータパイプライン近代化
(slack.engineering)- Slackのデータプラットフォームでは、700以上のSSHベースOperatorが日次の検索インデックス作成、分析ジョブなど中核的なデータパイプラインを運用しており、すべてのジョブが本番AWS EMRクラスターへの直接SSH接続を必要としていたため、広範なセキュリティ攻撃面を形成していた
- このSSH依存はセキュリティリスクにとどまらず、Spark on Kubernetes、EMR on EKSへの移行、Whitecastleイニシアチブの完了などインフラ近代化を阻む主要な障害になっていた
- 解決策としてYARN Distributed Shellを活用し、任意のシェルコマンドまでYARNコンテナ内で実行可能にし、Slack独自のRESTゲートウェイQuarryを通じてすべてのジョブ投入を統合
- 8つのデータリージョンにまたがってダウンタイムなしで700以上のジョブを移行し、3四半期でSSHを100%撤廃
- 結果として攻撃面の縮小、ジョブ信頼性の向上、可視性の改善を実現し、Whitecastleの完遂およびSpark on Kubernetesなど次世代インフラの基盤を確保
背景: SSHベースのデータプラットフォームの形成
- 2017年ごろに構築されたSlackのデータプラットフォームでは、AirflowがEMRクラスターでジョブを実行する最も直接的な経路としてSSH方式を採用
SSHOperatorでEMRマスターノードに接続し、spark-submitのようなコマンドを実行
- その後、各チームがさまざまな用途に応じて(SparkだけでなくMapReduce、AWS CLI、カスタムPythonスクリプトまで)カスタムSSHベースOperatorを独自に作成
- その結果、本番環境には700以上のSSHベースジョブが蓄積
SSH方式の実際のコスト
-
潜在的なセキュリティリスク
- コンピュートクラスターへの直接SSH接続が攻撃面を拡大
- オーケストレーションワーカー全体にわたる鍵の配布とローテーションで運用負荷が増大
- きめ細かな監査のために複数システムのログ相関分析が必要
- 専用セキュリティグループやカスタム設定など権限管理が複雑化
-
運用面の問題
- ジョブが分散されずEMRマスターノード上で直接実行されるため、リソース競合が発生
- Kubernetes Podの再起動時にSSH接続が切れてジョブが失敗
- 長時間ジョブが接続終了後も実行され続け、ゾンビプロセス化
- 接続が切れるとジョブの成功/失敗を確認できない
-
近代化を妨げる要因
- Spark on KubernetesおよびEMR on EKSへ進めない(SSH依存の解消が先行条件)
- 最後に残ったメインアカウントのEMRクラスターを子アカウントへ移せず、Whitecastleイニシアチブが未完了
- Whitecastleは、セキュリティとネットワーク分離の強化のためにAWSインフラを子アカウントへ移行するSlackの取り組み
- 適切なジョブ監視と可視性の実装が不可能
-
代表例 — Search Infrastructureチーム
- テラバイト級データで日次のSolr検索インデックスを構築するパイプラインで、Slack検索機能の中核
- SSHベースのジョブ投入に依存していたため、上記の信頼性問題がすべて表面化していた
RESTベースのジョブ投入の基本概念
-
SSHの本質的な限界
- SSH接続は状態を持つ直接接続であり、Pod再起動などで接続が切れるとコマンドはそのまま実行されるか失敗するか、あるいは孤立プロセスが残る
- 再接続して状態を確認する信頼できる手段がない
-
RESTという代替
- YARN、Trino、Snowflakeなどの現代的なコンピュートエンジンはHTTP APIでジョブ投入をサポート
- POST ジョブ要求 → ジョブIDを返却
- GET ジョブ状態照会 → 実行中/完了/失敗を確認
- DELETE ジョブ → 正常にキャンセル
- ジョブのライフサイクルをサーバー側で管理するため、クライアントが再起動してもジョブは継続し、状態照会も可能
- YARN、Trino、Snowflakeなどの現代的なコンピュートエンジンはHTTP APIでジョブ投入をサポート
-
YARNの役割と限界
- Hadoopワークロード(MapReduce、Spark、Hive)では、YARNがリソースマネージャーでありREST APIも提供
- しかし、
aws s3 sync、hadoop distcpなど任意のシェルコマンドを実行する300以上のCLIベースジョブには、そのまま使えるREST APIがない - これを解決した鍵がYARN Distributed Shell
ブレークスルー: YARN Distributed Shell
- SparkにはLivy REST API、HiveにはHiveServer2があるため、移行は比較的容易
- 一方でMapReduceジョブと300以上のCLIベースジョブは、すぐ使えるREST APIがなく難題だった
-
要件
- アーキテクチャに自然に適合するシンプルなRESTベースソリューション
- 既存の認証・認可メカニズムを活用(カスタムセキュリティ層は不要)
- 独自実装ではなく**オープンソースのプロトコル(標準YARN API)**を活用
- 最小限の複雑さでカスタムジョブ実行基盤の構築・運用を回避
-
検討後に却下された案
- リモートコマンド実行用のカスタムラッパーサービス構築
- Ansible、Saltなどのリモート実行フレームワークの利用
- YARNに新しいジョブタイプをゼロから追加
- いずれも複雑すぎること、カスタムセキュリティ実装、新たな依存関係の導入といった問題で不適切だった
-
YARN Distributed Shellの発見
org.apache.hadoop.yarn.applications.distributedshell.ApplicationMasterにより、任意のシェルスクリプトをYARNコンテナ内で実行可能- すでにYARNに含まれている機能で、同じREST APIを使えるため、カスタムセキュリティ層は不要
-
動作方式
- 1. コマンドスクリプトをS3へアップロード(例:
aws s3 sync /tmp/data/ s3://bucket/output/) - 2. Distributed Shell設定でYARNへ投入
application-typeをMAPREDUCEに設定し、am-container-specにDISTRIBUTEDSHELLSCRIPTLOCATION、DISTRIBUTEDSHELLSCRIPTLEN、DISTRIBUTEDSHELLSCRIPTTIMESTAMPなどの環境変数を含める
- 3. YARNがコンテナを割り当て、スクリプトをダウンロードして実行
- YARNがメモリ/vCoreなどのリソース制限、コンテナ分離、リトライと耐障害性、正常キャンセル、YARN UIベースのログ管理を担う
- 1. コマンドスクリプトをS3へアップロード(例:
- この判断により、Hadoopワークロードだけでなく
aws s3 sync、hadoop distcp、カスタムPythonスクリプトまですべてYARNコンテナ内で実行可能になった
ソリューション: Quarry
- Quarryは、EMR/YARN、Trino、Snowflakeなど複数のコンピュートエンジンにジョブを投入するために作られたSlackのRESTベースジョブ投入ゲートウェイ
- すでに認証、信頼性、可視性の問題を解決しており、SSH廃止の要件に正確に合致していた
-
Quarryの機能
- 認証: SSH鍵の代わりにサービス間トークンを使用
- ジョブ投入: YARN、Trino、SnowflakeへREST APIで送信
- 状態追跡: ジョブ状態をサーバー側で監視
- ライフサイクル管理: REST APIベースで正常キャンセルとクリーンアップを実施
- 可視性: すべてのジョブ投入に対する構造化ログ、メトリクス、トレーシングを提供
-
アーキテクチャの変化
- 以前:
Airflow → SSH Connection → EMR Master Node → Execute Command - 以後:
Airflow → Quarry REST API → YARN ResourceManager → EMR Container - Airflow OperatorはSSH接続の代わりにQuarryへHTTPリクエストを送り、QuarryがYARNへ投入した後に状態をポーリング
- Airflow Podが再起動してもジョブは維持され、Quarryが接続を保持
- 以前:
-
Quarryの強み
- YARN Distributed Shell対応により汎用ジョブ投入ゲートウェイ化
- Sparkジョブ、Hiveクエリ、シェルスクリプトをすべて同じREST API経由で扱える
- SSH資格情報やクラスターへの直接アクセスを完全に排除し、認証付きREST呼び出しとサーバー側ジョブ追跡のみを利用
移行の道のり
- 8つの独立したデータリージョンにまたがる700以上の本番ジョブ、それぞれ異なるネットワーク構成とデータ主権要件、さらに検索インデックス作成のように停止不可のワークロードがあったため、体系的な計画が必要だった
-
段階的アプローチ
- Phase 1 – 概念実証(PoC): パイロットジョブでQuarryベースのアプローチを検証し、最初のQuarry Operatorを開発してdev環境でテスト
- Phase 2 – セキュリティレビュー: セキュリティチームと連携し、資格情報削除計画を策定、RESTベース方式が要件を満たすか検証
- Phase 3 – OKR主導の実行: Key Resultに設定して経営層の可視性を確保し、この段階で移行率80%のマイルストーンを達成
- Phase 4 – 大規模移行: Search Infrastructure、Data Engineering & Analytics、ML Servicesなど複数チームが並行して残りワークロードを全リージョンへ展開
- Phase 5 – 最終整理: 取りこぼしたDAGを完了し、すべてのレガシーSSH Operatorを廃止して100%達成
-
移行の数値
- 7種類のOperatorにまたがって700以上のジョブを移行
- 調整されたロールアウトで8つの独立データリージョンに適用
- 5チームが新しいOperatorへ切り替え
- ビジネス中核サービスは無停止
- 初期パイロットからSSH完全撤廃まで3四半期で完了
移行中に直面した課題
-
Challenge 1 — Virtual Memory Check失敗
- データexport DAGの移行中、SSHでは正常だったジョブがvmemチェックエラーで失敗
- 原因: SSHではマスターノード上で直接実行されるためYARNのリソース強制を回避していたが、Quarryはジョブを正しくYARNへ投入するため、仮想メモリ上限を超えたコンテナが拒否された
- 解決: AWSベストプラクティスに従い、全クラスターでvmemチェックを無効化 —
"yarn.nodemanager.vmem-check-enabled": "false"- Linuxの仮想メモリ会計は信頼できず、物理メモリ上限で十分だというAWSの推奨に沿う
- 教訓: SSHは多くの問題を隠していたため、正規のYARN投入へ切り替える際には以前は見えなかったリソース上限問題が出ることを想定し、dev環境で十分に検証する必要がある
-
Challenge 2 — ネットワーク分離とEKM接続問題
- dev検索基盤ジョブをdevクラスターからstaging分析クラスターへ移した際、EKM(Enterprise Key Management)接続タイムアウトが発生
- エラー:
Unable to execute HTTP request: Connect to sts.amazonaws.com:443 failed: connect timed out - 原因: 元のクラスターにはキー管理エンドポイントへのネットワークルーティングが構成されていたが、より厳格なネットワークセグメントにあるstaging分析クラスターには同等の接続性がなく、ジョブ設定に明示されていなかったネットワークトポロジー依存が露呈した
- 解決: Search Infrastructureの作業をdevサービスへルーティング可能なdev ETLクラスターへ移し、本番Hiveカタログが必要な作業はstagingに残し、dev ETLクラスターをスケールアップして追加ワークロードを吸収
- 教訓: ネットワークトポロジーは極めて重要であり、どのクラスターでどのジョブを動かすか決める前に、ネットワーク分離とアカウント境界を理解する必要がある
-
Challenge 3 — マルチリージョンの複雑さ
- データ主権要件により8つの独立データリージョンでEMRクラスターを運用しており、SSH廃止は事実上8件の並行移行だった
-
複雑性の要素
- 設定管理: リージョンごとに個別のQuarry設定、クラスターエンドポイント、ネットワークルーティングルールが必要
- テスト負荷: すべてのコード変更を8リージョン全体で検証
- 順次デプロイ: 同時展開は不可で、リージョン単位の段階的ロールアウトが必要
- リージョン固有の問題: ネットワーク構成、データ主権ルール、クラスターのバージョン差異
-
対応方法
- 単一のパイロットリージョン(主にUSベース)で検証
- リージョン別の設定要件を文書化
- リージョン認識可能なQuarry Operatorを構築
- 段階的ロールアウトとリージョンごとの学びを反映
- リージョンごとの移行進捗を個別に追跡
- 教訓: マルチリージョン基盤は単にN倍ではなく、リージョン固有の障害モードまで含めてN倍難しいため、クロスリージョン調整とリージョン別デバッグに十分な時間を割く必要がある
結果
- SSHを100%撤廃し、すべての本番ジョブをQuarry経由のRESTベース投入へ移行
-
セキュリティ面の成果
- 8つの独立データリージョンすべての本番EMRクラスターでSSHアクセスを削除し、攻撃面を大幅に縮小
- SSH鍵配布をサービス間トークン認証に置き換え、REST APIログによって適切な監査証跡を確保
- すべてのジョブ投入がQuarry経由の構造化ログを保有
- 最後のAWSメインアカウントEMRクラスターを子アカウントへ移行し、Whitecastleイニシアチブを完遂
- 特殊なセキュリティグループと複雑な権限管理を排除し、コンプライアンスを簡素化
-
運用面の改善
- マスターノードでのリソース競合を解消し、すべての非Hadoopジョブが分散YARNコンテナで適切なリソース割り当てのもと実行
- クライアント側Kubernetes Podが再起動してもジョブは維持され、ジョブ信頼性が大幅向上、ゾンビプロセスも消滅し、REST API経由で正常終了が可能に
- Quarry APIにより構造化されたジョブ状態/ログ/メトリクスを提供し、ジョブライフサイクル全体の追跡、YARNコンテナログの確認、適切なツールでのデバッグが可能に
-
将来に向けた基盤
- SSH依存の解消によりSpark on Kubernetesへの移行が可能に
- RESTベースのアーキテクチャがクラウドネイティブの実践と整合
- 複雑なSSH設定に比べてシンプルで保守しやすいQuarry Operatorにより、チームのオンボーディングが容易
- AirflowをEMRインフラの詳細から疎結合化
- すべてのジョブ投入をQuarryへ標準化し、将来の変更を容易に
- 完了後2年の本番運用を通じて、このアーキテクチャ判断の妥当性が検証され、セキュリティ、運用安定性、インフラ柔軟性のすべてが改善した
学んだ教訓
-
うまくいった点
- 段階的移行: Dev → GovDev/CommDev → Prodの順次ロールアウトとOperatorタイプ別の移行により、段階ごとの学びを蓄積
- 強力なチーム連携: Search、Analytics、Data Engineering、ML、Marketingなど多領域が協力し、迅速なコードレビューと共通チャネルでのコミュニケーションを実現
- 分析に基づく進捗追跡: 全リージョンの移行進捗ダッシュボードを構築し、Airflowデータベースへのクエリで残存するSSHベース作業を特定
-
やり直すなら変えたい点
- ネットワークトポロジーの事前マッピングが必要: EKM接続のようなネットワーク分離問題を後半で発見したため、Whitecastleのアカウント境界とネットワークルーティングをクラスター移行前に文書化すべきだった
- リソース上限テストを早期に実施すべき: vmemチェック問題が後半で発生したため、初期パイロット段階からSSHとの差分としてYARNリソース上限テストを含めるべきだった
- Operator制限に関する事前周知を強化すべき: 最終段階でSSHOperatorの新規利用を制限した際、一部チームが認識していなかったため、Airflow利用者全体への事前告知をより強化すべきだった
-
大規模移行のベストプラクティス
- 移行前にまず監視を整備: 残存ジョブを常に把握できるダッシュボードを早期に用意し、Airflow DBクエリを活用
- 複数環境でのテスト: Dev、CommDev、GovDevでテストし、環境固有の問題を本番前に把握。特にアカウント境界をまたぐテストでネットワーク分離問題を事前発見
- Operatorの段階的廃止: CrunchExecOperator、S3SyncOperatorなどを一度に1つずつ廃止し、各段階をテストと検証を含むミニプロジェクトとして運用。速度は落ちるがリスクを大幅に低減
まだコメントはありません。