コンテンツにスキップ

データプレーン@Istio

はじめに

本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。


01. データプレーンとは

サイドカープロキシメッシュの場合

サイドカープロキシメッシュのデータプレーンは、istio-iptables、 istio-initコンテナ、istio-proxyコンテナ、といったコンポーネントから構成される。


アンビエンドメッシュの場合

記入中...


02. データプレーンの要素

istio-initコンテナ

istio-initコンテナとは

コンテナの起動時に、istio-iptablesコマンドを実行することにより、istio-iptablesをPodに適用する。

istio_istio-init


istio-iptables

▼ istio-iptablesとは

istio-iptablesは、istio-proxyコンテナを持つPod内のネットワークの経路を制御する。

サービスディスカバリーとしてPodのIPアドレスを持つのはistio-proxyコンテナであり、istio-iptablesではないことに注意する。

# istio-initコンテナの起動時に実行する。
$ istio-iptables \
    -p 15001 \
    -z 15006 \
    -u 1337 \
    -m REDIRECT \
    -i * \
    -x \
    -b * \
    -d 15090,15020

▼ ルール

(1)

psコマンドを使用して、istio-proxyコンテナのenvoyプロセスのID (PID) を取得する。

# PIDが出力結果の2行目である。そのため、awkコマンドを使用して、2行目のみを取得している。
$ ps aux | grep envoy | awk '{print $2}'

1234567
2345678
3456789
(2)

nsenterコマンドを使用して、コンテナの稼働するユーザー空間を介し、コンテナにiptablesコマンドを送信する。Istioによって管理されているChainのルールを取得できる。

$ nsenter -t <istio-proxyコンテナのPID> -n iptables -L -n -t nat --line-number


Chain PREROUTING (policy ACCEPT)
...


Chain INPUT (policy ACCEPT)
...


Chain OUTPUT (policy ACCEPT)
...


Chain POSTROUTING (policy ACCEPT)
...


# istio-proxyコンテナへのインバウンド通信時に、NAPT処理を実行する。
Chain ISTIO_INBOUND (1 references)
num  target             prot  opt  source     destination
1    RETURN             tcp   --   0.0.0.0/0  0.0.0.0/0    tcp dpt:15008
2    RETURN             tcp   --   0.0.0.0/0  0.0.0.0/0    tcp dpt:15090 # メトリクス収集ツールからのリクエストを待ち受ける。
3    RETURN             tcp   --   0.0.0.0/0  0.0.0.0/0    tcp dpt:15021 # kubeletからのReadinessProbeチェックを待ち受ける。
4    RETURN             tcp   --   0.0.0.0/0  0.0.0.0/0    tcp dpt:15020 # データプレーンのデバッグエンドポイントに対するリクエストを待ち受ける。
5    ISTIO_IN_REDIRECT  tcp   --   0.0.0.0/0  0.0.0.0/0


Chain ISTIO_IN_REDIRECT (3 references)
num  target    prot  opt  source     destination
1    REDIRECT  tcp   --   0.0.0.0/0  0.0.0.0/0    redir ports 15006 #


# istio-proxyコンテナからのアウトバウンド通信時に、NAPT処理を実行する。
Chain ISTIO_OUTPUT (1 references)
num  target             prot  opt  source     destination
1    RETURN             all   --   127.0.0.6  0.0.0.0/0
2    ISTIO_IN_REDIRECT  all   --   0.0.0.0/0  !127.0.0.1   owner UID match 1337
3    RETURN             all   --   0.0.0.0/0  0.0.0.0/0    ! owner UID match 1337
4    RETURN             all   --   0.0.0.0/0  0.0.0.0/0    owner UID match 1337
5    ISTIO_IN_REDIRECT  all   --   0.0.0.0/0  !127.0.0.1   owner GID match 1337
6    RETURN             all   --   0.0.0.0/0  0.0.0.0/0    ! owner GID match 1337
7    RETURN             all   --   0.0.0.0/0  0.0.0.0/0    owner GID match 1337
8    RETURN             all   --   0.0.0.0/0  127.0.0.1
9    ISTIO_REDIRECT     all   --   0.0.0.0/0  0.0.0.0/0


