 Welcome to this session. So my name is Yip Sam, I'm an architect in Red Hat. So we will go over how to build high quality OpenShift application. So when we think about high quality in OpenShift, what does quality mean? Quality have a lot of meaning. A lot of time we come with customer satisfaction, assurance, excellent program, reliability, right? And there were different things that come with it. So let's see how we could apply all these different concepts into our OpenShift application. So when we think about OpenShift, what does OpenShift mean? The characteristic of OpenShift is an application adopting the principle of microservices and package as containers orchestrated by a platform, running on top of cloud infrastructure. So from here, we can focus on looking into application, microservices, container, and platform to see how we could improve the quality. So overall, in general, OpenShift have some sort of ecosystem that when you commit your code into Git, it would trigger a web hook into Jenkins, right? Or TapTon or any CI CD pipeline that will start the build process, start the test, and then it will kick off the deployment to deploy to Dev QA and port, right? So this is kind of a high-level picture of what OpenShift does. So now we have a basic understanding. So let's think about like, how do we ensure quality and each individual piece? So the first thing is to keep application config file outside of the image. This is important because the container image that include environment-specific configuration cannot promote it to production environment, right? There needs to be specific into Dev QA and port. And to achieve a reliable release process, the same image needs to be tested in a lower environment in Dev and QA first before it deploy to production. Keeping the environment-specific configuration outside of the image allow you to use the same image for deployment in Dev QA and port. An example is to use config map or secret to store the application configuration. So now another suggestion is to keep the resource request and the limit at the port. So a lot of time we have application running out of memory and incurring CPU starvation because of improper configuration, you're asking too many resources that you don't need, right? So specify a request memory and CPU resource limit to the customer to make a proper scheduling decision. This is important. So that we could ensure the application will have the requested resource when it is available. Define the lifeless and readiness pope, right? So this lifeless readiness pope come back to the health check. If we have health check pope that allow us to check the cluster resiliency of the application, it's going to improve your application quality, right? That also allow you to, the cluster to restart your application even your lifeless pope fail. We could avoid traffic to the application when it is not ready, right? When the application lifeless pope is returning of not successful status, then just stop all this traffic to this cluster and redirect the traffic to another cluster, right? So that would allow you to do a high availability and improve the quality. All these lifeless readiness pope go tie back to the monitoring and health check of your application in OCP. Protect your application with port disruption budget, right? The application port may needs to be evicted from the cluster node. So the erection is needed before the administrator can perform maintenance on the node or before the cluster auto scale can remove the node from the cluster while downscaling, right? So how do we do that? We can do this by ensuring the application we make available when the port needs to be erected. So in order to do that, we must define the port disruption budget object that needs to be tied to your application. Ensure your application part terminated gracefully when it needs to be terminated, right? The application complete all the in-flight requests and terminated according to your process and terminated all the existing connection gracefully so you don't have any dangling open connection. This is going to help you maintain the application without going out of memory and without going into any dangling connection from your when it restarted, right? And this also help you keep track of when a newer version of the application is deployed. Wanting only one process per container, this is really important to remember. We want to avoid wanting multiple processes in one single container because there were a high risk, right? You may get into a raising condition, right? Or one process modify the resources while the other is trying to use it. You don't want to get into that situation, right? Each process in a separate container allow a much better isolation of the process. Avoid issue with signal routing, right? We want to avoid all these zombie processes. So make sure that you check how many processes you are wanting in your container. Implementing application monitoring and alerting is also an important concept. Keep the application operating well in production to serve the business, right? So some of the tools such as Prometheus or Gavrana dashboard would allow you to monitor your application. Configure the application to standard output and standard error when there's an exception. So OpenShift collect those logs and send them to a centralized location such as ELK or Splunk. The application logs are a very important resource when you need to analyze production issue. Alerting based on the content of the application logs enable you to ensure that the application is performing as expected. So a lot of time we were checking, reporting, alerting based on specific error messages, based on error code status code, right? So that we can say, hey, you know, if you get too many external error of HTTP 500 happening, then we know, oh, this is an application code issue, then we need to go and dig into the code. We see the NC measures, right? Such as, you know, circuit breaker, if we try too many times, you definitely need to have a circuit breaker to avoid a DOS attack, right? Time out, we try, wait limit. Application performs much better in case of failure. So you need to protect your application from getting overloaded, right? So that's really the point. So you can improve the performance with connection issue and also consider leveraging OpenShift surface match. My surface match is important because it help you implement all these different measures without the need of the code change in your application. Use trusted base image, base container image. This is important because it will avoid any security issue that you may have, right? The vendor provided image needs to be tested, needs to be hardened, needs to be supported. Community image, supported image usually could be used as a trusted resource, right? We don't want to use any unknown origin of images that would pose a security risk. Use the latest version of any base image, right? The base image contain the latest security fixes or bug fixes, right? Set up your CI CD pipeline, always pull the latest image so that when there's an image change, you will get the failure and go back and fix your code, right? With the new change. Set up your CI CD pipeline to rebuild the application using the updated base image. The build image and one-time image, you need to separate the build image versus the one-time image because they are different, right? So you can create a separate one-time image with minimum dependency that would allow you to reduce the attack surface and produce a much smaller one-time image. The idea is to build this image. The build image contain the build dependency that are required for building but not required for running the application. So that's an important concept to remember. Restricted the security context constraint. Modify your container image to allow running under the restricted SCC. The application are vulnerable, right? When the attacker can take control of the application. So using the SCC, right? You can provide the highest level of security that protect the customer from being compromised in case of the application being beach or attack. Always communicate using TLS, right? Using your application component, communicate sensitive data and information should be protected. So yeah, use TLS to protect any traffic in between any application components and consider using surface mesh as well to offload the TLS management from the application. So now we have some high level idea what to look for at the container layer, right? So let's look at the application layer. On the application layer, the first thing I would introduce is the solid principle, S-O-L-I-D. What does it mean? Single responsibility, open-close principle, least cost substitution principle, interface aggregation principle and dependency inversion principle. We will go over example in the following slides and help you understand how to do that in your application development. Single responsibility is simple, right? So you don't want to do too many things at the same time. So as a result, every class should have only one responsibility. That is basically do one single responsibility. If you are doing too much, then you need to re-factor that class into smaller classes. Open-close principle. So what does it mean? Software entitles and software entities should be open for extension and close for modification, right? So think about using interfaces and abstract classes, right? You should always have a way to extend your object when you need to add additional feature, but you don't want to modify the base class. This cost substitution principle, right? So what does that mean? So any object in a program should be replaceable with an instant of a subtype without changing the correctness of that program, right? So that allow you to take on, you know, subtype or more granular object, right? When your business requirement change, I can spin off another type, do another subtype that contain the new business requirement. And then when I feed that object into the main program, my program should not need to change, right? So that's the idea. Interface aggregation principle. So a lot of time we have a lot of different interfaces that we need to deal with, especially legacy code, right? The interface may return, you know, tens of hundreds of different data fields or a lot of these fields in the data model, we don't need it at all, right? So when you develop your application, remember to always create your client-specific interface. You could have a client-specific interface on top of a legacy interface that will only return the data that you need. So the benefit of doing that is to, you know, your data model will be a lot smaller. Your performance of the code will be a lot faster and you don't need to deal with any additional information that you don't need. So that would reduce the size of your code and at the end improve quality. Dependency inversion principle. So one should depend upon abstraction, not concretion, right? So always, you know, think about if you have a way to use the abstraction layer of the object, right? Use the abstraction. Do not depend on the concretion. So that include example in the Java world, we use auto wire for dependency. We use spin connection, right? These are a good example of the dependency inversion. So how about the principles in the open shift layer, right? How do we apply similar solid principle to the open shift world? Single responsibility principle. Yes, definitely we can apply the open shift, right? Each part should contain multiple container. Each container should be single responsibility, right? Each container should only do one thing. If it is doing more than one thing, then we need to refactor it and create additional containers. Self-containment principle, right? Each container should not rely on anything else, except the Linux kernel that it wants on, right? So that kind of tie to the, you know, related to the open-close principle in the Java world, right? So you should be self-contained, right? If you have container one depends on container two and so on, you are creating additional dependency. The dependency layer will cause additional problems. So don't do that. Image immutability principle, right? The container image should be targeted for all environment, right? So you should be able to use the same image for depth, for QA, for production without changing it. So as we talked about earlier, how do we achieve that is to move the application from environment configuration out of the image and move it into our config map or secret. High observability principle, right? So each container should have its own health check. Health check include liveness probe, readiness probe for microservices, right? Sonar queue databases, you know, Spring Boot actuator for Java, for example. And at the container layer, we should use Splunk, CloudWatch, right? So you should have additional information inside for monitoring and alerting, right? Life cycle conformant principle. Each container should conform to signal coming from the platform, right? So the signal will trigger the life cycle of the container, right? SIG term, right? So signal terminate or terminate the container. SIG queue, it will kill the container. P-stop and post-start, right? So these are P and post-event that happen, you know, on the stop or start. So these should all be respected as part of the container life cycle. So if you are not following any of these events, then you are not following this principle. Process this possibility principle. A container can be killed at any time, it can be killed at one time, right? So think about this, right? So when your container crash, it should be able to kill itself. Your applications should not be dependent on a specific instance of that container. So how do we do that? So that go bring up the discussion of stay-for-versus-stay-less application, right? So for applications that have stay, you just need to store the application stay into databases or persistent voiding claim, right? So when your applications restart, you still have a way to retrieve the data, right? It would allow us to do a rapid start-up or shutdown of the application. The one-time confinement principle. Each container should be viewed as a one-time dimension, including three vertical, size, memory, and CPU usages. You need to specify this dimension in the configuration, including in auto-scaling, max mean number of instances, warm-up cooldown period of the scaling, right? Scaling threshold and scheduling. All this needs to be considered and identified by doing the container definition. So now we have a high-level understanding of the container, right? So on the Java world, remember, we have different type of design pattern that we could leverage, right, for the coding quality, right? So you can think about your three different vertical creation, behavior, and structure, but each one of them has a specific design pattern you could follow to improve the Java code. I personally also like TDD a lot because it helped me improve the code quality, right? The benefit of TDD, it has high code coverage, right? I do the unit test first before I ego it to implement my code, right? All these codes are intentional, so only require code will be checked in, right? I understand the business use cases before I go start the development process. It reduce bugs in the production environment and shorten development lifecycle, right? Also, follow the triangle for TDD. Write your test, change your code and make the test pass, and then do refactor. So when we talk about TDD, a lot of time we would think about the test pyramid. What does testing pyramid do, right? At the base of your pyramid, you should have your unit test. Unit test should contain about 70% of your test. On top of unit test, you have integration test. Integration test would be another 20%, and then your UI automation test will be another 15%. Total testing, it will still exist because it's not possible to automate everything, so it will be at the top of the pyramid. This should be the smallest portion of your test. This should compose between 5% to 10% of your total test. So remember to follow the pyramid. Do not do the inverse of the pyramid on the other side, because a lot of organization may go into that direction, and then at the end, they end up having too many manual tests and it's just wasting their time.