Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(notebook): Support Launching Notebooks with CRD Directly #15

Merged
merged 5 commits into from
Apr 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions api/v1alpha1/jupyternotebook_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@ import (
type JupyterNotebookSpec struct {
Gateway *v1.ObjectReference `json:"gateway,omitempty"`

// Compute Resources required by this container.
// Cannot be updated.
// More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/
// +optional
Resources *v1.ResourceRequirements `json:"resources,omitempty"`
Template *v1.PodTemplateSpec `json:"template,omitempty"`
}

// JupyterNotebookStatus defines the observed state of JupyterNotebook
Expand Down
6 changes: 3 additions & 3 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ spec:
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
cpu: "500m"
8 changes: 8 additions & 0 deletions controllers/jupyternotebook_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ import (
"context"

"github.com/go-logr/logr"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/source"

"github.com/tkestack/elastic-jupyter-operator/api/v1alpha1"
kubeflowtkestackiov1alpha1 "github.com/tkestack/elastic-jupyter-operator/api/v1alpha1"
Expand Down Expand Up @@ -74,5 +77,10 @@ func (r *JupyterNotebookReconciler) Reconcile(req ctrl.Request) (ctrl.Result, er
func (r *JupyterNotebookReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&kubeflowtkestackiov1alpha1.JupyterNotebook{}).
Watches(&source.Kind{Type: &appsv1.Deployment{}},
&handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &v1alpha1.JupyterNotebook{},
}).
Complete(r)
}
2 changes: 1 addition & 1 deletion docs/api/generated.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ JupyterNotebookSpec defines the desired state of JupyterNotebook
|===
| Field | Description
| *`gateway`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#objectreference-v1-core[$$ObjectReference$$]__ |
| *`resources`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#resourcerequirements-v1-core[$$ResourceRequirements$$]__ | Compute Resources required by this container. Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/
| *`template`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#podtemplatespec-v1-core[$$PodTemplateSpec$$]__ |
|===


Expand Down
74 changes: 46 additions & 28 deletions pkg/notebook/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package notebook
import (
"fmt"

"github.com/go-logr/logr"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -37,27 +38,65 @@ const (
)

type generator struct {
nb *v1alpha1.JupyterNotebook
nb *v1alpha1.JupyterNotebook
log logr.Logger
}

// newGenerator creates a new Generator.
func newGenerator(nb *v1alpha1.JupyterNotebook) (
func newGenerator(nb *v1alpha1.JupyterNotebook, l logr.Logger) (
*generator, error) {
if nb == nil {
return nil, fmt.Errorf("Got nil when initializing Generator")
}
g := &generator{
nb: nb,
nb: nb,
log: l,
}

return g, nil
}

func (g generator) DesiredDeploymentWithoutOwner() *appsv1.Deployment {
func (g generator) DesiredDeploymentWithoutOwner() (*appsv1.Deployment, error) {
if g.nb.Spec.Template == nil && g.nb.Spec.Gateway == nil {
return nil, fmt.Errorf("no gateway and template applied")
}

podSpec := v1.PodSpec{}
podLabels := g.labels()
labels := g.labels()
selector := &metav1.LabelSelector{
MatchLabels: labels,
}

if g.nb.Spec.Template != nil {
if g.nb.Spec.Template.Labels != nil {
for k, v := range g.nb.Spec.Template.Labels {
podLabels[k] = v
}
}
podSpec = g.nb.Spec.Template.Spec
} else {
podSpec = v1.PodSpec{
Containers: []v1.Container{
{
Name: defaultContainerName,
Image: defaultImage,
ImagePullPolicy: v1.PullIfNotPresent,
Args: []string{
"start-notebook.sh",
},
Ports: []v1.ContainerPort{
{
Name: defaultPortName,
ContainerPort: defaultPort,
Protocol: v1.ProtocolTCP,
},
},
},
},
}
}

d := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: g.nb.Namespace,
Expand All @@ -68,27 +107,9 @@ func (g generator) DesiredDeploymentWithoutOwner() *appsv1.Deployment {
Selector: selector,
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: defaultContainerName,
Image: defaultImage,
ImagePullPolicy: v1.PullIfNotPresent,
Args: []string{
"start-notebook.sh",
},
Ports: []v1.ContainerPort{
{
Name: defaultPortName,
ContainerPort: defaultPort,
Protocol: v1.ProtocolTCP,
},
},
},
},
Labels: podLabels,
},
Spec: podSpec,
},
},
}
Expand All @@ -99,11 +120,8 @@ func (g generator) DesiredDeploymentWithoutOwner() *appsv1.Deployment {
d.Spec.Template.Spec.Containers[0].Args = append(
d.Spec.Template.Spec.Containers[0].Args, "--gateway-url", gatewayURL)
}
if g.nb.Spec.Resources != nil {
d.Spec.Template.Spec.Containers[0].Resources = *g.nb.Spec.Resources
}

return d
return d, nil
}

func (g generator) labels() map[string]string {
Expand Down
27 changes: 23 additions & 4 deletions pkg/notebook/reconcile.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@ import (
"context"

"github.com/go-logr/logr"
"github.com/tkestack/elastic-jupyter-operator/api/v1alpha1"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

"github.com/tkestack/elastic-jupyter-operator/api/v1alpha1"
)

type Reconciler struct {
Expand All @@ -43,7 +45,7 @@ type Reconciler struct {
func NewReconciler(cli client.Client, l logr.Logger,
r record.EventRecorder, s *runtime.Scheme,
i *v1alpha1.JupyterNotebook) (*Reconciler, error) {
g, err := newGenerator(i)
g, err := newGenerator(i, l)
if err != nil {
return nil, err
}
Expand All @@ -65,7 +67,10 @@ func (r Reconciler) Reconcile() error {
}

func (r Reconciler) reconcileDeployment() error {
desired := r.gen.DesiredDeploymentWithoutOwner()
desired, err := r.gen.DesiredDeploymentWithoutOwner()
if err != nil {
return err
}

if err := controllerutil.SetControllerReference(
r.instance, desired, r.scheme); err != nil {
Expand All @@ -75,8 +80,10 @@ func (r Reconciler) reconcileDeployment() error {
}

actual := &appsv1.Deployment{}
err := r.cli.Get(context.TODO(),
err = r.cli.Get(context.TODO(),
types.NamespacedName{Name: desired.GetName(), Namespace: desired.GetNamespace()}, actual)

// Create deployment if not found
if err != nil && errors.IsNotFound(err) {
r.log.Info("Creating deployment", "namespace", desired.Namespace, "name", desired.Name)

Expand All @@ -90,5 +97,17 @@ func (r Reconciler) reconcileDeployment() error {
"deployment", desired.Name)
return err
}

// Update deployment from desired to actural
if !equality.Semantic.DeepEqual(desired.Spec.Template, actual.Spec.Template) {
actual.Spec.Template = desired.Spec.Template
if err := r.cli.Update(context.TODO(), actual); err != nil {
r.log.Error(err, "Failed to update deployment")
return err
} else {
r.log.Info("deployment updated")
}
}

return nil
}