Chain ISTIO_REDIRECT (1 references)
num  target     prot  opt  source     destination
1    REDIRECT   tcp   --   0.0.0.0/0  0.0.0.0/0    redir ports 15001

▼ ローカルホストは127.0.0.1ではない

istio-proxyコンテナがインバウンドをマイクロサービスにプロキシする時、127.0.0.6にリクエストを送信する。

127.0.0.1にするとiptables上で処理がループしてしまう。

Istiov1.9までは127.0.0.1で、v1.10から127.0.0.6になった。

▼ Pod外からのインバウンド通信の場合

Pod外からアプリコンテナへのインバウンド通信は、istio-iptablesにより、istio-proxyコンテナの15006番ポートにリダイレクトされる。

istio-proxyコンテナはこれを受信し、ローカルホスト (http://127.0.0.6:<アプリコンテナのポート番号>) のアプリコンテナにルーティングする。

istio_iptables_inbound

▼ Pod外へのアウトバウンド通信の場合

アプリコンテナからPod外へのアウトバウンド通信は、istio-iptablesにより、istio-proxyコンテナの15001番ポートにリダイレクトされる。

サービスディスカバリーによってPod等の宛先情報が、istio-proxyコンテナ内のEnvoyに登録されており、istio-proxyコンテナはアウトバウンド通信をPodに向けてルーティングする。

istio_iptables_outbound_other

▼ ローカスホスト通信の場合

アプリコンテナからローカルホスト (http://127.0.0.6:<ポート番号>) へのアウトバウンド通信は、istio-iptablesにより、istio-proxyコンテナの15001番ポートにリダイレクトされる。

istio_iptables_outbound_self


istio-proxyコンテナ

istio-proxyコンテナとは

istio_istio-proxy

リバースプロキシの能力を持つサイドカーコンテナである。

Dockerfileとしては、Envoyのバイナリファイルをインストールした後にpilot-agentを実行している。

そのため、pilot-agent、Envoy、が稼働している。

...

# Install Envoy.
ARG TARGETARCH
COPY ${TARGETARCH:-amd64}/${SIDECAR} /usr/local/bin/${SIDECAR}

...

# The pilot-agent will bootstrap Envoy.
ENTRYPOINT ["/usr/local/bin/pilot-agent"]

istio-proxyコンテナは、アプリコンテナのあるPodのみでなく、Istio IngressGatewayのPod内にも存在している。

Istioのサービスメッシュ外のネットワークからのインバウンド通信では、Istio IngressGateway内のistio-proxyコンテナにて、Pod等の宛先情報に基づいて、ルーティングを実行している。

一方で、アプリコンテナを持つPod間通信では、Pod内のistio-proxyコンテナに登録されたものに基づいて、Pod間で直接的に通信している。

仕様上、NginxやApacheを必須とする言語 (例:PHP) では、Pod内にリバースプロキシが2個ある構成になってしまうことに注意する。

▼ InitContainerとして

Kubernetesのv1.28では、InitContainerでサイドカーを作成できるようになった。

Istioでもこれに対応している。

istio-proxyコンテナのインジェクションの仕組みはそのままで、PodのマニフェストのPatch処理の内容をInitContainerのインジェクションに変更している。

これにより、Podの作成時にInitContainerをインジェクションできるようになる。

今まで、.spec.containers[*].lifecycle.preStopキーや.spec.containers[*].lifecycle.postStartキーに自前のコマンドを定義してistio-proxyコンテナの起動/終了タイミングを制御する必要があったが、InitContainerではそれが不要になる。

apiVersion: v1
kind: Pod
metadata:
  name: foo-pod
spec:
  containers:
    - name: app
      image: app:1.0.0
      ports:
        - containerPort: 8080
      volumeMounts:
        - name: app-volume
          mountPath: /go/src
  initContainers:
    - name: istio-proxy
      image: istio/proxyv2:latest
      restartPolicy: Always


istio-cniアドオンによるistio-validationコンテナ

▼ istio-cniアドオンとは

istio_istio-cni

各Node上で、istio-cni-nodeという名前のDaemonSetとして稼働する。

istio-initコンテナはistio-iptablesをPodに適用する権限を持っている。

しかし、Linuxのiptablesを操作するためにはroot権限が必要になるため、脆弱性が指摘されている (同様にして、ユーザーがiptablesコマンドを実行する時もsudo権限が必要である) 。

istio-initコンテナの代替案として、istio-cniアドオンが提供されている。

もしistio-cniアドオンを使用する場合は、istio-initコンテナが不要になる代わりとして、istio-validationコンテナが必要になる。

istio-validationコンテナ

istio-cniを採用している場合にのみそう挿入されるコンテナ。

istio-cniのDaemonSetがistio-iptablesを適用し終了することを待機するために、これが完了したかどうかを検証する。


02-02. istio-proxyコンテナ

pilot-agent (新istio-agent)

▼ pilot-agentとは

元々は、istio-agentといわれていた。

実体は、GitHubのpilot-agentディレクトリ配下のmain.goファイルで実行されるGoのバイナリファイルである。

ADS-APIとの間で双方向ストリーミングRPCを確立し、EnvoyからのADS-APIへのリクエストと反対にADS-APIからのリクエストを仲介する。

▼ ADSクライアントの実装

package adsc

import (

  ...

  discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"

  ...
)

...

func (a *ADSC) Run() error {
    var err error

    // 双方向ストリーミングRPCの接続を確立する。
    a.client = discovery.NewAggregatedDiscoveryServiceClient(a.conn)

    // Envoyのgo-control-planeパッケージから提供されている。
    // https://github.com/envoyproxy/go-control-plane/blob/v0.11.0/envoy/service/discovery/v3/ads.pb.go#L213-L220
    // また。.protoファイルで双方向ストリーミングRPCとして定義されている。
    // https://github.com/envoyproxy/envoy/blob/v1.25.0/api/envoy/service/discovery/v3/ads.proto#L32-L33
    a.stream, err = a.client.StreamAggregatedResources(context.Background())

    if err != nil {
        return err
    }

    a.sendNodeMeta = true

    a.InitialLoad = 0

    for _, r := range a.cfg.InitialDiscoveryRequests {
        if r.TypeUrl == v3.ClusterType {
            a.watchTime = time.Now()
        }
        // istio-proxyコンテナの起動時に、Istiodコントロールプレーンにリクエストを送信する。
        _ = a.Send(r)
    }


    a.RecvWg.Add(1)

    // ADS-APIからリクエストを受信し、Envoyの各処理コンポーネント別に整理する。
    go a.handleRecv()

    return nil
}

handleRecvメソッド内で、Envoyの各処理コンポーネントを整理し、最後にXDSUpdatesチャンネルに値を送信している。

func (a *ADSC) handleRecv() {

    for{

        ...

        a.VersionInfo[msg.TypeUrl] = msg.VersionInfo
        switch msg.TypeUrl {

        // 受信した宛先Podのリスナーを処理する。
        case v3.ListenerType:
            listeners := make([]*listener.Listener, 0, len(msg.Resources))
            for _, rsc := range msg.Resources {
                ...
            }
            a.handleLDS(listeners)

        // 受信した宛先Podのクラスターを処理する。
        case v3.ClusterType:
            clusters := make([]*cluster.Cluster, 0, len(msg.Resources))
            for _, rsc := range msg.Resources {
                ...
            }
            a.handleCDS(clusters)

        // 受信した宛先Podのエンドポイントを処理する。
        case v3.EndpointType:
            eds := make([]*endpoint.ClusterLoadAssignment, 0, len(msg.Resources))
            for _, rsc := range msg.Resources {
                ...
            }
            a.handleEDS(eds)

        // 受信した宛先Podのルートを処理する。
        case v3.RouteType:
            routes := make([]*route.RouteConfiguration, 0, len(msg.Resources))
            for _, rsc := range msg.Resources {
                ...
            }
            a.handleRDS(routes)

        default:
            if isMCP {
                a.handleMCP(gvk, msg.Resources)
            }
        }

        ...

        select {
        // XDSUpdatesチャンネルに値を送信する。
        // 最終的に、Envoyに設定する。
        case a.XDSUpdates <- msg:
        default:
        }
    }
}

▼ ADSクライアントとしてのistioctlコマンドの実装

RunメソッドによるXDS-APIとの通信は、istioctlコマンドでも使用されている。

func GetXdsResponse(dr *discovery.DiscoveryRequest, ns string, serviceAccount string, opts clioptions.CentralControlPlaneOptions, grpcOpts []grpc.DialOption,) (*discovery.DiscoveryResponse, error) {

    ...

    err = adscConn.Run()
    if err != nil {
        return nil, fmt.Errorf("ADSC: failed running %v", err)
    }

    err = adscConn.Send(dr)
    if err != nil {
        return nil, err
    }

    response, err := adscConn.WaitVersion(opts.Timeout, dr.TypeUrl, "")
    return response, err
}


Envoy

▼ Envoyとは

istio-proxyコンテナにて、リバースプロキシとして動作する。Envoyは、pilot-agentを介して、ADS-APIにリモートプロシージャーコールを実行する。また反対に、XDS-APIからのリモートプロシージャーコールをpilot-agentを介して受信する。


Graceful Drainモード

istio-proxyは、自分自身を安全に停止する。

(1)

現在のEnvoyプロセスがホットリロードを実行し、新しいEnvoyプロセスが起動する。

(2)

terminationDrainDuration値 (デフォルト5秒) によるGraceful Drainモード待機時間が開始する。

(3)

現在のEnvoyプロセスは、Graceful Drainモードを開始する。

(4)

現在のEnvoyプロセスへの接続を新しいEnvoyに段階的に移行する。

この時、現在のEnvoyプロセスはすぐに通信を閉じず、drainDuration値 (デフォルト5秒) による待機時間だけ、リクエストを受信しながら移行していく。

(5)

現在のEnvoyは、プロセスのGraceful Drainモードを終了する。

(6)

Graceful Drainモードの終了後、terminationDrainDurationによるGraceful Drainモード待機時間が完了する。

(7)

現行EnvoyプロセスにSIGKILLを送信する。

pod_terminating_process_istio-proxy


02-03. 待ち受けるポート番号

15000

istio-proxyコンテナの15000番ポートでは、Envoyのダッシュボードに対するリクエストを待ち受ける。

# istio-proxyコンテナ内でローカルホストにリクエストを送信する。
istio-proxy@<Pod名>: $ curl http://127.0.0.1:15000/config_dump


15001

istio-proxyコンテナの15001番ポートでは、アプリコンテナからのアウトバウンド通信を待ち受ける。

アプリコンテナからのアウトバウンド通信は、一度、istio-proxyコンテナの15001番ポートにリダイレクトされる。


15004

istio-proxyコンテナの15004番ポートでは、コントロールプレーンのコンテナの8080番ポートと一緒に使用される。

用途がわからず記入中...


15006

istio-proxyコンテナの15006番ポートでは、アプリコンテナへのインバウンド通信を待ち受ける。

アプリコンテナへのインバウンド通信は、一度、istio-proxyコンテナの15006番ポートにリダイレクトされる。


15020

istio-proxyコンテナの15020番ポートでは、データプレーンのデバッグエンドポイントに対するリクエストを待ち受ける。


15021

istio-proxyコンテナの15021番ポートでは、kubeletからのReadinessProbeチェックを待ち受ける。

istio-proxyコンテナ内のEnvoyが、/healthz/readyエンドポイントでReadinessProbeチェックを待ち受けており、もしEnvoyが停止してれば503ステータスのレスポンスを返却する。


15053

記入中...


15090

istio-proxyコンテナの15090番ポートでは、istio-proxyコンテナのメトリクス収集ツール (例:Prometheus) からのリクエストを待ち受ける。

istio-proxyコンテナ内のEnvoyが、/stats/prometheusエンドポイントでリクエストを待ち受けており、データポイントを含むレスポンスを返信する。

ただ、discoveryコンテナにも/stats/prometheusエンドポイントがあり、メトリクス収集ツールはこれを指定することが多い。

$ kubectl exec \
    -it foo-pod \
    -n foo-namespace \
    -c istio-proxy \
    -- bash -c "curl http://127.0.0.1:15090/stats/prometheus"

istio_build{component="proxy",tag="<リビジョン番号>"} 1

...

istio_request_bytes_count{...}
istio_request_messages_total{...}

...