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.ferr...@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"