Lake Formation本番導入で3時間ハマった話、タイムゾーン設定の落とし穴

AWS Lake Formationを本番環境に6ヶ月使ってわかった、IAM・タグベース権限の実装パターンと本当の落とし穴。ドキュメントに書いてない「あるある」をリアルに共有します。

Lake Formationで本番データアクセス制御、タイムゾーン設定で3時間ハマった話

うちのチームがAWS Lake Formationを本番環境に導入したのが去年の秋。それから約6ヶ月、毎週のようにアクセス権限まわりで火消しをしてきた。その過程で気づいたことを、正直に書き残しておきたい。

というのも、Lake Formationのドキュメントって「できます」という説明は充実してるんだけど、「実装してみたらこういう落とし穴がある」という話がなくて。特に複数の部門で異なるデータアクセス要件がある環境では、設計段階でつまずきやすいんだ。

本番導入前にやった設計が半分外れた理由

最初、うちのチームは「Lake Formationを使えば、IAMの複雑な権限管理が簡潔になるだろう」と考えていた。確かにそれは本当なんだけど、事前に見えていなかった制約がいくつかあった。

まず、Lake Formationのアクセス制御は「IAMとの二重管理」になることが多い。つまり、IAMでS3へのアクセスを許可していないと、Lake Formationの権限があっても結局データにアクセスできないということ。これは当たり前っちゃ当たり前なんだけど、実装段階では「Lake Formationの設定が完了したから大丈夫」って思い込みがちなんだ。

うちの場合、マーケティング部門がAthenaからLake Formationで管理されているテーブルにクエリを投げようとしたら、IAMロールにS3のGetObjectがなくて権限エラーになってしまった。Lake Formation側には「テーブルAへのアクセスOK」という設定があるのに、だ。

# 実際に起きたエラー
User: arn:aws:iam::123456789012:role/MarketingAnalyst 
is not authorized to perform: s3:GetObject on resource: 
arn:aws:s3:::my-data-lake/warehouse/marketing_data/...

これを解決するには、IAMポリシーと Lake Formationの権限設定を両方確認する必要がある。ドキュメントには「最小権限の原則に基づいて、S3へのアクセスは制限してください」と書かれているんだけど、実装するときは両方のレイヤーをチューニングしないといけない。

タグベース権限管理で失敗した3つの理由

最初の計画では、Lake Formationのタグベースアクセス制御(LF-TBAC)を活用する予定だった。データのセンシティビティレベル(Public/Internal/Confidential)をタグで分類して、それに応じて権限を制御する。きれいな設計だと思っていたんだ。

でも、実装してみたらいくつも問題が出た。

問題1:タグの伝播が予想と異なる

データベースレベルでタグを設定しても、その直下のテーブルに自動で伝播されない。テーブル単位でタグを設定する必要があるんだ。これは、Glueカタログの数百のテーブルがあるうちのチームにとって、かなりの手作業になってしまった。

# 最初のアプローチ(失敗)
import boto3

client = boto3.client('lakeformation')

# データベースにタグを設定
client.tag_resource(
    ResourceArn='arn:aws:s3:::my-data-lake/warehouse',
    TagsToAdd={
        'Sensitivity': 'Confidential',
        'Owner': 'Finance'
    }
)

# ここで「じゃあテーブルにも伝播されるはず」と考えたが、されない
# 各テーブルに個別にタグ設定が必要

後から対応したのは、Glue Catalogの自動タグ付け機能と Lake Formationを組み合わせるアプローチ。Glueのクローラーが新しいテーブルを検出したときに、テーブル名やパスに応じた自動タグ付けをする仕組みだ。それでもすべてが完全自動化されるわけではないけど、手作業が大幅に減った。

問題2:IAMロールとIAMユーザーの権限モデルが混在しやすい

これはLake Formation固有の話じゃなくてAWS全般的なことなんだけど、本番環境ではIAMロールを使うのが基本。だけど開発環境では、エンジニアが直接IAMユーザーでアクセスすることもあるじゃないか。

Lake Formationの権限設定は、ロールとユーザーで異なる挙動をすることがあるんだ。特に、IAMロールがSTSで assume されている場合は、Lake Formation側で「どのIAM principalが実際にアクセスしているのか」を正確に判定する必要がある。

