内容简介:本文分析基于 Istio 1.1 版本,但是日志或者流程是基于 1.0.5 版本。pilot 的代码仓库位于
本文分析基于 Istio 1.1 版本,但是日志或者流程是基于 1.0.5 版本。
整体架构
pilot 的代码仓库位于 pilot repo ,当前主要实现了 3 个命令:
- pilot-agent 充当 Proxy 节点上与 API-Server 和 proxy 的桥梁,负责生成 envoy 初始配置文件和管理envoy 生命周期;
- pilot-discovery 为 proxy 提供集群地址服务发现服务;
- sidecar-injector 自动注入提供的 Webhook 服务;
上图为旧版本的结构图,与新版本可能有差异,但是整体架构类似
在 istio-proxy 容器 pilot-agent 启动的命令行参数如下:
$ ps -ef -www istio-p+ 1 0 0 Jan14 ? 00:01:00 /usr/local/bin/pilot-agent proxy sidecar --configPath /etc/istio/proxy --binaryPath /usr/local/bin/envoy --serviceCluster helloworld --drainDuration 45s --parentShutdownDuration 1m0s --discoveryAddress istio-pilot.istio-system:15007 --discoveryRefreshDelay 1s --zipkinAddress zipkin.istio-system:9411 --connectTimeout 10s --proxyAdminPort 15000 --controlPlaneAuthPolicy NONE istio-p+ 608 1 0 Jan15 ? 03:31:24 /usr/local/bin/envoy -c /etc/istio/proxy/envoy-rev13.json --restart-epoch 13 --drain-time-s 45 --parent-shutdown-time-s 60 --service-cluster helloworld --service-node sidecar~10.128.5.4~helloworld-v1-8f8dd85-cfz42.default~default.svc.cluster.local --max-obj-name-len 189 --allow-unknown-fields -l warn --v2-config-only
其中 envoy 进程为 pilot-agent 的子进程,配置文件的初始化和启动监护由 pilot-agent 来管理。
pilot-agent 命令行
# /usr/local/bin/pilot-agent --help
Istio Pilot agent runs in the side car or gateway container and bootstraps envoy.
Usage:
pilot-agent [command]
Available Commands:
help Help about any command
proxy Envoy proxy agent
request Makes an HTTP request to the Envoy admin API
version Prints out build version information
Flags:
-h, --help help for pilot-agent
--log_as_json Whether to format output as JSON or in plain console-friendly format
--log_caller string Comma-separated list of scopes for which to include caller information, scopes can be any of [default, model]
--log_output_level string Comma-separated minimum per-scope logging level of messages to output, in the form of <scope>:<level>,<scope>:<level>,... where scope can be one of [default, model] and level can be one of [debug, info, warn, error, none] (default "default:info")
--log_rotate string The path for the optional rotating log file
--log_rotate_max_age int The maximum age in days of a log file beyond which the file is rotated (0 indicates no limit) (default 30)
--log_rotate_max_backups int The maximum number of log file backups to keep before older files are deleted (0 indicates no limit) (default 1000)
--log_rotate_max_size int The maximum size in megabytes of a log file beyond which the file is rotated (default 104857600)
--log_stacktrace_level string Comma-separated minimum per-scope logging level at which stack traces are captured, in the form of <scope>:<level>,<scope:level>,... where scope can be one of [default, model] and level can be one of [debug, info, warn, error, none] (default "default:none")
--log_target stringArray The set of paths where to output the log. This can be any path as well as the special values stdout and stderr (default [stdout])
Use "pilot-agent [command] --help" for more information about a command.
关于子命令 proxy 的帮助内容如下:(省略了公共的参数)
$ /usr/local/bin/pilot-agent proxy --help
Envoy proxy agent
Usage:
pilot-agent proxy [flags]
Flags:
--applicationPorts stringSlice Ports exposed by the application. Used to determine that Envoy is configured and ready to receive traffic.
--availabilityZone string Availability zone
--binaryPath string Path to the proxy binary (default "/usr/local/bin/envoy")
--bootstrapv2 Use bootstrap v2 - DEPRECATED (default true)
--concurrency int number of worker threads to run
--configPath string Path to the generated configuration file directory (default "/etc/istio/proxy")
--connectTimeout duration Connection timeout used by Envoy for supporting services (default 1s)
--controlPlaneAuthPolicy string Control Plane Authentication Policy (default "NONE")
--customConfigFile string Path to the custom configuration file
--disableInternalTelemetry Disable internal telemetry
--discoveryAddress string Address of the discovery service exposing xDS (e.g. istio-pilot:8080) (default "istio-pilot:15007")
--discoveryRefreshDelay duration Polling interval for service discovery (used by EDS, CDS, LDS, but not RDS) (default 1s)
--domain string DNS domain suffix. If not provided uses ${POD_NAMESPACE}.svc.cluster.local
--drainDuration duration The time in seconds that Envoy will drain connections during a hot restart (default 2s)
-h, --help help for proxy
--id string Proxy unique ID. If not provided uses ${POD_NAME}.${POD_NAMESPACE} from environment variables
--ip string Proxy IP address. If not provided uses ${INSTANCE_IP} environment variable.
--parentShutdownDuration duration The time in seconds that Envoy will wait before shutting down the parent process during a hot restart (default 3s)
--proxyAdminPort uint16 Port on which Envoy should listen for administrative commands (default 15000)
--proxyLogLevel string The log level used to start the Envoy proxy (choose from {trace, debug, info, warn, err, critical, off}) (default "warn")
--serviceCluster string Service cluster (default "istio-proxy")
--serviceregistry string Select the platform for service registry, options are {Kubernetes, Consul, CloudFoundry, Mock, Config} (default "Kubernetes")
--statsdUdpAddress string IP Address and Port of a statsd UDP listener (e.g. 10.75.241.127:9125)
--statusPort uint16 HTTP Port on which to serve pilot agent status. If zero, agent status will not be provided.
--templateFile string Go template bootstrap config
--zipkinAddress string Address of the Zipkin service (e.g. zipkin:9411)
如果启动方式为 /usr/local/bin/pilot-agent proxy 则后面可以选的启动类型分为:
- pilot-agent proxy sidecar
-
pilot-agent proxy router, 用在 istio-gateway 方式下
# ingressgateway 或者 egressgateway $ ps -ef -www UID PID PPID C STIME TTY TIME CMD root 1 0 0 Jan15 ? 00:00:52 /usr/local/bin/pilot-agent proxy router -v 2 --discoveryRefreshDelay 1s --drainDuration 45s --parentShutdownDuration 1m0s --connectTimeout 10s --serviceCluster istio-ingressgateway --zipkinAddress zipkin:9411 --proxyAdminPort 15000 --controlPlaneAuthPolicy NONE --discoveryAddress istio-pilot:8080 root 64 1 0 Jan15 ? 03:37:59 /usr/local/bin/envoy -c /etc/istio/proxy/envoy-rev1.json --restart-epoch 1 --drain-time-s 45 --parent-shutdown-time-s 60 --service-cluster istio-ingressgateway --service-node router~10.128.45.4~istio-ingressgateway-78c6d8b8d7-sxvpx.istio-system~istio-system.svc.cluster.local --max-obj-name-len 189 --allow-unknown-fields -l warn --v2-config-only
- pilot-agent proxy ingress,仅用于 ingress 模式下
pilot-agent 代码流程分析
pilot-agent 需要监视相关的证书。
- sidecar模式,监视
/etc/certs/目录下的cert-chain.pem/key.pem/root-cert.pem三个文件; - ingress 模式,监控
/etc/istio/ingress-certs/目录下的tls.crt/tls.key两个文件;
在 pilot-agent 启动的初始日志里面,会打印出来当前的配置和监控的证书,如下:
$ # kubectl logs helloworld-v1-8f8dd85-cfz42 -c istio-proxy |more
2019-01-14T06:35:21.726068Z info Version root@6f6ea1061f2b-docker.io/istio-1.0.5-c1707e45e71c75d74bf3a5dec8c7086f32f32fad-Clean
2019-01-14T06:35:21.726179Z info Proxy role: model.Proxy{ClusterID:"", Type:"sidecar", IPAddress:"10.128.5.4", ID:"helloworld-v1-8f8dd8
5-cfz42.default", Domain:"default.svc.cluster.local", Metadata:map[string]string(nil)}
2019-01-14T06:35:21.726865Z info Effective config: binaryPath: /usr/local/bin/envoy
configPath: /etc/istio/proxy
connectTimeout: 10s
discoveryAddress: istio-pilot.istio-system:15007
discoveryRefreshDelay: 1s
drainDuration: 45s
parentShutdownDuration: 60s
proxyAdminPort: 15000
serviceCluster: helloworld
zipkinAddress: zipkin.istio-system:9411
2019-01-14T06:35:21.726902Z info Monitored certs: []envoy.CertSource{envoy.CertSource{Directory:"/etc/certs/", Files:[]string{"cert-cha
in.pem", "key.pem", "root-cert.pem"}}}
2019-01-14T06:35:21.727115Z info Starting proxy agent
2019-01-14T06:35:21.728269Z info Received new config, resetting budget
2019-01-14T06:35:21.728587Z info Reconciling configuration (budget 10)
2019-01-14T06:35:21.728626Z info Epoch 0 starting
2019-01-14T06:35:21.729334Z info Envoy command: [-c /etc/istio/proxy/envoy-rev0.json --restart-epoch 0 --drain-time-s 45 --parent-shutd
own-time-s 60 --service-cluster helloworld --service-node sidecar~10.128.5.4~helloworld-v1-8f8dd85-cfz42.default~default.svc.cluster.local --m
ax-obj-name-len 189 --allow-unknown-fields -l warn --v2-config-only]
本文主要分析 proxy sidecar 方式下的主要逻辑:
主函数入口:
istio.io/istio/pilot/cmd/pilot-agent/main.go
func main() {
if err := rootCmd.Execute(); err != nil {
log.Errora(err)
os.Exit(-1)
}
}
由于使用的命令行为 pilot-agent proxy sidecar ,最终调用的子命令的函数入口
proxyCmd = &cobra.Command{
Use: "proxy",
Short: "Envoy proxy agent",
RunE: func(c *cobra.Command, args []string) error {
// ...
// 用于设置默认配置文件的默认配置相关参数
proxyConfig := model.DefaultProxyConfig()
// set all flags
proxyConfig.CustomConfigFile = customConfigFile
proxyConfig.ConfigPath = configPath
proxyConfig.BinaryPath = binaryPath
proxyConfig.ServiceCluster = serviceCluster
proxyConfig.DrainDuration = types.DurationProto(drainDuration)
proxyConfig.ParentShutdownDuration = types.DurationProto(parentShutdownDuration)
proxyConfig.DiscoveryAddress = discoveryAddress
proxyConfig.ConnectTimeout = types.DurationProto(connectTimeout)
proxyConfig.StatsdUdpAddress = statsdUDPAddress
proxyConfig.ProxyAdminPort = int32(proxyAdminPort)
proxyConfig.Concurrency = int32(concurrency)
// ...
// 1. 启动 status server
// If a status port was provided, start handling status probes.
if statusPort > 0 {
parsedPorts, err := parseApplicationPorts()
if err != nil {
return err
}
statusServer := status.NewServer(status.Config{
AdminPort: proxyAdminPort,
StatusPort: statusPort,
ApplicationPorts: parsedPorts,
})
go statusServer.Run(ctx)
}
// 初始化 envoyProxy 对象
envoyProxy := envoy.NewProxy(proxyConfig, role.ServiceNode(), proxyLogLevel, pilotSAN, role.IPAddresses)
agent := proxy.NewAgent(envoyProxy, proxy.DefaultRetry)
watcher := envoy.NewWatcher(certs, agent.ConfigCh())
// 2. 启动 agent
go agent.Run(ctx)
// 3. 启动 watcher
go watcher.Run(ctx)
// 4. 主 goroutine 等待信号量
stop := make(chan struct{})
cmd.WaitSignal(stop)
<-stop
return nil
}
}
status server
如果 statusPort 端口进行了设置,则会启动 statusServer。
- 对于 ready 检查,调用的路径为
/healthz/ready, 并配合设置的端口applicationPorts通过 envoy 的 admin 端口进行对应的端口进行检查,用于决定 envoy 是否已经 ready 接受相对应的流量。
--statusPort uint16 HTTP Port on which to serve pilot agent status. If zero, agent status will not be provided. --applicationPorts stringSlice Ports exposed by the application. Used to determine that Envoy is configured and ready to receive traffic.
检查原理是通过本地管理端口,如 http://127.0.0.1:15000/listeners 获取 envoy 当前监听的全部端口,然后将配置的端口 applicationPorts 在监听的端口中进行查找,来决定 envoy 是否 ready。
- 应用端口检查
检查的路径为
/url路径,在 header 中设置istio-app-probe-port端口,使用 访问路径中的url来进行检查,最终调用的是http://127.0.0.1:istio-app-probe-port/url,头部设置的全部参数也都会传递到别检测的服务端口上;
agent
agent 的函数入口代码位于 istio.io/istio/pilot/pkg/proxy/agent.go 文件中。
interface 定义:
type Agent interface {
// ConfigCh returns the config channel used to send configuration updates.
// Agent compares the current active configuration to the desired state and
// initiates a restart if necessary. If the restart fails, the agent attempts
// to retry with an exponential back-off.
ConfigCh() chan<- interface{}
// Run starts the agent control loop and awaits for a signal on the input
// channel to exit the loop.
Run(ctx context.Context)
}
agent 结构定义为:
type agent struct {
// proxy commands
proxy Proxy
// 记录 envoy 重启的各种参数,包括 time、budget、MaxRetries 和 InitialInterval 间隔
// 对于异常退出的进程,一般来说抢救 10 次,中间使用退步算法,如果 10 次仍然不好,
// 则会退出 proxy 容器,启动新的容器
// retry configuration
retry Retry
// 期望使用的配置文件,当前为对应证书的 sha256 的值
// desired configuration state
desiredConfig interface{}
// 用来保存全部对应的 epoch 对应的证书 sha256 的值
// active epochs and their configurations
epochs map[int]interface{}
// 当前使用的配置文件,为对应证书的 sha256 的值
// current configuration is the highest epoch configuration
currentConfig interface{}
// 读取从 watcher 监护到证书变化的 channel
// channel for posting desired configurations
configCh chan interface{}
// 用于监护管理 Envoy 的 channel
// channel for proxy exit notifications
statusCh chan exitStatus
// 记录 epoch 对应的 abortCh channel,当前最大为10个,最大允许10个正在重启中的 proxy
// channel for aborting running instances
abortCh map[int]chan error
}
agent 接口体中外部的控制主要是通过 channel 来实现的:
- configCh 用于接受到是否有配置文件发生变化,当前主要是有 watcher goroutine 来监视相关的证书,如果证书发生了变化或者定时(当前为10s),configCh 就会节后到 watcher 发送的 sha256 摘要值;
-
statusCh 用于管理启动 envoy 后的状态通道,用于监视 envoy 进程的状态;
-
proxy 对象则是实现了对于 envoy 管理的主要工作,在 proxyCmd 的函数中初始化:
envoyProxy := envoy.NewProxy(proxyConfig, role.ServiceNode(), proxyLogLevel, pilotSAN, role.IPAddresses)
其中 Proxy 为接口定义如下:
// Proxy defines command interface for a proxy
type Proxy interface {
// Run command for a config, epoch, and abort channel
Run(interface{}, int, <-chan error) error
// Cleanup command for an epoch
Cleanup(int)
// Panic command is invoked with the desired config when all retries to
// start the proxy fail just before the agent terminating
Panic(interface{})
}
agent 的主入口函数为:
istio.io/istio/pilot/pkg/proxy/agent.go
func (a *agent) Run(ctx context.Context) {
log.Info("Starting proxy agent")
// Throttle processing up to smoothed 1 qps with bursts up to 10 qps.
// High QPS is needed to process messages on all channels.
rateLimiter := rate.NewLimiter(1, 10)
var reconcileTimer *time.Timer
for {
err := rateLimiter.Wait(ctx)
if err != nil {
a.terminate()
return
}
// maximum duration or duration till next restart
var delay time.Duration = 1<<63 - 1
if a.retry.restart != nil {
// 如果设置了下次重启的时间间隔,则 delay 设置为该值
delay = time.Until(*a.retry.restart)
}
// 停止原有的 reconcileTimer, 并设置成当前的 delay 值
if reconcileTimer != nil {
reconcileTimer.Stop()
}
reconcileTimer = time.NewTimer(delay)
select {
// 1. 如果相关的配置发生了变化,如果没有变化则忽略
case config := <-a.configCh:
if !reflect.DeepEqual(a.desiredConfig, config) {
log.Infof("Received new config, resetting budget")
a.desiredConfig = config
// reset retry budget if and only if the desired config changes
// 因为配置发生了变化,把下一次重启时间间隔设置为最大
a.retry.budget = a.retry.MaxRetries
a.reconcile()
}
// 默认的重试策略值为
/*
DefaultRetry = Retry{
MaxRetries: 10,
InitialInterval: 200 * time.Millisecond,
}*/
// 2. 如果 proxy-envoy 的状态发生了变化
case status := <-a.statusCh:
// delete epoch record and update current config
// avoid self-aborting on non-abort error
delete(a.epochs, status.epoch)
delete(a.abortCh, status.epoch)
a.currentConfig = a.epochs[a.latestEpoch()]
// errAbort 为被正常取消情况下的退出,比如 <-ctx.Done() 情况下调用 a.terminate()
if status.err == errAbort {
log.Infof("Epoch %d aborted", status.epoch)
} else if status.err != nil { // 异常情况下的退出
log.Warnf("Epoch %d terminated with an error: %v", status.epoch, status.err)
// NOTE: due to Envoy hot restart race conditions, an error from the
// process requires aggressive non-graceful restarts by killing all
// existing proxy instances
// Envoy热重启竞争条件,进程中的错误需要通过终止所有现有代理实例来进行积极的非正常重启
a.abortAll()
} else {
// 正常情况下的退出
log.Infof("Epoch %d exited normally", status.epoch)
}
// cleanup for the epoch
// 删除当前 epoch 对应的配置文件
a.proxy.Cleanup(status.epoch)
// 设置出错后的重试,由于 proxy 可能已中止,因此当前配置可能已过期。当前配置
// 将在中止时更改,因此在中止之前重试将不会进行。
// 如果重新启动的计划尚未安排,需要重新安排相关重启。
if status.err != nil {
// skip retrying twice by checking retry restart delay
// a.retry.restart nil 表示还未安排相关的重启进程
if a.retry.restart == nil {
if a.retry.budget > 0 {
delayDuration := a.retry.InitialInterval * (1 << uint(a.retry.MaxRetries-a.retry.budget))
restart := time.Now().Add(delayDuration)
a.retry.restart = &restart
a.retry.budget = a.retry.budget - 1
log.Infof("Epoch %d: set retry delay to %v, budget to %d", status.epoch, delayDuration, a.retry.budget)
} else {
// 耗费了所有的重启次数尝试,仍然不能正常启动,退出容器
log.Error("Permanent error: budget exhausted trying to fulfill the desired configuration")
a.proxy.Panic(status.epoch)
return
}
} else { // 重启已经安排过了
log.Debugf("Epoch %d: restart already scheduled", status.epoch)
}
}
// 3. reconcileTimer 时间到了
case <-reconcileTimer.C:
a.reconcile()
case _, more := <-ctx.Done():
if !more { // 表明被关闭了
a.terminate()
return
}
}
}
}
简化一下为:
for {
// 根据当前的重启策略设置 reconcileTimer 定时器的时间
select {
// 接收到的配置如果和当前使用的而配置不相同,则调用, a.reconcile(); 相同则忽略
case config := <-a.configCh:
a.reconcile()
// 检测各种错误值,如果是特定的 errAbort 退出或者 根据退出的各种参数检查判断是预期安排的重启
// 还是异常退出;同时根据重启的策略设置相关的重启策略,在后续的循环中设置 reconcileTimer 时间
case status := <-a.statusCh:
// 非预期错误的错误处理
// 如果是重启策略失效了,则直接退出当前循环
// 特定的重启策略内,设置下次重启的时间
// 特定的重启策略内,设置下次重启的时间
// 设置的重启时间到达
case <-reconcileTimer.C:
a.reconcile()
// 如果是取消,则全部退出
case _, more := <-ctx.Done():
if !more {
a.terminate()
return
}
在配置发生变化或者异常重启设置重启策略后,最终调用的函数为 reconcile :
func (a *agent) reconcile() {
// cancel any scheduled restart
a.retry.restart = nil
log.Infof("Reconciling retry (budget %d)", a.retry.budget)
// check that the config is current
if reflect.DeepEqual(a.desiredConfig, a.currentConfig) {
log.Infof("Desired configuration is already applied")
return
}
// discover and increment the latest running epoch
epoch := a.latestEpoch() + 1
// buffer aborts to prevent blocking on failing proxy
abortCh := make(chan error, maxAborts)
a.epochs[epoch] = a.desiredConfig
a.abortCh[epoch] = abortCh
a.currentConfig = a.desiredConfig
// 最终的调用,会将相关相关结果放到 abortCh channel 中
go a.runWait(a.desiredConfig, epoch, abortCh)
}
reconcile 设置相关参数后,最终启动一个新的 goroutine 来进行启动最终的程序
// runWait runs the start-up command as a go routine and waits for it to finish
func (a *agent) runWait(config interface{}, epoch int, abortCh <-chan error) {
log.Infof("Epoch %d starting", epoch)
err := a.proxy.Run(config, epoch, abortCh)
a.statusCh <- exitStatus{epoch: epoch, err: err}
}
在 runWait 函数中,最终调用 a.proxy.Run(config, epoch, abortCh) ,并将其返回的错误值放到 agent 的 statusCh channel 中。
对于 envoy 的启动过程可以通过 proxy.run 来进行总结分析:
envoy 的结构体定义如下:
type envoy struct {
config meshconfig.ProxyConfig // 配置文件
node string
extraArgs []string
pilotSAN []string
opts map[string]interface{}
errChan chan error
nodeIPs []string
}
istio.io/istio/pilot/pkg/proxy/envoy/proxy.go
func (e *envoy) Run(config interface{}, epoch int, abort <-chan error) error {
var fname string
// Note: the cert checking still works, the generated file is updated if certs are changed.
// We just don't save the generated file, but use a custom one instead. Pilot will keep
// monitoring the certs and restart if the content of the certs changes.
// 1. 如果指定了模板文件,则使用用户指定的,否则则使用默认的
if len(e.config.CustomConfigFile) > 0 {
// there is a custom configuration. Don't write our own config - but keep watching the certs.
fname = e.config.CustomConfigFile
} else {
out, err := bootstrap.WriteBootstrap(&e.config, e.node, epoch, e.pilotSAN, e.opts, os.Environ(), e.nodeIPs)
if err != nil {
log.Errora("Failed to generate bootstrap config", err)
os.Exit(1) // Prevent infinite loop attempting to write the file, let k8s/systemd report
return err
}
fname = out
}
// spin up a new Envoy process
args := e.args(fname, epoch)
log.Infof("Envoy command: %v", args)
/* #nosec */
cmd := exec.Command(e.config.BinaryPath, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
return err
}
// Set if the caller is monitoring envoy, for example in tests or if envoy runs in same
// container with the app.
if e.errChan != nil {
// Caller passed a channel, will wait itself for termination
go func() {
e.errChan <- cmd.Wait()
}()
return nil
}
// 通过 done channel 来获取 evnoy 启动的最终状态
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
// 等待 abort channel 和 done,用于结束 envoy 和正确返回当前的启动状态
select {
case err := <-abort:
log.Warnf("Aborting epoch %d", epoch)
if errKill := cmd.Process.Kill(); errKill != nil {
log.Warnf("killing epoch %d caused an error %v", epoch, errKill)
}
return err
case err := <-done:
return err
}
}
函数 bootstrap.WriteBootstrap 用来生成 envoy 使用的初始配置文件,默认的配置模板文件路径为 /var/lib/istio/envoy/envoy_bootstrap_tmpl.json ,该文件也可以通过参数 templateFile 传递进来。
// istio.io/istio/pkg/bootstrap/bootstrap_config.go DefaultCfgDir = "/var/lib/istio/envoy/envoy_bootstrap_tmpl.json"
整体内容参见 file-envoy_bootstrap_tmpl-json
envoy_bootstrap_tmpl.json + meshconfig.ProxyConfig + epoch = envoy 初始化文件,文件名格式为 ”envoy-rev%d.json“, 其中 %d 会被替换成 epoch 的值。
在 envoy 默认配置输出成功以后,就接着构造 envoy 启动的参数,主要函数如下:
func (e *envoy) args(fname string, epoch int) []string {
startupArgs := []string{"-c", fname,
"--restart-epoch", fmt.Sprint(epoch),
"--drain-time-s", fmt.Sprint(int(convertDuration(e.config.DrainDuration) / time.Second)),
"--parent-shutdown-time-s", fmt.Sprint(int(convertDuration(e.config.ParentShutdownDuration) / time.Second)),
"--service-cluster", e.config.ServiceCluster,
"--service-node", e.node,
"--max-obj-name-len", fmt.Sprint(e.config.StatNameLength),
"--allow-unknown-fields",
}
startupArgs = append(startupArgs, e.extraArgs...)
if e.config.Concurrency > 0 {
startupArgs = append(startupArgs, "--concurrency", fmt.Sprint(e.config.Concurrency))
}
return startupArgs
}
到此为止,envoy 的配置文件和启动命令行已经构造完成,后续就需要采用 exec.Command 命令来进行启动。
至此 agent 对于 envoy 的启动和管理流程结束。
watcher
watcher 的整体逻辑相对比较简单,就是 watch 相关的证书变化,当证书有变化或者定期(10s),向 agent 发送配置当前的 sha256 摘要,agent 如果发现配置已经变化,则会触发 agent 重启 envoy,并增加 epoch 的值。
istio.io/istio/pilot/pkg/proxy/envoy/watcher.go
func (w *watcher) Run(ctx context.Context) {
// kick start the proxy with partial state (in case there are no notifications coming)
w.SendConfig() // 用于向 agent 发送相关的摘要值
// monitor certificates
certDirs := make([]string, 0, len(w.certs))
for _, cert := range w.certs {
certDirs = append(certDirs, cert.Directory)
}
go watchCerts(ctx, certDirs, watchFileEvents, defaultMinDelay, w.SendConfig)
<-ctx.Done()
}
watchCerts 的函数主体:
// watchCerts watches all certificate directories and calls the provided
// `updateFunc` method when changes are detected. This method is blocking
// so it should be run as a goroutine.
// updateFunc will not be called more than one time per minDelay.
func watchCerts(ctx context.Context, certsDirs []string, watchFileEventsFn watchFileEventsFn,
minDelay time.Duration, updateFunc func()) {
fw, err := fsnotify.NewWatcher()
if err != nil {
log.Warnf("failed to create a watcher for certificate files: %v", err)
return
}
defer func() {
if err := fw.Close(); err != nil {
log.Warnf("closing watcher encounters an error %v", err)
}
}()
// watch all directories
for _, d := range certsDirs {
if err := fw.Watch(d); err != nil {
log.Warnf("watching %s encounters an error %v", d, err)
return
}
}
watchFileEventsFn(ctx, fw.Event, minDelay, updateFunc)
}
关于监听的相关证书,则来自于注入时从命名空间中 secret 中加载到 pod 中的文件,deployment文件如下:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
creationTimestamp: null
name: helloworld-v2
spec:
replicas: 1
strategy: {}
template:
metadata:
labels:
app: helloworld
version: v2
spec:
containers:
- image: istio/examples-helloworld-v2
imagePullPolicy: IfNotPresent
name: helloworld
ports:
- containerPort: 5000
resources:
requests:
cpu: 100m
- args:
- proxy
- sidecar
- --configPath
- /etc/istio/proxy
- --binaryPath
- /usr/local/bin/envoy
- --serviceCluster
- helloworld
- --drainDuration
- 45s
- --parentShutdownDuration
- 1m0s
- --discoveryAddress
- istio-pilot.istio-system:15007
- --discoveryRefreshDelay
- 1s
- --zipkinAddress
- zipkin.istio-system:9411
- --connectTimeout
- 10s
- --proxyAdminPort
- "15000"
- --controlPlaneAuthPolicy
- NONE
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: INSTANCE_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: ISTIO_META_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: ISTIO_META_INTERCEPTION_MODE
value: REDIRECT
- name: ISTIO_METAJSON_LABELS
value: |
{"app":"helloworld","version":"v2"}
image: docker.io/istio/proxyv2:1.0.5
imagePullPolicy: IfNotPresent
name: istio-proxy
ports:
- containerPort: 15090
name: http-envoy-prom
protocol: TCP
resources:
requests:
cpu: 10m
securityContext:
readOnlyRootFilesystem: true
runAsUser: 1337
volumeMounts:
- mountPath: /etc/istio/proxy
name: istio-envoy
- mountPath: /etc/certs/ # 将证书挂载的目录, watcher 会监视该目录
name: istio-certs
readOnly: true
initContainers:
- args:
- -p
- "15001"
- -u
- "1337"
- -m
- REDIRECT
- -i
- '*'
- -x
- ""
- -b
- "5000"
- -d
- ""
image: docker.io/istio/proxy_init:1.0.5
imagePullPolicy: IfNotPresent
name: istio-init
resources: {}
securityContext:
capabilities:
add:
- NET_ADMIN
privileged: true
volumes:
- emptyDir:
medium: Memory
name: istio-envoy
- name: istio-certs # 来自于 istio.default 的 secret
secret:
optional: true
secretName: istio.default
使用 kubectl 命令验证:
# kubectl get secrets istio.default
NAME TYPE DATA AGE
istio.default istio.io/key-and-cert 3 17d
# kubectl get secrets istio.default -o yaml
apiVersion: v1
data:
cert-chain.pem: xxxx
key.pem: xxxx==
root-cert.pem: xxxx==
kind: Secret
metadata:
annotations:
istio.io/service-account.name: default
creationTimestamp: 2019-01-15T08:24:26Z
name: istio.default
namespace: default
resourceVersion: "16685160"
selfLink: /api/v1/namespaces/default/secrets/istio.default
uid: f863ce6f-189e-11e9-ab53-00163e0c1552
type: istio.io/key-and-cert
证书的生成和管理是由 Citadel 负责的,具体细节可以参见 Keys and Certificates ,可以使用以下命令来检查证书的详细信息
# jq 为命令下处理 json 的工具,参见
# https://www.ibm.com/developerworks/cn/linux/1612_chengg_jq/index.html
$ sudo yum install -y jq
$ kubectl get secret -o json istio.default | jq -r '.data["cert-chain.pem"]' | base64 --decode | openssl x509 -noout -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
07:d1:25:13:1c:38:db:ef:89:8e:95:2e:d1:6c:b5:4d
Signature Algorithm: sha256WithRSAEncryption
Issuer: O=k8s.cluster.local
Validity
Not Before: Jan 15 08:24:26 2019 GMT
Not After : Apr 15 08:24:26 2019 GMT
Subject: O=
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:d1:13:34:58:e7:b0:6e:b5:07:0e:bd:7f:d5:a0:
66:d9:4a:2a:6d:ec:bd:26:ab:22:26:31:c7:c9:48:
da:57:9a:5a:91:b3:7c:78:2c:c4:8d:14:4b:b1:b4:
a4:29:3d:26:d1:ad:8d:6e:6f:b0:27:64:31:93:cf:
43:be:f4:04:0a:d2:0f:e6:dc:45:4a:5d:38:65:c0:
08:44:25:5f:e8:2d:c3:2a:9a:6b:82:bb:27:81:59:
c7:f5:38:66:b1:f2:06:eb:94:46:34:47:c5:b1:9d:
01:59:04:e7:8e:df:bf:ed:17:f8:16:06:9d:85:c0:
e9:43:0f:3a:a0:b6:b9:64:50:3e:e1:26:e3:03:d4:
dc:43:08:ef:de:af:56:5c:d5:6c:c1:72:72:7b:f5:
05:f8:09:15:08:2d:f3:5b:c7:57:7d:1a:15:72:90:
3e:df:0a:e6:a1:e3:d9:81:9f:bb:9c:f2:c5:da:7f:
48:a6:4d:12:f7:5e:e8:21:99:1d:f0:95:d7:c5:1a:
34:d5:a7:56:79:4b:dd:82:a1:39:cc:d5:0b:e3:fa:
92:04:21:38:89:41:7c:9b:12:aa:c4:5f:93:c1:1d:
fc:bc:5a:6d:e5:3d:98:e1:9c:28:38:58:75:c6:e3:
27:5f:77:4b:10:b6:53:70:7b:25:fd:5c:44:28:67:
54:51
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Subject Alternative Name:
URI:spiffe://cluster.local/ns/default/sa/default
Signature Algorithm: sha256WithRSAEncryption
68:fb:87:12:c6:d1:fb:c1:69:fa:ec:2e:30:ea:f7:4d:8f:9c:
5b:54:a1:f9:a3:5f:ff:83:3f:76:c5:d6:9c:2b:cb:55:6a:66:
49:b5:a2:bd:dd:71:06:7f:a4:f8:18:cd:0d:7a:3f:bf:4c:56:
e0:35:5b:68:2d:71:72:e3:a2:7d:b9:90:f3:86:d0:1a:87:f6:
31:7e:24:db:00:a8:69:df:54:7f:8b:b0:1d:7d:02:03:c0:26:
1a:87:53:aa:e4:66:76:e9:80:1e:28:61:53:0c:53:c6:1e:80:
7e:b0:2d:71:75:1b:42:9f:51:f4:5c:7b:53:ee:06:02:31:5d:
71:2d:b5:4a:dd:58:25:a6:c4:24:ad:19:86:ac:24:87:99:ad:
6b:be:c8:ae:84:7e:7d:86:0a:1d:44:a0:50:62:2d:8b:d6:79:
ff:db:43:40:de:3b:ec:4e:2b:80:87:e5:1a:cb:1e:cf:e6:12:
8e:96:10:46:f7:fa:ed:1c:bb:0b:11:35:41:c8:69:43:64:79:
44:ea:a8:72:b7:27:2a:0d:a6:39:bb:34:b0:8b:e3:86:ba:3c:
1e:b9:ee:b5:61:bb:c8:65:b0:8d:bd:a1:9c:29:64:7d:0b:2c:
f9:9b:34:18:98:38:24:ad:85:b8:1e:59:41:09:1f:2e:a8:6d:
ef:ee:0d:a8
特别是,Subject Alternative Name 字段应为 URI:spiffe://cluster.local/ns/default/sa/default 。
参考
以上所述就是小编给大家介绍的《istio源码 – pilot-agent 源码分析(原创)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 以太坊源码分析(36)ethdb源码分析
- [源码分析] kubelet源码分析(一)之 NewKubeletCommand
- libmodbus源码分析(3)从机(服务端)功能源码分析
- [源码分析] nfs-client-provisioner源码分析
- [源码分析] kubelet源码分析(三)之 Pod的创建
- Spring事务源码分析专题(一)JdbcTemplate使用及源码分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
嵌入式系统软件设计中的常用算法
周航慈 / 2010-1 / 24.00元
《嵌入式系统软件设计中的常用算法》根据嵌入式系统软件设计需要的常用算法知识编写而成。基本内容有:线性方程组求解、代数插值和曲线拟合、数值积分、能谱处理、数字滤波、数理统计、自动控制、数据排序、数据压缩和检错纠错等常用算法。从嵌入式系统的实际应用出发,用通俗易懂的语言代替枯燥难懂的数学推导,使读者能在比较轻松的条件下学到最基本的常用算法,并为继续学习其他算法打下基础。 《嵌入式系统软件设计中的......一起来看看 《嵌入式系统软件设计中的常用算法》 这本书的介绍吧!