You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by lb...@apache.org on 2018/12/06 16:06:50 UTC

[camel-k] 03/06: Fix #209: add a Istio trait

This is an automated email from the ASF dual-hosted git repository.

lburgazzoli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel-k.git

commit 11da9119ca8e9ee60e1fccbaef72788e298e0fdd
Author: nferraro <ni...@gmail.com>
AuthorDate: Tue Dec 4 16:49:53 2018 +0100

    Fix #209: add a Istio trait
---
 docs/traits.adoc                       |  17 ++++-
 examples/knative/README.adoc           | 110 +++++++++++++++++++++++++++++++++
 examples/knative/feed.groovy           |   4 ++
 examples/knative/messages-channel.yaml |   9 +++
 examples/knative/printer.groovy        |   4 ++
 examples/knative/splitter.groovy       |   5 ++
 examples/knative/words-channel.yaml    |   9 +++
 pkg/trait/catalog.go                   |   4 ++
 pkg/trait/istio.go                     |  64 +++++++++++++++++++
 pkg/trait/knative.go                   |   3 +
 pkg/util/kubernetes/collection.go      |  25 +++++---
 pkg/util/kubernetes/replace.go         |  10 +++
 12 files changed, 255 insertions(+), 9 deletions(-)

diff --git a/docs/traits.adoc b/docs/traits.adoc
index 130958e..5b91a80 100644
--- a/docs/traits.adoc
+++ b/docs/traits.adoc
@@ -84,8 +84,23 @@ The following is a list of common traits that can be configured by the end users
 
 !===
 
+| istio
+| Knative (Kubernetes, Openshift)
+| Allows to configure outbound traffic for Istio.
+  +
+  +
+  It's enabled by default when the Knative profile is active.
+
+[cols="m,"]
+!===
+
+! istio.allow
+! Configures a (comma-separated) list of CIDR subnets that should not be intercepted by the Istio proxy (`10.0.0.0/8,172.16.0.0/12,192.168.0.0/16` by default).
+
+!===
+
 | service
-| Kubernetes, OpenShift
+| All (Knative in deployment mode)
 | Exposes the integration with a Service resource so that it can be accessed by other applications (or integrations) in the same namespace.
   +
   +
