23 Advanced Practical Iv How to Progress From 0 to 1 in Upgrading an Open Source Engine

23 Advanced Practical IV How to progress from 0 to 1 in upgrading an open-source engine #

Hello, I’m Jingyuan.

Starting from this lesson, we will experience the complete process of building a Serverless platform and learn about the precautions involved.

In the first half of today’s class, we will focus on practicing the deployment and verification of Knative core components from the perspectives of environment preparation, component installation, and component validation. In the second half of the class, we will discuss how to learn when facing an open source Serverless framework.

Before we start the hands-on practice, you can review the knowledge points of scaling and traffic forwarding in the Scaling and Traffic Forwarding lessons about Knative.

Without further ado, let’s get started with the hands-on preparation.

Environment Preparation #

Since Knative relies entirely on Kubernetes, we need to prepare a Kubernetes cluster in advance. As for the specifications of the cluster, the official website also provides recommendations, which you can refer to for the latest version.

For the prototype environment, if you just want to build a prototype Knative function environment for trial purposes, you need at least a single-node K8s cluster, and the specifications of this node should be at least 2 cores and 4GB of memory.

If you want to achieve a production-level Knative function environment, the specifications of a single-node cluster should be at least 6 cores, 6GB of memory, and 30GB of disk space. For a multi-node environment, the specifications should ensure that each node has 2 cores, 4GB of memory, and 20GB of disk space. In addition, there are a few points to note:

  • The Kubernetes cluster version must be at least 1.22 or above.
  • The Kubernetes command-line access tool, kubectl, must be installed.
  • Ensure that Kubernetes can access the image repository.

In this lesson, I will build the environment according to the production-level environment setup. The number of K8s cluster nodes is 2, with each node having 4 cores, 8GB of memory, and 50GB of disk space. I have chosen Baidu Intelligent Cloud CCE to build the K8s cluster.

Component Deployment and Verification #

Once the K8s cluster environment is ready, we can start deploying the Knative Serving components on the cluster. Take it step by step.

Deploying the Serving Component #

First, let’s deploy the Serving component by following the commands on the official website:

kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.4.0/serving-crds.yaml
kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.4.0/serving-core.yaml

After deployment, all Pods should be in the Running state under the knative-serving namespace:

$ kubectl get pod -n knative-serving
NAME                                     READY   STATUS    RESTARTS   AGE
activator-847d5df4bc-psd7k               1/1     Running   0          47h
autoscaler-65898c7fcf-wkkmc              1/1     Running   0          46h
controller-775b774844-fwm2k              1/1     Running   0          46h
domain-mapping-555ff9b9c8-8dtcw          1/1     Running   0          46h
domainmapping-webhook-57456fcb8c-x6frp   1/1     Running   0          46h
net-istio-controller-59b6b7765-z8w2g     1/1     Running   0          46h
net-istio-webhook-6b98fd5bd4-4pcdf       1/1     Running   0          46h
webhook-7f987868-5f8bg                   1/1     Running   0          46h

If there are any exceptions, you can use kubectl describe command to check which step the Pod deployment is stuck at. You can refer to the following command for reference:

kubectl describe pod controller-744577dddc-7jf5r -nknative-serving

Note that if there is a network issue, the download of images may get stuck. In this case, you can download the images from a Google Cloud VM and upload them to an accessible image repository, or directly find an accessible image repository and manually replace the gcr.io images.

Deploying the Networking Component #

After completing the deployment of the Serving components, we need to deploy the networking components that Knative relies on. The official website recommends three options, and we will use Istio for deployment:

kubectl apply -l knative.dev/crd-install=true -f https://github.com/knative/net-istio/releases/download/knative-v1.4.0/istio.yaml
kubectl apply -f https://github.com/knative/net-istio/releases/download/knative-v1.4.0/istio.yaml
kubectl apply -f https://github.com/knative/net-istio/releases/download/knative-v1.4.0/net-istio.yaml

After completion, we need to check if the Pods under the istio-system namespace are working properly:

$ k get pod -nistio-system                     
NAME                                    READY   STATUS    RESTARTS   AGE
istio-ingressgateway-666588bf64-jpwgl   1/1     Running   0          46h
istiod-56967d8fcc-d6vmr                 1/1     Running   0          46h
istiod-56967d8fcc-f46kr                 1/1     Running   0          2d2h
istiod-56967d8fcc-mmfkd                 1/1     Running   0          46h

We also need to check if we can get the external IP of the gateway, which will be used for accessing functions later:

$ kubectl --namespace istio-system get service istio-ingressgateway
NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP                  PORT(S)                                      AGE
istio-ingressgateway   LoadBalancer   10.0.142.236   182.61.144.199,192.168.0.2   15021:31462/TCP,80:30977/TCP,443:32684/TCP   2d2h

