tjtjtjのメモ

自分のためのメモです

envoy go-control-plane 実験

envoy control-plane を調べていた。control-plane から data-plane に接続するのかと思い込んでいたがそうではなかった。

今の解釈はこんな感じ

  • envoy(data-plane)は Endpoint Discovery Service(EDS) 等のxDSの利用する側。クライアント
  • control-plane は xDS を提供する側。サーバー
  • envoy は control-plane から配信された情報から、自分の設定を更新していく
  • envoy が参照すべき xDS のエンドポイントを envoy.yaml に設定しておく

という解釈ができるようになってから envoy.yaml を眺めると前より理解しやすくなった。

今回のコードはここに置きました。 envoyprac/prac1 at master · tjtjtj/envoyprac · GitHub

実験の流れ

ここを参考(ほとんどコピーですが)にさせていただきました。 感謝感謝

  • helloを起動
  • envoy 起動
    • このときenvoyはhelloを知らない。 EDSのエンドポイントは知っている
  • curl しても失敗するはず
  • control-plane 起動
    • envoy が control-plane(EDS) に接続
    • control-plane がエンドポイントを配信
  • curl するとhelloを参照するはず

実験

helloを起動

docker run --rm -d -p 8080:80 dockercloud/hello-world

直接 curl

# curl http://localhost:8080

        <h3>My hostname is 9c2075e4f35a</h3>    </body>

/tmp/envoy/envoy.yaml

node:
  id: node0
  cluster: cluster.local

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address: { address: 0.0.0.0, port_value: 80 }
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        config:
          stat_prefix: ingress_http
          route_config:
            name: route
            virtual_hosts:
            - name: hello_service
              domains: ["hello.local"]
              routes:
              - match: { prefix: "/" }
                route: { cluster: hello_cluster }
          http_filters:
          - name: envoy.router
  clusters:
  - name: hello_cluster
    connect_timeout: 0.25s
    lb_policy: ROUND_ROBIN
    type: EDS
    eds_cluster_config:
      eds_config:
        api_config_source:
          api_type: GRPC
          grpc_services:
            envoy_grpc:
              cluster_name: xds_cluster
  - name: xds_cluster
    connect_timeout: 0.25s
    lb_policy: ROUND_ROBIN
    http2_protocol_options: {}
    load_assignment:
      cluster_name: xds_cluster
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address: {address: 127.0.0.1, port_value: 20000 }

envoy 起動

# docker run \
    --name envoy --rm --publish 80:80 \
    --net=host \
    -v /tmp/envoy:/etc/envoy \
    envoyproxy/envoy:v1.10.0

最初の curl は失敗

# curl -H 'Host: hello.local' 127.0.0.1
no healthy upstream

prac1.go

package main

import (
    "flag"
    "fmt"
    "log"
    "net"
    "os"

    api "github.com/envoyproxy/go-control-plane/envoy/api/v2"
    core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
    "github.com/envoyproxy/go-control-plane/envoy/api/v2/endpoint"
    "github.com/envoyproxy/go-control-plane/pkg/cache"
    xds "github.com/envoyproxy/go-control-plane/pkg/server"
    "google.golang.org/grpc"
)

// NodeHash interfaceの実装。Envoyの識別子から文字列をかえすハッシュ関数を実装する。
type hash struct{}

func (hash) ID(node *core.Node) string {
    if node == nil {
        return "unknown"
    }
    return node.Cluster + "/" + node.Id
}

var upstreams = map[string][]struct {
    Address string
    Port    uint32
}{
    // ここはコンテナのアドレス
    "hello_cluster": {{"127.0.0.1", 8080}},
}

// スナップショットを返す。構造体の形はProtocol Bufferの定義と同じ。
func defaultSnapshot() cache.Snapshot {
    var resources []cache.Resource
    for cluster, ups := range upstreams {
        eps := make([]endpoint.LocalityLbEndpoints, len(ups))
        for i, up := range ups {
            eps[i] = endpoint.LocalityLbEndpoints{
                LbEndpoints: []endpoint.LbEndpoint{{
                    HostIdentifier: &endpoint.LbEndpoint_Endpoint {
                        Endpoint: &endpoint.Endpoint{
                            Address: &core.Address{
                                Address: &core.Address_SocketAddress{
                                    SocketAddress: &core.SocketAddress{
                                        Address:       up.Address,
                                        PortSpecifier: &core.SocketAddress_PortValue{PortValue: up.Port},
                                    },
                                },
                            },
                        },
                    },
                }},
            }
        }
        assignment := &api.ClusterLoadAssignment{
            ClusterName: cluster,
            Endpoints:   eps,
        }
        resources = append(resources, assignment)
    }

    return cache.NewSnapshot("0.0", resources, nil, nil, nil)
}

func run(listen string) error {
    // xDSの結果をキャッシュとして設定すると、いい感じにxDS APIとして返してくれる。
    snapshotCache := cache.NewSnapshotCache(false, hash{}, nil)
    server := xds.NewServer(snapshotCache, nil)

    // NodeHashで返ってくるハッシュ値とその設定のスナップショットをキャッシュとして覚える
    err := snapshotCache.SetSnapshot("cluster.local/node0", defaultSnapshot())
    if err != nil {
        return err
    }

    // gRCPサーバーを起動してAPIを提供
    grpcServer := grpc.NewServer()
    api.RegisterEndpointDiscoveryServiceServer(grpcServer, server)

    lsn, err := net.Listen("tcp", listen)
    if err != nil {
        return err
    }
    return grpcServer.Serve(lsn)
}

func main() {
    var listen string
    flag.StringVar(&listen, "listen", ":20000", "listen port")
    flag.Parse()

    log.Printf("Starting server with -listen=%s", listen)

    err := run(listen)
    if err != nil {
        fmt.Println(os.Stderr, err)
        os.Exit(1)
    }
}

prac1.go(controle-plane) 起動後の curl は成功した。

# curl -H 'Host: hello.local' 127.0.0.1

        <h3>My hostname is 9c2075e4f35a</h3>    </body>

あーenvoy の stats の変化も確認するんだった。次回やる。

参考

i-beam.org

github.com

github.com