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.
Git is one of the most popular version control systems used by developers worldwide. As a software developer, you must be well-versed in Git and its commands to manage code efficiently, collaborate with other team members, and keep track of changes. While there are many Git commands available, not all are equally important. In this article, I’ll cover the top Git commands that every senior-level developer should know. “A Git pull a day keeps the conflicts away” Git Init The git init command initializes a new Git repository. This command is used to start tracking changes in your project. As soon as you run this command, Git creates a new .git directory, which contains all the necessary files to start using Git for version control. Once initialized, Git tracks all changes made to your code and creates a history of your commits. Shell $ git init Initialized empty Git repository in /path/to/repository/.git/ Git Clone The git clone command creates a copy of a remote Git repository on your local machine. This is a great way to start working on a new project or to collaborate with others on existing projects. When you run this command, Git downloads the entire repository, including all branches and history, to your local machine. Shell $ git clone https://github.com/username/repository.git Git Add The git add command adds new or modified files to the staging area, which prepares them to be committed. The staging area is a temporary storage area where you can prepare your changes before committing them. You can specify individual files or directories with this command. Git tracks changes in three stages — modified, staged, and committed. The add command moves changes from the modified stage to the staged stage. To add all changes in the current directory to the staging area, run: Shell $ git add file.txt Shell git add . Git Commit The git commit command creates a new commit with the changes you've added to the staging area. A commit is a snapshot of your repository at a specific point in time, and each commit has a unique identifier. The git commit command records changes to the repository. A commit includes a commit message that describes the changes made. To commit changes to the repository, run the following command: Shell $ git commit -m "Added new feature" Git Status The git status command shows you the current state of your repository, including any changes that have been made and which files are currently staged for commit. It tells you which files are modified, which files are staged, and which files are untracked. Shell $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: file.txt Git Log The git log command shows you the history of all the commits that have been made to your repository. You can use this command to see who made changes to the repository and when those changes were made along with their author, date, and commit message. Shell $ git log commit 5d5b5e5dce7d1e09da978c8706fb3566796e2f22 Author: John Doe <john.doe@example.com> Date: Tue Mar 23 14:39:51 2023 -0400 Added new feature git log --graph: Displays the commit history in a graph format. git log --oneline: Shows the commit history in a condensed format. git log --follow: Follows the history of a file beyond renames. Git Diff The git diff command shows you the differences between the current version of a file and the previous version. This command is useful when you want to see what changes were made to a file. When you run this command, Git shows you the changes that were made between two commits or between a commit and your current working directory. This will show you the differences between the current branch and the “feature_branch” branch. Shell $ git diff feature_branch Git Branch The git branch command shows you a list of all the branches in the Git repository. You can use this command to see which branch you are currently on and to create new branches. This command is used to create, list, or delete branches. Shell $ git branch feature-1 Git Checkout The git checkout command is used to switch between branches. You can use this command to switch to a different branch or to create a new branch. Shell $ git checkout feature-1 Git Merge The git merge command is used to merge changes from one branch into another. This command is useful when you want to combine changes from different branches. When you run this command, Git combines the changes from two branches and creates a new commit. Shell $ git merge feature-1 Git Pull The git pull command is used to update your local repository with changes from a remote repository. This command is used to download changes from a remote repository and merge them into your current branch. When you run this command, Git combines the changes from the remote repository with your local changes and creates a new commit. Shell $ git pull origin master Git Push The git push command is used to push your changes to a remote repository. This command is useful when you want to share your changes with others. When you run this command, Git sends your commits to the remote repository and updates the remote branch. Shell $ git push origin master Git Remote This command is used to manage the remote repositories that your Git repository is connected to. It allows you to add, rename, or remove remote repositories. git remote rm: Removes a remote repository. git remote show: Shows information about a specific remote repository. git remote rename: Renames a remote repository. Git Fetch This command is used to download changes from a remote repository to your local repository. When you run this command, Git updates your local repository with the latest changes from the remote repository but does not merge them into your current branch. Shell $ git fetch origin Git Reset This command is used to unstaged changes in the staging area or undo commits. When you run this command, Git removes the changes from the staging area or rewinds the repository to a previous commit. Shell $ git reset file.txt Git Stash This command is used to temporarily save changes that are not yet ready to be committed. When you run this command, Git saves your changes in a temporary storage area and restores the repository to its previous state. Shell $ git stash save "Work in progress" “Why did the Git user become a magician? Because they liked to git stash and make their code disappear” Git Cherry-Pick This command is used to apply a specific commit to a different branch. When you run this command, Git copies the changes from the specified commit and applies them to your current branch. Shell $ git cherry-pick 5d5b5e5dce7d1e09da978c8706fb3566796e2f22 Git Rebase This command is used to combine changes from two branches into a single branch. When you run this command, Git replays the changes from one branch onto another branch and creates a new commit. Shell $ git rebase feature-1 Git Tag This command is used to create a tag for a specific commit. A tag is a label that marks a specific point in your Git history. Shell $ git tag v1.0.0 Git Blame This command is used to view the commit history for a specific file or line of code. When you run this command, Git shows you the author and date for each line of code in the file. Shell $ git blame file.txt “Git: making it easier to blame others since 2005” Git Show This command is used to view the changes made in a specific commit. When you run this command, Git shows you the files that were changed and the differences between the old and new versions. Shell $ git show 5d5b5e5dce7d1e09da978c8706fb3566796e2f22 Git Bisect This command is used to find the commit that introduced a bug in your code. When you run this command, Git helps you narrow down the range of commits to search through to find the culprit. Shell $ git bisect start $ git bisect bad HEAD $ git bisect good 5d5b5e5dce7d1e09da978c8706fb3566796e2f22 Git Submodule This command is used to manage submodules in your Git repository. A submodule is a separate Git repository included as a subdirectory of your main Git repository. Shell $ git submodule add https://github.com/example/submodule.git Git Archive This command is used to create a tar or zip archive of a Git repository. When you run this command, Git creates an archive of the repository that you can save or send to others. Shell $ git archive master --format=zip --output=archive.zip Git Clean This command is used to remove untracked files from your working directory. When you run this command, Git removes all files and directories that are not tracked by Git. Shell $ git clean -f Git Reflog This command is used to view the history of all Git references in your repository, including branches, tags, and HEAD. When you run this command, Git shows you a list of all the actions that have been performed in your repository. Git Config This command is used in Git to configure various aspects of the Git system. It is used to set or get configuration variables that control various Git behaviors. Here are some examples of how to use git config: Set user information: Shell git config --global user.name "Your Name" git config --global user.email "your.email@example.com" The above commands will set your name and email address globally so that they will be used in all your Git commits. Show configuration variables: Shell git config --list The above command will display all the configuration variables and their values that are currently set for the Git system. Set the default branch name: Shell git config --global init.defaultBranch main The above command will set the default branch name to “main”. This is the branch name that Git will use when you create a new repository. Git Grep This command in Git searches the contents of a Git repository for a specific text string or regular expression. It works similarly to the Unix grep command but is optimized for searching through Git repositories. Here's an example of how to use git grep: Let’s say you want to search for the word “example” in all the files in your Git repository. You can use the following command: Shell git grep example This will search for the word “example” in all the files in the current directory and its subdirectories. If the word “example” is found, git grep will print the name of the file, the line number, and the line containing the matched text. Here's an example output: Shell README.md:5:This is an example file. index.html:10:<h1>Example Website</h1> In this example, the word “example” was found in two files: README.md and index.html. The first line of the output shows the file name, followed by the line number and the line containing the matched text. You can also use regular expressions with git grep. For example, if you want to search for all instances of the word "example" that occur at the beginning of a line, you can use the following command: Shell git grep '^example' This will search for all instances of the word “example” that occur at the beginning of a line in all files in the repository. Note that the regular expression ^ represents the beginning of a line. Git Revert This command is used to undo a previous commit. Unlike git reset, which removes the commit from the repository, git revert creates a new commit that undoes the changes made by the previous commit. Here's an example of how to use git revert. Let’s say you have a Git repository with three commits: Plain Text commit 1: Add new feature commit 2: Update documentation commit 3: Fix bug introduced in commit 1 You realize that the new feature you added in commit 1 is causing issues and you want to undo that commit. You can use the following command to revert commit 1: Shell git revert <commit-1> This will create a new commit that undoes the changes made by commit 1. If you run git log after running git revert, you'll see that the repository now has four commits: Plain Text commit 1: Add new feature commit 2: Update documentation commit 3: Fix bug introduced in commit 1 commit 4: Revert "Add new feature" The fourth commit is the one created by git revert. It contains the changes necessary to undo the changes made by commit 1. Git RM This command is used to remove files from a Git repository. It can be used to delete files that were added to the repository, as well as to remove files that were previously tracked by Git. Here's an example of how to use git rm- Remove a file that was added to the repository but not yet committed: Shell git rm filename.txt This will remove filename.txt from the repository and stage the deletion for the next commit. Remove a file that was previously committed: Shell git rm filename.txt git commit -m "Remove filename.txt" The first command will remove filename.txt from the repository and stage the deletion for the next commit. The second command will commit the deletion. Remove a file from the repository but keep it in the working directory: Shell git rm --cached filename.txt This will remove filename.txt from the repository but keep it in the working directory. The file will no longer be tracked by Git, but it will still exist on your local machine. Remove a directory and its contents from the repository: Shell git rm -r directoryname This will remove directoryname and its contents from the repository and stage the deletion for the next commit. In conclusion, these frequently used Git commands are essential for every software professional who works with Git repositories regularly. Knowing how to use these commands effectively can help you streamline your workflow, collaborate with your team more effectively, and troubleshoot issues that may arise in your Git repository. In conclusion, these 25 Git commands are essential for every software engineer who work with Git repositories regularly. Knowing how to use these commands effectively can help you streamline your workflow, collaborate with your team more effectively, and troubleshoot issues that may arise in your Git repository. If you enjoyed this story, please share it to help others find it! Feel free to leave a comment below. Thanks for your interest. Connect with me on LinkedIn.
Cloud-native technologies like Kubernetes enable companies to build software quickly and scale effortlessly. However, debugging these Kubernetes-based applications can be quite challenging due to the added complexity of building service-oriented architectures (microservices) and operating the underlying Kubernetes infrastructure. Bugs are inevitable and typically occur as a result of an error or oversight made during the software development process. So, in order for a business to keep pace with app delivery and keep their end users happy, developers need an efficient and effective way to debug. This involves finding, analyzing, and fixing these bugs. This article highlights five Kubernetes debugging challenges and how to tackle them. #1. Slow Dev Loop Due To Building and Re-Deploying Containers When a development team adopts a cloud-native technology like Kubernetes, their developer experience is significantly altered as they’ll now be expected to carry out extra steps in the inner dev loop. Instead of coding and seeing the result of their code changes immediately, as they used to when working with monolithic environments, they now have to manage external dependencies, build containers, and implement orchestration configuration (e.g., Kubernetes YAML) before they can see the impact of their code changes. There are several ways to tackle this Kubernetes debugging challenge: The first one is for you to develop services locally and focus on unit tests over end-to-end tests but this was painful when a service/web application has authentication requirements and dependencies on databases. Another way to solve this is to use a tool called DevSpace which will automate your build and deployment steps, thereby making it faster. And finally, you can also utilize a CNCF tool called Telepresence to connect your local development environment to a remote Kubernetes cluster, thereby making it possible to access these external dependencies in the remote Kubernetes cluster and test them against the service being developed locally for an instant feedback loop. #2. Lack of Visibility in the End-to-End Flow of a Distributed Application Another debugging challenge when working with Kubernetes is having full visibility of the end-to-end flow of your application because there are often just too many services. And without full visibility, it’s difficult to identify and fix a bug. Ideally, you should be able to get cross-service visibility into what is calling what, what is timing out, etc. To tackle this, you need to utilize tools that make observability and tracing more seamless. For example, tools OpenTelemetry, Jaeger, and Grafana Tempo can help you get the necessary information to reproduce errors. The goal here is to get as much information as possible, and when you do, you’d be able to fix bugs in real-time and ultimately improve the overall performance of your application. #3. Inability To Attach a Debugger to the Code One of the most important things a developer needs is the ability to attach a debugger to their code, and working with Kubernetes doesn’t make this easy. Yes, things like print/log statements work, but they are nowhere near as good as being able to put a debugger on something and step through the code, especially if it’s a new code base that a user isn’t familiar with. Two possible ways to tackle this Kubernetes debugging issue are to: Develop locally and find ways to mock or spin up local instances of dependencies. Ensure code is unit testable and focus on those because they are easier to write tests for and easy to throw a debugger on. #4. Complicated Setup for Performing Integration Testing With a Local Change Cloud-native applications are often composed of various microservices. More often than not, these microservices work interdependently and communicate with each other to process larger business requests. As an example, a timeline service for a social media application may need to talk to a user profile service to determine a user's followers and, at the same time, may need to talk to an authentication service to determine the authentication state of a user. Because of this multi-directional, service-to-service communication that happens between microservices, it is crucial to perform integration testing on microservices before deploying any changes because unit testing alone doesn't always provide guarantees about the behavior of the application in the target environment. Performing integration testing in this context naturally involves running multiple services and connecting to (potentially remote) middleware and data stores. This requires techniques and tooling that present multiple challenges. These challenges include having limited resources and inconsistent data between production and non-production environments; managing distinct configurations for separate environments; and difficulties associated with managing service versioning, releases, and deployment cycles. #5. Reproducing an Issue That Only Happens in Prod/Staging Sometimes, it can be very complex to reproduce a bug that happened in production or staging locally. At this point, your mocks or existing values will not be sufficient. You’d think to yourself, how can I actually reproduce this issue? How can I get to the root of the problem faster? Well, an open-source tool called Telepresence is usually my go-to when facing the K8s debugging challenge — The tool allows you to access remote dependencies as if they were running locally and reroute traffic from remote to local services. This means you’d get to debug them in real-time, reproduce these issues, and push a fix to your preferred version control and CI/CD pipeline faster. Conclusion Most organizations insist that any important delivery of software goes through multiple iterations of testing, but it’s important to remember that bugs are inevitable. Having the ability to debug applications effectively is one of the best techniques for identifying, understanding, and fixing bugs. Container technology, such as Kubernetes, provides many benefits for software developers but also introduces app debugging challenges. Fortunately, there are multiple ways to address these challenges easily. If there are other Kubernetes debugging techniques that you’d like to share, please mention them in the comment section.
Steel Threads are a powerful but obscure software design approach. Learning about Steel Threads will make you a better engineer. You can use them to avoid common problems like integration pain, and you can use them to cut through the complexity of system design. So Obscure It Was Deleted From Wikipedia in 2013 How unknown are Steel Threads? The concept was deleted from Wikipedia in 2013 because “the idea is not notable within Software Engineering, and hasn’t received significant coverage from notable sources.” Let’s add to the coverage, and also talk through why it is such a useful approach. What Are Steel Threads? A Steel Thread is a very thin slice of functionality that threads through a software system. They are called “threads” because they weave through the various parts of the software system and implement an important use case. They are called “steel” because the thread becomes a solid foundation for later improvements. With a Steel Thread approach, you build the thinnest possible version that crosses the boundaries of the system and covers an important use case. Example of Conventional, Problematic Approach Let’s say you’re building a new service to replace a part of your monolithic codebase. The most common way to do this would be to: Look at the old code, and figure out the needs of the new system. Design and build out the APIs that provide the capabilities you need. Go into the old code, and update references to use the new APIs. Do it behind a feature flag. Cut over using the feature flag. Fix any issues that come up until it’s working, turning off the feature flag if necessary to go back to the old code path. When it’s stable, remove the old code paths. Sounds reasonable, right? Well, this is the most common way software engineers operate, but this approach has a lot of landmines. What problems would I expect in this project? It may be appealing to build the new service in a way disconnected from the old system. After all, the design might feel purer. But you’re also introducing significantly more structural change and you’re making these changes without any integration into the old system. This increases integration pain significantly. My expectation would be that all the estimates for the project are unrealistic. And I’d expect the project to be considered a failure after it is completed, even if the resulting service has a generally good design. I would expect the switchover to the new system to be problematic. There will be a series of problems uncovered as you switch over, that will require switching back to the old code paths or working intensely to fix problems in the final stages of the project. Both of these things are avoidable, by not having a huge cutover. Note that even cutting over one percent of traffic to the new service with a feature flag is a cutover approach. Why? You’re cutting over all that one percent of traffic to all the changes at the same time. I still would not expect it to go well. You are taking steps that are too large. Example Using a Steel Thread Contrast that approach with the Steel Thread way of doing it. Think about the new system you’re building. Come up with some narrow use cases that represent Steel Threads of the system – they cover useful functionality into the system, but don’t handle all use cases, or are constrained in some ways. Choose a starting use case that is as narrow as possible, that provides some value. For example, you might choose one API that you think would be part of the new service. Build out the new API in a new service. Make it work for just that narrow use case. For any other use case, use the old code path. Get it out to production, and into full use. (Tip: you could even do both the new AND old code path, and compare!) Then you gradually add the additional use cases, until you’ve moved all of the functionality you need to, to the new service. Each use case is in production. Once you’re done, you rip out the old code and feature flags. This isn’t risky at all, since you’re already running on the new system. Steel Threads Avoid Integration Pain, and Give You Higher Confidence Integration pain is one of the bigger causes of last-minute problems in projects. When you cut over to a new system, you always find problems you don’t expect. You should be suspicious of anything that involves a cut-over. Do things in small increments. Steel Threads integrate from the beginning, so you never have a lot of integration pain to wade through. Instead, you have small integration pain, all along the way. Also, your service never needs to be tested before it goes live, because you’ve tested it incrementally, along the way. You know it can handle production loads. You’ve already added network latency, so you know the implications of that. All the surprises are moved forward, and handled incrementally, as just part of the way you gradually roll out the service. The important thing is that you have a working, integrated system, and as you work on it, you keep it working. And you flesh it out over time. Steel Threads Can Help Cut Through Complexity When you’re designing a system, you have a LOT of complexity. Building a set of requirements for the new system can be a challenging endeavor. When using a Steel Thread approach, you choose some of the core requirements and phrase them in a way that cuts through the layers of the system, and exercises your design. It provides a sort of skeletal structure for the whole system. The implementation of that Steel Thread then becomes the bones upon which further requirements can be built. Thus, Steel Threads are a subset of the requirements of a system. For example, let’s say you’re implementing a clone of Slack. Your initial Steel Thread might be something like: “Any unauthenticated person can post a message in a hardcoded #general room in a hardcoded account. Messages persist through page refreshes.” Note how limited this initial Steel Thread is. It doesn’t handle authentication, users, or accounts. It does handle writing messages, and persisting them. Your second Steel Thread can move the system towards being more useful. You could, for example, have a Steel Thread that allows the message poster to choose the name they post under. This second Steel Thread hasn’t actually done much. You still don’t have authentication, accounts, or even a concept of a user. But you have made a chat room that works enough that you can start using it. Steel Threads Provide Early Feedback Note that in this Slack clone example, you can get early feedback on the system you’re building, even though you haven’t built that much yet. This is another powerful reason for using Steel Threads. After just those two Steel Threads, your team could start using the chat room full-time. Think about how much your team will learn from using your system. It’s a working system. Compare that to what you would have learned building out the User and Account systems, hooking everything up, and finally building out a chat room. Start With Steel Threads Steel Threads are often a good place to start when designing your projects. They create a skeleton for the rest of the work to come. They nail down the core parts of the system so that there are natural places to flesh out. I encourage you to try a Steel Threaded approach. I think you’ll find it can transform your projects. Let me know your experiences with it! Steel Threads Are Closely Related To Vertical Slices You may have heard of the term “vertical slicing." I describe the concept in my post on Milestones. Steel Threads are a software design technique that results in delivering your software in vertical slices. The term tends to be used to describe the initial vertical slices of a system. They’re closely related concepts, but not completely the same. I’ve also heard of Steel Threads being referred to as “tracer bullets."
Three Hard Facts First, the complexity of your software systems is through the roof, and you have more external dependencies than ever before. 51% of IT professionals surveyed by SolarWinds in 2021 selected IT complexity as the top issue facing their organization. Second, you must deliver faster than the competition, which is increasingly difficult as more open-source and reusable tools let small teams move extremely fast. Of the 950 IT professionals surveyed by RedHat, only 1% indicated that open-source software was “not at all important.” And third, reliability is slowing you down. The Reliability/Speed Tradeoff In the olden days of software, we could just test the software before a release to ensure it was good. We ran unit tests, made sure the QA team took a look, and then we’d carefully push a software update during a planned maintenance window, test it again, and hopefully get back to enjoying our weekend. By 2023 standards, this is a lazy pace! We expect teams to constantly push new updates (even on Fridays) with minimal dedicated manual testing. They must keep up with security patches, release the latest features, and ensure that bug fixes flow to production. The challenge is that pushing software faster increases the risk of something going wrong. If you took the old software delivery approach and sped it up, you’d undoubtedly have always broken releases. To solve this, modern tooling and cloud-native infrastructure make delivering software more reliable and safer, all while reducing the manual toil of releases. According to the 2021 State of DevOps report, more than 74% of organizations surveyed have Change Failure Rate (CFR) greater than 16%. For organizations seeking to speed up software changes (see DORA metrics), many of these updates caused issues requiring additional remediation like a hotfix or rollback. If your team hasn’t invested in improving the reliability of software delivery tooling, you won’t be able to achieve reliable releases at speed. In today’s world, all your infrastructure, including dev/test infrastructure, is part of the production environment. To go fast, you also have to go safely. More minor incremental changes, automated release and rollback procedures, high-quality metrics, and clearly defined reliability goals make fast and reliable software releases possible. Defining Reliability With clearly defined goals, you will know if your system is reliable enough to meet expectations. What does it mean to be up or down? You have hundreds of thousands of services deployed in clouds worldwide in constant flux. The developers no longer coordinate releases and push software. Dependencies break for unexpected reasons. Security fixes force teams to rush updates to production to avoid costly data breaches and cybersecurity threats. You need a structured, interpreted language to encode your expectations and limits of your systems and automated corrective actions. Today, definitions are in code. Anything less is undefined. The alternative is manual intervention, which will slow you down. You can’t work on delivering new features if you’re constantly trying to figure out what’s broken and fix releases that have already gone out the door. The most precious resource in your organization is attention, and the only way to create more is to reduce distractions. Speeding Up Reliably Service level objectives (SLOs) are reliability targets that are precisely defined. SLOs include a pointer to a data source, usually a query against a monitoring or observability system. They also have a defined threshold and targets that clearly define pass or fail at any given time. SLOs include a time window (either rolling or calendar aligned) to count errors against a budget. OpenSLO is the modern de facto standard for declaring your reliability targets. Once you have SLOs to describe your reliability targets across services, something changes. While SLOs don’t improve reliability directly, they shine a light on the disconnect between expectations and reality. There is a lot of power in simply clarifying and publishing your goals. What was once a rough shared understanding becomes explicitly defined. We can debate the SLO and decide to raise, lower, redefine, split, combine, and modify it with a paper trail in the commit history. We can learn from failures as well as successes. Whatever other investments you’re making, SLOs help you measure and improve your service. Reliability is engineered; you can’t engineer a system without understanding its requirements and limitations. SLOs-as-code defines consistent reliability across teams, companies, implementations, clouds, languages, etc.
What Is Distributed Tracing? The rise of microservices has enabled users to create distributed applications that consist of modular services rather than a single functional unit. This modularity makes testing and deployment easier while preventing a single point of failure with the application. While applications begin to scale and distribute their resources amongst multiple cloud-native services, tracing a single transaction becomes tedious and nearly impossible. Hence, developers need to apply distributed tracing techniques. Distributed tracing allows a single transaction to be tracked across the front end to the backend services while providing visibility into the systems’ behavior. How Distributed Tracing Works The distributed tracing process operates on a fundamental concept of being able to trace every transaction through multiple distributed components of the application. To achieve this visibility, distributed tracing technology uses unique identifiers, namely the Trace ID, to tag each transaction. The system then puts together each trace from the various components of the application by using this unique identifier, thus building a timeline of the transaction. Each trace consists of one or more spans that represent a single operation within a single trace. It is essential to understand that a span can be referred to as a parent span for another span, indicating that the parent span triggers the child span. Implementing Distributed Tracing Setting up a distributed tracing depends on the selected solution. However, every solution will consist of these common steps. These three steps ensure developers have a solid base to start their distributed tracing journey: Setting up a distributed tracing system. Instrumenting code for tracing. Collecting and storing trace data. 1. Setting Up a Distributed System Selecting the right distributed tracing solution is crucial. Key aspects, such as compatibility, scale, and other important factors must be addressed. Many distributed tracing tools support various programming languages, including Node.js, Python, Go, .NET, Java, etc. These tools allow developers to use a single solution for distributed tracing across multiple services. 2. Instrumenting Code for Tracing Depending on the solution, the method of integration may change. The most common approach many solutions provide is using an SDK that collects the data during runtime. For example, developers using Helios with Node.js require installing the latest Helios OpenTelemetry SDK by running the following command: npm install --save helios-opentelemetry-sdk Afterward, the solution requires defining the following environment variables. Finally, it enables the SDK to collect the necessary data from the service: export NODE_OPTIONS="--require helios-opentelemetry-sdk" export HS_TOKEN="{{HELIOS_API_TOKEN}" export HS_SERVICE_NAME="<Lambda01>" export HS_ENVIRONMENT="<ServiceEnvironment01>" 3. Collecting and Storing Trace Data In most distributed tracing systems, trace data collection occurs automatically during the runtime. Then, this data makes its way to the distributed tracing solution, where the analysis and visualization occur. The collection and storage of the trace data depend on the solution in use. For example, if the solution is SaaS-based, the solution provider will take care of all trace data collecting and storage aspects. However, if the tracing solution is self-hosted, the responsibility of taking care of these aspects falls on the administrators of the solution. Analyzing Trace Data Analyzing trace data can be tedious. However, visualizing the trace data makes it easier for developers to understand the actual transaction flow and identify anomalies or bottlenecks. The following demonstrates the flow of the transaction through the various services and components of the application. An advanced distributed tracing system may highlight errors and bottlenecks that each transaction runs through. Since the trace data contains the time it takes for each service to process the transaction, developers can analyze the latencies and identify abnormalities that may impact the application’s performance. Identifying an issue using the distributed tracing solution can provide insight into the problem that has taken place. However, to gain further details regarding the issue, developers may need to use additional tools that provide added insight with observability or the capability to correlate traces with the logs to identify the cause. Distributed tracing solutions, such as Helios, offer insight into the error’s details, which eases the developer’s burden. Best Practices for Distributed Tracing A comprehensive distributed tracing solution empowers developers to respond to crucial issues swiftly. The following best practices set the fundamentals for a successful distributed tracing solution. 1. Ensuring Trace Data Accuracy and Completeness Collecting trace data from services enable developers to identify the performance and latency of all the services each transaction flows through. However, when the trace data does not contain information from a specific service, it reduces the accuracy of the entire trace and its overall completeness. To ensure developers obtain the most out of distributed tracing, it is vital that the system collects accurate trace information from all services to reflect the original data. 2. Balancing Trace Overhead and Detail Collecting all trace information from all the services will provide the most comprehensive trace. However, collecting most trace information comes at the cost of the overhead to the overall application or the individual service. The tradeoff between the amount of data collected and the acceptable overhead is crucial. Planning for this tradeoff ensures distributed tracing does not harm the overall solution, thus outweighing the benefits the solution brings. Another take on balancing these aspects is filtering and sampling the trace information to collect what is required. However, this would require additional planning and a thorough understanding of the requirement to collect valuable trace information. 3. Protecting Sensitive Data in Trace Data Collecting trace information from transactions includes collecting payloads of the actual transaction. This information is usually considered sensitive since it may contain personally identifiable information of customers, such as driver’s license numbers or banking information. Regulations worldwide clearly define what information to store during business operations and how to handle this information. Therefore, it is of unparalleled importance that the information collected must undergo data obfuscation. Helios enables its users to easily obfuscate sensitive data from the payloads collected, thereby enabling compliance with regulations. In addition to obfuscation, Helios provides other techniques to enhance and filter out the data sent to the Helios platform. Distributed Tracing Tools Today, numerous distributed tracing tools are available for developers to easily leverage their capabilities in resolving issues quicker. 1. Lightstep Lightstep is a cloud-agnostic distributed tracing tool that provides full-context distributed tracing across multi-cloud environments or microservices. It enables developers to integrate the solution with complex systems with little extra effort. It also provides a free plan with the features required for developers to get started on their distributed tracing journey. In addition, the free plan offers many helpful features, including data ingestion, analysis, and monitoring. Source: LightStep UI 2. Zipkin Zipkin is an open-source solution that provides distributed tracing with easy-to-use steps to get started. It enhances its distributed tracing efforts by enabling the integration with Elasticsearch for efficient log searching. Source: Zipkin UI It was developed at Twitter to gather crucial timing data needed to troubleshoot latency issues in service architectures, and it is straightforward to set up with a simple Docker command: docker run -d -p 9411:9411 openzipkin/zipkin 3. Jaeger Tracing Jaeger Tracing is yet another open-source solution that provides end-to-end distributed tracing and the ability to perform root cause analysis to identify performance issues or bottlenecks across each trace. It also supports Elasticsearch for data persistence and exposes Prometheus metrics by default to help developers derive meaningful insights. In addition, it allows filtering traces based on duration, service, and tags using the pre-built Jaeger UI. Source: Jaeger Tracing 4. SigNoz SigNoz is an open-source tool that enables developers to perform distributed tracing across microservices-based systems while capturing logs, traces, and metrics and later visualizing them within its unified UI. It also provides insightful performance metrics such as the p50, p95, and p99 latency. Some key benefits of using SigNoz include the consolidated UI that showcases logs, metrics, and traces while supporting OpenTelemetry. Source: SigNoz UI 5. New Relic New Relic is a distributed tracing solution that can observe 100% of an application’s traces. It provides compatibility with a vast technology stack and support for industry-standard frameworks such as OpenTelemetry. It also supports alerts to diagnose errors before they become major issues. New Relic has the advantage of being a fully managed cloud-native with support for on-demand scalability. In addition, developers can use a single agent to automatically instrument the entire application code. Source: New Relic UI 6. Datadog Datadog is a well-recognized solution that offers cloud monitoring as a service. It provides distributed tracing capabilities with Datadog APM, including additional features to correlate distributed tracing, browser sessions, logs, profiles, network, processes, and infrastructure metrics. In addition, Datadog APM allows developers to easily integrate the solution with the application. Developers can also use the solution’s capabilities to seamlessly instrument application code to monitor cloud infrastructure. Source: DataDog UI 7. Splunk Splunk offers a distributed tracing tool capable of ingesting all application data while enabling an AI-driven service to identify error-prone microservices. It also adds the advantage of correlating between application and infrastructure metrics to better understand the fault at hand. You can start with a free tier that brings in essential features. However, it is crucial to understand that this solution will store data in the cloud; this may cause compliance issues in some industries. Source: Splunk UI 8. Honeycomb Honeycomb brings in distributed tracing capabilities in addition to its native observability functionalities. One of its standout features is that it uses anomaly detection to pinpoint which spans are tied to bad user experiences. It supports OpenTelemetry to enable developers to instrument code without being stuck to a single vendor while offering a pay-as-you-go pricing model to only pay for what you use. Source: HoneyComb UI 9. Helios Helios brings advanced distributed tracing techniques that enhance the developer’s ability to get actionable insight into the end-to-end application flow by adapting OpenTelemetry’s context propagation framework. The solution provides visibility into your system across microservices, serverless functions, databases, and third-party APIs, thus enabling you to quickly identify, reproduce, and resolve issues. Source: Helios Sandbox Furthermore, Helios provides a free trace visualization tool based on OpenTelemetry that allows developers to visualize and analyze a trace file by simply uploading it. Conclusion Distributed tracing has seen many iterations and feature enhancements that allow developers to easily identify issues within the application. It reduces the time taken to detect and respond to performance issues and helps understand the relationships between individual microservices. The future of distributed tracing would incorporate multi-cloud tracing, enabling developers to troubleshoot issues across various cloud platforms. Also, these platforms consolidate the trace, thus cutting off the requirement for developers to trace these transactions across each cloud platform manually, which is time-consuming and nearly impossible to achieve. I hope you have found this helpful. Thank you for reading!
In this blog post, you will learn how to run a Go application to AWS App Runner using the Go platform runtime. You will start with an existing Go application on GitHub and deploy it to AWS App Runner. The application is based on the URL shortener application (with some changes) that persists data in DynamoDB. Introduction AWS App Runner is a robust and user-friendly service that simplifies the deployment process of web applications in the AWS Cloud. It offers developers an effortless and efficient way to deploy their source code or container image directly to a scalable and secure web application without requiring them to learn new technologies or choose the appropriate compute service. One of the significant benefits of using AWS App Runner is that it connects directly to the code or image repository, enabling an automatic integration and delivery pipeline. This eliminates the need for developers to go through the tedious process of manually integrating their code with AWS resources. For developers, AWS App Runner simplifies the process of deploying new versions of their code or image repository. They can easily push their code to the repository, and App Runner will automatically take care of the deployment process. On the other hand, for operations teams, App Runner allows for automatic deployments every time a new commit is pushed to the code repository or a new container image version is added to the image repository. App Runner: Service Sources With AWS App Runner, you can create and manage services based on two types of service sources: Source code (covered in this blog post) Source image Source code is nothing but your application code that App Runner will build and deploy. All you need to do is point App Runner to a source code repository and choose a suitable runtime that corresponds to a programming platform version. App Runner provides platform-specific managed runtimes (for Python, Node.js, Java, Go, etc.). The AWS App Runner Go platform runtime makes it easy to build and run containers with web applications based on a Go version. You don’t need to provide container configuration and build instructions such as a Dockerfile. When you use a Go runtime, App Runner starts with a managed Go runtime image which is based on the Amazon Linux Docker image and contains the runtime package for a version of Go and some tools. App Runner uses this managed runtime image as a base image and adds your application code to build a Docker image. It then deploys this image to run your web service in a container. Let’s Get Started Make sure you have an AWS account and install AWS CLI. 1. Create a GitHub Repo for the URL Shortener Application Clone this GitHub repo and then upload it to a GitHub repository in your account (keep the same repo name i.e. apprunner-go-runtime-app): git clone https://github.com/abhirockzz/apprunner-go-runtime-app 2. Create a DynamoDB Table To Store URL Information Create a table named urls. Choose the following: Partition key named shortcode (data type String) On-Demand capacity mode 3. Create an IAM Role With DynamoSB-Specific Permissions export IAM_ROLE_NAME=apprunner-dynamodb-role aws iam create-role --role-name $IAM_ROLE_NAME --assume-role-policy-document file://apprunner-trust-policy.json Before creating the policy, update the dynamodb-access-policy.json file to reflect the DynamoDB table ARN name. aws iam put-role-policy --role-name $IAM_ROLE_NAME --policy-name dynamodb-crud-policy --policy-document file://dynamodb-access-policy.json Deploy the Application to AWS App Runner If you have an existing AWS App Runner GitHub connection and want to use that, skip to the Repository selection step. 1. Create an AWS App Runner GitHub Connection Open the App Runner console and choose Create service. Create AWS App Runner Service On the Source and deployment page, in the Source section, for Repository type, choose Source code repository. Under Connect to GitHub, choose Add new, and then, if prompted, provide your GitHub credentials. Add GitHub connection In the Install AWS Connector for GitHub dialog box, if prompted, choose your GitHub account name. If prompted to authorize the AWS Connector for GitHub, choose Authorize AWS Connections. Choose Install. Your account name appears as the selected GitHub account/organization. You can now choose a repository in your account. 2. Repository Selection For Repository, choose the repository you created: apprunner-go-runtime-app. For Branch, choose the default branch name of your repository (for example, main). Configure your deployment: In the Deployment settings section, choose Automatic, and then choose Next. Choose GitHub repo 3. Configure Application Build On the Configure build page, for the Configuration file, choose Configure all settings here. Provide the following build settings: Runtime: Choose Go 1 Build command: Enter go build main.go Start command: Enter ./main Port: Enter 8080 Choose Next. Configure runtime info 4. Configure Your Service Under Environment variables, add an environment variable. For Key, enter TABLE_NAME, and for Value, enter the name of the DynamoDB table (urls) that you created before. Add environment variables Under Security > Permissions, choose the IAM role that you had created earlier (apprunner-dynamodb-role). Add IAM role for App Runner Choose Next. On the Review and create page, verify all the details you’ve entered, and then choose Create and deploy. If the service is successfully created, the console shows the service dashboard, with a Service overview of the application. Verify URL Shortener Functionality The application exposes two endpoints: To create a short link for a URL Access the original URL via the short link First, export the App Runner service endpoint as an environment variable: export APP_URL=<enter App Runner service URL> # example export APP_URL=https://jt6jjprtyi.us-east-1.awsapprunner.com 1. Invoke It With a URL That You Want to Access via a Short Link curl -i -X POST -d 'https://abhirockzz.github.io/' $APP_URL # output HTTP/1.1 200 OK Date: Thu, 21 Jul 2022 11:03:40 GMT Content-Length: 25 Content-Type: text/plain; charset=utf-8 {"ShortCode":"ae1e31a6"} You should get a JSON response with a short code and see an item in the DynamoDB table as well. You can continue to test the application with other URLs that you want to shorten! 2. Access the URL Associated With the Short Code Enter the following in your browser http://<enter APP_URL>/<shortcode>. For example, when you enter https://jt6jjprtyi.us-east-1.awsapprunner.com/ae1e31a6, you will be redirected to the original URL. You can also use curl. Here is an example: export APP_URL=https://jt6jjprtyi.us-east-1.awsapprunner.com curl -i $APP_URL/ae1e31a6 # output HTTP/1.1 302 Found Location: https://abhirockzz.github.io/ Date: Thu, 21 Jul 2022 11:07:58 GMT Content-Length: 0 Clean up Once you complete this tutorial, don’t forget to delete the following resources: DynamoDB table App Runner service Conclusion In this blog post, you learned how to go from a Go application in your GitHub repository to a complete URL shortener service deployed to AWS App Runner!
In the past few years, there has been a growing number of organizations and developers joining the Docker journey. Containerization simplifies the software development process because it eliminates dealing with dependencies and working with specific hardware. Nonetheless, the biggest advantage of using containers is down to the portability they offer. But, it can be quite confusing how to run a container on the cloud. You could certainly deploy these containers to servers on your cloud provider using Infrastructure as a Service (IaaS). However, this approach will only take you back to the issue we mentioned previously, which is, you’d have to maintain these servers when there’s a better way to do that. Table of Contents How To Run a Docker Container on the Cloud Using a Container Registry Using Container-as-a-Service Why Should I Use CaaS? What Are the Best CaaS Solutions? AWS ECS AWS Lambda AWS App Runner Azure Container Instances Google Cloud Run Conclusion How To Run a Docker Container on the Cloud Using a Container Registry You are probably reading this if your container runs locally but are wondering how to run it on the cloud. In this case, the next step to take to bring it to the cloud is to select a container registry that will act as a centralized location to store your containers. Essentially, you will need to push your container to this registry, whether public or private, so your image can be distributed from there. Using Container-as-a-Service Containers-as-a-Service (CaaS) is a concept that allows companies to directly run their container on the cloud using any given provider of choice. With CaaS, the infrastructure required to run containers such as orchestration tools, e.g, Docker Swarm, Kubernetes, OpenStack, etc., as well as cluster management software are non-existent for the user. As a side note, CaaS joins the already established cloud service models such as Infrastructure-as-a-Service (IaaS), Platform-as-a-Service (PaaS), and Software-as-a-Service (SaaS). Why Should I Use CaaS? Some of the advantages of using Container-as-a-Service are: Cost reduction: it eliminates the time, effort, and money spent on maintaining secure infrastructure to run your container. Flexibility: you can easily move from cloud to cloud or even back to your on-prem infrastructure, freeing you from vendor lock-in. Speed: Since the underlying infrastructure abstracts from it, you can deploy your container quicker. Overall, CaaS will not only simplify the running process of a software application but also improve overall security around it as most CaaS solutions offer vulnerability scans. Furthermore, you don’t have to worry about managing the hardware that will run your container. What Are the Best CaaS Solutions? When choosing a CaaS solution, some of the key considerations include: Can it operate multi-container applications? What networks and storage functions are available? Which file format does it support? How is storage achieved? Which billing model does it use? Amazon Elastic Container Service (Amazon ECS) Amazon ECS is a scalable container orchestration platform by AWS designed to run, stop, and manage containers in a cluster environment by using task definition. Essentially, task definition is where you define: The container to use. How many containers to run. How your containers are linked. What resources your containers use. Note: AWS ECS also supports mounting EFS volumes. With that in mind, you have two ways of using ECS: By using EC2 Instances. By using Fargate. ECS With EC2 In this case, containers will be deployed to EC2 Instances (VMs) created for the cluster. The merits include: Full control over the type of EC2 instance used. Your container is used for machine learning and is GPU-oriented, meaning you can choose to run on an EC2 instance that is optimized for this usage. Reduce your cost by using Spot instances, which can reduce your cost by up to 90%. On the other hand, the only demerit is that: You are responsible for patching, managing network security, and the scalability associated with these instances. Pricing: When it comes to cost, you are charged for the EC2 instances run within your ECS cluster and VPC networking. ECS With Fargate AWS Fargate was launched in 2017, and with this model, you don’t have to be worried about managing EC2 Instances. AWS Fargate directly manages the underlying servers required to run your container by pre-configuring a cluster for you. You will just need to add your workload to it. The advantages include: No infrastructure to manage. AWS deals with availability and scalability of your container application. Fargate Spot, based on similar principles as the Spot instances, AWS mentions a cost reduction of up to 70%. In contrast, the downside is: Only one networking mode is currently supported (awsvpc), which might limit you with the network layers in some specific scenarios you might try to achieve. A recent report by Datadog, mentions that, in 2021, 32% of AWS container environments were using AWS Fargate. This trend confirms that companies are switching gradually to serverless environments. Pricing: Fargate’s pricing is based on a “pay as you go” model. There are no upfront costs and you only pay for the compute and memory resources consumed. Here’s a pricing example for the region US West (Oregon): $0.04048 per vCPU per hour. $0.004445 per gigabyte per hour. The table below will help you better understand the terminology used with ECS/Fargate and Kubernetes: Infrastructure Layer Component ECS Fargate Kubernetes Workload Deployment UnitDesired StateAccess Endpoint TaskServiceALB PodDeploymentIngress Control Plane API EndpointSchedulerControllerState Management Frontend ServiceCapacity ManagerCluster ManagerState DB Kube-apiserverKube-schedulerKube-controlleretcd Data Plane Guest OSAgentContainer RuntimeNetwork Amazon Linux 2Fargate AgentDockerENI/VPC Linux/WindowsKubeletContainerdCN/Kubeproxy AWS Lambda A serverless service by AWS whereby you bring your code, whether it is Java, Go, C#, Python, Powershell, Node.js or Ruby, and Amazon will run it into a callable function that complies with their language’s Lambda interface. Lambda functions are mostly called by connecting them to AWS API Gateway, which exposes the functions as REST API calls. You might be wondering why we are even mentioning AWS Lambda at this point as there’s no link with Docker or container images. According to AWS, in December 2020, AWS Lambda began supporting running container images up to 10GB in size. Using Lambda to run a Docker container on the cloud gives you: Scalability: Lambda will automatically create new instances of your function to meet demand as it can scale up to 500 new instances every minute. However, you may have to contend with: Reduced portability: Since Lambda is AWS’ proprietary serverless tech, you will need to significantly adjust your function to move to another cloud provider. Slow scalability: When we mentioned how Lambda can spin up new instances, we weren’t talking about its speed. A cold start for your function will require time and has a hard impact on Java and .NET applications. Can’t run long-running tasks: Lambda functions can only run up to 15 minutes. Pricing: You are charged by the number of requests for their functions and by the duration (time spent to execute the function). Pricing will also vary depending on the amount of memory you allocate to your function. Nonetheless, Lambda offers a free tier that works even if you use your annual AWS Free Tier term, which offers 400,000 GB-seconds of compute time every month. AWS App Runner Launched in May 2021, AWS App Runner facilitates bringing a web application to the cloud without worrying about scaling or the infrastructure associated with it. Essentially, it simply runs Amazon ECS with Fargate to execute your container but you don’t need to set up or configure anything related to Fargate to get going. It can run in build mode, which pulls code from your GitHub repository and builds the application at any commits you might push to your main branch. Alternatively, it can run in container mode, where you will connect your container registry (only AWS ECR is supported) and point to your image. If you want to see what AWS has planned for App Runner, they outline everything you need to know with their detailed roadmap. The core advantage of AWS App Runner when it comes to running a Docker container on the cloud is that: It is easy to configure and provides a simple way to get a web application to run in the cloud. On the other hand, the disadvantages include: Build mode only supports Python and Node.js runtimes. Can’t scale down to 0, you need to pay for at least one instance. Build mode has no integration with AWS CodeCommit or other Source Control Management, meaning you will be forced to use GitHub. App cannot communicate with private VPC: More details here. Pricing: You are billed for what you use. For example, a minimal instance (1vCPU, 2GB) will cost $0.078 per hour or around $56.00 per month, plus a little extra for automatic build and deployment, if it is always running: $0.064 per vCPU per hour. $0.007 per gigabyte per hour. Automatic deployment: $1 per application per month. Build Fee: $0.005/build-minute. Detailed pricing information is available on their website. Azure Container Instances (ACI) Microsoft was a late entrant in the CaaS market since Azure Container Instances was announced in July 2017. It offers: Support for persistent storage by mounting Azure file share to the container. Co-scheduled groups, Azure supports the scheduling of multi-container groups that share a host machine, local network, or storage. Container is in your virtual network and can communicate with other resources in that network. Full control over the instance that runs your container. Adding GPU compute power is not a problem with ACI. The only downside associated with it is that it: Only supports calling Docker containers from a registry. Pricing: Billing is per hour of vCPU, Memory, GPU, and OS used. Using a container that requires a GPU or Windows will be more expensive. $0.04660 per vCPU per hour. $0.0051 per gigabyte per hour. Google Cloud Run Google Cloud Run, GCP’s CaaS solution, became available in November 2019. Similar to the other options of running Docker containers in the cloud listed above, this service is built on the Knative platform based on Kubernetes. Similar to AWS App Runner, you can choose to point to a container registry or repository that contains your application code. Benefits: Use of secrets from Google Secret Manager. Deployment from source code supports Go, Python, Java, Node.js, Ruby, and more. Support traffic splitting between revisions. Disadvantage: Not directly related to Cloud Run but the only disadvantage is connected to GCP as a whole, whereby there is a limited number of regions compared to Azure or AWS, for instance. Pricing: Anyone could try Cloud Run for free with the $300 credit that GCP offers to their new customers. After that, you’ll be billed once you go over the free tier. The free monthly quotas for Google Cloud Run are as follows: CPU: The first 180,000 vCPU-seconds. Memory: The first 360,000 GB-seconds. Requests: The first 2 million requests. Networking: The first 1 GB egress traffic (platform-wide). Once you bypass these limits; however, you’ll need to pay for your usage. The costs for the paid tier of Google Cloud Run are: CPU: $0.00144 per vCPU per minute. Memory: $0.00015 per GB per minute. Requests: $0.40 per 1 million requests. Networking: $0.085 per GB delivered. Conclusion Cloud providers are always innovating to fulfill the needs of customers by continually bringing new services. A minor concern is that the delivery of more services and features makes it even more confusing for developers and organizations. Although there may be slight differences in AWS, Azure, and Google Cloud offerings, it is evident that they all share a common goal. They are all seeking to simplify running Docker containers on the cloud orchestration while maintaining the flexibility required to support a wide range of developer use cases.
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.
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