 Hello everyone, I hope everyone is having a fantastic time as I am having. My name is Valentina Rodriguez, I'm the moderator for this session. I'm a principal technical marketing manager and I am here with Ian Losson, who will be talking about client-native application ML, the composition with client-native and OpenShift. He already told me that he will be doing some slides and demonstrations, so I'm really looking forward to this session. Ian, I hand it over to you. Cheers, thank you. Hi everybody, my name is Ian Losson. I am a developer advocate that works for Red Hat. I've been a Red Hat about 10 years now. I've only just started being a developer advocate. Prior to this, I was actually a domain solution architect working with customers. But before I was at Red Hat, I focused completely on artificial intelligence. I've been working with AI and AI systems for about 25 years. And I've been doing a lot of developments that have required a huge amount of compute, a huge amount of complexity and a huge amount of just physical system resources. And it's only in the last year or so when I started playing with Knative, which is a new part of OpenShift, a part of Kubernetes, that I've really seen a way in which we can actually use these new technologies to produce some very, very cool artificial intelligence machine learning and just all-round really nice cloud-native applications. So what we're going to talk about today is I'm going to give you an introduction to the concept of cloud-native applications, how OpenShift works under the covers. I'm then going to go into Knative, which is our version of what we call serverless. And then after that, I'm going to show you an example of a fully decomposed artificial intelligence system using the serverless constructs running on OpenShift itself. So it's going to be a bit of a beginner session in terms of actually introducing things and then we're going to dive deep into the tech as we go. So we can start with what is a cloud-native application because we use this phrase a lot and it's something that has come around over time. In the old days with legacy applications, applications were quite big. They were monolithic. We went through a phase of doing microservices where we break these applications down into smaller services. But it's only now when we get to the point of actually producing cloud-native applications that we can produce atomic components. I'm going to talk about that later. But cloud-native applications are a collection of small and independent and loosely coupled services. They basically know only what they need to know in terms of their functionality. So you get less of these large legacy applications and more of these microservices and distributed functionality applications. And with the cloud-native applications, components that are theoretically designed to be ephemeral. You can get around that. You can have things like persisted volumes and stuff like that. But when I'm talking to customers normally, I describe them as sausage machines. These things will fire up. You provide through dependency injection all the things they need to actually know what they're doing from a context perspective. They process it and then they go away. Now, with cloud-native applications, from my perspective, atomic decomposition, which is breaking a task down to the smallest functional unit, is critical in actually producing something that you can actually use, reuse, and introduces a huge amount of agility into your application spaces. Before we get on to actually talking a little bit more depth about atomic decomposition, I want to give you a quick overview of OpenShift. And basically what I've done with this is I have to dump these things down so I understand it because these things are way too complicated for me. So the OpenShift architecture is you have what's called a control plane. That control plane is what you talk to. Every time you interact with OpenShift or Kubernetes, all you're doing is asking it to create objects or change the state of objects. That's all it is. How it actually does it is it uses what's called worker nodes, which are these actually, I used to call them buckets, that sit behind the control plane that actually physicalize the objects that you're asking to be created in the control plane itself. The control plane and the worker node sit on a software-defined network. And if you expand the concept of a worker node, the worker node has a little robot on it called a kubelet. And that kubelet basically responds to state changes of the object that it's physicalizing, and it physicalizes them as pods. So you have this distinction between when you're talking to the control plane or OpenShift or Kubernetes, you as a user are interacting directly with the brain, with the ETCD cluster data store. And in the background, if the object has to be physicalized, then it is passed off to an individual kubelet on a worker node and it's created as a pod. Everything in OpenShift is part of what we call a desired state engine. When you interact with OpenShift, it's all declarative requirements of state. You're saying, I want this object or I want to change the state of this object The control plane receives these requests, alters the desired state of the system, then tasks the worker nodes to realize the requirements if there's a physical aspect to them. Some of the objects such as services and routes are changes to the configuration of the network. But if you're creating an application, if you're creating pods, that's a physical creation that has to be done at a worker node. The worker node then instantiates the required components and reports back after the health, initial and ongoing of the required state. If the components are not healthy, the control plane can task other worker nodes to instantiate them. So for example, if you've got a worker node and you've told the kubelet to create a pod and that pod fails or the worker node fails itself, the actual control plane can see that that state isn't there, isn't reconciled, and it can actually retask another worker node to do it. This abstract approach is called the reconciliation loop. It's a fantastically elegant way of doing things. There are some gotchas, especially when you have something called a crash loop back off. But I like the idea that you actually ask for a desired state, and in the background it will eventually create the actual physicalization of that state. What is an object model? So this is a nice thing about OpenShift. Everything in OpenShift is an object. So when we're talking to the control plane, the brain contains objects. And actually everything within OpenShift, including OpenShift, is represented as objects. You have objects right down to the pods that are running on the worker nodes, and you have objects representing the nodes themselves. You have objects representing the machines that underpin the nodes. And OpenShift works with these objects as currency. When you interact with the control plane, you request to create new instances, edit existing instances, and remove instances of these object currencies. And the control plane actually consists of just a number of wild true loops, which are called controllers, each of which own an object type. So for example, there'd be a controller for a pod when you pass a request to create a pod, that event will drop into the pod controller, and the pod controller would control the physicalization and the maintenance of the state and the brain. The control plane consumes the events generated by API calls that are based around an object, and the controller for the object consumes the event and acts upon it, creating, deleting, or editing the object in the state, in the ETCD in the brain. The object state is updated in ETCD, and if any reconciliation, i.e. creating the pod, changing the state to the pod, has to be done physically, then it'll task a couplet to do it. And the couplet syndrome of a working node will do what it needs to do and then report back when there are state changes. That's that desired state mechanism. But it's all based around this object model. And also in OpenShift, everything can be represented as code. So not only have you got everything physically with an OpenShift being represented by object, they can actually be represented by code. And as I've said before, it's a declarative API. So when you're interacting with OpenShift and you want to create something, you throw a piece of YAML at it. You don't target an object-specific API endpoint. You just provide this chunk of YAML, which tells the actual endpoint what object it wants to interact with. So you'll see a lot of kind equals and all those kind of things. OpenShift also applies a tightly controlled R-bag, role-based access control, on the objects themselves, and who can do what to objects from an action perspective. So that was a very quick introduction to the concept of what OpenShift does in terms of the objects and the events. We're now going to look at what we call atomic decomposition. And it's all about making, from my perspective, it's all about making an application agile. Because in the old days when we used to write applications, I've been writing applications for 35 years, our application was a big chunk of monolithic code that sat on a machine. And everything that application needed to do was done within the application itself. And if you look on the right-hand side here, I've got a representation of an application where it's got a number of functional components that are actually within the application itself. And that was okay, but if you had to fix something, or if it was to change something, or once it enhanced a part of that application, you'd have to build the whole thing. So it was a legacy monolith that brought everything with it. By breaking that application to functional units and making them stand alone, we made the applications more agile. And this was kind of the advent of microservices where we break out a chunk of functionality into a service, into a service endpoint, and then have the application as a number of calls into the service endpoint to do the things it needed to do. And that was cool, that was absolutely brilliant. But what we're doing now is we've got this concept of what's called atomic decomposition, and that's breaking down one of those functional units to its absolute smallest functional components. And what we mean by that is that each of the functional components becomes an orchestrator of the other smaller components. If you look on the right-hand side here, the cloud native applications we described on the previous slide is basically a loosely bound set of functional components. What we're talking about with atomic decomposition is for every single one of those functional components that does something, it becomes a loose mapping of individual atomic components. And the atomic components, the smallest possible thing you can do. The reason we're talking about this is when we get onto the cognitive serverless technology, you'll see there's a fantastic way to actually write these things. They're very small, they're very quick, and they're only resident on the OpenShift cluster whilst they're being called. And that's the point of this. Going back to a legacy application, when you're running a legacy application, the full thing is sat there on the machine consuming resources. When you're running microservices, especially in a Kubernetes or an OpenShift cluster, those pods that actually contain the microservices are running 24-7. They're waiting for traffic. They have to be there to wait, to be a physical endpoint for the services to get the traffic into them. So often you have an application that's called once a day, and it's sat there 24-7 consuming resources, and when it gets called, it processes it. But for 24 hours, it's just sat there consuming the resources. As I said, the more atomic decomposition is applied, the smaller and more varied the subcomponents become. So when you break a functional component down to atomic components and get them as small as possible, like additives, subtractive, store a piece of data, access a file, you find you can put these combinations together very easily to make complex systems or make complex functional components, which you can then create more complex actual cloud-native applications. So this atomic decomposition is breaking down the functionality. So you have, like, a core target of your functionality. You break it down into little units, and then each of the units, you break down into atomic components. So it's this decomposition concept. So now I'm going to talk very quickly about Knative itself, which is our serverless technology. So Knative is the Kubernetes-native implementation of serverless technology. I hate the terminology serverless. It's a daft terminology because there always is a server. It's just, it's somebody else's server. The concept with serverless is that you're making a call to a service and you don't have to stand up and mission me for that service. You don't have to start the service itself. You've just got an endpoint, you call and you get a response, and there you go. From the open source project site, Knative is an open source enterprise-level solution to build serverless and event-driven applications. And this is where it gets cool. So Knative is actually two things. It's the serverless concept, which we'll talk about in a second called Knative Serving. And it's another set of APIs that it actually adds on top of it to encourage the building of event-driven architectures. And when we get onto the demo, when we get onto the atomic decomposition that's being implemented as part of Knative serverless applications, you'll see that that EDA approach makes it incredibly easy to create these complex systems to just spin up and spin down when you need the individual components. So Knative provides a set of additional objects, because remember I said that everything in OpenShift is an object. It creates a set of additional objects in the OpenShift lexicon based through the custom resource definitions that allow the management of serverless components. These are independent of the actual application objects, and that's an important thing. So when you write an application, when you produce an image, when you deploy a pod, using Knative service doesn't change that. In fact, if you've been developing on OpenShift for the last three or four years, and you've got preexisting images or you've got source material, you can build with source to image to produce containers and all those kind of things, you can make them serverless in an incredibly easy way. In fact, what we've done if you've used OpenShift for lately is that when you're actually creating an application through the developer topology, all you have to do is set an option saying make this serverless. There's no code changes to the underlying application. This just provides additional objects that get the OpenShift cluster to treat your application as a serverless application. And it actually has two distinct components, which I mentioned before, for different paradigms of approach. So we've got something called Knative serving. And this provides a set of objects slash constructs for controlling traffic-driven serverless components. We also have Knative eventing and this provides a set of APIs for putting event-driven architecture over the top of those Knative serving applications. And it actually introduces a fantastic thing called Cloud Events, which has made my life incredibly easy. And we'll get on to that when we get on to the eventing side. So the first component of this is the Knative serving, which is what I call traffic-based serverless. To put it basic, in a very basic way, what it does, it provides four additional objects. The first object is what's called a service. And again, apologies. We tend to name things very, very similar. So you've got an OpenShift service, which is an SDN endpoint for ingressing traffic into a pod. And you've got a Knative service, which is kind of a replacement for it. But what the service is, the actual object is a controller object that orchestrates the other components within the serverless. So the Knative service object is this is my service application. It actually controls two objects directly. One is called the configuration. And that defines the required state of the application deployment. So that says what image I'm using, what the timeout is for waiting for traffic, what the environment variables are. It's the whole deployment information for deploying an instance of that application. But we don't actually use the configuration directly to deploy the application. We create an additional object called a revision. And a revision is an instance of the Knative serverless app using the latest configuration. And the interesting thing with this is if you change the Knative service, you change the configuration, the base configuration of the application you need, you will generate another revision. The revisions are immutable. So let's say you've got an application that uses an image from key.io and has an environment variable called mva. You do that as the actual original configuration. You then come along and say, well, I want to add an additional environment variable to my deployment. So you actually push a change to the configuration and you add nv, a second environment variable. Instead of deleting the previous revision, it will create a new revision and that will be the latest revision with nva and nv. However, the previous revision still exists. And the other object that we provide is called a root. And that maps the network endpoint to the actual one or more revisions. So when you're actually coming into the actual application, you can have, for example, have your traffic go into the latest revision and 40% go into the previous revision and you've got any number of percentages you can change on the actual flow of the traffic coming in. The mechanics are where it gets very, very sweet. Now, a Kinetiv serving application initially deploys without traffic, giving the settings defined on the configuration. So when you create a Kinetiv service and you say, this is the image I want and this is the timeout and this is the environment variables and this is the config wrap and all those kind of things, it will automatically spin up an instance of that application. After it's become healthy, it will wait for the timeout that you've asked for and then it scales it down to zero. And this is the cool thing. A Kinetiv serving serverless application, all it really does is it scales the pods down to zero so there's no physical pods. Now, if you're running Kubernetes, you're running OpenShift, you're running a deployment and you scale your deployment down to zero, it removes the pods, but it leaves the service point there. So when you pass traffic into your OpenShift, your Kubernetes system, you get an error. Say that application is not ready, application is not available. How Kinetiv serving works is as soon as it gets traffic coming into the service point, if the application is not resident, it will spin it up. Once it's spun up, it will start to process the thing. If it gets no more traffic in on that port, on that service point, or its timer expires, it will then scale it down to zero after the fact. Now, what's lovely about this is that when you deploy these applications or when you place them on the cluster themselves, the size of this application does not contribute to the node placement algorithm. What I mean by that is every time you actually push a new deployment, excuse me, to an OpenShift cluster, it chooses which worker node to place that deployment on and it'll do it based on the desired sizing of the application itself. How it does that is it looks at all the deployments that are actually on that physical worker node and it comes up with whether there's room to add another deployment based on the deployment size. The algorithm does not use the Kinetiv applications. So you could deploy a thousand Kinetiv applications to a single worker node and you can just keep deploying these applications. They only exist when they're being called. Now, how it actually works, in a flood where you get too many applications running on the worker node, is the Kinetiv serving engine is clever enough to spot that it can no longer create that application in that space or move it to another worker node. But it basically gives you the ability to over-commit a huge amount of applications to your worker nodes because they don't actually exist other than when they're being called. That's the key thing about the Kinetiv serving stuff. It's incredibly efficient because your applications, let's say you've got an application that's six microservices. Five of the microservices are called once a day and the other microservice is called 1,700 milliseconds. Standard OpenShift, standard Kubernetes, you'd have six pods each containing a microservice and they would be resident all the time. So they'd be consuming resources 24-7. If you make five of them Kinetiv serverless because they're called once a day, when they're not being called, they consume zero resource. So you don't have any resource consumption. So you've got plenty of resource to do other things. They only pop up, they only exist, they only consume resource when they're being called. And that's the kind of interpretation we've got of the serverless thing. But it makes for highly efficient placement of applications. Combine that with atomic decomposition where you're making things incredibly small, single process, single action applications that can spin up and spin down very fast. And you can build incredibly complex applications what would be a normal huge application footprint in a very, very small system resource space. And that's the key behind all of this. And I say it, what when traffic arrives at the root is forward appropriately to the revisions. In the case of one revision, for instance, this defaults to 100% the traffic going to one revision. Remember that revisions are immutable versions of the configuration based on configuration changes. If the application is scaled down to zero replicas at the time when traffic arrives, then the K-native serving engine scales it back up according to the behavior stated in the configuration. Because not only can you scale it from zero to one, you can actually put definitions in the configuration to scale it upward from one based on network traffic. So on a standard OpenShift system, you can do what we call horizontal port auto scaling where you can scale an application based on its CPU or its memory usage. And what it does is if you're running an application and it suddenly gets floods and its CPU gets too high based on the threshold you give, it will scale up the pods and then take an aggregate CPU resource until it comes down to the CPU resource that you're targeting. And then if you fall under that CPU resource consumption, it will remove replicas of the pod. But we only offer that on a standard OpenShift system without using K-native serverless against CPU or memory. What we do with the configuration within the K-native serving is allow you to put scaling of the application based on the network traffic, i.e. how many concurrent connections my application currently has or how much throughput I want to do, how many requests per second. And you can do a combination of both of these. So not only can your application scale down to zero or your microservice or your atomic components scale down to zero, but if it starts getting trashed, you can scale it up based on the traffic. So it offers you basically network traffic auto scaling on the applications themselves, which is very, very cool. And I think instances of people using K-native serving where it never scales down to zero. So they've got a system where they have a situation where they want to always be processing requests in real time. So there's no weight when it spins back up, but they want it to be able to handle floods where you get 100 requests at once or 1,000 requests at once. So they have this ability to scale up and scale down the replica count based on the actual network traffic, which is a very cool feature. Just describe that, but here is the actual slide for it. The scaling can be driven by concurrency, which is the maximum number of simultaneous requests per replica and requests per second for each of the replica. So you can say I can handle a maximum of, let's say, 10 concurrent requests, and also I can handle, let's say, 100 requests per second. And it can scale up and scale down the number of replicas to make sure that every single replica is only having 10 concurrent connections and only processing 100 requests per second. So that's very, very cool. And it can also be set per revision. So you can actually have a situation where you've got two different revisions that are actually working at different response types based on the network traffic. I'll say for revisions, this is done simply through annotations in the service definition. So when you change an annotation in the service definition, you change the configuration, you create a new revision. So that's the kind of serving side. The other side is eventing. And this is where it gets a little bit clearer. This is the nice side from a developer perspective. Okay, native eventing provides the ability to hook the concepts of serverless into event-driven capabilities. So what we talked about so far is just the mechanics of being able to spin up and spin down these applications based on definitions, configurations, and network traffic. What this does is it actually decouples the applications from network traffic and allows you to set up the applications being triggered by events rather than traffic. So eventing is a set of API for routing events from producers to consumers. And we also talk about consumers as what we call syncs. And eventing provides and conforms to something called the cloud event specification, which provides a superbly simple and code agnostic way of bundling events. If you've ever written event-based systems in the past, and I've written a lot of them, you find a lot of your code is normally boilerplate code for handling the mechanisms of the events, how you talk to the brokers, how you translate the events. It's all, there's a lot of kind of baggage when you write an event-based application. What we've done with cloud events is we've simplified the mechanism for transmitting and driving the reaction points based on the cloud event itself. And put simply the eventing side of Knative allows for the creation of serverless components that are driven by event rather than traffic. So rather having a situation where the application is waiting for traffic to come in and traffic causes it to spin up from zero and then after a time-out spin back to zero, it's all done by the arrival of events into the application. And those events, you don't have to write specific code, you just have to configure your code to use an API that can receive these events. And we've got a lot of these APIs available for Node. There's a Quarkus extension to do something called Funky, F-U-N-Q-Y. And it's incredibly easy to write this. In the demonstration I'll show you, I've actually got a number of Quarkus applications that are acting as atomic components. And I also use Camel K, which allows you to write camel contexts which can directly interact with these events and can generate these events. So eventing versus serving. Eventing is a set of extras that allow for the engineering of EDA, the event-driven architecture applications. The Knative serving applications, the one we talked about before, which spins up from zero and spins down, all those kind of things, they can act as sinks. They can act as consumers of events. So they're driven by events rather than being driven by the traffic. And this allows for a combination of event-driven architecture and extremely efficient resource applications. And together it's a composite solution for building complex EDA systems with on-demand components, which simplifies the design and makes it way more efficient when you're using the resources on the target clusters. I want to talk very quickly about cloud events here because they are incredibly simplistic and they're very, very, very useful. So cloud events are a simple yet extremely powerful concept within eventing. And put very simply, a cloud event consists of a header with three pieces of simple metadata and a payload. And that's it. You can actually satisfy by creating a post request and just putting the headers into the post into the headers of the actual request itself. And the three bits of metadata you have to give every cloud event are ID, which is a unique identifier for that event, source, which is a textual representation of the producer. We talked about the VM producers, which produce cloud events and consumers, or sinks, which consume them, and type, which is the target of the cloud event. The combination of the ID and source provides the unique consumed reference for eventing. I'll explain how that works. So if you've got an application that's generating these events, let's say it's an application called MyApp. You can generate, let's say, an event from MyApp with the ID one and pass it into what we call a broken. I'll talk about broken in a minute. If the broker sees one and MyApp together, sends it off through using targets based on the types, if it receives that combination again, it doesn't process it because the combination of the ID and the source is a unique ID to indicate processing of the cloud event itself. So the combination of those two is the unique ID. Once a broker has seen that unique ID, it's not reprocessed. That's how the broker knows that it's actually sent one down the line. I'll talk about brokers. And it's simplest eventing provides the concept of what we call brokers and triggers. And they're both Kubernetes-native objects that are created by CRDs, the same way that we create the creative services, configurations, revisions and routes. Brokers are namespace-bound cloud event receivers and forwarders. And I'll show you one in action on the demo. When you're working with OpenShift, you work within projects which are extremely similar to Kubernetes namespaces. Your brokers are basically pots that live within the namespace. And they're actually targeted when you want to push a cloud event to them by a combination of the namespace and the broker name, because you use the same ingress point, which happens to be the eventing ingress point. The triggers, you actually register triggers with the brokers and they are forwarders that push events to consumers. So for example, you have a K-native service that's waiting for a certain cloud event. You can have a broker that receives, let's say, an event of type MyEvent. If you registered a trigger with that broker to say whenever you see an event of MyEvent, push it to that K-native service, that event will be instantly delivered to that K-native service. So the triggers act as additional objects that filters and pushes the events to the appropriate consumers. What's nice about it as well is you can have brokers as syncs. So brokers can actually push events to other brokers. So you can do distributed actual event propagation. And we got other things called channels and subscriptions that allow you to do multi-push and multi-cast within this concept as well. So you can build some very, very clever event nets using just a combination of these very, very simple K-native objects. And the triggers use the cloud event ID, if you remember the third metadata field, the ID, to filter cloud events to the appropriate consumer. And what's nice about this is it's all offered through a broker API. So out of the box in OpenShift, we ship a number of different brokers. We have what's called an ephemeral broker, which is the basic broker. That sits in a namespace. When you throw an event to it, it looks through its list of triggers, finds the ones with the appropriate type that's listed in the event, and throws it at those things. And then it's gone. It's an ephemeral actual thing. It's an in-memory broker. It's on Kafka, so you can have a broker that's actually processing cloud events, but it's backed by a Kafka topic. So you push an event or a message into a Kafka topic, and this broker will convert that message off of that topic into a cloud event and push it down to the appropriate applications through the triggers. That's what we call a persisted broker. We also do one for RabbitMQ as well. And I say these are offered through the broker API, but that's extensible. So if you're using in a situation a type of broker that can store or replay these messages, you just have to write an application that conforms to the broker API, produce an image, and then deploy it as a broker within your system. So we talked a little bit about the technologies, giving you a quick understanding of the K-native serverless stuff. And what we can talk about now is the next-generation AI design using K-native. So if you were going to the other ones today, you probably heard about LLMs and the sort of things like Jupyter Notebook and stuff like this. Well, what I've been doing in the background to try and take advantage of K-native serverless is to build an adaptive artificial intelligence from a number of very small atomic K-native hosted event-driven components. And I've called them neurons, so I'm building them basically a neural net, but I'm calling it a K-neural because everything in Kubernetes starts with a K. And each component, each of the individual neurons is stateless. It receives an ID which it actually used to get its memory from a data grid. It then processes the event that's caused it to be called, and then it pushes its memory back to the data grid, but it also generates additional events based on rules that you provide to the neural. So it's becoming a neural net. And if you think about the way that events work, what these things do is they're atomic components, they're K-native serverless, they spin up on demand, they spin up as a result of an event, they process information pushed into them in a cloud-native way, and then they emit events if those rules are satisfied. And I've got a very simple demo I'll show you where I've got a number of these neurons set up that take additive, subtractive, divide, and multiplication and a value. They store initial value within the neuron and then if they exceed a certain threshold, they will throw an additional event back to the broker. If they fall beneath another threshold, they throw a different type of event back to the broker. And I've written some very small camel-K context that actually are driven by the events that are thrown out of the neurons. Now the point with this is that this is incredibly simple, like a basic example, but if you can break any legacy application or a next-generation application down into atomic components and use this event-based architecture to kick them off, use state persistence through, let's say, a data grid or a database so there's no state in the actual applications themselves, you can build some incredibly complex applications just using very simple components. And because those components are being hosted in a serverless way, that means that you can have very, very big and very complex applications that consume very, very little resources. And it's actually, when you think about the way in which you design these and the way you wire them together, it becomes easier to actually engineer them because the atomic components have very simple jobs. They sit there like sausage machines, you pass information into them through an event, they process that information, they get state, they look at the state within themselves and they throw events if they need to. That's a very, very simple concept. Now you can build incredibly complicated systems from a combination of those. So rather than writing, let's say, a 30,000 line program to do something very complicated with data, translation of data, comparison of data, even deal with large language models, you can write individual atomic components and then build your system as a combination of those components. And you get much better system resource consumption because the components you're using the atomic ones only exist on the cluster when they're physically being caught. So that's the theory behind this kind of stuff. Again, I say it's quite a pithy example. I'll switch the demo time, but before I switch the demo, there is a QR code if you want to go to that QR code. That QR code will take you to a web page that has lots of downloadable free ebooks. There's a lot of cool stuff on there you can play with. I'm just going to check the comments, see any questions so far. I don't think they are as far as I can see them. I'm going to switch the demo now. So what we have running here is a standard OpenShift 4 system. What's nice about this is that I've set this up on a cluster and this cluster I've installed all the operators. So I've installed the KATIVE service operator, I've installed the CamelK operator, those are the only two additional operators I need. However, if you want to play with this, if you want to have a go with this, you don't need to have an OpenShift cluster. We have what's called the developer sandbox. So the developer sandbox gives you access to an OpenShift 4 system for 30 days for free. One of the nice things about it is that it actually has the KATIVE serverless components installed. So for example, this is my developer sandbox instance and I've got a basic version of my neural net running on here where I have a broker and I've got two receivers that actually respond to and if we click on, for example, this one you'll see this is a trigger that's waiting on the broker for events of test event one. So if the broker receives a cloud event of test event one, it will spin up this application and push it into it. And you can go and play with that now with the developer sandbox. What I've done with this one is I've actually produced a slightly more complicated system. So what we got here and this shows all of the aspects of the KATIVE serverless stuff we've talked about so far. We have here a KATIVE serving application and this is an application that's sitting waiting for traffic. It's currently running at zero. So if I click on it you'll see it's auto-scaled to zero. It's a node application that I've built into an image that's just sitting there and waiting for traffic. What that actually does is it produces a web page and I'll show you very quickly that allows me to push event types at the broker. Because remember what I said about the broker is name space bound. So this is the broker here. The broker is incredibly simple to define. If we have a look at the actual broker YAML you'll see it's simply a number of annotations and a name and that's all it is. When you create a broker it's like four lines of YAML. If we pop back to the topology we've got a broker in here that's waiting for and if we click on it here you'll see they're going to resources. It has two subscribers. It actually has four subscribers but it has two subscribers that are subscribed through triggers. So we have trigger test one. We look at this. So trigger test one is waiting for test event one and in the case of test event one there's a subscriber called kneuron 0000 401. What you'll see is that I've got a neural net with two neurons. One's 401 one's 402. So that's two, that's the one. There are two triggers in there. Once test event one, once test event two. When I push events into the broker of type test event one they will actually go into this neuron and this neuron is being driven by events not being driven by traffic. If I push test event two that will go into neuron two. Now how these neurons work is they both built from the same image. They look at what you're asking them to do. So the neuron actually looks at an operation you're asking it to do. It's got an internal state which is a number so you could say to this you could pass a message in saying I need you to add 1000 to your current number. I need you to delete 5000 from your number. And it's got two thresholds which actually define a point at which it emits a neuron higher then and the point at which it emits a neuron lower then. What I've got down here are actually two very very simple camel K context that are waiting for those events. One is a neuron high output and one is a neuron low output. So these are actually sat there with triggers waiting for those events and those events are not going to generate. Those events are going to be generated by these neurons that are going to throw them out if they exceed the actual values or fall under the values that are defined. The nice thing about it is if we look at the candidate of service all that stuff I'm talking about if we look for example the details if I go into actions if I edit the service itself you will see that it has environment variables. So all of my information for how that application or how that atomic component is behaving is being driven by environment variables. In this case here what it's saying is it's got an ID which it uses to store its memory it's got a cache which you can talk to to go and get the actual thing itself. It's got a low threshold of 100 and a high threshold of 500. In the case of falling under 100 within itself it throws a low event. Now that low event is K neuron low. If you remember the event that's being listened to on the far side of one of the actual camel K it's listening for a K neuron low. If it exceeds the high event it's throwing a K neuron high and it's got an initial state of 200. So I'm defining how that application is going to behave by the environment variables that are being pushed into it and if you remember when we go back to the topology at this point before we start to push any events onto the system we have nothing running in the actual project. In fact we actually have one thing running and that's the operator for camel K because I've actually installed the camel K operator within the project itself. Now I can show you that if we go to observe I can go to the dashboard for let's say the namespace pods and you can see that we currently have one pod running at the moment which is the actual operator which is handling the creation and the maintenance of the camel K context but apart from that we have nothing running in there. Now what we have actually going there is two neurons that are going to spin up on event and we've got two camel context that are going to spin up and spin down on event. So I'll start the process right now and what I'm going to do is I'm going to use my cloud event emitter and you'll notice as well the cloud event emitter isn't running so nothing is physically running. Before we actually started this I went to the root because this is a traffic based application and I got a web page out of it so I'm saying it with a web page but the backing application is not running. It doesn't currently exist on the ownership side. So what I'm going to do is I'm going to throw a test event one cloud event which contains this payload. So if you remember there are three metadata fields going across the ID is the producer and the actual unique ID the counter. The ID I'm sending is test event one and the actual payload is I'm passing an operator of subtraction with value 2000. This is going to test event one to that trigger is going to push this event into the first neuron. So what I'll do I'll admit it and I'll jump back and what you'll see we're going to jump back very quickly is that cognitive serving pod immediately spins up because that application has received information and it's pushed an event which has driven this neuron to start. I've actually fallen beneath the low threshold so I've instantly said set a low output event which has caused this to fire up. We can go into here very quickly and you'll see it's actually running I can quickly look at the logs and you can see that it's coming with a subtraction operator value 2000 and it's got a state of 200 and the neuron lower threshold was passed so it's thrown an event back at the broker to trigger a low neuron event and what's happened is that has driven this Knative context here which is waiting for that event to spin up as well. I can just go in here what it does is it's very simple it's just basically recorded the system time stamp when it was received so there's not a lot of functionality within it but if we sit here and I talk a little bit for a minute or so you'll watch that after a minute these things will start to shut down because this application here on the right it's actually on the left it's actually going now it hasn't received any traffic and I've set a time out to say if you don't have a certain amount scale yourself down to zero so that is actually removing itself off and it's taking itself away and it's not consuming any resources this one is starting to shut down as well as is this one they go dark blue because what they're doing in the dark blue phases are actually shutting down because I haven't done this before it takes slightly longer than normal but at this point the system is going back so this one has gone down to zero this one has gone down to zero and hopefully in a second this one will go down to zero and we're back to a situation where no resources are being consumed what I'm going to do now we jump back to the cloud event and I'm going to send one to the other neuron and we'll say addition so this time I'm going to pass an event in from the outside that says you know add 20,000 to your content and this should be going to Neuron too so when I click emit cloud event pop back over this will spin up and Neuron number two will kick off and seed the higher threshold which will drive a higher then into the high listener so you can see I've got four different components there that only exist when they're actually being driven by the events this application here has no knowledge of the other application it's driven purely by an event coming through this it doesn't understand the actual end applications it just knows when it falls beneath a certain threshold it throws an event so you can build some fantastically complicated events but they're also highly efficient I'll say if I jump back on to the observe now what you should see and I'll say that this refreshes every 30 seconds or so if I go back into the pod resources you should start to see these pods actually firing up and then disappearing so what you're seeing here is basically resource consumption until the point which it removes it and then there's no resource consumption so at the point at which those pods finish they're removed the thing and the resources of the backend worker node and the cluster itself can be used for something else so the nice thing about this is not only is that incredibly powerful from an efficiency perspective it also encourages you number one to build smaller applications do that atomic decomposition break things down into the smallest possible units but also architect these things from an event driven perspective or from a traffic driven perspective and it requires a bit of a change of thought instead of thinking about what my application needs to do you break your application down into all kind of tiny components that make up the application and then you abstract them in a certain way that they can actually be created have the information provided to them and they don't need to know about anything else you can do transactional stuff with this don't think because it's ephemeral that you can't do transactional thing because one of the nice things we've introduced is the ability to actually use persisted volumes with these services as well because if you're using for example an application in OpenShift or Kubernetes and you want to actually persist data what we mean by that is when you create a container you create a pod in OpenShift you create a file system within that pod and that file system only exists for the duration of the pod when the pod goes away the file system is lost when you recreate it it's recreated from the original immutable image in the OpenShift and Kubernetes days what we do is create a what's called a persisted volume which is an external file system which is mapped and mounted into the file system of the container the container writes to it when the container is destroyed the persisted volume remains and when that thing is recreated anywhere in the system it's reconnected to the PV when we originally built the kernitive serverless we found it was it was too tricky to create the connection from the persisted volume into these applications because they were coming up and going down so fast but we've engineered a solution to it so now you can actually map persisted volumes into your kernitive services so what you could do and one of the applications one of the things I'm thinking of doing is rather than using for example a data grid from my memory state I'm just going to have a kernitive service as a PV attached so when it spins up attaches a file system it writes the files into that file system and when it's scaled down to zero those files are retained when it's spun back up again it's reattached to the PV so we've now got the functionality to use persisted volumes within the kernitive services which makes it you can use it to do transactions, transactional base things but I say I'm coming up to time I think I'm about over three minutes when I should have stopped speaking but I hope that was a useful introduction to what you can do with this and again if you go to the developer sandbox which is available on developers.redhat.com you can register for free for a 30 day access to the sandbox. The nice thing about sandbox is when you're actually on the sandbox itself you've got the ability to export an application so you can build all of your kernitive services, your roots, your triggers, your brokers and then you can export that application to actually produce the YAML which you can then re-import on another cluster so question is it really good to break our applications down to atomic items I think it is and the reason is that when you break your applications down to atomic items you'll find that a lot of those atomic items are duplicates across multiple applications so I'm working with some customers we've broken down some of their big legacy applications into 100, 150 atomic components and then found that 75, 80% of those components are the same across the applications so you talk about having too much clutter but if you're breaking it down into let's say 80% of shared components and you actually get less clutter because you're two applications, three applications, four applications you're going to be reusing the components so there is excuse me there is kind of a fine line between how far you go down in terms of our atomic decomposition you're absolutely right it depends on the applications you're producing but I say when I've been working with customers and we've gone down to the absolute minimum almost the tiniest possible atomic we've found that even though you're generating 100, 200, 500 of these atomic components you get a huge amount of reuse so when you're writing new applications or getting previous legacy applications you find you don't have to do atomic decomposition because they are there any more questions or can I go have a beer okay well thank you so much for this session I really enjoyed it and I have everyone enjoyed it as well especially the demos we love demos so yeah there was a lot of content from how OpenShift works behind the scenes K-native right now talking about the composition, the atomic measure and yeah we now pros and cons and we're talking about when to use that so with that yeah anything else on your side Ian? one thing I would say is the QR code if you go to the QR code it will take you straight to developers.redhat.com it will take you to developer sandbox but there's fantastic free books on there there's a good one on GitOps the podman book that was written by Dan Walsh which is an absolutely brilliant piece of work and it's all free as I said it's a blatant red hat plug with freebies yeah sandbox is one of my favorite places so if you don't know it please take the time to go there and try, you can try not only OpenShift but as Ian was saying it's a ton of content there for you to learn and gain practical knowledge so with that thank you so much for this terrific session it was great I hope everyone learned something new with that we will give you some minutes back for you to have a short break before our last session of the day we'll be talking about microservices and how we manage different protocols of communication such as REST gRPC, GraphQL how that can work within distributed systems I will see you in a few minutes thank you so much cheers all