28 Toward the Cloud, the Platform Thinking in the Era of Cloud Native Applications

28 Toward the Cloud, The Platform Thinking in the Era of Cloud-Native Applications #

Hello, I am Shi Xuefeng.

In recent years, I believe you have heard the term “cloud-native” in various occasions. For example, the 12 factors of cloud-native applications, the recent popular technologies Docker and container orchestration technology Kubernetes. Behind Kubernetes is CNCF, the Cloud Native Computing Foundation, which has become an organization that many enterprises are eager to join.

DevOps seems to have an indescribable connection with cloud technology, as containers, microservices, immutable infrastructure, service mesh, declarative APIs, etc., are all common in the DevOps technology field. Cloud-native applications seem to be a perfect match for DevOps, with built-in high availability, easy maintenance, scalability, and continuous delivery.

So, what exactly is cloud-native? Let me quote the official definition from CNCF:

Cloud native computing uses an open source software stack to deploy applications as microservices, packaging each part into its own container, and dynamically orchestrating those containers to optimize resource utilization.

I summarize the keywords here: open source software, microservice applications, containerized deployment, and dynamic orchestration. In short, cloud-native applications are the deployment of microservice architecture in a containerized manner on a cloud platform, typically using Kubernetes as the core of the cloud platform, thus benefiting from the various advantages of cloud services.

I have repeatedly emphasized in my column that container technology and Kubernetes are revolutionary technologies and are essential skills for every engineer learning DevOps. Just like many years ago, everyone needed a copy of “The Linux Guidebook by Vogon” to learn Linux, Kubernetes, as the Linux of the cloud era, is equally worthy of your attention.

Today, I’m not going to talk to you about Kubernetes. Through a project and my personal experience in the past two years, I want to share with you the changes that cloud-native will bring to DevOps. This project is Jenkins X.

In early 2018, I shared an article about Jenkins X, which received over 10,000 views in just a few days. This reflects Jenkins’ tremendous influence in China, but also highlights the conflict and incompatibility between Jenkins and this era. Why do I say that? Because Jenkins, as a 15-year-old system, is full of anti-patterns in cloud-native, such as:

  1. Jenkins is a Java monolithic application that runs on the JVM and is no different from other typical Java applications.
  2. Jenkins uses file storage, various loading patterns, resource scheduling mechanisms, etc., ensuring that it is inherently not highly available.
  3. Although Jenkins provides pipelines, the pipelines are still executed on the master node, which means that as the number of tasks increases, the resources consumed by the master node also increase. It is not only difficult to scale but also susceptible to being overwhelmed by any unreliable task.

For the simplest example, if a task outputs 500MB of logs, when you click on “View All Logs” in Jenkins, your server better be able to handle it. Because most of the time, the server may just die. Of course, I strongly advise against trying this experiment in a production environment.

So, how can we make Jenkins cloud-native? Some colleagues might say, “Put Jenkins in a container and let Kubernetes manage it, right?” If you think that way, it means that your understanding of Kubernetes and cloud-native applications is still insufficient. Let me list the problems you need to solve in order to transform Jenkins into a true cloud-native application:

  1. Pluggable storage (typical examples are S3 and OSS)
  2. External artifact management
  3. Credentials management
  4. Configuration management
  5. Test report and coverage report management
  6. Log management
  7. Jenkins Job

As you can see, I have only listed a part of it. According to the 12-factor standard for cloud-native applications, there are still many transformations to be made.

Take logs as an example. Currently, all Jenkins logs are written on the master node. To transform it into a cloud-native application, first, logs should be considered as an output stream. The output stream should not be managed by the application and written locally on the application’s running node, but should be collected, analyzed, organized, and displayed by a dedicated log service. Services like ElasticSearch, Fluent, or AWS’s CloudWatch Logs can achieve this function.

So how does Jenkins X solve this problem?

Let’s imagine a scenario: When a developer wants to develop a cloud-native application, what does he need to do?

First, he needs a runnable Kubernetes environment. Considering various uncontrollable factors, this is definitely not a simple task. Especially a few years ago, if someone could build and deploy a Kubernetes cluster using binaries, it would have been something to boast about. Fortunately, nowadays, there are dedicated personnel in the company responsible for maintaining the Kubernetes cluster, and major public cloud providers also provide support in this regard.

Now, let’s go back to the perspective of the engineer.

When he receives a requirement, the first thing he needs to do is modify the code, then compile and package the code and test it locally. Next, he needs to submit the code to the version control system, manually trigger the pipeline task, and wait for it to complete. If, by chance, the compilation command was adjusted this time, he also needs to modify the pipeline configuration file. Finally, after all the hardships, an image file is generated and pushed to the image server. That’s not all, he also needs to modify the Kubernetes resource configuration of the test environment, call the kubectl command to update the application, and wait for the deployment to complete. If new issues are discovered during system verification for this modification, I’m sorry, all the previous steps need to be done again.

