Fluent BitがPermission deniedでログを読めない|ECS/Fargate共有VolumeのUID/GID設計

Fluent BitがPermission deniedでログを読めない|ECS/Fargate共有VolumeのUID/GID設計システム開発

はじめに

ECS/FargateでTomcatとFluent Bitを別コンテナとして起動し、同じVolumeを共有してログを収集する構成を作りました。

Tomcatが共有Volumeへログを書き込み、Fluent Bitが同じVolumeをマウントしてログを読み取る構成です。

最初に発生したのはTomcat側のPermission deniedでした。その問題を解消してTomcatが正常にログを書けるようになると、今度はFluent Bit側で次のエラーが発生しました。

[error] [input:tail:tail.0]
cannot open /var/log/tomcat/rein71/catalina.2026-01-19.log

[error] [plugins/in_tail/tail_file.c:888 errno=13]
Permission denied

GCログでも同様です。

[error] [input:tail:tail.2]
cannot open /var/log/tomcat/rein71/GC_log....txt

[error] [plugins/in_tail/tail_file.c:888 errno=13]
Permission denied

Tomcat自身はログを書けています。それなのに、同じVolumeを参照するFluent Bitはログを読めません。

今回の問題は、Tomcat単体では解決していても、ECSタスク全体では権限設計が成立していなかったことにありました。

前回は「ビルド時と実行時を分けて考える」話でした。今回は、その続きとして「コンテナ単体とタスク全体を分けて考える」話です。

今回の構成|Tomcatが書き、Fluent Bitが読む

今回の構成を単純化すると、Tomcatが共有Volumeへログを書き込み、Fluent Bitが同じVolumeをマウントしてログを読み取る構成です。

図:ECS/Fargateの共有Volume構成(Tomcat → Fluent Bit)

TomcatはWriterとして、共有Volume上の/var/log/tomcat/rein71/catalina.2026-01-19.logやGCログを書き込みます。Fluent BitはReaderとして同じVolumeを参照し、in_tailでこれらのログファイルを監視します。

つまり、両コンテナが直接データを受け渡しているのではなく、共有Volume上のログファイルがTomcatとFluent Bitの間のインターフェースになっています。

本記事で扱うのは、Fluent Bitのin_tailで共有Volume上のファイルを直接監視する構成です。アプリケーションの標準出力をawsfirelensログドライバ経由でFluent Bitへ渡すFireLensの一般的な構成とは異なります。

また、公式のaws-for-fluent-bitイメージをデフォルトのrootユーザーで実行している場合、本記事で扱うような読み取り権限の問題は通常表面化しません。今回の環境ではFluent Bitを非rootユーザーで実行していたため、共有Volume上のファイル権限が問題になりました。

発端はTomcat側のPermission deniedだった

今回、最初に問題になったのはFluent Bitではありません。

Tomcat側でGCログを作成できず、Javaの起動そのものが失敗していました。

Error opening log file
'/var/log/tomcat/rein71/GC_log....txt':
Permission denied

Initialization of output
'file=/var/log/tomcat/rein71/GC_log....txt' failed.

Error: Could not create the Java Virtual Machine.

Dockerfileでは対象ディレクトリへchownしていましたが、Fargateタスク起動時に同じパスへVolumeをマウントしていたため、イメージ作成時に設定したディレクトリではなく、実行時にマウントされたVolume側のファイルシステムが見えていました。

この仕組みについては、以下の記事で詳しく整理しています。

ECS/FargateでDockerfileのchownが効かない|非rootコンテナがPermission deniedになる理由
ECS/Fargateで非rootコンテナを実行した際、Dockerfileでchown済みのログディレクトリがPermission deniedになった実例をもとに、空のデータVolume(Empty Volume)とbind mount、VOLUMEの仕様、対策、設計レビュー、事前PoCで確認すべき点を解説します。

本記事では、その問題を解消した後に発生した問題を扱います。

Tomcatは書けるようになった。しかしFluent Bitは読めなかった

Tomcat側では、Volumeがマウントされた後に対象ディレクトリのowner/groupとpermissionを変更することで、ログを書き込める状態にしました。

これによってTomcatは正常に起動します。

ところが、次にFluent Bitでerrno=13が発生しました。

Linuxのerrno=13はEACCES、つまりアクセス拒否です。

ここで重要なのは、

Tomcatがログを書けることと、Fluent Bitがそのログを読めることは別の権限要件

だという点です。

例えばTomcatが所有者であるログファイルなら、owner権限によってTomcat自身は問題なく書き込めます。

一方、Fluent Bitが別UIDで実行されている場合、そのファイルのgroupまたはotherに読み取り権限がなければアクセスできません。