Here, I need to explain one thing. In Baidu Intelligent Cloud CCE, if the user creates a Service of type LoadBalancer, by default, CCE will create a BLB (Baidu Load Balancer) and bind an EIP (Elastic IP) to it. So you can see that the EIP address displayed in the table is 182.61.144.199.

Deploying Eventing Components #

Next, let’s install the related components for Eventing. The installation of Eventing is similar to Serving and involves two YAML files:

kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.4.1/eventing-crds.yaml
kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.4.1/eventing-core.yaml

After executing the commands, let’s check if the pods in the knative-eventing namespace are running properly:

$ kubectl get pod -n knative-eventing
NAME                             READY   STATUS    RESTARTS   AGE
eventing-controller-c7bdccd6d-ktnmc   1/1     Running   0          13m
eventing-webhook-7c49c58fcb-ng576      1/1     Running   0          13m

If the deployment is successful, you should see two running pods in this namespace. If an error occurs, you can use the kubectl describe command to view the error and take appropriate actions as discussed in the Getting Started lesson.

After deploying Serving and Eventing, you may notice that the Tekton component is missing. I will leave it as an exercise for you to practice further. You can deploy Tekton in the same way. Note that Tekton is incubated from the Building component of Knative and currently has many features and components. It is recommended to follow the step-by-step deployment instructions in the official installation guide to avoid any omissions.

At this point, your Knative components should be running correctly. Next, let’s try executing a simple demo to verify.

Verifying Function Invocation #

Let’s start by verifying the basic function invocation feature of Knative, ensuring that the Serving component is functioning correctly. I will demonstrate using the helloworld-go example provided by the official Knative documentation. I have modified the response content in the handler function to print “Hello Serverless!”:

func handler(w http.ResponseWriter, r *http.Request) {
    log.Print("helloworld: received a request")
  
    fmt.Fprintf(w, "Hello Serverless!\n")
}

After modifying the code, we proceed with building the container image. Please note that if you are using a Mac with an M1 chip, you need to include the --platform linux/amd64 parameter, as shown below:

$ docker --platform linux/amd64 build -t jingyuan/hello:v1 .

Next, you can push the image to a repository accessible by the cluster. I am using CCR (Baidu Cloud Container Registry) here. You can refer to its documentation for specific usage instructions. After that, replace the image value in the service.yaml of the Knative demo and execute kubectl apply:

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: helloworld-go
  namespace: default
spec:
  template:
    spec:
      containers:
      - image: jingyuan/hello:v1
        env:
        - name: TARGET
          value: "Go Sample v1"

Here, I’m using the simplest fields. You can configure different revisions and traffic distribution in the Knative Service. So what will be the result? Let’s find out together.

curl  -H "Host: helloworld-go.default.example.com" http://182.61.144.199

Response:
Hello Serverless!

In this step, if you observe carefully, you will find that there were no related pods of the helloworld-go function in the default namespace before the invocation, but they appeared after the invocation. This is actually the phenomenon of “cold start” caused by the first invocation.

Verifying event triggering #

Finally, let’s validate the event triggering feature of Knative to ensure that the Eventing component is functioning properly. Here, we use the PingSource model provided by the official website for verification. First, we create a service named event-display to receive events. The deployment and service definitions are as follows:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: event-display
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels: &labels
      app: event-display
  template:
    metadata:
      labels: *labels
    spec:
      containers:
        - name: event-display
          image: gcr.io/knative-releases/knative.dev/eventing/cmd/event_display

---

kind: Service
apiVersion: v1
metadata:
  name: event-display
  namespace: default
spec:
  selector:
    app: event-display
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080

Next, we create a corresponding PingSource:

apiVersion: sources.knative.dev/v1
kind: PingSource
metadata:
  name: test-ping
  namespace: default
spec:
  schedule: "*/1 * * * *"
  contentType: "application/json"
  data: '{"message": "Hello geek!"}'
  sink:
    ref:
      apiVersion: v1
      kind: Service
      name: event-display

This definition means that an event will be sent every minute according to the schedule defined in schedule. The data to be sent is specified by data, and the destination to send the event is specified by sink.name, which is the service event-display defined in the previous step.

Next, we can check the deployments in the knative-eventing namespace and find that a new deployment for the PingSource is created. It serves as the event producer and generates events based on the definition of the PingSource object, then sends them to the corresponding service.

$ kubectl get deploy -n knative-eventing
NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
eventing-controller   1/1     1            1           3h36m
eventing-webhook      1/1     1            1           3h36m
pingsource-mt-adapter 1/1     1            1           3h36m

