In the SDLC, deployment is the final lever that must be pulled to make an application or system ready for use. Whether it's a bug fix or new release, the deployment phase is the culminating event to see how something works in production. This Zone covers resources on all developers’ deployment necessities, including configuration management, pull requests, version control, package managers, and more.
Microservices and Containerization
According to our 2022 Microservices survey, 93% of our developer respondents work for an organization that runs microservices. This number is up from 74% when we asked this question in our 2021 Containers survey. With most organizations running microservices and leveraging containers, we no longer have to discuss the need to adopt these practices, but rather how to scale them to benefit organizations and development teams. So where do adoption and scaling practices of microservices and containers go from here? In DZone's 2022 Trend Report, Microservices and Containerization, our research and expert contributors dive into various cloud architecture practices, microservices orchestration techniques, security, and advice on design principles. The goal of this Trend Report is to explore the current state of microservices and containerized environments to help developers face the challenges of complex architectural patterns.
This article explains what Eclipse JKube Remote Development is and how it helps developers build Kubernetes-native applications with Quarkus. Introduction As mentioned in my previous article, microservices don’t exist in a vacuum. They typically communicate with other services, such as databases, message brokers, or other microservices. Because of this distributed nature, developers often struggle to develop (and test) individual microservices that are part of a larger system. The previous article examines some common inner-loop development cycle challenges and shows how Quarkus, combined with other technologies, can help solve some of the challenges. Eclipse JKube Remote Development was not one of the technologies mentioned because it did not exist when the article was written. Now that it does exist, it certainly deserves to be mentioned. What Is Eclipse JKube Remote Development? Eclipse JKube provides tools that help bring Java applications to Kubernetes and OpenShift. It is a collection of plugins and libraries for building container images and generating and deploying Kubernetes or OpenShift manifests. Eclipse JKube Remote Development is a preview feature first released as part of Eclipse JKube 1.10. This new feature is centered around Kubernetes, allowing developers the ability to run and debug Java applications from a local machine while connected to a Kubernetes cluster. It is logically similar to placing a local development machine inside a Kubernetes cluster. Requests from the cluster can flow into a local development machine, while outgoing requests can flow back onto the cluster. Remember this diagram from the first article using the Quarkus Superheroes? Figure 1: Local development environment logically inserted into a Kubernetes cluster. We previously used Skupper as a proxy to connect a Kubernetes cluster to a local machine. As part of the 1.10 release, Eclipse JKube removes the need to use Skupper or install any of its components on the Kubernetes cluster or your local machine. Eclipse JKube handles all the underlying communication to and from the Kubernetes cluster by mapping Kubernetes Service ports to and from the local machine. Eclipse JKube Remote Development and Quarkus The new Eclipse JKube Remote Development feature can make the Quarkus superheroes example very interesting. If we wanted to reproduce the scenario shown in Figure 1, all we’d have to do is re-configure the rest-fights application locally a little bit and then run it in Quarkus dev mode. First, deploy the Quarkus Superheroes to Kubernetes. Then, add the Eclipse JKube configuration into the <plugins> section in the rest-fights/pom.xml file: XML <plugin> <groupId>org.eclipse.jkube</groupId> <artifactId>openshift-maven-plugin</artifactId> <version>1.11.0</version> <configuration> <remoteDevelopment> <localServices> <localService> <serviceName>rest-fights</serviceName> <port>8082</port> </localService> </localServices> <remoteServices> <remoteService> <hostname>rest-heroes</hostname> <port>80</port> <localPort>8083</localPort> </remoteService> <remoteService> <hostname>rest-villains</hostname> <port>80</port> <localPort>8084</localPort> </remoteService> <remoteService> <hostname>apicurio</hostname> <port>8080</port> <localPort>8086</localPort> </remoteService> <remoteService> <hostname>fights-kafka</hostname> <port>9092</port> </remoteService> <remoteService> <hostname>otel-collector</hostname> <port>4317</port> </remoteService> </remoteServices> </remoteDevelopment> </configuration> </plugin> Version 1.11.0 of the openshift-maven-plugin was the latest version as of the writing of this article. You may want to check if there is a newer version available. This configuration tells OpenShift (or Kubernetes) to proxy requests going to the OpenShift Service named rest-fights on port 8082 to the local machine on the same port. Additionally, it forwards the local machine ports 8083, 8084, 8086, 9092, and 4317 back to the OpenShift cluster and binds them to various OpenShift Services. The code listing above uses the JKube OpenShift Maven Plugin. If you are using other Kubernetes variants, you could use the JKube Kubernetes Maven Plugin with the same configuration. If you are using Gradle, there is also a JKube OpenShift Gradle Plugin and JKube Kubernetes Gradle Plugin available. Now that the configuration is in place, you need to open two terminals in the rest-fights directory. In the first terminal, run ./mvnw oc:remote-dev to start the remote dev proxy service. Once that starts, move to the second terminal and run: Shell ./mvnw quarkus:dev \ -Dkafka.bootstrap.servers=PLAINTEXT://localhost:9092 \ -Dmp.messaging.connector.smallrye-kafka.apicurio.registry.url=http://localhost:8086 This command starts up a local instance of the rest-fights application in Quarkus dev mode. Requests from the cluster will come into your local machine. The local application will connect to other services back on the cluster, such as the rest-villains and rest-heroes applications, the Kafka broker, the Apicurio Registry instance, and the OpenTelemetry collector. With this configuration, Quarkus Dev Services will spin up a local MongoDB instance for the locally-running application, illustrating how you could combine local services with other services available on the remote cluster. You can do live code changes to the local application while requests flow through the Kubernetes cluster, down to your local machine, and back to the cluster. You could even enable continuous testing while you make local changes to ensure your changes do not break anything. The main difference between Quarkus Remote Development and Eclipse JKube Remote Development is that, with Quarkus Remote Development, the application is running in the remote Kubernetes cluster. Local changes are synchronized between the local machine and the remote environment. With JKube Remote Development, the application runs on the local machine, and traffic flows from the cluster into the local machine and back out to the cluster. Wrap-Up As you can see, Eclipse JKube Remote Development compliments the Quarkus Developer Joy story quite well. It allows you to easily combine the power of Quarkus with Kubernetes to help create a better developer experience, whether local, distributed, or somewhere in between.
Kubernetes is an open-source container orchestration platform that helps manage and deploy applications in a cloud environment. It is used to automate the deployment, scaling, and management of containerized applications. It is an efficient way to manage application health with Kubernetes probes. This article will discuss Kubernetes probes, the different types available, and how to implement them in your Kubernetes environment. What Are Kubernetes Probes? Kubernetes probes are health checks that are used to monitor the health of applications and services in a Kubernetes cluster. They are used to detect any potential problems with applications or services and identify potential resource bottlenecks. Probes are configured to run at regular intervals and send a signal to the Kubernetes control plane if they detect any issues with the application or service. Kubernetes probes are typically implemented using the Kubernetes API, which allows them to query the application or service for information. This information can then be used to determine the application’s or service’s health. Kubernetes probes can also be used to detect changes in the application or service and send a notification to the Kubernetes control plane, which can then take corrective action. Kubernetes probes are an important part of the Kubernetes platform, as they help ensure applications and services run smoothly. They can be used to detect potential problems before they become serious, allowing you to take corrective action quickly. A successful message for a readiness probe indicates the container is ready to receive traffic. If a readiness probe is successful, the container is considered ready and can begin receiving requests from other containers, services, or external clients. A successful message for a liveness probe indicates the container is still running and functioning properly. If a liveness probe succeeds, the container is considered alive and healthy. If a liveness probe fails, the container is considered to be in a failed state, and Kubernetes will attempt to restart the container to restore its functionality. Both readiness and liveness probes return a successful message with an HTTP response code of 200-399 or a TCP socket connection is successful. If the probe fails, it will return a non-2xx HTTP response code or a failed TCP connection, indicating that the container is not ready or alive. A successful message for a Kubernetes probe indicates the container is ready to receive traffic or is still running and functioning properly, depending on the probe type. Types of Kubernetes Probes There are three types of probes: Startup probes Readiness probes Liveness probes 1. Startup Probes A startup probe is used to determine if a container has started successfully. This type of probe is typically used for applications that take longer to start up, or for containers that perform initialization tasks before they become ready to receive traffic. The startup probe is run only once, after the container has been created, and it will delay the start of the readiness and liveness probes until it succeeds. If the startup probe fails, the container is considered to have failed to start and Kubernetes will attempt to restart the container. 2. Readiness Probes A readiness probe is used to determine if a container is ready to receive traffic. This type of probe is used to ensure a container is fully up and running and can accept incoming connections before it is added to the service load balancer. A readiness probe can be used to check the availability of an application’s dependencies or perform any other check that indicates the container is ready to serve traffic. If the readiness probe fails, the container is removed from the service load balancer until the probe succeeds again. 3. Liveness Probes A liveness probe is used to determine if a container is still running and functioning properly. This type of probe is used to detect and recover from container crashes or hang-ups. A liveness probe can be used to check the responsiveness of an application or perform any other check that indicates the container is still alive and healthy. If the liveness probe fails, Kubernetes will attempt to restart the container to restore its functionality. Each type of probe has its own configuration options, such as the endpoint to check, the probe interval, and the success and failure thresholds. By using these probes, Kubernetes can ensure containers are running and healthy and can take appropriate action if a container fails to respond. How To Implement Kubernetes Probes Kubernetes probes can be implemented in a few different ways: The first way is to use the Kubernetes API to query the application or service for information. This information can then be used to determine the application’s or service’s health. The second way is to use the HTTP protocol to send a request to the application or service. This request can be used to detect if an application or service is responsive, or if it is taking too long to respond. The third way is to use custom probes to detect specific conditions in an application or service. Custom probes can be used to detect things such as resource usage, slow responses, or changes in the application or service. Once you have decided which type of probe you will be using, you can then configure the probe using the Kubernetes API. You can specify the frequency of the probe, the type of probe, and the parameters of the probe. Once the probe is configured, you can deploy it to the Kubernetes cluster. Today, I’ll show how to configure health checks to your application deployed on Kubernetes with HTTP protocol to check whether the application is ready, live, and starting as per our requirements. Prerequisites A Kubernetes cluster from any cloud provider. You can even use Minikube or Kind to create a single-node cluster. Docker Desktop to containerize the application. Docker Hub to push the container image to the Docker registry. Node.js installed, as we will use a sample Node.js application. Tutorial Fork the sample application here. Get into the main application folder with the command: cd Kubernetes-Probes-Tutorial Install the dependencies with the command: npm install Run the application locally using the command: node app.js You should see the application running on port 3000. In the application folder, you should see the Dockerfile with the following code content: # Use an existing node image as base image FROM node:14-alpine # Set the working directory in the container WORKDIR /app # Copy package.json and package-lock.json to the container COPY package*.json ./ # Install required packages RUN npm install # Copy all files to the container COPY . . # Expose port 3000 EXPOSE 3000 # Start the application CMD [ "npm", "start" ] This Dockerfile is to create a container image of our application and push it to the Docker Hub. Next, build and push your image to the Docker Hub using the following command: docker buildx build --platform=linux/arm64 --platform=linux/amd64 -t docker.io/Docker Hub username/image name:tag --push -f ./Dockerfile . You can see the pushed image on your Docker Hub account under repositories. Next, deploy the manifest files. In the application folder, you will notice a deployment.yaml file with health checks/probes included, such as readiness and liveness probes. Note: we have used our pushed image name in the YAML file: apiVersion: apps/v1 kind: Deployment metadata: name: notes-app-deployment labels: app: note-sample-app spec: replicas: 2 selector: matchLabels: app: note-sample-app template: metadata: labels: app: note-sample-app spec: containers: - name: note-sample-app-container image: pavansa/note-sample-app resources: requests: cpu: "100m" imagePullPolicy: IfNotPresent ports: - containerPort: 3000 readinessProbe: httpGet: path: / port: 3000 livenessProbe: httpGet: path: / port: 3000 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 You can see the image used and the health checks configured in the above YAML file. We are all set with our YAML file. Assuming you have a running cluster ready, let’s deploy the above mentioned manifest file with the command: kubectl apply -f deployment.yaml You should see the successful deployment of the file: “deployment.apps/notes-app-deployment created.” Let’s check the pod status with the following command to make sure the pods are running: kubectl get pods Let’s describe a pod using the following command: kubectl describe pod notes-app-deployment-7fb6f5d74b-hw5fn You can see the “Liveness and Readiness” status when you describe the pods. Next, let’s check the events section. You can see the different events, such as “scheduled,” “pulled,” “created,” and “started.” All the pod events were successful. Conclusion Kubernetes probes are an important part of the Kubernetes platform, as they help ensure applications and services run smoothly. They can be used to detect potential problems before they become serious, allowing you to take corrective action quickly. Kubernetes probes come in two types: Liveness probes Readiness probes Along with custom probes that can be used to detect specific conditions in an application or service. Implementing Kubernetes probes is a straightforward process that can be done using the Kubernetes API. If you are looking for a way to ensure the health of your applications and services, Kubernetes probes are the way to go. So, make sure to implement Kubernetes probes in your Kubernetes environment today!
Deploying applications with Kubernetes has become increasingly popular due to its numerous benefits. Kubernetes enables easy management of containerized applications, providing a platform for application deployment, scaling, and management. With Kubernetes, applications can be deployed quickly and consistently across different environments, including on-premises and cloud platforms. While deploying applications with Kubernetes, many of us will have questions about what deployment type to use — rolling, blue-green, canary, etc. In this article, we will discuss these deployment types (canary, rolling, and blue-green), how they work, and which one you should choose. Canary Deployment Kubernetes canary deployment is a technique for rolling out new features or changes to a small subset of users or servers before releasing the update to the entire system. This is done by creating a new replica set with the updated version of the software while keeping the original replica set running. A small percentage of traffic is then routed to the new replica set, while the majority of the traffic continues to be served by the original replica set. This allows for the new version to be tested in a live environment while minimizing the risk of issues affecting the entire system. If issues are detected during the canary deployment, it can be quickly rolled back to the original replica set. Canary deployments are a valuable tool for minimizing risk and ensuring high availability in complex distributed systems, by allowing for controlled testing of changes before they are released to the entire system. Here is an example of a canary deployment YAML file in Kubernetes. apiVersion: apps/v1 kind: Deployment metadata: name: myapp-deployment spec: replicas: 3 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: myapp-container image: myapp:v1 ports: - containerPort: 8080 readinessProbe: httpGet: path: / port: 8080 initialDelaySeconds: 5 periodSeconds: 5 livenessProbe: httpGet: path: / port: 8080 initialDelaySeconds: 10 periodSeconds: 10 In this example, we are deploying a Kubernetes deployment that will create three replicas of our application. The deployment uses a selector to find the appropriate pods to manage based on the label app: myapp. The template section specifies the configuration for the pods created by the deployment. In this case, we're running a single container in each pod, which is specified with the containers field. The container image used is myapp:v1, which is the first version of our application. We've also added two probes to the container to check its health. The readinessProbe checks whether the container is ready to receive traffic, and the livenessProbe checks whether the container is still running. If either of these probes fails, the pod will be restarted. To perform a canary deployment, we would create a second deployment YAML file that specifies the new version of the application, for example, myapp:v2. We would then update the original deployment to set up a canary test by scaling up the replicas of the new version to 1 while keeping the replicas of the old version at 2. Once the new version is running, we can test it to ensure it's functioning correctly. If everything looks good, we can update the original deployment to scale up the replicas of the new version and gradually scale down the replicas of the old version. This will cause a small percentage of traffic to gradually shift from the old version to the new version until all traffic goes to the new version. If any issues arise with the new version, we can quickly roll back to the old version by reversing the process. Rolling Deployment Kubernetes rolling deployment is a strategy for updating and deploying new versions of software in a controlled and gradual manner. Instead of deploying updates all at once, Kubernetes rolls out changes incrementally, reducing the risk of downtime and allowing for easy rollbacks in case of errors. Rolling deployment involves creating a new replica set with the updated version of the software while gradually scaling down the old replica set. This allows for the new version to be deployed while the old version is still running, ensuring that there is no interruption to service. Once the new version is fully deployed, the old replica set is deleted, and the deployment is complete. Kubernetes rolling deployment is essential for ensuring high availability and reliability in complex distributed systems. Here is an example of a rolling deployment YAML file in Kubernetes. apiVersion: apps/v1 kind: Deployment metadata: name: myapp-deployment spec: replicas: 3 selector: matchLabels: app: myapp strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 1 template: metadata: labels: app: myapp spec: containers: - name: myapp-container image: myapp:v1 ports: - containerPort: 8080 In this example, we are deploying a Kubernetes deployment that will create three replicas of our application. The deployment uses a selector to find the appropriate pods to manage based on the label app: myapp. The strategy section specifies the strategy that Kubernetes should use to update the deployment. In this case, we are using a RollingUpdate strategy. This means that Kubernetes will gradually update the deployment by replacing old replicas with new replicas. The maxSurge and maxUnavailable fields control the rolling update process. maxSurge determines the maximum number of replicas that can be created above the desired number of replicas, and maxUnavailable determines the maximum number of replicas that can be unavailable during the update. The template section specifies the configuration for the pods created by the deployment. In this case, we're running a single container in each pod, which is specified with the containers field. The container image used is myapp:v1, which is the first version of our application. To update the deployment, we would create a new deployment YAML file that specifies the new version of the application, for example, myapp:v2. We would then update the original deployment to point to the new YAML file. Once the update is started, Kubernetes will gradually replace old replicas with new ones until all replicas run the new version. The rolling update process allows for updates to be performed with minimal disruption to users, as it always ensures that at least one replica of the old version is running while the new version is being rolled out. Blue-Green Deployment Strategy The blue-green Kubernetes deployment strategy is a technique for releasing new versions of an application to minimize downtime and risk. It involves running two identical environments, one serving as the active production environment (blue) and the other as a new release candidate (green). The new release candidate is thoroughly tested before being switched with the production environment, allowing for a smooth transition without any downtime or errors. Here is an example YAML file for a blue-green deployment on Kubernetes: apiVersion: apps/v1 kind: Deployment metadata: name: my-app labels: app: my-app spec: replicas: 2 selector: matchLabels: app: my-app template: metadata: labels: app: my-app version: blue spec: containers: - name: my-app image: myregistry/my-app:blue ports: - containerPort: 8080 --- apiVersion: v1 kind: Service metadata: name: my-app-service spec: selector: app: my-app ports: - protocol: TCP port: 80 targetPort: 8080 In this example, we define a deployment for an application called "my-app" with two replicas. The deployment has a label selector of "app: my-app", which will match the corresponding service that routes traffic to the deployment. The first template defines the "blue" version of the application. It is defined with the label "version: blue", which allows for easy identification of the version currently in production. The container image for this version is pulled from the Docker registry at "myregistry/my-app:blue".When a new version is ready, a new template is added with the label "version: green". This new template will have an updated container image with the new version of the application. Once this new template has been created and is ready to be released, the Kubernetes service can be updated to route traffic to the new "green" deployment. This blue-green deployment strategy ensures that the new version of the application can be fully tested before it is released, with no downtime or errors. Which One Should You Choose? The choice of Kubernetes deployment strategy depends on the specific needs and requirements of the application or service being deployed. A canary deployment strategy is typically used when deploying new features or updates to a subset of users or servers to test them in a live environment and is often used for applications that require frequent updates. This strategy allows for the testing of new features with minimal impact on the production environment and can help to identify issues before they affect the entire system. A rolling deployment strategy is ideal for applications that require zero downtime during deployment. It involves incrementally rolling out new versions of an application while ensuring that the old version is still running, reducing the risk of downtime and allowing for easy rollbacks in case of issues. A blue-green deployment strategy is useful for applications where downtime is acceptable but must be minimized. It involves running two identical environments, one serving as the active production environment and the other as a new release candidate. The new release candidate is tested before being switched with the production environment, allowing for a smooth transition without any downtime or errors. In summary, the choice of deployment strategy depends on the specific needs and requirements of the application or service being deployed. Canary deployments are ideal for frequent updates and testing, rolling deployments are ideal for zero-downtime deployments, and blue-green deployments are ideal for minimizing downtime during deployments.
In addition to the third article of the series, this article covers some more case studies related to To Do Tasks. For this use case, I used the MS Graph Java SDK 5.42.0. If you haven’t read parts 1 and 2 of this series, here they are: Part 1: “Commonly Occurring Errors in Microsoft Graph Integrations and How To Troubleshoot Them (Part 1)” Part 2: “Commonly Occurring Errors in Microsoft Graph Integrations and How To Troubleshoot Them (Part 2)” Read Tasks With a Specific Time Zone Microsoft Graph Todo Task is a powerful tool for developers to integrate Microsoft To Do task management software into their applications. It enables them to create, update, and retrieve tasks using the Microsoft Graph API. One of the essential considerations when working with task management tools, like Microsoft To Do, is ensuring they operate in the correct time zone. Time zone plays a crucial role in task management tools because they rely on deadlines and reminders to keep users on track. If a user creates a task and sets a deadline or reminder, it must be displayed accurately based on the user’s time zone. Developers working with the Microsoft Graph Todo Task must ensure their applications handle time zones correctly to avoid confusion and ensure that tasks are displayed accurately. Microsoft Graph API provides built-in functionality for handling time zones. When creating or updating tasks, developers can set the time zone information for the task. The API supports the standard time zone format (e.g., “America/Los_Angeles”) and the Windows time zone format (e.g., “Pacific Standard Time”). Developers can also use the Microsoft Graph API to retrieve the user’s time zone information. This can be useful when displaying tasks and reminders in the correct time zone. By default, the API returns the user’s time zone in the IANA time zone format (e.g., “America/Los_Angeles”). However, developers can also request the user’s time zone in the Windows time zone format. When retrieving tasks using the Microsoft Graph API, developers can also specify the time zone in which they want to retrieve the tasks. This can be useful when displaying tasks to users in different time zones. By default, the API returns tasks in the user’s time zone. However, developers can also specify a different time zone using the “Prefer” header in the API request. For instance, in Java: LinkedList<Option> requestOptions = new LinkedList<>(); requestOptions.add(new HeaderOption("Prefer", "outlook.timezone=" + "\"" + timeZone + "\"")); Beside the header adjustments, let’s take a look at how to create a ToDo task with a specific time zone: Java String timeZone = "Pacific Standard Time"; //replace with the desired timezone String apiEndpoint = "https://graph.microsoft.com/v1.0/me/tasks"; String json = "{\n" + " \"subject\": \"Complete Task\",\n" + " \"body\": {\n" + " \"contentType\": \"Text\",\n" + " \"content\": \"This is a task with specific time zone\"\n" + " },\n" + " \"dueDateTime\": {\n" + " \"dateTime\": \"2021-12-01T14:30:00\",\n" + " \"timeZone\": \""+timeZone+"\"\n" + " }\n" + "}"; HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(apiEndpoint)) .header("Authorization", "Bearer " + accessToken) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); In the above code, replace the value of the timeZone variable with the desired time zone. Also, replace the subject and body properties of the task object with your own values. The dueDateTime property of the task object contains a dateTime and timeZone property. Set the dateTime property to the desired due date and time in ISO format. Set the timeZone property to the desired time zone. Time zone is a crucial consideration when working with task management tools like Microsoft To Do. Developers working with the Microsoft Graph Todo Task must ensure their applications handle time zones correctly to avoid confusion and ensure tasks are displayed accurately. Microsoft Graph API provides built-in functionality for handling time zones and supports DST, making it easy for developers to create applications that work with tasks in different time zones. Read the Changekey Property of ToDo Tasks Based on a documented issue on Github from November 2020, there are missing properties from the Microsoft.Graph.todoTask resource type as compared to the Microsoft.OutlookServices.Task type. Some of these properties are absolutely necessary for business logic and play a key role, according to the comments of the issue: property Description AssignedTo The name of the person who has been assigned the task. Attachments The collection of FileAttachment and ItemAttachment attachments for the task. Categories The categories associated with the task. ChangeKey The version of the task. HasAttachments Set to true if the task has attachments. Owner The name of the person who created the task. ParentFolderId The unique identifier for the task’s parent folder. Sensitivity Indicates the level of privacy for the event: Normal, Personal, Private, Confidential. StartDateTime The date in the specified time zone when the task is to begin. By now, not all requested properties are available for use in Microsoft Graph v1.0. In the current version of the MS Graph Java SDK 5.42.0, the following properties are included within the default data model: Property Attachments Categories HasAttachments Owner StartDateTime In this use case, I will show how to read the changeKey from ToDo Tasks via the MS Graph APIs and the Java SDK. The changeKey is part of the ETag that comes with the ToDo Task. To retrieve the ETag value for a ToDo task via Microsoft Graph, you can use the GET method on the tasks endpoint, along with the task ID. Here is an example URL to retrieve the ETag value for a specific task: https://graph.microsoft.com/v1.0/me/tasks/{task_id}?$select=etag Replace {task_id} with the ID of the task you want to retrieve the ETag value for. The $select=etag query parameter will ensure that only the ETag value is returned in the response. How do you read the changeKey from the ToDo task via the MS Graph Java SDK? The SDK contains the Java class AdditionalDataManager. The class holds additional properties that are not part of the default object’s schema, according to the Microsoft documentation. We can read all JSON elements provided by the response from the API and which are not included within the default data model. Let’s take a look at the following sample: Java public static final String ODATA_ETAG = "@odata.etag"; public static final String PREFIX = "W/\""; public static final String SUFFIX = "\""; String eTag = todoTask.additionalDataManager().get(ODATA_ETAG).getAsString(); String changeKey = convertETagToChangeKey(eTag); private String convertETagToChangeKey(String eTag) { String changekey = ""; if (Objects.nonNull(eTag) && eTag.startsWith(PREFIX)) { String noPrefixSubString = eTag.substring(PREFIX.length()); if (noPrefixSubString.endsWith(SUFFIX)) { return noPrefixSubString.substring(0, noPrefixSubString.length() - 1); } } return changekey; } In this sample, we use the key @odata.etag to read the value of the ETag and remove the prefix and suffix of the ETag to get the value of the changeKey. Conclusion At this point, you should have a better understanding of the common integration errors that may be seen in Microsoft Graph integrations and how to troubleshoot them. I hope this article was informative and useful. Feel free to comment below and share this article!
Docker is a containerization technology that allows developers to package and deploy applications in lightweight, portable containers. These containers are isolated from the host operating system, which makes them portable across different environments and eliminates the “works on my machine” problem. Docker is a popular platform for creating and managing containerized applications; however, several alternatives for Docker can also be used for this purpose. Podman, Kubernetes, Openshift, LXD, Docker Swarm, BuidKit, and Mesos are some of the popular Docker alternatives available in the market today. In this article, we’ll discuss the following three Docker hub alternatives: Podman, Containerd, and LXD. So, let’s Begin! Podman Developed by RedHat, Podman is a daemonless, open-source, Linux-native container engine that is considered one of the best alternatives for Docker. Podman is used to build, run, and manage Linux OCI containers and container images. A container engine is an all-in-one software that is responsible for creating, running, and managing containers. A container engine provides an API or command-line interface for interacting with the containers, allowing developers to create, start, stop, and manage containers. Examples of container engines include Docker, Podman, and CRI-O. Podman uses the lib pod library, which provides a higher-level API for managing pods and containers. It also provides built-in support for rootless containers and improved security features. Advantages of Podman Easy to use: Podman has a simple and intuitive command-line interface that is similar to Docker’s command-line interface, making it easy for users who are already familiar with Docker to start using Podman. Compatible with Kubernetes: Podman can be used in confluence with Kubernetes, which means it can be used to run containers on a cluster adn locally. Support for multiple container formats: Podman supports both OCI and Docker container formats, which means it can run containers created using either format. Support for Cgroups v2: Podman supports Cgroups v2, which is a new version of the Linux kernel’s control group (cgroup) mechanism that provides more fine-grained control over resource allocation. Network support namespaces: Podman supports network namespaces, which allows you to use different network configurations for different containers. Differences Between Podman and Docker Docker and Podman are container engines, but there are some key differences between the two. Docker and Docker hub alternatives, such as Podman, are widely used and supported in the industry, and it depends on the specific use case and requirements of which one to use. Here are some of the key differences between Docker and Podman: Daemonless: Podman does not require a daemon to run containers, whereas Docker uses a daemon to manage containers. This means that Podman can run containers directly without the need for an additional service running in the background. Rootless: Podman can run containers without needing root access, whereas Docker requires root access to manage the container daemon. This makes Podman more secure, as it limits the potential attack surface. Image storage: Podman stores images in the local file system, whereas Docker uses a centralized image registry. This means that, with Podman, it is not necessary to have an internet connection to use local images. Networking: Docker uses its own networking stack, while Podman uses the host’s networking stack. CLI: Both have similar command line interfaces, so it’s easy to switch between them. Overall, Docker and Podman are powerful tools for containerization. For these two, and any other Docker alternatives, the ultimate choice between them often comes down to personal preference and specific use case requirements. Containerd Next on the list of Docker alternatives is Containerd. Containerd is a high-level, lightweight container runtime that provides a consistent and stable interface for running containers. Designed to be used as a daemon process that runs on a host system, it manages the lifecycle of containers by starting and stopping them, as well as providing other features, such as image management and storage. Containerd is also designed to work with other container orchestration tools, such as Kubernetes, to manage the scaling and scheduling of containers in a cluster. Advantages of Containerd Lightweight: Containerd is designed to be lightweight and fast, which means it has a small footprint and uses minimal resources. This makes it well-suited for use in high-performance and resource-constrained environments. Consistency: Containerd provides a consistent and stable interface for running containers, which makes it easier to manage and orchestrate them at scale. Flexibility: Containerd can be used with a variety of container orchestration tools, such as Kubernetes and Docker Swarm, which allows for greater flexibility in terms of how containers are managed and scaled. Plugins: Containerd has a modular design and support for plugins, which allows for easy customization and extension of its functionality. Security: Containerd provides a secure and isolated environment for running containers, and it has built-in support for image signing and validation. Support: Containerd is an open-source project with a large and active community, which means it has a wide range of support and resources available. Differences Between Containerd and Docker Containerd and Docker are container runtimes, but they have some key differences. Let’s take a look at these: Design: Containerd is designed to be a lightweight and minimal container runtime, while Docker is a more fully-featured container platform that includes additional components such as a built-in container registry and a management API. Functionality: Containerd focuses on providing a stable and consistent interface for running containers, while Docker provides a more comprehensive set of features such as image management and orchestration. Deployment: Containerd is intended to be used as a daemon process that runs on a host system, while Docker is typically deployed as a standalone service. Architecture: Containerd has a modular architecture that is designed to work with other container orchestration tools, while Docker has its own built-in orchestration features. Support: Containerd is an open-source project that is backed by a large and active community, while Docker is a commercial product that is supported by the company behind it. Plugins: Containerd has a pluggable architecture, which means it can be extended or customized using plugins, while Docker does not have a similar feature. Security: Containerd provides a secure and isolated environment for running containers and has built-in support for image signing and validation, while Docker does not have this feature by default. LXD Now, we’ll discuss one of the most commonly used alternatives of Docker in the list of Docker hub alternatives. LXD (Linux Containers Daemon) is a container hypervisor for Linux. It allows multiple isolated Linux systems (containers) to run on a single host, providing a lightweight alternative to virtual machines. LXD uses Linux kernel features, such as control groups and namespaces, to provide isolation, while also providing a simple and user-friendly command-line interface for managing containers. LXD is designed to work with existing Linux distributions and tools and supports a wide range of container images and formats, including Docker. It also provides advanced features like live migration, storage management, and network management. Developed and maintained by Canonicals, LXD is one of the well-known Docker hub alternatives and is the default container hypervisor for Ubuntu 20.04 and later versions. Advantages of LXD There are several advantages to using LXD as a container hypervisor. LXD is one of the most known Docker desktop alternatives available in the industry today. Take a look at the advantages of LXD below: Lightweight and fast: LXD uses Linux kernel features, such as control groups and namespaces, which are more lightweight and efficient than traditional virtualization methods. This results in faster startup times and lower resource overhead for containers. Easy to use: LXD provides a simple and user-friendly command-line interface for managing containers, making it easy to create, start, stop, and manage containers. Compatible with existing Linux distributions and tools: LXD is designed to work with existing Linux distributions and tools, and supports a wide range of container images and formats, including Docker. Advanced features: LXD provides advanced features, such as live migration, storage management, and network management, which allows you to move running containers between hosts without interruption, manage storage resources, and network interfaces within containers. Security: LXD uses AppArmor and Seccomp to provide additional security to the containers. Networking: LXD provides easy-to-use networking features to manage the container’s network interfaces, assign IP addresses, and create virtual networks. Scalability: LXD can run thousands of containers on a single host, making it highly scalable for large-scale deployments. High availability: LXD supports clustering features with HAproxy, allowing the creation of a highly available environment with automatic failover. Differences Between LXD and Docker LXD and Docker are containerization technologies, but they have some key differences. The decision of choosing Docker desktop alternatives should be made based on the use case and business requirements. Use case: LXD is a container hypervisor, while Docker is a container runtime. This means that LXD provides an additional layer of abstraction, allowing multiple isolated Linux systems (containers) to run on a single host, while Docker focuses on running individual containers. Containerization: LXD provides a more complete, system-level containerization experience, while Docker is more focused on application-level containerization. Design: LXD is designed to work with existing Linux distributions and tools and supports a wide range of container images and formats, including Docker. Docker, on the other hand, is focused on its own container format and ecosystem. Security Integration: LXD uses AppArmor and Seccomp to provide additional security to the containers, while Docker uses namespaces and control groups to isolate containers. Networking: LXD provides easy-to-use Networking features to manage the container's network interfaces and assign IP addresses, and create virtual networks, while Docker uses virtual networks based on IP addresses and network interfaces provided by the host. Overall, while Docker and LXD are powerful containerization technologies, they are designed to solve different problems and have different use cases. Depending on the use case, these alternatives of Docker can be used. How To Choose the Best Docker Alternative When choosing a Docker alternative, it is important to consider the following factors: Compatibility: Make sure the alternative is compatible with your existing infrastructure and technologies. Features: Evaluate the features offered by the alternative and see if they align with your needs. Support: Consider the level of support offered by the alternative and its community. Performance: Consider the performance of the alternative in terms of resource usage and scalability. Security: Evaluate the security features offered by the alternative and see if they meet your requirements. Cost: Consider the cost of using the alternative and compare it to other options. Conclusion So these were some of the popular alternatives for Docker. Each of these Docker alternatives has its own strengths and weaknesses, so it's important to analyze the pros and cons of each and study your business requirements before choosing any of these alternatives of Docker.
This is the last part of the debugging series. To learn the rest, you’ll need to get the book “Practical Debugging at Scale: Cloud Native Debugging in Kubernetes and Production” or the course. One of the most frequently asked questions I receive is: can we do these things in VS Code? The answer is, unfortunately, no. But I elaborate on the debugging capabilities of VS Code in this video: “16 Missing Features in the VS Code Debugger” on YouTube. I’ll do a blog post that covers that next week. Below is the last video in the series: Transcript Welcome back to the ninth part of debugging at scale, where we really know the quality of your code. Remote debugging doesn’t always deal with a remote machine. We often need it when debugging into Kubernetes or Docker. We’ll delve more into that later, but for now, we’ll discuss the basic mechanics. How to connect, how to make it slightly less vulnerable to security attacks, and then we’ll discuss the problems of remote debugging. The Connection and Command Line We’ll start with a discussion around the connection. We first need to run the process that we’ll connect to remotely. To do that, we need to run a command similar to this one. Notice that this is a simplified version. In many cases, the argument should be embedded in configuration files. When you inspect your maven or gradle files, you might see many of the arguments listed here. This is how these things work under the hood. Let’s go over the command and break it down piece by piece to see that we understand it correctly. The first part is the launch of the Java command line. This is pretty obvious. We need quotes in bash since there’s a star at the end of the line, and bash wants to expand it. Without this quote, the command won’t work properly. Agent lib is the system that loads the native library wiring directly into the virtual machine, and JDWP is the Java Debug Wire Protocol. This is the underlying networking protocol used to communicate between the debugger and the running process. It’s a high-level protocol, that means it can be implemented on top of various transports. Typically, it’s implemented over TCP sockets, but it’s the same protocol we used to debug devices directly. You don’t need to know too much about JDWP, but the concept is simple. You send commands and can query the system. That’s what the IDE does for you. When you add a breakpoint, the IDE sends a JDWP command to add a breakpoint at the given location. When the breakpoint is hit, JDWP sends back an event to the IDE, indicating that the IDE can then query the details about the current environment, stack, variables, etc. In this case, we transfer the details via a server socket. We can use dt_shmem, which stands for shared memory, as the wire protocol. This is faster and useful for processes that have access to a shared memory area. This is actually pluggable, and you can build your own JDWP transport. This isn’t useful usually but speaks to the power and flexibility of the API. We can optionally suspend the virtual machine on launch if you want to debug something right from the start. I’ve set this to no, which means the VM will start running right away. If you set it to yes with the letter “y,” the VM will pause on launch and wait for the JDWP connection. This is the address and port we are listening on. In this case, I allow anyone to connect on port 5005. I can limit this to localhost only by changing the star character. This is probably the better approach. Although, it won’t make the protocol fully secure. This is the rest of the command, the class we’re running. Typically, you would have something more substantial here. In this case, I’m just running the PrimeMain class. To start debugging, we need to edit the run configuration in intellij. Connecting from IntelliJ/IDEA Next, we need to locate a configuration for remote debugging. Once I select that, we can add it. Notice it’s pre-configured with the defaults, such as port 5005. I give the new run configuration a name, and we’re ready to go with debugging the app. Notice there are many options to tune here, but we don’t need any of them. Also, check out this area right here. Seems familiar? That’s the exact line we discussed before. The IDE is showing us how to set up the command line for the remote process. This lets us verify that we entered everything correctly. We now have a new debug remote run configuration. We can switch to a different configuration from the same location. But when we want to do remote debugging, we need to toggle it here. Next, we need to press the debug button to run this command. We are now instantly connected to the running process. Once that is done, this feels and acts like any debugger instance launched from within the IDE. I can set a breakpoint, step over, inspect variables, etc., so why do it? In some cases, running the server locally in the IDE is impractical. A good example would be debugging a container on your own machine. That might not be trivial. Security Implications of JDWP Calling JDWP insecure is inaccurate. That would be like putting your house keys and home address wrapped in a nice gift wrapping with an itemized list of your valuables sorted by value in front of your house. This is an open door. An open door isn’t a security vulnerability. It’s an open door! JDWP is very insecure when used remotely. Locally, on your own machine, it isn’t a problem, but it has almost no security protections. There’s no solution for that. But there’s a very partial workaround of tunneling it over SSH. This is relatively trivial. Just use this command to open a tunnel between the remote machine to your local machine. For both sides, it will seem like local debugging. So the example I showed before (of connecting to a local host server) would work perfectly with this remote host as SSH will move all the packets back and forth securely. We can’t SSH into a Kubernetes container, but we can port forward, which is almost identical. We can do something similar to this command to forward the port from the given pod to the local machine and vice versa. Same idea as the SSH tunneling but appropriate to the Kubernetes world. Dangers of Remote Debugging In this final section, I want to talk about the dangers of remote debugging in production. Breakpoints break, seems obvious. That’s what they’re here to do. But if we run on a server, we block it completely by mistake. We can use trace points. As I said, they’re great. But they are no replacement to breakpoints, and an accidental click in the gutter can literally stop your server in its tracks. JDWP effectively allows remote code execution. Lets you access all the bytecode of the app, which is effectively the same as giving access to your full server source code. It lets attackers do almost anything since it wasn’t designed with security in mind. We need to relaunch the application with debugging enabled. That means killing the running process and starting it over again. Disconnecting existing users, etc. That isn’t great. Some operations in the debugger require more than one step in terms of the protocol. As a result, you could send a request to the debugger, lose your connection, and the debugger could be stuck in a problematic state. This is an inherent limitation of the JDWP protocol and can’t be worked around in a standard debugger. The problem is that even unintentional actions can demolish a server. A simple conditional breakpoint that invokes a method as part of the condition can demolish server performance and crash it. JDWP effectively allows remote code execution. Lets you access all the bytecode of the app, which is effectively the same as giving access to your full source code. It lets attackers do almost anything since it wasn’t designed with security in mind. Imagine placing a breakpoint where the user password is passed to authentication. If JDWP is open for your server, a member of your team might use that, and you will never know. There’s no tracking at all! 60% of security hacks happen from within the organization. If your company does remote debugging, they have no way of knowing whether an employee used that to manipulate the application state or siphon user details. There’s no tracking or anything. This can be in violation of various rules and regulations since it might expose personal user data. Remote debugging into production can trigger liability risks. I discuss some of the solutions for those problems both in the low-level tooling and in higher-level observability solutions. This is covered in the book and in the full course. Final Word With this, we finished the first part of the course. If you want to check out the full course; go to “debugagent.com” to learn more. The next video covers the strategies for debugging and the science of debugging. If you have any questions, please use the comments section below. Thank you!
Liquibase is an open-source database-independent library for tracking, managing, and applying database changes. It allows developers to version control their database changes and easily roll them out in a controlled and predictable manner. Kubernetes, on the other hand, is an open-source container orchestration system that automates the deployment, scaling, and management of containerized applications. When deploying a containerized application on Kubernetes, it is common to use an init container to perform any necessary setup tasks before the main application container starts. This can include tasks such as installing dependencies, configuring environment variables, or running database migrations. In this article, we will show you how to set up an init container in Kubernetes to run Liquibase migrations on your database. Liquibase Setup To use Liquibase in an init container, we first need to create a Docker image that contains the Liquibase tool and any necessary dependencies, such as a JDBC driver for the database. The following Dockerfile can be used to create an image that includes Liquibase and the MySQL JDBC driver: Dockerfile FROM openjdk:8-jdk-alpine RUN apk add --update bash curl ENV LIQUIBASE_VERSION=4.1.1 RUN curl -L https://github.com/liquibase/liquibase/releases/download/v${LIQUIBASE_VERSION}/liquibase-${LIQUIBASE_VERSION}.tar.gz \ | tar -xz -C /opt \ && ln -s /opt/liquibase-${LIQUIBASE_VERSION}/liquibase /usr/local/bin/liquibase RUN curl -L https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.22/mysql-connector-java-8.0.22.jar \ -o /opt/mysql-connector-java-8.0.22.jar Before we begin, make sure that you have the following prerequisites: A Kubernetes cluster set up and running A database running in a separate container or on a separate host A Liquibase project set up for your application Here are the steps you need to follow: Create a new Kubernetes deployment for your application. In the deployment definition, specify the init container and the main application container. YAML apiVersion: apps/v1 kind: Deployment metadata: name: my-app spec: selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: initContainers: - name: liquibase image: liquibase/liquibase:latest env: - name: LIQUIBASE_URL value: jdbc:postgresql://my-database:5432/my-database - name: LIQUIBASE_USERNAME value: my-user - name: LIQUIBASE_PASSWORD value: my-password command: ["liquibase", "update"] volumeMounts: - name: liquibase-config mountPath: /liquibase/ - name: my-app image: my-app:latest ports: - containerPort: 8080 env: - name: DATABASE_URL value: jdbc:postgresql://my-database:5432/my-database - name: DATABASE_USERNAME value: my-user - name: DATABASE_PASSWORD value: my-password volumes: - name: liquibase-config configMap: name: liquibase-config Create a configMap to store the Liquibase configuration files. YAML apiVersion: v1 kind: ConfigMap metadata: name: liquibase-config data: liquibase.properties: | changeLogFile: /liquibase/changelog.xml driver: org.postgresql.Driver classpath: /liquibase/postgresql-42.2.18.jar Run the deployment on your Kubernetes cluster. Shell kubectl apply -f my-app-deployment.yaml Validation In Liquibase, you can use Liquibase changelog table to validate whether your DB migrations are successful. In Liquibase, a changelog is a collection of changesets that describes the changes that need to be made to the database. Each changeset is a single, atomic change to the database, such as adding a new table, modifying an existing column, or running a SQL script. When Liquibase runs, it keeps track of which changesets have been executed in the database by storing information in a special table called the DATABASECHANGELOG table. This table contains a record for each changeset that has been executed, including the change's unique ID, author, and execution date. By default, the DATABASECHANGELOG table is created in the same schema as the rest of the database, but its name and schema can be customized. Here is an example of the DATABASECHANGELOG table structure: SQL ID | VARCHAR(255) | NOT NULL AUTHOR | VARCHAR(255) | NOT NULL FILENAME | VARCHAR(255) | NOT NULL DATEEXECUTED | TIMESTAMP | NOT NULL ORDEREXECUTED | INT | NOT NULL EXECTYPE | VARCHAR(10) | NOT NULL MD5SUM | VARCHAR(35) | DESCRIPTION | VARCHAR(255) | COMMENTS | VARCHAR(255) | TAG | VARCHAR(255) | LIQUIBASE | VARCHAR(20) | You can query the DATABASECHANGELOG table to see which changesets have been executed, and in what order. Additionally, you can also use the Liquibase command-line tool to generate reports on the current state of the database, including a list of all executed changesets and any pending changes that have not yet been applied. Liquibase Rollback In Liquibase, it is possible to revert a specific changeset that has already been applied to the database. This can be useful in cases where a mistake was made in a previous change or if you want to undo a change in a development or testing environment. To revert a changeset in Liquibase, you can use the "rollback" command and specify the ID of the changeset that you want to revert. The rollback command will undo the changes made by the specified changeset and update the DATABASECHANGELOG table to reflect the change. Here is an example of how to revert a changeset with ID "12345" using the Liquibase command-line tool: Shell liquibase rollback 12345 In Kubernetes, to revert a changeset, you will have to create a new deployment with the rollback command and apply it to the cluster. Here is an example of how to revert a changeset with ID "12345" in Kubernetes: YAML apiVersion: apps/v1 kind: Deployment metadata: name: my-app-rollback spec: selector: matchLabels: app: my-app-rollback template: metadata: labels: app: my-app-rollback spec: initContainers: - name: liquibase-rollback image: liquibase/liquibase:latest env: - name: LIQUIBASE_URL value: jdbc:postgresql://my-database:5432/my-database - name: LIQUIBASE_USERNAME value: my-user - name: LIQUIBASE_PASSWORD value: my-password command: ["liquibase", "rollback", "12345"] volumeMounts: - name: liquibase-config mountPath: /liquibase/ volumes: - name: liquibase-config configMap: name: liquibase-config It's worth noting that depending on the changes made by the changeset, reverting it might not be straightforward and can have an impact on other parts of the database, for example, if the changeset creates a table, reverting it will drop the table and all the data inside it. it's important to test the rollback in a development environment before applying it to production. In conclusion, using an init container in Kubernetes to run Liquibase database migrations is a convenient and efficient way to manage and version database changes. It allows developers to track and roll back changes easily and ensures that the database is in the correct state before the main application starts.
In the first part of this article, we have seen how to set up a simple scenario with a Spring Cloud Config server and the client counterpart. The server was set with a native profile, and the configuration was stored in the classpath, in a subfolder named config. The demo was made of a single instance of a server and client. In part two, we will show how to configure Spring Cloud Config to connect and use an external Git repository. Then, in the next sections, we will talk about refreshing the configuration data without restarting the services. Microservice Configuration by Git Repository: Server Side Spring Cloud Config uses a Git repository by default. In the first part of this article we saw to switch on a repository based on filesystem/classpath, setting the property spring.profiles.active to the native value. To use a Git repository, we have to set the following instead: YAML spring: cloud: config: server: git: uri: https://github.com/mcasari/spring-cloud-config-git-server-repo.git username: ${github.username} password: ${github.password} cloneOnStart: true We have set a URI to a sample public repository in the example above. Even if not required in this case, we have also put a username and password to show how we would configure access to a protected private repository. For a private repository, we can pass the credentials of a user who has access to that repository and as a password, we can set a security token expressly generated using the GitHub administration panel. In the example, we have used placeholders for both username and password values, which can be provided as Java arguments in the startup command: java -jar -Dgithub.username=<myusername> -Dgithub.password=<mygithubtoken> spring-cloud-config-git-server-1.0-SNAPSHOT.jar In the case of a public repository, the username and password will be simply ignored. The cloneOnStart property allows us to configure when the server is supposed to clone the repository. By default, the server does this when the configuration data is requested for the first time. If we set it to true, it will be triggered in the startup phase instead. Microservice Configuration by Git Repository: Client Side From the standpoint of the client side, nothing changes compared to the discussion in the first part of this article. We can use the following piece of configuration in the application.yml file if we want to contact the server using the default host and port (localhost:8888): YAML config: import: "optional:configserver:" or, if we have to set a more specific address: config: import: "optional:configserver:http://configserverhost:8888" And if the server is protected by a username and password, the following is also needed: cloud: config: username: myusername password: mypassword Running Server and Client We can run the server and client in the same way we did for the native profile scenario: java -jar spring-cloud-config-git-server-1.0-SNAPSHOT.jar ... java -jar spring-cloud-config-git-client-1.0-SNAPSHOT.jar Microservice Configuration by Git Repository: Reloading Configuration Let’s turn back a while to the configuration described in the previous sections. If we clone the Git repository locally, make some changes to the properties, commit, push them, and check the config server by its endpoints, we will see the updated values. But, if we call the client REST services, we will still see the old values. This is because the client needs its Spring context to be reloaded in some way, to refresh the bean properties. Reloading Spring Beans Manually Spring Cloud provides a specific actuator “refresh” endpoint, that we can call by HTTP POST request. This endpoint has the effect of reloading the beans marked with the @RefreshScope annotation. To build a simplified demo, we can avoid introducing a security layer in the client application. We can achieve this by simply not setting a security starter in the Maven POM. This way we will be able to call the refresh endpoint freely. Let’s suppose we have a bean defined like this: Java @Component @RefreshScope @ConfigurationProperties(prefix = "myproperties") public class DemoClient { private List<String> properties = new ArrayList<String>(); public List<String> getProperties() { return properties; } @Value("${myproperty}") private String myproperty; public String getMyproperty() { return myproperty; } } And a controller class with two REST services: Java @RestController public class ConfigClientController { private static final Logger LOG = LoggerFactory.getLogger(ConfigClientController.class); @Autowired private DemoClient demoClient; @GetMapping("/getProperties") public List<String> getProperties() { LOG.info("Properties: " + demoClient.getProperties().toString()); return demoClient.getProperties(); } @GetMapping("/getProperty") public String getProperty() { LOG.info("Property: " + demoClient.getMyproperty().toString()); return demoClient.getMyproperty(); } } Then, let’s make some changes to the properties and myProperty fields, commit and push them, and finally call the refresh endpoint by an HTTP POST request: curl -X POST http://localhost:8081/actuator/refresh If we then call the two client REST services, we will see the updated values. Reloading Spring Beans Automatically Clearly, the option of manually reloading the configuration properties is not the best solution. We would prefer a scenario where after a Git push operation the system is automatically updated to the new configuration state, without any restart and the need to manually execute a refresh on specific beans. The picture below describes an available architecture implementing such a feature. In the above diagram, we can see a remote Git repository, that can send notifications to the config server as some commit is pushed on the main branch. The config server then dispatches those notifications to a message broker, and the clients can connect to the message broker to consume them. For simplicity, we can limit ourselves to considering just one client. To make the first part of the above architecture work, we have to make some configurations. GitHub, like other Version Control Software tools, provides a feature named Webhook. A Webhook is a definition of an endpoint that GitHub can invoke passing the information involved in a push to the main Git branch. A /monitor endpoint is available in Spring Cloud Config and can be activated by the spring-cloud-config-monitor dependency: XML <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-monitor</artifactId> </dependency> and adding a piece of configuration in the application.yml file: YAML spring: cloud: config: server: monitor: github: enabled: true We can configure the /monitor endpoint as a Webhook in the GitHub repository so that GitHub can call it to notify any changes to the config server. Once the above pieces are put in place, the next piece for the architecture is a message broker that acts as a funnel for events coming from the config server and originating from a Webhook notification and as a source for the clients that are meant to consume those notifications (as events of type RefreshRemoteApplicationEvent). We can choose between Kafka and RabbitMQ as message brokers. In this article, we will use RabbitMQ. To run a demo of the above architecture in a local environment, the easiest way would be to run a Docker image of RabbitMQ: docker run -d --name rabbit -p 5672:5672 -p 15672:15672 rabbitmq:management Both the client and server need the following dependency to be able to connect to the RabbitMQ message broker: XML <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency> And the following configuration in the application.yml file: YAML rabbitmq: host: localhost port: 5672 username: guest password: guest Once we run all the pieces, the config server and the client will be bound to the message broker by a RabbitMQ exchange named SpringCloudBus, as we can see in the following screenshot. Testing the above architecture would be problematic in a local environment: we can connect our config server to the remote Git repository but not vice-versa. To overcome this situation, we can simulate the Webhook feature by making a dummy HTTP POST pretending it is a real Git push notification. We can use any tool we want to make the POST call, here we use a curl command: curl -H "X-Github-Event: push" -H "Content-Type: application/json" -X POST -d '{"commits": [{"modified": ["client-service.yaml"]}]}' http://localhost:8888/monitor In this call, we pass the type of event as a specific HTTP header, and in the JSON body of the POST request, we specify what file has been modified. Note: In a Windows system, we will have problems with the single quote character, so we have to replace them with double quotes and escape those inside the JSON content, like this: curl -H "X-Github-Event: push" -H "Content-Type: application/json" -X POST -d "{\"commits\": [{\"modified\": [\"client-service.yaml\"]}]}" http://localhost:8888/monitor Quick Recap To summarize, to run a full example test we can do the following steps: Run the RabbitMQ instance by Docker. Run the config server and client applications by using their executable jars with the java-jar command. Clone the remote repository, make some changes to the configuration properties, commit, and push them. Simulate a Webhook interaction, making an HTTP POST call to the config server/monitor endpoint by curl or any other tool. Call the client REST services exposing the configuration properties: if all works as expected, we should see the updated values printed on the screen. Conclusion We have seen how to serve the microservice system configuration out of a Git repository. This is the default and the most common way. We have also seen how to refresh the configuration properties without the need to restart the application. You can find the server and side parts used as samples in this article on the following GitHub projects: Spring Cloud Config Server Spring Cloud Client The repository is also available on GitHub, as already mentioned in this post: Spring Cloud Config Git repository
Most engineers don’t want to spend more time than necessary to keep their clusters highly available, secure, and cost-efficient. How do you make sure your Google Kubernetes engine cluster is ready for the storms ahead? Here are fourteen optimization tactics divided into three core areas of your cluster. Use them to build a resource-efficient, highly-available GKE cluster with airtight security. Here are the three core sections in this article: Resource Management Security Networking Resource Management Tips for a GKE Cluster 1. Autoscaling Use the autoscaling capabilities of Kubernetes to make sure your workloads perform well during peak load and control costs in times of normal or low loads. Kubernetes gives you several autoscaling mechanisms. Here’s a quick overview to get you up to speed: Horizontal pod autoscaler: HPA adds or removes pod replicas automatically based on utilization metrics. It works great for scaling stateless and stateful applications. Use it with Cluster Autoscaler to shrink the number of active nodes when the pod number decreases. HPA also comes in handy for handling workloads with short high utilization spikes. Vertical pod autoscaler: VPA increases and lowers the CPU and memory resource requests of pod containers to make sure the allocated and actual cluster usage match. If your HPA configuration doesn’t use CPU or memory to identify scaling targets, it’s best to use it with VPA. Cluster autoscaler: it dynamically scales the number of nodes to match the current GKE cluster utilization. Works great with workloads designed to meet dynamically changing demand. Best Practices for Autoscaling in a GKE Cluster Use HPA, VPA and Node Auto Provisioning (NAP): By using HPA, VPA and NAP together, you let GKE efficiently scale your cluster horizontally (pods) and vertically (nodes). VPA sets values for CPU, memory requests, and limits for containers, while NAP manages node pools and eliminates the default limitation of starting new nodes only from the set of user-created node pools. Check if your HPA and VPA policies clash: Make sure the VPA and HPA policies don’t interfere with each other. For example, if HPA only relies on CPU and memory metrics, HPA and VPA cannot work together. Also, review your bin packing density settings when designing a new GKE cluster for a business-or purpose-class tier of service. Use instance weighted scores: This allows you to determine how much of your chosen resource pool will be dedicated to a specific workload and ensure that your machine is best suited for the job. Slash costs with a mixed-instance strategy: Using mixed instances helps achieve high availability and performance at a reasonable cost. It’s basically about choosing from various instance types, some of which may be cheaper and good enough for lower-throughput or low-latency workloads. Or you could run a smaller number of machines with higher specs.This way it would bring costs down because each node requires Kubernetes to be installed on it, which always adds a little overhead. 2. Choose the Topology for Your GKE Cluster You can choose from two types of clusters: Regional topology: In a regional Kubernetes cluster, Google replicates the control plane and nodes across multiple zones in a single region. Zonal topology: In a zonal cluster, they both run in a single compute zone specified upon cluster creation. If your application depends on the availability of a cluster API, pick a regional cluster topology, which offers higher availability for the cluster’s control plane API. Since it’s the control plane that does jobs like scaling, replacing, and scheduling pods, if it becomes unavailable, you’re in for reliability trouble. On the other hand, regional clusters have nodes spreaded across multiple zones, which may increase your cross-zone network traffic and, thus, costs. 3. Bin Pack Nodes for Maximum Utilization This is a smart approach to GKE cost optimization shared by the engineering team at Delivery Hero. To maximize node utilization, it’s best to add pods to nodes in a compacted way. This opens the door to reducing costs without any impact on performance. This strategy is called bin packing and goes against the Kubernetes that favors even distribution of pods across nodes. Source: Delivery Hero The team at Delivery Hero used GKE Autopilot, but its limitations made the engineers build bin packing on their own. To achieve the highest node utilization, the team defines one or more node pools in a way that allows nodes to include pods in the most compacted way (but leaving some buffer for the shared CPU). By merging node pools and performing bin packing, pods fit into nodes more efficiently, helping Delivery Hero to decrease the total number of nodes by ~60% in that team. 4. Implement Cost Monitoring Cost monitoring is a big part of resource management because it lets you keep an eye on your expenses and instantly act on cost spike alerts. To understand your Google Kubernetes Engine costs better, implement a monitoring solution that gathers data about your cluster’s workload, total cost, costs divided by labels or namespaces, and overall performance. The GKE usage metering enables you to monitor resource usage, map workloads, and estimate resource consumption. Enable it to quickly identify the most resource-intensive workloads or spikes in resource consumption. This step is the bare minimum you can do for cost monitoring. Tracking these 3 metrics is what really makes a difference in how you manage your cloud resources: daily cloud spend, cost per provisioned and requested CPU, and historical cost allocation. 5. Use Spot VMs Spot VMs are an incredible cost-saving opportunity—you can get a discount reaching up to 91% off the pay-as-you-go pricing. The catch is that Google may reclaim the machine at any time, so you need to have a strategy in place to handle the interruption. That’s why many teams use spot VMs for workloads that are fault-and interruption-tolerant like batch processing jobs, distributed databases, CI/CD operations, or microservices. Best Practices for Running Your GKE Cluster on Spot VMs How to choose the right spot VM? Pick a slightly less popular spot VM type—it’s less likely to get interrupted. You can also check its frequency of interruption (the rate at which this instance reclaimed capacity within the trailing month). Set up spot VM groups: This increases your chances of snatching the machines you want. Managed instance groups can request multiple machine types at the same time, adding new spot VMs when extra resources become available. Security Best Practices for GKE Clusters Red Hat 2022 State of Kubernetes and Container Security found that almost 70% of incidents happen due to misconfigurations. GKE secures your Kubernetes cluster in many layers, including the container image, its runtime, the cluster network, and access to the cluster API server. Google generally recommends implementing a layered approach to GKE cluster security. The most important security aspects to focus on are: Authentication and authorization Control plane Node Network security 1. Follow CIS Benchmarks All of the key security areas are part of Center of Internet Security (CIS) Benchmarks, a globally recognized best practices collection that gives you a helping hand for structuring security efforts. When you use a managed service like GKE, you don’t have the power over all the CIS Benchmark items. But some things are definitely within your control, like auditing, upgrading, and securing the cluster nodes and workloads. You can either go through the CIS Benchmarks manually or use a tool that does the benchmarking job for you. We recently introduced a container security module that scans your GKE cluster to check for any benchmark discrepancies and prioritizes issues to help you take action. 2. Implement RBAC Role-Based Access Control (RBAC) is an essential component for managing access to your GKE cluster. It lets you establish more granular access to Kubernetes resources at cluster and namespace levels, and develop detailed permission policies. CIS GKE Benchmark 6.8.4 emphasizes that teams give preference to RBAC over the legacy Attribute-Based Access Control (ABAC). Another CIS GKE Benchmark (6.8.3) suggests using groups for managing users. This is how you make controlling identities and permissions simpler and don’t need to update the RBAC configuration whenever you add or remove users from the group. 3. Follow the Principle of Least Privilege Make sure to grant user accounts only the privileges that are essential for them to do their jobs. Nothing more than that. The CIS GKE Benchmark 6.2.1 states: Prefer not running GKE clusters using the Compute Engine default service account. By default, nodes get access to the Compute Engine service account. This comes in handy for multiple applications but opens the door to more permissions than necessary to run your GKE cluster. Create and use a minimally privileged service account instead of the default—and follow the same principle everywhere else. 4. Boost Your Control Plane’s Security Google implements the Shared Responsibility Model to manage the GKE control plane components. Still, you’re the one responsible for securing nodes, containers, and pods. The Kubernetes API server uses a public IP address by default. You can secure it with the help of authorized networks and private Kubernetes clusters that let you assign a private IP address. Another way to improve your control plane’s security is performing a regular credential rotation. The TLS certificates and cluster certificate authority get rotated automatically when you initiate the process. 5. Protect Node Metadata CIS GKE Benchmarks 6.4.1 and 6.4.2 point out two critical factors that may compromise your node security—and fall on your plate. Kubernetes deprecated the v0.1 and v1beta1 Compute Engine metadata server endpoints in 2020. The reason was that they didn’t enforce metadata query headers. Some attacks against Kubernetes clusters rely on access to the metadata server of virtual machines. The idea here is to extract credentials. You can fight such attacks with workload identity or metadata concealment. 6. Upgrade GKE Regularly Kubernetes often releases new security features and patches, so keeping your deployment up-to-date is a simple but powerful approach to improving your security posture. The good news about GKE is that it patches and upgrades the control plane automatically. The node auto-upgrade also upgrades cluster nodes and CIS GKE Benchmark 6.5.3 recommends that you keep this setting on. If you want to disable the auto-upgrade for any reason, Google suggests performing upgrades on a monthly basis and following the GKE security bulletins for critical patches. Networking Optimization Tips for Your GKE Cluster 1. Avoid Overlaps With IP Addresses From Other Environments When designing a larger Kubernetes cluster, keep in mind to avoid overlaps with IP addresses used in your other environments. Such overlaps might cause issues with routing if you need to connect cluster VPC network to on-premises environments or other cloud service provider networks via Cloud VPN or Cloud Interconnect. 2. Use GKE Dataplane V2 and Network Policies If you want to control traffic flow at the OSI layer 3 or 4 (IP address or port level), you should consider using network policies. Network policies allow specifying how a pod can communicate with other network entities (pods, services, certain subnets, etc.). To bring your cluster networking to the next level, GKE Dataplane V2 is the right choice. It’s based on eBPF and provides extended integrated network security and visibility experience. Adding to that, if the cluster uses the Google Kubernetes Engine Dataplane V2, you don’t need to enable network policies explicitly as the former manages services routing, network policy enforcement, and logging. 3. Use Cloud DNS for GKE Pod and Service DNS resolution can be done without the additional overhead of managing the cluster-hosted DNS provider. Cloud DNS for GKE requires no additional monitoring, scaling, or other management activities as it’s a fully hosted Google service. Conclusion In this article, you have learned how to optimize your GKE cluster with fourteen tactics across security, resource management, and networking for high availability and optimal cost. Hopefully, you have taken away some helpful information that will help you in your career as a developer.
The concept of distributed applications is certainly not new. Whoever has a long IT career certainly remembers a number of different technologies implementing distributed components even in the early years. Nowadays, is all about microservices. They are a new form by which we consider today the concept of distributed computing. Their peculiarity is that their communications are based essentially on REST and messaging protocols, which have the advantage of being widely spread standards. The core concept is essentially the same, having pieces of the whole system completely independent one from the other and running each in its own process. The microservices world, coupled with the advent of cloud platforms has paved the way for a thriving of related technologies. This new architectural style has its drawbacks and a number of specific patterns are required to overcome them. Spring Cloud is a Spring project based on Spring Boot and contains specific packages that cover such patterns, both with its own solutions and integrating third-party ones as well (like Netflix OSS tools). In this article, we will show a list of the main microservices patterns and a brief overview of how Spring Cloud copes with them. The present post is meant to be a quick introduction to the framework and the first of a series of articles aimed at covering the most important features and modules of Spring Cloud. In the next article, we will cover the basics of remote configuration, which is a fundamental piece of the Spring Cloud microservice ecosystem. Monolithic Applications We can describe a monolithic application as a self-contained system, the goal is to solve a range of functionalities in a single processing unit. The following picture shows what a monolithic application could look like. The main concept is that all the application is decomposed into layers specialized in some general design logic, like business logic and database layers but all those layers run typically in the same process and communicate with each other by internal method calls (internal to the Java Virtual Machine in which they run). Microservice Applications Microservice applications have a more complex structure. We can think of a microservice system as an evolution of a monolithic one where its main features are separated into independent applications, each running in its own process, and possibly internally decomposed into layers like in the monolithic schema depicted above. The following picture shows a rough example of what a microservice system could look like. It’s an oversimplified schema, but it serves the purpose of having a general understanding. In the picture, we have a gateway, which represents the entrance of the external world into the system, and some microservices, each in a separate node (hardware or virtual machine). In the picture above, each microservice uses its own database instance running in a separate node as well. In reality, the way we deploy the single services does not follow rigid rules, we could have a single shared node to host the database or even a single node to host the three services each running in a separate process (we don’t talk here about containers just to keep things simple and generic). Besides the specific deploy schema, the main difference compared to the monolithic scenario is that we have a number of features running in their own process and these are connected typically with REST or messaging protocols, that is to say, they communicate by remote calls and they are namely “distributed” components. This “distributed” nature allows us to develop each piece of the system independently. This way we can enhance the reuse logic: it is simpler to devise clean and robust designs in such specialized components and the single pieces can be developed by completely independent teams. Unfortunately, this also comes with a number of drawbacks. How do we coordinate these distributed components? How do we deal with configuration in a centralized and consistent way? To exploit this new software paradigm, we need technologies to cope with its main drawbacks. The advent of Cloud technologies has offered an enhanced and effective way of treating these concerns. This does not mean that microservices are a solution for each and every problem. Sometimes a monolithic solution would be the more natural choice. We can say that microservices could be an excellent choice for large and complex systems, but lose some of their appeal for simpler ones. Spring Cloud, Microservices, and Netflix OSS Spring Cloud sits on the Spring Boot framework. It offers a number of solutions on its own and integrates with external tools to cope with the main microservices architectural concerns. Netflix OSS is a series of software solutions that cover the main microservices patterns. Spring Cloud packages offer a layer of integration towards those solutions: it will be enough to use the related Spring Boot starter dependencies and some specific configurations. Setting Dependencies as Release Trains To simplify dependency management the concept of release train has been introduced. Spring Cloud is a collection of modules, each specialized on some specific feature, and each of them is developed independently. A release train identifies a set of modules releases that are verified as fully compatible with each other. Once we have chosen the Spring Boot version to use, we have to pick a Spring Cloud release train that is compatible with that version, and set it in a Maven dependency management section: XML <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2021.0.5</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> Then we can set the specific module’s dependencies by Spring Cloud starters in the “dependencies section.” The dependency management section serves the only purpose of specifying the whole set of modules and related versions that we want to use. This way, we don’t have to specify any version for the individual module in the dependency section. This Maven feature is called BOM (Bill of Materials). Following the above dependency management settings, if we want our application to use the features of the Spring Cloud Config module, we can simply add a piece of configuration like this: XML <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> </dependencies> To avoid searching for a compatibility matrix in the Spring documentation in setting up an application from scratch, a practical way would be to use the start.spring.io site. There you can first select the Spring Boot version and then the wanted Spring Cloud modules. Spring Cloud and Microservice Patterns We list here the main patterns involved in microservice architecture and what the Spring Cloud package offers for them: Distributed/versioned configuration Service registration and discovery Routing Service-to-service calls Load balancing Circuit breakers Distributed messaging Distributed Configuration An important concern with microservice architectures is how to deal with configuration. Since we have a number of services each running in its own process, we could simply think that each service is responsible for its own configuration. From the standpoint of system administration, this would be a nightmare. Spring Cloud provides its own solution to provide a centralized configuration feature and overcome this problem, named Spring Cloud Config. Spring Cloud Config uses Git as the first choice for a backend storing facility (an alternative would be Vault from Hashicorp). Two alternatives of Spring Cloud Config are Consul by Hashicorp and Apache ZooKeeper (both with features not strictly limited to distributed configuration). Service Registration and Discovery One of the main characteristics of microservices architecture is service registration and discovery. Each service registers itself to a central server (possibly distributed on more than one node for high availability) and uses that central server to find other services to communicate with. Spring Cloud provides integration with the Netflix OSS tool Eureka . Eureka has both a client and a server package. The client is provided by spring-cloud-starter-eureka and the server by a spring-cloud-starter-eureka-server dependency. A service that implements the client side will use the server to register itself and at the same time find other already registered services. Distributed Logging and Tracing Logging and tracing in microservices applications are not trivial tasks. We need to collect the logging activity in a centralized way and at the same time offer some advanced way of tracing service interactions across the whole system. Spring Cloud Sleuth library is an available choice to solve these problems. Sleuth’s more important feature is to associate all the secondary requests across microservices to the single input request that triggered them. So it collects all the logging involved by some identification information and at the same time provides a complete picture of the interactions involved in each request, including timing information. Such information can then be exported to another tool named Zipkin, which is specialized in analyzing latency problems in a microservices architecture. Routing To establish communication between the external world and our microservices system we need to route the incoming request to the right services. A Netflix OSS solution is Zuul, which can be used inside a Spring application through the spring-cloud-starter-zuul starter dependency and related configuration. Zuul can play the role of an API gateway to the system and also work as a server-side load balancer. Service-To-Service calls The main form of communication between services is the REST protocol. Spring offers RestTemplate as a synchronous client to perform REST calls (a recent asynchronous alternative is WebClient). Spring Cloud also supports Netflix Feign as a REST-based client by the spring-cloud-starter-feign starter dependency. Feign uses specific annotations that allow defining interfaces that will be implemented by the framework itself. Load Balancing Load balancing is another important feature that microservices systems must implement. Different rules could be used, like a simple round robin, skipping servers that are overloaded, or using an algorithm based on the average response time. Spring Cloud can support load balancing by integrating Ribbon, a library from Netflix OSS. Circuit Breakers A common scenario in a microservices system is the possibility that some service failure would affect the other services and the whole system. Circuit Breaker is a pattern that tries to solve this problem by defining a failure threshold after which the service interrupts immediately its execution and returns some form of predefined result. Hystrix is a library of Netflix OSS that implements this pattern. To include Hystrix in a Spring Cloud project the spring-cloud-starter-hystrix Spring Boot starter must be used. Distributed Messaging Besides the classic REST-style communications between services, we also have the option of using messaging architectural patterns. We can base our whole architecture, or just a part of it, on publish/subscribe or event-driven point-to-point messaging. Spring Cloud Stream package allows us to implement message-driven microservices and integrate the more popular message brokers like RabbitMQ and Apache Kafka. Conclusion Microservices architectures require a number of patterns to overcome their inherent complexity. Spring Cloud provides solutions to these patterns with its own implementations and integration with third-party tools as well. In this article, we have made a quick overview of the main modules of Spring Cloud. In the next article, we cover the basics of the Spring Cloud Config module. We have also covered some important features of Spring Boot, which is the base platform of Spring Cloud, in the following articles: Spring Boot for Cloud: Configuration and Dependencies Spring Boot for Cloud: REST API Development Spring Boot for Cloud: Actuator
John Vester
Lead Software Engineer,
Marqeta @JohnJVester
Marija Naumovska
Product Manager,
Microtica
Vishnu Vasudevan
Head of Product Engineering & Management,
Opsera
Seun Matt
Engineering Manager,
Cellulant