さらにファイル自身のrだけでは足りません。

Fluent Bitが対象ファイルへ到達するためには、ルートから対象ファイルまで、パス上に存在するすべてのディレクトリにx権限が必要です。

例えば、

/
└─ var
   └─ log
      └─ tomcat
         └─ rein71
            └─ catalina.2026-01-19.log

であれば、/var、/var/log、/var/log/tomcat、/var/log/tomcat/rein71を順に辿れる必要があります。

さらに、*.logのようなワイルドカードでファイルを探索する場合は、ディレクトリエントリを列挙するためのr権限も考慮する必要があります。

つまり確認すべきなのは、

ファイルのread権限
        +
パス上の全ディレクトリのtraverse権限
        +
Fluent Bit自身のUID/GID

です。

なお、Volumeをマウントしているパスより上位のディレクトリは、各コンテナイメージ側のファイルシステムです。同じVolumeを共有していても、その上位階層まで同じ状態になるわけではありません。

chmod 777で原因を権限問題に絞り込んだ

切り分けとして、対象ログのpermissionを一時的に緩める確認を行いました。

777にするとFluent Bitからログを監視できるようになったため、

  • Fluent Bitのtail設定

  • 対象ログのパス

  • Volumeの共有そのもの

ではなく、ファイルシステム上のアクセス権限が原因であることを確認できました。

もちろん、

chmod -R 777 /var/log/tomcat/rein71

を恒久対策にはしません。

777は、原因を切り分けるための確認方法です。

さらにchmod -R 775のようにディレクトリと通常ファイルへ同じmodeを再帰的に設定する方法も避けます。通常のログファイルに実行ビットを付与する必要はないためです。

恒久対策では、ディレクトリとファイルの権限を分けて考えます。

直接原因と設計上の原因は分けて考える

今回の問題には、二つの原因があります。

技術的な直接原因

Tomcatが生成したログファイルのowner/groupとmodeが、Fluent Bit側の読み取り条件を満たしていませんでした。

Tomcatから見れば書き込めるファイルでも、別UID/GIDで動作するFluent Bitから見れば読み取れない状態です。

設計上の原因

Tomcatがログを書けることを権限設計のゴールにしてしまい、共有Volumeを利用するFluent Bitまで含めたアクセス要件になっていませんでした。

本来の要件は、

Tomcatが書ける

ではなく、

Tomcatがログを書く
        ↓
Fluent Bitがそのログを継続して読む

です。

ここをコンテナ単体ではなく、ECSタスク全体の要件として扱う必要があります。

共有VolumeではUID/GIDをコンテナ間インターフェースとして考える

一つの解決方法として、TomcatとFluent Bitが共有するGIDを決める設計が考えられます。

例えば、

Tomcat
UID = 1071
GID = 3000

Fluent Bit
UID = xxxx
GID = 3000

とします。

共有Volumeのログディレクトリを、

owner = 1071
group = 3000

とすれば、

  • Tomcatはownerとしてログを生成する

  • Fluent Bitはgroupとしてログを読む

という役割分担ができます。

重要なのは、3000という数値そのものではありません。

共有Volumeを境界として、WriterとReader双方から成立するUID/GIDを決めることです。

ECSのタスク定義では、Linuxコンテナの実行ユーザーをuserで指定できます。

Fluent Bitを共有GIDで実行する場合、例えば設計上は、

"user": "2000:3000"

のようにUIDとプライマリGIDを指定する方法があります。

一方、ECSにはKubernetesのように、プロセスへ補助グループを追加する supplementalGroups 相当の仕組みや、Volumeのgroup所有権を自動調整する fsGroup 相当の仕組みはありません。

必要に応じて、

  • タスク定義のuser

  • Fluent Bitのカスタムイメージ

  • イメージ内のuser/group定義

などを使って実行UID/GIDを設計する必要があります。

実環境では、設定値を見るだけではなく、

id

で実際にプロセスがどのUID/GID/groupsで動作しているか確認することが重要です。

また、タスク定義でuserを指定すると、コンテナのentrypoint自体もそのユーザーで実行されます。イメージのentrypointがroot権限を前提としたディレクトリ操作などを行っている場合は、別の起動エラーを起こす可能性があるため、その点も確認します。

setgidで新規ログのgroupを引き継ぐ

共有Volumeのディレクトリを最初に、

owner = 1071
group = 3000

としても、それだけでは十分ではありません。

Tomcatがその後に新しいログファイルを作成すると、新規ファイルのgroupが期待した値にならない可能性があります。

そこで利用できるのが、ディレクトリのsetgidビットです。

例えば、

