Kubernetesネットワーク分析のFlannel


FlannelはcereosオープンソースのCNIネットワークプラグインで、下図のflannel公式サイトが提供するパケットの1つがパケットの封止、伝送、パケットの取り外しの概略図を経て、この画像から2台の機械のdocker 0がそれぞれ異なるセグメントにあることを見ることができます:10.1.20.1/24と10.1.15.1/24、Web App Frontend 1 pod(10.1.15.2)から別のホスト上のBackend Services 2 pod(10.1.20.3)に接続すると、ネットワークパケットはホスト192.168.0.100から192.168.0.200に送信され、内層コンテナのパケットはホストのUDPにカプセル化され、ホストのIPとmacアドレスが外層にパッケージされる.これは古典的なoverlayネットワークであり、コンテナのIPは内部IPであり、ホスト間通信ができないため、コンテナのネットワークは互いに通じ合い、ホストのネットワーク上に負荷する必要がある.
flannelは多種のネットワークモードをサポートして、よく使うのはvxlan、UDP、hostgw、ipipとgceとアリ雲などで、vxlanとUDPの違いは:vxlanはカーネルパッケージで、UDPはflanneldユーザー状態のプログラムパッケージで、だからUDPの方式の性能は少し悪いです;hostgwモードはホストゲートウェイモードであり、コンテナから別のホストへのコンテナのゲートウェイがホストのNICアドレスに設定されている.これはcalicoと非常に似ているが、calicoはBGPで宣言されているが、hostgwは中心のetcdで配布されているため、hostgwは直結モードであり、overlayでパッケージを封印したり、パッケージを取り外したりする必要はなく、性能が高い.しかしhostgwモードの最大の欠点は、2層ネットワークでなければならないことです.結局、次のホップのルーティングは隣のテーブルにある必要があります.そうしないと通行できません.
実際の生産環境では、vxlanモードが最もよく使われています.まず、動作原理を見てから、ソースコード解析によってプロセスを実現します.
インストールの手順は非常に簡単で、主に2つのステップに分けられます.
最初のステップflannelのインストール
yum install flannelまたはkubernetesのdaemonset方式で起動し、flannel用のetcdアドレスを構成する
ステップ2クラスタネットワークの構成
curl -L http://etcdurl:2379/v2/keys/flannel/network/config -XPUT -d value="{\"Network\":\"172.16.0.0/16\",\"SubnetLen\":24,\"Backend\":{\"Type\":\"vxlan\",\"VNI\":1}}"

