2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

cronで podman コンテナを落とすと nftables の DNAT ルールが残ってしまった件

2
Posted at

はじめに

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用)
チェーン テーブル内のルールグループ。パケットの処理フローに対応 PODMANFORWARD など
ルール 実際の処理内容。条件にマッチしたパケットをどう扱うか 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では問題が発生しないことを確認しました

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?