blob: 2d839fcf2b7de8123c8976a913bb6d94e8160b66 [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package envoy
import (
"fmt"
"os"
"os/exec"
"strings"
"syscall"
)
import (
"google.golang.org/protobuf/types/known/durationpb"
"istio.io/pkg/env"
"istio.io/pkg/log"
)
import (
"github.com/apache/dubbo-go-pixiu/pilot/pkg/util/network"
)
type envoy struct {
ProxyConfig
extraArgs []string
}
// Envoy binary flags
type ProxyConfig struct {
LogLevel string
ComponentLogLevel string
NodeIPs []string
Sidecar bool
LogAsJSON bool
// TODO: outlier log path configuration belongs to mesh ProxyConfig
OutlierLogPath string
BinaryPath string
ConfigPath string
ConfigCleanup bool
AdminPort int32
DrainDuration *durationpb.Duration
ParentShutdownDuration *durationpb.Duration
Concurrency int32
// For unit testing, in combination with NoEnvoy prevents agent.Run from blocking
TestOnly bool
AgentIsRoot bool
}
// NewProxy creates an instance of the proxy control commands
func NewProxy(cfg ProxyConfig) Proxy {
// inject tracing flag for higher levels
var args []string
logLevel, componentLogs := splitComponentLog(cfg.LogLevel)
if logLevel != "" {
args = append(args, "-l", logLevel)
}
if len(componentLogs) > 0 {
args = append(args, "--component-log-level", strings.Join(componentLogs, ","))
} else if cfg.ComponentLogLevel != "" {
// Use the old setting if we don't set any component log levels in LogLevel
args = append(args, "--component-log-level", cfg.ComponentLogLevel)
}
return &envoy{
ProxyConfig: cfg,
extraArgs: args,
}
}
// splitComponentLog breaks down an argument string into a log level (ie "info") and component log levels (ie "misc:error").
// This allows using a single log level API, with the same semantics as Istio's logging, to configure Envoy which
// has two different settings
func splitComponentLog(level string) (string, []string) {
levels := strings.Split(level, ",")
var logLevel string
var componentLogs []string
for _, sl := range levels {
spl := strings.Split(sl, ":")
if len(spl) == 1 {
logLevel = spl[0]
} else if len(spl) == 2 {
componentLogs = append(componentLogs, sl)
} else {
log.Warnf("dropping invalid log level: %v", sl)
}
}
return logLevel, componentLogs
}
func (e *envoy) Drain() error {
adminPort := uint32(e.AdminPort)
err := DrainListeners(adminPort, e.Sidecar)
if err != nil {
log.Infof("failed draining listeners for Envoy on port %d: %v", adminPort, err)
}
return err
}
func (e *envoy) UpdateConfig(config []byte) error {
return os.WriteFile(e.ConfigPath, config, 0o666)
}
func (e *envoy) args(fname string, epoch int, bootstrapConfig string) []string {
proxyLocalAddressType := "v4"
if network.AllIPv6(e.NodeIPs) {
proxyLocalAddressType = "v6"
}
startupArgs := []string{
"-c", fname,
"--restart-epoch", fmt.Sprint(epoch),
"--drain-time-s", fmt.Sprint(int(e.DrainDuration.AsDuration().Seconds())),
"--drain-strategy", "immediate", // Clients are notified as soon as the drain process starts.
"--parent-shutdown-time-s", fmt.Sprint(int(e.ParentShutdownDuration.AsDuration().Seconds())),
"--local-address-ip-version", proxyLocalAddressType,
// Reduce default flush interval from 10s to 1s. The access log buffer size is 64k and each log is ~256 bytes
// This means access logs will be written once we have ~250 requests, or ever 1s, which ever comes first.
// Reducing this to 1s optimizes for UX while retaining performance.
// At low QPS access logs are unlikely a bottleneck, and these users will now see logs after 1s rather than 10s.
// At high QPS (>250 QPS) we will log the same amount as we will log due to exceeding buffer size, rather
// than the flush interval.
"--file-flush-interval-msec", "1000",
"--disable-hot-restart", // We don't use it, so disable it to simplify Envoy's logic
}
if e.ProxyConfig.LogAsJSON {
startupArgs = append(startupArgs,
"--log-format",
`{"level":"%l","time":"%Y-%m-%dT%T.%fZ","scope":"envoy %n","msg":"%j"}`,
)
} else {
// format is like `2020-04-07T16:52:30.471425Z info envoy config ...message..
// this matches Istio log format
startupArgs = append(startupArgs, "--log-format", "%Y-%m-%dT%T.%fZ\t%l\tenvoy %n\t%v")
}
startupArgs = append(startupArgs, e.extraArgs...)
if bootstrapConfig != "" {
bytes, err := os.ReadFile(bootstrapConfig)
if err != nil {
log.Warnf("Failed to read bootstrap override %s, %v", bootstrapConfig, err)
} else {
startupArgs = append(startupArgs, "--config-yaml", string(bytes))
}
}
if e.Concurrency > 0 {
startupArgs = append(startupArgs, "--concurrency", fmt.Sprint(e.Concurrency))
}
return startupArgs
}
var istioBootstrapOverrideVar = env.RegisterStringVar("ISTIO_BOOTSTRAP_OVERRIDE", "", "")
func (e *envoy) Run(epoch int, abort <-chan error) error {
// spin up a new Envoy process
args := e.args(e.ConfigPath, epoch, istioBootstrapOverrideVar.Get())
log.Infof("Envoy command: %v", args)
/* #nosec */
cmd := exec.Command(e.BinaryPath, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if e.AgentIsRoot {
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{
Uid: 1337,
Gid: 1337,
}
}
if err := cmd.Start(); err != nil {
return err
}
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
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
}
}
func (e *envoy) Cleanup(epoch int) {
if e.ConfigCleanup {
if err := os.Remove(e.ConfigPath); err != nil {
log.Warnf("Failed to delete config file %s for %d, %v", e.ConfigPath, epoch, err)
}
}
}