Kubernetes objects are deployed to Kubernetes clusters using configuration files written in YAML, often referred to as YAML manifests. You specify the "desired state" of objects in a manifest file and send the file to the Kubernetes API server. Kubernetes then automatically configures and manages the application based on your specifications. In this blog post, we'll outline five best practices you should remember while writing Kubernetes YAML manifests. Let's get started!
Kubernetes API versions typically go through three stages:
v1alpha1
). These are experimental features that may be unstable and are disabled by default. Alpha APIs can change without notice and are not recommended for production use. v2beta3
). These are well-tested features, but are disabled by default. Beta features are considered safe to enable but are not recommended for production use as they may still undergo breaking changes. v1
). These are production-ready features that are fully supported, enabled by default, and maintain backwards compatibility.
To leverage these versioned APIs, every Kubernetes object manifest must specify the API version in a field named apiVersion
. This field tells Kubernetes which version of the API to use when processing the manifest.
The apiVersion
field typically consists of two parts: the API group (such as apps
or batch
) and the actual version (such as v1
). Note that for core Kubernetes objects, only the version is specified.
Here's an example manifest for a Deployment object:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.27
In this example, apiVersion
: apps/v1
indicates that this Deployment object uses the v1
version of the apps
API group, which is the latest stable version for Deployments. When creating Kubernetes object manifests, you must always use the latest stable API version available for each object type.
Here’s why:
To find out the latest stable API version for an object on your current Kubernetes cluster, you can use the kubectl api-resources
command. This command queries the Kubernetes API server you're connected to and lists all available resources and their API versions supported by that specific cluster.
In Kubernetes, labels are arbitrary key/value pairs that you can attach to objects. They provide a flexible way to organize and categorize objects and manage them efficiently. Labels are particularly useful when combined with label selectors, which allow you to filter and operate on specific sets of objects.
Consider this example Deployment manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
labels:
app: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:v1
While these labels are valid, they don't fully capture the semantic attributes of the application. In other words, these labels don't convey meaningful characteristics about the application. A better way to label the application would be to add more descriptive labels.
The Kubernetes official documentation recommends a set of common labels that you can apply to your object manifest. These labels, which begin with the prefix app.kubernetes.io/
followed by a separator (/
), provide a standardized way to describe your application's components and improve interoperability with various Kubernetes tools and systems.
Here's what an improved version of the aforementioned Deployment manifest could look like, incorporating these recommended labels:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
labels:
app.kubernetes.io/name: myapp
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: frontend
app.kubernetes.io/part-of: web-application
spec:
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: myapp
template:
metadata:
labels:
app.kubernetes.io/name: myapp
spec:
containers:
- name: myapp
image: myapp:v1.0
These semantic labels provide much more context about the application. They describe not just what the application is, but also its version and its role in the larger system.
The benefits of this semantic labelling approach include:
app.kubernetes.io/
labels enables different Kubernetes tools to work together seamlessly, recognizing and utilizing the same information across various platforms and systems.
By using semantic labels consistently across your Kubernetes objects, you create a more self-documenting system that is easier to understand and manage.
In Kubernetes, annotations are key-value pairs that allow you to attach non-identifying metadata to objects. Typical examples of annotations include build information, release IDs, Git branch names, PR numbers, image hashes, registry information, or team contact details.
Annotations provide a way to examine and understand the objects in the cluster more deeply. This is what the phrase "better introspection" means in the context of Kubernetes annotations.
Here's an example of a Pod with three annotations for commit, author, and branch:
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
annotations:
git.commit: "7a8b9c0d1e2f3g4h5i6j7k8l9m0n1o2p"
git.author: "Sarah Chen <sarah.chen@example.com>"
git.branch: "feature/custom-nginx-config"
spec:
containers:
- name: nginx
image: nginx:1.27
In this example, annotations provide valuable context about the specific version of the NGINX configuration being deployed, which can be extremely useful for debugging, auditing, and managing your Kubernetes Deployments. While these annotations offer useful information for human readers, their power extends far beyond simple documentation.
In fact, annotations are primarily used to provide additional context or configuration information that can be utilized by external tools, automation systems, or client libraries interacting with the Kubernetes API.
For example:
Therefore, always ensure to include descriptive and relevant annotations in your Kubernetes object definitions to enhance manageability and observability.
Secret data consists of sensitive information that should be protected from unauthorized access. This includes passwords, API keys, tokens, and other confidential data. A common mistake in Kubernetes deployments is hardcoding this sensitive information directly into manifest files.
To illustrate this issue, let's examine the following Deployment manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql-deployment
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql-container
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
value: "my-secret-password"
In this manifest, we have hardcoded the value for the root password directly in the environment variable. This exposes the password in plain text, posing significant security risks.
A more secure approach is to store the root password in a Kubernetes Secret object and then reference it in the Deployment. Here's how you can create a Secret:
apiVersion: v1
kind: Secret
metadata:
name: mysql-root-pass
type: Opaque
stringData:
password: mysql_secret_password
Once you've created the Secret, you can reference it in your Deployment manifest by mounting it as an environment variable:
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql-deployment
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql-container
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-root-pass
key: password
Using Secrets in this manner offers several key benefits:
When working with Kubernetes, you often need to create multiple related objects to deploy an application. While it's possible to define each object in a separate file, the following are some scenarios where combining related Kubernetes objects into a single file can be beneficial.
Let's explore this concept with an example. Imagine you want to deploy a backend API server for a simple web application. Traditionally, you might have separate YAML files for this component: backend-deployment.yaml
and backend-service.yaml
.
apiVersion: v1
kind: Pod
metadata:
name: nginx-web-server
labels:
app.kubernetes.io/name: nginx-web-server
spec:
containers:
- name: nginx-web-server
image: nginx:1.27
ports:
- containerPort: 80
name: http-web-svc
apiVersion: v1
kind: Service
metadata:
name: nginx-web-server-service
spec:
selector:
app.kubernetes.io/name: nginx-web-server
ports:
- name: http
protocol: TCP
port: 80
targetPort: http-web-svc
This approach results in two separate files that you need to manage and deploy individually.
Instead, you could combine the two objects into a single backend.yaml
file. This file would contain the Deployment and the Service for your backend, separated by ---
delimiters, like this:
apiVersion: v1
kind: Pod
metadata:
name: nginx-web-server
labels:
app.kubernetes.io/name: nginx-web-server
spec:
containers:
- name: nginx-web-server
image: nginx:1.27
ports:
- containerPort: 80
name: http-web-svc
---
apiVersion: v1
kind: Service
metadata:
name: nginx-web-server-service
spec:
selector:
app.kubernetes.io/name: nginx-web-server
ports:
- name: http
protocol: TCP
port: 80
targetPort: http-web-svc
This combined approach offers several benefits:
kubectl apply -f backend.yaml
command.
However, it's important to note that this approach works best for smaller applications or tightly coupled components. For larger, more complex applications, you might still prefer to keep objects in separate files for better modularity and easier management of individual components.
Adhering to best practices for writing Kubernetes YAML manifests is essential, particularly when collaborating within a team or managing complex applications. By consistently implementing these practices, you not only streamline your workflow but also enhance the clarity, security, and organization of your deployments. You contribute to a more efficient and collaborative development environment, which leads to improved productivity and a faster software development lifecycle.
Additionally, utilizing tools such as the YAML extension for Visual Studio Code can further improve your efficiency when working with YAML manifests. This extension provides features like auto-completion, error highlighting, and snippets, which can help you avoid syntax errors and speed up the file creation process, making it an invaluable tool for any Kubernetes user.
A Kubernetes YAML manifest is a configuration file written in YAML format that defines Kubernetes resources such as Pods, Deployments, Services, and ConfigMaps. It specifies the desired state of an application, including metadata, specifications, and configurations, allowing Kubernetes to manage and deploy workloads efficiently.
An example of a simple Kubernetes manifest YAML file for deploying a basic Nginx pod looks like this:
This file defines a Pod running an Nginx container, exposing port 80.
To create a Kubernetes manifest YAML file, define the required resource specifications using YAML syntax. Start with the apiVersion
, kind
, metadata
, and spec
fields. Save the file with a .yaml
extension and apply it using kubectl apply -f filename.yaml.
Tools like kubectl create --dry-run=client -o yaml
can help generate manifest templates.
The key components of a Kubernetes manifest YAML file include:
These elements work together to instruct Kubernetes on how to deploy and manage the application.
Kubernetes manifests are typically stored in version control systems (e.g., Git) for better collaboration and change tracking. When applied, they are stored in the Kubernetes API server as live resources. Local manifests are usually kept in repositories, deployment directories, or CI/CD pipelines for automated deployments.
Subscribe to our newsletter and stay on top of the latest developments