mirror of
https://github.com/kairos-io/entangle.git
synced 2025-11-26 00:34:57 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2250450b88 | ||
|
|
b4a78705dd | ||
|
|
b25f31d789 | ||
|
|
daab27404c | ||
|
|
1da47ac24f | ||
|
|
297f557a04 | ||
|
|
c3cd5f5654 |
7
Makefile
7
Makefile
@@ -275,4 +275,9 @@ unit-tests: test_deps
|
||||
e2e-tests:
|
||||
KUBE_VERSION=${KUBE_VERSION} $(ROOT_DIR)/script/test.sh
|
||||
|
||||
kind-e2e-tests: kind-setup install undeploy-dev deploy-dev e2e-tests
|
||||
kind-e2e-tests: kind-setup install undeploy-dev deploy-dev e2e-tests
|
||||
|
||||
kubesplit: manifests kustomize
|
||||
rm -rf helm-chart
|
||||
mkdir helm-chart
|
||||
$(KUSTOMIZE) build config/default | kubesplit -helm helm-chart
|
||||
91
README.md
91
README.md
@@ -1,80 +1,33 @@
|
||||
# entangle
|
||||
// TODO(user): Add simple overview of use/purpose
|
||||
|
||||
## Description
|
||||
// TODO(user): An in-depth paragraph about your project and overview of use
|
||||
| :exclamation: | This is experimental! |
|
||||
|-|:-|
|
||||
|
||||
## Getting Started
|
||||
You’ll need a Kubernetes cluster to run against. You can use [KIND](https://sigs.k8s.io/kind) to get a local cluster for testing, or run against a remote cluster.
|
||||
**Note:** Your controller will automatically use the current context in your kubeconfig file (i.e. whatever cluster `kubectl cluster-info` shows).
|
||||
This is the Kairos entangle Kubernetes Native Extension.
|
||||
|
||||
### Running on the cluster
|
||||
1. Install Instances of Custom Resources:
|
||||
To install, use helm:
|
||||
|
||||
```sh
|
||||
kubectl apply -f config/samples/
|
||||
```
|
||||
# Adds the kairos repo to helm
|
||||
$ helm repo add kairos https://kairos-io.github.io/helm-charts
|
||||
"kairos" has been added to your repositories
|
||||
$ helm repo update
|
||||
Hang tight while we grab the latest from your chart repositories...
|
||||
...Successfully got an update from the "kairos" chart repository
|
||||
Update Complete. ⎈Happy Helming!⎈
|
||||
|
||||
2. Build and push your image to the location specified by `IMG`:
|
||||
|
||||
```sh
|
||||
make docker-build docker-push IMG=<some-registry>/entangle:tag
|
||||
# Install the CRD chart
|
||||
$ helm install kairos-crd kairos/kairos-crds
|
||||
NAME: kairos-crd
|
||||
LAST DEPLOYED: Tue Sep 6 20:35:34 2022
|
||||
NAMESPACE: default
|
||||
STATUS: deployed
|
||||
REVISION: 1
|
||||
TEST SUITE: None
|
||||
|
||||
# Installs entangle
|
||||
$ helm install kairos-entangle kairos/entangle
|
||||
```
|
||||
|
||||
3. Deploy the controller to the cluster with the image specified by `IMG`:
|
||||
|
||||
```sh
|
||||
make deploy IMG=<some-registry>/entangle:tag
|
||||
```
|
||||
|
||||
### Uninstall CRDs
|
||||
To delete the CRDs from the cluster:
|
||||
|
||||
```sh
|
||||
make uninstall
|
||||
```
|
||||
|
||||
### Undeploy controller
|
||||
UnDeploy the controller to the cluster:
|
||||
|
||||
```sh
|
||||
make undeploy
|
||||
```
|
||||
|
||||
## Contributing
|
||||
// TODO(user): Add detailed information on how you would like others to contribute to this project
|
||||
|
||||
### How it works
|
||||
This project aims to follow the Kubernetes [Operator pattern](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/)
|
||||
|
||||
It uses [Controllers](https://kubernetes.io/docs/concepts/architecture/controller/)
|
||||
which provides a reconcile function responsible for synchronizing resources untile the desired state is reached on the cluster
|
||||
|
||||
### Test It Out
|
||||
1. Install the CRDs into the cluster:
|
||||
|
||||
```sh
|
||||
make install
|
||||
```
|
||||
|
||||
2. Run your controller (this will run in the foreground, so switch to a new terminal if you want to leave it running):
|
||||
|
||||
```sh
|
||||
make run
|
||||
```
|
||||
|
||||
**NOTE:** You can also run this in one step by running: `make install run`
|
||||
|
||||
### Modifying the API definitions
|
||||
If you are editing the API definitions, generate the manifests such as CRs or CRDs using:
|
||||
|
||||
```sh
|
||||
make manifests
|
||||
```
|
||||
|
||||
**NOTE:** Run `make --help` for more information on all potential `make` targets
|
||||
|
||||
More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -29,13 +29,14 @@ type EntanglementSpec struct {
|
||||
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
||||
// Important: Run "make" to regenerate code after modifying this file
|
||||
|
||||
ServiceUUID string `json:"serviceUUID,omitempty"`
|
||||
ServiceRef *string `json:"serviceRef,omitempty"`
|
||||
SecretRef *string `json:"secretRef,omitempty"`
|
||||
Host string `json:"host,omitempty"`
|
||||
Port string `json:"port,omitempty"`
|
||||
HostNetwork bool `json:"hostNetwork,omitempty"`
|
||||
Inbound bool `json:"inbound,omitempty"`
|
||||
ServiceUUID string `json:"serviceUUID,omitempty"`
|
||||
ServiceRef *string `json:"serviceRef,omitempty"`
|
||||
SecretRef *string `json:"secretRef,omitempty"`
|
||||
Host string `json:"host,omitempty"`
|
||||
Port string `json:"port,omitempty"`
|
||||
HostNetwork bool `json:"hostNetwork,omitempty"`
|
||||
Inbound bool `json:"inbound,omitempty"`
|
||||
Envs []v1.EnvVar `json:"env,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
ServiceSpec *v1.ServiceSpec `json:"serviceSpec,omitEmpty"`
|
||||
}
|
||||
|
||||
@@ -98,6 +98,13 @@ func (in *EntanglementSpec) DeepCopyInto(out *EntanglementSpec) {
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Envs != nil {
|
||||
in, out := &in.Envs, &out.Envs
|
||||
*out = make([]v1.EnvVar, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.ServiceSpec != nil {
|
||||
in, out := &in.ServiceSpec, &out.ServiceSpec
|
||||
*out = new(v1.ServiceSpec)
|
||||
|
||||
@@ -35,6 +35,110 @@ spec:
|
||||
spec:
|
||||
description: EntanglementSpec defines the desired state of Entanglement
|
||||
properties:
|
||||
env:
|
||||
items:
|
||||
description: EnvVar represents an environment variable present in
|
||||
a Container.
|
||||
properties:
|
||||
name:
|
||||
description: Name of the environment variable. Must be a C_IDENTIFIER.
|
||||
type: string
|
||||
value:
|
||||
description: 'Variable references $(VAR_NAME) are expanded using
|
||||
the previously defined environment variables in the container
|
||||
and any service environment variables. If a variable cannot
|
||||
be resolved, the reference in the input string will be unchanged.
|
||||
Double $$ are reduced to a single $, which allows for escaping
|
||||
the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the
|
||||
string literal "$(VAR_NAME)". Escaped references will never
|
||||
be expanded, regardless of whether the variable exists or
|
||||
not. Defaults to "".'
|
||||
type: string
|
||||
valueFrom:
|
||||
description: Source for the environment variable's value. Cannot
|
||||
be used if value is not empty.
|
||||
properties:
|
||||
configMapKeyRef:
|
||||
description: Selects a key of a ConfigMap.
|
||||
properties:
|
||||
key:
|
||||
description: The key to select.
|
||||
type: string
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
type: string
|
||||
optional:
|
||||
description: Specify whether the ConfigMap or its key
|
||||
must be defined
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
fieldRef:
|
||||
description: 'Selects a field of the pod: supports metadata.name,
|
||||
metadata.namespace, `metadata.labels[''<KEY>'']`, `metadata.annotations[''<KEY>'']`,
|
||||
spec.nodeName, spec.serviceAccountName, status.hostIP,
|
||||
status.podIP, status.podIPs.'
|
||||
properties:
|
||||
apiVersion:
|
||||
description: Version of the schema the FieldPath is
|
||||
written in terms of, defaults to "v1".
|
||||
type: string
|
||||
fieldPath:
|
||||
description: Path of the field to select in the specified
|
||||
API version.
|
||||
type: string
|
||||
required:
|
||||
- fieldPath
|
||||
type: object
|
||||
resourceFieldRef:
|
||||
description: 'Selects a resource of the container: only
|
||||
resources limits and requests (limits.cpu, limits.memory,
|
||||
limits.ephemeral-storage, requests.cpu, requests.memory
|
||||
and requests.ephemeral-storage) are currently supported.'
|
||||
properties:
|
||||
containerName:
|
||||
description: 'Container name: required for volumes,
|
||||
optional for env vars'
|
||||
type: string
|
||||
divisor:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: Specifies the output format of the exposed
|
||||
resources, defaults to "1"
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
resource:
|
||||
description: 'Required: resource to select'
|
||||
type: string
|
||||
required:
|
||||
- resource
|
||||
type: object
|
||||
secretKeyRef:
|
||||
description: Selects a key of a secret in the pod's namespace
|
||||
properties:
|
||||
key:
|
||||
description: The key of the secret to select from. Must
|
||||
be a valid secret key.
|
||||
type: string
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
type: string
|
||||
optional:
|
||||
description: Specify whether the Secret or its key must
|
||||
be defined
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
host:
|
||||
type: string
|
||||
hostNetwork:
|
||||
@@ -22,7 +22,7 @@ func genOwner(ent entanglev1alpha1.Entanglement) []metav1.OwnerReference {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *EntanglementReconciler) genDeployment(ent entanglev1alpha1.Entanglement) (*appsv1.Deployment, error) {
|
||||
func (r *EntanglementReconciler) genDeployment(ent entanglev1alpha1.Entanglement, logLevel string) (*appsv1.Deployment, error) {
|
||||
objMeta := metav1.ObjectMeta{
|
||||
Name: ent.Name,
|
||||
Namespace: ent.Namespace,
|
||||
@@ -40,25 +40,26 @@ func (r *EntanglementReconciler) genDeployment(ent entanglev1alpha1.Entanglement
|
||||
}
|
||||
}
|
||||
|
||||
v := ent.Spec.Envs
|
||||
v = append(v, v1.EnvVar{
|
||||
Name: "EDGEVPNTOKEN",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
SecretKeyRef: &v1.SecretKeySelector{
|
||||
Key: "network_token",
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: *ent.Spec.SecretRef,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expose := v1.Container{
|
||||
ImagePullPolicy: v1.PullAlways,
|
||||
SecurityContext: &v1.SecurityContext{Privileged: &privileged},
|
||||
Name: "entanglement",
|
||||
Image: r.EntangleServiceImage,
|
||||
Env: []v1.EnvVar{
|
||||
{
|
||||
Name: "EDGEVPNTOKEN",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
SecretKeyRef: &v1.SecretKeySelector{
|
||||
Key: "network_token",
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: *ent.Spec.SecretRef,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Command: []string{"/usr/bin/edgevpn"},
|
||||
Env: v,
|
||||
Command: []string{"/usr/bin/edgevpn"},
|
||||
}
|
||||
|
||||
cmd := "service-add"
|
||||
@@ -96,9 +97,9 @@ func (r *EntanglementReconciler) genDeployment(ent entanglev1alpha1.Entanglement
|
||||
}
|
||||
|
||||
if ent.Spec.ServiceRef != nil {
|
||||
expose.Args = []string{cmd, "--log-level", "debug", ent.Spec.ServiceUUID, fmt.Sprintf("%s:%s", fmt.Sprintf("%s.svc.cluster.local", svc.Name), ent.Spec.Port)}
|
||||
expose.Args = []string{cmd, "--log-level", logLevel, ent.Spec.ServiceUUID, fmt.Sprintf("%s:%s", fmt.Sprintf("%s.svc.cluster.local", svc.Name), ent.Spec.Port)}
|
||||
} else {
|
||||
expose.Args = []string{cmd, "--log-level", "debug", ent.Spec.ServiceUUID, fmt.Sprintf("%s:%s", ent.Spec.Host, ent.Spec.Port)}
|
||||
expose.Args = []string{cmd, "--log-level", logLevel, ent.Spec.ServiceUUID, fmt.Sprintf("%s:%s", ent.Spec.Host, ent.Spec.Port)}
|
||||
}
|
||||
|
||||
pod := v1.PodSpec{
|
||||
|
||||
@@ -37,8 +37,8 @@ import (
|
||||
type EntanglementReconciler struct {
|
||||
clientSet *kubernetes.Clientset
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
EntangleServiceImage string
|
||||
Scheme *runtime.Scheme
|
||||
EntangleServiceImage, LogLevel string
|
||||
}
|
||||
|
||||
//+kubebuilder:rbac:groups=entangle.kairos.io,resources=entanglements,verbs=get;list;watch;create;update;patch;delete
|
||||
@@ -70,7 +70,7 @@ func (r *EntanglementReconciler) Reconcile(ctx context.Context, req ctrl.Request
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
desiredDeployment, err := r.genDeployment(ent)
|
||||
desiredDeployment, err := r.genDeployment(ent,r.LogLevel)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -24,7 +25,7 @@ type Webhook struct {
|
||||
clientSet *kubernetes.Clientset
|
||||
Scheme *runtime.Scheme
|
||||
|
||||
SidecarImage string
|
||||
SidecarImage, LogLevel string
|
||||
}
|
||||
|
||||
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
|
||||
@@ -34,8 +35,10 @@ var (
|
||||
EntanglementNameLabel = "entanglement.kairos.io/name"
|
||||
EntanglementServiceLabel = "entanglement.kairos.io/service"
|
||||
EntanglementDirectionLabel = "entanglement.kairos.io/direction"
|
||||
EntanglementNetHost = "entanglement.kairos.io/nethost"
|
||||
EntanglementPortLabel = "entanglement.kairos.io/target_port"
|
||||
EntanglementHostLabel = "entanglement.kairos.io/host"
|
||||
EnvPrefix = "entanglement.kairos.io/env."
|
||||
)
|
||||
|
||||
func (w *Webhook) SetupWebhookWithManager(mgr manager.Manager) error {
|
||||
@@ -55,34 +58,77 @@ func (w *Webhook) Mutate(ctx context.Context, request admission.Request, object
|
||||
_ = log.FromContext(ctx)
|
||||
|
||||
pod := object.(*corev1.Pod)
|
||||
entanglementName, exists := pod.Labels[EntanglementNameLabel]
|
||||
|
||||
// Let user use both label and annotations
|
||||
info := make(map[string]string)
|
||||
|
||||
// Annotations take precedence
|
||||
for k, v := range pod.Labels {
|
||||
info[k] = v
|
||||
}
|
||||
|
||||
// Annotations take precedence
|
||||
for k, v := range pod.Annotations {
|
||||
info[k] = v
|
||||
}
|
||||
|
||||
entanglementName, exists := info[EntanglementNameLabel]
|
||||
if !exists {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
entanglementPort, exists := pod.Labels[EntanglementPortLabel]
|
||||
envs := []corev1.EnvVar{
|
||||
{
|
||||
Name: "EDGEVPNTOKEN",
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
SecretKeyRef: &corev1.SecretKeySelector{
|
||||
Key: "network_token",
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: entanglementName,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
||||
for k, v := range info {
|
||||
if strings.HasPrefix(k, EnvPrefix) {
|
||||
env := strings.ReplaceAll(k, EnvPrefix, "")
|
||||
envs = append(envs, corev1.EnvVar{Name: env, Value: v})
|
||||
}
|
||||
}
|
||||
|
||||
entanglementPort, exists := info[EntanglementPortLabel]
|
||||
if !exists {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
cmd := "service-connect"
|
||||
entanglementDirection, exists := pod.Labels[EntanglementDirectionLabel]
|
||||
entanglementDirection, exists := info[EntanglementDirectionLabel]
|
||||
if exists && entanglementDirection == "entangle" {
|
||||
cmd = "service-add"
|
||||
}
|
||||
|
||||
host := "127.0.0.1"
|
||||
entanglementHost, exists := pod.Labels[EntanglementHostLabel]
|
||||
entanglementHost, exists := info[EntanglementHostLabel]
|
||||
if exists && entanglementHost != "" {
|
||||
host = entanglementHost
|
||||
}
|
||||
|
||||
entanglementService, exists := pod.Labels[EntanglementServiceLabel]
|
||||
entanglementService, exists := info[EntanglementServiceLabel]
|
||||
if !exists {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
podCopy := pod.DeepCopy()
|
||||
|
||||
hostNetwork, exists := info[EntanglementNetHost]
|
||||
// By default it injects hostnetwork, however if set to false it does enforces it to false
|
||||
if exists && hostNetwork == "false" {
|
||||
podCopy.Spec.HostNetwork = false
|
||||
} else {
|
||||
podCopy.Spec.HostNetwork = true
|
||||
}
|
||||
|
||||
secret, err := w.clientSet.CoreV1().Secrets(request.Namespace).Get(context.Background(), entanglementName, v1.GetOptions{})
|
||||
if err != nil || secret == nil {
|
||||
return admission.Denied("entanglement secret not found: " + entanglementName + err.Error())
|
||||
@@ -99,20 +145,8 @@ func (w *Webhook) Mutate(ctx context.Context, request admission.Request, object
|
||||
servingContainer := corev1.Container{
|
||||
ImagePullPolicy: corev1.PullAlways,
|
||||
Command: []string{"/usr/bin/edgevpn"},
|
||||
Args: []string{cmd, entanglementService, fmt.Sprintf("%s:%s", host, entanglementPort)},
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: "EDGEVPNTOKEN",
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
SecretKeyRef: &corev1.SecretKeySelector{
|
||||
Key: "network_token",
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: entanglementName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Args: []string{cmd, entanglementService, fmt.Sprintf("%s:%s", host, entanglementPort), "--log-level", w.LogLevel},
|
||||
Env: envs,
|
||||
SecurityContext: &corev1.SecurityContext{Privileged: &privileged},
|
||||
Name: "entanglement",
|
||||
Image: w.SidecarImage,
|
||||
|
||||
11
main.go
11
main.go
@@ -57,8 +57,13 @@ func main() {
|
||||
var metricsAddr string
|
||||
var enableLeaderElection bool
|
||||
var probeAddr string
|
||||
var serviceImage string
|
||||
var logLevel string
|
||||
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
|
||||
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
|
||||
flag.StringVar(&serviceImage, "service-image", defaultImage, "The image used to create services.")
|
||||
flag.StringVar(&logLevel, "service-log-level", "debug", "The log level of the sidecar container.")
|
||||
|
||||
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
|
||||
"Enable leader election for controller manager. "+
|
||||
"Enabling this will ensure there is only one active controller manager.")
|
||||
@@ -97,15 +102,17 @@ func main() {
|
||||
if err = (&controllers.EntanglementReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
EntangleServiceImage: defaultImage,
|
||||
EntangleServiceImage: serviceImage,
|
||||
LogLevel: logLevel,
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "Entanglement")
|
||||
os.Exit(1)
|
||||
}
|
||||
if err = (&webhooks.Webhook{
|
||||
Client: mgr.GetClient(),
|
||||
SidecarImage: defaultImage,
|
||||
SidecarImage: serviceImage,
|
||||
Scheme: mgr.GetScheme(),
|
||||
LogLevel: logLevel,
|
||||
}).SetupWebhookWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create webhook", "webhook", "Pod")
|
||||
os.Exit(1)
|
||||
|
||||
Reference in New Issue
Block a user