As you can see, although cloud-native applications have many advantages, they greatly increase the complexity of development. An engineer must be familiar with a series of processes, new technologies, and tools related to Kubernetes, pipelines, images, packaging, and deployments, in order to complete a deployment. If these operations rely on other departments or other people, then you’ll just have to wait. So it seems that this path is not sustainable.

In the era of the cloud, everything is a service. Therefore, in the era of cloud-native applications, DevOps or continuous delivery should also exist in the form of a service. Just like when you use electricity, you won’t consider how the power plant operates or how the electricity is delivered to your home. You just need to be responsible for using it.

So, let’s see how Jenkins X gradually “kills” Jenkins. Actually, I hope you can remember that Jenkins X itself is not important, what is more important is the tools and technologies used in this process, and the design principles behind them.

1. Automating the Generation of Dependency Configuration Files #

For a cloud-native application, what configuration files does it rely on besides the source code itself? These include:

  • Dockerfile: Used to generate Docker images
  • Jenkinsfile: Pipeline configuration associated with the application
  • Helm Chart: Resource files that package and deploy the application to run on Kubernetes
  • Skaffold: A tool used to generate Docker images in Kubernetes

Considering that you might not be familiar with the Skaffold tool, let me provide a brief introduction.

In order to generate Docker images in a Kubernetes environment, you will find that in general, this depends on the Docker service, also known as the Docker daemon. The common approaches are either Docker-in-Docker or Docker-outside-Docker.

Docker-in-Docker provides a built-in Docker daemon and image generation environment within the base image, relying on support from official images. On the other hand, Docker-outside-Docker is more easily understood - it involves mounting the host’s Docker daemon into the Docker image.

There are three typical implementation methods: the first is to mount the node’s Docker daemon, the second is to use external services provided by cloud platforms, such as Google Cloud Builder, and the third is to use solutions that can package without the Docker daemon, such as the popular Kaniko.

Skaffold aims to solve the problem of you no longer needing to worry about how to generate, push, and run images. It will take care of all that for you, relying on the skaffold.yaml file.

If these files were to be manually generated by developers, it would significantly increase the barrier to entry. Fortunately, you can automate these operations using the Draft tool. Draft is an open-source tool developed by Microsoft, and it consists of two parts.

  • Source code analyzer: It can automatically scan your source code and identify the types of code you use based on code features, such as JavaScript, Python, etc.
  • Build pack: Simply put, a build pack is a template corresponding to a language. By defining preset environment dependency configuration files in the template, including the Dockerfile and Jenkinsfile mentioned above, it achieves automatic generation and creation of dependencies. Of course, you can also define your own build pack and use it as a template within your internal projects.

Templates are often a great approach as they greatly simplify initial configuration costs and improve the standardization of environments and services. The same goes for pipelines; after all, not many people are experts in this area. As long as a set or a few sets of best practice templates can be provided for 90% of scenarios, it is sufficient.

As a result, whether it is existing code or projects undergoing permission initialization, developers do not need to worry about how to package code, generate images, and go through the deployment process. This will greatly save developers’ energy. After all, as I mentioned earlier, not everyone is an expert in containers and builds.

2. Automated Pipeline Process #

Once the application initialization is complete, the pipeline should be in a ready-to-use state. This means that if the project adopts a feature branch and main development branch release strategy, the build pack should already have pre-set pipeline configuration files for each branch. These files define the checks that each branch needs to go through.

When developers submit code, the corresponding pipeline is automatically triggered. For developers, all of this is seamless. Only when necessary (such as when an issue occurs), the system will notify developers to review error messages. This requires that the Jenkinsfile of the pipeline be automatically generated, and the version control system and CI/CD system be seamlessly integrated. For example, registering and configuring webhooks, setting up MR approval conditions, and automatically filtering branch information all need to be automated.

The main technologies used here include three points.

  1. Pipeline as code. After all, only code-based pipeline configuration can be automated.
  2. Abstraction and reuse of pipelines. Taking a typical Jenkinsfile as an example, most operations should be extracted into a shared library, rather than hard-coding them in the pipeline configuration file, in order to improve abstraction and enable reuse of capabilities.
  3. Conditional branching in the pipeline. For the same pipeline, different execution paths can be achieved based on different conditions.

3. Automated Multi-Environment Deployment #

For traditional applications, especially those with complicated dependencies, environment management is a challenging problem. The emergence of Kubernetes greatly simplifies this process. However, it is important to note that for cloud-native applications deployed on Kubernetes, all dependencies should be resources within the environment.

By relying on Kubernetes’ powerful resource management capabilities, it becomes possible to dynamically initialize a set of environments, which is a significant improvement.

Jenkins X provides default staging and production environments. In addition to this, for every code submission resulting in a pull request (PR), Jenkins X automatically initializes a preview environment and deploys the application on that environment. This allows for reviewing the functionality of the application in the preview environment during every code review. By accepting these functionalities from a user’s perspective, the quality of the final delivery is improved.

