はじめに
ネットワークを中心にデータセンターからKubernetesまで、幅広くインフラに関わらせていただいている市川です。
本エントリーでは、先日の「OpenStackとKubernetesを利用したマルチプラットフォームへのCI環境」エントリーから続くヤフーのOpenStackデプロイ環境におけるKubernetes利用連載の第2弾として、われわれのKubernetesネットワーク環境について基盤となるProject Calicoの紹介と合わせて説明したいと思います。
Project Calico
Project Calicoとは
通常Kubernetesではflannelを用いてPod用のネットワークを構築しますが、今回私たちはネットワーク環境としてProject Calico(以下 Calico)を利用した構成を選択しています。Calicoは2014年にMetaswitch Networks社が発表したオープンソースの仮想ネットワークソリューションであり、Kubernetesを始めとしたDockerなどのLinuxコンテナネットワーキング機能の他、OpenStackのNeutronと連携した仮想マシンのネットワーキング機能を提供します。現在はCoreOS社のflannelと合流してTigera社を立ち上げ、クラウドネットワーキングソリューションCanalの一部としてそれぞれ開発が進められています。
Calicoの特徴
クラウドコンピューティングにおけるネットワーク接続性を、インターネットと同じ技術で実現することがCalicoの大きな特徴です。
flannelなどのクラウドネットワーキング技術の多くは、VXLANやGREなどのオーバーレイ技術やトンネリング技術を利用してLayer 2(以下 L2)のネットワークを構築します。これらのネットワークは柔軟性がありますが、L2フレームのカプセル化処理が追加で必要となるため、オーバーヘッドやフレームサイズの制限によるパフォーマンス低下の懸念が生じてしまいます。最近はこれらの処理のオフロード機能を持つハードウェア製品なども市場に増えてきていますが、ベンダーロックインとなってしまったりトラブルシューティングの複雑性といった運用面を考慮すると、なるべくシンプルで運用しやすく、どのような環境でも実現できるものの方が良いという考えがあります。
対してCalicoはこれらのオーバーレイ技術を用いず、インターネットを支える代表的なルーティング技術であるBGPを利用して、オーバーヘッドを気にすることのないシンプルで”ピュアな”Layer 3(以下 L3)ネットワークをコンテナネットワーク環境に実現します。ここで言う”ピュアな”L3ネットワークというのは、コンテナネットワークからL2ベースの通信を無くしたネットワークを意味しています。Calicoは各コンテナに対して/32(IPv6であれば/128)という、コンテナ自身のみが存在する最小のネットワークレンジのIPアドレスを付与し、各ホストノードに対しては仮想ルータ機能を提供して、ホスト内に存在するコンテナのIPアドレスをネットワーク上に”直接”広報します。仮想ルータがホストノードに構成するルーティングテーブルを元に、各コンテナはL3ベースで内外との通信を行います。この最小単位のネットワークで構成されるコンテナは、作成・削除・移動のイベントが発生しても他のコンテナに影響を与えることがなく、ネットワークリソースの有効活用にもつながります。また、世界中で広く利用され成熟したプロトコルであるBGPは安定感があり、インターネット負荷分散技術によってスケーラビリティも備えています。運用の面でも、経験者であればこれまでのノウハウが活用できるという点でメリットがあります。
しかし、L3ベースであるということは、当然ながらL2通信機能を利用するソフトウェアには不向きです。今回私たちが利用する環境ではL2機能は必要としていなかったため、Calicoの導入に対して問題は発生しませんでした。
Calicoの構成
Calicoは下記のコンポーネントで構成されています。
- Felix
各ホスト上で動作し、エンドポイントとなるデーモンです。ホスト内のPodのインタフェース情報を管理し、ルート情報やACLポリシーの組み立てを行います。 - confd
etcdに保存されているcalicoの設定情報からBIRD用の設定ファイルを生成し適用します。 - BIRD
Quaggaと並ぶOSSのルーティングソフトウェアです。Felixの生成するPodの経路情報を、BGPを利用して外部ネットワークへ配布します。 - コンテナネットワーキングインタフェース(CNI)プラグイン
KubernetesからCNIプラグインを通じて実行され、Podに対するネットワーク関連設定を行います。 - calicoctl
Calicoに関する設定やステータス確認は、このコマンドを通して行います。
下図のように、Felix/confd/BIRDは1つのDockerコンテナ内で動作し、CNIプラグインはホストノード上で実行されます。
Calicoのインストール
Calicoのインストールはシンプルで、基本的に以下の流れとなります。
- Calico用 etcdの構築
- calicoctlの配置
- 仮想ルータとなるDockerイメージの取得
- CNIプラグインの配置
- calicoctl nodeで起動
Kubernetesの公式ドキュメントに参考となるものがありますので、ここではインストールに関しては省略させていただきます。
インストール完了後、calicoctlコマンドを利用して仮想ルータのコンテナ起動や各種設定、確認を行います。その際、calicoctlは設定が保存されているetcdの場所を求めてきます。環境変数としてETCD_AUTHORITY
をあらかじめ設定しておくか、適宜コマンド実行時に渡して実行してください。
Kubernetesネットワークの設定と動作
Kubernetesクラスタを構築する際、まず初めにPodのIPとして利用するネットワークレンジをCalicoに定義します。
# calicoctl pool add 192.168.0.0/22
# calicoctl pool show
+----------------+---------+
| IPv4 CIDR | Options |
+----------------+---------+
| 192.168.0.0/22 | |
+----------------+---------+
+-----------+---------+
| IPv6 CIDR | Options |
+-----------+---------+
+-----------+---------+
その後、設定したネットワークから各ホストノードに対してサブネットの割り当てが行われます。calicoctlコマンドでは確認できませんが、etcdにて分割状況とホストへの割り当てが確認できます。
# etcdctl --endpoint http://{{ CalicoEtcd }} ls /calico/ipam/v2/assignment/ipv4/block
/calico/ipam/v2/assignment/ipv4/block/192.168.0.0-26
/calico/ipam/v2/assignment/ipv4/block/192.168.0.64-26
/calico/ipam/v2/assignment/ipv4/block/192.168.0.128-26
/calico/ipam/v2/assignment/ipv4/block/192.168.0.192-26
/calico/ipam/v2/assignment/ipv4/block/192.168.1.0-26
/calico/ipam/v2/assignment/ipv4/block/192.168.1.64-26
/calico/ipam/v2/assignment/ipv4/block/192.168.1.128-26
/calico/ipam/v2/assignment/ipv4/block/192.168.1.192-26
/calico/ipam/v2/assignment/ipv4/block/192.168.2.0-26
/calico/ipam/v2/assignment/ipv4/block/192.168.2.64-26
/calico/ipam/v2/assignment/ipv4/block/192.168.2.128-26
# etcdctl --endpoint http://{{ CalicoEtcd }} get /calico/ipam/v2/assignment/ipv4/block/192.168.0.0-26 | python -mjson.tool | grep -e host -e cidr
"affinity": "host:node-001",
"cidr": "192.168.0.0/26",
また、各ホスト上で確認する場合はip route
コマンドを実行した際に出力される経路情報でblackholeとして設定されている経路が、自ホスト上でPod用に割り当てられているネットワークとなります。
# ip r | grep blackhole
blackhole 192.168.0.0/26 proto bird
Podが作成されると割り当てられたサブネットをさらに切り出し、/32のIPを付与します。また、デフォルトゲートウェイとしてホストのIPが設定されます。
/ # ip r
default via 172.16.0.1 dev eth0
172.16.0.1 dev eth0
/ # ip -f inet a show dev eth0
47: eth0@if48: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc pfifo_fast state UP qlen 1000
inet 192.168.0.13/32 scope global eth0
valid_lft forever preferred_lft forever
続いてBGPの設定を行います。デフォルトではAS番号としてAS64511を利用しますが、ここではプライベートASとして定義されている範囲内のAS64512へ変更しています。他の設定コマンド同様、この変更は一度で全てのノードに適用されて設定が切り替わります。BGPの接続状態はcalicoctl status
のコマンドで確認できます。デフォルトではiBGPを利用して、全てのノード間でフルメッシュのピアリングが行われます。
# calicoctl bgp default-node-as 64512
# calicoctl status
calico-node container is running. Status: Up 2 days
Running felix version 1.4.0rc1
IPv4 BGP status
IP: 172.16.0.1 AS Number: 64512 (inherited)
+---------------+-------------------+-------+------------+------------------------------------------+
| Peer address | Peer type | State | Since | Info |
+---------------+-------------------+-------+------------+------------------------------------------+
| 172.16.0.2 | node-to-node mesh | up | 2016-07-12 | Established |
| 172.16.0.3 | node-to-node mesh | up | 2016-07-12 | Established |
| 172.16.0.4 | node-to-node mesh | up | 13:20:50 | Established |
| 172.16.0.5 | node-to-node mesh | up | 13:29:15 | Established |
| 172.16.0.6 | node-to-node mesh | up | 2016-07-12 | Established |
+---------------+-------------------+-------+------------+------------------------------------------+
IPv6 BGP status
No IPv6 address configured.
これでホスト上の仮想ルータによって各Pod用のネットワーク情報がお互いに広報され、クラスタ内においてPod間のネットワーク接続性が得られるようになります。しかし、これだけではPodの経路情報はクラスタ内部でしか共有されていないため、外部ネットワークからは到達できません。物理ネットワーク上のルーターへもPodの経路情報を伝える必要がありますが、このフルメッシュのiBGP構成でルーターへ同様に接続するには負荷の懸念があるため、下記で紹介するようにルートリフレクタを導入しピア数の削減も行った上で実際は構築しています。
ネットワーク構成
デフォルトでは、クラスタ内の各ホストノード上で動作するCalicoのBIRDプロセスはフルメッシュ型の構成でiBGPのピアを張り、お互いに割り当てられたIPアドレスレンジの経路情報を交換します。しかし、その場合クラスタ内のiBGPピアの数はN(N-1)/2となり、クラスタの規模が大きくなった場合に各仮想ルータの負荷も上昇し、ホストノードのリソースを有効に利用できなくなってしまいます。この負荷を軽減するために、ルートリフレクタを導入します。ルートリフレクタは自身を中心として、各仮想ルータに対してiBGPピアをスター型の構成をとります。これによりピア数はNMとなり、フルメッシュ型と比較してピア数を抑えることが可能です。通常のiBGPピアでは自身が持つネットワーク以外の経路情報は他のiBGPピアに対して広報しませんが、ルートリフレクタは受け取った経路情報をクライアントとして設定されている他のiBGPピアに対して反射して広報します。
本環境においては、ルートリフレクタは各ノード上のBIRDプロセスとiBGPのピアリングを行うだけでなく物理ネットワーク上のルータともiBGPのピアリングを行い、ルートリフレクタのクライアントとして設定します。そうすることによって物理ネットワーク上にKubernetesのPodの経路情報が伝わり、ルータは受け取った経路情報を外部ネットワークに対して再配布することで、外部ネットワークからPodへの直接通信が可能となります。また、PodのIPだけでなく前エントリーにて紹介しているKubernetesロードバランサで利用するクラスタIPの経路情報についても、合わせて外部に広報します。
今回、ルートリフレクタは運用面を考慮してH/Wのネットワーク機器を採用しました。台数が1台のみでは機器の障害時に単一障害点となってしまうため、冗長性を持たせてクラスタを組んだ構成で利用しています。
設定方法は以下となります。これまでのフルメッシュ構成を解き、ルートリフレクタのピア情報を追加します。iBGPですので、AS番号はCalicoで利用するものと同じ値を設定します。
# calicoctl bgp node-mesh off
# calicoctl bgp peer add X.X.X.X as 64512
# calicoctl bgp peer add Y.Y.Y.Y as 64512
# calicoctl status
calico-node container is running. Status: Up 2 days
Running felix version 1.4.0rc1
IPv4 BGP status
IP: 172.16.0.1 AS Number: 64512 (inherited)
+--------------+-----------+-------+----------+---------------------------------------------+
| Peer address | Peer type | State | Since | Info |
+--------------+-----------+-------+----------+---------------------------------------------+
| X.X.X.X | global | up | 10:42:48 | Established |
| Y.Y.Y.Y | global | up | 10:42:49 | Established |
+--------------+-----------+-------+----------+---------------------------------------------+
IPv6 BGP status
No IPv6 address configured.
ルートリフレクタ側のiBGP設定は利用する機器によってさまざまですので、ここでは省略させていただきます。最後に、ルーター側にてルートリフレクタから受け取った各Calicoノードの経路情報を他のネットワークへ再広報する設定を入れたら、Podと外部ネットワーク間の接続環境は完成です。
おまけ
余談ですが、構築にあたり発生した私たちならではの事象についてもご紹介したいと思います。
Podから外部に対する通信がうまくいかない!?
構築した環境においてPodの接続性をテストしたところ、正常に通信できない状況が発生しました。状況を確認してみると、経路広報は問題無いようでしたが接続対象のPodが存在するホストノードのところでPodから外部に出て行くことができていない状態でした。PodのデフォルトゲートウェイにはホストノードのIPアドレスが設定されていた(libcalico:v0.13.0までの仕様)のですが、Podがこのゲートウェイアドレスと通信するために必要となるMACアドレス解決ができず、通信できていないという状況でした。CalicoはL3ネットワークを提供しますが、Pod - ホストノード間ではL2通信が必要となるわけです。
原因とその背景
ヤフーでは、以前よりネットワーク負荷分散環境としてDSR構成を用いていました(参考: ヤフーネットワーク10年)。DSR構成では、VIPが設定されているLBは同一セグメントに存在するリアルサーバまでL2でパケットを転送します。リアルサーバではループバックインタフェースに該当するVIPが設定されることになりますが、VIPのMACアドレスを解決するARP要求に対して、本来応答するべきLBだけでなくリアルサーバもARPに応えてしまうことが設定次第で考えられます。LBより先にリアルサーバがVIPのMACとしてアドレス解決されてしまった場合、パケットはLBを介さずリアルサーバへ直接送られることになり、負荷分散されずにトラフィックに偏りが生じることになってしまいます。この事象を回避するために、LinuxサーバではARP応答は要求を受け取ったインタフェース自身が持つアドレスの時のみ応えるようカーネルパラメータを以下のように変更する必要があります。
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.all.arp_ignore = 1
現在のヤフーのネットワークではL3DSR構成が主流となっていて、従来のL2ベースのDSR機能で必要となるこのパラメータ変更は不要でしたが、過去の経緯からこの設定がデフォルト設定となっていました。しかしこの設定によって、Podとホスト間でのARP解決ができない状態であったということで、以下のようにカーネルパラメータを変更し、期待する正常な動作が可能となりました。
# sysctl -w net.ipv4.conf.all.arp_ignore=0
通常この値は0となっていることが多いのですが、過去の経緯から変更したカーネルパラメータによって問題が引き起こされていたという、ちょっとわれわれの環境ならではの小話でした。
まとめ
本エントリーでは、ヤフーのOpenStackコントローラ環境の基盤として利用しているKubernetesのネットワーク環境について紹介いたしました。Calicoを利用することで、シンプルかつ運用のしやすいL3ネットワークが実現できます。引き続き、日々進化する技術を追い求めつつ、柔軟に実環境への導入を進めていく予定です。またぜひどこかの場にて、情報発信させていただけたらと思います。どうもありがとうございました。
こちらの記事のご感想を聞かせください。
- 学びがある
- わかりやすい
- 新しい視点
ご感想ありがとうございました