diff --git a/examples/knative/README.adoc b/examples/knative/README.adoc
new file mode 100644
index 0000000..d7f8e9b
--- /dev/null
+++ b/examples/knative/README.adoc
@@ -0,0 +1,110 @@
+Knative Example (Apache Camel K)
+================================
+
+This example shows how Camel K can be used to connect Knative building blocks to create awesome applications.
+
+It's assumed that both Camel K and Knative are properly installed (including Knative Build, Serving and Eventing) into the cluster.
+Refer to the specific documentation to install and configure all components.
+
+We're going to create two channels:
+- messages
+- words
+
+The first channel will contain phrases, while the second one will contains the single words contained in the phrases.
+
+To create the channels (they use the in-memory channel provisioner):
+
+```
+kubectl create -f messages-channel.yaml
+kubectl create -f words-channel.yaml
+```
+
+We can now proceed to install all camel K integrations.
+
+== Install a "Printer"
+
+We'll install a Camel K integration that will print all words from the `words` channel.
+
+Writing a "function" that does this is as simple as writing:
+
+```
+from('knative:channel/words')
+  .convertBodyTo(String.class)
+  .to('log:info')
+```
+
+You can run this integration by running:
+
+```
+kamel run printer.groovy
+```
+
+Under the hood, the Camel K operator does this:
+- Understands that the integration is passive, meaning that it can be activated only using an external HTTP call (the knative consumer endpoint)
+- Materializes the integration as a Knative autoscaling service, integrated in the Istio service mesh
+- Adds a Knative Eventing `Subscription` that points to the autoscaling service
+
+The resulting integration will be scaled to 0 when not used (if you wait ~5 minutes, you'll see it).
+
+== Install a "Splitter"
+
+We're now going to deploy a splitter, using the Camel core Split EIP. The splitter will take all messages from the `messages` channel,
+split them and push the single words into the `words` channel.
+
+The integration code is super simple:
+
+```
+from('knative:channel/messages')
+  .split().tokenize(" ")
+  .log('sending ${body} to words channel')
+  .to('knative:channel/words')
+```
+
+Let's run it with:
+
+```
+kamel run splitter.groovy
+```
+
+This integration will be also materialized as a Knative autoscaling service, because the only entrypoint is passive (waits for a push notification).
+
+== Install a "Feed"
+
+We're going to feed this chain of functions using a timed feed like this:
+
+```
+from('timer:clock?period=3s')
+  .setBody().constant("Hello World from Camel K")
+  .to('knative:channel/messages')
+  .log('sent message to messages channel')
+```
+
+Every 3 seconds, the integration sends a message to the Knative `messages` channel.
+
+Let's run it with:
+
+```
+kamel run feed.groovy
+```
+
+This cannot be materialized into an autoscaling service, but the operator understands it automatically and maps it to a plain Kubernetes Deployment
+(Istio sidecar will be injected).
+
+== Playing around
+
+If you've installed all the services, you'll find that the printer pod will print single words as they arrive from the feed (every 3 seconds, passing by the splitter function).
+
+If you now stop the feed integration (`kamel delete feed`) you will notice that the other services (splitter and printer) will scale down to 0 in few minutes.
+
+And if you reinstall the feed again (`kamel run feed.groovy`), the other integration will scale up again as soon as they receive messages (splitter first, then printer).
+
+You can also play with different kind of feeds. E.g. the following simple feed can be used to bind messages from Telegram to the system:
+
+```
+from('telegram:bots/<put-here-your-botfather-authorization>')
+  .convertBodyTo(String.class)
+  .to('log:info')
+  .to('knative:channel/messages')
+```
+
+Now just send messages to your bot with the Telegram client app to see all single words appearing in the printer service.
diff --git a/examples/knative/feed.groovy b/examples/knative/feed.groovy
new file mode 100644
index 0000000..1e86907
--- /dev/null
+++ b/examples/knative/feed.groovy
@@ -0,0 +1,4 @@
+from('timer:clock?period=3s')
+	.setBody().constant("Hello World from Camel K")
+	.to('knative:channel/messages')
+	.log('sent message to messages channel')
\ No newline at end of file
diff --git a/examples/knative/messages-channel.yaml b/examples/knative/messages-channel.yaml
new file mode 100644
index 0000000..2dcd271
--- /dev/null
+++ b/examples/knative/messages-channel.yaml
@@ -0,0 +1,9 @@
+apiVersion: eventing.knative.dev/v1alpha1
+kind: Channel
+metadata:
+  name: messages
+spec:
+  provisioner:
+    apiVersion: eventing.knative.dev/v1alpha1
+    kind: ClusterChannelProvisioner
+    name: in-memory-channel
\ No newline at end of file
diff --git a/examples/knative/printer.groovy b/examples/knative/printer.groovy
new file mode 100644
index 0000000..58a0068
--- /dev/null
+++ b/examples/knative/printer.groovy
@@ -0,0 +1,4 @@
+
+from('knative:channel/words')
+  .convertBodyTo(String.class)
+  .to('log:info')
diff --git a/examples/knative/splitter.groovy b/examples/knative/splitter.groovy
new file mode 100644
index 0000000..9e848e3
--- /dev/null
+++ b/examples/knative/splitter.groovy
@@ -0,0 +1,5 @@
+
+from('knative:channel/messages')
+  .split().tokenize(" ")
+  .log('sending ${body} to words channel')
+  .to('knative:channel/words')
\ No newline at end of file
diff --git a/examples/knative/words-channel.yaml b/examples/knative/words-channel.yaml
new file mode 100644
index 0000000..ad8640f
--- /dev/null
+++ b/examples/knative/words-channel.yaml
@@ -0,0 +1,9 @@
+apiVersion: eventing.knative.dev/v1alpha1
+kind: Channel
+metadata:
+  name: words
+spec:
+  provisioner:
+    apiVersion: eventing.knative.dev/v1alpha1
+    kind: ClusterChannelProvisioner
+    name: in-memory-channel
\ No newline at end of file
diff --git a/pkg/trait/catalog.go b/pkg/trait/catalog.go
index 1766929..796670a 100644
--- a/pkg/trait/catalog.go
+++ b/pkg/trait/catalog.go
@@ -39,6 +39,7 @@ type Catalog struct {
 	tOwner        Trait
 	tBuilder      Trait
 	tSpringBoot   Trait
+	tIstio	Trait
 }
 
 // NewCatalog creates a new trait Catalog
@@ -54,6 +55,7 @@ func NewCatalog() *Catalog {
 		tOwner:        newOwnerTrait(),
 		tBuilder:      newBuilderTrait(),
 		tSpringBoot:   newSpringBootTrait(),
+		tIstio: newIstioTrait(),
 	}
 }
 
