Skip to content

Commit ba2b6db

Browse files
dgmoralesCopilot
andauthored
Feature: Service Sync (#51)
This PR implements the Service Sync feature. The purpose of this feature is to facilitate exposing a service in one cluster to other clusters, using Cilium Global Services. It implements all the behaviors described in [docs/service-sync.md](https://github.com/powerhome/keess/blob/main/docs/service-sync.md). In a high level overview, this PR - Implements service sync create and update flows - Refactors package structure, moving service sync to a subpackage and renaming main package - Implements service orphan handling It does not implement managed namespace deletion, that is left for another version. ## Notes about the code - I did an extensive work on the Go E2E tests, trying my best to make them correct, readable, and covering most if not all from Service Sync functionality. - Now for the unit tests (the new ones, for service), they are mostly the exact thing generated by AI. Quite incomplete, not so good quality, but don't seem to be harmful either. So I left them there, but I think we should move on with this and not wait for me to improve them now. - I placed a few TODO notes on places I believe to be good spots for unit tests, but left that for the future. ## How this was tested This code is passing tests-e2e (new) and tests-python-e2e (old). Together those are covering most functionality of the operator (both namespace sync and cluster sync). ## How to run tests ```shell # Create the clusters make setup-local-clusters-with-keess # Run tests make tests make tests-e2e make tests-python-e2e # or run make tests-all ``` --------- Co-authored-by: Copilot <[email protected]>
1 parent 4a2e9b4 commit ba2b6db

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+2465
-873
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ _testmain.go
3535
apiserver.local.config/**
3636
.DS_Store
3737
.vscode
38-
keess
38+
/keess
3939
coverage.*
4040
localTestKubeconfig
41+
localInternalTestKubeconfig

Makefile

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
PROJECT_NAME := "keess"
33
DOCKER_IMAGE_NAME := "keess"
44
DOCKER_TAG := "latest"
5+
# this file will host kubeconfig for local clusters created with kind
56
LOCAL_TEST_KUBECONFIG_FILE := "localTestKubeconfig"
7+
# this file will host the same kubeconfig, but to be used within the clusters themselves
8+
LOCAL_INTERNAL_TEST_KUBECONFIG_FILE := "localInternalTestKubeconfig"
69
LOCAL_CLUSTER := "kind-source-cluster"
710
K8S_VERSION_PAC_V1 := v1.22.17
811
K8S_VERSION := v1.32.2
@@ -15,12 +18,13 @@ ARCH := $(shell uname -m | sed 's/x86_64/amd64/;s/arm64/arm64/;s/aarch64/arm64/'
1518
GOBASE := $(shell pwd)
1619
GOBIN := $(GOBASE)/bin
1720

18-
.PHONY: build docker-build coverage run docker-run create-local-clusters delete-local-clusters local-docker-run help create-local-clusters-pac-v1 install-cilium-cli install-cilium-to-clusters tests e2e-tests python-e2e-tests
21+
.PHONY: build docker-build coverage run docker-run create-local-clusters create-local-clusters-pac-v1 delete-local-clusters install-cilium-cli install-cilium-to-clusters install-keess setup-local-clusters setup-local-clusters-with-keess local-docker-run tests tests-e2e tests-python-e2e help
1922

2023
# Build the project
2124
build:
2225
@echo "Building $(GOBIN)/$(PROJECT_NAME)..."
23-
@GOBIN=$(GOBIN) go build -o $(GOBIN)/$(PROJECT_NAME) $(GOBASE)
26+
mkdir -p $(GOBIN)
27+
GOBIN=$(GOBIN) go build -o $(GOBIN)/$(PROJECT_NAME) $(GOBASE)
2428

2529
# Build Docker image
2630
docker-build:
@@ -38,7 +42,7 @@ coverage:
3842
# Target to execute the application
3943
run: build
4044
@echo "Running the application..."
41-
@./bin/keess run --localCluster=$(LOCAL_CLUSTER) --logLevel=debug --kubeConfigPath=$(LOCAL_TEST_KUBECONFIG_FILE) --pollingInterval=10 --housekeepingInterval=10 --namespacePollingInterval=10
45+
@$(GOBIN)/$(PROJECT_NAME) run --localCluster=$(LOCAL_CLUSTER) --logLevel=debug --kubeConfigPath=$(LOCAL_TEST_KUBECONFIG_FILE) --pollingInterval=10 --housekeepingInterval=10 --namespacePollingInterval=10
4246

4347
# Target to run the Docker image with the .kube directory mounted
4448
docker-run:
@@ -52,16 +56,20 @@ create-local-clusters-pac-v1:
5256

5357
# Target to start local kube clusters for testing purposes
5458
create-local-clusters:
55-
@kind create cluster --image=kindest/node:$(K8S_VERSION) -n source-cluster --kubeconfig $(LOCAL_TEST_KUBECONFIG_FILE) --config extra/kind-config-1.yaml
56-
@kind create cluster --image=kindest/node:$(K8S_VERSION) -n destination-cluster --kubeconfig $(LOCAL_TEST_KUBECONFIG_FILE) --config extra/kind-config-2.yaml
59+
kind create cluster --image=kindest/node:$(K8S_VERSION) -n source-cluster --kubeconfig $(LOCAL_TEST_KUBECONFIG_FILE) --config tests/kind-config-1.yaml
60+
kind create cluster --image=kindest/node:$(K8S_VERSION) -n destination-cluster --kubeconfig $(LOCAL_TEST_KUBECONFIG_FILE) --config tests/kind-config-2.yaml
61+
KUBECONFIG=$(LOCAL_INTERNAL_TEST_KUBECONFIG_FILE) kind export kubeconfig -n source-cluster --internal
62+
KUBECONFIG=$(LOCAL_INTERNAL_TEST_KUBECONFIG_FILE) kind export kubeconfig -n destination-cluster --internal
63+
5764

5865
# Target to delete local kube clusters
5966
delete-local-clusters:
6067
@kind delete clusters source-cluster destination-cluster
6168

6269
$(GOBIN)/cilium:
63-
@curl -sL https://github.com/cilium/cilium-cli/releases/download/$(CILIUM_CLI_VERSION)/cilium-$(OS)-$(ARCH).tar.gz | tar -xzf - -C $(GOBIN);
64-
@chmod +x $(GOBIN)/cilium
70+
mkdir -p $(GOBIN)
71+
curl -sL https://github.com/cilium/cilium-cli/releases/download/$(CILIUM_CLI_VERSION)/cilium-$(OS)-$(ARCH).tar.gz | tar -xzf - -C $(GOBIN);
72+
chmod +x $(GOBIN)/cilium
6573

6674
install-cilium-cli: $(GOBIN)/cilium
6775

@@ -79,9 +87,32 @@ install-cilium-to-clusters: install-cilium-cli
7987
$(GOBIN)/cilium clustermesh status --wait --kubeconfig $(LOCAL_TEST_KUBECONFIG_FILE) --context kind-source-cluster
8088
$(GOBIN)/cilium clustermesh status --wait --kubeconfig $(LOCAL_TEST_KUBECONFIG_FILE) --context kind-destination-cluster
8189

82-
# Fully prepare local clusters for testing purposes
90+
install-keess: build docker-build
91+
@echo "Installing or updating Keess on local clusters..."
92+
kind load docker-image $(DOCKER_IMAGE_NAME):$(DOCKER_TAG) --name source-cluster
93+
helm upgrade --install keess chart \
94+
--kubeconfig $(LOCAL_TEST_KUBECONFIG_FILE) --kube-context kind-source-cluster \
95+
--namespace keess --create-namespace \
96+
--set localCluster=kind-source-cluster \
97+
--set-file config.kubeconfigContent=$(LOCAL_INTERNAL_TEST_KUBECONFIG_FILE) \
98+
--values tests/helm-values.yaml
99+
kind load docker-image $(DOCKER_IMAGE_NAME):$(DOCKER_TAG) --name destination-cluster
100+
helm upgrade --install keess chart \
101+
--kubeconfig $(LOCAL_TEST_KUBECONFIG_FILE) --kube-context kind-destination-cluster \
102+
--namespace keess --create-namespace \
103+
--set localCluster=kind-destination-cluster \
104+
--set-file config.kubeconfigContent=$(LOCAL_INTERNAL_TEST_KUBECONFIG_FILE) \
105+
--values tests/helm-values.yaml
106+
@echo "Make sure we restart Keess pods to apply changes..."
107+
kubectl --kubeconfig $(LOCAL_TEST_KUBECONFIG_FILE) --context kind-source-cluster -n keess delete pod --all
108+
kubectl --kubeconfig $(LOCAL_TEST_KUBECONFIG_FILE) --context kind-destination-cluster -n keess delete pod --all
109+
110+
# Fully prepare local clusters for testing with Keess running outside of the clusters (with make run)
83111
setup-local-clusters: create-local-clusters install-cilium-to-clusters
84112

113+
# Fully prepare local clusters for testing with Keess running inside the clusters
114+
setup-local-clusters-with-keess: setup-local-clusters install-keess
115+
85116
# Target to run the Docker image with local test kubeconfig mounted
86117
local-docker-run:
87118
@echo "Running Docker image with $(LOCAL_TEST_KUBECONFIG_FILE) mounted..."
@@ -99,24 +130,24 @@ local-docker-run:
99130
--namespacePollingInterval=10 \
100131
--logLevel=debug
101132

102-
# Run tests
133+
# Run unit tests only (excludes e2e tests in /tests directory)
103134
tests:
104135
@echo "Running Unit tests..."
105-
@go test ./...
136+
go test $(shell go list ./... | grep -v /tests)
106137

107138
# Run e2e tests (requires local clusters to be running)
108139
tests-e2e:
109140
@echo "Running e2e tests..."
110141
@echo "Make sure local clusters are running (use 'make setup-local-clusters' if needed)"
111-
@cd tests && ginkgo -v
142+
@cd tests/e2e && ginkgo -v
112143

113144
# Original e2e tests on python
114145
tests-python-e2e:
115146
@echo "Running python e2e tests on docker ..."
116147
@echo "Building keess-test image"
117-
@docker build -f Dockerfile.localTest -t keess-test:1.0 .
148+
docker build -f Dockerfile.localTest -t keess-test:1.0 .
118149
@echo "Run container ..."
119-
@docker run \
150+
docker run \
120151
-it \
121152
--rm \
122153
--mount type=bind,source="./$(LOCAL_TEST_KUBECONFIG_FILE)",target=/root/.kube/config,readonly \
@@ -125,6 +156,10 @@ tests-python-e2e:
125156
keess-test:1.0 \
126157
python test.py
127158

159+
# Run all tests (unit + e2e)
160+
tests-all: tests tests-e2e tests-python-e2e
161+
162+
128163
# Help
129164
help:
130165
@echo "--------------------------------"
@@ -135,8 +170,11 @@ help:
135170
@echo "coverage - Generate and view code coverage report"
136171
@echo "run - Run the application from local machine build"
137172
@echo "setup-local-clusters - Create 2 clusters locally using Kind ready for testing for PAC-V2 (includes Cilium)"
138-
@echo "tests - Run Unit tests"
139-
@echo "tests-e2e - Run e2e tests (requires local clusters)"
173+
@echo "setup-local-clusters-with-keess - Create 2 clusters locally with Keess running inside the clusters"
174+
@echo "tests - Run Unit tests only"
175+
@echo "tests-e2e - Run e2e tests (cluster sync focused, requires local clusters)"
176+
@echo "tests-python-e2e - Run python e2e tests using docker (namespace sync focused)"
177+
@echo "tests-all - Run all tests (unit + e2e)"
140178
@echo "delete-local-clusters - Delete the 2 local clusters created with Kind"
141179
@echo "--------------------------------"
142180
@echo "## Other Makefile commands:"
@@ -145,6 +183,6 @@ help:
145183
@echo "create-local-clusters-pac-v1 - Create 2 clusters locally using Kind with PAC-V1 Kubernetes version (no Cilium)"
146184
@echo "install-cilium-cli - Install Cilium CLI on the local machine"
147185
@echo "install-cilium-to-clusters - Install Cilium on the local clusters"
186+
@echo "install-keess - Install Keess on local clusters using Helm"
148187
@echo "docker-run - Run the Docker image with .kube directory mounted"
149188
@echo "local-docker-run - Run the application locally using docker and pointing to the local cluster created with Kind"
150-
@echo "tests-python-e2e - Run python e2e tests using docker"

README.md

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11

2-
# Keess: Kubernetes Secrets and ConfigMaps Synchronization
2+
# Keess: Kubernetes Secrets, ConfigMaps, and Services Synchronization
33

4-
Keess (Keep Stuff Synchronized) is a versatile command-line tool designed to synchronize secrets and configmaps across different namespaces and Kubernetes clusters. Built with simplicity and efficiency in mind, it ensures that your Kubernetes environments are consistently updated, secure, and easy to manage.
4+
Keess (Keep Stuff Synchronized) is a versatile command-line tool designed to synchronize secrets, configmaps, and services across different namespaces and Kubernetes clusters. Built with simplicity and efficiency in mind, it ensures that your Kubernetes environments are consistently updated, secure, and easy to manage.
55

66
## Features
77

8-
- **Cross-Namespace Synchronization**: Effortlessly sync secrets and configmaps across multiple namespaces within a single Kubernetes cluster.
8+
- **Cross-Namespace Synchronization**: Effortlessly sync secrets, configmaps, and services across multiple namespaces within a single Kubernetes cluster.
99
- **Inter-Cluster Synchronization**: Extend your synchronization capabilities to multiple clusters, keeping your configurations consistent across different environments.
10+
- **Service Synchronization**: Sync services across clusters using Cilium Global Services, enabling seamless cross-cluster service access.
1011
- **Secure and Reliable**: Implements robust mechanisms to securely transfer sensitive information, ensuring data integrity and confidentiality.
1112
- **Automation**: Automates the synchronization process, reducing manual overhead and minimizing human error.
1213
- **Customizable**: Offers flexible command line options and Kubernetes annotations to tailor the synchronization process to your specific needs.
@@ -67,6 +68,50 @@ Configure which namespaces to synchronize with:
6768

6869
Specify the remote clusters for synchronization: `keess.powerhrg.com/clusters: clustera, clusterb`
6970

71+
#### Service Synchronization
72+
73+
Keess supports synchronizing services across clusters using Cilium Global Services. This feature enables applications in one cluster to access services in another cluster as if they were local.
74+
75+
**Prerequisites:**
76+
- Cilium CNI with ClusterMesh enabled on all participating clusters
77+
- Services must have the `service.cilium.io/global: "true"` annotation
78+
79+
**Configuration:**
80+
1. Add the sync label to your service: `keess.powerhrg.com/sync: cluster`
81+
2. Add the clusters annotation: `keess.powerhrg.com/clusters: clustera, clusterb`
82+
3. Ensure the service has the Cilium global annotation: `service.cilium.io/global: "true"`
83+
84+
**Example:**
85+
```yaml
86+
apiVersion: v1
87+
kind: Service
88+
metadata:
89+
name: mysql-svc
90+
namespace: my-namespace
91+
labels:
92+
keess.powerhrg.com/sync: "cluster"
93+
annotations:
94+
service.cilium.io/global: "true"
95+
keess.powerhrg.com/clusters: "cluster-b, cluster-c"
96+
spec:
97+
ports:
98+
- name: mysql
99+
port: 3306
100+
protocol: TCP
101+
targetPort: 3306
102+
selector:
103+
app.kubernetes.io/component: mysql
104+
type: ClusterIP
105+
```
106+
107+
Keess will automatically create service references in the target clusters with:
108+
- Same name and namespace as the source service
109+
- Cilium annotations for global service configuration
110+
- Empty selector (no local endpoints)
111+
- Keess management labels and annotations
112+
113+
**Note:** Service synchronization only supports cluster-level sync. Namespace-level sync for services is not supported.
114+
70115
## Contributing
71116
72117
Contributions are welcome! Please refer to our [Contributing Guidelines](CONTRIBUTING.md) for more information.

chart/Chart.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ type: application
1515
# This is the chart version. This version number should be incremented each time you make changes
1616
# to the chart and its templates, including the app version.
1717
# Versions are expected to follow Semantic Versioning (https://semver.org/)
18-
version: 1.2.0
18+
version: 1.3.0
1919

2020
# This is the version number of the application being deployed. This version number should be
2121
# incremented each time you make changes to the application. Versions are not expected to
2222
# follow Semantic Versioning. They should reflect the version the application is using.
2323
# It is recommended to use it with quotes.
24-
appVersion: "1.2.0"
24+
appVersion: "1.3.0"

chart/templates/cluster-role.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ rules:
1010
resources:
1111
- configmaps
1212
- secrets
13+
- services
1314
verbs: ["get", "create", "update", "patch", "delete", "list", "watch"]
1415
- apiGroups: [""]
1516
resources:
1617
- namespaces
18+
- endpoints
1719
verbs: ["get", "list", "watch"]
1820
- apiGroups: [""]
1921
resources:

cmd/run.go

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ package cmd
2424
import (
2525
"context"
2626
"fmt"
27-
"keess/pkg/services"
27+
"keess/pkg/keess"
28+
"keess/pkg/keess/service"
2829
"net/http"
2930
"os"
3031
"time"
@@ -33,7 +34,6 @@ import (
3334
"go.uber.org/zap"
3435
"go.uber.org/zap/zapcore"
3536
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
36-
"k8s.io/client-go/kubernetes"
3737
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc" // required for oidc
3838
"k8s.io/client-go/rest"
3939
)
@@ -84,20 +84,19 @@ var runCmd = &cobra.Command{
8484
logger.Sugar().Debugf("Log level: %s", logLevel)
8585
logger.Sugar().Debugf("Kubeconfig path: %s", kubeConfigPath)
8686

87-
8887
config, err := rest.InClusterConfig()
8988
if err != nil {
90-
config, err = services.BuildConfigWithContextFromFlags(localCluster, kubeConfigPath)
89+
config, err = keess.BuildConfigWithContextFromFlags(localCluster, kubeConfigPath)
9190
if err != nil {
9291
logger.Sugar().Error("Error building localCluster kubeconfig: ", err)
9392
return
9493
}
9594
}
9695

9796
// create the clientset
98-
localKubeClient, err := kubernetes.NewForConfig(config)
97+
localKubeClient, err := keess.NewKubeClientAdapter(config)
9998
if err != nil {
100-
logger.Sugar().Error("Error creating inCluster clientset: ", err)
99+
logger.Sugar().Error("Error creating local kube client: ", err)
101100
return
102101
}
103102

@@ -106,20 +105,20 @@ var runCmd = &cobra.Command{
106105
defer cancel()
107106

108107
// Create a map of remote clients
109-
remoteKubeClients := make(map[string]services.IKubeClient)
108+
remoteKubeClients := make(map[string]keess.IKubeClient)
110109

111-
kubeConfigLoader := services.NewKubeconfigLoader(kubeConfigPath, logger.Sugar(), remoteKubeClients, configReloaderMaxRetries, configReloaderDebounceTimer)
110+
kubeConfigLoader := keess.NewKubeconfigLoader(kubeConfigPath, logger.Sugar(), remoteKubeClients, configReloaderMaxRetries, configReloaderDebounceTimer)
112111
kubeConfigLoader.StartWatching(ctx)
113112

114113
// Create a NamespacePoller
115-
namespacePoller := services.NewNamespacePoller(localKubeClient, logger.Sugar())
114+
namespacePoller := keess.NewNamespacePoller(localKubeClient, logger.Sugar())
116115
namespacePoller.PollNamespaces(ctx, metav1.ListOptions{}, time.Duration(namespacePollingInterval)*time.Second, localCluster)
117116

118117
// Create a SecretPoller
119-
secretPoller := services.NewSecretPoller(localCluster, localKubeClient, logger.Sugar())
118+
secretPoller := keess.NewSecretPoller(localCluster, localKubeClient, logger.Sugar())
120119

121120
// Create a SecretSynchronizer
122-
secretSynchronizer := services.NewSecretSynchronizer(
121+
secretSynchronizer := keess.NewSecretSynchronizer(
123122
localKubeClient,
124123
remoteKubeClients,
125124
secretPoller,
@@ -131,10 +130,10 @@ var runCmd = &cobra.Command{
131130
secretSynchronizer.Start(ctx, time.Duration(pollingInterval)*time.Second, time.Duration(housekeepingInterval)*time.Second)
132131

133132
// Create a ConfigMapPoller
134-
configMapPoller := services.NewConfigMapPoller(localCluster, localKubeClient, logger.Sugar())
133+
configMapPoller := keess.NewConfigMapPoller(localCluster, localKubeClient, logger.Sugar())
135134

136135
// Create a ConfigMapSynchronizer
137-
configMapSynchronizer := services.NewConfigMapSynchronizer(
136+
configMapSynchronizer := keess.NewConfigMapSynchronizer(
138137
localKubeClient,
139138
remoteKubeClients,
140139
configMapPoller,
@@ -145,6 +144,21 @@ var runCmd = &cobra.Command{
145144
// Start the configMap synchronizer
146145
configMapSynchronizer.Start(ctx, time.Duration(pollingInterval)*time.Second, time.Duration(housekeepingInterval)*time.Second)
147146

147+
// Create a ServicePoller
148+
servicePoller := service.NewServicePoller(localCluster, localKubeClient, logger.Sugar())
149+
150+
// Create a ServiceSynchronizer
151+
serviceSynchronizer := service.NewServiceSynchronizer(
152+
localKubeClient,
153+
remoteKubeClients,
154+
servicePoller,
155+
namespacePoller,
156+
logger.Sugar(),
157+
)
158+
159+
// Start the service synchronizer
160+
serviceSynchronizer.Start(ctx, time.Duration(pollingInterval)*time.Second, time.Duration(housekeepingInterval)*time.Second)
161+
148162
// Create an HTTP server and add the health check handler as a handler
149163
http.HandleFunc("/health", healthHandler)
150164

cmd/version.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import (
77
)
88

99
// Version of the application, set this variable during build
10-
var version = "1.0.0"
10+
var version = "1.3.0"
1111

1212
// versionCmd represents the version command
1313
var versionCmd = &cobra.Command{
1414
Use: "version",
15-
15+
1616
Short: "Print the version number of the application",
1717
Long: `Print the version number of the application`,
1818
Run: func(cmd *cobra.Command, args []string) {

0 commit comments

Comments
 (0)