Then, we can check the logs of the pod deployed in the default namespace and see the printed messages every minute:

$ kubectl logs event-display-685f8f46f4-k5dbw
2022/10/07 01:06:32 Failed to read tracing config, using the no-op default: empty json tracing config
☁️  cloudevents.Event
Context Attributes,
  specversion: 1.0
  type: dev.knative.sources.ping
  source: /apis/v1/namespaces/default/pingsources/test-ping
  id: caa8d0f9-f3bb-4c4d-a777-749241a9cb32
  time: 2022-10-07T06:52:00.301997865Z
  datacontenttype: application/json
Data,
  {
    "message": "Hello geek!"
  }
☁️  cloudevents.Event
Context Attributes,
  specversion: 1.0
  type: dev.knative.sources.ping
  source: /apis/v1/namespaces/default/pingsources/test-ping
  id: a029df47-fd4b-4eb4-986d-75d6dd9814d0
  time: 2022-10-07T06:53:00.001908595Z
  datacontenttype: application/json
Data,
  {
    "message": "Hello geek!"
  }
☁️  cloudevents.Event
Context Attributes,
  specversion: 1.0
  type: dev.knative.sources.ping
  source: /apis/v1/namespaces/default/pingsources/test-ping
  id: b4331a71-031f-4b3a-93cb-8764a0c5830e
  time: 2022-10-07T06:54:00.309563083Z
  datacontenttype: application/json
Data,
  {
    "message": "Hello geek!"
  }

So far, we have completed the validation of the basic function invocation and event triggering capabilities of Knative. Let’s summarize the process. The installation of Knative can be roughly divided into three parts: environment preparation, component deployment, and functional verification. This is also the basic approach to install any serverless framework.

  • Environment preparation: Before installation, you need to understand the relevant content in the official installation documentation, especially pay attention to the limitations of resources, operating systems, and dependency versions. Failure to do so may lead to problems that are only exposed at the last step of the installation process, wasting time and effort. For example, this version of Knative specifically requires the Kubernetes version to be 1.22 or above, and in a multi-node cluster, the specification of a single node should be at least 2 cores and 4GB.
  • Component deployment: Here, I suggest following the official installation documentation directly, as many technical blogs on the Internet may not provide complete information, and the versions they discuss may be different from your intended installation. Also, pay attention to access issues with some images. Due to network restrictions, you may need to find domestic mirror sources and replace them to ensure a successful installation.
  • Functional verification: After completing the component deployment, it is important to perform functional verification. In this step, I recommend starting with simple demos instead of complex test cases, to ensure that basic functionalities are working correctly and verify more advanced features after that. This approach allows you to validate the correctness of the component deployment in the shortest possible time and reduces the difficulty of troubleshooting any issues.

How to Learn an Open Source Serverless Engine #

Combining the previous theoretical learning and the hands-on practice in this lesson, I believe you already have a comprehensive understanding of Knative. However, I believe that you will encounter more features of Knative in the process of building a serverless engine. In this course, we cannot cover everything, but you can learn about features such as Triggers, Sinks, CLI, and more by applying the learning methods we have summarized.

What does that mean?

When working with an open source framework, my three-step approach is “get hands-on experience,” “study the architecture,” and “dive into the source code.” Let’s explore each step in detail.

Get Hands-on Experience #

When you encounter any open source serverless engine, I don’t recommend starting by learning various concepts, architectural principles, and source code analysis through online resources. This is not very helpful for someone who has just come into contact with the engine framework, and these complex concepts will only confuse you.

Instead, getting hands-on experience should always be the first step in learning a framework. The experiential feeling it brings can help you understand the basic product form and workflow of the framework more quickly.

In this lesson, I didn’t start by giving you a lengthy introduction to the various concepts of Knative. Instead, I walked you through the entire process of setting up Knative and running two demos. By doing so, you should have a good understanding of the components necessary for Knative to work and observe certain phenomena (such as cold starts and event triggering processes). This kind of practical experience will make your subsequent learning more efficient.

Also, there’s a little trick. Many mature frameworks, besides providing the regular deployment steps on their official websites, also offer Quick Start deployment methods for non-production line deployment (such as the knative quick start we are learning in this lesson), which can help you get hands-on experience more quickly.

Study the Architecture #

After experiencing the basic functionalities, we can study the basic concepts, which will be easier to understand in combination with the previous hands-on experience. At this stage, I suggest adopting a learning approach from the whole picture to the details. Start by learning the basic concepts and overall architecture, and then dive into a specific processing flow or component for exploration.

After experiencing Knative, we can study the interaction process of some components in Serving, which will be easier for you to understand. It’s like this core logic that I have already introduced to you in advance, but after the hands-on practice in this lesson, isn’t it easier to understand?