chown 1071:3000 /var/log/tomcat/rein71
chmod 2750 /var/log/tomcat/rein71

とします。

先頭の2がsetgidです。

setgidを設定したディレクトリ配下で新しくファイルを作成すると、ファイルのgroupは親ディレクトリのgroupを引き継ぎます。

概念的には、

/var/log/tomcat/rein71
owner = 1071
group = 3000
setgid
        │
        │ Tomcatが新規作成
        ▼
catalina.2026-01-20.log
group = 3000

となります。

これによって、日付が変わって新しいログファイルが作成された場合でも、共有GIDを維持しやすくなります。

umaskで新規ログのmodeを制御する

groupが正しくても、ファイルにgroupの読み取り権限がなければFluent Bitは読めません。

そこで次に考えるのがumaskです。

Linuxで一般的なファイルが新規作成される場合、最終的なmodeにはプロセスのumaskが影響します。

例えば、

umask 0027

であれば、通常ファイルは基本的に、

640
rw-r-----

となります。

これなら、

owner = Tomcat
group = Fluent Bitと共有するgroup

という構成において、

Tomcat      → read / write
Fluent Bit  → read
others      → accessなし

というアクセス制御ができます。

考え方としては、

setgid
  ↓
誰のgroupになるか

umask
  ↓
そのgroupに何を許可するか

です。

共有Volumeでは、この二つをセットで考えると整理しやすくなります。

なお、アプリケーション自身が明示的にpermissionを変更する場合など、umaskだけでは決まらないケースもあります。実際に生成されたファイルのowner/group/modeは必ず確認します。

Volumeの初期権限を直すだけでは足りない

既存記事では、権限設定用コンテナを先行起動してVolumeを初期化する方式を扱いました。

ECSにはKubernetesのような専用のinit container機能はありませんが、

権限設定用コンテナ
        ↓ SUCCESS
Tomcat / Fluent Bit

という依存関係を作ることができます。

SUCCESSやCOMPLETEを依存条件として使う場合、依存先コンテナをessentialコンテナにはできません。したがって、権限設定用コンテナはessential: falseとして定義する必要があります。

この方式そのものについては既存記事で詳しく説明しているため、本記事では繰り返しません。

今回重要なのは、そのです。

権限設定用コンテナが変更できるのは、基本的にタスク起動時点のVolumeの状態です。

その後にTomcatが作成するログファイルについては、

  • TomcatのUID

  • TomcatのGID

  • 親ディレクトリのsetgid

  • プロセスのumask

  • アプリケーション自身のファイル生成方法

などによってowner/group/modeが決まります。

つまり、

Volume初期化
    ↓
正しい権限になった
    ↓
完了

ではありません。

Volume初期化
    ↓
Tomcat起動
    ↓
ログ新規生成
    ↓
Fluent Bitから読み取り
    ↓
翌日のログ新規生成
    ↓
引き続き読み取り

まで成立して、初めてタスク全体として権限設計が成立します。

恒久対策として考えられる構成

今回確認した事象とLinuxの権限の仕組みから整理すると、恒久対策としては次の構成が考えられます。

1. 権限設定用コンテナでVolumeを初期化する

例えば、共有Volumeのログディレクトリを、

chown 1071:3000 /mnt
chmod 2750 /mnt

とします。

2750はあくまで一例です。

必要なアクセス要件に応じてmodeを決めます。

複数階層を初期化する場合も、ディレクトリと通常ファイルを分けて設定します。

例えば、

find /mnt -type d -exec chmod 2750 {} +
find /mnt -type f -exec chmod 640 {} +

のように扱えば、ログファイルへ不要な実行ビットを付与せずに済みます。

2. Tomcatを非rootで実行する

Volumeの初期化を別コンテナへ分離すれば、Tomcat自身を権限変更のためにrootで実行する必要はありません。

Tomcatはアプリケーション実行用のUID/GIDで起動します。

3. Tomcat側でumaskを設定する

例えば、

umask 0027

としてからTomcatを起動します。

これにより、Tomcatが新しく作成するログファイルについて、共有groupから読み取り可能なmodeにすることを狙います。

4. Fluent Bitを共有GIDから読み取れる状態にする

Fluent Bit側も、共有Volume上のログを読み取れるUID/GIDで実行します。

タスク定義のuserを利用する場合は、例えば、

"user": "2000:3000"

のような指定が候補になります。

実際に使用するUID/GIDは、利用しているFluent Bitイメージやその実行方式に合わせて決めます。

5. Fluent Bit側は可能ならread-onlyでマウントする

Fluent Bitはログを読むだけであれば、共有ログVolumeをread-onlyでマウントする方が役割を明確にできます。

