Skip to content

Commit

Permalink
service-mesh: open envoy admin endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
davidweisse committed May 22, 2024
1 parent 4b3c7a6 commit 03b2cc4
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 11 deletions.
18 changes: 18 additions & 0 deletions docs/docs/architecture/observability.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,21 @@ For the mesh API, the metric names are prefixed with `meshapi_grpc_server_`. The
metrics include similar data to the user API for the method `NewMeshCert` which
gets called by the [Initializer](../components#the-initializer) when starting a
new workload.

## Service Mesh metrics

The [Service Mesh](../components/service-mesh.md) can be configured to expose
metrics via its [Envoy admin
interface](https://www.envoyproxy.io/docs/envoy/latest/operations/admin). Be
aware that the admin interface can expose private information and allows
destructive operations to be performed. To enable the admin interface for the
Service Mesh, set the environment variable `EDG_ADMIN_PORT` in the configuration
of the Service Mesh sidecar. If this variable is set, the admin interface will
be started on this port.

To access the admin interface, the Service Mesh sidecar container needs to have
a corresponding container port and the ingress settings of the Proxy have to be
configured to allow access to the specified port (see [Configuring the
Proxy](../components/service-mesh#configuring-the-proxy)). All metrics will be
exposed under the `/stats` endpoint. Metrics in Prometheus format can be scraped
from the `/stats/prometheus` endpoint.
1 change: 1 addition & 0 deletions e2e/internal/contrasttest/contrasttest.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ func (ct *ContrastTest) Init(t *testing.T, resources []any) {
// Prepare resources
resources = kuberesource.PatchImages(resources, ct.ImageReplacements)
resources = kuberesource.PatchNamespaces(resources, ct.Namespace)
resources = kuberesource.PatchServiceMeshAdminInterface(resources, 9901)
resources = kuberesource.AddLogging(resources, "debug")
unstructuredResources, err := kuberesource.ResourcesToUnstructured(resources)
require.NoError(err)
Expand Down
21 changes: 21 additions & 0 deletions e2e/servicemesh/servicemesh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,27 @@ func TestIngressEgress(t *testing.T) {
stdout, stderr, err = c.Exec(ctx, ct.Namespace, frontendPods[0].Name, argv)
require.NoError(err, "Expected call with client certificate to succeed.\nstdout: %s\nstderr: %q", stdout, stderr)
})

t.Run("admin interface is available", func(t *testing.T) {
require := require.New(t)

ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()

c := kubeclient.NewForTest(t)

backendPods, err := c.PodsFromDeployment(ctx, ct.Namespace, "emoji")
require.NoError(err)
require.Len(backendPods, 1, "pod not found: %s/%s", ct.Namespace, "emoji")

frontendPods, err := c.PodsFromDeployment(ctx, ct.Namespace, "web")
require.NoError(err)
require.Len(frontendPods, 1, "pod not found: %s/%s", ct.Namespace, "web")

argv := []string{"curl", "-sS", frontendPods[0].Status.PodIP + ":9901"}
stdout, stderr, err := c.Exec(ctx, ct.Namespace, backendPods[0].Name, argv)
require.NoError(err, "Expected Service Mesh admin interface to be reachable.\nstdout: %s\nstderr: %q", stdout, stderr)
})
}

func TestMain(m *testing.M) {
Expand Down
25 changes: 24 additions & 1 deletion internal/kuberesource/sets.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package kuberesource

import (
"fmt"
"strings"

"k8s.io/apimachinery/pkg/util/intstr"
applyappsv1 "k8s.io/client-go/applyconfigurations/apps/v1"
Expand Down Expand Up @@ -220,7 +221,7 @@ func Emojivoto(smMode serviceMeshMode) ([]any, error) {
smProxyWeb = smProxyWeb.
WithEnv(EnvVar().
WithName("EDG_INGRESS_PROXY_CONFIG").
WithValue("web#8080#false"),
WithValue("web#8080#false##admin#9901#true"),
).
WithEnv(EnvVar().
WithName("EDG_EGRESS_PROXY_CONFIG").
Expand Down Expand Up @@ -600,3 +601,25 @@ func PatchNamespaces(resources []any, namespace string) []any {
}
return resources
}

// PatchServiceMeshAdminInterface activates the admin interface on the
// specified port for all Service Mesh components in a set of resources.
func PatchServiceMeshAdminInterface(resources []any, port int32) []any {
for _, resource := range resources {
switch r := resource.(type) {
case *applyappsv1.DeploymentApplyConfiguration:
for i := 0; i < len(r.Spec.Template.Spec.InitContainers); i++ {
if strings.Contains(*r.Spec.Template.Spec.InitContainers[i].Image, "service-mesh-proxy") {
r.Spec.Template.Spec.InitContainers[i] = *r.Spec.Template.Spec.InitContainers[i].
WithEnv(NewEnvVar("EDG_ADMIN_PORT", fmt.Sprint(port))).
WithPorts(
ContainerPort().
WithName("admin-interface").
WithContainerPort(port),
)
}
}
}
}
return resources
}
2 changes: 1 addition & 1 deletion packages/by-name/service-mesh/package.nix
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ buildGoModule rec {
};

proxyVendor = true;
vendorHash = "sha256-hKoHRgcji48ZnWWoeOuYkvSX2jhUqDTfsIl1ENSlA7E=";
vendorHash = "sha256-UbUoj6SzUaTASfMawVQnMNqE//csvsM3hSbXTntP3gQ=";

subPackages = [ "." ];

Expand Down
32 changes: 28 additions & 4 deletions service-mesh/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ var loopbackCIDR = netip.MustParsePrefix("127.0.0.1/8")

// ProxyConfig represents the configuration for the proxy.
type ProxyConfig struct {
egress []egressConfigEntry
ingress []ingressConfigEntry
egress []egressConfigEntry
ingress []ingressConfigEntry
adminPort uint32
}
type egressConfigEntry struct {
name string
Expand All @@ -52,8 +53,8 @@ type ingressConfigEntry struct {
// Example:
//
// emoji#127.137.0.1:8081#emoji-svc:8080##voting#127.137.0.2:8081#voting-svc:8080
func ParseProxyConfig(ingressConfig, egressConfig string) (ProxyConfig, error) {
if ingressConfig == "" && egressConfig == "" {
func ParseProxyConfig(ingressConfig, egressConfig, adminPort string) (ProxyConfig, error) {
if ingressConfig == "" && egressConfig == "" && adminPort == "" {
return ProxyConfig{}, nil
}

Expand Down Expand Up @@ -118,6 +119,14 @@ func ParseProxyConfig(ingressConfig, egressConfig string) (ProxyConfig, error) {

}

if adminPort != "" {
adminPortInt, err := strconv.Atoi(adminPort)
if err != nil {
return ProxyConfig{}, fmt.Errorf("invalid admin port: %s", adminPort)
}
cfg.adminPort = uint32(adminPortInt)
}

return cfg, nil
}

Expand All @@ -127,6 +136,21 @@ func (c ProxyConfig) ToEnvoyConfig() ([]byte, error) {
config := &envoyConfigBootstrapV3.Bootstrap{
StaticResources: &envoyConfigBootstrapV3.Bootstrap_StaticResources{},
}
if c.adminPort != 0 {
config.Admin = &envoyConfigBootstrapV3.Admin{
Address: &envoyCoreV3.Address{
Address: &envoyCoreV3.Address_SocketAddress{
SocketAddress: &envoyCoreV3.SocketAddress{
Address: "0.0.0.0",
PortSpecifier: &envoyCoreV3.SocketAddress_PortValue{
PortValue: c.adminPort,
},
},
},
},
}
}

listeners := make([]*envoyConfigListenerV3.Listener, 0)
clusters := make([]*envoyConfigClusterV3.Cluster, 0)

Expand Down
4 changes: 2 additions & 2 deletions service-mesh/go.mod
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
module github.com/edgelesssys/contrast/service-mesh

go 1.21
go 1.22.0

require (
github.com/coreos/go-iptables v0.7.0
github.com/envoyproxy/go-control-plane v0.12.0
google.golang.org/protobuf v1.34.1
google.golang.org/protobuf v1.34.0
)

require (
Expand Down
4 changes: 2 additions & 2 deletions service-mesh/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4=
google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
6 changes: 5 additions & 1 deletion service-mesh/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
const (
egressProxyConfigEnvVar = "EDG_EGRESS_PROXY_CONFIG"
ingressProxyConfigEnvVar = "EDG_INGRESS_PROXY_CONFIG"
adminPortEnvVar = "EDG_ADMIN_PORT"
envoyConfigFile = "/envoy-config.yml"
)

Expand All @@ -35,7 +36,10 @@ func run() (retErr error) {
ingressProxyConfig := os.Getenv(ingressProxyConfigEnvVar)
log.Println("Egress Proxy configuration:", ingressProxyConfig)

pconfig, err := ParseProxyConfig(ingressProxyConfig, egressProxyConfig)
adminPort := os.Getenv(adminPortEnvVar)
log.Println("Port for Envoy admin interface:", adminPort)

pconfig, err := ParseProxyConfig(ingressProxyConfig, egressProxyConfig, adminPort)
if err != nil {
return err
}
Expand Down

0 comments on commit 03b2cc4

Please sign in to comment.