inotifyでファイルの変更を監視するサイドカーを作る
コンテナでログなどでファイルが出力された際に、そのままだとコンテナが停止してそのまま消えてしまうため、外部のストレージに出力したいなどあるかと思います。
通常のログであればFluentBitなどで送るなり、そもそも標準出力をするなりすれば良いかと思いますが、今回はJavaでOutOfMemoryErrorが発生した際に出力されるバイナリ形式のヒープダンプを出力するという内容です。
サイドカーを起動してinotifyでファイルの作成を監視しファイル出力する処理を書くことで、サイドカー経由で外部ストレージに出力することが実現出来ます。
inotifyを利用したイメージの作成
一番重要なコンテナイメージの作成です。
FROM alpine:3.17.0 as builder
RUN apk update
RUN apk add inotify-tools
RUN mkdir -p /var/log/jvmlogs
COPY main.sh /bin/main.sh
RUN chmod +x /bin/main.sh
FROM builder
CMD /bin/main.sh
#!/bin/sh
LOGS_DIR=/var/log/jvmlogs
inotifywait -mq -r -e create ${LOGS_DIR} |
while read path action file; do
# ここでaws s3などで外部ストレージに出力する処理を書く。今回は標準出力のみ
echo "$(date) ${path}${file} が更新されました"
done
このシェルでは /var/log/jvmlogs
のディレクトリを監視して、ファイルが作成されたらメッセージを出力しています。
このディレクトリにはアプリケーション側でファイルを出力します。
アプリケーション側のコンテナとinotifyのコンテナでボリュームマウントすることでファイルの監視が出来ます。
アプリケーション側のイメージの作成
まずOOMEが発生するコードの作成です。
ルートのエンドポイントにアクセスすると100MBの配列を生成しリストに保持するクラスを作成します。
@RestController
@RequestMapping("/")
public class HelloController {
private List<byte[]> list = new ArrayList<>();
@GetMapping
public String index() {
list.add(new byte[1024 * 1024 * 100]); // 100MBの配列を生成する
return "hello";
}
}
Dockerfileです。
FROM openjdk:17-jdk-alpine3.14
RUN mkdir -p /var/log/jvmlogs
ARG JAR_FILE=build/libs/oome-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"]
docker-compose
最後にdocker-compose.yamlです。
version: "3.7"
services:
app:
build:
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
- "JAVA_OPTS=-Xmx512m -XX:+ExitOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/jvmlogs/"
volumes:
- type: volume
source: jvmlogs_volume
target: /var/log/jvmlogs/
inotify:
build:
context: inotify
dockerfile: Dockerfile
volumes:
- type: volume
source: jvmlogs_volume
target: /var/log/jvmlogs/
volumes:
jvmlogs_volume:
Javaの起動オプションには最大ヒープサイズに512mbを指定し、OutOfMemoryErrorが発生した際にコンテナを落として、/var/log/jvmlogs/
にヒープダンプを出力する設定を入れました。
また/var/log/jvmlogs/
をappコンテナとinotifyコンテナでボリュームマウントする設定を入れています。
これによってディレクトリの共有が出来ました。
テスト
docker-compose up -d
を実行してコンテナを起動します。
curl "http://localhost:8080/"
を5回ほど実行します。
アプリケーション側のログを確認します。
$ docker logs spring-oome-app-1
・・・途中省略・・・
2023-01-15T06:55:05.093Z INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 5 ms
java.lang.OutOfMemoryError: Java heap space
Dumping heap to /var/log/jvmlogs/java_pid1.hprof ...
Heap dump file created [440052960 bytes in 0.885 secs]
Terminating due to java.lang.OutOfMemoryError: Java heap space
想定通りOOMEが発生し/var/log/jvmlogs/java_pid1.hprof
が出力されています。
inotify側のログを確認します。
$ docker logs spring-oome-inotify-1
Sun Jan 15 06:55:09 UTC 2023 /var/log/jvmlogs/java_pid1.hprof が更新されました
無事ファイルの作成を検知してメッセージを出力することが出来ました!
ECSで上記を実現してs3に吐き出す方法はまた機会があれば作成したいと思います。
Discussion