{
  "sourceVolume": "tomcat-logs",
  "containerPath": "/var/log/tomcat/rein71",
  "readOnly": true
}

Fluent BitのDBファイルなど書き込みが必要なファイルは、共有ログVolumeとは別の書き込み可能な場所へ配置する必要があります。

6. 新しいログが生成された後まで確認する

起動直後のファイルだけではなく、Tomcatが新しくログを作成した後に、

ls -ld /var/log/tomcat/rein71
ls -l /var/log/tomcat/rein71
id

などで、

  • owner

  • group

  • mode

  • TomcatのUID/GID

  • Fluent BitのUID/GID

を確認します。

特に日付が変わってcatalina.YYYY-MM-DD.logが新しく生成された後や、GCログが新しいファイルへ切り替わった後にもFluent Bitが継続して読み取れることを確認します。

この構成は各環境で検証が必要

ここまでの構成は、今回確認した事象とLinuxのファイル権限の仕組みから整理した恒久対策案です。

実際に必要となる設定は、

  • 使用するコンテナイメージ

  • Tomcat/JDKのバージョン

  • ログの生成方式

  • Fluent Bitの実行UID/GID

  • Fluent Bitのin_tail設定

  • Volumeのマウントポイント

などによって変わります。

したがって、本記事のUID/GIDやmodeをそのまま適用するのではなく、各環境で実際に生成されたファイルのowner/group/modeと、各コンテナの実行UID/GIDを確認したうえで検証してください。

ECS/Fargateで共有Volumeを使うときの確認項目

共有Volumeの権限問題を確認する際は、Writer側だけを見るのではなく、実行UID/GID、Volumeとファイルの権限、その後のファイル生成まで順に確認します。今回の事象を踏まえて、確認項目を以下に整理しました。

項番確認項目確認内容
1WriterのUID/GIDログを生成するコンテナを誰で実行するか
2ReaderのUID/GIDログを読むコンテナを誰で実行するか
3実行時のid設定値ではなく、実際のUID/GID/groupsを確認する
4Volumeのowner/groupマウント後の所有者・グループを確認する
5ディレクトリ権限Readerがパス上の各ディレクトリを辿り、必要に応じて列挙できるか
6ファイル権限Readerが実際のログファイルを読み取れるか
7setgid新規ファイルへ共有groupを引き継げるか
8umask新規ファイルに必要なgroup権限が付くか
9新規ログ生成日付切替やGCログ切替後も権限が維持されるか
10起動順序Volume初期化完了後に各コンテナが起動するか
11root権限権限初期化など、rootが必要な処理だけに限定できているか
12read-onlyReader側から不要な書き込みを防止できるか
13タスク再作成新しいFargateタスクでも同じ状態を再現できるか

特に、起動時のVolumeのowner/groupが正しくても、その後に生成されるログのgroupとmodeがReaderの要件を満たすとは限りません。setgidとumask、新規ログ生成後の確認までを一連のテストとして扱うことが重要です。

まとめ:共有Volumeはコンテナ間インターフェースとして設計する

今回、Tomcatからログを書けるようになった時点では、権限問題を解決できたと思っていました。実際には、同じVolumeを利用するFluent Bitからログを読めず、タスク全体としてはまだ成立していませんでした。

共有Volumeでは、マウント直後のowner/groupとpermissionを整えるだけでは不十分です。その後に生成されるファイルのgroupをどう引き継ぐか(setgid)、どのmodeで作成するか(umask)、Reader側のUID/GIDから読み取れるか、そしてログが新しく作り直された後も読み続けられるかまで確認する必要があります。

TomcatとFluent Bitのように複数コンテナでVolumeを共有する場合、Volumeは単なる保存領域ではなく、コンテナ間でデータを受け渡すインターフェースになります。Writerが書けることではなく、Readerが継続して読めることが要件です。

コンテナ単体では正しい設定でも、ECSタスク全体では正しいとは限りません。


関連記事

本記事の前段となった事象です。

DockerfileでchownしたディレクトリへECS/FargateのEmpty Volumeをマウントすると、なぜ設定したはずの権限が見えなくなるのか。ビルド時のファイルシステムと実行時のVolumeを分けて整理しています。

ECS/FargateでDockerfileのchownが効かない|非rootコンテナがPermission deniedになる理由
ECS/Fargateで非rootコンテナを実行した際、Dockerfileでchown済みのログディレクトリがPermission deniedになった実例をもとに、空のデータVolume(Empty Volume)とbind mount、VOLUMEの仕様、対策、設計レビュー、事前PoCで確認すべき点を解説します。

コメント

タイトルとURLをコピーしました