istio-proxy相關概念以及啓動過程

Istio-proxy相關概念

istio-proxy

   Istio代理是可在客戶端和服務器端使用的微服務代理,並形成微服務網格。代理支持大量功能。

 客戶端功能:

  • 發現和負載平衡。代理可以使用幾個標準的服務發現和負載平衡API,以有效地將流量分配給服務。
  • 憑證注入。代理可以通過連接隧道或特定於協議的機制(例如HTTP請求的JWT令牌)注入客戶端身份。
  • 連接管理。代理管理與服務的連接,處理運行狀況檢查,重試,故障轉移和流控制。
  • 監控和記錄。代理可以報告客戶端指標並記錄到混合器。

服務器端功能:

  • 速率限制和流量控制。代理可以防止後端系統過載,並提供客戶端感知的速率限制。
  • 協議翻譯。代理是gRPC網關,提供JSON-REST和gRPC之間的轉換。
  • 認證與授權。代理支持多種身份驗證機制,並且可以使用客戶端身份通過混合器執行授權檢查。
  • 監控和記錄。代理可以報告服務器端指標並記錄到混合器。

istio proxy和Envoy的關係

istio proxy這個項目工程既包含引用了Envoy的源碼,還在此基礎上自己做了擴展,這個擴展是通過Envoy filter(過濾器)的形式來提供,這樣的話就可以使得proxy代理將策略執行決策委託給Mixer,這樣就解釋了爲什麼Mixer可以被設計爲提供策略和遙測的組件,Mixer->istio proxy->Envoy這種形式來控制。這樣通過這個方式就能:

  • 使用到Envoy的全部功能
  • 基於Envoy做擴展,結合istio本身做處理

編譯環境

centos7.x ,首先參考Bazel的官方文檔安裝Bazel,並且需要安裝gcc等相關工具。

git clone https://github.com/istio/proxy.git
cd proxy
make build_envoy

項目主要目錄如下:

