Integrating PostgreSQL Databases with ANF: Join this workshop to learn how to create a PostgreSQL server using Instaclustr’s managed service
Mobile Database Essentials: Assess data needs, storage requirements, and more when leveraging databases for cloud and edge applications.
Open source refers to non-proprietary software that allows anyone to modify, enhance, or view the source code behind it. Our resources enable programmers to work or collaborate on projects created by different teams, companies, and organizations.
Automate Migration Assessment With XML Linter
Can Redis Be Used as a Relational Database?
Spring Cloud is a versatile framework for building Java applications in various cloud environments. Today, we'll explore how to use two components of the framework - Spring Cloud Gateway and Discovery Service (aka Spring Cloud Netflix) - for easy routing of user requests between your Java microservices. We'll build two microservices, register them with a Discovery Service instance, and use the Cloud Gateway for routing requests to a specific microservice instance. The cool thing is that the Cloud Gateway will also be registered with the Discovery Service and will use the latter to resolve a microservice name into an actual connection endpoint. So, whether you prefer reading or watching, let’s walk through this practical example: Creating Sample Microservices Imagine we’re creating an online service for a pizza company. There are two basic capabilities the service needs to support - customers can order a pizza online and then track the order status. To achieve this, let's introduce two microservices - the Kitchen and the Tracker. Kitchen Microservice The Kitchen microservice allows customers to place pizza orders. Once an order is placed, it'll hit the kitchen, and the chef will start cooking. Let's create a basic implementation for the purpose of testing Spring Cloud Gateway with the Discovery Service. This service is a Spring Boot web application with a REST controller that simply acknowledges an order. Java @RestController @RequestMapping("/kitchen") public class KitchenController { @PostMapping("/order") public ResponseEntity<String> addNewOrder(@RequestParam("id") int id) { return ResponseEntity.ok("The order has been placed!"); } } The service will be listening on port 8081, which is set in the application.properties file: Properties files server.port=8081 Once the microservice is started you can use curl or HTTPie to test that the REST endpoint works. We’ll be using HTTPie throughout the article: Shell http POST localhost:8081/kitchen/order id==1 HTTP/1.1 200 Connection: keep-alive Content-Length: 26 Content-Type: text/plain;charset=UTF-8 Date: Thu, 03 Aug 2023 18:45:26 GMT Keep-Alive: timeout=60 The order has been placed! Tracker Microservice Customers use the second microservice, the Tracker, to check their order status. We'll go the extra mile with this service implementation by supporting several order statuses, including ordered, baking, and delivering. Our mock implementation will randomly select one of these statuses: Java @RestController @RequestMapping("/tracker") public class TrackerController { @GetMapping("/status") public ResponseEntity<String> getOrderStatus(@RequestParam("id") int orderId) { String[] status = { "Ordered", "Baking", "Delivering" }; Random rand = new Random(); return ResponseEntity.ok(status[rand.nextInt(status.length)]); } } The Tracker will be listening on port 8082, which is configured in the application.properties file: Properties files server.port=8082 Once the microservice is started, we can test it by sending the following GET request: Shell http GET localhost:8082/tracker/status id==1 HTTP/1.1 200 Connection: keep-alive Content-Length: 10 Content-Type: text/plain;charset=UTF-8 Date: Thu, 03 Aug 2023 18:52:45 GMT Keep-Alive: timeout=60 Delivering Registering Microservices With Spring Cloud Discovery Service Our next step is to register these two microservices with the Spring Cloud Discovery Service. But what exactly is a Discovery Service? Discovery Service The Discovery Service lets your microservices connect to each other using only their names. For instance, if Tracker needs to connect to Kitchen, the Discovery Service gives Tracker the IP addresses of Kitchen's available instances. This list can change - you can add or remove Kitchen instances as needed, and the Discovery Service always keeps the updated list of active endpoints. There are several ways to start a Discovery Service server instance. One of the options is to use the Spring Initializr website to generate a Spring Boot project with the Eureka Server dependency. If you choose that method, the generated project will come with the following class that initiates a server instance of the Discovery Service: Java @SpringBootApplication @EnableEurekaServer public class DiscoveryServerApplication { public static void main(String[] args) { SpringApplication.run(DiscoveryServerApplication.class, args); } } By default, the server listens on port 8761. So, once we start the server, we can visit localhost:8761 to view the Discovery Service dashboard: Currently, the Discovery Service is running, but no microservices are registered with it yet. Now, it's time to register our Kitchen and Tracker microservices. Update the Kitchen Microservice To register the Kitchen service with the Discovery Service, we need to make the following changes: 1. Add the Discovery Service’s client library to the Kitchen’s pom.xml file: XML <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> 2. Annotate the Kitchen’s application class with @EnableDiscoveryClient: Java @SpringBootApplication @EnableDiscoveryClient public class KitchenApplication { public static void main(String[] args) { SpringApplication.run(KitchenApplication.class, args); } } 3. Update the application.properties file by adding these two parameters: Properties files # The microservice will be registered under this name # with the Discovery Service spring.application.name=kitchen-service # Discovery Service address eureka.client.service-url.defaultZone=http://localhost:8761/eureka Update the Tracker Microservice We need to follow the same steps to update the Tracker microservice. There's only one difference for Tracker: its name, which is provided via the spring.application.name property in the application.properties file: Properties files # The microservice will be registered under this name with the Discovery Service spring.application.name=tracker-service Register Microservices Finally, restart the microservices to confirm their registration with the Discovery Service. As expected, both the Kitchen and Tracker microservices successfully register with the Discovery Service! Now, it's time to focus on the Spring Cloud Gateway. Spring Cloud Gateway Spring Cloud Gateway is used to resolve user requests and forward them to the appropriate microservices or API endpoints for further processing. You can generate a Spring Boot project with Cloud Gateway support using the same Spring Initializr website. Simply add the Gateway and Eureka Discovery Client libraries, then click the generate button: The Gateway will serve as a one-stop solution for directing requests to Kitchen and Tracker instances. Its implementation is simple yet powerful: Java @SpringBootApplication @EnableDiscoveryClient public class ApiGatewayApplication { public static void main(String[] args) { SpringApplication.run(ApiGatewayApplication.class, args); } @Bean public RouteLocator routeLocator(RouteLocatorBuilder builder) { return builder.routes() .route("kitchen-route", r -> r.path("/kitchen/**").uri("lb://kitchen-service")) .route("tracker-route", r -> r.path("/tracker/**").uri("lb://tracker-service")) .build(); } } The Gateway supports two routes: The kitchen-route is for all requests beginning with the /kitchen/** path. The tracker-route is for requests starting with the /tracker/** path. The most interesting part of this route configuration is how we define the destination endpoint (the uri(...) part of the configuration). Each destination starts with lb:, followed by a microservice name. When the Gateway encounters such a destination URL, it will use the Discovery Service to resolve a microservice name into an IP address of a microservice instance and establish a connection to it. Furthermore, lb stands for load balancer, which means the Gateway will distribute requests evenly if the Discovery Service returns several instances of the same microservice. Finally, once we initiate the Gateway, it begins to monitor port 8080 for incoming traffic. We can then use the following HTTP requests to confirm that the Gateway is successfully routing our requests to the Kitchen and Tracker microservices! Shell http POST localhost:8080/kitchen/order id==2 HTTP/1.1 200 OK Content-Length: 26 Content-Type: text/plain;charset=UTF-8 Date: Thu, 03 Aug 2023 20:11:26 GMT The order has been placed! http GET localhost:8080/tracker/status id==2 HTTP/1.1 200 OK Content-Length: 6 Content-Type: text/plain;charset=UTF-8 Date: Thu, 03 Aug 2023 20:11:41 GMT Baking Summary With Spring Cloud, building robust, scalable Java applications for the cloud has never been easier. Say goodbye to the hassles of tracking IP addresses and instances, and say hello to intelligent, effortless request routing. Enjoy!
Open-source software has revolutionized the world of technology, enabling developers to collaborate, innovate, and build upon existing projects. One of the key pillars of open source is the involvement of developers from around the globe, offering their time, skills, and expertise to enhance and expand these projects. In this article, we explore the reasons why developers should actively contribute to open source and how it can benefit their professional growth and the software industry as a whole. Collaboration and Learning Opportunities Exposure to diverse perspectives: Contributing to open-source projects provides developers with an invaluable opportunity to collaborate with a global community of like-minded individuals. By working with people from various backgrounds, developers gain exposure to new ideas, perspectives, and approaches, fostering a rich learning environment. Enhancement of problem-solving skills: Open source projects often pose unique challenges that require innovative solutions. By contributing to these projects, developers have the chance to sharpen their problem-solving skills as they navigate through complex issues with the support of the open-source community. Access to mentorship: The open-source community is made up of seasoned experts who are often willing to offer guidance and mentorship to aspiring developers. By engaging in open-source projects, developers can tap into a vast network of experienced professionals, gaining valuable insights and advancing their skill set. Career Advancement Showcase of abilities and expertise: Open source contributions serve as a tangible demonstration of a developer's abilities and expertise. Employers value candidates who actively participate in open-source projects as it showcases their commitment, passion, and willingness to learn and improve continuously. Expanded professional network: Engaging in open-source projects allows developers to build meaningful connections with other professionals in the industry. These connections can lead to new job opportunities, collaborations, and professional growth, opening doors to further career advancements. Stronger resumes: Having open-source contributions listed on a resume adds depth and credibility to a developer's profile. It highlights their ability to work collaboratively, take initiative, and contribute to the software community, setting them apart from other job applicants. Growth and Improvement of Skills Learning from project codebases: Open source projects often have extensive codebases that expose developers to different programming languages, frameworks, and libraries. By studying these codebases and contributing to their development, developers enhance their understanding of best practices, coding standards, and optimization techniques. Refining coding and documentation skills: Contributing to open source encourages developers to write clean, maintainable code that is easily understandable by others. In doing so, developers refine their coding and documentation skills, making them more effective communicators and collaborators. Understanding software development workflows: Open source projects typically follow established software development workflows, including version control systems, issue tracking, and code reviews. By actively participating in these processes, developers gain firsthand experience and understanding of these workflows, preparing them for future professional projects. Making a Positive Impact on the Software Community Contributing to the greater good: Open source projects strive to provide software solutions that benefit the community at large. By actively contributing, developers become agents of positive change, helping to improve and advance the software industry as a whole. This sense of purpose and community is deeply fulfilling and can be a driving force for developers to continue contributing to open-source projects. Giving back to the community: Developers often rely on open-source software for their own projects. Contributing to open source is a way to give back to the community that has supported and empowered them throughout their careers. By sharing their knowledge and expertise, developers help create a virtuous cycle of growth and innovation within the software community. Conclusion Contributing to open source is a rewarding endeavor for developers, offering a multitude of benefits that extend far beyond coding proficiency. The collaborative and learning opportunities, career advancements, skill growth, and positive impact on the software community are all reasons why developers should actively engage in open-source projects. By participating in open source, developers not only enhance their own professional growth but also contribute to the advancement and evolution of the entire software industry. So, let's embrace the power of open source and join the thriving community of developers working together to build a better future.
It is impossible to know with certainty what is happening inside a remote or distributed system, even if they are running on a local machine. Telemetry is precisely what provides all the information about the internal processes. This data can take the form of logs, metrics, and Distributed Traces. In this article, I will explain all of the forms separately. I will also explain the benefits of OpenTelemetry protocol and how you can configure telemetry flexibly. Telemetry: Why Is It Needed? It provides insights into the current and past states of a system. This can be useful in various situations. For example, it can reveal that system load reached 70% during the night, indicating the need to add a new instance. Metrics can also show when errors occurred and which specific traces and actions within those traces failed. It demonstrates how users interact with the system. Telemetry allows us to see which user actions are popular, which buttons users frequently click, and so on. This information helps developers, for example, to add caching to actions. For businesses, this data is important for understanding how the system is actually being used by people. It highlights areas where system costs can be reduced. For instance, if telemetry shows that out of four instances, only one is actively working at 20% capacity, it may be possible to eliminate two unnecessary instances (with a minimum of two instances remaining). This allows for adjusting the system's capacity and reducing maintenance costs accordingly. Optimizing CI/CD pipelines. These processes can also be logged and analyzed. For example, if a specific step in the build or deployment process is taking a long time, telemetry can help identify the cause and when these issues started occurring. It can also provide insights for resolving such problems. Other purposes. There can be numerous non-standard use cases depending on the system and project requirements. You collect the data, and how it is processed and utilized depends on the specific circumstances. Logging everything may be necessary in some cases, while in others, tracing central services might be sufficient. No large IT system or certain types of businesses can exist without telemetry. Therefore, this process needs to be maintained and implemented in projects where it is not yet present. Types of Data in Telemetry Logs Logs are the simplest type of data in telemetry. There are two types of logs: Automatic logs: These are generated by frameworks or services (such as Azure App Service). With automatic logs, you can log details such as incoming and outgoing requests, as well as the contents of the requests. No additional steps are required to collect these logs, which is convenient for routine tasks. Manual logs: These logs need to be manually triggered. They are not as straightforward as automatic logs, but they are justified for logging important parts of the system. Typically, these logs capture information about resource-intensive processes or those related to specific business tasks. For example, in an education system, it would be crucial not to lose students' test data for a specific period. info: ReportService.ReportServiceProcess[0] Information: {"Id":0,"Name":"65", "TimeStamp":"2022-06-15T11:09:16.2420721Z"} info: ReportService. ReportServiceProcess[0] Information: {"Id":0,"Name":"85","TimeStamp":"2022-06-15T11:09:46.5739821Z"} Typically, there is no need to log all data. Evaluate the system and identify (if you haven't done so already) the most vulnerable and valuable parts of the system. Most likely, those areas will require additional logging. Sometimes, you will need to employ log-driven programming. In my experience, there was a desktop application project on WPW that had issues with multi-threading. The only way to understand what was happening was to log every step of the process. Metrics Metrics are more complex data compared to logs. They can be valuable for both development teams and businesses. Metrics can also be categorized as automatic or manual: Automatic metrics are provided by the system itself. For example, in Windows, you can see metrics such as CPU utilization, request counts, and more. The same principle applies to the Monitoring tab when deploying a virtual machine on AWS or Azure. There, you can find information about the amount of data coming into or going out of the system. Manual metrics can be added by you. For instance, when you need to track the current number of subscriptions to a service. This can be implemented using logs, but the metrics provide a more visual and easily understandable representation, especially for clients. Distributed Trace This data is necessary for working with distributed systems that are not running on a single instance. In such cases, we don't know which instance or service is handling a specific request at any given time. It all depends on the system architecture. Here are some possible scenarios: In the first diagram, the client sends a request to the BFF, which then separately forwards it to three services. In the center, we see a situation where the request goes from the first service to the second, and then to the third. The diagram on the right illustrates a scenario where a service sends requests to a Message Broker, which further distributes them between the second and third services. I'm sure you've come across similar systems, and there are countless examples. These architectures are quite different from monoliths. In systems with a single instance, we have visibility into the call stack from the controller to the database. Therefore, it is relatively easy to track what happened during a specific API call. Most likely, the framework provides this information. However, in distributed systems, we can't see the entire flow. Each service has its own logging system. When sending a request to the BFF, we can see what happens within that context. However, we don't know what happened within services 1, 2, and 3. This is where Distributed Trace comes in. Here is an example of how it works: Let's examine this path in more detail… The User Action goes to the API Gateway, then to Service A, and further to Service B, resulting in a call to the database. When these requests are sent to the system, we receive a trace similar to the one shown. Here, the duration of each process is clearly visible: from User Action to the Database. For example, we can see that the calls were made sequentially. The time between the API Gateway and Service A was spent on setting up the HTTP connection, while the time between Service B and the Database was needed for database setup and data processing. Therefore, we can assess how much time was spent on each operation. This is possible thanks to the Correlation ID mechanism. What is the essence of it? Typically, in monolithic applications, logs and actions are tied to process ID or thread ID during logging. Here, the mechanism is the same, but we manually add it to the requests. Let's look at an example: When the Order Service action starts in the Web Application, it sees the added Correlation ID. This allows the service to understand that it is part of a chain and passes the "marker" to the next services. They, in turn, see themselves as part of a larger process. As a result, each component logs data in a way that allows the system to see everything happening during a multi-stage action. The transmission of the Correlation ID can be done in different ways. For example, in HTTP, this data is often passed as one of the header parameters. In Message Broker services, it is typically written inside the message. However, there are likely SDKs or libraries available in each platform that can help implement this functionality. How OpenTelemetry Works Often, the telemetry format of an old system is not supported in a new one. This leads to many issues when transitioning from one system to another. For example, this was the case with AppInsight and CloudWatch. The data was not grouped properly, and something was not working as expected. OpenTelemetry helps overcome such problems. It is a data transfer protocol in the form of unified libraries from OpenCensus and OpenTracing. The former was developed by Google for collecting metrics and traces, while the latter was created by Uber experts specifically for traces. At some point, the companies realized that they were essentially working on the same task. Therefore, they decided to collaborate and create a universal data representation format. Thanks to the OTLP protocol, logs, metrics, and traces are sent in a unified format. According to the OpenTelemetry repository, prominent IT giants contribute to this project. It is in demand in products that collect and display data, such as Datadog and New Relic. It also plays a significant role in systems that require telemetry, including Facebook, Atlassian, Netflix, and others. Key Components of the OTLP Protocol Cross-language specification: This is a set of interfaces that need to be implemented to send logs, metrics, and traces to a telemetry visualization system. SDK: These are implemented parts in the form of automatic traces, metrics, and logs. Essentially, they are libraries connected to the framework. With them, you can view the necessary information without writing any code. There are many SDKs available for popular programming languages. However, they have different capabilities. Pay attention to the table. Tracing has stable versions everywhere except for the PHP and JS SDKs. On the other hand, metrics and logs are not yet well-implemented in many languages. Some have only alpha versions, some are experimental, and in some cases, the protocol implementation is missing altogether. From my experience, I can say that everything works fine with services on .NET. It provides easy integration and reliable logging. Collector: This is the main component of OpenTelemetry. It is a software package that is distributed as an exe, pkg, or Docker file. The collector consists of four components: Receivers: These are the data sources for the collector. Technically, logs, metrics, and traces are sent to the receivers. They act as access points. Receivers can accept OTLP from Jaeger or Prometheus. Processors: These can be launched for each data type. They filter data, add attributes, and customize the process for specific system or project requirements. Exporters: These are the final destinations for sending telemetry. From here, data can be sent to OTLP, Jaeger, or Prometheus. Extensions: These tools extend the functionality of the collector. One example is the health_check extension, which allows sending a request to an endpoint to check if the collector is working. Extensions provide various insights, such as the number of receivers and exporters in the system and their operation status. In this diagram, we have two types of data: metrics and logs (represented by different colors). Logs go through their processor to Jaeger, while metrics go through another processor, have their own filter, and are sent to two data sources: OTLP and Prometheus. This provides flexible data analysis capabilities, as different software has different ways of displaying telemetry. An interesting point: data can be received from OpenTelemetry and sent back to it. In certain cases, you can send the same data to the same collector. OTLP Deployment There are many ways to build a telemetry collection system. One of the simplest schemes is shown in the illustration below. It involves a single .NET service that sends OpenTelemetry directly to New Relic: If needed, the scheme can be enhanced with an agent. The agent can act as a host service or a background process within the service, collecting data and sending it to New Relic: Moving forward, let's add another application to the scheme (e.g., a Node.js application). It will send data directly to the collector, while the first application will do it through its own agent using OTLP. The collector will then send the data to two systems. For example, metrics will go to New Relic, and logs will go to Datadog: You can also add Prometheus as a data source here. For instance, when someone on the team prefers this tool and wants to use it. However, the data will still be collected in New Relic and Datadog: The telemetry system can be further complicated and adapted to your project. Here's another example: Here, there are multiple collectors, each collecting data in its own way. The agent in the .NET application sends data to both New Relic and the collector. One collector can send information to another because OTLP is sent to a different data source. It can perform any action with the data. As a result, the first collector filters the necessary data and passes it to the next one. The final collector distributes logs, metrics, and traces among New Relic, Datadog, and Azure Monitor. This mechanism allows you to analyze telemetry in a way that is convenient for you. Exploring OpenTelemetry Capabilities Let's dive into the practical aspects of OpenTelemetry and examine its features. For this test, I've created a project based on the following diagram: It all starts with an Angular application that sends HTTP requests to a Python application. The Python application, in turn, sends requests to .NET and Node.js applications, each working according to its own scenario. The .NET application sends requests to Azure Service Bus and handles them in the Report Service, also sending metrics about the processed requests. Additionally, .NET sends requests to MS SQL. The Node.js requests go to Azure Blob Queue and Google. This system emulates some workflow. All applications utilize automatic tracing systems to send traces to the collector. Let's begin by dissecting the docker-compose file. version: "2" services: postal-service: build: context: ../Postal Service dockerfile: Dockerfile ports: - "7120:80" environment: - AZURE_EXPERIMENTAL_ENABLE_ACTIVITY_SOURCE=true depends_on: - mssql report-service: build: context: ../Report dockerfile: Dockerfile -"7133:80" environment: - AZURE_EXPERIMENTAL_ENABLE_ACTIVITY_SOURCE=true depends_on: - mssql billing-service: The file contains the setup for multiple BFF (Backend For Frontend) services. Among the commented-out sections, we have Jaeger, which helps visualize traces. ports: - "5000:5000" #jaeger-all-in-one: # image: jaegertracing/all-in-one: latest # ports: # - "16686:16686" # - "14268" # - "14250" There is also Zipkin, another software for trace visualization. # Zipkin zipkin-all-in-one: image: openzipkin/zipkin:latest ports: "9411:9411" MS SQL and the collector are included as well. The collector specifies a config file and various ports to which data can be sent. # Collector otel-collector: image: ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib:0.51.0 command: ["--config=/etc/otel-collector-config.yaml" ] volumes: - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml ports: - "1888:1888" # pprof extension - "13133:13133" # health_check_extension - "4317:4317" - "4318:4318" - "55678:55679" # zpages extension depends_on: - jaeger-all-in-one - zipkin-all-in-one The config file includes key topics: receivers, exporters, processors, extensions, and the service itself, which acts as the constructor for all of this. otel-collector-config.yaml receivers: otlp: protocols: grpc: http:/ cors: allowed_origins: - http://localhost:4200 max_age: 7200 exporters: prometheus: endpoint: "0.0.0.0:8889" const_labels: label1: value1 logging: There is a single receiver, otlp, which represents the OpenTelemetry Protocol. Other receivers can be added as well (such as Prometheus). The receiver can be configured, and in my example, I set up the allowed_origins. receivers: otrip: protocols: grpc: http: cors: allowed_origins: http://localhost:4200 max_age: 7200 Next are exporters. They allow metrics to be sent to Prometheus. exporters: prometheus: endpoint: "0.0.0.0:8889" const_labels: label1: value1 logging: Then come the extensions. In this case, there is a health_check extension, which serves as an endpoint to check the collector's activity. extensions: health_check: pprof: endpoint: :1888 zpages: endpoint: :55679 Lastly, we have a service with pipelines, traces, and metrics. This section clarifies the data type, its source, processing, and destination. In this example, traces from the receiver are sent for logging to two backends, while metrics are sent to Prometheus. service extensions: [pprof, zpages, health_check] pipelines: traces: receivers: [otlp] processors: [batch] exporters: [zipkin, jaeger] metrics: receivers: [otlp] processors: [batch] exporters: [prometheus] Now, let's see how it works in practice. The frontend sends requests to the backend, and the backend uses BFF to send requests. We create a trace and observe the results. Among them, we see some requests with a 500 status. To understand what went wrong, we look at the traces through Zipkin. The detailed description of the problematic request shows that the frontend called BFF, which then sent two synchronous requests, one after the other. Through the traces, we can learn where this request was directed, the URL it targeted, and the HTTP method used. All this information is generated based on automatic data. Additionally, manual traces can be added to make the infographic more informative. Additionally, we see that BFF called BILLINGSERVICE. In it, there are middleware processes, requests sent to Azure, and an HTTP POST request that was sent to Azure, resulting in a CREATED status. The system also sets up and sends requests to Google. There is also POSTALSERVICE, where one request failed. Taking a closer look, we see the error description: "ServiceBusSender has already been closed...". Therefore, one must be cautious with ServiceBusSender in the future. Here, we can also observe multiple requests being sent to MS SQL. Finally, we obtain a comprehensive infographic of all the processes in the system. However, I want to warn you that things are not always as transparent. In our case, two traces, as they say, are "out of context." Nothing is clear about them: where they are executed, what happens with them, and there are minimal details. Sometimes, this happens, and you need to be prepared. As an option, you can add manual traces. Let's take a look at how metrics are sent to Prometheus. The illustration shows that the additional request was successfully sent. There was one request, and now there are five. Therefore, metrics are working properly. In the .NET application, requests are sent to Azure Service Bus, and they are processed by the Report Service. However, in Zipkin, there was no Report Service. Nevertheless, the metrics show that it is functioning. So, remember that not everything in OTLP works as expected everywhere. I know libraries that add traces to message brokers by default, and you can see them in the stack. However, this functionality is still considered experimental. Let's not forget about health_check. It shows whether our collector is functioning. {["status":"Server available","upSince": "2022-06-17T15:49:00.320594Z","uptime": "56m4.4995003s"} Now let's send data to Jaeger as well (by adding a new trace resource). After starting it, we need to resend the requests since it does not receive previous data. We receive a list of services like this: We have similar traces to those in Zipkin, including ones with a 500 status. I personally like the System Architecture tab, which displays a system graph. It shows that everything starts with a request to BFF, which is then redirected to BillingService and PostalService. This exemplifies how different tools display data in their unique ways. Lastly, let's discuss the order. In it, we can find the request and the generated trace ID. If you specify this trace ID in the system, you can learn what happened in the request and thoroughly investigate the HTTP call. This way, the frontend learns that it is the first to receive the User Action. In the same way, the frontend understands that it needs to create a trace that will be passed along the chain and send data to the collector. The collector collects and sends the data to Jaeger, Zipkin, and Prometheus. Therefore, the advantages of using the OpenTelemetry Protocol are evident. It is a flexible system for collecting, processing, and sending telemetry. It is particularly convenient in combination with Docker, which I used in creating this demo. However, always remember the limitations of OTLP. When it comes to traces, everything works quite well. However, the feasibility of using this protocol for metrics and logs depends on the readiness of specific system libraries and SDKs.
In this tutorial, we explore the powerful combination of Hazelcast and Redpanda to build high-performance, scalable, and fault-tolerant applications that react to real-time data. Redpanda is a streaming data platform designed to handle high-throughput, real-time data streams. Compatible with Kafka APIs, Redpanda provides a highly performant and scalable alternative to Apache Kafka. Redpanda's unique architecture enables it to handle millions of messages per second while ensuring low latency, fault tolerance, and seamless scalability. Hazelcast is a unified real-time stream data platform that enables instant action on data in motion by uniquely combining stream processing and a fast data store for low-latency querying, aggregation, and stateful computation against event streams and traditional data sources. It allows you to build resource-efficient, real-time applications quickly. You can deploy it at any scale from small edge devices to a large cluster of cloud instances. In this post, we will guide you through setting up and integrating these two technologies to enable real-time data ingestion, processing, and analysis for robust streaming analytics. By the end, you will have a solid understanding of how to leverage the combined capabilities of Hazelcast and Redpanda to unlock the potential of streaming analytics and instant action for your applications. So, let's dive in and get started! Pizza in Motion: The Solution Architecture for a Pizza Delivery Service First, let’s understand what we are going to build. Most of us love pizza, so let’s use a pizza delivery service as an example. Our pizza delivery service receives orders from multiple users in real time. These orders contain a timestamp, user_id, pizza_type, and quantity. We’ll generate orders using Python, ingest them into Redpanda, then use Hazelcast to process them. But what if you want to enrich pizza orders with contextual data? For example, recommending specific starters for specific types of pizzas. How can you do this in real-time? There are actually multiple options, but for this blog post, we’ll show you how to use Hazelcast to enrich pizza orders coming from Redpanda with starters stored in iMap in Hazelcast. Here’s a quick diagram of what this solution looks like. Tutorial: Real-Time Stream Processing With Redpanda and Hazelcast Before diving in, let's make sure we have all the necessary prerequisites in place. You can download the demo from this GitHub repository. Setting up Redpanda For the scope of this tutorial, we will set up a Redpanda cluster with Docker Compose. So, make sure you have Docker Compose installed locally. Create the docker-compose.yml file in a location of your choice and add the following content to it. XML version: "3.7" name: redpanda-quickstart networks: redpanda_network: driver: bridge volumes: redpanda-0: null services: redpanda-0: command: - redpanda - start - --kafka-addr internal://0.0.0.0:9092,external://0.0.0.0:19092 # Address the broker advertises to clients that connect to the Kafka API. # Use the internal addresses to connect to the Redpanda brokers' # from inside the same Docker network. # Use the external addresses to connect to the Redpanda brokers' # from outside the Docker network. - --advertise-kafka-addr internal://redpanda-0:9092,external://localhost:19092 - --pandaproxy-addr internal://0.0.0.0:8082,external://0.0.0.0:18082 # Address the broker advertises to clients that connect to the HTTP Proxy. - --advertise-pandaproxy-addr internal://redpanda-0:8082,external://localhost:18082 - --schema-registry-addr internal://0.0.0.0:8081,external://0.0.0.0:18081 # Redpanda brokers use the RPC API to communicate with eachother internally. - --rpc-addr redpanda-0:33145 - --advertise-rpc-addr redpanda-0:33145 # Tells Seastar (the framework Redpanda uses under the hood) to use 1 core on the system. - --smp 1 # The amount of memory to make available to Redpanda. - --memory 1G # Mode dev-container uses well-known configuration properties for development in containers. - --mode dev-container # enable logs for debugging. - --default-log-level=debug image: docker.redpanda.com/redpandadata/redpanda:v23.1.11 container_name: redpanda-0 volumes: - redpanda-0:/var/lib/redpanda/data networks: - redpanda_network ports: - 18081:18081 - 18082:18082 - 19092:19092 - 19644:9644 console: container_name: redpanda-console image: docker.redpanda.com/redpandadata/console:v2.2.4 networks: - redpanda_network entrypoint: /bin/sh command: -c 'echo "$$CONSOLE_CONFIG_FILE" > /tmp/config.yml; /app/console' environment: CONFIG_FILEPATH: /tmp/config.yml CONSOLE_CONFIG_FILE: | kafka: brokers: ["redpanda-0:9092"] schemaRegistry: enabled: true urls: ["http://redpanda-0:8081"] redpanda: adminApi: enabled: true urls: ["http://redpanda-0:9644"] ports: - 8080:8080 depends_on: - redpanda-0 The above file contains the configuration necessary to spin up a Redpanda cluster with a single broker. If needed, you can use a three-broker cluster. But, a single broker would be more than enough for our use case. Please note that using Redpanda on Docker is only recommended for development and testing purposes. For other deployment options, consider Linux or Kubernetes. To generate the data, we use a Python script: import asyncio import json import os import random from datetime import datetime from kafka import KafkaProducer from kafka.admin import KafkaAdminClient, NewTopic BOOTSTRAP_SERVERS = ( "localhost:19092" if os.getenv("RUNTIME_ENVIRONMENT") == "DOCKER" else "localhost:19092" ) PIZZASTREAM_TOPIC = "pizzastream" PIZZASTREAM_TYPES = [ "Margherita", "Hawaiian", "Veggie", "Meat", "Pepperoni", "Buffalo", "Supreme", "Chicken", ] async def generate_pizza(user_id): producer = KafkaProducer(bootstrap_servers=BOOTSTRAP_SERVERS) while True: data = { "timestamp_": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "pizza": random.choice(PIZZASTREAM_TYPES), "user_id": user_id, "quantity": random.randint(1, 10), } producer.send( PIZZASTREAM_TOPIC, key=user_id.encode("utf-8"), value=json.dumps(data).encode("utf-8"), ) print( f"Sent a pizza stream event data to Redpanda: {data}" ) await asyncio.sleep(random.randint(1, 5)) async def main(): tasks = [ generate_pizza(user_id) for user_id in [f"user_{i}" for i in range(10)] ] await asyncio.gather(*tasks) if __name__ == "__main__": # Create kafka topics if running in Docker. if os.getenv("RUNTIME_ENVIRONMENT") == "DOCKER": admin_client = KafkaAdminClient( bootstrap_servers=BOOTSTRAP_SERVERS, client_id="pizzastream-producer" ) # Check if topics already exist first existing_topics = admin_client.list_topics() for topic in [PIZZASTREAM_TOPIC]: if topic not in existing_topics: admin_client.create_topics( [NewTopic(topic, num_partitions=1, replication_factor=1)] ) asyncio.run(main()) Setting up Hazelcast Start a Hazelcast local cluster. This will run a Hazelcast cluster in client/server mode and an instance of Management Center running on your local network. brew tap hazelcast/hz brew install hazelcast@5.3.1 hz -V Now that we understand what we are going to build, and have prerequisites set up, let’s jump right into the solution. Step 1: Start the Redpanda Cluster Let’s start the Redpanda cluster by running the following command in a terminal. Make sure you are in the same location where you saved the docker-compose.yml file. docker compose up -d An output similar to the following confirms that the Redpanda cluster is up and running. [+] Running 4/4 ⠿ Network redpanda_network Created 0.0s ⠿ Volume "redpanda-quickstart_redpanda-0" Created 0.0s ⠿ Container redpanda-0 Started 0.3s ⠿ Container redpanda-console Started 0.6s Step 2: Run Hazelcast You can run the following command to start a Hazelcast cluster with one node. hz start To add more members to your cluster, open another terminal window and rerun the start command. Step 3: Run SQL on Hazelcast We will use the SQL shell—the easiest way to run SQL queries on a cluster. You can use SQL to query data in maps and Kafka topics. The results can be sent directly to the client or inserted into maps or Kafka topics. You can also use Kafka Connector which allows you to stream, filter, and transform events between Hazelcast clusters and Kafka. You can do so by running the following command: bin/hz-cli sql Step 4: Ingest Into Hazelcast iMap (pizzastream) Using the SQL command, we create pizzastream Map: CREATE OR REPLACE MAPPING pizzastream( timestamp_ TIMESTAMP, pizza VARCHAR, user_id VARCHAR, quantity DOUBLE ) TYPE Kafka OPTIONS ( 'keyFormat' = 'varchar', 'valueFormat' = 'json-flat', 'auto.offset.reset' = 'earliest', 'bootstrap.servers' = 'localhost:19092'); Step 5: Enrich the Stream With Recommendations Data (recommender) For this step, we create another Map: CREATE or REPLACE MAPPING recommender ( __key BIGINT, user_id VARCHAR, extra1 VARCHAR, extra2 VARCHAR, extra3 VARCHAR ) TYPE IMap OPTIONS ( 'keyFormat'='bigint', 'valueFormat'='json-flat'); We add some values into the Map: INSERT INTO recommender VALUES (1, 'user_1', 'Soup','Onion_rings','Coleslaw'), (2, 'user_2', 'Salad', 'Coleslaw', 'Soup'), (3, 'user_3', 'Zucchini_fries','Salad', 'Coleslaw'), (4, 'user_4', 'Onion_rings','Soup', 'Jalapeno_poppers'), (5, 'user_5', 'Zucchini_fries', 'Salad', 'Coleslaw'), (6, 'user_6', 'Soup', 'Zucchini_fries', 'Coleslaw'), (7, 'user_7', 'Onion_rings', 'Soup', 'Jalapeno_poppers'), (8, 'user_8', 'Jalapeno_poppers', 'Coleslaw', 'Zucchini_fries'), (9, 'user_9', 'Onion_rings','Jalapeno_poppers','Soup'); Step 6: Combine Both Maps Using SQL Based on the above two Maps, we send the following SQL query: SELECT pizzastream.user_id AS user_id, recommender.extra1 as extra1, recommender.extra2 as extra2, recommender.extra3 as extra3, pizzastream.pizza AS pizza FROM pizzastream JOIN recommender ON recommender.user_id = recommender.user_id AND recommender.extra2 = 'Soup'; Step 7: Send the Combined Data Stream to Redpanda To send the results back to Redpanda, we create a Jet job in Hazelcast that stores the SQL query results into a new Map, then into Redpanda: CREATE OR REPLACE MAPPING recommender_pizzastream( timestamp_ TIMESTAMP, user_id VARCHAR, extra1 VARCHAR, extra2 VARCHAR, extra3 VARCHAR, pizza VARCHAR ) TYPE Kafka OPTIONS ( 'keyFormat' = 'int', 'valueFormat' = 'json-flat', 'auto.offset.rest' = 'earliest', 'bootstrap.servers' = 'localhost:19092' ); CREATE JOB recommender_job AS SINK INTO recommender_pizzastream SELECT pizzastream.timestamp_ as timestamp_, pizzastream.user_id AS user_id, recommender.extra1 as extra1, recommender.extra2 as extra2, recommender.extra3 as extra3, pizzastream.pizza AS pizza FROM pizzastream JOIN recommender ON recommender.user_id = recommender.user_id AND recommender.extra2 = 'Soup'; Conclusion In this post, we explained how to build a pizza delivery service with Redpanda and Hazelcast. Redpanda adds value by ingesting pizza orders as high-throughput streams, storing them reliably, and allowing Hazelcast to consume them in a scalable manner. Once consumed, Hazelcast enriches pizza orders with contextual data (recommending starters to users instantly) and sends enriched data back to Redpanda. Hazelcast allows you to quickly build resource-efficient, real-time applications. You can deploy it at any scale, from small-edge devices to a large cluster of cloud instances. A cluster of Hazelcast nodes shares the data storage and computational load, which can dynamically scale up and down. Referring back to the pizza example, that means that this solution is reliable, even when there are significantly higher volumes of users ordering pizzas, like after a Superbowl ad. We look forward to your feedback and comments about this blog post! Share your experience with us in the Hazelcast GitHub repository. Hazelcast also runs a weekly live stream on Twitch, so give us a follow to get notified when we go live. To start exploring Redpanda, download the Redpanda Community Edition on GitHub. See you there!
In today's rapidly evolving enterprise architecture landscape, MongoDB and Couchbase have emerged as two prominent contenders in NoSQL databases. This comprehensive guide explores the key differences, comparisons, and trade-offs between these powerful solutions, shedding light on their unique features and strengths. As we delve into NoSQL databases, we'll present practical examples of using MongoDB and Couchbase with Java in real-world enterprise architectures. This hands-on approach will provide insights into their individual performance and capabilities and demonstrate how to switch swiftly between them, ensuring seamless adaptation to project requirements. Whether you're an experienced developer, a database administrator, or simply curious about NoSQL databases, this article equips you with the knowledge to make informed decisions in your database architecture. Get ready to elevate your database game, optimize applications for unmatched efficiency, and discover the possibilities MongoDB and Couchbase offers in today's dynamic technology landscape. MongoDB vs Couchbase We will begin by providing a foundational understanding of NoSQL document databases and how they revolutionize data storage and retrieval. Embracing a flexible schema and JSON/BSON document format, NoSQL databases pave the way for seamless adaptability to evolving data requirements. The Document database structure NoSQL Document Databases NoSQL databases, also known as "Not Only SQL," represent a family of databases designed to address the limitations of traditional relational databases. Unlike relational databases with structured tables with fixed schemas, NoSQL databases offer a flexible schema-less data model. Within the NoSQL category, document databases store and retrieve data in documents, typically JSON-like objects or BSON (Binary JSON) documents. These documents can have nested structures, making them highly adaptable to changing data requirements. Couchbase Couchbase is a leading NoSQL document database that excels in performance, scalability, and availability. It combines the features of a distributed key-value store and a document-oriented database, providing seamless horizontal scaling across multiple nodes and data centers. Couchbase is known for its strong consistency model and its ability to handle large-scale applications with low latency and high-throughput demands. It also offers robust mobile and edge computing support, making it an ideal choice for applications requiring data synchronization across different devices. Benefits of Couchbase High performance: Couchbase's distributed architecture and memory-first storage engine deliver exceptional performance, ensuring low-latency access to data even under heavy loads. Its efficient caching mechanisms optimize read and write operations, enabling seamless scaling for high-throughput applications. Scalability: Couchbase's automatic sharding and multi-dimensional scaling capabilities make horizontal scaling effortless. It can scale out quickly across multiple nodes and clusters, accommodating growing data and user demands without compromising performance. Strong consistency: Couchbase offers strong consistency with Multi-Dimensional Scaling (MDS), providing the ACID properties required for critical applications. It ensures data integrity and reliability, making it suitable for mission-critical use cases. Built-in full-text search: Couchbase integrates a powerful full-text search engine, enabling developers to execute complex search queries against unstructured data quickly. This built-in feature streamlines development and enhances the search functionality of applications. Mobile and edge computing support: Couchbase provides Couchbase Lite, an embedded NoSQL database designed for mobile and edge computing. It allows seamless data synchronization across devices, enabling offline access and real-time updates, making it ideal for mobile applications and IoT use cases. MongoDB MongoDB is another prominent NoSQL document database that has gained widespread popularity for its ease of use, flexibility, and powerful querying capabilities. As a document database, it stores data in JSON-like BSON documents and allows for dynamic schema evolution. MongoDB's design is well-suited for agile development, enabling developers to iterate rapidly and accommodate evolving application needs. With horizontal scaling capabilities and a rich ecosystem of tools and libraries, MongoDB has become a popular choice for various use cases, ranging from web applications to big data and real-time analytics. Benefits of MongoDB Flexible schema: MongoDB's schema-less design allows developers to adapt data structures on-the-fly, facilitating agile development and accommodating evolving application requirements without database schema migrations. Horizontal scalability: MongoDB's native support for horizontal scaling and automatic sharding empowers applications to handle massive amounts of data and concurrent user traffic effortlessly. It ensures seamless growth as user bases expand. Rich query Language: MongoDB Query Language (MQL) provides a flexible and expressive way to retrieve and manipulate data. With support for complex queries and powerful aggregation pipelines, developers can tailor responses to their needs. Replication and High Availability: MongoDB's replica set architecture ensures automatic data replication across multiple nodes, guaranteeing data redundancy and high availability. In primary node failure, secondary nodes can take over, minimizing downtime. Agile Development: MongoDB's ease of use, intuitive API, and straightforward setup facilitate rapid prototyping and development cycles. Developers can quickly iterate and experiment, reducing time-to-market for new features and applications. Commonalities Between Couchbase and MongoDB Both Couchbase and MongoDB share several core features and attributes: Document storage: Both databases store data in flexible, schema-less documents, allowing for straightforward data representation and manipulation. Horizontal scalability: They support horizontal scaling, allowing applications to distribute data across multiple nodes, ensuring high availability and fault tolerance. Distributed architecture: Both databases are designed to work in distributed environments, providing seamless data replication and distribution across clusters. JSON/BSON support: Couchbase and MongoDB use JSON-like BSON documents as their primary data format, ensuring compatibility with modern application architectures. Rich query capabilities: They offer robust query languages (N1QL for Couchbase and MongoDB Query Language) to retrieve and manipulate data efficiently. Now, let's explore the trade-offs associated with each database: Tradeoffs of Couchbase Complexity: Setting up and configuring Couchbase clusters can be more complex than MongoDB, especially in large-scale deployments. Learning curve: Developers new to Couchbase may face a steeper learning curve due to its advanced features and concepts. Community and ecosystem: While Couchbase has a growing community and ecosystem, it might not be as extensive as MongoDB's, resulting in potentially fewer resources and community-driven solutions. Tradeoffs of MongoDB Data consistency: MongoDB's default consistency model (eventual consistency) might not be suitable for applications requiring strong consistency, necessitating careful design and consideration. Joint operations: MongoDB's lack of support for traditional SQL-like may lead to complex data denormalization and increased application-side processing. Memory usage: MongoDB's memory usage can be relatively higher, especially when working with extensive indexes or datasets, which might impact overall performance. Choosing between Couchbase and MongoDB depends on specific project requirements, the existing technology stack, and the need for consistency, scalability, and ease of use. Understanding the trade-offs of each database empowers architects and developers to make informed decisions that align with their application's unique demands and performance goals. Feature couchbase mongodb Query Language N1QL (SQL-like queries) MongoDB Query Language (MQL) Consistency Model Strong consistency Eventual consistency (configurable) Sharding Mechanism Automatic and manual sharding Automatic sharding Aggregation Framework Yes (with N1QL) Yes Joins Yes (with N1QL) No (denormalization required) Our comparative study of Couchbase and MongoDB showcased their strengths in embracing flexibility and scalability in NoSQL document databases. While Couchbase excels with robust consistency and high-throughput capabilities, MongoDB shines in its agility and user-friendliness. The next session promises an exciting live code moment where we'll witness the true potential of both databases in real-world scenarios using Java. Through hands-on examples, we'll learn to leverage their unique features, execute queries, manage consistency, and handle aggregations. Join us for this immersive experience and discover how to build efficient, scalable applications with Couchbase and MongoDB, pushing the boundaries of modern data management. Show Me the Code In the upcoming session, we will embark on an exciting project to demonstrate the simultaneous use of Couchbase and MongoDB with Jakarta NoSQL specification in a Java enterprise application. Leveraging the power of NoSQL databases, we will create a Beer factory application that efficiently manages beer delivery addresses and user information. The application will run on the Open Liberty server and is compatible with Jakarta EE 10 or Eclipse MicroProfile 6 vendors. Switching database super smooth with Eclipse JNoSQL To get started, we'll use Jakarta EE Starter or Eclipse MicroProfile Starter to create the project and then add the required dependencies. With Jakarta NoSQL's extensive database support, we'll add Couchbase and MongoDB to the Maven dependency list. XML <dependency> <groupId>org.eclipse.jnosql.databases</groupId> <artifactId>jnosql-couchbase</artifactId> <version>${jnosql.version}</version> </dependency> <dependency> <groupId>org.eclipse.jnosql.databases</groupId> <artifactId>jnosql-mongodb</artifactId> <version>${jnosql.version}</version> </dependency> Once the dependencies are set, we'll configure the credentials in the microprofile-config.properties file. Additionally, we'll follow the Twelve-Factor App principles and utilize System Environment configuration overrides for Eclipse MicroProfile Configuration. The jnosql.document.provider key will ensure we choose the appropriate document provider when multiple options are present in the classpath. Properties files jnosql.couchbase.host=couchbase://localhost jnosql.couchbase.user=root jnosql.couchbase.password=123456 jnosql.document.database=factory jnosql.mongodb.host=localhost:27017 #for use couchbase uncomment this line #jnosql.document.provider=org.eclipse.jnosql.databases.couchbase.communication.CouchbaseDocumentConfiguration #for use MongoDB uncomment this line jnosql.document.provider=org.eclipse.jnosql.databases.mongodb.communication.MongoDBDocumentConfiguration The heart of the application lies in entity creation, where we define the Beer entity along with a nested Address subdocument structure. Java @Entity("beer") public class Beer { @Id private String id; @Column private String name; @Column private String style; @Column private String hop; @Column private String yeast; @Column private String malt; @Column private Address address; @Column private String user; } @Entity public class Address { @Column private String city; @Column private String country; } With the entities ready, we'll establish communication between the Java application and the databases using the Template and DocumentTemplate. Leveraging Jakarta Data's repository interface, we'll efficiently interact with the databases and perform CRUD operations on the Beer entities. Java @ApplicationScoped @Path("beers2") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class BeerTemplateResource { private final DocumentTemplate template; @Inject public BeerTemplateResource(DocumentTemplate template) { this.template = template; } @Deprecated BeerTemplateResource() { this(null); } @GET public List<Beer> findByAll(@BeanParam BeerParam param){ if(param.isMaltAndHopQuery()){ return this.template.select(Beer.class).where("malt") .eq(param.malt()) .and("hop") .eq(param.hop()) .result(); } else if(param.isHopQuery()) { return this.template.select(Beer.class).where("hop") .eq(param.hop()) .result(); } else if(param.isMaltQuery()) { return this.template.select(Beer.class).where("malt") .eq(param.malt()) .result(); } return this.template.select(Beer.class).result(); } @POST public void create(Beer beer){ this.template.insert(beer); } @DELETE @Path("{id}") public void deleteById(@PathParam("id") String id){ this.template.delete(Beer.class).where("id").eq(id).execute(); } @Path("random") @POST public void random() { var faker = new Faker(); for (int index = 0; index < 1_000; index++) { var beer = Beer.of(faker); this.template.insert(beer); } } } The journey to building this robust application concludes with executing the application, witnessing the seamless integration of Couchbase and MongoDB, and experiencing the efficient management of beer delivery data. Through this project, we'll gain invaluable insights into harnessing the capabilities of NoSQL databases in Java enterprise applications, laying the groundwork for scalable, high-performance data management solutions. Join us for this hands-on experience as we delve into the world of NoSQL databases and unlock their potential in enterprise architecture. MongoDB Configuration and Run After configuring both databases, we proceed with the next steps to set up MongoDB and Couchbase for our project. Starting with MongoDB, the process involves creating and configuring the database environment. For simplicity, we can use a single Docker container using the command provided: Shell docker run -d --name mongodb-instance -p 27017:27017 mongo Next, we modify the properties file by commenting the Couchbase line and uncommenting the MongoDB line to set MongoDB as the provider: Properties files #for use couchbase uncomment this line #jnosql.document.provider=org.eclipse.jnosql.databases.couchbase.communication.CouchbaseDocumentConfiguration #for use MongoDB uncomment this line jnosql.document.provider=org.eclipse.jnosql.databases.mongodb.communication.MongoDBDocumentConfiguration With MongoDB set up, you can now use it as desired. Couchbase Configuration and Run Transitioning to Couchbase, we follow a similar approach by creating a single Docker container for testing purposes using the provided command: Shell docker run -d --name db -p 8091-8097:8091-8097 -p 9123:9123 -p 11207:11207 -p 11210:11210 -p 11280:11280 -p 18091-18097:18091-18097 couchbase While Couchbase requires more setup configuration, you can easily define the required parameters through its user interface (UI) accessed via your browser: Access http://localhost:8091/ui/index.html. Choose Setup New Cluster. Define root as Admin username. Define 123456 as the password. Set localhost as Cluster Name. Accept the terms and conditions. Navigate to the Buckets session. Create a bucket named beers using the "Add Bucket" option. Click on beers and add Hero and Villain as collections. Go to the Query session and execute: CREATE PRIMARY INDEX `#primary` ON `factory`.`_default`.`beer`. In the configuration, we need to do the opposite of what we did before, as shown in the code below: Properties files #for use couchbase uncomment this line jnosql.document.provider=org.eclipse.jnosql.databases.couchbase.communication.CouchbaseDocumentConfiguration #for use MongoDB uncomment this line #jnosql.document.provider=org.eclipse.jnosql.databases.mongodb.communication.MongoDBDocumentConfiguration With both MongoDB and Couchbase ready for action, we can develop our Beer factory application, leveraging the capabilities of Jakarta NoSQL to interact with these powerful NoSQL databases using Java seamlessly. Prepare for an exciting journey as we simultaneously build and demonstrate the project running with MongoDB and Couchbase. Consuming the API Once you have defined and set up the database as per your preferences, you can proceed with the following setup for the project: 1. Build the project using Maven: Shell mvn clean package 2. Run the application: Shell java -jar target/eclipse-store.jar Now, the application is running, and you can test it using any HTTP client. In this case, we will use curl for demonstration purposes: To generate random beer data, use the following command: Shell curl --location --request POST 'http://localhost:9080/beers/random' 2. To retrieve all beers, use: Shell curl --location 'http://localhost:9080/beers/' 3. To filter beers based on specific criteria (e.g., hop "Magnum" or malt "Vienna"), use: Properties files curl --location 'http://localhost:9080/beers/?page=1&hop=Magnum' curl --location 'http://localhost:9080/beers/?page=1&hop=Magnum&malt=Vienna' By executing these commands, you will interact with the Beer Factory application, which communicates seamlessly with MongoDB and Couchbase databases using Jakarta NoSQL. Through this demonstration, you can witness the power and efficiency of NoSQL databases in enterprise applications, showcasing the true potential of Couchbase and MongoDB in a Java environment. Conclusion We have embarked on an illuminating journey exploring the power of two prominent NoSQL databases: MongoDB and Couchbase. Leveraging the capabilities of Jakarta NoSQL, we seamlessly integrated these databases into a Java enterprise application, unlocking their potential for efficient data management and retrieval. Through Jakarta NoSQL's implementation by Eclipse JNoSQL, we experienced the ease of working with NoSQL databases within the Jakarta EE ecosystem. The standardized annotations, APIs, and Jakarta Data's repository interface streamlined the development process, enabling smooth communication with MongoDB and Couchbase. By adopting Jakarta NoSQL, we harnessed the flexibility of NoSQL document databases, embracing the schema-less nature of JSON/BSON documents. It allowed us to adapt to evolving data requirements swiftly, a critical advantage in modern enterprise applications with constantly changing needs. The seamless switching capability between MongoDB and Couchbase was facilitated by Jakarta NoSQL, empowering us to effortlessly switch between the two databases based on our project requirements. With a few simple configuration changes, we were able to leverage the strengths of both databases, ensuring optimal performance and scalability. Docker containers further simplified the setup process, allowing us to create isolated environments for testing and development purposes. With just a few commands, we had fully functional MongoDB and Couchbase instances ready to interact with our application. Throughout this journey, we witnessed the power of Jakarta NoSQL in creating a robust, scalable, and high-performance Java enterprise application that can harness the capabilities of multiple NoSQL databases. As the NoSQL landscape continues to evolve, Jakarta NoSQL remains a reliable choice for seamless integration and efficient utilization of NoSQL databases in enterprise architectures. Jakarta NoSQL and its implementation by Eclipse JNoSQL have proven invaluable tools for modern Java developers, enabling easy and efficient usage of NoSQL databases like MongoDB and Couchbase. As we continue exploring and innovating in data management, Jakarta NoSQL will undoubtedly remain at the forefront, empowering developers to build scalable, flexible, high-performance applications in the ever-evolving technology landscape. References Source code MongoDB Official Documentation Couchbase Official Documentation Jakarta NoSQL Project Eclipse JNoSQL GitHub Repository Jakarta Data Project Docker Official Website Oracle Cloud Container Service
I don’t know anyone who is still using the Oracle JDK. It has been my recommendation for quite a while to just switch to an OpenJDK distribution as they are roughly drop-in replacements for Oracle’s official JDK. I’ve repeated that advice quite frequently, but I guess I glossed over a lot of details that might be insignificant for hackers but can become a pretty big deal in an enterprise setting. Following the review from Bazlur I chose to also pick up Simon Ritter's “OpenJDK Migration for Dummies." This book has two things going against it: For dummies — I’ve never read one of these before and never considered reading those. While it does use overly simplified language, I think the Dummies brand hurts this book. The subject matter is sophisticated and geared towards developers (and DevOps) who can follow the nuances. I think it might deter some developers from reading it, which is a shame. It's a corporate book — Simon is the Deputy CTO at Azul. This creates the justified concern that the book is a promotion for Azul products. It has those. But having read through it, the material seems objective and valuable. It does give one advantage: we're getting the book for free. Unique Analysis There are many Java books, but this is the first time I read a book that explains these specific subjects. The first chapter discusses licensing, TCK (Test Compatibility Kit), and similar issues. I’m familiar with all of them since I worked for Sun Microsystems and Oracle. I had a team composing TCKs for the mobile platform at Sun Microsystems. However, even experienced engineers outside of Sun might be unfamiliar with these tests. The TCK is how we verify that our port of OpenJDK is still compatible with Java. The book illustrates why a reputable OpenJDK distribution can be trusted due to the TCK. This is knowledge that’s probably not available elsewhere if you aren’t deeply involved in the JVM. Simon explained nicely the scope of the current TCK (139k tests for Java 11), but I think he missed one important aspect: TCK isn’t enforced. Oracle doesn’t know you ran the TCK “properly.” It can’t verify that. This is why OpenJDK vendors must have a good reputation and understanding of the underlying QA process. This is just the beginning, but pretty much every chapter covered material that I haven’t seen in other books. As a side note, the whole TCK creation process is pretty insane. The engineers in my team would go over the JavaDoc like religious scholars and fill up Excel sheets with every statement made by the JavaDoc or implied by the Javadoc. Then devise tests to verify in isolation that every statement is indeed true. In that sense, TCK doesn’t test quality. It tests compliance to a uniform, consistent standard. A JDK can fail after running for a week, and we might not be able to tell from running the TCK alone. Early releases of JDK 8 did exactly that at that time… Learning From a 'For Dummies' Book I mentioned at the top of this post that I treat the OpenJDK migration casually as a drop-in replacement. This book convinced me that this is not always the case. There are some nuances. I was casually aware of most of them, e.g., I worked a lot with Pisces back in the day, but I never saw all of these nuances in a single place. This is an important list for anyone considering a migration of this type. One should comb over these and verify the risks before embarking on such a migration. As a startup, you might not care about exact fonts or NTLM support, but in an enterprise environment, there are still projects that might rely on that. In a later chapter comparing the various OpenJDK distributions, Simon included a great chart illustrating the differences. Take into consideration that Simon works for Azul, and it is obvious in the chart. Still, the content of the chart is pretty accurate. I am missing the Microsoft VM in the comparison, but I guess it’s a bit too new to be registered as a major vendor. Business Related Aspects I did consulting work for major organizations quite often, such as banks, insurance companies, etc. In these organizations, commercial support is crucial. I used to scoff at that notion, but as I ran into some of the edge cases those organizations run into, I get it. We had a senior engineer from IBM debug AIX and Websphere issues. Similarly, a bank I worked with was having issues with RTL support in newer versions of Swing. As the older JDKs were nearing the end of their life cycle, they were forced to migrate but had no way of addressing these issues. Oracle’s support for those issues was a dud in that case. Commercial support for the JVM isn’t something I ever needed or wanted to buy, but I understand the motivation. At the end of the book, Simon goes into more detail on the extra value that can be layered on top of an OpenJDK distribution. This was interesting to me as I often don’t understand the “it’s free” business model. It helped me both in understanding the motivation for offering (and maintaining) an OpenJDK release. It’s also valuable when I work with larger organizations. I can advise better on the value they can deliver for Java (e.g., fast response to zero days, etc.). Who Should Read This Book? It’s not a book for everyone. If you’re using the Oracle JDK, then you need to pick this up and review it. Make sure the reasons you picked Oracle JDK are still valid. They probably aren’t. If your job includes picking the JDKs for provisioning or development then you should make sure you’re familiar with the materials in the book. If you’re just learning Java or using it in a hobbyist capacity, then there’s an appendix on Java’s history that might be interesting to you. But the book as a whole is probably more targeted at developers who are handling production. In that regard, it’s useful both for server and desktop developers. BTW if you or someone you know is interested in learning Java, please check out my new book for learning Java.
I sometimes surprise fellow technologists when I tell them that a successful digital payments company we built runs on FreeBSD. It’s not that they haven’t heard of FreeBSD — the open-source operating system turns 30 this year, after all. Rather, it’s that they hadn’t previously thought of FreeBSD as an enterprise OS. But that’s exactly what it is and how we use it: FreeBSD is a critical enterprise-grade OS that is on par with —and in some ways surpasses — other operating systems when working in enterprise environments. I’ve been a FreeBSD user for 16 years now. Like many others, I got started as a hobbyist. I’d been working with various OSes in enterprise environments, mostly in the payments industry, and getting frustrated with some of the drawbacks of other well-known options: random error codes, endless support tickets, and black boxes. A colleague tipped me off to FreeBSD, and I immediately loved it for its simplicity and capabilities. We rebuilt that company on the FreeBSD stack, and the project was highly successful. When we launched our digital payments company, we built it on FreeBSD and never looked back; it is still our OS of choice today. We have nearly 60 servers running across two different U.S. data centers. We run on an active-active architecture, with our own IPV4 blocks that we Anycast. We operate several highly segmented VLANs for isolation and security purposes. And we do all of this with FreeBSD. One reason this surprises some people is that, as a payments company, we handle millions of credit card and bank account numbers. To put it in simple terms: a data breach would be extraordinarily bad for us. We do intensive pen testing and vulnerability scanning on a daily basis to make sure we’re as strong as possible. We regularly undergo security and compliance assessments, which we pass with flying colors. And in the process, I’ve spent many hours talking with auditors, InfoSec pros, CIOs, CISSPs, and others in the industry to make the business case for FreeBSD and teach them what we’ve learned first-hand: FreeBSD is a remarkably powerful, reliable enterprise OS that can stand up to the most stringent security standards and regulatory scrutiny. Below, I’ll unpack five fundamentals behind why FreeBSD is our enterprise OS of choice—which can double as five fundamentals for making the business case for FreeBSD in your own organization. Using FreeBSD as an Enterprise OS: Five Things To Know 1. Security and Stability One of the best features of FreeBSD — along with all of the products in its ecosystem — is that it’s a completely free and open-source project. Historically, open source sometimes got a bad rap in enterprise environments, but that is changing as IT pros recognize that, when properly configured and managed, open source software can be just as secure (or even more so) than proprietary code. A key reason for this is that open source eliminates all of the black boxes. You can look under the hood and see everything that is going on; there are no mysteries. Even more importantly, there are lots of eyes on the code, which means issues tend to be discovered and mitigated much faster, especially where there is a longstanding, engaged community around the project (like there is with FreeBSD). Moreover, I’ve never seen another OS that has such consistent behavior from the release-and-patch cycle to the release-and-patch cycle. With FreeBSD, we never encounter the common scenario where you install an update or new version and, suddenly, a bunch of tools stop working because an underlying library changed. FreeBSD’s base install also runs a very minimal set of services that are exposed to the outside world, which supports the many security frameworks that emphasize zero-trust or least-privilege principles by only running what is actually necessary. It’s easy to add what you need while not needing to worry about uninstalling or disabling a bunch of things that you don’t. Similarly, it’s very easy to tune and configure everything from how your network stack runs to how your intrusion detection runs and more. The documentation is great; again, no black boxes or endless support tickets. The open-source nature also helps deliver excellent performance, minimizing calls and other potential drags on your environment. Finally, from a security and stability standpoint, there’s a separation between that base OS and everything else that gets installed on that OS. 2. High Availability High availability (HA) is a hot topic and an increasingly common requirement in many sectors. In the past, I’d never been able to actually achieve it with other OSes without purchasing expensive firewalls and routing equipment. We built our active-active, multi-homed, Anycast-ed environment more or less using out-of-the-box FreeBSD. Even our firewalls are FreeBSD. The only third-party application we’ve had to add is BIRD for BGP routing. After two years, we have had zero downtime. We’ve load-tested the current configuration to 20,000 concurrent credit card transactions – and that didn’t even stretch the platform to its outer limits. And we’ve done this essentially with simple hardware and out-of-the-box FreeBSD. 3. Patching This is probably one of the most compelling reasons to consider using FreeBSD in an enterprise environment — and one of my favorite aspects to talk about as a result. I am regularly asked some version of the question: “Wait, you’re using FreeBSD as the OS for running a firewall?” And my answer is always: “Yes, absolutely.” The reality is that a lot of auditing and assessment firms and CISSPs are used to seeing brand-name OSes. There’s nothing wrong with that, per se, but I like to share my belief that many of those platforms are using a lot of open sources — like FreeBSD and its PF firewall — under the hood. They’re wrapping their own UI and code around it, of course, but essentially it’s the same thing we do on our own. And what happens is that when we run things like pkg audit nightly to check our systems against CVEs and other known issues, we can patch those vulnerabilities much faster than would be possible if we had to wait on a commercial OS vendor. When a vulnerability becomes known, we have a fresh, patched build up and running soon after. 4. Configuration Management Nobody loves the mysterious checkbox UI style of configuration management. It’s another form of black box in that you don’t really know what’s going on in the background. For this reason, I love the simplicity of configuring FreeBSD. It’s all flat files. You don’t have to log into a UI to make changes. FreeBSD’s file-based configuration arms you with a persuasive case in terms of security and auditing because it creates a simple but powerful change management system. Just about any security and assessment framework requires some type of change management process in place. The file-based configuration makes it simple to GIF those file configurations or use version control on them, inherently adding change management to your configuration management. If you want to take it to the next level, we use SaltStack for centralized automation and orchestration of our configuration management. There is not a single manually created or updated configuration file on any server in our environment; everything is done via SaltStack and Git, which makes it easy for us to track configuration management and change management throughout the environment. That gives us a centralized audit trail of any change made on any system. 5. Compliance Management Regulatory compliance is a major requirement for us as a payments company, as it is for other firms in the banking and financial services sector (as well as most industries now). We regularly work with PCI DSS and the NIST Cybersecurity Framework. In our experience, frameworks like these are heavily tuned toward one of the major commercial operating systems[, and, as a result, many of the auditors and assessors are more experienced with that OS as well. That’s created a learning curve when doing something like a PCI level 1 assessment or a NIST audit, where we’ve basically had to teach the assessors all of the above because they weren’t familiar with how FreeBSD gets configured and secured. (As a result, there are now some vendors who know very well how it works!) If you do work with a vendor that doesn’t know FreeBSD, the first assessment or audit will likely be more painful than future ones because you’ll have to help them navigate that learning curve. But here’s the great news: Every time we’ve completed a course with a security assessor who wasn’t previously familiar with FreeBSD, by the end of it, they always become convinced FreeBSD is the best OS from the security, compliance, patching, and configuration perspectives. Seriously. And successfully completing those audits and assessments is ultimately what qualifies an OS as “enterprise-ready,” and I can say emphatically that FreeBSD exceeds that bar. That’s why I love sharing our experience and what we’ve learned—because, inevitably, that might help be a catalyst for helping our technologists and teams reap the benefits of such a great open-source OS.
OpenTelemetry (OTel) is an open-source standard used in the collection, instrumentation, and export of telemetry data from distributed systems. As a framework widely adopted by SRE teams and security teams, OTel is more than just one nice-to-have tool among many; it is critical. In this post, we’ll explore the role that OTel plays in system security. We’ll look at how telemetry data is used to secure systems along with how OTel securely handles telemetry data. Then, we’ll consider concrete practices — basic and advanced — that you can adopt as you use OTel in your organization. Let’s begin by looking at the relationship between system security and telemetry data. Telemetry Data Is Essential Robust system security relies on the application of many practices, including: Defense in depth Risk mitigation Granular access control Early threat detection and response Resiliency and business continuity Designing and implementing solid security requires a deep understanding of your systems coupled with high visibility into both your business systems and your security mechanisms. That visibility comes through the capturing and monitoring of telemetry data. Telemetry data (logs, metrics, distributed tracing) provides information about the normal behavior of a system. By continuously collecting and analyzing this data, security teams can establish baselines and thresholds that help identify deviations from the norm. The Role of Telemetry Data in Security Telemetry data is pivotal in enhancing the security posture of organizations. It not only facilitates incident response and forensic investigations, but also aids in proactive threat hunting and compliance auditing. Its importance becomes even more pronounced when identifying patterns, trends, and possible indicators of compromise (IOCs) in complex systems, thus enabling robust security measures and strategies. OTel Is the Framework for Working Telemetry Data In the quest for tools to help with collecting and normalizing telemetry data, several options have come and gone. However, OTel has emerged as the industry standard for working with telemetry data. It provides a standardized way to capture and transmit telemetry data across various components and services within your systems. In addition to its role in instrumentation, OTel brings secure practices to its handling of telemetry data. OTel as Instrumental to Zero-Trust Architecture OTel’s focus on observability and telemetry data collection is enhanced and supported by various security capabilities. First, OTel ensures the secure transmission of telemetry data across distributed systems. It supports secure communication protocols like HTTPS and gRPC, which use Transport Layer Security (TLS). This ensures that telemetry data is encrypted during transmission, protecting it from unauthorized access or tampering. OTel can also leverage existing authentication strategies within your system. By integrating OTel with authentication systems like OAuth, JWT, or API keys, you ensure that only authorized entities can access and transmit telemetry data. As per compliance policies, your systems may need role-based access control (RBAC). OTel instrumentation works within the boundaries of your RBAC policies. By defining fine-grained access control rules, you can specify which authenticated users or services have permission to perform instrumentation actions or access telemetry data through an OTel collector. OTel contributes significantly to auditing and compliance efforts. By capturing logs as part of the telemetry data, it provides visibility into the actions and behaviors of a distributed system. This can be valuable for detecting security incidents, investigating breaches, and complying with regulatory requirements. This is particularly true if your organization is using highly federated service mesh architecture. The goal of which could be to separate customer data from a payment portal and the application data. As the industry standard, OTel enjoys seamless integration on the receiving end, with countless systems and components that support OTel out of the box. Similarly, on the exporting end, cloud providers and observability platforms support the ingestion of telemetry data from OTel. Therefore, a major advantage of using OTel is the avoidance of vendor/technology lock-in. If you want to use multiple collection agents (for logs, metrics, security event data, traces) or migrate away from a specific vendor, you can do so without losing all the hard work you put into instrumenting your applications and the processes you created around monitoring. Finally, the OTel project actively involves the developer community in addressing security-related concerns. Through community contributions, code reviews, and security audits, efforts are made to identify and mitigate potential security vulnerabilities. Regular updates and patches are released to address any security issues discovered, ensuring a more secure framework. Now that we’ve looked at the what, let’s look at the how. How might you enhance the security of your systems with telemetry data and OTel? Best Practices for Securing Your Systems With OTel Let’s consider concrete steps that you can take to begin securing your systems with OTel. 1. Identify Applications and Security Components Before you can effectively instrument your system for security, you must first identify which parts would be the most beneficial to instrument. Identify your applications and the security components of your system. The security-related components include: Firewalls Intrusion detection systems (IDS) Antivirus software Authentication mechanisms 2. Use Auto-Instrumentation to Quickly Collect Logs, Metrics, and Trace Data Use OTel libraries to instrument the components you’ve identified. Instrumentation allows you to capture relevant telemetry data from these components. Define and collect data from the applications and security components to monitor their health and performance. Metrics such as CPU usage, memory utilization, network traffic, and event counts can provide insights into your system’s overall health and resource utilization. OTel allows you to define and capture custom metrics specific to your security infrastructure. 3. Log Security Events Use the distributed tracing capabilities of OTel to trace security events across different components and services. By capturing traces, you can gain visibility into the flow of security-related activities, identify bottlenecks, and analyze the effectiveness of security controls. Traces help you understand the sequence of events during security incidents or breaches, aiding in incident response and forensic investigations. 4. Export and Visualize Security-Related Telemetry Data Configure OTel exporters to transmit the collected telemetry data to backend systems for storage, analysis, and visualization. Choose an appropriate observability platform. You can use open-source solutions such as Grafana, Prometheus, and Elasticsearch. Or, you can use an integrated platform like Sumo Logic to receive and process all of the telemetry data (in the form of logs, metrics, traces, and events). These platforms provide dashboards and visualization tools to monitor and analyze your system's real-time health, performance, and security. 5. Enable Alerts on Anomaly Detection Set up alerting mechanisms based on predefined thresholds or anomaly detection algorithms. By leveraging the collected telemetry data, you can configure alerts to notify security teams when certain metrics or events fall outside of normal or expected ranges. This enables proactive monitoring, rapid incident response, and the mitigation of potential security breaches. 6. Use Logs for Incident Investigation By combining distributed traces with application logs, you bring context that can help reconstruct the sequence of events leading up to an incident. Utilize all the information to perform a root cause analysis and better understand the impact of the incident. Define log filtering and retention policies to ensure you have the relevant data and enough historic context. Balance the amount and duration of logs you keep with the storage cost. The above list would be considered baseline practices for organizations seeking to use OTel for enhancing system security. If you’re looking to level up your security practices, the following advanced practices may interest you: Advanced Security Practices With OTel Advanced security analysis with OTel can provide valuable insights and capabilities to further enhance security monitoring and incident response. Let’s look at three key opportunities. 1. Leverage Metadata to Aid in Security Analysis and Auditing OTel allows you to attach custom metadata to telemetry data. You might attach user IDs, transaction IDs, or any other contextual information relevant to security analysis and auditing. By incorporating this metadata, you can enrich the telemetry data with additional details that might aid you in the following ways: Identifying the source of security events Tracing the actions of specific users or transactions Conducting forensic investigations during security incidents. 2. Identify Deviations from Policies and Best Practices Telemetry data collected by OTel can play a vital role in identifying deviations from security policies and best practices within your systems. By defining policies and desired security configurations, you can compare the telemetry data against these benchmarks to identify any deviations or non-compliant behaviors. 3. Automatic Incident Response With AIOps When telemetry data collected and exported by OTel is used in conjunction with AIOps tools, you can implement proactive security measures such as automatic incident response. By using machine learning algorithms and anomaly detection techniques to analyze system telemetry data, you can identify patterns of unusual behavior and potential security threats. The result is early detection of incidents—or even prediction of potential incidents. You can couple this early detection with an automatic response or simply collect and consolidate the relevant information, devising an action plan which your human security engineers can approve and apply. Conclusion Capturing and monitoring telemetry data is essential if you are to understand your systems and keep them secure. With it, you can detect anomalous system behavior, identify policy deviations, and enable rapid incident response. OTel is a powerful framework — the industry standard — for collecting and analyzing telemetry data in systems. It provides security capabilities such as secure data transmission, authentication integration, access control, and auditing. With OTel, you can instrument applications and security components to collect metrics and trace security events. As you export that telemetry data to observability platforms, you can visualize your data and set up alerts. By combining telemetry data with AIOps tools, you unlock capabilities for early incident detection and automatic incident response. The ability to secure your systems ultimately revolves around your adoption of OTel. Without it, you cut off access to an essential tool that helps you gather the data you need for effective security incident detection and response.
Kubernetes autoscaling quickly gets tricky, but developers can save time and effort thanks to all the ecosystem's tools that make configuration and management easier. One of them is Helm. Helm charts are there to help teams define, install, and upgrade complex Kubernetes applications. And the Cluster Autoscaler Helm Chart does some of the heavy lifting for you around autoscaling. But how exactly does this Helm chart work? And are there any alternatives you could use to make cluster autoscaling even easier? Let's Start With the Basics: What Is Cluster Autoscaler Anyway? Along with Kubernetes Horizontal Pod Autoscaler (HPA) and Vertical Pod Autoscaler, Cluster Autoscaler is one of the autoscaling mechanisms K8s provides. Its goal is pretty simple: Cluster Autoscaler changes the number of nodes (worker machines) in a cluster. Note that Cluster Autoscaler can only manage nodes on a handful of supported platforms. And every platform has its own specific requirements or limitations. The tricky part here is that the autoscaler controller operates at the infrastructure level. To do its job, it needs permission to add and delete virtual machines (if you're in the cloud). So before you set Cluster Autoscaler to work, ensure airtight security for these credentials. Following the principle of least privilege is definitely a good idea (I'll share some more Cluster Autoscaler best practices later on). When Should You Use Cluster Autoscaler? There's no denying that a well-configured Cluster Autoscaler can make a massive impact on your cloud bill. Three amazing things happen when you're able to dynamically scale the number of nodes to match the current level of utilization: Minimize cloud waste, Maximize your ROI from every dollar you spend on cloud services, And, at the same time, you make sure there is no downtime as your application scales. That isn't to say that Cluster Autoscaler doesn't have its limitations. How Does Cluster Autoscaler Work? Cluster Autoscaler simply loops through two tasks: It checks for unschedulable pods (pods that don't have a node to run on), And it calculates whether consolidating all the currently deployed pods on a smaller number of nodes is possible or not. Here's how Cluster Autoscaler works, step by step: 1. It Scans Clusters To Identify Any Pods That Can’t Be Scheduled on Any Existing Nodes Where do unschedulable pods come from? The issue might arise because of inadequate CPU or memory resources or due to the pod's node taint tolerations or affinity rules failing to match an existing node. In addition to that, it could be that you have just scaled your application, and the new pods haven't found a node to run on yet. Step 2: Cluster Autoscaler Detects a Cluster That Contains Unschedulable Pods Next, it checks the managed node pools of this cluster to understand whether adding a node would let the pod run. If this is true, the autoscaler adds a node to the node pool. Step 3: Cluster Autoscaler Also Scans Nodes Across the Node Pools It Manages If it detects a node with pods that could be rescheduled to other nodes in the cluster, the autoscaler evicts the pods, moves them to an existing node, and finally removes the spare node. Note: When deciding to move a pod, Cluster Autoscaler considers pod priority and PodDisruptionBudgets. If you’re looking for more guidance on how to configure and run Cluster Autoscaler, here’s a hands-on guide to EKS Cluster Autoscaler with code snippets. Why Use the Cluster Autoscaler Helm Chart? Let's start with Helm. Helm is a package manager that has the sole purpose of making Kubernetes application deployment easier. Helm does that via charts. There are two ways you can use it: you can create your own chart or reuse existing Helm charts. Once you start developing a lot of tools in your K8s cluster, you'll be thankful for Helm charts. But Helm doesn't only help you with deployment. You can also manage releases, including rollbacks, if something goes south in your deployment. Let's get back to the original topic of this article: The Cluster Autoscaler Helm Chart is a configuration template that allows you to deploy and manage the Cluster Autoscaler component in Kubernetes using Helm (here's the repo). But Cluster Autoscaler Comes With Limitations We've already started talking about maximizing utilization and minimizing cloud costs. The problem with Cluster Autoscaler is that it doesn’t consider CPU or memory utilization in its decision-making process. All it does is check the pod’s limits and requests. What does this mean in dollar terms? That the Autoscaler isn't able to see all the unutilized capacity requested by pods. As a result, your cluster will end up being wasteful, and your utilization efficiency will be low. Another issue with Cluster Autoscaler is that spinning up a new node takes more time than the autoscaler gives you, as it issues a request for scaling up within a minute. This delay might easily cause performance issues while your pods are waiting for capacity. Finally, Cluster Autoscaler doesn't take into account the changing costs of instance types, making the decisions less cost-optimized. Is There an Alternative to the Cluster Autoscaler Helm Chart? Certain cloud cost management tools feature autoscalers that select the best matching instance types autonomously or according to your preferences, which can be simply configured. However, it's important to note that not all tools necessarily come with this feature. These specific platforms continuously track cloud provider inventory pricing and availability in supported cloud provider regions and zones. This information is utilized to select instance families that provide the most value. Implementing the Cluster Autoscaler Helm Charts, when available, is more straightforward than doing all the work independently. Since these particular services are fully managed, you won't have to devote any time thinking about upgrades, scalability, and availability. Furthermore, they often come with Helm charts. Here's an example that illustrates how closely these select autoscaling tools follow the actual resource requests in the cluster. Such cloud management tools are compatible with Amazon EKS, Kops, and OpenShift, as well as GKE and AKS. If you're in the market for dependable autoscalers that save you money without causing any performance issues, be sure to delve into the documentation of the specific tools that offer these capabilities.
Open-source Cloud Foundry Korifi is designed to provide developers with an efficient approach to delivering and managing cloud-native applications on Kubernetes with automated networking, security, availability, and more. With Korifi, the simplicity of the cf push command is now available on Kubernetes. In this tutorial, I will walk you through the installation of Korifi on kind using a locally deployed container registry. The installation process happens in two steps: Installation of prerequisites Installation of Korifi and dependencies Then, we will deploy two applications developed in two very different programming languages: Java and Python. This tutorial has been tested on Ubuntu Server 22.04.2 LTS. Let's dive in! Installing Prerequisites There are several prerequisites needed to install Korifi. There is a high chance that Kubernetes users will already have most of them installed. Here is the list of prerequisites: Cf8 cli Docker Go Helm Kbld Kind Kubectl Make To save time, I wrote a Bash script that installs the correct version of prerequisites for you. You can download it and run it by running the two commands below. Shell git clone https://github.com/sylvainkalache/korifi-prerequisites-installation cd korifi-prerequisites-installation && ./install-korifi-prerequisites.sh Installing Korifi The Korifi development team maintains an installation script to install Korifi on a kind cluster. It installs the required dependencies and a local container registry. This method is especially recommended if you are trying Korifi for the first time. Shell git clone https://github.com/cloudfoundry/korifi cd korifi/scripts && ./deploy-on-kind.sh korifi-cluster The install script does the following: Creates a kind cluster with the correct port mappings for Korifi Deploys a local Docker registry using the twuni helm chart Creates an admin user for Cloud Foundry Installs cert-manager to create and manage internal certificates within the cluster Installs kpack, which is used to build runnable applications from source code using Cloud Native Buildpacks Installs contour, which is the ingress controller for Korifi Installs the service binding runtime, which is an implementation of the service binding spec Installs the metrics server Installs Korifi Similar to installing prerequisites, you can always do this manually by following the installation instructions. Setting up Your Korifi Instance Before deploying our application to Kubernetes, we must sign into our Cloud Foundry instance. This will set up a tenant, known as a target, to which our apps can be deployed. Authenticate with the Cloud FoundryAPI: Shell cf api https://localhost --skip-ssl-validation cf auth cf-admin Create an Org and a Space. Shell cf create-org tutorial-org cf create-space -o tutorial-org tutorial-space Target the Org and Space you created. Shell cf target -o tutorial-org -s tutorial-space Everything is ready; let’s deploy two applications to Kubernetes. Single-Command Deployment to Kubernetes Deploying a Java Application For the sake of the tutorial, I am using a sample Java app, but you feel free to try it out on your own. Shell git clone https://github.com/sylvainkalache/sample-web-apps cd sample-web-apps/java Once you are inside your application repository, run the following command. Note that the first run of this command will take a while as it needs to install language dependencies from the requirements.txt and create a runnable container image. But all subsequent updates will be much faster: Shell cf push my-java-app That’s it! The application has been deployed to Kubernetes. To check the application status, you can simply use the following command: Shell cf app my-java-app Which will return an output similar to this: Showing health and status for app my-java-app in org tutorial-org / space tutorial-space as cf-admin... Shell Showing health and status for app my-java-app in org tutorial-org / space tutorial-space as cf-admin... name: my-java-app requested state: started routes: my-java-app.apps-127-0-0-1.nip.io last uploaded: Tue 25 Jul 19:14:34 UTC 2023 stack: io.buildpacks.stacks.jammy buildpacks: type: web sidecars: instances: 1/1 memory usage: 1024M state since cpu memory disk logging details #0 running 2023-07-25T20:46:32Z 0.1% 16.1M of 1G 0 of 1G 0/s of 0/s type: executable-jar sidecars: instances: 0/0 memory usage: 1024M There are no running instances of this process. type: task sidecars: instances: 0/0 memory usage: 1024M There are no running instances of this process. Within this helpful information, we can see the app URL of our app and the fact that it is properly running. You can double-check that the application is properly responding using curl: Shell curl -I --insecure https://my-java-app.apps-127-0-0-1.nip.io/ And you should get an HTTP 200 back. Shell HTTP/2 200 date: Tue, 25 Jul 2023 20:47:07 GMT x-envoy-upstream-service-time: 134 vary: Accept-Encoding server: envoy Deploying a Python Application Next, we will deploy a simple Python Flask application. While we could deploy a Java application directly, there is an additional step for a Python one. Indeed, we need to provide a Buildpack that Korifi can use for Python applications – a more detailed explanation is available in the documentation. Korifi uses Buildpacks to transform your application source code into images that are eventually pushed to Kubernetes. The Paketo open-source project provides base production-ready Buildpacks for the most popular languages and frameworks. In this example, I will use the Python Paketo Buildpacks as the base Buildpacks. Let’s start by adding the Buildpacks source to our ClusterStore by running the following command: Shell kubectl edit clusterstore cf-default-buildpacks -n tutorial-space Then add the line - image: gcr.io/paketo-buildpacks/python, your file should look like this: Shell spec: sources: - image: gcr.io/paketo-buildpacks/java - image: gcr.io/paketo-buildpacks/nodejs - image: gcr.io/paketo-buildpacks/ruby - image: gcr.io/paketo-buildpacks/procfile - image: gcr.io/paketo-buildpacks/go - image: gcr.io/paketo-buildpacks/python Then we need to specify when to use these Buildbacks by editing our ClusterBuilder. Execute the following command: Shell kubectl edit clusterbuilder cf-kpack-cluster-builder -n tutorial-space Add the line - id: paketo-buildpacks/python at the top of the spec order list. your file should look like this: Shell spec: order: - group: - id: paketo-buildpacks/python - group: - id: paketo-buildpacks/java - group: - id: paketo-buildpacks/go - group: - id: paketo-buildpacks/nodejs - group: - id: paketo-buildpacks/ruby - group: - id: paketo-buildpacks/procfile That’s it! Now you can either bring your own Python app or use this sample one by running the following commands: Shell git clone https://github.com/sylvainkalache/sample-web-apps cd sample-web-apps/python And deploy it: Shell cf push my-python-app Run curl to make sure the app is responding: Shell curl --insecure https://my-python-app.apps-127-0-0-1.nip.io/dzone Curl should return the following output: Shell Hello world! Python version: 3.10.12 Video Conclusion As you can see, Korifi makes deploying applications to Kubernetes very easy. While Java and Python are two very different stacks, the shipping experience remains the same. Korifi supports many other languages like Go, Ruby, PHP, Node.js, and more.
Mark Gardner
Independent Contractor,
The Perl Shop
Nuwan Dias
VP and Deputy CTO,
WSO2
Radivoje Ostojic
Principal Software Engineer,
BrightMarbles
Adam Houghton
Senior Software Developer,
SAS Institute