次に、各ノードのflannedプログラムを起動します.
一、動作原理
1、容器の住所はどのように分配します
Dockerコンテナの起動時にdocker 0を通じてIPアドレスを割り当て、flannelは各マシンにIPセグメントを割り当て、docker 0に配置し、コンテナの起動後にこのセグメント内で使用されていないIPを選択します.flannelはどのようにdocker 0ネットワークセグメントを修正しますか?
まずflannelの起動ファイル/usr/lib/systemd/system/flanneldを見てみましょう.service
[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/flanneld
ExecStart=/usr/bin/flanneld-start $FLANNEL_OPTIONS
ExecStartPost=/opt/flannel/mk-docker-opts.sh -k DOCKER_NETWORK_OPTIONS -d /run/flannel/docker

ファイルにはflannel環境変数と起動スクリプトと起動後実行スクリプトExecStartPost設定のmk-docker-optsが指定されています.sh、このスクリプトの役割は/run/flannel/dockerを生成することであり、ファイルの内容は以下の通りである.
DOCKER_OPT_BIP="--bip=10.251.81.1/24"
DOCKER_OPT_IPMASQ="--ip-masq=false"
DOCKER_OPT_MTU="--mtu=1450"
DOCKER_NETWORK_OPTIONS=" --bip=10.251.81.1/24 --ip-masq=false --mtu=1450"

このファイルはdocker起動ファイル/usr/lib/systemd/system/docker.によってサービスに関連付けられ、
[Service]
Type=notify
NotifyAccess=all
EnvironmentFile=-/run/flannel/docker
EnvironmentFile=-/etc/sysconfig/docker

これでdocker 0のブリッジを設定できます.
開発環境では、3台のマシンがあり、それぞれ次のセグメントが割り当てられています.
host-139.245 10.254.44.1/24
host-139.246 10.254.60.1/24
host-139.247 10.254.50.1/24
2、容器の通信方法
各コンテナにIPを割り当てると、異なるホスト上のコンテナがどのように通信するかについて説明しました.最も一般的なvxlanを例に挙げます.ここには3つのキー、1つのルーティング、1つのarp、1つのFDBがあります.私たちはコンテナ発注の過程に従って、上の3つの要素の役割を一つ一つ分析して、まずコンテナから出たパケットはdocker 0を通って、それでは下は直接ホストネットワークから出て、それともvxlanパッケージを通じて転送しますか?これは各機械の上のルートで設定されています.
 #ip route  show dev flannel.1
10.254.50.0/24 via 10.254.50.0 onlink
10.254.60.0/24 via 10.254.60.0 onlink

各ホストには他の2台のマシンへのルーティングが表示されます.このルーティングはonlinkルーティングです.onlinkパラメータは、このゲートウェイを強制することが「リンク上」であることを示しています(リンク層ルーティングはありませんが).そうしないと、linux上には異なるセグメントのルーティングを追加できません.これによりパケットは、コンテナの直接アクセスであればflannelに渡すことがわかる.1デバイス処理.
flannel.1この仮想ネットワークデバイスはデータをカプセル化しますが、次の問題がまた来ました.このゲートウェイのmacアドレスはいくらですか.このゲートウェイはonlinkで設定されているので、flannelはこのmacアドレスを送信し、arpテーブルを確認します.
# ip neig show dev flannel.1
10.254.50.0 lladdr ba:10:0e:7b:74:89 PERMANENT
10.254.60.0 lladdr 92:f3:c8:b2:6e:f0 PERMANENT

このゲートウェイに対応するmacアドレスが表示され、内層のパケットがカプセル化されます
それとも最後の質問ですが、外出先のパケットの目的IPはいくらですか?言い換えれば、このパッケージされたパケットはどの機械に送るべきですか?パケットごとに放送されるのは難しいです.vxlanのデフォルト実装は最初は確かにブロードキャスト方式だったが、flannelは再びhack方式でこの転送発表FDBを直接送信した.
# bridge fdb show dev flannel.1
92:f3:c8:b2:6e:f0 dst 10.100.139.246 self permanent
ba:10:0e:7b:74:89 dst 10.100.139.247 self permanent

これによりmacアドレス転送先IPに対応して取得できる.
ここで注意すべき点は、arpテーブルでもFDBテーブルでもpermanentであり、書き込み記録は手動でメンテナンスされていることを示しており、従来のarpが隣人を取得する方法はブロードキャストで取得されており、受信対端のarpが対応すると対端がreachableとマークされ、reachable設定時間を超えた後、対端が失効したことを発見するとstaleとマークされ、その後転送されるdelayおよびprobeはプローブの状態に入り,プローブに失敗するとFailed状態とマークされる.arpの基本的な内容を紹介するのは、旧バージョンのflannelが本明細書で述べた方法ではなく、一時的なarpスキームを採用しているためであり、このとき送信されたarpはreachable状態を表し、これはflannelでreachableタイムアウト時間を超えると、このマシン上のコンテナのネットワークが中断することを意味する.以前(0.7.x)のバージョンを簡単に振り返ってみましょう.コンテナは、エンドarpアドレスを取得するために、カーネルがarp問い合わせを最初に送信します.
/proc/sys/net/ipv4/neigh/$NIC/ucast_solicit

この時点でarp問い合わせがユーザ空間に送信されます
/proc/sys/net/ipv4/neigh/$NIC/app_solicit

以前のバージョンのflannelはまさにこの特性を利用して、設定します
# cat   /proc/sys/net/ipv4/neigh/flannel.1/app_solicit
3

これによりflanneldは、カーネルがユーザ空間に送信するL 3 MISSを取得し、etcdに合わせてこのIPアドレスに対応するmacアドレスを返し、reachableに設定することができる.解析から,flanneldプログラムが終了するとコンテナ間の通信が中断することが分かるが,ここでは注意が必要である.Flannelの起動手順を下図に示します.
FlannelはnewSubnetManagerの実行を開始し、バックグラウンドデータストレージを作成します.現在は2つのバックエンドをサポートしています.デフォルトはetcdストレージです.flannelが「kube-subnet-mgr」パラメータを指定した場合、kubernetesのインタフェースを使用してデータを格納します.
具体的なコードは以下の通りです.
func newSubnetManager() (subnet.Manager, error) {
    if opts.kubeSubnetMgr {
       return kube.NewSubnetManager(opts.kubeApiUrl, opts.kubeConfigFile)
    }
  
    cfg := &etcdv2.EtcdConfig{
       Endpoints: strings.Split(opts.etcdEndpoints, ","),
       Keyfile:   opts.etcdKeyfile,
       Certfile:  opts.etcdCertfile,
       CAFile:    opts.etcdCAFile,
       Prefix:    opts.etcdPrefix,
       Username:  opts.etcdUsername,
       Password:  opts.etcdPassword,
    }
  
    // Attempt to renew the lease for the subnet specified in the subnetFile
    prevSubnet := ReadCIDRFromSubnetFile(opts.subnetFile, "FLANNEL_SUBNET")
  
    return etcdv2.NewLocalManager(cfg, prevSubnet)
 }

SubnetManagerでは、上記で紹介した配置時に配置したetcdのデータと合わせて、backendとセグメント情報を取得できます.vxlanであれば、NewManagerで対応するネットワークマネージャを作成します.ここでは簡単なエンジニアリングモードを使用します.まず、各ネットワークモードマネージャはinit初期化によって登録されます.
vxlanのように
func init() {
    backend.Register("vxlan", New)

udpなら
  func init() {
    backend.Register("udp", New)
 }

他にも同様に、構築方法をmapに登録し、etcd構成のネットワークモードに基づいて、対応するネットワークマネージャを有効にするように設定します.
3、登録ネットワーク
RegisterNetwork、まずflannelを作成します.vxlaniDのNIC、デフォルトvxlaniDは1です.次にetcdにリースを登録し、対応するセグメント情報を取得するという詳細があります.古いバージョンのflannelは起動するたびに新しいセグメントを取得し、新しいバージョンのflannelはetcdに登録されているetcd情報を遍歴し、以前に割り当てられたセグメントを取得し、引き続き使用します.
最後にWriteSubnetFileでローカルサブネットファイルを書き、
    # cat /run/flannel/subnet.env 
FLANNEL_NETWORK=10.254.0.0/16
FLANNEL_SUBNET=10.254.44.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true

このファイルでdockerのネットワークを設定します.注意深い読者は、ここのMTUがイーサネット規定の1500ではないことに気づくかもしれません.これは、外層のvxlanパッケージが50 Byteを占めているからです.
もちろんflannelが起動した後も継続的なwatch etcdのデータが必要です.これは新しいflannelノードが追加されたり、変更されたりしたときに、他のflannelノードが動的に更新できる3つのテーブルです.主な処理方法はhandleSubnetEventsにあります
    func (nw *network) handleSubnetEvents(batch []subnet.Event) {
 . . .
  
       switch event.Type {//          (      )
       case subnet.EventAdded:
  . . .//     
if err := netlink.RouteReplace(&directRoute); err != nil {
    log.Errorf("Error adding route to %v via %v: %v", sn, attrs.PublicIP, err)
    continue
 } 
//  arp 
log.V(2).Infof("adding subnet: %s PublicIP: %s VtepMAC: %s", sn, attrs.PublicIP, net.HardwareAddr(vxlanAttrs.VtepMAC))
             if err := nw.dev.AddARP(neighbor{IP: sn.IP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil {
                log.Error("AddARP failed: ", err)
                continue
             }
 //  FDB 
             if err := nw.dev.AddFDB(neighbor{IP: attrs.PublicIP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil {
                log.Error("AddFDB failed: ", err)
  
                              if err := nw.dev.DelARP(neighbor{IP: event.Lease.Subnet.IP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil {
                   log.Error("DelARP failed: ", err)
                }
  
                continue
             }//       
      case subnet.EventRemoved:
//    
             if err := netlink.RouteDel(&directRoute); err != nil {
                log.Errorf("Error deleting route to %v via %v: %v", sn, attrs.PublicIP, err)
             
          } else {
             log.V(2).Infof("removing subnet: %s PublicIP: %s VtepMAC: %s", sn, attrs.PublicIP, net.HardwareAddr(vxlanAttrs.VtepMAC))
  
           //  arp            if err := nw.dev.DelARP(neighbor{IP: sn.IP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil {
                log.Error("DelARP failed: ", err)
             }
 //  FDB
             if err := nw.dev.DelFDB(neighbor{IP: attrs.PublicIP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil {
                log.Error("DelFDB failed: ", err)
             }
  
             if err := netlink.RouteDel(&vxlanRoute); err != nil {
                log.Errorf("failed to delete vxlanRoute (%s -> %s): %v", vxlanRoute.Dst, vxlanRoute.Gw, err)
             }
          }
       default:
          log.Error("internal error: unknown event type: ", int(event.Type))
       }
    }
 }

これによりflannel内の任意のホストの追加と削除が他のノードによって感知され、ローカルカーネル転送が更新されます.
作者:陈晓宇
出所:宜信技術学院