7 Commits

Author SHA1 Message Date
Dimitris Karakasilis
2250450b88 Don't mutate original Pod's labels
because we don't want to, plus annotations may not fit in labels
because they have different character limit.

Fix for error about 63 character limit

Signed-off-by: Dimitris Karakasilis <jimmykarily@gmail.com>
2022-12-06 12:41:42 +00:00
Ettore Di Giacinto
b4a78705dd Inject nethost by default in entangled pods 2022-12-05 16:09:07 +01:00
mudler
b25f31d789 Add kubesplit target 2022-11-14 23:58:12 +01:00
mudler
daab27404c Allow to propagate envs 2022-11-14 23:52:21 +01:00
Ettore Di Giacinto
1da47ac24f Allow to set service image and loglevel from CLI 2022-11-11 08:03:59 +00:00
Ettore Di Giacinto
297f557a04 Read both annotations and labels 2022-11-11 08:03:25 +00:00
Ettore Di Giacinto
c3cd5f5654 Update README.md 2022-09-23 23:08:07 +02:00
9 changed files with 231 additions and 119 deletions

View File

@@ -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

View File

@@ -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
Youll 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

View File

@@ -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"`
}

View File

@@ -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)

View File

@@ -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:

View File

@@ -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{

View File

@@ -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
}

View File

@@ -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
View File

@@ -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)