2016年9月5日月曜日

OpenStack : お手軽にポートフォワーディングして、IPアドレスを有効活用する

訳あってこの頃OpenStackをちょくちょく触ることがあります。始めたばかりのときはIcehouseでしたので、結構不安定であり苦労しましたが、最近のLibertyとかMitakaは基本的な機能に目立った不具合もなく、なんとか使えるような感触になってきました。

それでも、実運用で現場に当ててみると、そのままではうまくいかない部分が少々あるため、Tips的に記事を出していければええのかなと思います。(ツッコミが怖いですが...)

とりあえず、ポートフォワーディングしたい

OpenStackのNeutronは通常、Floating IP等を用いて、インスタンスと外部ネットワークをグローバルIP等を用いてダイレクトに通信する仕組みを持っています。 実際の運用においてFloating IPをふんだんに使える環境であればこの仕組みは便利に機能するのですが、実際にはこうにはいかないケースが多いものと思います。中には、利用可能なグローバルIPはひとつだけ、という家庭のネット環境に近い実環境もあるかもしれません。

そんな場合、Neutron で router を作成してそこから通信する形になるのですが、そのままではインスタンスにWebサーバを立てたとしても、外部からそれにアクセスすることができません。 この点においては、現状のOpenStackでは外部->内部へのポートフォワーディングAPI等の仕組みがありません(後述)。

幸い、RDO等でインストールした普通(?)のNeutronでは、router でNAT的な処理を行う部分は、 Linux kernel の機能である iptables で行われていますので、そこにDNATルールを追加すればこの欠点を補うことができます。つまり、手動でポートフォワーディングをしてあげれば良い感じです。

この部分は通常の iptables では指定できず、namespace で区切られた部分のみとなりますので、例えばルールを追加するときは、例えば以下のようにコマンドを入れる必要があります。

※ 以下の例は、Neutronにrouterがひとつだけある場合を想定しています。2つ以上ある場合は、$(/sbin/ip netns|/usr/bin/grep qrouter) の部分を該当の qrouter 名に置き換えてください。

From : 外部ネットワーク (192.168.1.100:5001) -> 内部のインスタンス (192.168.50.11:22) とした

# ip netns exec $(/sbin/ip netns|/usr/bin/grep qrouter) iptables  -t nat -A PREROUTING -p tcp -m tcp --dport 5001 -d 192.168.1.100/32 -j DNAT --to 192.168.50.11:22

これだけで外部からの通信がポートフォワーディングされますが、この設定は reboot 等で消えてしまうため、保存する仕組みを作る必要があります。
手動では以下のコマンドで保存することができます。

# ip netns exec $(/sbin/ip netns|/usr/bin/grep qrouter) iptables-save > /etc/iptables-namespace

同じように復元するには以下のコマンドで行います。

# /sbin/ip netns exec $(/sbin/ip netns|/usr/bin/grep qrouter) /sbin/iptables-restore < /etc/iptables-namespace

ポートフォワーディングを起動時に自動適用したい

次に、このスクリプトをサーバ自体の起動時に自動的に反映されるようにします。なぜこれが必要なのかといいますと、何らかの原因で再起動が必要になった際に、設定を失ったり、手動での反映を失念するのを防ぎたいからです。

しかし、ただ systemd に仕掛ければよい訳ではありません。qrouter-* ネームスペースが生成されるのは起動シーケンスが見た目終了してしばらく後と大変遅く、仕掛けづらい状況です。いろいろ試行錯誤しましたが、Neutronの起動が完了したタイミングでもうまくいきません。 このため、今のところ systemd ファイルを以下のように書いてみます(抜粋です)。

( /usr/lib/systemd/system/openstack-namespace-iptables.service )


[Unit]

Description=Setup iptables on Linux namespace

DefaultDependencies=false

After=neutron-server.service



[Service]

Type=oneshot

ExecStart=/usr/local/bin/openstack-namespace-iptables restore

ExecStop=/usr/local/bin/openstack-namespace-iptables backup

TimeoutSec=60

RemainAfterExit=yes



[Install]

RequiredBy=multi-user.target



( /usr/local/bin/openstack-namespace-iptables )

#!/bin/sh



logfile="/var/log/iptables-namespace.log"

iptables_file="/etc/iptables-namespace"

iptables_backup_file="$iptables_file"



# Wait for standup namespaces (because always delay about 1 min).

while :

do

  namespace="$(/sbin/ip netns|/usr/bin/grep qrouter)"

  if [ "$namespace" ];

  then

    break;

  fi

  sleep 2

done

… (以下省略)

 本当は、このスクリプトはもっと改良の余地があると思います。qrouter-* namespace が見えるようになってから、という点を [Unit] の After= 以下に書ければいいのですが、その辺どうやるべきか思いつかず、こんな表現になってしまっています。 それでも、手元の環境ではある程度の確率(100%ではないのですが…)で自動的に namespace 内の iptables に反映できるようになるかと思います。

これらのScriptはダウンロードできるようにしましたので、参考にしていただければと思います。
CentOS 7 で実際に動かしています。

根本策?

根本的には、FWaaS側にポートフォワーディングの機能があり、APIで操作できればこのような手動設定を行う必要がなく、またHA構成であったり、バックエンドが iptables ではなく MidoNet 等の場合でもスムーズな対応を期待できるのですが、残念ながら blueprint に複数の要望が 上がってる程度で、まだ搭載までは至っていないようです。

...とここまで書いたところで再度調べてみたのですが、作業を行っていると思われるlaunchpadでは、最近少しづつではありますが実装作業が行われている(らしい)状況ですので、この Tips が将来不要になる日は来るかもしれません。