うちで困ったのは、開発環境ではマーケティングチームのエンジニアがIAMユーザーで直接アクセスできるけど、本番環境ではIAMロール経由でしかアクセスできないという設定にしたかったこと。Lake Formationは、この「環境による権限の違い」を1つのポリシーで管理するのが難しいんだ。

結局、環境ごとにLake Formationのポリシー設定を分けることにした。本番環境には本番IAMロール専用の権限セットを、開発環境には開発ロール+直接ユーザーアクセスの設定を作った。

問題3:タグの値に日本語が入ると地獄

これはマジで3時間ハマった。

# 日本語タグを設定してみた
client.tag_resource(
    ResourceArn='arn:aws:glue:ap-northeast-1:123456789012:table/...',
    TagsToAdd={
        'Department': '営業部',  # ← これでハマる
        'Classification': '売上データ'
    }
)

タグそのものは設定されるんだけど、Lake Formationのフィルター検索で日本語の値を含むタグを使うと、マッチしないことがある。特にタグベースアクセス制御の評価時に、なぜか推移的にヒットしないデータが出てくるんだ。

AWSサポートに問い合わせたら、「Lake Formationのタグ評価エンジンは内部的にUTF-8処理で若干の癖がある」という回答をもらった。公式ドキュメントには載っていない落とし穴だ。

うちの対応は、タグの値を英語の識別子に統一することにした。

# 修正版
client.tag_resource(
    ResourceArn='arn:aws:glue:ap-northeast-1:123456789012:table/...',
    TagsToAdd={
        'Department': 'sales',  # 英語に統一
        'Classification': 'revenue_data',
        'Department_JA': '営業部'  # 参考用に日本語タグは別に
    }
)

テーブルレベルのアクセス制御は粒度が荒い

Lake Formationの基本単位はテーブルだ。テーブル内の特定のカラムだけにアクセスを限定することもできるけど、そこまで細かく設定すると運用が大変になる。

うちの場合、財務部門は売上テーブルのすべての列を見る必要はなく、商品ID・売上金額・日付だけ見たい。でも、別の分析用途では詳細な顧客情報も必要。こういう「同じテーブルの異なるサブセット」を複数の部門で共有するシーンは、Lake Formationだけでは結構複雑になるんだ。

解決策としては、ビューを使うアプローチを取った。Athenaで実装されたビューの上にLake Formation権限を設定して、各部門は自分たちが必要な列だけを含むビューにアクセスする。ビュー自体はread-onlyに設定する。

-- Athena上のビュー定義
CREATE OR REPLACE VIEW sales_summary_for_finance AS
SELECT 
    product_id,
    revenue,
    transaction_date
FROM sales_raw_data
WHERE transaction_date >= DATE_FORMAT(CURRENT_DATE - INTERVAL '365' DAY, '%Y-%m-%d');

このビューに対してLake Formationで「Financeロール = 読み取り権限」という設定をすれば、詳細なカラム管理なしに目的達成できる。ただし、ビューの定義自体をメンテナンスする責任はLake Formation側にはなく、データ所有者(この場合はデータ分析チーム)にある。

実装する際の現実的なアーキテクチャ

ここまでの失敗から、うちのチームが落ち着いた設計はこんな感じだ。

graph TB
    subgraph Data["データレイヤー"]
        S3Raw["S3: Raw Data Lake"]
        S3Curated["S3: Curated/Processed"]
        S3Derived["S3: Derived Analytics"]
    end
    
    subgraph Catalog["AWS Glue Catalog"]
        DBRaw["Database: raw"]
        DBCurated["Database: curated"]
        DBDerived["Database: derived"]
        
        TableRaw["Tables (Raw)"]
        TableCurated["Tables (Curated)"]
        ViewFinance["View: sales_finance"]
        ViewSales["View: sales_operations"]
    end
    
    subgraph LF["Lake Formation Control"]
        IAMAuth["IAM Principal Auth"]
        TagPolicy["Tag-Based Access"]
        TablePerms["Table/Column Perms"]
    end
    
    subgraph Access["アクセスレイヤー"]
        AthenaFin["Athena: Finance"]
        AthenaOps["Athena: Sales Ops"]
        QuickSight["QuickSight: Dashboards"]
        Lambda["Lambda: ETL Jobs"]
    end
    
    S3Raw --> DBRaw
    S3Curated --> DBCurated
    S3Derived --> DBDerived
    
    DBRaw --> TableRaw
    DBCurated --> TableCurated
    DBCurated --> ViewFinance
    DBCurated --> ViewSales
    
    TableRaw --> IAMAuth
    TableCurated --> IAMAuth
    ViewFinance --> TagPolicy
    ViewSales --> TagPolicy
    
    IAMAuth --> AthenaFin
    TagPolicy --> AthenaFin
    TagPolicy --> AthenaOps
    IAMAuth --> Lambda
    
    AthenaFin --> QuickSight
    AthenaOps --> QuickSight
    
    style LF fill:#fff4e6
    style Data fill:#e8f5e9
    style Catalog fill:#e3f2fd
    style Access fill:#f3e5f5