The technology used here, in addition to GitOps as I introduced in Lecture 16, is primarily Prow.

You can think of Prow as a specific implementation of ChatOps. In fact, it provides highly scalable webhook event handling capabilities. For example, by using conversational commands, such as entering the /approve command, Prow will receive the command and trigger the corresponding webhook, automatically executing the pipeline and a series of background operations.

4. Using Cloud-Native Pipelines #

At the beginning of this year, Jenkins X underwent a comprehensive upgrade and started to support Tekton pipelines. Tekton was originally a project called KNative, created in early 2018 as a serverless solution for Kubernetes. However, as the scope of the project expanded, it gradually incorporated the entire delivery process orchestration, leading to the creation of the Tekton project, which provides native Kubernetes pipeline capabilities.

Tekton provides the lowest-level capabilities, while Jenkins X provides higher-level abstractions in the form of a YAML file that describes the entire delivery process. Let me share an example of a pipeline configuration file with you:

agent:
  label: jenkins-maven
  container: maven
pipelines:
  pullRequest:
    build:
      steps:
      - sh: mvn versions:set -DnewVersion=$PREVIEW_VERSION
      - sh: mvn install
  release:
    setVersion:
      steps:
      - sh: echo \$(jx-release-version) > VERSION
        comment: so we can retrieve the version in later steps
      - sh: mvn versions:set -DnewVersion=\$(cat VERSION)
      - sh: jx step tag --version \$(cat VERSION)
    build:
      steps:
      - sh: mvn clean deploy

In this example, you can see that the pipeline process is described in YAML format, rather than the Groovy format we’re familiar with. Additionally, in this file, you won’t see resource types from Tekton, such as Task and TaskRun.

In reality, Jenkins X, based on the existing Jenkins pipeline syntax structure, has redefined a syntax based on YAML. You can still use previous commands to define the entire pipeline in YAML, but in the background, Jenkins X will convert this file into Custom Resource Definition (CRD) resources that Tekton needs and trigger Kubernetes to execute them.

In simple terms, users appear to still be using Jenkins, but the execution engine for the pipeline has transitioned from the JVM to Kubernetes. The execution and scheduling of the pipeline are handled by Kubernetes, and each step within the process is initialized dynamically in containers, with all data stored using external storage.

With this upgrade, we have finally achieved true platform cloud-native transformation. For more information about this new Jenkins pipeline syntax definition, you can refer to the official documentation.

Let me also show you an illustrative diagram of the relationship between Serverless Jenkins and Tekton, hoping it will help you better understand the underlying implementation mechanism.

Serverless Jenkins and Tekton Relationship

Source: Move Toward Next-Generation Pipelines

Ultimately, our goal is to no longer have a persistent Jenkins Master instance waiting for user calls. Instead, we want to introduce a mechanism called “Ephemeral Jenkins,” where a transient Jenkins instance is only launched when needed and closed once it’s done. This shifts from a static service to a fleeting dynamic service, seemingly omnipresent and driving the CI/CD journey of cloud-native applications.

Now, let’s revisit the initial scenario. For engineers who enjoy cloud-native pipeline services, all they need to focus on is writing good code. Other concerns that used to require their attention are now automatically and template-driven in the background.

Even when it comes to local development and debugging, you can fully leverage Kubernetes’ environment management capabilities. You can perform the entire process of packaging, generating images, pushing to a repository, initializing environments, and deploying, right within your IDE. I believe this is the ultimate pursuit of empowering developers with cloud-native tools.

Summary #

In recent years, many people have asked me whether Jenkins is outdated and if more lightweight solutions like Argo and Drone are more suitable for the development of cloud-native applications.

Actually, even developers in the community are asking themselves this question, and the answer is the Jenkins X project. This project integrates a large number of open-source tools and cloud-native solutions, including:

  • Cloud-native development experience based on Kubernetes
  • Automated CI/CD workflows
  • Multiple pre-configured environments with flexible initialization
  • Deployment promotion between multiple environments using GitOps
  • Cloud-native pipeline architecture and user-friendly configuration
  • Customizable pipeline execution engines

I have to admit that the changes brought by cloud-native are huge and profound to the platform. In these years, on one hand, I am amazed by the immense vitality and innovation of the community, and on the other hand, I deeply realize that “the future is here” and that this transformation is getting closer.

In the era of cloud-native, what we need to build is an automated, service-oriented, and highly scalable platform. This means that the platform used to build cloud-native applications should also possess the characteristics of cloud-native applications and maximize the empowerment of development engineers to enhance their productivity levels.

Thought-provoking Questions #

When it comes to the implementation of DevOps, building tools is only the first step. So, how can we ensure that these tools unleash their true power and are truly promoted and implemented within the team? What suggestions do you have?

Feel free to leave your thoughts and answers in the comment section. Let’s discuss together and learn from each other. If you find this article helpful, please feel free to share it with your friends.