├── "BUILD"
├── "Makefile"
├── "WORKSPACE"
├── src
│   ├── envoy                               -- envoy filter 插件源碼
│   │   ├── alts
│   │   │   ├── *.cc
│   │   │   ├── *.h
│   │   │   └── "BUILD"
│   │   ├── "BUILD"
│   │   ├── http
│   │   │   ├── authn                     --認證 filte
│   │   │   │   ├── *.cc
│   │   │   │   ├── *.h
│   │   │   │   └── "BUILD"
│   │   │   ├── jwt_auth                    --jwt 認證 filter
│   │   │   │   ├── *.cc
│   │   │   │   ├── *.h
│   │   │   │   └── "BUILD"
│   │   │   └── mixer                      --mixer filter,實現metrics上報,Quota(Rate Limiting (處理http協議) 
│   │   │       ├── *.cc
│   │   │       ├── *.h
│   │   │       └── "BUILD"
│   │   ├── tcp
│   │   │   └── mixer                      --mixer filter(處理tcp協議)
│   │   │       ├── *.cc
│   │   │       ├── *.h
│   │   │       └── "BUILD"
│   │   └── utils
│   │       ├── *.cc
│   │       ├── *.h
│   │       └── "BUILD"
│   └── istio
│       └── **
├── test
│   └── **
└── tools
    └── **

makefile文件的關鍵信息:其中//src/envoy:envoy爲bazel的語法,我們根據路徑查詢對應的內容;

build_envoy:
   export PATH=$(PATH) CC=$(CC) CXX=$(CXX) && bazel $(BAZEL_STARTUP_ARGS) build $(BAZEL_BUILD_ARGS) $(BAZEL_CONFIG_REL) //src/envoy:envoy

/src/envops/BUILD文件關鍵內容爲:cc_binary表明該target對應的是c++二進制文件路徑,其中deps部分是其依賴的其他target。前13個target都是本地依賴,對應到源碼目錄中的其他子目錄下的BUILD文件,其中最後一個比較特殊,是一個外部依賴,該外部庫爲envoy,因此proxy會make時音容envoy的源碼,也算是擴展吧。

envoy_cc_binary(
    name = "envoy",
    repository = "@envoy",
    visibility = ["//visibility:public"],
    deps = [
        // 對應本地文件
        "//extensions/access_log_policy:access_log_policy_lib",
        "//extensions/metadata_exchange:metadata_exchange_lib",
        "//extensions/stackdriver:stackdriver_plugin",
        "//extensions/stats:stats_plugin",
        "//src/envoy/http/alpn:config_lib",
        "//src/envoy/http/authn:filter_lib",
        "//src/envoy/http/jwt_auth:http_filter_factory",
        "//src/envoy/http/mixer:filter_lib",
        "//src/envoy/tcp/forward_downstream_sni:config_lib",
        "//src/envoy/tcp/metadata_exchange:config_lib",
        "//src/envoy/tcp/mixer:filter_lib",
        "//src/envoy/tcp/sni_verifier:config_lib",
        "//src/envoy/tcp/tcp_cluster_rewrite:config_lib",
        // 對應外部的envoy
        "@envoy//source/exe:envoy_main_entry_lib",
    ],
)

外部庫定義在根目錄下的workspace中,envoy的相關內容如圖,在執行過程中,根據URL下載編譯指定資源。

http_archive(
    name = "envoy",
    sha256 = ENVOY_SHA256,
    strip_prefix = "envoy-wasm-" + ENVOY_SHA,
    url = "https://github.com/envoyproxy/envoy-wasm/archive/" + ENVOY_SHA + ".tar.gz",
)

編譯過程中的依賴關係如下圖所示:

從istio-proxy項目中的Envoy BUILD中可以知道,這裏會編譯出name = "Envoy"的二進制程序,然後start_Envoy會啓動Envoy,同時會根據一些默認參數和配置文件模板生成一個全新的配置文件,然後運行。一些關鍵參數如Envoy二進制的路徑和配置路徑、監聽的端口、mixer的地址如下:

Envoy相關部分

istio-proxy源碼中提供了envoy.conf.template 通用配置模板,這個模板文件最終會生成一個envoy的配置文件,然後envoy啓動的時候指定運行。模板配置文件中已經配置好了Mixer相關的參數如mixer_server,這個Mixer對於Envoy來說就是一個cluster,因此是在cluster_manager裏面進行管理配置。

然後Envoy的官方文檔Listener discovery service (LDS)一文中有說明靜態的Listener文件配置是無法通過LDS API進行修改或刪除的,因此靜態配置會一直生效,istio-proxy源碼中則提供了Envoy的靜態配置文件envoy_lds.conf 靜態listeners配置

Filter相關

官方文檔中描述的三個filtuers相關概念;Istio-proxy實現了Network::ReadFilter 和 Network::WriteFilter 過濾器,這樣就可以通過filter API 綁定到Listener,然後當有數據讀or寫的時候調用到對應的filter了,這樣數據流就從Envoy本身轉到了filter中。

// path:/src/envoy/tcp/mixer/filter.h
// Network::ReadFilter
Network::FilterStatus onData(Buffer::Instance &data, bool) override;

// Network::WriteFilter
Network::FilterStatus onWrite(Buffer::Instance &data, bool) override;

然後network filter再轉到Envoy 的 http filter,根據官方文檔HTTP filters的介紹也有兩種Filters,爲Decoder和Encoder,然後在istio-proxy源碼中實現了 Http::StreamDecoderFilter和Http::StreamEncoderFilter這兩個Filter,這樣的話整個流程就串起來了。

 

// path:/src/envoy/
//Http::StreamDecoderFilter
FilterHeadersStatus decodeHeaders(HeaderMap& headers, bool) override;
FilterDataStatus decodeData(Buffer::Instance& data, bool end_stream) override;

// Http::StreamEncoderFilter
FilterHeadersStatus encode100ContinueHeaders(HeaderMap&) override {
  return FilterHeadersStatus::Continue;
}
FilterHeadersStatus encodeHeaders(HeaderMap& headers, bool) override;
FilterDataStatus encodeData(Buffer::Instance&, bool) override {
  return FilterDataStatus::Continue;
}

Mixer client

原有mixer client倉庫是獨立的,現在已經整合到了istio-proxy的代碼倉庫中,這樣就可以很方便的在Sidecar Envoy代理中實現:

  • 和mixer server交互
  • 添加一級緩存
  • 前置檢查
  • 後置批量上報
  • 策略控制
  • 屬性、字段轉換和傳遞

對此Istio-proxy的理解,這個Envoy的擴展,就是在Envoy基礎上,增加了一些Filter,然後通過這些個Filter在利用Mixer Client和 Mixer Server進行通信,這樣就可以在proxy代理中:

  • 流量代理
  • 策略控制
  • 遙測代理

Envoy詳解

Envoy是一個高性能的C++寫的proxy轉發器,那Envoy如何轉發請求呢?需要定一些規則,然後按照這些規則進行轉發。

規則可以是靜態的,放在配置文件中的,啓動的時候加載,要想重新加載,一般需要重新啓動,但是Envoy支持熱加載和熱重啓,一定程度上緩解了這個問題。

當然最好的方式是規則設置爲動態的,放在統一的地方維護,這個統一的地方在Envoy眼中看來稱爲Discovery Service,過一段時間去這裏拿一下配置,就修改了轉發策略。

無論是靜態的,還是動態的,在配置裏面往往會配置四個東西。

  • listener,也即envoy既然是proxy,專門做轉發,就得監聽一個端口,接入請求,然後才能夠根據策略轉發,這個監聽的端口稱爲listener
  • endpoint,是目標的ip地址和端口,這個是proxy最終將請求轉發到的地方。
  • cluster,一個cluster是具有完全相同行爲的多個endpoint,也即如果有三個容器在運行,就會有三個IP和端口,但是部署的是完全相同的三個服務,他們組成一個Cluster,從cluster到endpoint的過程稱爲負載均衡,可以輪詢等。
  • route,有時候多個cluster具有類似的功能,但是是不同的版本號,可以通過route規則,選擇將請求路由到某一個版本號,也即某一個cluster。
  • 靜態配置表

 

Envoy啓動過程解析

下載Envoy源碼,啓動入口函數在source/exe/main.cc

std::unique_ptr<Envoy::MainCommon> main_common;
// 聲明並初始化Envoy::MainCommon實例爲main_common
try {
  main_common = std::make_unique<Envoy::MainCommon>(argc, argv);
} catch (const Envoy::NoServingException& e) {
  return EXIT_SUCCESS;
} catch (const Envoy::MalformedArgvException& e) {
  std::cerr << e.what() << std::endl;
  return EXIT_FAILURE;
} catch (const Envoy::EnvoyException& e) {
  std::cerr << e.what() << std::endl;
  return EXIT_FAILURE;
}
// 聲明並初始化Envoy::MainCommon實例爲main_common,執行main_common->run啓動Server
return main_common->run() ? EXIT_SUCCESS : EXIT_FAILURE;

MainCommon關鍵代碼

class MainCommon {
public:
  MainCommon(int argc, const char* const* argv);
  // 啓動函數
  bool run() { return base_.run(); }
  .............
  Server::Instance* server() { return base_.server(); }
  MainCommonBase base_;
};

MainCommonBase.cc關鍵代碼

 

// main_common.cc
int main_common(OptionsImpl& options) {
  try {
      // 生成maincommonbase,在裏面會做server instance的初始化
    MainCommonBase main_common(options);
    return main_common.run() ? EXIT_SUCCESS : EXIT_FAILURE;
  } catch (EnvoyException& e) {
    return EXIT_FAILURE;
  }
  return EXIT_SUCCESS;
}

MainCommonBase::MainCommonBase(OptionsImpl& options) : options_(options) {
    ......
    // 可以看到,MainCommon將會初始化Instance,即一個服務的實例,於是,InstanceImpl進行初始化
    server_.reset(new Server::InstanceImpl(
        options_, local_address, default_test_hooks_, *restarter_, *stats_store_, access_log_lock,
        component_factory_, std::make_unique<Runtime::RandomGeneratorImpl>(), *tls_));
    ......
}

Instance會啓動初始化,在初始化核心函數中,將會進行listenerConfig的全面註冊

 

// server.cc
InstanceImpl::InstanceImpl(Options& options, Network::Address::InstanceConstSharedPtr local_address,
                           TestHooks& hooks, HotRestart& restarter, Stats::StoreRoot& store,
                           Thread::BasicLockable& access_log_lock,
                           ComponentFactory& component_factory,
                           Runtime::RandomGeneratorPtr&& random_generator,
                           ThreadLocal::Instance& tls) {
    ......  
    initialize(options, local_address, component_factory);
    ......
}

void InstanceImpl::initialize(Options& options,
                              Network::Address::InstanceConstSharedPtr local_address,
                              ComponentFactory& component_factory) {
    ...
    // Handle configuration that needs to take place prior to the main configuration load.
    InstanceUtil::loadBootstrapConfig(bootstrap_, options,
                 messageValidationContext().staticValidationVisitor(), *api_);
    .......
    // 初始化ListenerManager
    listener_manager_.reset(new ListenerManagerImpl(
      *this, listener_component_factory_, worker_factory_, ProdSystemTimeSource::instance_));

    // 會初始化
    main_config->initialize(bootstrap_, *this, *cluster_manager_factory_);

    ...
}
// 通過loadFromFile和loadFromYaml讀取配置文件路徑下的配置,並完成參數校驗。
InstanceUtil::loadBootstrapConfig(envoy::config::bootstrap::v2::Bootstrap& bootstrap,
                                  Options& options) {
  try {
      // 根據配置信息查找對應配置文件
    if (!options.configPath().empty()) {
      MessageUtil::loadFromFile(options.configPath(), bootstrap);
    }
    if (!options.configYaml().empty()) {
      envoy::config::bootstrap::v2::Bootstrap bootstrap_override;
      MessageUtil::loadFromYaml(options.configYaml(), bootstrap_override);
      bootstrap.MergeFrom(bootstrap_override);
    }
    MessageUtil::validate(bootstrap);
    return BootstrapVersion::V2;
  } catch (const EnvoyException& e) {
    if (options.v2ConfigOnly()) {
      throw;
    }
    // TODO(htuch): When v1 is deprecated, make this a warning encouraging config upgrade.
    ENVOY_LOG(debug, "Unable to initialize config as v2, will retry as v1: {}", e.what());
  }
  if (!options.configYaml().empty()) {
    throw EnvoyException("V1 config (detected) with --config-yaml is not supported");
  }
  Json::ObjectSharedPtr config_json = Json::Factory::loadFromFile(options.configPath());
  Config::BootstrapJson::translateBootstrap(*config_json, bootstrap);
  MessageUtil::validate(bootstrap);
  return BootstrapVersion::V1;
}

void MainImpl::initialize(const envoy::config::bootstrap::v2::Bootstrap& bootstrap,
                          Instance& server,
                          Upstream::ClusterManagerFactory& cluster_manager_factory) {
  ......
  const auto& listeners = bootstrap.static_resources().listeners();
  ENVOY_LOG(info, "loading {} listener(s)", listeners.size());
  // 從bootstrap配置(yaml文件)中提取listener配置,並依次進行添加操作。
  for (ssize_t i = 0; i < listeners.size(); i++) {
    ENVOY_LOG(debug, "listener #{}:", i);
    server.listenerManager().addOrUpdateListener(listeners[i], "", false);
  }
  ...... 

回到MainCommonBase.run方法

bool MainCommonBase::run() {
  switch (options_.mode()) {
  case Server::Mode::Serve:
    server_->run();
    return true;
  case Server::Mode::Validate: {
    auto local_address = Network::Utility::getLocalAddress(options_.localAddressIpVersion());
    return Server::validateConfig(options_, local_address, component_factory_, thread_factory_,
                                  file_system_);
  }
  case Server::Mode::InitOnly:
    PERF_DUMP();
    return true;
  }
  NOT_REACHED_GCOVR_EXCL_LINE;
}

調用到InstanceImpl的run方法

 

void InstanceImpl::run() {
  // startWorker即會進行eventloop
  RunHelper helper(*dispatcher_, clusterManager(), restarter_, access_log_manager_, init_manager_,
                   [this]() -> void { startWorkers(); });
  ......
}

void InstanceImpl::startWorkers() {
  listener_manager_->startWorkers(*guard_dog_);
  ......
}

InstanceImpl::startWorkers()方法解析

void ListenerManagerImpl::startWorkers(GuardDog& guard_dog) {
  ENVOY_LOG(info, "all dependencies initialized. starting workers");
  ASSERT(!workers_started_);
  workers_started_ = true;
  for (const auto& worker : workers_) {
    ASSERT(warming_listeners_.empty());
    for (const auto& listener : active_listeners_) {
      // 此處即會將所有listener綁定到所有worker身上。worker即服務的併發線程數。
      addListenerToWorker(*worker, *listener);
    }
    worker->start(guard_dog);
  }
}

void WorkerImpl::addListener(Network::ListenerConfig& listener, AddListenerCompletion completion) {
   ......
   handler_->addListener(listener);
   ......
}

進一步看addListener()方法

 

void ConnectionHandlerImpl::addListener(Network::ListenerConfig& config) {
  // 生成ActiveListener
  ActiveListenerPtr l(new ActiveListener(*this, config));
  listeners_.emplace_back(config.socket().localAddress(), std::move(l));
}

ConnectionHandlerImpl::ActiveListener::ActiveListener(ConnectionHandlerImpl& parent,
                                                      Network::ListenerConfig& config)
    : ActiveListener(
          parent,
          // 可以看到,在ActiveListener初始化過程中,將進行真正Listener的初始化。
          parent.dispatcher_.createListener(config.socket(), *this, config.bindToPort(),
                                            config.handOffRestoredDestinationConnections()),
          config) {}

ListenerImpl::ListenerImpl(Event::DispatcherImpl& dispatcher, Socket& socket, ListenerCallbacks& cb,
                           bool bind_to_port, bool hand_off_restored_destination_connections)
    : local_address_(nullptr), cb_(cb),
      hand_off_restored_destination_connections_(hand_off_restored_destination_connections),
    ......
    // 通過libevent的`evconnlistener_new`實現對指定監聽fd的新連接事件的回調處理。
    listener_.reset(
        evconnlistener_new(&dispatcher.base(), listenCallback, this, 0, -1, socket.fd()));
    ......
}
Listener對新連接設置回調函數 & Listener Filter創建
void ListenerImpl::listenCallback(evconnlistener*, evutil_socket_t fd, sockaddr* remote_addr,
                                  int remote_addr_len, void* arg) {
  ......
  // 此處的fd已經不是listenfd,已經是該新連接的connfd。
  listener->cb_.onAccept(std::make_unique<AcceptedSocketImpl>(fd, local_address, remote_address),
                         listener->hand_off_restored_destination_connections_);
  ......
}

// 回調時候主要做兩件事情,
// 1. 構建出對應的Listener Accept Filter
// 2. 構建出ServerConnection
void ConnectionHandlerImpl::ActiveListener::onAccept(
    Network::ConnectionSocketPtr&& socket, bool hand_off_restored_destination_connections) {
  ......
  auto active_socket = std::make_unique<ActiveSocket>(*this, std::move(socket),
                                                 hand_off_restored_destination_connections);
  // 構建對應的Filter
  config_.filterChainFactory().createListenerFilterChain(*active_socket);
  active_socket->continueFilterChain(true);
  ......
}

void ConnectionHandlerImpl::ActiveSocket::continueFilterChain(bool success) {
   ......
   // 創建連接
   listener_.newConnection(std::move(socket_));
   ......  
}

我們接着newConnecion方法看

 

void ConnectionHandlerImpl::ActiveListener::newConnection(Network::ConnectionSocketPtr&& socket) {
  ......
  auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket();
  // 創建ServerConnection
  Network::ConnectionPtr new_connection =
      parent_.dispatcher_.createServerConnection(std::move(socket), std::move(transport_socket));
  new_connection->setBufferLimits(config_.perConnectionBufferLimitBytes());
  // 創建真正的Read/Write Filter
 const bool empty_filter_chain = !config_.filterChainFactory().createNetworkFilterChain(
      *new_connection, filter_chain->networkFilterFactories());
  ......
}

ConnectionImpl::ConnectionImpl(Event::Dispatcher& dispatcher, ConnectionSocketPtr&& socket,
                               TransportSocketPtr&& transport_socket, bool connected)
    : transport_socket_(std::move(transport_socket)), filter_manager_(*this, *this),
      socket_(std::move(socket)), write_buffer_(dispatcher.getWatermarkFactory().create(
                                      [this]() -> void { this->onLowWatermark(); },
                                      [this]() -> void { this->onHighWatermark(); })),
      dispatcher_(dispatcher), id_(next_global_id_++) {
    // 當read/writed生成事件
  file_event_ = dispatcher_.createFileEvent(
      fd(), [this](uint32_t events) -> void { onFileEvent(events); }, Event::FileTriggerType::Edge,
      Event::FileReadyType::Read | Event::FileReadyType::Write);
}

初始化完了連接和Listener,也初始化完了Accept Listener Filter和Read/Write Listener Filter,此時,Listener的libevent事件已經準備就緒,有請求到來後,Connection的read/write事件也將被觸發。此時Worker啓動源碼:

void WorkerImpl::start(GuardDog& guard_dog) {
  ASSERT(!thread_);
  thread_.reset(new Thread::Thread([this, &guard_dog]() -> void { threadRoutine(guard_dog); }));
}

void WorkerImpl::threadRoutine(GuardDog& guard_dog) {
  ......
  dispatcher_->run(Event::Dispatcher::RunType::Block);

  // 異常退出後做的清理操作
  guard_dog.stopWatching(watchdog);
  handler_.reset();
  tls_.shutdownThread();
  watchdog.reset();
}

void DispatcherImpl::run(RunType type) {
  // 啓動libevent處理
  event_base_loop(base_.get(), type == RunType::NonBlock ? EVLOOP_NONBLOCK : 0);
}

總結

     對於istio-proxy的相關概念,envoy的啓動部分源碼做了學習,對於啓動後的動態過程,後續學習發佈。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章