この設計のポイントはこんなふうになってる:

  1. レイヤー分離:Raw → Curated → Derivedという3層構造で、各層ごとに異なるアクセスポリシーを適用する
  2. ビューの活用:テーブルレベルではなくビューレベルで細かい権限制御を行う
  3. IAMとLake Formationの責任分離:IAMで「誰がアクセス可能か」を決め、Lake Formationで「何にアクセス可能か」を制御する
  4. タグは識別子として:日本語を避け、英語の簡潔な識別子を使用する

Athenaとの連携で気づいた、クエリ実行時の権限確認タイミング

もう1つ、本番運用で気づいたことがある。

Athenaでクエリを実行するときの権限確認は、Lake Formation側で「クエリ実行前」に行われると思っていたんだけど、実際には「クエリが実行されて、データを読み込もうとするとき」に確認される。

つまり、大きなテーブルでのフルスキャンクエリは、権限がなくても S3へのアクセスが始まるまでエラーにならないということ。これは、コスト的に厄介だ。

-- このクエリが実行されると
SELECT COUNT(*) FROM very_large_table;

-- 権限なくてもS3スキャンが始まって、途中でエラーになる
-- つまり、余計なスキャンコストが発生する

対策としては、Athenaのクエリ実行前に「このユーザーはこのテーブルに権限があるのか」を事前に確認するカスタムロジックを実装することにした。Lambda + Lake Formation APIを使って、クエリ投入前にバリデーションする。

import boto3
import json
from botocore.exceptions import ClientError

lf_client = boto3.client('lakeformation')

def check_table_access(principal_arn, database, table):
    """指定されたPrincipalがテーブルにアクセス可能か確認"""
    try:
        response = lf_client.describe_resource(
            ResourceArn=f'arn:aws:glue:ap-northeast-1:123456789012:table/{database}/{table}'
        )
        # Lake Formationで管理されているなら、詳細権限を確認
        return check_permission_via_lf_tags(principal_arn, database, table)
    except ClientError as e:
        if e.response['Error']['Code'] == 'EntityNotFoundException':
            # Lake Formationで管理されていない(IAM権限で制御)
            return check_iam_permission(principal_arn, database, table)
        raise

def check_permission_via_lf_tags(principal_arn, database, table):
    """Lake Formationのタグベース権限を確認"""
    # 実装の複雑さのため、簡略化
    # 実際には、Principalのロールに付与されたタグと
    # テーブルのタグをマッチングして確認する
    pass

def check_iam_permission(principal_arn, database, table):
    """IAMポリシーベースの権限を確認"""
    # IAMポリシーシミュレーターを使用して確認
    iam_client = boto3.client('iam')
    # ... シミュレーション実装 ...
    pass

このアプローチは完璧ではないけど、予期しないS3スキャンコストを減らすのに役立っている。

6ヶ月運用してわかった、Lake Formation導入のベストプラクティス

正直、Lake Formationは「データ民主化」の理想を掲げた素晴らしいサービスだ。でも、本番環境で複数部門のアクセス要件を満たそうとすると、かなり細かいチューニングが必要になる。

うちのチームが実践してきた、実際に効果があった対策をまとめてみた:

項目ベストプラクティス理由
タグ命名規則英語の識別子を統一日本語タグはUTF-8処理で予期しない挙動をすることがある
IAM権限の扱いLake Formationと並行してチェックIAMとLake Formationは独立した層として機能
粒度制御テーブルではなくビューで実装カラムレベル制御は複雑さのわりにメリットが限定的
タグ伝播自動伝播を前提にしないテーブル単位での個別設定が必須
権限確認クエリ実行前の事前検証Lake Formationは実行時に権限確認するため、無駄なコストが発生する可能性
アクセスログCloudWatch Logsを有効化CloudTrailより詳細で、トラブルシューティングに有効

実装で気をつけること

タグ命名規則を最初に決めることが地味に重要だ。日本語は避け、英語の識別子を使う。タグの伝播を前提にしない(各テーブル単位で設定する)。このシンプルなルールだけで、後の運用がかなり楽になる。

IAM権限とLake Formation権限を並行して考える必要がある。Lake Formationだけで完結しない。IAMポリシーも同時に設定する必要があるんだ。チェックリストを作って、両方の層を確認する習慣が重要だ。

テーブルレベルではなくビュー + データベースレベルで設計するのが現実的だ。カラムごとの細かい制御が必要ならば、テーブルを細分化するか、ビューを使う。Lake Formationのカラムレベル権限管理は、複雑さに対して得られるメリットが小さい。

アクセスログを有効化することは、運用の透明性を大きく改善する。Lake Formationには、アクセス制御に関する詳細なログをCloudWatchに出力する機能がある。これを使えば、「誰が何をアクセスしようとしたのか」が可視化される。

# CloudWatch LogsにLake Formation監査ログを出力する設定
lf_client = boto3.client('lakeformation')

response = lf_client.put_data_lake_settings(
    DataLakeSettings={
        'DataLakeAdmins': [
            {'DataLakePrincipalIdentifier': 'arn:aws:iam::123456789012:role/LakeFormationServiceRole'}
        ],
        'CreateDatabaseDefaultPermissions': [
            {
                'Principal': {'DataLakePrincipalIdentifier': 'arn:aws:iam::123456789012:role/AnalystRole'},
                'Permissions': ['ALL']
            }
        ],
        'CreateTableDefaultPermissions': [
            {
                'Principal': {'DataLakePrincipalIdentifier': 'arn:aws:iam::123456789012:role/AnalystRole'},
                'Permissions': ['SELECT']
            }
        ],
        'AllowExternalDataFiltering': True,  # Cross-account accessを許可
        'AuthorizedSessionTagValueList': ['arn:aws:iam::123456789012:role/DataScienceRole']
    }
)

次のステップ:マルチアカウント環境への拡張

今うちが検討しているのは、Lake Formationのマルチアカウント対応だ。複数のAWSアカウントでLake Formationを集約管理する仕組みのことなんだけど、これはまた別の複雑さがある。そのあたりはまた次の記事で書きたい。

いま感じているのは、Lake Formationは「単一アカウント + シンプルなアクセス要件」にはぴったりだということ。でも、成長するにつれてアーキテクチャの工夫が必要になってくるんだ。最初から「複数部門 + マルチリージョン + 成長を見据えた設計」を目指すなら、他のデータガバナンスツールとの組み合わせも検討する価値がある。

まとめ

Lake Formationを本番環境で使う際には、以下のポイントを頭に入れておくといいだろう:

  • Lake Formationはアクセス制御の一部にすぎない:IAMとの二重管理が前提。IAM権限を確認してからLake Formationの設定に進まないと、思わぬトラブルに遭遇する
  • タグは英語の識別子で統一する:日本語タグは権限評価で予期しない挙動をすることがあり、3時間単位でハマることになる
  • テーブルより上位(データベース・ビュー)で粒度を合わせる:カラムレベル制御は複雑さの割にメリットが限定的
  • Athenaとの連携では事前権限確認が重要:クエリ実行時まで権限チェックが遅延すると、予期しないS3スキャンコストが発生する
  • ログ出力を有効化する:Lake Formationの監査ログは運用の透明性を大きく改善する

本番環境でのデータアクセス制御は、やはり地味で地道な作業が必要だ。でも、この辺りを先に整備しておくと、後々のアクセス権限トラブルが劇的に減るんだ。

U

Untanbaby

ソフトウェアエンジニア|AWS / クラウドアーキテクチャ / DevOps

10年以上のIT実務経験をもとに、現場で使える技術情報を発信しています。 記事の誤りや改善点があればお問い合わせからお気軽にご連絡ください。

関連記事