Czytaj książkę: «Kubernetes Cookbook»
© Kirill Kazakov, 2024
© Maksim Muravev, 2024
© Viachaslau Matsukevich, 2024
ISBN 978-5-0064-6563-3
Created with Ridero smart publishing system
Kubernetes Navigating the Cloud-Native field
Reviewers about the book
Giridhar Reddy Bojja:
“This book is a groundbreaking contribution to the field of cloud-native technologies and container orchestration. The authors have masterfully combined theoretical insights with practical applications, making it an indispensable resource for both novices and seasoned professionals. The detailed explanations and hands-on examples empower readers to confidently deploy, manage, and optimize Kubernetes clusters. This work stands out as a major scholarly contribution, providing deep technical knowledge and practical skills essential for advancing the field of computer science. It is a testament to the authors’ expertise and a must-read for anyone involved in modern cloud infrastructure.”
Vladislav Bilay:
“In an era where cloud-native applications are becoming the backbone of modern IT infrastructure, this book is a significant scholarly work that offers profound insights into Kubernetes. The authors have achieved an exceptional balance between clarity and depth, making complex topics accessible and practical. Through well-structured chapters and comprehensive case studies, readers gain a robust understanding of both self-hosted and PaaS Kubernetes environments. This book not only educates but also inspires innovation, marking a major scientific and technical contribution to the field. It is a critical resource for developers, DevOps engineers, and cloud architects aiming to excel in cloud computing and container orchestration.”
Introduction
This chapter covers:
– Scope and Objective of this Cookbook
In this part, we’ll learn how to use various tools to put your application in a container. Assuming we have an online microservice called auth-app, which handles authorization. We wrote this microservice in Rust. We will begin with Docker, then move on to Podman, and finally, Colima. Also, we will modify our containerized application step by step for the better.
Containerizing with Docker
To start with Docker, you need to have Docker Desktop installed. Use [official website] (https://docs.docker.com/engine/install/) to get it done, then check the Docker version by using this command:
– Why This Book? The Purpose Unveiled
– What You Will Learn
– Which Tasks Does Kubernetes Solve
– The Role of Kubernetes
“The magician’s power comes from being the only one that understands how something works. Learn how it works and they won’t be able to trick you.”
– Kelsey Hightower, ex-Google Cloud’s Principal Developer Advocate
In an era where the IT landscape is rapidly evolving, cloud-native architectures have emerged as the new standard for developing applications. At the heart of this transformation is Kubernetes, a platform that, much like Linux in its heyday, has become the foundational layer upon which countless projects are built. Kubernetes is not merely another tool in the developer’s arsenal; it represents an entire ecosystem teeming with plugins, addons, and tools designed to foster the creation of reliable, scalable, and secure systems. However, the complexity of Kubernetes means that a deep understanding of its inner workings is crucial. Without this knowledge, there’s a tangible risk of not just failure but significant financial and temporal losses.
This book is crafted to demystify Kubernetes, guiding you through its practical application in real-world scenarios while highlighting common pitfalls and how to sidestep them. Our goal is to arm you with the knowledge to not only prevent your company from facing catastrophic failures due to common missteps but also to provide insights into optimizing your Kubernetes infrastructure for both resource management and cost efficiency.
Why This Book? The Purpose Unveiled
If you’re contemplating migrating your projects to Kubernetes or eager to understand how to leverage this technology effectively in the real world, this book is your compass.
The journey to Kubernetes mastery is fraught with questions and challenges:
1. The Learning Curve: While initiating a simple demo may seem straightforward, the operational and troubleshooting aspects of Kubernetes are anything but. Real-world guidance and insights into potential hurdles are invaluable.
2. Navigational Challenges: The Kubernetes ecosystem is vast, offering numerous paths for teams. Determining the most effective route without wasting resources is a common quandary.
3. Resource Optimization: How can you ensure your Kubernetes clusters are as resource-efficient as possible?
4. Avoiding Pitfalls: The fear of “breaking the company” with Kubernetes is real. How do you use it safely?
5. The Infrastructure Puzzle: Kubernetes is not a standalone solution; it requires a suite of additional modules and infrastructure. The necessity of these components often catches teams off guard.
6. Production Challenges: Managing a live production cluster presents its own set of challenges. How do you address these effectively?
7. Developer Access: Not all developers need to know the intricacies of Kubernetes, but they should be able to deploy and manage applications. Simplifying access is crucial.
This book aims to address these and more, drawing from real-world experiences and challenges encountered by DevOps engineers deeply entrenched in the Kubernetes ecosystem.
What You Will Learn
Authored by seasoned DevOps engineers, this book distills years of hands-on experience with Kubernetes into actionable insights.
Here’s what you can expect to gain:
– Practical Application: Understand how to apply Kubernetes in real-world settings, sidestepping common pitfalls and optimizing for cost and efficiency.
– CI/CD and Developer Access: Learn to utilize Kubernetes for continuous integration and delivery, streamline developer interactions with the platform, and manage production issues effectively.
– Choosing the Right Tech Stack: Gain insights into selecting the optimal tools and solutions for your project, beyond just the Kubernetes platform itself.
– Cost Management: Dive into the financial aspects of Kubernetes, learning how to manage your infrastructure with an eye towards high availability and low costs.
– Advanced Concepts: Explore deeper topics such as metrics, logs, tracing, chaos experiments, CI/CD, GitOps, and more, enhancing your Kubernetes mastery.
Accompanied by real-world examples, best practices, and reproducible case studies, this book is your gateway to mastering Kubernetes, enabling you to build robust, scalable, and efficient cloud-native applications.
Which Tasks Does Kubernetes Solve?
– Automating Deployment and Scaling
Kubernetes automates the deployment, scaling, and management of containerized applications. It ensures that the desired state specified by the user is maintained, handling the scheduling and deployment of containers on available nodes, and scaling them up or down based on the demand.
– Load Balancing and Service Discovery
Kubernetes provides built-in solutions for load balancing and service discovery. It can automatically assign IP addresses to containers and a single DNS name for a set of containers, and can load-balance the traffic between them, improving application accessibility and performance.
– Health Monitoring and Self-healing
Kubernetes regularly checks the health of nodes and containers and replaces containers that fail, kill those that don’t respond to user-defined health checks, and doesn’t advertise them to clients until they are ready to serve.
– Automated Rollouts and Rollbacks
Kubernetes enables you to describe the desired state for your deployed containers using deployments and automatically changes the actual state to the desired state at a controlled rate. This means you can easily and safely roll out new code and configuration changes. If something goes wrong, Kubernetes can rollback the change for you.
– Secret and Configuration Management
Kubernetes allows you to store and manage sensitive information such as passwords, OAuth tokens, and SSH keys using Kubernetes secrets. You can deploy and update secrets and application configuration without rebuilding your container images and without exposing secrets in your stack configuration.
– Storage Orchestration
Kubernetes allows you to automatically mount a storage system of your choice, whether from local storage, a public cloud provider, or a network storage system like NFS, iSCSI, etc.
– Resource Management
Kubernetes enables you to allocate specific amounts of CPU and memory (RAM) for each container. It can also limit the resource consumption for a namespace, thus ensuring that one part of your cluster doesn’t monopolize all available resources.
The Role of Kubernetes
In the modern cloud-native ecosystem, characterized by a multitude of services, technologies, and key components, Kubernetes stands out as a unified platform that orchestrates these diverse elements to ensure seamless operation. By abstracting the underlying infrastructure, Kubernetes enables developers to concentrate on building and deploying applications without needing to manage the specifics of the hosting environment. Effectively, Kubernetes operates equally well across cloud systems and on-premises infrastructure, providing versatility in deployment options.
Kubernetes acts as a bridge between developers and infrastructure, offering a common framework and set of protocols. This functionality facilitates a more efficient and coherent interaction between those developing the applications and those managing the infrastructure. Through Kubernetes, the complexities of the infrastructure are masked, allowing developers to deploy applications that are scalable, resilient, and highly available, without needing deep knowledge of the underlying system details.
Getting Started With Kubernetes
This chapter covers
– In-depth exploration of containerization with Docker, Podman, and Colima
– Steps for effective application containerization
– Introduction to Kubernetes and its role in orchestration
– The deployment of applications through a first cluster was created with Minikube
– Best practices and architectural considerations for migrating projects to Kubernetes
– Core components of Kubernetes architecture
– Fundamental concepts such as pods, nodes, and clusters
– Overview of Kubernetes interfaces, including CNI, CSI, and CRI
– Insights into command-line tools and plugins for efficient cluster management
Key Learnings
– Grasp the distinctions between Docker and Kubernetes containers.
– Master effective project migration to Kubernetes.
– Understand the fundamental architecture of Kubernetes.
– Explore the Kubernetes ecosystem and interfaces.
– Develop proficiency in managing Kubernetes using command-line tools.
Recipes:
– Wrap Your Application into a Container
– Deploying Your First Application to Kubernetes
– Use Podman for Kubernetes Migration
– Lightweight Distributions: Setting Up k3s and microk8s
– Enabling Calico CNI in Minikube and Exploring Its Features
– Enhancing Your CLI Cluster Management with Krew: kubectx, kubens, kubetail, kubectl-tree, and kubecolor
Introduction
Welcome to Chapter 2, where we demystify Kubernetes, the cloud-native orchestration platform revolutionizing the deployment and management of containerized applications at scale. We will delve into containerization, starting with Docker and contrasting traditional packaging with containerization’s benefits in the software development lifecycle. Exploring tools like Podman and Colima, we analyze Docker alternatives and enhance container configurations. Moving to Kubernetes, we unveil its orchestration capabilities, introducing Pods, Nodes, Clusters, and Deployments. Practical examples guide you in setting up a Kubernetes Cluster with Minikube, touching on alternatives like K3s and Microk8s. The chapter concludes by highlighting Kubernetes’ extensible plugin ecosystem, empowering you with enhanced kubectl functionalities. By the end, you’ve navigated containerization, mastered Kubernetes essentials, and gained confidence in managing clusters.
Docker and Kubernetes: Understanding Containerization
Traditional Ways to Package Software
Deploying software involves installing both the software itself and its dependencies on a server, coupled with the necessity of appropriate application configuration. This process demands considerable effort, time, and skills and is prone to errors.
To streamline this cumbersome task, engineers have devised solutions such as Ansible, Puppet, or Chef, which automate the installation and configuration of software on servers. These tools adopt a declarative approach to system configuration and management, often emphasizing idempotency as a crucial feature. Another strategy to simplify installation in specific programming languages is to package the application into a single file. For instance, in the Java Runtime Environment (JRE), Java class files can be bundled using JAR files.
Various methods can achieve a similar goal. Options like Omnibus or Homebrew packages offer diverse approaches to creating installers. Omnibus excels in crafting full-stack installers, while Homebrew packages leverage formulae written in Ruby. Alternatively, one can utilize virtual machine snapshots from VirtualBox or VMWare to encapsulate the entire state of the operating system alongside the installed application. Despite their continued use, these solutions exhibit notable limitations compared to Docker.
Containerization
As evidenced in the evolution of application packaging, containerization has emerged as a predominant format, encapsulating only essential libraries and dependencies for software delivery. It utilizes OS-level virtualization to run the code and create a single lightweight executable called a container that runs consistently on any infrastructure.
This OS-level virtualization is a complex subject, and its intricacies are beyond the scope of this book. In essence, it is a technology in which the kernel permits the existence of multiple isolated user-space instances. Each instance is a virtual environment with CPU, memory, block I/O, network, and process space. This mechanism is made possible through various underlying technologies such as filesystem (chroot), namespaces (unshare), and control groups (cgroups).
Understanding Docker
Nowadays, Docker has practically become synonymous with containers, and this reputation is well-deserved. Docker was the first tool to show many users the concept of containers. It made managing container lifecycle, communication, and orchestration easier.
What is Docker?
The term “Docker” encompasses various meanings. At a broad level, Docker refers to a collection of containerization tools, including Docker Desktop and Docker Compose. At a more detailed level, Docker represents a container image format, a container runtime library, and a suite of command-line tools. Additionally, Docker, Inc. handles developing and maintaining these tools. Finally, Docker, Inc. founded the Open Container Initiative (OCI), a critical governance structure for container standards.
Docker Engine vs. Docker Desktop
As of today, Docker, Inc. offers two primary methods to use Docker: Docker Engine and Docker Desktop.
If you have a popular Linux system, you can install Docker Engine. Run the official installation script or use your package manager. Docker Engine installation includes the Docker Daemon (dockerd) and Docker Client (docker). Docker Engine is highly regarded for its user-friendly nature and ease of use.
Docker Desktop helps you use Docker Engine with a graphical interface and useful tools. If you are on macOS or Windows, the exclusive way to use Docker is through Docker Desktop. To use Docker Engine on these operating systems, you need to run it on a Linux virtual machine. You can use tools like VirtualBox, Hyper-V, or Vagrant to manage and set up the VMs.
Docker Desktop itself uses a virtual machine. The virtual machine runs a Linux environment. The Linux environment has Docker Engine as its core component. The choice of virtualization technology depends on the host operating system. It can use Windows Subsystem for Linux (WSL) or Hyper-V on Windows. On MacOS, it may use HyperKit or QEMU. You don’t have to know how Docker Desktop’s virtualization works and use it like Docker Engine.
Exploring Podman
Podman (the POD Manager) is a more recent container engine initially released by RedHat in 2018. Podman differs from Docker because it doesn’t need a separate daemon to run containers. It utilizes the libpod library for running OCI-based containers on Linux. On macOS, each Podman machine is backed by QEMU, and on Windows, by WSL. Unlike Docker, Podman can run rootless containers by default without any prerequisites.
Podman makes it easy to migrate your project to Kubernetes. It is capable of generating manifests and quickly deploying them in your cluster. This chapter will delve deeper into Podman and its migration capabilities.
Colima: The Newcomer
Colima is a relatively new development tool released in 2021. It uses Lima on Linux virtual machines. Lima has containerd runtime with nerdctl installed. Colima adds support for Docker and Kubernetes runtime. Colima’s virtual machines use QEMU with an HVF accelerator. Colima works on MacOS and Linux and is easier to use than Docker Desktop. The good part is that it’s completely free. Yet, it’s important to note that Colima is still in its early stages and has a few limitations.
Docker, Podman, Colima: Distinctions and Considerations
In most cases, you can simply interchange Docker, Podman, and Colima. However, there are some critical distinctions between them.
alias docker=podman
To use Colima, you must install the Docker or Podman command-line tools.
When switching from Docker to Podman, users may face minor problems. Podman has a compatibility mode with Docker, which lets you use the same commands. Yet, caution is crucial when switching between these tools in a production environment.
If you like GUI, you can use Docker Desktop or Podman Desktop. Podman Desktop is a multi-engine tool that is compatible with the APIs of both Docker and Podman. This means you can see all engine containers and images at once.
All the container tools mentioned above can work with Kubernetes, but their support could be better than high-end tools like Rancher, Kind, or Kubespray. The Kubernetes server runs in the container engine. It is less customizable and is designed for single-node setups. So, it is primarily used for local testing purposes.
Recipe: Wrap Your Application into a Container
In this part, we’ll learn how to use various tools to put your application in a container. Assuming we have an online microservice called auth-app, which handles authorization. We wrote this microservice in Rust. We will begin with Docker, then move on to Podman, and finally, Colima. Also, we will modify our containerized application step by step for the better.
Containerizing with Docker
To start with Docker, you need to have Docker Desktop installed. Use [official website] (https://docs.docker.com/engine/install/) to get it done, then check the Docker version by using this command:
docker – version
You should see something like this:
Docker version 20.10.7, build f0df350
We won’t dive deep into our application’s code. You can find it in the GitHub repository. For now, assume that we have the following project structure:
auth-app/
├── src/
│ ├── main.rs
├── Cargo.toml
├──.env
├──.gitignore
├── README.md
The `main.rs` file serves as the entry point for our Rust application. Hypothetically, if we’re about to use it in a non-container environment, we must install all specified Rust dependencies from Cargo.toml. You can do this by using a command with the help of the Cargo. Cargo is the Rust package manager. It is similar to npm in the JavaScript world or pip in the Python world.
cargo build
Then, we can run the application by using the following command:
cargo run
And that’s it. The application will continue because of the Actix framework’s infinite loop until you stop it manually. You can verify this by making a Curl request to the /health endpoint:
curl http://localhost:8000/health
You should see the following response:
{“status”: “OK”}
Running the application in Docker isn’t significantly different. We need a Dockerfile to make an image. Dockerfile is a text document with instructions for the command line. The syntax is straightforward to learn. Let’s create a Dockerfile in the root directory of our project:
FROM rust:1.73-bookworm as builder
WORKDIR /app
COPY..
RUN – mount=type=cache, target=$CARGO_HOME/registry/cache \
cargo build – release – bins
FROM gcr.io/distroless/cc-debian12
ENV RUST_LOG=info
COPY – from=builder /app/target/release/auth-app.
CMD [”. /auth-app”, "-a”, “0.0.0.0”, "-p”, “8080”]
Let’s go through this Dockerfile line by line:
FROM rust:1.73-bookwork as builder
This line tells Docker to use the official Rust image as a base image. The 1.73 tag means using the Debian 12 Bookworm distribution. We also give the base name to the image. We will use it later.
Many base images from various vendors on the Docker Hub public registry exist. You can find any programming language, database, or full-fledged operating system. Anybody can create an image and publish it. You can inherit an image from any other image or make it from scratch.
The vital thing to say is that each “FROM” instruction represents the build stage.
WORKDIR /app
This line sets the working directory for the following instructions. It is like the “cd’ command in the shell. The “WORKDIR” instruction can be used multiple times in a Dockerfile. It will create the directory if it does not exist.
COPY..
This line copies the current directory’s content to the `/app’ directory in the container.
RUN – mount=type=cache, target=$CARGO_HOME/registry/cache \
cargo build – release
This line runs the build command. By default, the Rust origin image includes the Cargo package manager. By Cargo command, we build the static binary of our application in a release mode.
Mounts is a relatively new Docker feature. It allows you to mount various types of volume to the container. In this case, we mount the Cargo cache directory. The persistent cache helps speed up the build steps. If you rebuild a layer, a persistent cache ensures that you only download new or changed packages.
FROM gcr.io/distroless/cc-debian12
Now, we are beginning the second stage of the build process. We use the Distroless Docker image by Google, which has a minimal Linux and glibc runtime. It is designed for mainly statically compiled languages such as Rust. This image is commonly used for creating highly minimal images. We chose to use it to reduce the final image size in which our app will eventually run.
Reducing image size is important because it decreases the time it takes to download and deploy the image. It also reduces the attack surface of the image. The smaller the image, the fewer the number of packages and dependencies it contains. This means there are fewer vulnerabilities to exploit.
ENV RUST_LOG=info
This line sets the environment variable. It is similar to the “export’ command in the shell. We set the “RUST_LOG” variable to the “info’ level. It means that the application will log only information messages.
COPY – from=builder /app/target/release/auth-app.
This line copies the binary from the first build stage to the current directory of the second stage image. We didn’t set “WORKDIR” in the second build stage, so by default, the current directory is the root directory.
CMD [”. /auth-app”, "-a”, “0.0.0.0”, "-p”, “8080”]
This line sets the default command to run when the container starts.
Now, we can build the image by using the following command:
docker build -t auth-app.
The `-t’ flag sets the image tag. The same tag as we placed in the “FROM” instruction in the first build stage. We didn’t put the version after the colon, so Docker automatically builds with the “latest’ tag. The’. ’ at the end of the command means a build’s context. The build process happens inside the build context’s directory.
After the image is built, we can check it by using the following command:
docker images
See the obtained size of our build image, which is compared to a regular Rust image, is much smaller:
REPOSITORY TAG IMAGE ID CREATED SIZE
auth-app latest 94e11dc49c66 2 minutes ago 34.8MB
rust 1.73-bookworm 890a6b209e1c 3 days ago 1.5GB
Now, the time is to create an instance of this image. We call such an instance a container. We can do it by using the following command:
docker run -p 8080:8080 auth-app: latest
The `-p’ flag maps the container port to the host port. The first port is the host port, and the second is the container port. Also, we explicitly specified a tag. However, Docker will pull the “latest’ tag without it by default if not specified. Let’s now request the `/health’ endpoint:
curl http://localhost:8080/health
You should see the following response, meaning that our application is healthy:
{“status”: “OK”}
Containerizing with Podman
To start with Podman, you need to install Podman Desktop. You can download it from the [official website] (https://podman.io/docs/installation). Once you’ve installed it, you can check the Podman version by using this command:
podman – version
You should see the version of Podman:
podman version 4.7.0
Compared to Docker Desktop, Podman requires an additional step from the user to start a virtual machine. You can do it by running the command below:
podman machine start
By default, the Podman machine is configured in rootless mode. So if your container requires root permissions (as an example, you need to get access to privileged ports), you need to change the machine settings to root mode:
podman machine set – rootful
We will run our application in rootless mode, which is more secure. If the “USER” instruction is not specified, the image will run as root by default. It could be a better way to do it, so we need to create a user and group in the container and run the application as this user. So, let’s adjust the second stage in our Dockerfile:
# … (first stage is omitted)
FROM gcr.io/distroless/cc-debian12
ENV RUST_LOG=info
COPY – from=builder /app/target/release/auth-app.
USER nobody: nobody
ENTRYPOINT [”. /auth-app”]
CMD [” -a”, “0.0.0.0”, "-p”, “8080”]
The “nobody’ user is uniquely reserved in Unix-like operating systems. To limit the harm if a process is compromised, it’s common to run daemons or other processes as the “nobody’ user. Also, we added the “ENTRYPOINT” instruction to the Dockerfile. It is like “CMD” but cannot be overridden when running the container.
After that, you can build the image similarly to Docker by using the following statement:
podman build -t auth-app: latest.
Start the container using, overriding predefined “CMD” instruction:
podman run -p 5555:5555 auth-app -a 0.0.0.0 -p 5555
The part after the image name is the “CMD” instruction. It overrides the default command specified in the Dockerfile with a new port. After requesting the `/health’ endpoint with a new port, we should get the same response as with Docker, which will say that our application is healthy.
Containerizing with Colima
To install Colima, you can obtain the latest release from the official [Github repository] (https://github.com/abiosoft/colima), and follow the provided installation guide. Once you’ve installed it, you can check the Colima version by using this command:
colima – version
You should see something like this:
colima version 0.5.6
git commit: ceef812c32ab74a49df9f270e048e5dced85f932
To start Colima machine, you need to use “start’ command:
colima start
This command adds Docker context to your environment. You can use the Docker client (Docker CLI) to interact with the Docker daemon inside the Colima machine. To get the context list, run the following:
docker context ls – format=json
[
{
“Current”: true,
“Description”: “colima”,
“DockerEndpoint”: "unix:///Users/m_muravyev/.colima/default/docker.sock”,
“KubernetesEndpoint”: “”,
“ContextType”: “moby”,
“Name”: “colima”,
“StackOrchestrator”:””
},
{
“Current”: false,
“Description”: “”,
“DockerEndpoint”: "unix:///Users/m_muravyev/.docker/run/docker.sock”,
“KubernetesEndpoint”: “”,
“ContextType”: “moby”,
“Name”: “desktop-linux”,
“StackOrchestrator”:””
}
]
The Colima context is the default one pointing to the Docker daemon inside the Colima machine. The desktop-linux context is the default Docker Desktop context. You can always switch between them.
Building Multi-Architecture Docker Images
Docker, Podman, and Colima support multi-architecture images, a powerful feature. You can create and share container images that work on different hardware types. This section will briefly touch on the concept of multi-arch images and how to make them.
Let’s refresh our memory about computer architecture. The Rust compiler can build the application for different architectures. The default one is the host architecture. For example, if you want to run an application on a modern macOS with an M chip, you must compile it on that machine. That’s because the M chip has “arm64” architecture. This architecture differs from the common “amd64”, which you can find on most regular Windows or Linux systems.
You can use Rust’s cross-compilation feature to compile a project for any architecture. It works on any source host platform, even if the target is different. You need to add simple flags to build up running for Apple’s M chip on a regular Linux machine. No matter what our system is, the Rust compiler will always make M chip compatible binary:
rustup target add aarch64-apple-darwin # add the M chip target tripple
cargo build – release – target aarch64-apple-darwin # build the binary using that target
To build the application for Linux, we can use the target triple “x86_64-unknown-linux-gnu’. Don’t worry about the “unknown’ part. It is just a placeholder for the vendor and operating system. In this case, it just means for any vendor and Linux OS. The “gnu’ part means that the GNU C library is used. It is the most common C language library for Linux.