Skip to content

Commit

Permalink
Add flag to run a dummy xDS server and envoy-sidecar configurer
Browse files Browse the repository at this point in the history
This adds a flag to add a dummy server mode, where all requested
resources will be returned to clients with endpoints that point to a
local port where an envoy sidecar is supposed to be running. It also
adds a simple envoy configurer to produce envoy config for the sidecar
  • Loading branch information
ffilippopoulos committed Oct 23, 2024
1 parent fa8af65 commit e0da2b8
Show file tree
Hide file tree
Showing 22 changed files with 499 additions and 58 deletions.
14 changes: 14 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ on:
env:
REGISTRY: quay.io
IMAGE_NAME: ${{ github.repository }}
IMAGE_NAME_ENVOY_CONFIGURER: ${{ github.repository }}/envoy-sidecar/configurer

jobs:
docker:
Expand Down Expand Up @@ -44,3 +45,16 @@ jobs:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Extract envoy/configurer metadata (tags, labels) for Docker
id: meta-envoy-configurer
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ENVOY_CONFIGURER }}
- name: Build and push Docker image for envoy configurer
if: github.actor != 'dependabot[bot]'
uses: docker/build-push-action@v5
with:
context: ./envoy-sidecar/configurer
push: true
tags: ${{ steps.meta-envoy-configurer.outputs.tags }}
labels: ${{ steps.meta-envoy-configurer.outputs.labels }}
32 changes: 16 additions & 16 deletions controller/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func TestReconcileServices_LabelledService(t *testing.T) {
"./test-resources/labelled_service.yaml",
"./test-resources/endpointslice.yaml",
)
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0))
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0), false)
controller := NewController(
client,
[]kube.Client{},
Expand Down Expand Up @@ -82,7 +82,7 @@ func TestReconcileServices_LabelledServiceLbPolicy(t *testing.T) {
"./test-resources/labelled_service_ring_hash_balancer.yaml",
"./test-resources/endpointslice.yaml",
)
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0))
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0), false)
controller := NewController(
client,
[]kube.Client{},
Expand Down Expand Up @@ -119,7 +119,7 @@ func TestReconcileServices_LabelledServiceInvalidLbPolicy(t *testing.T) {
"./test-resources/labelled_service_invalid_balancer.yaml",
"./test-resources/endpointslice.yaml",
)
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0))
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0), false)
controller := NewController(
client,
[]kube.Client{},
Expand Down Expand Up @@ -156,7 +156,7 @@ func TestReconcileServices_XdsService(t *testing.T) {
"./test-resources/xds_service.yaml",
"./test-resources/endpointslice.yaml",
)
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0))
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0), false)
controller := NewController(
client,
[]kube.Client{},
Expand Down Expand Up @@ -199,7 +199,7 @@ func TestReconcileServices_XdsServiceNotExistent(t *testing.T) {
"./test-resources/xds_service_not_existent.yaml",
"./test-resources/endpointslice.yaml",
)
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0))
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0), false)
controller := NewController(
client,
[]kube.Client{},
Expand Down Expand Up @@ -233,7 +233,7 @@ func TestReconcileServices_XdsServiceDelete(t *testing.T) {
"./test-resources/xds_service.yaml",
"./test-resources/endpointslice.yaml",
)
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0))
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0), false)
controller := NewController(
client,
[]kube.Client{},
Expand Down Expand Up @@ -284,7 +284,7 @@ func TestReconcileLocalEndpointSlice_SnapOnUpdate(t *testing.T) {
"./test-resources/xds_service.yaml",
"./test-resources/endpointslice.yaml",
)
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0))
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0), false)
controller := NewController(
client,
[]kube.Client{},
Expand Down Expand Up @@ -318,7 +318,7 @@ func TestReconcileLocalEndpointSlice_NotFound(t *testing.T) {
"./test-resources/endpointslice.yaml",
)
client.EndpointSliceApiError(kubeerror.NewNotFound(schema.GroupResource{Resource: "endpointslice"}, "foo"))
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0))
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0), false)
controller := NewController(
client,
[]kube.Client{},
Expand All @@ -345,7 +345,7 @@ func TestReconcileLocalEndpointSlice_NonXdsService(t *testing.T) {
client := kube.NewClientMock(
"./test-resources/endpointslice.yaml",
)
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0))
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0), false)
controller := NewController(
client,
[]kube.Client{},
Expand Down Expand Up @@ -373,7 +373,7 @@ func TestReconcileServices_XdsServiceWithRemoteEndpoints(t *testing.T) {
remoteClient := kube.NewClientMock(
"./test-resources/endpointslice-remote.yaml",
)
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0))
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0), false)
controller := NewController(
localClient,
[]kube.Client{remoteClient},
Expand Down Expand Up @@ -436,7 +436,7 @@ func TestReconcileServices_XdsServiceWithRemoteEndpoints_NoRemoteEndpoints(t *te
remoteClient := kube.NewClientMock(
"./test-resources/endpointslice-remote.yaml",
)
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0))
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0), false)
controller := NewController(
localClient,
[]kube.Client{remoteClient},
Expand Down Expand Up @@ -487,7 +487,7 @@ func TestReconcileServices_XdsServiceWithOnlyRemoteEndpoints(t *testing.T) {
remoteClient := kube.NewClientMock(
"./test-resources/endpointslice-remote.yaml",
)
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0))
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0), false)
controller := NewController(
localClient,
[]kube.Client{remoteClient},
Expand Down Expand Up @@ -539,7 +539,7 @@ func TestReconcileServices_XdsServiceWithRemoteEndpointsAndLocalPriority(t *test
remoteClient := kube.NewClientMock(
"./test-resources/endpointslice-remote.yaml",
)
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0))
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0), false)
controller := NewController(
localClient,
[]kube.Client{remoteClient},
Expand Down Expand Up @@ -601,7 +601,7 @@ func TestReconcileServices_XdsServiceWithOnlyRemoteEndpointsAndLocalPriority(t *
remoteClient := kube.NewClientMock(
"./test-resources/endpointslice-remote.yaml",
)
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0))
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0), false)
controller := NewController(
localClient,
[]kube.Client{remoteClient},
Expand Down Expand Up @@ -656,7 +656,7 @@ func TestReconcileLocalEndpointSlices_XdsServiceWithEmptyLocalEndpoints(t *testi
remoteClient := kube.NewClientMock(
"./test-resources/endpointslice-remote.yaml",
)
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0))
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0), false)
controller := NewController(
localClient,
[]kube.Client{remoteClient},
Expand Down Expand Up @@ -700,7 +700,7 @@ func TestReconcileServices_XdsServiceWithRingHash(t *testing.T) {
"./test-resources/xds_service_ring_hash_balancing.yaml",
"./test-resources/endpointslice.yaml",
)
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0))
snapshotter := xds.NewSnapshotter("", testSnapshotterListenPort, float64(0), float64(0), false)
controller := NewController(
client,
[]kube.Client{},
Expand Down
7 changes: 7 additions & 0 deletions envoy-sidecar/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Tooling to deploy envoy as a sidecar and use it with the xDS server

This is in a very experimental stage and definitely not production ready.

It includes a small app to be used as an initContainer to produce envoy config
based on a map of xDS services and local ports and a kustomize base to deploy a
Kyverno mutating rule for the initContainer and the envoy sidecar.
1 change: 1 addition & 0 deletions envoy-sidecar/configurer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
configurer
13 changes: 13 additions & 0 deletions envoy-sidecar/configurer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM golang:1.21-alpine AS build
WORKDIR /go/src/github.com/utilitywarehouse/semaphore-xds/envoy-sidecar/configurer
COPY . /go/src/github.com/utilitywarehouse/semaphore-xds/envoy-sidecar/configurer
ENV CGO_ENABLED=0
RUN \
apk --no-cache add git upx \
&& go build -ldflags='-s -w' -o /semaphore-xds-envoy-configurer . \
&& upx /semaphore-xds-envoy-configurer

FROM alpine:3.18
COPY --from=build /semaphore-xds-envoy-configurer /semaphore-xds-envoy-configurer
COPY --from=build /go/src/github.com/utilitywarehouse/semaphore-xds/envoy-sidecar/configurer/templates /templates
ENTRYPOINT [ "/semaphore-xds-envoy-configurer" ]
12 changes: 12 additions & 0 deletions envoy-sidecar/configurer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# semaphore-xds-envoy-configurer

Expects an environment variable named `ENVOY_SIDECAR_TARGETS` in the form of a
comma separated list of xDS listeners. For example:

`ENVOY_SIDECAR_TARGETS="<xds-address1>,<xds-address2>"`
`XDS_SERVER_ADDRESS`="semaphore-xds.sys-semaphore.svc.cluster.local"
`XDS_SERVER_PORT`="18000"

It generates envoy config to point listeners on the specified local ports to
the respective xDS dynamic resources in order for envoy to be able to proxy
traffic to the configured services.
120 changes: 120 additions & 0 deletions envoy-sidecar/configurer/configure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package main

import (
"bytes"
"path"
"strings"
"text/template"
)

type Listener struct {
RouteConfigName string
}

type Cluster struct {
Name string
}

type XdsCluster struct {
XdsServerAddress string
XdsServerPort string
}

type EnvoyConfig struct {
NodeID string
ClusterID string
Listeners string
Clusters string
XdsCluster string
}

func makeEnvoyConfig(nodeID, envoySidecarTargets, XdsServerAddress, XdsServerPort string) (string, error) {
listeners, clusters := extractConfigFromTargets(envoySidecarTargets)
// Generate Listeners Config
listenersTmplPath := "./templates/listeners.tmpl"
listenersTmplBase := path.Base(listenersTmplPath)
tmpl, err := template.New(listenersTmplBase).ParseFiles(listenersTmplPath)
if err != nil {
return "", err
}
var renderedListeners bytes.Buffer
err = tmpl.Execute(&renderedListeners, listeners)
if err != nil {
return "", err
}
// Generate Clusters Config
clustersTmplPath := "./templates/clusters.tmpl"
clustersTmplBase := path.Base(clustersTmplPath)
tmpl, err = template.New(clustersTmplBase).ParseFiles(clustersTmplPath)
if err != nil {
return "", err
}
var renderedClusters bytes.Buffer
err = tmpl.Execute(&renderedClusters, clusters)
if err != nil {
return "", err
}
// Generate XdsCluster Config
xdsCluster := XdsCluster{
XdsServerAddress: XdsServerAddress,
XdsServerPort: XdsServerPort,
}
XdsClusterTmplPath := "./templates/xds-cluster.tmpl"
XdsClusterTmplBase := path.Base(XdsClusterTmplPath)
tmpl, err = template.New(XdsClusterTmplBase).ParseFiles(XdsClusterTmplPath)
if err != nil {
return "", err
}
var renderedXdsCluster bytes.Buffer
err = tmpl.Execute(&renderedXdsCluster, xdsCluster)
if err != nil {
return "", err
}

// Generate the Envoy config
envoyConfig := EnvoyConfig{
NodeID: nodeID,
ClusterID: nodeID, // needed by envoy, add node id here as a dummy value here
Listeners: renderedListeners.String(),
Clusters: renderedClusters.String(),
XdsCluster: renderedXdsCluster.String(),
}
envoyConfigTmplPath := "./templates/envoy-config.tmpl"
envoyConfigTmplBase := path.Base(envoyConfigTmplPath)
tmpl, err = template.New(envoyConfigTmplBase).ParseFiles(envoyConfigTmplPath)
if err != nil {
return "", err
}
var renderedEnvoyConfig bytes.Buffer
err = tmpl.Execute(&renderedEnvoyConfig, envoyConfig)
if err != nil {
return "", err
}
return renderedEnvoyConfig.String(), nil
}

// List expected upstream listeners in the form:
// <xds-address1>,<xds-address2>,<xds-address2>
// From this we should extract the xds addresses as listerner and route config
// names.
// XdsAddress is expected in the name that semaphore-xds would configure
// listeners: service.namespace:port and should be copied as is to the listener
// and routeConfig names. Clusters names should be derived from the above and
// follow the form: service.namespace.port to comply with the xds naming
// limitations and how semaphore-xds configures cluster names.
func extractConfigFromTargets(envoySidecarTargets string) ([]Listener, []Cluster) {
listeners := []Listener{}
for _, target := range strings.Split(envoySidecarTargets, ",") {
listeners = append(listeners, Listener{
RouteConfigName: target,
})
}
clusters := []Cluster{}
for _, l := range listeners {
clusterName := strings.Join(strings.Split(l.RouteConfigName, ":"), ".")
clusters = append(clusters, Cluster{
Name: clusterName,
})
}
return listeners, clusters
}
3 changes: 3 additions & 0 deletions envoy-sidecar/configurer/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/utilitywarehouse/semaphore-xds/envoy-sidecar/configurer

go 1.21.3
41 changes: 41 additions & 0 deletions envoy-sidecar/configurer/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package main

import (
"flag"
"fmt"
"os"
)

var (
flagEnvoyNodeId = flag.String("envoy-node-id", getEnv("ENVOY_NODE_ID", ""), "Node id to configure for envoy sidecar")
flagEnvoySidecarTargets = flag.String("envoy-sidecar-targets", getEnv("ENVOY_SIDECAR_TARGETS", ""), "Comma separated list of listeners to get xDS config for: <xds-listener-1>,<xds-listener-2>")
flagXdsServerAddress = flag.String("xds-server-address", getEnv("XDS_SERVER_ADDRESS", ""), "The address of the xds server for envoy sidecar to fetch config")
flagXdsServerPort = flag.String("xds-server-port", getEnv("XDS_SERVER_PORT", ""), "The port of the xds server for envoy sidecar to fetch config")
)

func usage() {
flag.Usage()
os.Exit(1)
}

func getEnv(key, defaultValue string) string {
value := os.Getenv(key)
if len(value) == 0 {
return defaultValue
}
return value
}

func main() {
flag.Parse()

if *flagEnvoyNodeId == "" || *flagEnvoySidecarTargets == "" || *flagXdsServerAddress == "" || *flagXdsServerPort == "" {
usage()
}
c, err := makeEnvoyConfig(*flagEnvoyNodeId, *flagEnvoySidecarTargets, *flagXdsServerAddress, *flagXdsServerPort)
if err != nil {
fmt.Print(err)
os.Exit(1)
}
fmt.Print(c)
}
14 changes: 14 additions & 0 deletions envoy-sidecar/configurer/templates/clusters.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{{- range . }}
- name: {{ .Name }}
http2_protocol_options: {}
type: EDS
eds_cluster_config:
eds_config:
resource_api_version: V3
api_config_source:
api_type: GRPC
transport_api_version: V3
grpc_services:
- envoy_grpc:
cluster_name: xds_cluster
{{- end }}
Loading

0 comments on commit e0da2b8

Please sign in to comment.