はじめに
Podman 4.6.1や4.9.4で、-pオプションでポートバインドしていると、cronでのコンテナ停止時にnftablesにゴミデータが残って困ったことがあったので、この記事ではその原因と対策をまとめます。
nftables とは
nftables は Linux カーネルのパケットフィルタリングフレームワークです。従来の iptables の後継として RHEL 8 以降でデフォルトになっています。
ファイアウォールのルール管理、NAT(ネットワークアドレス変換)、パケットの転送制御などを担っており、nft コマンドで操作します。
基本的な概念
nftables は階層構造でルールを管理します:
テーブル (table)
└── チェーン (chain)
└── ルール (rule)
| 概念 | 説明 | 例 |
|---|---|---|
| テーブル | ルールのグループ。プロトコルファミリーごとに分かれる |
ip podman(IPv4のpodman用) |
| チェーン | テーブル内のルールグループ。パケットの処理フローに対応 |
PODMAN、FORWARD など |
| ルール | 実際の処理内容。条件にマッチしたパケットをどう扱うか | tcp dport 5000 dnat to 10.88.0.5:5000 |
DNAT とは
DNAT(Destination NAT) は、パケットの宛先 IP アドレス・ポートを書き換えるルールです。
例えば、Podman が -p 5000:5000 でコンテナを起動すると、こういうルールが自動で作られます。
# ホスト:5000 へのパケットを コンテナIP:5000 に転送する
tcp dport 5000 dnat to XX.XX.XX.XX:5000
これにより、ホストの 5000 番ポートへのアクセスがコンテナに届くようになります。
コンテナ停止時は自動的にルールが削除されます。
--net host の場合はホストのネットワークをそのまま使うため、DNAT ルールは作られません。
発生した事象
本記事ではregistryコンテナを例にしています。
-
podman run -p 5000:5000 registry:2でコンテナは正常起動しcurlでのアクセスに問題なし - 手動での
podman restart実行では問題ない -
cron 経由で再起動(restart or stop/start)だとおかしくなる
→停止はできる、起動は出来ない、手動起動はできるが5000番は正常に繋がらなくなる - cron経由での再起動を実施したあとは、
curl http://127.0.0.1:5000/v2/→No route to host - コンテナログには
listening on :5000が出ている - 5000 ポートはLISTENされている
--net hostにすると問題が起きない
原因
nftables にゴミが残る
Podman は -p でポートバインドしたコンテナの起動時に、nftables に DNAT ルールを自動追加します。
tcp dport 5000 dnat to <コンテナIP>:5000
通常はコンテナを停止・削除すると自動でルールも消えます。しかし Podman の特定バージョンには、cron などの非インタラクティブセッションから podman stop/start を実行した時に、stop 時の nftables クリーンアップが正常に動作しないバグがあるようです。
新しいコンテナには別の IP が割り当てられているため、古い IP への DNAT が邪魔をして通信できません。
確認方法
nftables にゴミルールが残っているか確認
nft -a list ruleset | grep 5000
以下のように、コンテナが存在しないのにルールが残っている場合が問題です:
tcp dport 5000 counter packets 159970 bytes 9598200 dnat to XX.XX.XX.XX:5000 # handle 23
nftables の出力の読み方
nft -a list ruleset の出力はこのような階層構造になっています:
table ip nat { ← テーブル: family=ip, name=nat
chain hogehoge { ← チェーン名
...
tcp dport 5000 counter packets 159970 bytes 9598200 dnat to XX.XX.XX.XX:5000 # handle 23
... ↑ handle番号
}
}
5000 を含む行の直上にある chain XXX { がチェーン名、行末の # handle 番号 がルール特定に必要な番号です。
対処方法
応急処置:ゴミルールを手動削除する
nft delete rule <family> <table> <chain> handle <番号>
上記の例であれば:
nft delete rule ip nat hogehoge handle 23
-
ip→ family(IPv4) -
nat→ テーブル名 -
hogehoge→ チェーン名(環境によって異なる場合あり) -
23→ handle番号
削除後、再度 5000:5000 でコンテナを起動すると正常に繋がるようになります。
根本対策①:--net host に変える
最もシンプルで確実な解決策です。
-p でポートバインドする代わりに --net host を使うと、DNAT ルール自体が作られないため、このバグを根本から回避できます。
# 変更前
podman run -d -p 5000:5000 --name registry registry:2
# 変更後
podman run -d --net host --name registry registry:2
根本対策②:Podman をアップデートする
このバグは新しいバージョンの Podman では修正されているようです。
※Podman 5.2.2では問題が発生しないことを確認しました