🌱 Add daemonset

Signed-off-by: mudler <mudler@c3os.io>
This commit is contained in:
mudler 2022-12-28 19:02:29 +01:00
parent ca06cf47e4
commit 737bae9d77
17 changed files with 685 additions and 1 deletions

View File

@ -19,4 +19,13 @@ resources:
webhooks:
defaulting: true
webhookVersion: v1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: kairos.io
group: entangle
kind: VPN
path: github.com/kairos-io/entangle/api/v1alpha1
version: v1alpha1
version: "3"

62
api/v1alpha1/vpn_types.go Normal file
View File

@ -0,0 +1,62 @@
/*
Copyright 2022.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// VPNSpec defines the desired state of VPN
type VPNSpec struct {
SecretRef *string `json:"secretRef,omitempty"`
Env []v1.EnvVar `json:"env,omitempty"`
}
// VPNStatus defines the observed state of VPN
type VPNStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
}
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
// VPN is the Schema for the vpns API
type VPN struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec VPNSpec `json:"spec,omitempty"`
Status VPNStatus `json:"status,omitempty"`
}
//+kubebuilder:object:root=true
// VPNList contains a list of VPN
type VPNList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []VPN `json:"items"`
}
func init() {
SchemeBuilder.Register(&VPN{}, &VPNList{})
}

View File

@ -136,3 +136,104 @@ func (in *EntanglementStatus) DeepCopy() *EntanglementStatus {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VPN) DeepCopyInto(out *VPN) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VPN.
func (in *VPN) DeepCopy() *VPN {
if in == nil {
return nil
}
out := new(VPN)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *VPN) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VPNList) DeepCopyInto(out *VPNList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]VPN, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VPNList.
func (in *VPNList) DeepCopy() *VPNList {
if in == nil {
return nil
}
out := new(VPNList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *VPNList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VPNSpec) DeepCopyInto(out *VPNSpec) {
*out = *in
if in.SecretRef != nil {
in, out := &in.SecretRef, &out.SecretRef
*out = new(string)
**out = **in
}
if in.Env != nil {
in, out := &in.Env, &out.Env
*out = make([]v1.EnvVar, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VPNSpec.
func (in *VPNSpec) DeepCopy() *VPNSpec {
if in == nil {
return nil
}
out := new(VPNSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VPNStatus) DeepCopyInto(out *VPNStatus) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VPNStatus.
func (in *VPNStatus) DeepCopy() *VPNStatus {
if in == nil {
return nil
}
out := new(VPNStatus)
in.DeepCopyInto(out)
return out
}

View File

@ -0,0 +1,152 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.9.0
creationTimestamp: null
name: vpns.entangle.kairos.io
spec:
group: entangle.kairos.io
names:
kind: VPN
listKind: VPNList
plural: vpns
singular: vpn
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: VPN is the Schema for the vpns API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: VPNSpec defines the desired state of VPN
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
secretRef:
type: string
type: object
status:
description: VPNStatus defines the observed state of VPN
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@ -3,17 +3,20 @@
# It should be run by config/default
resources:
- bases/entangle.kairos.io_entanglements.yaml
- bases/entangle.kairos.io_vpns.yaml
#+kubebuilder:scaffold:crdkustomizeresource
patchesStrategicMerge:
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
# patches here are for enabling the conversion webhook for each CRD
#- patches/webhook_in_entanglements.yaml
#- patches/webhook_in_vpns.yaml
#+kubebuilder:scaffold:crdkustomizewebhookpatch
# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.
# patches here are for enabling the CA injection for each CRD
#- patches/cainjection_in_entanglements.yaml
#- patches/cainjection_in_vpns.yaml
#+kubebuilder:scaffold:crdkustomizecainjectionpatch
# the following config is for teaching kustomize how to do kustomization for CRDs.

View File

@ -0,0 +1,7 @@
# The following patch adds a directive for certmanager to inject CA into the CRD
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
name: vpns.entangle.kairos.io

View File

@ -0,0 +1,16 @@
# The following patch enables a conversion webhook for the CRD
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: vpns.entangle.kairos.io
spec:
conversion:
strategy: Webhook
webhook:
clientConfig:
service:
namespace: system
name: webhook-service
path: /convert
conversionReviewVersions:
- v1

View File

@ -23,6 +23,18 @@ rules:
- get
- list
- watch
- apiGroups:
- apps
resources:
- daemonsets
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- apps
resources:
@ -61,3 +73,29 @@ rules:
- get
- patch
- update
- apiGroups:
- entangle.kairos.io
resources:
- vpns
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- entangle.kairos.io
resources:
- vpns/finalizers
verbs:
- update
- apiGroups:
- entangle.kairos.io
resources:
- vpns/status
verbs:
- get
- patch
- update

View File

@ -0,0 +1,31 @@
# permissions for end users to edit vpns.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: clusterrole
app.kubernetes.io/instance: vpn-editor-role
app.kubernetes.io/component: rbac
app.kubernetes.io/created-by: entangle
app.kubernetes.io/part-of: entangle
app.kubernetes.io/managed-by: kustomize
name: vpn-editor-role
rules:
- apiGroups:
- entangle.kairos.io
resources:
- vpns
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- entangle.kairos.io
resources:
- vpns/status
verbs:
- get

View File

@ -0,0 +1,27 @@
# permissions for end users to view vpns.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: clusterrole
app.kubernetes.io/instance: vpn-viewer-role
app.kubernetes.io/component: rbac
app.kubernetes.io/created-by: entangle
app.kubernetes.io/part-of: entangle
app.kubernetes.io/managed-by: kustomize
name: vpn-viewer-role
rules:
- apiGroups:
- entangle.kairos.io
resources:
- vpns
verbs:
- get
- list
- watch
- apiGroups:
- entangle.kairos.io
resources:
- vpns/status
verbs:
- get

View File

@ -0,0 +1,12 @@
apiVersion: entangle.kairos.io/v1alpha1
kind: VPN
metadata:
labels:
app.kubernetes.io/name: vpn
app.kubernetes.io/instance: vpn-sample
app.kubernetes.io/part-of: entangle
app.kubernetes.io/managed-by: kustomize
app.kubernetes.io/created-by: entangle
name: vpn-sample
spec:
# TODO(user): Add fields here

View File

@ -1,4 +1,5 @@
## Append samples you want in your CSV to this file as resources ##
resources:
- entangle_v1alpha1_entanglement.yaml
- entangle_v1alpha1_vpn.yaml
#+kubebuilder:scaffold:manifestskustomizesamples

87
controllers/daemonset.go Normal file
View File

@ -0,0 +1,87 @@
package controllers
import (
entanglev1alpha1 "github.com/kairos-io/entangle/api/v1alpha1"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func genDaemonsetOwner(ent entanglev1alpha1.VPN) []metav1.OwnerReference {
return []metav1.OwnerReference{
*metav1.NewControllerRef(&ent.ObjectMeta, schema.GroupVersionKind{
Group: entanglev1alpha1.GroupVersion.Group,
Version: entanglev1alpha1.GroupVersion.Version,
Kind: "VPN",
}),
}
}
func (r *VPNReconciler) genDaemonset(ent entanglev1alpha1.VPN) (*appsv1.DaemonSet, error) {
objMeta := metav1.ObjectMeta{
Name: ent.Name,
Namespace: ent.Namespace,
OwnerReferences: genDaemonsetOwner(ent),
}
privileged := true
serviceAccount := false
v := ent.Spec.Env
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{
SecurityContext: &v1.SecurityContext{
Privileged: &privileged,
Capabilities: &v1.Capabilities{
Add: []v1.Capability{"NET_ADMIN"},
},
},
ImagePullPolicy: v1.PullAlways,
Name: "vpn",
Image: r.EntangleServiceImage,
Env: v,
Command: []string{"/usr/bin/edgevpn"},
VolumeMounts: []v1.VolumeMount{v1.VolumeMount{Name: "dev-net-tun", MountPath: "/dev/net/tun"}},
}
pod := v1.PodSpec{
Containers: []v1.Container{expose},
AutomountServiceAccountToken: &serviceAccount,
HostNetwork: true,
Volumes: []v1.Volume{v1.Volume{Name: "dev-net-tun", VolumeSource: v1.VolumeSource{HostPath: &v1.HostPathVolumeSource{Path: "/dev/net/tun"}}}},
}
deploymentLabels := getnDaemonsetLabel(ent.Name)
return &appsv1.DaemonSet{
ObjectMeta: objMeta,
Spec: appsv1.DaemonSetSpec{
Selector: &metav1.LabelSelector{MatchLabels: deploymentLabels},
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: deploymentLabels,
},
Spec: pod,
},
},
}, nil
}
func getnDaemonsetLabel(s string) map[string]string {
return map[string]string{
"vpn.kairos.io": s,
}
}

View File

@ -70,7 +70,7 @@ func (r *EntanglementReconciler) Reconcile(ctx context.Context, req ctrl.Request
return ctrl.Result{}, err
}
desiredDeployment, err := r.genDeployment(ent,r.LogLevel)
desiredDeployment, err := r.genDeployment(ent, r.LogLevel)
if err != nil {
return ctrl.Result{}, err
}

View File

@ -0,0 +1,114 @@
/*
Copyright 2022.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controllers
import (
"context"
"fmt"
apierrors "k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
entanglev1alpha1 "github.com/kairos-io/entangle/api/v1alpha1"
)
// VPNReconciler reconciles a VPN object
type VPNReconciler struct {
client.Client
Scheme *runtime.Scheme
clientSet *kubernetes.Clientset
EntangleServiceImage string
}
//+kubebuilder:rbac:groups=entangle.kairos.io,resources=vpns,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=entangle.kairos.io,resources=vpns/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=entangle.kairos.io,resources=vpns/finalizers,verbs=update
//+kubebuilder:rbac:groups=apps,resources=daemonsets,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups="",resources=secrets,verbs=create;get;list;watch
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the VPN object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.0/pkg/reconcile
func (r *VPNReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
// Creates a deployment targeting a service
// TODO(user): your logic here
var ent entanglev1alpha1.VPN
if err := r.Get(ctx, req.NamespacedName, &ent); err != nil {
if apierrors.IsNotFound(err) {
return ctrl.Result{}, nil
}
return ctrl.Result{}, err
}
desiredDaemonset, err := r.genDaemonset(ent)
if err != nil {
return ctrl.Result{}, err
}
daemonset, err := r.clientSet.AppsV1().DaemonSets(req.Namespace).Get(ctx, desiredDaemonset.Name, v1.GetOptions{})
if daemonset == nil || apierrors.IsNotFound(err) {
logger.Info(fmt.Sprintf("Creating Daemonset %v", daemonset))
daemonset, err = r.clientSet.AppsV1().DaemonSets(req.Namespace).Create(ctx, desiredDaemonset, v1.CreateOptions{})
if err != nil {
logger.Error(err, "Failed while creating daemonset")
return ctrl.Result{}, nil
}
return ctrl.Result{Requeue: true}, nil
}
if err != nil {
return ctrl.Result{Requeue: true}, err
}
// // If args or env are missing, update it
// if desiredDaemonset.{
// deployment, err = r.clientSet.AppsV1().Deployments(req.Namespace).Update(ctx, desiredDeployment, v1.UpdateOptions{})
// if err != nil {
// logger.Error(err, "Failed while updating deployment")
// return ctrl.Result{}, nil
// }
// }
return ctrl.Result{}, nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *VPNReconciler) SetupWithManager(mgr ctrl.Manager) error {
clientset, err := kubernetes.NewForConfig(mgr.GetConfig())
if err != nil {
return err
}
r.clientSet = clientset
return ctrl.NewControllerManagedBy(mgr).
For(&entanglev1alpha1.VPN{}).
Complete(r)
}

View File

@ -118,6 +118,14 @@ func main() {
os.Exit(1)
}
if err = (&controllers.VPNReconciler{
Client: mgr.GetClient(),
EntangleServiceImage: serviceImage,
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "VPN")
os.Exit(1)
}
//+kubebuilder:scaffold:builder
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {

16
tests/fixtures/vpn.yaml vendored Normal file
View File

@ -0,0 +1,16 @@
apiVersion: v1
kind: Secret
metadata:
name: mysecret
namespace: entangle-system
type: Opaque
stringData:
network_token: b3RwOgogIGRodDoKICAgIGludGVydmFsOiA5MDAwCiAgICBrZXk6IFVSRDVaMkcySDNKSFVESVpYT1VXQkVPN1VEU0g2TUpGUzJGV01QM1dBRVhaMjZRUTJGUkEKICAgIGxlbmd0aDogMzIKICBjcnlwdG86CiAgICBpbnRlcnZhbDogOTAwMAogICAga2V5OiBKTVJKWVZKN0xFR0lZQktFVFNERzVSRFhESkFKM0dTWVJOUTNUTVRQUkpMUkwzWEZYUVpBCiAgICBsZW5ndGg6IDMyCnJvb206IFJYWlZSNURCN1VWVERGRDc0UzRBTEFKNllHRVFDVlQ0WE5VWExFQTRVM0FDN05ESFFLTVEKcmVuZGV6dm91czogYk9PR21WV0lCV1ptbXBtaW9PcXdhc0dyWExlaXpnTVkKbWRuczogUnN4ZWd6eGZTcFlRRUtqQk1lUEFQelROWUV5ZGRlemEKbWF4X21lc3NhZ2Vfc2l6ZTogMjA5NzE1MjAK
---
apiVersion: entangle.kairos.io/v1alpha1
kind: VPN
metadata:
name: test2
namespace: entangle-system
spec:
secretRef: "mysecret"