Skip to content

Commit

Permalink
Merge branch 'main' into support-deployment-affinity-tolerations
Browse files Browse the repository at this point in the history
  • Loading branch information
qicz authored May 6, 2023
2 parents b502f2a + afdea0b commit 4e43170
Show file tree
Hide file tree
Showing 39 changed files with 6,077 additions and 5,560 deletions.
2 changes: 1 addition & 1 deletion GOVERNANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ individuals and will be valid for 1 year from the start date.
- Envoy core proxy maintainers [Member: Matt Klein]
- Tetrate [Member: Varun Talwar; End Date: 5/16/2023]
- VMware [Member: Winnie Kwon; End Date: 5/16/2023]
- Ambassador Labs [Member: Richard Li; End Date: 5/16/2023]
- Ambassador Labs [Member: Alex Gervais; End Date: 5/16/2023]
- Fidelity Investments [Member: Venkat Kasisomayajula / Rajarajan Pudupatti Sundari Jeyakodi; End Date: 5/16/2023]
- Tencent Holdings Limited [Member: Xunzhuo Liu; End Date: 5/16/2023]

Expand Down
15 changes: 10 additions & 5 deletions api/config/v1alpha1/shared_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,17 +120,21 @@ type KubernetesContainerSpec struct {

// ServiceType string describes ingress methods for a service
// +enum
// +kubebuilder:validation:Enum=LoadBalancer;ClusterIP
// +kubebuilder:validation:Enum=ClusterIP;LoadBalancer;NodePort
type ServiceType string

const (
// ServiceTypeClusterIP means a service will only be accessible inside the
// cluster, via the cluster IP.
ServiceTypeClusterIP ServiceType = "ClusterIP"

// ServiceTypeLoadBalancer means a service will be exposed via an
// external load balancer (if the cloud provider supports it).
ServiceTypeLoadBalancer ServiceType = "LoadBalancer"

// ServiceTypeClusterIP means a service will only be accessible inside the
// cluster, via the cluster IP.
ServiceTypeClusterIP ServiceType = "ClusterIP"
// ServiceTypeNodePort means a service will be exposed on each Kubernetes Node
// at a static Port, common across all Nodes.
ServiceTypeNodePort ServiceType = "NodePort"
)

// KubernetesServiceSpec defines the desired state of the Kubernetes service resource.
Expand All @@ -142,9 +146,10 @@ type KubernetesServiceSpec struct {
Annotations map[string]string `json:"annotations,omitempty"`

// Type determines how the Service is exposed. Defaults to LoadBalancer.
// Valid options are ClusterIP and LoadBalancer.
// Valid options are ClusterIP, LoadBalancer and NodePort.
// "LoadBalancer" means a service will be exposed via an external load balancer (if the cloud provider supports it).
// "ClusterIP" means a service will only be accessible inside the cluster, via the cluster IP.
// "NodePort" means a service will be exposed on a static Port on all Nodes of the cluster.
// +kubebuilder:default:="LoadBalancer"
// +optional
Type *ServiceType `json:"type,omitempty"`
Expand Down
4 changes: 3 additions & 1 deletion api/config/v1alpha1/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ func validateServiceType(spec *EnvoyProxySpec) []error {
var errs []error
if spec.Provider.Kubernetes != nil && spec.Provider.Kubernetes.EnvoyService != nil {
if serviceType := spec.Provider.Kubernetes.EnvoyService.Type; serviceType != nil {
if *serviceType != ServiceTypeLoadBalancer && *serviceType != ServiceTypeClusterIP {
if *serviceType != ServiceTypeLoadBalancer &&
*serviceType != ServiceTypeClusterIP &&
*serviceType != ServiceTypeNodePort {
errs = append(errs, fmt.Errorf("unsupported envoy service type %v", serviceType))
}
}
Expand Down
2 changes: 1 addition & 1 deletion api/config/v1alpha1/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func TestValidateEnvoyProxy(t *testing.T) {
},
},
},
expected: false,
expected: true,
},
{
name: "valid envoy service type 'LoadBalancer'",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1800,15 +1800,17 @@ spec:
type:
default: LoadBalancer
description: Type determines how the Service is exposed.
Defaults to LoadBalancer. Valid options are ClusterIP
and LoadBalancer. "LoadBalancer" means a service will
be exposed via an external load balancer (if the cloud
provider supports it). "ClusterIP" means a service will
only be accessible inside the cluster, via the cluster
IP.
Defaults to LoadBalancer. Valid options are ClusterIP,
LoadBalancer and NodePort. "LoadBalancer" means a service
will be exposed via an external load balancer (if the
cloud provider supports it). "ClusterIP" means a service
will only be accessible inside the cluster, via the
cluster IP. "NodePort" means a service will be exposed
on a static Port on all Nodes of the cluster.
enum:
- LoadBalancer
- ClusterIP
- LoadBalancer
- NodePort
type: string
type: object
type: object
Expand Down
1 change: 1 addition & 0 deletions charts/gateway-helm/templates/generated/rbac/roles.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ rules:
- ""
resources:
- namespaces
- nodes
- secrets
- services
verbs:
Expand Down
2 changes: 1 addition & 1 deletion docs/latest/api/config_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ _Appears in:_
| Field | Description |
| --- | --- |
| `annotations` _object (keys:string, values:string)_ | Annotations that should be appended to the service. By default, no annotations are appended. |
| `type` _[ServiceType](#servicetype)_ | Type determines how the Service is exposed. Defaults to LoadBalancer. Valid options are ClusterIP and LoadBalancer. "LoadBalancer" means a service will be exposed via an external load balancer (if the cloud provider supports it). "ClusterIP" means a service will only be accessible inside the cluster, via the cluster IP. |
| `type` _[ServiceType](#servicetype)_ | Type determines how the Service is exposed. Defaults to LoadBalancer. Valid options are ClusterIP, LoadBalancer and NodePort. "LoadBalancer" means a service will be exposed via an external load balancer (if the cloud provider supports it). "ClusterIP" means a service will only be accessible inside the cluster, via the cluster IP. "NodePort" means a service will be exposed on a static Port on all Nodes of the cluster. |


## LogComponent
Expand Down
174 changes: 129 additions & 45 deletions internal/cmd/egctl/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@
package egctl

import (
"encoding/json"
"fmt"
"io"
"net/http"
"sync"

adminv3 "github.com/envoyproxy/go-control-plane/envoy/admin/v3"
"github.com/tetratelabs/multierror"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/reflect/protoreflect"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/yaml"

Expand All @@ -21,103 +25,183 @@ import (
)

var (
output string
podName string
podNamespace string
output string
podName string
podNamespace string
labelSelectors []string
)

const (
adminPort = 19000 // TODO: make this configurable until EG support
containerName = "envoy" // TODO: make this configurable until EG support
)

func retrieveConfigDump(args []string, includeEds bool) (*adminv3.ConfigDump, error) {
if len(args) == 0 {
return nil, fmt.Errorf("pod name is required")
}
type aggregatedConfigDump map[string]map[string]protoreflect.ProtoMessage

func retrieveConfigDump(args []string, includeEds bool, configType envoyConfigType) (aggregatedConfigDump, error) {
if len(labelSelectors) == 0 {
if len(args) == 0 {
return nil, fmt.Errorf("pod name is required")
}

podName = args[0]
podName = args[0]

if podName == "" {
return nil, fmt.Errorf("pod name is required")
if podName == "" {
return nil, fmt.Errorf("pod name is required")
}
}

if podNamespace == "" {
return nil, fmt.Errorf("pod namespace is required")
}

fw, err := portForwarder(types.NamespacedName{
Namespace: podNamespace,
Name: podName,
})
cli, err := getCLIClient()
if err != nil {
return nil, err
}
if err := fw.Start(); err != nil {
return nil, err
}
defer fw.Stop()

configDump, err := extractConfigDump(fw, includeEds)
pods, err := fetchRunningEnvoyPods(cli, types.NamespacedName{Namespace: podNamespace, Name: podName}, labelSelectors)
if err != nil {
return nil, err
}

return configDump, nil
}
podConfigDumps := make(aggregatedConfigDump, 0)
// Initialize the map with namespaces
for _, pod := range pods {
if _, ok := podConfigDumps[pod.Namespace]; !ok {
podConfigDumps[pod.Namespace] = make(map[string]protoreflect.ProtoMessage)
}
}

func portForwarder(nn types.NamespacedName) (kube.PortForwarder, error) {
c, err := kube.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
if err != nil {
return nil, fmt.Errorf("build CLI client fail: %w", err)
var errs error
var wg sync.WaitGroup
wg.Add(len(pods))
for _, pod := range pods {
pod := pod
go func() {
fw, err := portForwarder(cli, pod)
if err != nil {
errs = multierror.Append(errs, err)
return
}

if err := fw.Start(); err != nil {
errs = multierror.Append(errs, err)
return
}
defer fw.Stop()
defer wg.Done()

configDump, err := extractConfigDump(fw, includeEds, configType)
if err != nil {
errs = multierror.Append(errs, err)
return
}

podConfigDumps[pod.Namespace][pod.Name] = configDump
}()
}

pod, err := c.Pod(nn)
if err != nil {
return nil, fmt.Errorf("get pod %s fail: %w", nn, err)
wg.Wait()
if errs != nil {
return nil, errs
}

return podConfigDumps, nil
}

// fetchRunningEnvoyPods gets the Pods, either based on the NamespacedName or the labelSelectors.
// It further filters out only those Pods that are in "Running" state.
// labelSelectors, if provided, take precedence over the pod NamespacedName.
func fetchRunningEnvoyPods(c kube.CLIClient, nn types.NamespacedName, labelSelectors []string) ([]types.NamespacedName, error) {
var pods []corev1.Pod

if len(labelSelectors) > 0 {
podList, err := c.PodsForSelector(nn.Namespace, labelSelectors...)
if err != nil {
return nil, fmt.Errorf("get pod %s fail: %w", nn, err)
}

if len(podList.Items) == 0 {
return nil, fmt.Errorf("no Pods found for label selectors %+v", labelSelectors)
}

pods = podList.Items
} else {
pod, err := c.Pod(nn)
if err != nil {
return nil, fmt.Errorf("get pod %s fail: %w", nn, err)
}

pods = []corev1.Pod{*pod}
}
if pod.Status.Phase != "Running" {
return nil, fmt.Errorf("pod %s is not running", nn)

podsNamespacedNames := []types.NamespacedName{}
for _, pod := range pods {
podNsName := types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name}
if pod.Status.Phase != "Running" {
return podsNamespacedNames, fmt.Errorf("pod %s is not running", podNsName)
}

podsNamespacedNames = append(podsNamespacedNames, podNsName)
}

fw, err := kube.NewLocalPortForwarder(c, nn, 0, adminPort)
return podsNamespacedNames, nil
}

// portForwarder returns a port forwarder instance for a single Pod.
func portForwarder(cli kube.CLIClient, nn types.NamespacedName) (kube.PortForwarder, error) {
fw, err := kube.NewLocalPortForwarder(cli, nn, 0, adminPort)
if err != nil {
return nil, err
}

return fw, nil
}

func marshalEnvoyProxyConfig(configDump protoreflect.ProtoMessage, output string) ([]byte, error) {
out, err := protojson.MarshalOptions{
Multiline: true,
}.Marshal(configDump)
// getCLIClient returns a new kubernetes CLI Client.
func getCLIClient() (kube.CLIClient, error) {
c, err := kube.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
if err != nil {
return nil, err
return nil, fmt.Errorf("build CLI client fail: %w", err)
}

if output == "yaml" {
out, err = yaml.JSONToYAML(out)
if err != nil {
return nil, err
return c, nil
}

func marshalEnvoyProxyConfig(configDump aggregatedConfigDump, output string) ([]byte, error) {
configDumpMap := make(map[string]map[string]interface{})
for ns, nsConfigs := range configDump {
configDumpMap[ns] = make(map[string]interface{})
for pod, podConfigs := range nsConfigs {
var newConfig interface{}
if err := json.Unmarshal([]byte(protojson.MarshalOptions{Multiline: false}.Format(podConfigs)), &newConfig); err != nil {
return nil, err
}
configDumpMap[ns][pod] = newConfig
}
}

return out, nil
out, err := json.MarshalIndent(configDumpMap, "", " ")
if output == "yaml" {
return yaml.JSONToYAML(out)
}

return out, err
}

func extractConfigDump(fw kube.PortForwarder, includeEds bool) (*adminv3.ConfigDump, error) {
func extractConfigDump(fw kube.PortForwarder, includeEds bool, configType envoyConfigType) (protoreflect.ProtoMessage, error) {
out, err := configDumpRequest(fw.Address(), includeEds)
if err != nil {
return nil, err
}

configDump := &adminv3.ConfigDump{}
if err := protojson.Unmarshal(out, configDump); err != nil {
configDumpResponse := &adminv3.ConfigDump{}
if err := protojson.Unmarshal(out, configDumpResponse); err != nil {
return nil, err
}

return configDump, nil
return findXDSResourceFromConfigDump(configType, configDumpResponse)
}

func configDumpRequest(address string, includeEds bool) ([]byte, error) {
Expand Down
12 changes: 5 additions & 7 deletions internal/cmd/egctl/config_bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ func bootstrapConfigCmd() *cobra.Command {
Example: ` # Retrieve summary about bootstrap configuration for a given pod from Envoy.
egctl config envoy-proxy bootstrap <pod-name> -n <pod-namespace>
# Retrieve summary about bootstrap configuration for a pod matching label selectors
egctl config envoy-proxy bootstrap --labels gateway.envoyproxy.io/owning-gateway-name=eg -l gateway.envoyproxy.io/owning-gateway-namespace=default
# Retrieve full configuration dump as YAML
egctl config envoy-proxy bootstrap <pod-name> -n <pod-namespace> -o yaml
Expand All @@ -36,17 +39,12 @@ func bootstrapConfigCmd() *cobra.Command {
}

func runBootstrapConfig(c *cobra.Command, args []string) error {
configDump, err := retrieveConfigDump(args, false)
if err != nil {
return err
}

bootstrap, err := findXDSResourceFromConfigDump(BootstrapEnvoyConfigType, configDump)
configDump, err := retrieveConfigDump(args, false, BootstrapEnvoyConfigType)
if err != nil {
return err
}

out, err := marshalEnvoyProxyConfig(bootstrap, output)
out, err := marshalEnvoyProxyConfig(configDump, output)
if err != nil {
return err
}
Expand Down
Loading

0 comments on commit 4e43170

Please sign in to comment.