For example, the cold start process. Through hands-on operations, we found that when the function is requested for the first time, the instance is created from scratch, and after a certain period of no requests, the instance is released. This makes it easy for you to understand the role of Activator and the scaling process of AutoScaler.

Next, the event-driven mechanism in Knative. We deployed a service for printing events and a PingSource object for generating events at regular intervals. Then, we can observe the processing of the timed events. At this point, when we learn about Eventing from a macro perspective, we can understand why it is a loosely coupled event-driven system.

In the event-driven system of Eventing, you can create events through APIs, and events are initiated by event producers and received by event consumers. After studying the system, you will also know that our demo is the simplest form of multiple event-driven patterns - the Source & Sink pattern.

Image Among them, Source declares an association between the event source and the event receiver, while Sink represents an object that can receive events. Combining the previous practice process, you must understand that the PingSource object we deployed is a Source type, and event-display is a service that receives events. If you delve into the yaml of PingSource, you can see that Schedule defines the cron expression for triggering at regular intervals, while sink.name indicates that the events will be handled by event-display.

apiVersion: sources.knative.dev/v1
kind: PingSource
metadata:
  name: test-ping
  namespace: default
spec:
  schedule: "*/1 * * * *"
  contentType: "application/json"
  data: '{"message": "Hello geek!"}'
  sink:
    ref:
      apiVersion: v1
      kind: Service
      name: event-display

By understanding this, can you grasp it quickly? After learning this most basic event-driven model, you can further study the more complex models of Channel & Subscription and Broker & Trigger. Through such hands-on practice, step by step, you can learn faster and remember more.

Dive into the Source Code #

After gaining a clear understanding of the overall architecture and key processing flow, you can delve into the source code. The source code can help you better understand the thinking of the framework’s designers. This is actually a higher requirement, which requires you to have a certain mastery of the framework’s programming language and code writing style before you can understand it better.

There are various ways to read the source code. In addition to the official documentation, the code for open-source frameworks is usually hosted on GitHub. For some difficult-to-understand parts, you can also solve them by reading blogs and community comments.

Taking Knative as an example, GitHub includes its complete codebase. In addition to the code repositories of the two core components, Eventing and Serving, it also includes clients, some usage documentation, etc. For some critical processes like scaling, some contributors even provide detailed process explanations, so it can be said to be very comprehensive. When encountering problems, the issues can also provide you with some references. In addition, Knative’s official website also has some excellent technical blogs, which are effective means to help you dive into the source code.

Finally, in the process of learning serverless frameworks, preliminary knowledge reserves are also important. For example, before learning Knative, you need to have some understanding of the basic concepts of Kubernetes, and when it comes to the source code part, you also need to master the basics of Golang syntax, and so on.

These learning experiences are not limited to the study of Knative in this lesson. I hope you can apply them flexibly to the learning process of any new framework you encounter in the future.

Summary #

In today’s lesson, you should have gained a more intuitive understanding of the core components of Knative: Serving provides the ability to manage the entire serverless workload. Knative Serving uses a custom set of specific objects in conjunction with the Kubernetes CRD (Custom Resource Definition) to achieve complete traffic control and automatic scaling capabilities, with the help of key components such as Activator and AutoScaler.

Knative Eventing is actually a set of APIs that can help you invoke functions in an event-driven manner. The Source and Sink model is the simplest event-driven model in Knative. In addition to this, there are also Channel & Subscriptions and Broker & Trigger. The former provides an event transport mechanism that can distribute events to multiple destinations or Sinks. The latter is similar to the former, but the difference is that the latter makes it easier to filter events, making it suitable for multiple event sources under one namespace, and then consumers can consume events of interest based on event type and properties.

The Tekton component is mainly responsible for fetching source code from a code repository and compiling it into an image, which is then pushed to an image repository. All of these operations are performed in Kubernetes Pods.

Overall, Tekton complements Knative’s ability for rapid development and integration, improving development and deployment efficiency. Eventing greatly enriches Knative’s application scenarios through its loosely coupled structure and support for CloudEvents. The Serving component spans the entire lifecycle of the serverless container.

Finally, I want to tell you that serverless technology is constantly evolving, and we need to flexibly study newly released serverless engines, frameworks, and platforms. You can learn by directly experiencing them, then studying their architectural principles, and finally delving into the details of the source code for further understanding and improvement.

Homework #

Alright, the class is over here, and I have left you with some homework.

You can continue to try setting up Knative Tekton and see if you can build a complete CI/CD system using Trigger. If you have any questions, feel free to discuss and exchange ideas with me in the comments section.

Thank you for reading, and feel free to share this class with more friends to read together.