@@ -69,6 +71,7 @@ func (c *Catalog) allTraits() []Trait {
 		c.tOwner,
 		c.tBuilder,
 		c.tSpringBoot,
+		c.tIstio,
 	}
 }
 
@@ -104,6 +107,7 @@ func (c *Catalog) traitsFor(environment *Environment) []Trait {
 			c.tSpringBoot,
 			c.tKnative,
 			c.tDeployment,
+			c.tIstio,
 			c.tOwner,
 		}
 	}
diff --git a/pkg/trait/istio.go b/pkg/trait/istio.go
new file mode 100644
index 0000000..765cbe8
--- /dev/null
+++ b/pkg/trait/istio.go
@@ -0,0 +1,64 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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 trait
+
+import (
+	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+	serving "github.com/knative/serving/pkg/apis/serving/v1alpha1"
+	appsv1 "k8s.io/api/apps/v1"
+)
+
+type istioTrait struct {
+	BaseTrait `property:",squash"`
+	Allow     string `property:"allow"`
+}
+
+const (
+	istioIncludeAnnotation = "traffic.sidecar.istio.io/includeOutboundIPRanges"
+)
+
+func newIstioTrait() *istioTrait {
+	return &istioTrait{
+		BaseTrait: newBaseTrait("istio"),
+		Allow:     "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16",
+	}
+}
+
+func (t *istioTrait) appliesTo(e *Environment) bool {
+	return e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseDeploying
+}
+
+func (t *istioTrait) apply(e *Environment) error {
+	if t.Allow != "" {
+		e.Resources.VisitDeployment(func(d *appsv1.Deployment) {
+			d.Spec.Template.Annotations = t.injectIstioAnnotation(d.Spec.Template.Annotations)
+		})
+		e.Resources.VisitKnativeConfigurationSpec(func(cs *serving.ConfigurationSpec) {
+			cs.RevisionTemplate.Annotations = t.injectIstioAnnotation(cs.RevisionTemplate.Annotations)
+		})
+	}
+	return nil
+}
+
+func (t *istioTrait) injectIstioAnnotation(annotations map[string]string) map[string]string {
+	if annotations == nil {
+		annotations = make(map[string]string)
+	}
+	annotations[istioIncludeAnnotation] = t.Allow
+	return annotations
+}
diff --git a/pkg/trait/knative.go b/pkg/trait/knative.go
index 992b889..786d475 100644
--- a/pkg/trait/knative.go
+++ b/pkg/trait/knative.go
@@ -186,6 +186,9 @@ func (t *knativeTrait) getServiceFor(e *Environment) (*serving.Service, error) {
 			RunLatest: &serving.RunLatestType{
 				Configuration: serving.ConfigurationSpec{
 					RevisionTemplate: serving.RevisionTemplateSpec{
+						ObjectMeta: metav1.ObjectMeta{
+							Labels: labels,
+						},
 						Spec: serving.RevisionSpec{
 							Container: corev1.Container{
 								Image: e.Integration.Status.Image,
diff --git a/pkg/util/kubernetes/collection.go b/pkg/util/kubernetes/collection.go
index 4a770e5..d6b7b72 100644
--- a/pkg/util/kubernetes/collection.go
+++ b/pkg/util/kubernetes/collection.go
@@ -159,22 +159,31 @@ func (c *Collection) VisitKnativeService(visitor func(*serving.Service)) {
 // VisitContainer executes the visitor function on all Containers inside deployments or other resources
 func (c *Collection) VisitContainer(visitor func(container *corev1.Container)) {
 	c.VisitDeployment(func(d *appsv1.Deployment) {
-		for _, c := range d.Spec.Template.Spec.Containers {
-			visitor(&c)
+		for idx := range d.Spec.Template.Spec.Containers {
+			c := &d.Spec.Template.Spec.Containers[idx]
+			visitor(c)
 		}
 	})
+	c.VisitKnativeConfigurationSpec(func(cs *serving.ConfigurationSpec) {
+		c := &cs.RevisionTemplate.Spec.Container
+		visitor(c)
+	})
+}
+
+// VisitKnativeConfigurationSpec executes the visitor function on all knative ConfigurationSpec inside serving Services
+func (c *Collection) VisitKnativeConfigurationSpec(visitor func(container *serving.ConfigurationSpec)) {
 	c.VisitKnativeService(func(s *serving.Service) {
 		if s.Spec.RunLatest != nil {
-			c := s.Spec.RunLatest.Configuration.RevisionTemplate.Spec.Container
-			visitor(&c)
+			c := &s.Spec.RunLatest.Configuration
+			visitor(c)
 		}
 		if s.Spec.Pinned != nil {
-			c := s.Spec.Pinned.Configuration.RevisionTemplate.Spec.Container
-			visitor(&c)
+			c := &s.Spec.Pinned.Configuration
+			visitor(c)
 		}
 		if s.Spec.Release != nil {
-			c := s.Spec.Release.Configuration.RevisionTemplate.Spec.Container
-			visitor(&c)
+			c := &s.Spec.Release.Configuration
+			visitor(c)
 		}
 	})
 }
diff --git a/pkg/util/kubernetes/replace.go b/pkg/util/kubernetes/replace.go
index 45b908d..ec14b16 100644
--- a/pkg/util/kubernetes/replace.go
+++ b/pkg/util/kubernetes/replace.go
@@ -25,6 +25,7 @@ import (
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
+	eventing "github.com/knative/eventing/pkg/apis/eventing/v1alpha1"
 )
 
 // ReplaceResources allows to completely replace a list of resources on Kubernetes, taking care of immutable fields and resource versions
@@ -50,6 +51,7 @@ func ReplaceResource(res runtime.Object) error {
 		mapRequiredMeta(existing, res)
 		mapRequiredServiceData(existing, res)
 		mapRequiredRouteData(existing, res)
+		mapRequiredKnativeData(existing, res)
 		err = sdk.Update(res)
 	}
 	if err != nil {
@@ -82,6 +84,14 @@ func mapRequiredRouteData(from runtime.Object, to runtime.Object) {
 	}
 }
 
+func mapRequiredKnativeData(from runtime.Object, to runtime.Object) {
+	if fromC, ok := from.(*eventing.Subscription); ok {
+		if toC, ok := to.(*eventing.Subscription); ok {
+			toC.Spec.Generation = fromC.Spec.Generation
+		}
+	}
+}
+
 func findResourceDetails(res runtime.Object) string {
 	if res == nil {
 		return "nil resource"