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"